{ "info": { "author": "Yusuke Inuzuka", "author_email": "yuin@inforno.net", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks" ], "description": "Overview\n===================\n\n* rays is a WSGI compatible web framework designed for small web applications.\n* rays supports python2.6, 2,7, 3.2, 3.3 .\n* rays handles multibyte-charcters correctly(It is important for me, So I'm a Japanese).\n\nFeatures\n--------\n* Routings: Simple, but powerful.\n * Routes are defined by regular expressions and type constructors::\n\n @app.get(\"member/(int:\\d+)\")\n def show_member(member_id):\n # ...\n\n * ``app.url`` has easy reference to routes::\n\n app.url.show_member(1) #=> \"http://somehost/member/1\"\n\n* Filters and Hooks: Writing DRY code.\n * Hooks will be called back at following hook points.\n * `before_initialize()`\n * `after_initialize()`\n * `before_call(env, start_response)`\n * `before_dispatch()`\n * `before_action()`\n * `before_start_response()`\n * `after_load_extension(name, extension)`\n * `after_connect_database(database)`\n\n * Hooks example::\n\n @app.hook(\"before_start_response\")\n def status_log_hook():\n if app.res.is_success:\n app.logger.info(\"success\")\n elif app.res.is_abort:\n app.logger.warn(\"abort\")\n else:\n app.logger.error(\"error:%s\"%unicode(app.res.exception))\n\n * Filters enable actions to run pre- and post-processing code::\n\n def filter(*args):\n # pre-processing\n yield\n # post-processing\n \n with app.filter(filter):\n @app.get(\"member/(int:\\d+)\")\n def show_member(member_id):\n # ...\n\n* Templates: Fast and flexible.\n * To render ``index.html``, ``app.renderer.index(vars)``.\n * Strings surrounded by \"<%\" and \"%>\" will be interpreted as a python code.\n * ``<% a = 10 %>``\n * ``<%= python code %>`` will be replaced by the result of executing \"python code\".\n * Always applys a filter(i.e. ``cgi.escape``). To turn it off, use ``<%=r python code %>``\n * Many way to express blocks::\n\n <%- for i in xrange(10): -%>\n <%= a %>\n <% %>\n \n <%- for i in xrange(10) {: -%>\n <%= a %>\n <% :} %>\n \n <%- for i in xrange(10) : -%>\n <%= a %>\n <% end %>\n \n * Integrated useful template helpers::\n\n <% with h.capture(\"body\"): %>\n foo\n <% %>\n <%= body %>\n\n* ORMs: Simple wrapper for built-in sqlite3 module.::\n\n result = app.db.select([Site, Page], cond=\"Page.site_id=Site.id and Page.id = ?\", values=[1])\n print(result[0].site)\n print(result[0].page)\n app.db.insert(page)\n app.db.update(page)\n app.db.delete(page)\n app.db.shell() # interactive sqlite3 shell\n\n* Sessions::\n\n @app.get(\"signin\")\n def signin():\n if app.req.input[\"name\"] == \"bob\" and app.req.input[\"password\"] == \"abracadabra\":\n app.session.kill()\n app.session[\"authorized\"] = True\n else:\n # ...\n\n* WebSockets: Realtime messaging. ( **requires gevent, greenlet, gevent-websocket** )\n * You can find these source code in the `src/samples/websocketchat`_ directory. ::\n\n @app.get(\"chat\")\n def chat():\n ws = app.req.websocket\n SOCKETS.add(ws)\n app.logger.info(\"accepts: %s\"%repr(ws.socket))\n \n while True:\n msg = ws.receive()\n if msg is None:\n break\n \n error_sockets = set([])\n for s in SOCKETS:\n try:\n s.send(msg)\n except Exception, e:\n error_sockets.add(s)\n \n for s in error_sockets:\n SOCKETS.remove(s)\n\nAsynchronous applications\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n(TODO, See `src/samples/asynchronous`_)\n\nExtensions\n-------------------------\nrays has API that allows developers to add new features to their applications.\nThis api is consistent with 2 classes: ``rays.ExtensionLoader`` and ``rays.Extension``.\n\nTo install your extensions, you need to configure the ``rays.ExtensionLoader``.\n\nindex.py::\n\n import extensions\n\n app.config([\n (\"ExtensionLoader\", {\"module\": extensions }),\n ])\n\n``extensions`` is a module that has group of extensions.::\n\n root\n |---- index.py\n |---- extensions\n |---- __init__.py\n |---- cache_extension.py\n |---- template_extension.py\n .\n .\n .\n\n\nCreating your extension\n~~~~~~~~~~~~~~~~~~~~~~~\n\n(TODO)\n\n\nRequirements\n-------------\n\n* Python 2.6\n* Python 2.7 \n* Python 3.2\n* Python 3.3\n\nInstallation\n-------------\n\n``easy_install rays``\n\nor \n\n``pip install -e git://github.com/yuin/rays.git#egg=rays``\n\nor download a zip file from ``https://github.com/yuin/rays/zipball/master`` and ::\n\n python setup.py install\n\nExample\n------------\nYou can find these source code in the `src/samples/blog`_ directory.\n\nindex.py::\n\n\n from rays import *\n from rays.compat import *\n import sys, os.path, math, contextlib\n from datetime import datetime\n import threading\n \n app = Application()\n APP_DIR = os.path.dirname(__file__)\n DB_FILE = os.path.join(APP_DIR, \"test.db\")\n c = threading.local()\n \n app.config([\n (\"debug\", True),\n (\"renderer\", {\"template_dir\":os.path.join(APP_DIR, \"templates\"),\n \"cache_dir\":os.path.join(APP_DIR, \"templates/caches\")}),\n (\"DatabaseExtension\", {\"connection\":DB_FILE, \"transaction\":\"commit_on_success\"}),\n (\"SessionExtension\", {\"store\":\"Database\", \"secret\":\"asdfeE305Gs0lg\",\n \"cookie_path\":\"admin\"}),\n (\"StaticFileExtension\", {\"url\":\"statics/\", \"path\": os.path.join(APP_DIR, \"statics\")}),\n (\"admin_name\", \"admin\"),\n (\"admin_password\", \"password\"),\n (\"blog_title\", \"My blog\"),\n (\"entry_per_page\", 3),\n ])\n \n class BaseModel(Model): # {{{\n def class_init(cls):\n Model.class_init(cls)\n \n @cls.hook(\"before_create\")\n def before_create(self):\n self.created_at = datetime.now()\n # }}}\n \n class Entry(BaseModel): #{{{\n table_name = \"entries\"\n def validate(self):\n result = []\n if not self.title: result.append(\"Title required.\")\n if len(self.title) > 100: result.append(\"Title too long.\")\n if len(self.title) < 2: result.append(\"Title too short.\")\n if not self.body: result.append(\"Body required.\")\n return result\n # }}}\n \n # filters {{{\n def context_setup_filter(*a, **k):\n c.title = app.vars.blog_title\n c.errors = []\n yield\n \n def admin_filter(*a, **k):\n if not app.session[\"signin\"]:\n app.res.redirect(app.url.admin_signin())\n yield\n \n def flash_filter(*a, **k):\n cond = app.session[\"signin\"]\n if cond:\n app.session[\"flash\"] = app.session[\"flash\"] or {}\n keys = list(iter_keys(app.session[\"flash\"]))\n yield\n if cond:\n for key in keys: del app.session[\"flash\"][key]\n # }}}\n \n # helpers {{{\n @app.helper\n @contextlib.contextmanager\n def main_block(helper):\n helper.concat(\"