{ "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(\"
\")\n with helper.capture(\"__main_block\"):\n yield\n helper.concat(helper.captured(\"__main_block\"))\n helper.concat(\"
\")\n \n @app.helper\n def show_errors(helper, errors):\n if errors:\n helper.concat(\"
Error:
\")\n \n @app.helper\n def show_message(helper, message):\n if message:\n helper.concat(\"
\")\n helper.concat(message)\n helper.concat(\"
\")\n \n @app.helper\n def format_datetime(helper, dt):\n return dt.strftime(\"%m.%d.%y/%I%p %Z\").lower()\n \n @app.helper\n def hatom_published(helper, entry):\n return \"\"\"%s\"\"\"%(entry.created_at.isoformat(), helper.format_datetime(entry.created_at))\n \n @app.helper\n def format_body(helper, body):\n return body.replace(\"\\n\", \"
\")\n \n @app.helper\n def page_link(helper, page):\n return app.url.index()+\"?page=%d\"%page\n \n @app.helper\n def pagination(helper, count, page):\n page = int(page)\n n = app.vars.entry_per_page\n tpl = [\"\")\n return \"\".join(tpl)\n \n # }}}\n \n # db {{{\n def find_entry_by_id(entry_id):\n return app.db.select_one([Entry], cond=\"id=?\", values=[entry_id])\n \n def find_entries(offset, limit):\n return app.db.select([Entry], \n cond=\"1 order by created_at desc limit ? offset ?\",\n values=[limit, offset])\n \n def count_entries():\n return app.db.select_one([Entry], select=\"SELECT count(id) as count from %(tables)s\").count\n # }}}\n \n with app.filter(context_setup_filter):\n @app.get(\"\")\n def index():\n limit = app.vars.entry_per_page\n offset = limit*(int(app.req.input.get(\"page\", 1)) - 1)\n c.entries = find_entries(offset, limit)\n c.count = count_entries()\n return app.renderer.show_entries({\"c\":c})\n \n @app.get(\"articles/(int:\\d+)\")\n def show_entry(entry_id):\n c.entry = find_entry_by_id(entry_id)\n c.title += \" :: %s\"%c.entry.title\n return app.renderer.show_entry({\"c\":c})\n \n @app.get(\"admin/signin\")\n def admin_signin_form():\n return app.renderer.admin_signin_form({\"c\":c})\n \n @app.post(\"admin/signin\")\n def admin_signin():\n if app.req.input[\"name\"] == app.vars.admin_name and \\\n app.req.input[\"password\"] == app.vars.admin_password:\n app.session[\"signin\"] = True\n app.res.redirect(app.url.admin_index())\n else:\n c.errors = [\"Signin failed.\"]\n return app.renderer.admin_signin_form({\"c\":c})\n \n \n with app.filter(admin_filter, flash_filter):\n @app.get(\"admin\")\n def admin_index():\n return app.renderer.admin_index({\"c\":c})\n \n @app.get(\"admin/signout\")\n def admin_signout():\n app.session.kill()\n app.res.redirect(app.url.admin_signin_form())\n \n @app.get(\"admin/entry/new\")\n def admin_entry_new():\n if not hasattr(c, \"entry\"):\n c.entry = Entry(title=\"\", body=\"\")\n return app.renderer.admin_entry_new({\"c\":c})\n \n @app.post(\"admin/entry/create\")\n def admin_entry_create():\n c.entry = Entry(**app.req.input[\"entry\"])\n c.errors = c.entry.validate()\n if c.errors:\n return admin_entry_new(c)\n app.db.insert(c.entry)\n app.session[\"flash\"][\"message\"] = \"Entry added.\"\n app.res.redirect(app.url.admin_index())\n \n if not os.path.exists(DB_FILE):\n db = app.ext.database.create_new_session()\n db.autocommit = True\n try:\n db.execute(\"\"\" CREATE TABLE entries (\n id INTEGER PRIMARY KEY NOT NULL,\n title TEXT,\n body TEXT,\n created_at TIMESTAMP); \"\"\" )\n db.execute(DatabaseSessionStore.SCHEMA)\n db.execute(DatabaseSessionStore.INDEX)\n finally:\n db.close()\n \n if __name__ == \"__main__\":\n app.serve_forever()\n\n\n.. _`src/samples/websocketchat`: https://github.com/yuin/rays/tree/master/src/samples/websocketchat\n.. _`src/samples/asynchronous`: https://github.com/yuin/rays/tree/master/src/samples/asynchronous\n.. _`src/samples/blog`: https://github.com/yuin/rays/tree/master/src/samples/blog", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/yuin/rays", "keywords": "WSGI,web,framework,cgi", "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "rays", "package_url": "https://pypi.org/project/rays/", "platform": null, "project_url": "https://pypi.org/project/rays/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/yuin/rays" }, "release_url": "https://pypi.org/project/rays/0.4.3/", "requires_dist": null, "requires_python": null, "summary": "A \"LESS PAIN\" lightweight WSGI compatible web framework", "version": "0.4.3" }, "last_serial": 876858, "releases": { "0.4.0": [ { "comment_text": "", "digests": { "md5": "fe0e39090cfe66990e79a5046ad2d2ca", "sha256": "eed1df1711d38e9ec4d88d299fd468ee89041d7d1d17f466270a7985f4a31468" }, "downloads": -1, "filename": "rays-0.4.0.tar.gz", "has_sig": false, "md5_digest": "fe0e39090cfe66990e79a5046ad2d2ca", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 43772, "upload_time": "2012-04-13T10:12:27", "url": "https://files.pythonhosted.org/packages/b6/ce/77c459f94d4edce596955bb818c0dcf07a6edd46c3abb43a86c03bd090af/rays-0.4.0.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "7a1b9931e30b2250997424097e65f59b", "sha256": "9e904dab9249ec23af8bea264982d74da42cf8a8e10f931543b71e1dde5338e5" }, "downloads": -1, "filename": "rays-0.4.1.tar.gz", "has_sig": false, "md5_digest": "7a1b9931e30b2250997424097e65f59b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49199, "upload_time": "2013-05-10T07:30:51", "url": "https://files.pythonhosted.org/packages/de/2f/c888d91fd2a1e0e8f8d7be282411443f2a936f78d9e9b00b5ee87a35d1d4/rays-0.4.1.tar.gz" } ], "0.4.2": [ { "comment_text": "", "digests": { "md5": "46fd1901b585bf8f937446e553f61624", "sha256": "befaf69cdfdfab1b446116207412cfecce303e7236ac9d92859645dc88dcf19e" }, "downloads": -1, "filename": "rays-0.4.2.zip", "has_sig": false, "md5_digest": "46fd1901b585bf8f937446e553f61624", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 63379, "upload_time": "2013-09-16T08:44:32", "url": "https://files.pythonhosted.org/packages/11/1b/ca4c0f9fbaa6ca0c683d2ea8318446f2d6dada4fdf8e9de7ed71bb86b205/rays-0.4.2.zip" } ], "0.4.3": [ { "comment_text": "", "digests": { "md5": "82920d7d5b2db154326ba79a42c99de3", "sha256": "465dde63fed7d4629f9abd13b67abe3d32ebc72162f70bd64410721414a4fc17" }, "downloads": -1, "filename": "rays-0.4.3.zip", "has_sig": false, "md5_digest": "82920d7d5b2db154326ba79a42c99de3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 63436, "upload_time": "2013-09-30T10:40:01", "url": "https://files.pythonhosted.org/packages/f1/32/556ef1b079d858002f90007bea1455070c4af192708253c1e69408910704/rays-0.4.3.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "82920d7d5b2db154326ba79a42c99de3", "sha256": "465dde63fed7d4629f9abd13b67abe3d32ebc72162f70bd64410721414a4fc17" }, "downloads": -1, "filename": "rays-0.4.3.zip", "has_sig": false, "md5_digest": "82920d7d5b2db154326ba79a42c99de3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 63436, "upload_time": "2013-09-30T10:40:01", "url": "https://files.pythonhosted.org/packages/f1/32/556ef1b079d858002f90007bea1455070c4af192708253c1e69408910704/rays-0.4.3.zip" } ] }