From 1671f0a2fac8a1ee3716d400ae5879673bc92743 Mon Sep 17 00:00:00 2001 From: Adeel Ahmad Khan Date: Tue, 26 Jul 2011 01:25:23 -0400 Subject: [PATCH] add docs --- docs/.html | 0 docs/__init__.html | 52 ++++++++ docs/flash.html | 111 ++++++++++++++++ docs/jinja.html | 115 +++++++++++++++++ docs/middleware/__init__.html | 35 +++++ docs/middleware/flash.html | 111 ++++++++++++++++ docs/middleware/jinja.html | 124 ++++++++++++++++++ docs/middleware/not_found.html | 58 +++++++++ docs/middleware/pycco.css | 188 +++++++++++++++++++++++++++ docs/not_found.html | 58 +++++++++ docs/pycco.css | 188 +++++++++++++++++++++++++++ docs/response.html | 94 ++++++++++++++ docs/routing.html | 220 ++++++++++++++++++++++++++++++++ docs/setup.html | 86 +++++++++++++ picasso/__init__.py | 6 +- picasso/middleware/flash.py | 19 ++- picasso/middleware/jinja.py | 40 +++--- picasso/middleware/not_found.py | 11 +- picasso/response.py | 7 +- picasso/routing.py | 73 +++++------ picasso/setup.py | 39 +++--- 21 files changed, 1529 insertions(+), 106 deletions(-) create mode 100644 docs/.html create mode 100644 docs/__init__.html create mode 100644 docs/flash.html create mode 100644 docs/jinja.html create mode 100644 docs/middleware/__init__.html create mode 100644 docs/middleware/flash.html create mode 100644 docs/middleware/jinja.html create mode 100644 docs/middleware/not_found.html create mode 100644 docs/middleware/pycco.css create mode 100644 docs/not_found.html create mode 100644 docs/pycco.css create mode 100644 docs/response.html create mode 100644 docs/routing.html create mode 100644 docs/setup.html diff --git a/docs/.html b/docs/.html new file mode 100644 index 0000000..e69de29 diff --git a/docs/__init__.html b/docs/__init__.html new file mode 100644 index 0000000..9bfcd36 --- /dev/null +++ b/docs/__init__.html @@ -0,0 +1,52 @@ + + + + + __init__.py + + + +
+
+ + + + + + + + + + + + + + + + +

__init__.py

+
+ # +
+ +
+
import pack
+
+from setup import setup_api, setup_app
+from routing import GET, POST, PUT, DELETE, HEAD, ANY
+
+from pack.util.response import redirect, file_response
+
+
+ # +
+

Create a Pack app from a list of compiled routes (see +routing._compile_route).

+
+
def setup_routes(*routes):
+  return lambda req: routing.route(req, *routes)
+
+
+
+
+ diff --git a/docs/flash.html b/docs/flash.html new file mode 100644 index 0000000..d1b33f0 --- /dev/null +++ b/docs/flash.html @@ -0,0 +1,111 @@ + + + + + flash.py + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

flash.py

+
+ # +
+

A middleware for adding flash message support via sessions.

+
+
+
+
+ # +
+

Adds a "flash" key to the request that contains the current flash dict, which +is stored in the session (request["session"]["flash"]). If it is modified by +the app, the session is updated accordingly.

+

Note that request["flash"] is actually a dict-like object whose values +disappear after the first access.

+
+
def wrap_flash(app):
+
+
+ # +
+ +
+
  def wrapped(request):
+    request["flash"] = DisappearingDict(request["session"].get("flash", {}))
+    response = app(request)
+    request["session"]["flash"] = request["flash"]
+    return response
+  return wrapped
+
+
+ # +
+

Like a dict, but values disappear after the first access.

+
+
class DisappearingDict(dict):
+
+
+ # +
+ +
+
  def __getitem__(self, key):
+    return self._get_and_delete(key)
+
+
+ # +
+ +
+
  def get(self, key, d=None):
+    if self.has_key(key):
+      return self._get_and_delete(key)
+    return d
+
+
+ # +
+ +
+
  def _get_and_delete(self, key):
+    val = dict.__getitem__(self, key)
+    self.__delitem__(key)
+    return val
+
+
+
+
+ diff --git a/docs/jinja.html b/docs/jinja.html new file mode 100644 index 0000000..39d5eda --- /dev/null +++ b/docs/jinja.html @@ -0,0 +1,115 @@ + + + + + jinja.py + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

jinja.py

+
+ # +
+

A middleware that uses the Jinja templating system to render responses.

+
+
import os
+from jinja2 import Environment, FileSystemLoader
+
+
+ # +
+

If the app response is a tuple, will use Jinja2 to render the response. The +first element of the tuple is parsed as the template name, and the second +should be a dictionary of data to be passed to the template.

+

def view_product(req, id): + # product = ... + return "products/view", {"product": product}

+

If sessions are enabled, a "session" key will be added to the data and sent +to the template.

+

Takes a template_dir option, which should be the path to your templates, and +a config option, which can be a dict that will be passed to all your views.

