Metadata-Version: 1.1
Name: rays
Version: 0.4.3
Summary: A "LESS PAIN" lightweight WSGI compatible web framework
Home-page: https://github.com/yuin/rays
Author: Yusuke Inuzuka
Author-email: yuin@inforno.net
License: MIT
Description: Overview
        ===================
        
        * rays is a WSGI compatible web framework designed for small web applications.
        * rays supports python2.6, 2,7, 3.2, 3.3 .
        * rays handles multibyte-charcters correctly(It is important for me, So I'm a Japanese).
        
        Features
        --------
        * Routings: Simple, but powerful.
            * Routes are defined by regular expressions and type constructors::
        
                @app.get("member/(int:\d+)")
                def show_member(member_id):
                  # ...
        
            * ``app.url`` has easy reference to routes::
        
                app.url.show_member(1) #=> "http://somehost/member/1"
        
        * Filters and Hooks: Writing DRY code.
            * Hooks will be called back at following hook points.
                * `before_initialize()`
                * `after_initialize()`
                * `before_call(env, start_response)`
                * `before_dispatch()`
                * `before_action()`
                * `before_start_response()`
                * `after_load_extension(name, extension)`
                * `after_connect_database(database)`
        
            * Hooks example::
        
                @app.hook("before_start_response")
                def status_log_hook():
                  if app.res.is_success:
                    app.logger.info("success")
                  elif app.res.is_abort:
                    app.logger.warn("abort")
                  else:
                    app.logger.error("error:%s"%unicode(app.res.exception))
        
            * Filters enable actions to run pre- and post-processing code::
        
                def filter(*args):
                  # pre-processing
                  yield
                  # post-processing
                
                with app.filter(filter):
                  @app.get("member/(int:\d+)")
                  def show_member(member_id):
                    # ...
        
        * Templates: Fast and flexible.
            * To render ``index.html``, ``app.renderer.index(vars)``.
            * Strings surrounded by "<%" and "%>" will be interpreted as a python code.
                * ``<% a = 10 %>``
            * ``<%= python code %>`` will be replaced by the result of executing "python code".
            * Always applys a filter(i.e. ``cgi.escape``). To turn it off, use ``<%=r python code %>``
            * Many way to express blocks::
        
               <%- for i in xrange(10): -%>
                 <%= a %>
                 <% %>
               
               <%- for i in xrange(10) {: -%>
                 <%= a %>
               <% :} %>
               
               <%- for i in xrange(10) : -%>
                 <%= a %>
               <% end %>
               
            * Integrated useful template helpers::
        
                <% with h.capture("body"): %>
                  foo
                  <% %>
                <%= body %>
        
        * ORMs: Simple wrapper for built-in sqlite3 module.::
        
            result = app.db.select([Site, Page], cond="Page.site_id=Site.id and Page.id = ?", values=[1])
            print(result[0].site)
            print(result[0].page)
            app.db.insert(page)
            app.db.update(page)
            app.db.delete(page)
            app.db.shell() # interactive sqlite3 shell
        
        * Sessions::
        
            @app.get("signin")
            def signin():
              if app.req.input["name"] == "bob" and app.req.input["password"] == "abracadabra":
                app.session.kill()
                app.session["authorized"] = True
              else:
                # ...
        
        * WebSockets: Realtime messaging. ( **requires gevent, greenlet, gevent-websocket** )
            * You can find these source code in the `src/samples/websocketchat`_ directory. ::
        
                @app.get("chat")
                def chat():
                  ws = app.req.websocket
                  SOCKETS.add(ws)
                  app.logger.info("accepts: %s"%repr(ws.socket))
                
                  while True:
                    msg = ws.receive()
                    if msg is None:
                      break
                
                    error_sockets = set([])
                    for s in SOCKETS:
                      try:
                        s.send(msg)
                      except Exception, e:
                        error_sockets.add(s)
                
                    for s in error_sockets:
                      SOCKETS.remove(s)
        
        Asynchronous applications
        ~~~~~~~~~~~~~~~~~~~~~~~~~
        
        (TODO, See `src/samples/asynchronous`_)
        
        Extensions
        -------------------------
        rays has API that allows developers to add new features to their applications.
        This api is consistent with 2 classes: ``rays.ExtensionLoader`` and ``rays.Extension``.
        
        To install your extensions, you need to configure the ``rays.ExtensionLoader``.
        
        index.py::
        
            import extensions
        
            app.config([
              ("ExtensionLoader", {"module": extensions }),
            ])
        
        ``extensions`` is a module that has group of extensions.::
        
            root
            |---- index.py
            |---- extensions
                       |---- __init__.py
                       |---- cache_extension.py
                       |---- template_extension.py
                       .
                       .
                       .
        
        
        Creating your extension
        ~~~~~~~~~~~~~~~~~~~~~~~
        
        (TODO)
        
        
        Requirements
        -------------
        
        * Python 2.6
        * Python 2.7 
        * Python 3.2
        * Python 3.3
        
        Installation
        -------------
        
        ``easy_install rays``
        
        or 
        
        ``pip install -e git://github.com/yuin/rays.git#egg=rays``
        
        or download a zip file from ``https://github.com/yuin/rays/zipball/master`` and ::
        
            python setup.py install
        
        Example
        ------------
        You can find these source code in the `src/samples/blog`_ directory.
        
        index.py::
        
        
            from rays import *
            from rays.compat import *
            import sys, os.path, math, contextlib
            from datetime import datetime
            import threading
            
            app = Application()
            APP_DIR = os.path.dirname(__file__)
            DB_FILE = os.path.join(APP_DIR, "test.db")
            c = threading.local()
            
            app.config([
              ("debug", True),
              ("renderer", {"template_dir":os.path.join(APP_DIR, "templates"),
                            "cache_dir":os.path.join(APP_DIR, "templates/caches")}),
              ("DatabaseExtension", {"connection":DB_FILE, "transaction":"commit_on_success"}),
              ("SessionExtension", {"store":"Database", "secret":"asdfeE305Gs0lg",
                           "cookie_path":"admin"}),
              ("StaticFileExtension", {"url":"statics/", "path": os.path.join(APP_DIR, "statics")}),
              ("admin_name", "admin"),
              ("admin_password", "password"),
              ("blog_title", "My blog"),
              ("entry_per_page", 3),
            ])
            
            class BaseModel(Model): # {{{
              def class_init(cls):
                Model.class_init(cls)
            
                @cls.hook("before_create")
                def before_create(self):
                  self.created_at = datetime.now()
            # }}}
            
            class Entry(BaseModel): #{{{
              table_name = "entries"
              def validate(self):
                result = []
                if not self.title: result.append("Title required.")
                if len(self.title) > 100: result.append("Title too long.")
                if len(self.title) < 2: result.append("Title too short.")
                if not self.body: result.append("Body required.")
                return result
            # }}}
            
            # filters {{{
            def context_setup_filter(*a, **k):
              c.title = app.vars.blog_title
              c.errors = []
              yield
            
            def admin_filter(*a, **k):
              if not app.session["signin"]:
                app.res.redirect(app.url.admin_signin())
              yield
            
            def flash_filter(*a, **k):
              cond = app.session["signin"]
              if cond:
                app.session["flash"] = app.session["flash"] or {}
                keys = list(iter_keys(app.session["flash"]))
              yield
              if cond:
                for key in keys: del app.session["flash"][key]
            # }}}
            
            # helpers {{{
            @app.helper
            @contextlib.contextmanager
            def main_block(helper):
              helper.concat("<div id=\"main\">")
              with helper.capture("__main_block"):
                yield
              helper.concat(helper.captured("__main_block"))
              helper.concat("</div>")
            
            @app.helper
            def show_errors(helper, errors):
              if errors:
                helper.concat("<div class=\"error\"><strong>Error:</strong><ul>")
                for error in errors:
                  helper.concat("<li>"+error+"</li>")
                helper.concat("</ul></div>")
            
            @app.helper
            def show_message(helper, message):
              if message:
                helper.concat("<div class=\"message\">")
                helper.concat(message)
                helper.concat("</div>")
            
            @app.helper
            def format_datetime(helper, dt):
              return dt.strftime("%m.%d.%y/%I%p %Z").lower()
            
            @app.helper
            def hatom_published(helper, entry):
              return """<abbr class="published" title="%s">%s</abbr>"""%(entry.created_at.isoformat(), helper.format_datetime(entry.created_at))
            
            @app.helper
            def format_body(helper, body):
              return body.replace("\n", "<br />")
            
            @app.helper
            def page_link(helper, page):
              return app.url.index()+"?page=%d"%page
            
            @app.helper
            def pagination(helper, count, page):
              page = int(page)
              n = app.vars.entry_per_page
              tpl = ["<ul id=\"pagination\">"]
              append = tpl.append
              max_page = int(math.ceil(count/float(n)))
              if page > max_page: page=1
              start, end = max(page-4, 1), min(page+4, max_page)
              append("<li class=\"%s\">%s</li>"% \
                ((page-1) < 1 and ("previous-off", "&laquo;Previous") or\
                 ("previous", "<a href=\"%s\" rel=\"prev\">&laquo;Previous</a>"%(helper.page_link(c, page-1)))))
              if start != 1: append("<li><a href=\"%s\">1</a></li>"%helper.page_link(c, 1))
              if start > 2:  append("<li>&nbsp;&nbsp;.......&nbsp;&nbsp;</li>")
            
              for i in range(start, end+1):
                if i == page: 
                  append("<li class=\"active\">%d</li>"%i)
                else:
                  append("<li><a href=\"%s\">%d</a></li>"%(helper.page_link(c, i), i))
            
              if end < (max_page-1): append("<li>&nbsp;&nbsp;......&nbsp;&nbsp;</li>")
              if end != max_page: append("<li><a href=\"%s\">%d</a></li>"%(helper.page_link(c, max_page), max_page))
              append("<li class=\"%s\">%s</li>"% \
                ((page+1) > max_page  and ("next-off", "Next&raquo;") or\
                 ("next", "<a href=\"%s\" rel=\"next\">Next&raquo;</a>"%(helper.page_link(c, page+1)))))
            
              append("</ul>")
              return "".join(tpl)
            
            # }}}
            
            # db {{{
            def find_entry_by_id(entry_id):
              return app.db.select_one([Entry], cond="id=?", values=[entry_id])
            
            def find_entries(offset, limit):
              return app.db.select([Entry], 
                cond="1 order by created_at desc limit ? offset ?",
                values=[limit, offset])
            
            def count_entries():
              return app.db.select_one([Entry], select="SELECT count(id) as count from %(tables)s").count
            # }}}
            
            with app.filter(context_setup_filter):
              @app.get("")
              def index():
                limit = app.vars.entry_per_page
                offset = limit*(int(app.req.input.get("page", 1)) - 1)
                c.entries = find_entries(offset, limit)
                c.count   = count_entries()
                return app.renderer.show_entries({"c":c})
            
              @app.get("articles/(int:\d+)")
              def show_entry(entry_id):
                c.entry = find_entry_by_id(entry_id)
                c.title += " :: %s"%c.entry.title
                return app.renderer.show_entry({"c":c})
            
              @app.get("admin/signin")
              def admin_signin_form():
                return app.renderer.admin_signin_form({"c":c})
            
              @app.post("admin/signin")
              def admin_signin():
                if app.req.input["name"] == app.vars.admin_name and \
                    app.req.input["password"] == app.vars.admin_password:
                  app.session["signin"] = True
                  app.res.redirect(app.url.admin_index())
                else:
                  c.errors = ["Signin failed."]
                  return app.renderer.admin_signin_form({"c":c})
            
            
              with app.filter(admin_filter, flash_filter):
                @app.get("admin")
                def admin_index():
                  return app.renderer.admin_index({"c":c})
            
                @app.get("admin/signout")
                def admin_signout():
                  app.session.kill()
                  app.res.redirect(app.url.admin_signin_form())
            
                @app.get("admin/entry/new")
                def admin_entry_new():
                  if not hasattr(c, "entry"):
                    c.entry = Entry(title="", body="")
                  return app.renderer.admin_entry_new({"c":c})
            
                @app.post("admin/entry/create")
                def admin_entry_create():
                  c.entry = Entry(**app.req.input["entry"])
                  c.errors = c.entry.validate()
                  if c.errors:
                    return admin_entry_new(c)
                  app.db.insert(c.entry)
                  app.session["flash"]["message"] = "Entry added."
                  app.res.redirect(app.url.admin_index())
            
            if not os.path.exists(DB_FILE):
              db = app.ext.database.create_new_session()
              db.autocommit = True
              try:
                db.execute(""" CREATE TABLE entries (
                  id INTEGER PRIMARY KEY NOT NULL,
                  title TEXT,
                  body TEXT,
                  created_at TIMESTAMP); """ )
                db.execute(DatabaseSessionStore.SCHEMA)
                db.execute(DatabaseSessionStore.INDEX)
              finally:
                db.close()
            
            if __name__ == "__main__":
              app.serve_forever()
        
        
        .. _`src/samples/websocketchat`: https://github.com/yuin/rays/tree/master/src/samples/websocketchat
        .. _`src/samples/asynchronous`: https://github.com/yuin/rays/tree/master/src/samples/asynchronous
        .. _`src/samples/blog`: https://github.com/yuin/rays/tree/master/src/samples/blog
        
Keywords: WSGI,web,framework,cgi
Platform: Platform Independent
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
