A "LESS PAIN" lightweight WSGI compatible web framework
Project description
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", "«Previous") or\ ("previous", "<a href=\"%s\" rel=\"prev\">«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> ....... </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> ...... </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»") or\ ("next", "<a href=\"%s\" rel=\"next\">Next»</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()
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file rays-0.4.3.zip
.
File metadata
- Download URL: rays-0.4.3.zip
- Upload date:
- Size: 63.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 465dde63fed7d4629f9abd13b67abe3d32ebc72162f70bd64410721414a4fc17 |
|
MD5 | 82920d7d5b2db154326ba79a42c99de3 |
|
BLAKE2b-256 | f132556ef1b079d858002f90007bea1455070c4af192708253c1e69408910704 |