+
+
def wrap_jinja(app, options={}):
+  options = dict({"template_dir": "./", "config": {}}, **options)
+
+  template_dir = options["template_dir"]
+  if not os.path.isdir(template_dir):
+    raise Exception("Directory does not exist: %s" % template_dir)
+
+
+ # +
+ +
+
  def wrapped(request):
+    response = app(request)
+    if isinstance(response.get("body"), tuple):
+      if len(response["body"]) != 2:
+        raise Exception(
+          "picasso.middleware.jinja expected response to be a 2-element tuple,"
+          " but got:\n  %s" % str(response["body"]))
+
+      response["body"] = _render_with_jinja(options["template_dir"],
+        *response["body"], session=request.get("session"),
+                           flash=request.get("flash"),
+                           config=options.get("config"))
+      return response
+    else:
+      return response
+  return wrapped
+
+
+ # +
+

Looks for the template in template_dir, and passes data to it. Also adds a +session key to the data, if sessions are enabled, and a flash key if the +flash middleware is being used.

+
+
def _render_with_jinja(template_dir, template, data, session={}, flash={},
+                       config={}):
+  env = Environment(loader=FileSystemLoader(template_dir))
+  data = dict(data, **config)
+
+
+ # +
+

Add session key to data, if sessions are enabled.

+
+
  if session is not None:
+    data["session"] = session
+  if flash is not None:
+    data["flash"] = flash
+
+  return env.get_template(template + '.html').render(data).encode('utf-8')
+
+
+
+
+ diff --git a/docs/middleware/__init__.html b/docs/middleware/__init__.html new file mode 100644 index 0000000..78926e8 --- /dev/null +++ b/docs/middleware/__init__.html @@ -0,0 +1,35 @@ + + + + + __init__.py + + + +
+
+ + + + + + + + + + + + + +

__init__.py

+
+ # +
+ +
+
import jinja
+
+
+
+
+ diff --git a/docs/middleware/flash.html b/docs/middleware/flash.html new file mode 100644 index 0000000..d1b33f0 --- /dev/null +++ b/docs/middleware/flash.html @@ -0,0 +1,111 @@ + + + + + flash.py + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

flash.py

+
+ # +
+

A middleware for adding flash message support via sessions.

+
+
+
+
+ # +
+

Adds a "flash" key to the request that contains the current flash dict, which +is stored in the session (request["session"]["flash"]). If it is modified by +the app, the session is updated accordingly.

+

Note that request["flash"] is actually a dict-like object whose values +disappear after the first access.

+
+
def wrap_flash(app):
+
+
+ # +
+ +
+
  def wrapped(request):
+    request["flash"] = DisappearingDict(request["session"].get("flash", {}))
+    response = app(request)
+    request["session"]["flash"] = request["flash"]
+    return response
+  return wrapped
+
+
+ # +
+

Like a dict, but values disappear after the first access.

+
+
class DisappearingDict(dict):
+
+
+ # +
+ +
+
  def __getitem__(self, key):
+    return self._get_and_delete(key)
+
+
+ # +
+ +
+
  def get(self, key, d=None):
+    if self.has_key(key):
+      return self._get_and_delete(key)
+    return d
+
+
+ # +
+ +
+
  def _get_and_delete(self, key):
+    val = dict.__getitem__(self, key)
+    self.__delitem__(key)
+    return val
+
+
+
+
+ diff --git a/docs/middleware/jinja.html b/docs/middleware/jinja.html new file mode 100644 index 0000000..b7a131e --- /dev/null +++ b/docs/middleware/jinja.html @@ -0,0 +1,124 @@ + + + + + jinja.py + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

jinja.py

+
+ # +
+

A middleware that uses the Jinja templating system to render responses.

+
+
import os
+from jinja2 import Environment, FileSystemLoader
+
+
+ # +
+

If the app response is a tuple, will use Jinja2 to render the response. The +first element of the tuple is parsed as the template name, and the second +should be a dictionary of data to be passed to the template.

+
def view_product(req, id):
+  # product = ...
+  return "products/view", {"product": product}
+
+

If sessions are enabled, a "session" key will be added to the data and sent +to the template.

+

Takes a template_dir option, which should be the path to your templates, and +a config option, which can be a dict that will be passed to all your views.

+
+
def wrap_jinja(app, options={}):
+  options = dict({"template_dir": "./", "config": {}}, **options)
+
+  template_dir = options["template_dir"]
+  if not os.path.isdir(template_dir):
+    raise Exception("Directory does not exist: %s" % template_dir)
+
+
+ # +
+ +
+
  def wrapped(request):
+    response = app(request)
+    if isinstance(response.get("body"), tuple):
+      if len(response["body"]) != 2:
+        raise Exception(
+          "picasso.middleware.jinja expected response to be a 2-element tuple,"
+          " but got:\n  %s" % str(response["body"]))
+
+      response["body"] = _render_with_jinja(options["template_dir"],
+        *response["body"], session=request.get("session"),
+                           flash=request.get("flash"),
+                           config=options.get("config"))
+      return response
+    else:
+      return response
+  return wrapped
+
+
+ # +
+

Looks for the template in template_dir, and passes data to it. Also adds a +session key to the data, if sessions are enabled, and a flash key if the +flash middleware is being used.

+
+
def _render_with_jinja(template_dir, template, data, session={}, flash={},
+                       config={}):
+  env = Environment(loader=FileSystemLoader(template_dir))
+  data = dict(data, **config)
+
+
+ # +
+

Add session key to data, if sessions are enabled.

+
+
  if session is not None:
+    data["session"] = session
+  if flash is not None:
+    data["flash"] = flash
+
+
+ # +
+

Render the template.

+
+
  return env.get_template(template + '.html').render(data).encode('utf-8')
+
+
+
+
+ diff --git a/docs/middleware/not_found.html b/docs/middleware/not_found.html new file mode 100644 index 0000000..3e822ed --- /dev/null +++ b/docs/middleware/not_found.html @@ -0,0 +1,58 @@ + + + + + not_found.py + + + +
+
+ + + + + + + + + + + + + + + + + + + +

not_found.py

+
+ # +
+

A middleware that turns a None response into a 404 Not Found response.

+
+
from pack.util.response import with_status, with_body
+
+
+ # +
+

If none of the given routes match, the response will be None. This +will turn a None response into a 404 Not Found response, and is intended to +be the outermost middleware.

+
+
+
+
+ # +
+ +
+
def wrap_not_found(app):
+  return lambda req: app(req) or with_status(with_body("Not Found"), 404)
+
+
+
+
+ diff --git a/docs/middleware/pycco.css b/docs/middleware/pycco.css new file mode 100644 index 0000000..73a83ac --- /dev/null +++ b/docs/middleware/pycco.css @@ -0,0 +1,188 @@ +/*--------------------- Layout and Typography ----------------------------*/ +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 16px; + line-height: 24px; + color: #252519; + margin: 0; padding: 0; +} +a { + color: #261a3b; +} + a:visited { + color: #261a3b; + } +p { + margin: 0 0 15px 0; +} +h1, h2, h3, h4, h5, h6 { + margin: 40px 0 15px 0; +} +h2, h3, h4, h5, h6 { + margin-top: 0; + } +#container { + position: relative; +} +#background { + position: fixed; + top: 0; left: 580px; right: 0; bottom: 0; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + z-index: -1; +} +#jump_to, #jump_page { + background: white; + -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; + -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; + font: 10px Arial; + text-transform: uppercase; + cursor: pointer; + text-align: right; +} +#jump_to, #jump_wrapper { + position: fixed; + right: 0; top: 0; + padding: 5px 10px; +} + #jump_wrapper { + padding: 0; + display: none; + } + #jump_to:hover #jump_wrapper { + display: block; + } + #jump_page { + padding: 5px 0 3px; + margin: 0 0 25px 25px; + } + #jump_page .source { + display: block; + padding: 5px 10px; + text-decoration: none; + border-top: 1px solid #eee; + } + #jump_page .source:hover { + background: #f5f5ff; + } + #jump_page .source:first-child { + } +table td { + border: 0; + outline: 0; +} + td.docs, th.docs { + max-width: 500px; + min-width: 500px; + min-height: 5px; + padding: 10px 25px 1px 50px; + vertical-align: top; + text-align: left; + } + .docs pre { + margin: 15px 0 15px; + padding-left: 15px; + } + .docs p tt, .docs p code { + background: #f8f8ff; + border: 1px solid #dedede; + font-size: 12px; + padding: 0 0.2em; + } + .octowrap { + position: relative; + } + .octothorpe { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + top: 3px; left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + } + td.docs:hover .octothorpe { + opacity: 1; + } + td.code, th.code { + padding: 14px 15px 16px 50px; + width: 100%; + vertical-align: top; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + } +.code pre, .docs p code { + font-size: 12px; +} + pre, tt, code { + line-height: 18px; + font-family: Monaco, Consolas, "Lucida Console", monospace; + margin: 0; padding: 0; + } + + +/*---------------------- Syntax Highlighting -----------------------------*/ +td.linenos { background-color: #f0f0f0; padding-right: 10px; } +span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } +body .hll { background-color: #ffffcc } +body .c { color: #408080; font-style: italic } /* Comment */ +body .err { border: 1px solid #FF0000 } /* Error */ +body .k { color: #954121 } /* Keyword */ +body .o { color: #666666 } /* Operator */ +body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +body .cp { color: #BC7A00 } /* Comment.Preproc */ +body .c1 { color: #408080; font-style: italic } /* Comment.Single */ +body .cs { color: #408080; font-style: italic } /* Comment.Special */ +body .gd { color: #A00000 } /* Generic.Deleted */ +body .ge { font-style: italic } /* Generic.Emph */ +body .gr { color: #FF0000 } /* Generic.Error */ +body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +body .gi { color: #00A000 } /* Generic.Inserted */ +body .go { color: #808080 } /* Generic.Output */ +body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +body .gs { font-weight: bold } /* Generic.Strong */ +body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +body .gt { color: #0040D0 } /* Generic.Traceback */ +body .kc { color: #954121 } /* Keyword.Constant */ +body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ +body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ +body .kp { color: #954121 } /* Keyword.Pseudo */ +body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ +body .kt { color: #B00040 } /* Keyword.Type */ +body .m { color: #666666 } /* Literal.Number */ +body .s { color: #219161 } /* Literal.String */ +body .na { color: #7D9029 } /* Name.Attribute */ +body .nb { color: #954121 } /* Name.Builtin */ +body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +body .no { color: #880000 } /* Name.Constant */ +body .nd { color: #AA22FF } /* Name.Decorator */ +body .ni { color: #999999; font-weight: bold } /* Name.Entity */ +body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +body .nf { color: #0000FF } /* Name.Function */ +body .nl { color: #A0A000 } /* Name.Label */ +body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +body .nt { color: #954121; font-weight: bold } /* Name.Tag */ +body .nv { color: #19469D } /* Name.Variable */ +body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +body .w { color: #bbbbbb } /* Text.Whitespace */ +body .mf { color: #666666 } /* Literal.Number.Float */ +body .mh { color: #666666 } /* Literal.Number.Hex */ +body .mi { color: #666666 } /* Literal.Number.Integer */ +body .mo { color: #666666 } /* Literal.Number.Oct */ +body .sb { color: #219161 } /* Literal.String.Backtick */ +body .sc { color: #219161 } /* Literal.String.Char */ +body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ +body .s2 { color: #219161 } /* Literal.String.Double */ +body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +body .sh { color: #219161 } /* Literal.String.Heredoc */ +body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +body .sx { color: #954121 } /* Literal.String.Other */ +body .sr { color: #BB6688 } /* Literal.String.Regex */ +body .s1 { color: #219161 } /* Literal.String.Single */ +body .ss { color: #19469D } /* Literal.String.Symbol */ +body .bp { color: #954121 } /* Name.Builtin.Pseudo */ +body .vc { color: #19469D } /* Name.Variable.Class */ +body .vg { color: #19469D } /* Name.Variable.Global */ +body .vi { color: #19469D } /* Name.Variable.Instance */ +body .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/docs/not_found.html b/docs/not_found.html new file mode 100644 index 0000000..3e822ed --- /dev/null +++ b/docs/not_found.html @@ -0,0 +1,58 @@ + + + + + not_found.py + + + +
+
+ + + + + + + + + + + + + + + + + + + +

not_found.py

+
+ # +
+

A middleware that turns a None response into a 404 Not Found response.

+
+
from pack.util.response import with_status, with_body
+
+
+ # +
+

If none of the given routes match, the response will be None. This +will turn a None response into a 404 Not Found response, and is intended to +be the outermost middleware.

+
+
+
+
+ # +
+ +
+
def wrap_not_found(app):
+  return lambda req: app(req) or with_status(with_body("Not Found"), 404)
+
+
+
+
+ diff --git a/docs/pycco.css b/docs/pycco.css new file mode 100644 index 0000000..73a83ac --- /dev/null +++ b/docs/pycco.css @@ -0,0 +1,188 @@ +/*--------------------- Layout and Typography ----------------------------*/ +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 16px; + line-height: 24px; + color: #252519; + margin: 0; padding: 0; +} +a { + color: #261a3b; +} + a:visited { + color: #261a3b; + } +p { + margin: 0 0 15px 0; +} +h1, h2, h3, h4, h5, h6 { + margin: 40px 0 15px 0; +} +h2, h3, h4, h5, h6 { + margin-top: 0; + } +#container { + position: relative; +} +#background { + position: fixed; + top: 0; left: 580px; right: 0; bottom: 0; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + z-index: -1; +} +#jump_to, #jump_page { + background: white; + -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; + -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; + font: 10px Arial; + text-transform: uppercase; + cursor: pointer; + text-align: right; +} +#jump_to, #jump_wrapper { + position: fixed; + right: 0; top: 0; + padding: 5px 10px; +} + #jump_wrapper { + padding: 0; + display: none; + } + #jump_to:hover #jump_wrapper { + display: block; + } + #jump_page { + padding: 5px 0 3px; + margin: 0 0 25px 25px; + } + #jump_page .source { + display: block; + padding: 5px 10px; + text-decoration: none; + border-top: 1px solid #eee; + } + #jump_page .source:hover { + background: #f5f5ff; + } + #jump_page .source:first-child { + } +table td { + border: 0; + outline: 0; +} + td.docs, th.docs { + max-width: 500px; + min-width: 500px; + min-height: 5px; + padding: 10px 25px 1px 50px; + vertical-align: top; + text-align: left; + } + .docs pre { + margin: 15px 0 15px; + padding-left: 15px; + } + .docs p tt, .docs p code { + background: #f8f8ff; + border: 1px solid #dedede; + font-size: 12px; + padding: 0 0.2em; + } + .octowrap { + position: relative; + } + .octothorpe { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + top: 3px; left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + } + td.docs:hover .octothorpe { + opacity: 1; + } + td.code, th.code { + padding: 14px 15px 16px 50px; + width: 100%; + vertical-align: top; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + } +.code pre, .docs p code { + font-size: 12px; +} + pre, tt, code { + line-height: 18px; + font-family: Monaco, Consolas, "Lucida Console", monospace; + margin: 0; padding: 0; + } + + +/*---------------------- Syntax Highlighting -----------------------------*/ +td.linenos { background-color: #f0f0f0; padding-right: 10px; } +span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } +body .hll { background-color: #ffffcc } +body .c { color: #408080; font-style: italic } /* Comment */ +body .err { border: 1px solid #FF0000 } /* Error */ +body .k { color: #954121 } /* Keyword */ +body .o { color: #666666 } /* Operator */ +body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +body .cp { color: #BC7A00 } /* Comment.Preproc */ +body .c1 { color: #408080; font-style: italic } /* Comment.Single */ +body .cs { color: #408080; font-style: italic } /* Comment.Special */ +body .gd { color: #A00000 } /* Generic.Deleted */ +body .ge { font-style: italic } /* Generic.Emph */ +body .gr { color: #FF0000 } /* Generic.Error */ +body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +body .gi { color: #00A000 } /* Generic.Inserted */ +body .go { color: #808080 } /* Generic.Output */ +body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +body .gs { font-weight: bold } /* Generic.Strong */ +body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +body .gt { color: #0040D0 } /* Generic.Traceback */ +body .kc { color: #954121 } /* Keyword.Constant */ +body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ +body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ +body .kp { color: #954121 } /* Keyword.Pseudo */ +body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ +body .kt { color: #B00040 } /* Keyword.Type */ +body .m { color: #666666 } /* Literal.Number */ +body .s { color: #219161 } /* Literal.String */ +body .na { color: #7D9029 } /* Name.Attribute */ +body .nb { color: #954121 } /* Name.Builtin */ +body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +body .no { color: #880000 } /* Name.Constant */ +body .nd { color: #AA22FF } /* Name.Decorator */ +body .ni { color: #999999; font-weight: bold } /* Name.Entity */ +body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +body .nf { color: #0000FF } /* Name.Function */ +body .nl { color: #A0A000 } /* Name.Label */ +body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +body .nt { color: #954121; font-weight: bold } /* Name.Tag */ +body .nv { color: #19469D } /* Name.Variable */ +body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +body .w { color: #bbbbbb } /* Text.Whitespace */ +body .mf { color: #666666 } /* Literal.Number.Float */ +body .mh { color: #666666 } /* Literal.Number.Hex */ +body .mi { color: #666666 } /* Literal.Number.Integer */ +body .mo { color: #666666 } /* Literal.Number.Oct */ +body .sb { color: #219161 } /* Literal.String.Backtick */ +body .sc { color: #219161 } /* Literal.String.Char */ +body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ +body .s2 { color: #219161 } /* Literal.String.Double */ +body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +body .sh { color: #219161 } /* Literal.String.Heredoc */ +body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +body .sx { color: #954121 } /* Literal.String.Other */ +body .sr { color: #BB6688 } /* Literal.String.Regex */ +body .s1 { color: #219161 } /* Literal.String.Single */ +body .ss { color: #19469D } /* Literal.String.Symbol */ +body .bp { color: #954121 } /* Name.Builtin.Pseudo */ +body .vc { color: #19469D } /* Name.Variable.Class */ +body .vg { color: #19469D } /* Name.Variable.Global */ +body .vi { color: #19469D } /* Name.Variable.Instance */ +body .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/docs/response.html b/docs/response.html new file mode 100644 index 0000000..d8cbd3b --- /dev/null +++ b/docs/response.html @@ -0,0 +1,94 @@ + + + + + response.py + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

response.py

+
+ # +
+

Generating Pack responses.

+
+
import collections
+from pack.util.response import with_body, with_content_type
+
+
+ # +
+

Render the object into a Pack response suitable for the given Pack request.

+
+
def render(response, request):
+  if not response:
+    return
+
+  if not isinstance(response, dict):
+
+
+ # +
+

If the response is a function, call it and render the result.

+
+
    if callable(response):
+      return render(
+        response(request, **request.get("route_params", {})), request)
+    response = with_body(response)
+
+  response["body"] = _render_body(response.get("body"), request)
+
+
+ # +
+

Default content_type is text/html.

+
+
  if not response.get("headers", {}).get("content_type"):
+    response = with_content_type(response, "text/html")
+
+  return response
+
+
+ # +
+ +
+
def _render_body(obj, request):
+  if isinstance(obj, str) or isinstance(obj, unicode):
+    return obj
+  if isinstance(obj, file):
+    return obj
+  if isinstance(obj, collections.Iterable):
+    return obj
+
+
+
+
+ diff --git a/docs/routing.html b/docs/routing.html new file mode 100644 index 0000000..ea4ec44 --- /dev/null +++ b/docs/routing.html @@ -0,0 +1,220 @@ + + + + + routing.py + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

routing.py

+
+ # +
+ +
+
import collections
+
+from pack.util.response import with_body, with_status
+from routes.base import Route
+from routes.mapper import Mapper as RouteMapper
+
+from picasso import response
+
+
+ # +
+

Try to apply each route to the given request, returning the first nonempty +response. The routes need to have been compiled to functions with +_compile_route.

+
+
def route(request, *routes):
+  for route in routes:
+    response = route(request.copy())
+    if response:
+      return response
+
+
+ # +
+

Functions for each method that generate routes from a path and response. +Supported methods: GET, POST, PUT, DELETE, and HEAD. +ANY will match any method.

+
+
methods = ["get", "post", "put", "delete", "head", "any"]
+GET, POST, PUT, DELETE, HEAD, ANY = [lambda p, b, m=m:
+  compile_route(m, p, b) for m in methods]
+
+
+ # +
+

Generates a route that matches any URL and returns the given body. +You can add it as your last route to get a custom 404 page.

+
+
def not_found(body):
+  return ANY('/{url:.*}', body)
+
+
+ # +
+

Compile the route into a Pack app that will return the body given if and +only if a matching route is found. Otherwise it will return None.

+
+
def compile_route(method, path, body):
+
+
+ # +
+ +
+
  def app(request):
+    return response.render(body, request)
+  return if_method(method, if_route(prepare_route(path), app))
+
+
+ # +
+

Call the app if the method matches the request.

+
+
def if_method(method, app):
+
+
+ # +
+ +
+
  def wrapped(request):
+    if not method or method == request.get("method") or method == "any":
+      return app(request)
+    elif method == "get" and request.get("method") == "head":
+      response = app(request)
+      if response:
+        response["body"] = None
+      return response
+  return wrapped
+
+
+ # +
+

Call the app if the route matches the request.

+
+
def if_route(route, app):
+
+
+ # +
+ +
+
  def wrapped(request):
+    params = route_matches(route, request)
+    if params is not None:
+      return app(set_route_params(request, params))
+  return wrapped
+
+
+ # +
+

Set the "route_params" and "params" keys in the request.

+
+
def set_route_params(request, params):
+  return recursive_merge(request, {"route_params": params, "params": params})
+
+
+ # +
+

Return the params of the route matching the request, if there is one.

+
+
def route_matches(route, request):
+  m = RouteMapper()
+  m.extend([route])
+  return m.match(request["uri"])
+
+
+ # +
+

Returns an instance of routes.base.Route (from the Routes package). If +route is a tuple, the first item should be a route and the second should be +a dict with conditions on the params in the route.

+
prepare_route("products/view/:id")
+prepare_route(("products/view/:id", {"id": r"\d+"}))
+
+
+
def prepare_route(route):
+  if isinstance(route, str) or isinstance(route, unicode):
+    return Route(None, route)
+  if isinstance(route, collections.Iterable):
+    return Route(None, route[0], requirements=route[1])
+
+
+ # +
+

Merge two dictionaries recursively.

+
+
def recursive_merge(x, y):
+  z = x.copy()
+  for key, val in y.iteritems():
+    if isinstance(val, dict):
+      if not z.has_key(key):
+        z[key] = {}
+      z[key] = _recursive_merge(z[key], val)
+    else:
+      z[key] = val
+  return z
+
+
+
+
+ diff --git a/docs/setup.html b/docs/setup.html new file mode 100644 index 0000000..e93ebef --- /dev/null +++ b/docs/setup.html @@ -0,0 +1,86 @@ + + + + + setup.py + + + +
+
+ + + + + + + + + + + + + + + + + + + +

setup.py

+
+ # +
+

Some functions to create Pack apps from routes.

+
+
from pack.middleware.params import *
+from pack.middleware.nested_params import *
+from pack.middleware.cookies import *
+from pack.middleware.session import *
+from picasso.middleware.jinja import *
+from picasso.middleware.flash import *
+from picasso.middleware.not_found import *
+
+
+ # +
+

Generates a Pack app from the given routes that can be used for creating +a web API. Adds the following middleware:

+
    +
  • params
  • +
  • nested_params
  • +
+

(Does not add cookies and sessions.)

+
+
def setup_api(routes):
+  app = routes
+  for middleware in [wrap_not_found, wrap_nested_params, wrap_params]:
+    app = middleware(app)
+  return app
+
+
+ # +
+

Generates a Pack app from the given routes that are suitable for a +typical website or web application. Adds the following middleware:

+
    +
  • params
  • +
  • nested_params
  • +
  • sessions
  • +
+

Options can contain "views" and "session" keys which will be passed to the +jinja and session middlewares, respectively.

+
+
def setup_app(routes, options={}):
+  app = routes
+  app = wrap_not_found(app)
+  app = wrap_jinja(app, options.get("views", {}))
+  app = wrap_flash(app)
+  app = wrap_session(app, options.get("session", {}))
+  app = setup_api(app)
+  return app
+
+
+
+
+ diff --git a/picasso/__init__.py b/picasso/__init__.py index 05df790..5c23e13 100644 --- a/picasso/__init__.py +++ b/picasso/__init__.py @@ -5,9 +5,7 @@ from pack.util.response import redirect, file_response +# Create a Pack app from a list of compiled routes (see +# routing._compile_route). def setup_routes(*routes): - """ - Create a Pack app from a list of compiled routes (see - routing._compile_route). - """ return lambda req: routing.route(req, *routes) diff --git a/picasso/middleware/flash.py b/picasso/middleware/flash.py index afeb573..bccaa20 100644 --- a/picasso/middleware/flash.py +++ b/picasso/middleware/flash.py @@ -1,15 +1,12 @@ -"A middleware for adding flash message support via sessions." +# A middleware for adding flash message support via sessions. +# Adds a "flash" key to the request that contains the current flash dict, which +# is stored in the session (request["session"]["flash"]). If it is modified by +# the app, the session is updated accordingly. +# +# Note that request["flash"] is actually a dict-like object whose values +# disappear after the first access. def wrap_flash(app): - """ - Adds a "flash" key to the request that contains the current flash dict, which - is stored in the session (request["session"]["flash"]). If it is modified by - the app, the session is updated accordingly. - - Note that request["flash"] is actually a dict-like object whose values - disappear after the first access. - """ - def wrapped(request): request["flash"] = DisappearingDict(request["session"].get("flash", {})) response = app(request) @@ -17,8 +14,8 @@ def wrapped(request): return response return wrapped +# Like a dict, but values disappear after the first access. class DisappearingDict(dict): - "Like a dict, but values disappear after the first access." def __getitem__(self, key): return self._get_and_delete(key) diff --git a/picasso/middleware/jinja.py b/picasso/middleware/jinja.py index c39fd5f..96074b0 100644 --- a/picasso/middleware/jinja.py +++ b/picasso/middleware/jinja.py @@ -1,25 +1,22 @@ -"A middleware that uses the Jinja templating system to render responses." +# A middleware that uses the Jinja templating system to render responses. import os from jinja2 import Environment, FileSystemLoader +# If the app response is a tuple, will use Jinja2 to render the response. The +# first element of the tuple is parsed as the template name, and the second +# should be a dictionary of data to be passed to the template. +# +# def view_product(req, id): +# # product = ... +# return "products/view", {"product": product} +# +# If sessions are enabled, a "session" key will be added to the data and sent +# to the template. +# +# Takes a template_dir option, which should be the path to your templates, and +# a config option, which can be a dict that will be passed to all your views. def wrap_jinja(app, options={}): - """ - If the app response is a tuple, will use Jinja2 to render the response. The - first element of the tuple is parsed as the template name, and the second - should be a dictionary of data to be passed to the template. - - def view_product(req, id): - # product = ... - return "products/view", {"product": product} - - If sessions are enabled, a "session" key will be added to the data and sent - to the template. - - Takes a template_dir option, which should be the path to your templates, and - a config option, which can be a dict that will be passed to all your views. - """ - options = dict({"template_dir": "./", "config": {}}, **options) template_dir = options["template_dir"] @@ -43,13 +40,11 @@ def wrapped(request): return response return wrapped +# Looks for the template in template_dir, and passes data to it. Also adds a +# session key to the data, if sessions are enabled, and a flash key if the +# flash middleware is being used. def _render_with_jinja(template_dir, template, data, session={}, flash={}, config={}): - """ - Looks for the template in template_dir, and passes data to it. Also adds a - session key to the data, if sessions are enabled, and a flash key if the - flash middleware is being used. - """ env = Environment(loader=FileSystemLoader(template_dir)) data = dict(data, **config) @@ -59,4 +54,5 @@ def _render_with_jinja(template_dir, template, data, session={}, flash={}, if flash is not None: data["flash"] = flash + # Render the template. return env.get_template(template + '.html').render(data).encode('utf-8') \ No newline at end of file diff --git a/picasso/middleware/not_found.py b/picasso/middleware/not_found.py index fad88f3..5465858 100644 --- a/picasso/middleware/not_found.py +++ b/picasso/middleware/not_found.py @@ -1,11 +1,10 @@ -"A middleware that turns a None response into a 404 Not Found response." +# A middleware that turns a None response into a 404 Not Found response. from pack.util.response import with_status, with_body +# If none of the given routes match, the response will be None. This +# will turn a None response into a 404 Not Found response, and is intended to +# be the outermost middleware. + def wrap_not_found(app): - """ - If none of the given routes match, the response will be None. This - will turn a None response into a 404 Not Found response, and is intended to - the outermost middleware. - """ return lambda req: app(req) or with_status(with_body("Not Found"), 404) \ No newline at end of file diff --git a/picasso/response.py b/picasso/response.py index f66ea0c..3e3cf0e 100644 --- a/picasso/response.py +++ b/picasso/response.py @@ -1,21 +1,22 @@ -"Functions that turn various objects into Pack responses." +# Generating Pack responses. import collections from pack.util.response import with_body, with_content_type +# Render the object into a Pack response suitable for the given Pack request. def render(response, request): - "Render the object into a Pack response suitable for the given Pack request." - if not response: return if not isinstance(response, dict): + # If the response is a function, call it and render the result. if callable(response): return render( response(request, **request.get("route_params", {})), request) response = with_body(response) response["body"] = _render_body(response.get("body"), request) + # Default content_type is text/html. if not response.get("headers", {}).get("content_type"): response = with_content_type(response, "text/html") diff --git a/picasso/routing.py b/picasso/routing.py index 8b442f5..2df10c9 100644 --- a/picasso/routing.py +++ b/picasso/routing.py @@ -6,36 +6,36 @@ from picasso import response +# Try to apply each route to the given request, returning the first nonempty +# response. The routes need to have been compiled to functions with +# _compile_route. def route(request, *routes): - """ - Try to apply each route to the given request, returning the first nonempty - response. The routes need to have been compiled to functions with - _compile_route. - """ for route in routes: response = route(request.copy()) if response: return response +# Functions for each method that generate routes from a path and response. +# Supported methods: GET, POST, PUT, DELETE, and HEAD. +# ANY will match any method. methods = ["get", "post", "put", "delete", "head", "any"] GET, POST, PUT, DELETE, HEAD, ANY = [lambda p, b, m=m: - _compile_route(m, p, b) for m in methods] + compile_route(m, p, b) for m in methods] -not_found = lambda b: ANY('/{url:.*}', b) +# Generates a route that matches any URL and returns the given body. +# You can add it as your last route to get a custom 404 page. +def not_found(body): + return ANY('/{url:.*}', body) -def _compile_route(method, path, body): - """ - Compile the route into a Pack app. The app will return the body given if and - only if a matching route is found. Otherwise it will return None. - """ +# Compile the route into a Pack app that will return the body given if and +# only if a matching route is found. Otherwise it will return None. +def compile_route(method, path, body): def app(request): return response.render(body, request) - return _if_method(method, - _if_route(_prepare_route(path), - app)) + return if_method(method, if_route(prepare_route(path), app)) -def _if_method(method, app): - "Evaluate the app if the method matches the request." +# Call the app if the method matches the request. +def if_method(method, app): def wrapped(request): if not method or method == request.get("method") or method == "any": return app(request) @@ -46,41 +46,38 @@ def wrapped(request): return response return wrapped -def _if_route(route, app): - "Evaluate the app if the route matches the request." +# Call the app if the route matches the request. +def if_route(route, app): def wrapped(request): - params = _route_matches(route, request) + params = route_matches(route, request) if params is not None: - return app(_set_route_params(request, params)) + return app(set_route_params(request, params)) return wrapped -def _set_route_params(request, params): - "Set the \"route_params\" and \"params\" keys in the request." - return _recursive_merge(request, {"route_params": params, "params": params}) +# Set the "route_params" and "params" keys in the request. +def set_route_params(request, params): + return recursive_merge(request, {"route_params": params, "params": params}) -def _route_matches(route, request): - "Return the params of the route matching the request, if there is one." +# Return the params of the route matching the request, if there is one. +def route_matches(route, request): m = RouteMapper() m.extend([route]) return m.match(request["uri"]) -def _prepare_route(route): - """ - Returns an instance of routes.base.Route (from the Routes package). If - route is a tuple, the first item should be a route and the second should be - a dict with conditions on the params in the route. - - prepare_route("products/view/:id") - prepare_route(("products/view/:id", {"id": r"\d+"})) - """ +# Returns an instance of routes.base.Route (from the Routes package). If +# route is a tuple, the first item should be a route and the second should be +# a dict with conditions on the params in the route. +# +# prepare_route("products/view/:id") +# prepare_route(("products/view/:id", {"id": r"\d+"})) +def prepare_route(route): if isinstance(route, str) or isinstance(route, unicode): return Route(None, route) if isinstance(route, collections.Iterable): return Route(None, route[0], requirements=route[1]) -def _recursive_merge(x, y): - "Merge two dictionaries recursively." - +# Merge two dictionaries recursively. +def recursive_merge(x, y): z = x.copy() for key, val in y.iteritems(): if isinstance(val, dict): diff --git a/picasso/setup.py b/picasso/setup.py index 1af5c23..4fe61c8 100644 --- a/picasso/setup.py +++ b/picasso/setup.py @@ -1,4 +1,4 @@ -"Functions that create Pack apps from routes." +# Some functions to create Pack apps from routes. from pack.middleware.params import * from pack.middleware.nested_params import * @@ -8,34 +8,29 @@ from picasso.middleware.flash import * from picasso.middleware.not_found import * +# Generates a Pack app from the given routes that can be used for creating +# a web API. Adds the following middleware: +# +# - params +# - nested_params +# +# (Does not add cookies and sessions.) def setup_api(routes): - """ - Generates a Pack app from the given routes that can be used for creating - a web API. Adds the following middleware: - - -- params - -- nested_params - - Does not add cookies and sessions. - """ app = routes for middleware in [wrap_not_found, wrap_nested_params, wrap_params]: app = middleware(app) return app +# Generates a Pack app from the given routes that are suitable for a +# typical website or web application. Adds the following middleware: +# +# - params +# - nested_params +# - sessions +# +# Options can contain "views" and "session" keys which will be passed to the +# jinja and session middlewares, respectively. def setup_app(routes, options={}): - """ - Generates a Pack app from the given routes that can be used for creating - a typical website or web application. Adds the following middleware: - - -- params - -- nested_params - -- sessions - - Options can contain "views" and "session" keys which will be passed to the - jinja and session middlewares, respectively. - """ - app = routes app = wrap_not_found(app) app = wrap_jinja(app, options.get("views", {}))