changelog shortlog tags files raw

changeset: initial checkin of davenport, web wsgi toolkit extrated from new version of jmoiron.net

changeset 0: d77006130200
child 1:1e5659bd3df0
author: Jason Moiron <jmoiron@jmoiron.net>
date: Tue Apr 29 23:31:20 2008 -0400 (4 years ago)
files: constants.py forms.py template.py
description: initial checkin of davenport, web wsgi toolkit extrated from new version of jmoiron.net
       1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
       2+++ b/constants.py	Tue Apr 29 23:31:20 2008 -0400
       3@@ -0,0 +1,75 @@
       4+#!/usr/bin/env python
       5+
       6+import time
       7+
       8+types = {
       9+    'html' : [('Content-type', 'text/html')],
      10+    'redirect' : lambda x: [('Location', x)],
      11+}
      12+
      13+# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
      14+_status = {
      15+    # informational
      16+    100 : 'CONTINUE',
      17+    101 : 'SWITCHING PROTOCOLS',
      18+    # success
      19+    200 : 'OK',
      20+    201 : 'CREATED',
      21+    202 : 'ACCEPTED',
      22+    203 : 'NON-AUTHORATIVE INFORMATION',
      23+    204 : 'NO CONTENT',
      24+    205 : 'RESET CONTENT',
      25+    205 : 'PARTIAL CONTENT',
      26+    # redirection
      27+    300 : 'MULTIPLE CHOICES',
      28+    301 : 'MOVED PERMANENTLY',
      29+    302 : 'FOUND',
      30+    303 : 'SEE OTHER',
      31+    304 : 'NOT MODIFIED',
      32+    305 : 'USE PROXY',
      33+    307 : 'TEMPORARY REDIRECT',
      34+    # client error
      35+    400 : 'BAD REQUEST',
      36+    401 : 'UNAUTHORIZED',
      37+    403 : 'FORBIDDEN',
      38+    404 : 'NOT FOUND',
      39+    405 : 'METHOD NOT ALLOWED',
      40+    406 : 'NOT ACCEPTABLE',
      41+    407 : 'PROXY AUTHENTICATION REQUIRED',
      42+    408 : 'REQUEST TIMEOUT',
      43+    409 : 'CONFLICT',
      44+    410 : 'GONE',
      45+    411 : 'LENGTH REQUIRED',
      46+    412 : 'PRECONDITION FAILED',
      47+    413 : 'REQUEST ENTITY TOO LARGE',
      48+    414 : 'REQUEST-URI TOO LONG',
      49+    415 : 'UNSUPPORTED MEDIA TYPE',
      50+    416 : 'REQUESTED RANGE NOT SATISFIABLE',
      51+    # server error
      52+    500 : 'INTERNAL SERVER ERROR',
      53+    501 : 'NOT IMPLEMENTED',
      54+    502 : 'BAD GATEWAY',
      55+    503 : 'SERVICE UNAVAILABLE',
      56+    504 : 'BAD GATEWAY',
      57+    505 : 'HTTP VERSION NOT SUPPORTED',
      58+}
      59+
      60+months = {
      61+    'Jan' : 1,
      62+    'Feb' : 2,
      63+    'Mar' : 3,
      64+    'Apr' : 4,
      65+    'May' : 5,
      66+    'Jun' : 6,
      67+    'Jul' : 7,
      68+    'Aug' : 8,
      69+    'Sep' : 9,
      70+    'Oct' : 10,
      71+    'Nov' : 11,
      72+    'Dec' : 12,
      73+}
      74+
      75+thisyear = lambda: time.localtime()[0]
      76+
      77+status = lambda x: '%d %s' % (x, _status[x])
      78+
     1.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2+++ b/forms.py	Tue Apr 29 23:31:20 2008 -0400
     1.3@@ -0,0 +1,210 @@
     1.4+#!/usr/bin/env python
     1.5+
     1.6+"""This builds on the concepts of FormBuild (http://formbuild.org), but
     1.7+because that library was a bit complex for my needs, I've simplified it
     1.8+immensely.
     1.9+
    1.10+The basic idea is that you create a Form object, populated it with
    1.11+FormElements (which represent the form fields) and then call "render".
    1.12+
    1.13+You can control the way that the form is rendered via the Layout; the
    1.14+default layout manager accepts a few different parameters and outputs
    1.15+the form as a table.
    1.16+"""
    1.17+
    1.18+def rmkeys(dict, keys):
    1.19+    for key in keys:
    1.20+        if key in dict:
    1.21+            del dict[key]
    1.22+
    1.23+required = '<p class="smaller"><em>name</em>, <em>email</em>, and <em>comment</em> are <strong>required</strong>.</p>'
    1.24+
    1.25+markdownHelp = """
    1.26+<tr>
    1.27+\t<td></td>
    1.28+\t<td style="vertical-align: top;"><span class="smaller">links get auto-converted, you may also use <a href="http://daringfireball.net/projects/markdown/syntax">markdown</a></span></td>
    1.29+</tr>
    1.30+"""
    1.31+
    1.32+testform = {
    1.33+    'options' : {
    1.34+        'action' : '/comments/postfree/',
    1.35+        'class_' : 'post_comment',
    1.36+        },
    1.37+    'elements' : [
    1.38+        {'name':'options', 'type':'hidden', 'value':'ip'},
    1.39+        {'name':'target', 'type':'hidden', 'value':'4:299',
    1.40+            'postElem':required},
    1.41+        {'name':'name', 'label':'name:'},
    1.42+        {'name':'email', 'label':'email:<br /><span class="smaller">(not shown)</span>'},
    1.43+        {'name':'web_site', 'label':'url:'},
    1.44+        {'name':'comment', 'type':'textarea', 'rows':10, 'cols':60,
    1.45+            'postElem':markdownHelp}
    1.46+    ],
    1.47+}
    1.48+
    1.49+
    1.50+def FormBuilder(form_dict, submit=True):
    1.51+    """Takes a dictionary and creates a form from it.
    1.52+
    1.53+    The dictionary format is the following:
    1.54+        {
    1.55+          'options' : {<opts>},
    1.56+          'elements': [{<elem>},],
    1.57+        }
    1.58+        opts: {'method':<>, ...} (options for the Form
    1.59+        elem: {'name':<name>, 'opt':<val>, 'preElem':<pre>, 'postElem':<post>}
    1.60+
    1.61+    """
    1.62+    fd = form_dict
    1.63+    elems = fd.get('elements', [])
    1.64+    options = fd.get('options', {})
    1.65+    f = Form(**options)
    1.66+    for elem in elems:
    1.67+        name = elem['name']
    1.68+        pre = elem.get('preElem', None)
    1.69+        post = elem.get('postElem', None)
    1.70+        rmkeys(elem, ('name','postElem', 'preElem'))
    1.71+        f.elements.append(FormElement(name, **elem))
    1.72+        if pre: f.layout.preElement[name] = pre
    1.73+        if post: f.layout.postElement[name] = post
    1.74+        pre, post = None, None
    1.75+    return f
    1.76+
    1.77+class Form:
    1.78+    def __init__(self, **kwargs):
    1.79+        self.method = kwargs.get('method', 'post')
    1.80+        self.action = kwargs.get('action', None)
    1.81+        if not self.action: raise Exception('an action for your form is necessary.')
    1.82+        self.elements = kwargs.get('elements', [])
    1.83+        self.layout = kwargs.get('layout', Layout())
    1.84+        rmkeys(kwargs, ('method', 'action', 'elements', 'layout'))
    1.85+        self.args = kwargs
    1.86+
    1.87+    def addInput(self, formelem):
    1.88+        self.elements.append(formelem)
    1.89+
    1.90+    def checkElements(self):
    1.91+        checked = []
    1.92+        for elem in elements:
    1.93+            if elem.name in [x.name for x in checked]:
    1.94+                raise Exception('repeat "name" in elements')
    1.95+            checked.append(elem)
    1.96+
    1.97+    def fill(self, fill_dict):
    1.98+        eledict = dict([(x.name, x) for x in self.elements])
    1.99+        for k,v in fill_dict.items():
   1.100+            if k in eledict:
   1.101+                eledict[k]['value'] = v
   1.102+        return self.render()
   1.103+
   1.104+    def render(self):
   1.105+        header = '<form method="%s" action="%s"' % (self.method, self.action)
   1.106+        for key in self.args:
   1.107+            if key == 'class_':
   1.108+                header += ' class="%s"' % self.args[key]
   1.109+            else:
   1.110+                header += ' %s="%s"' % (key, self.args[key])
   1.111+        header += '>\n\t<fieldset>\n'
   1.112+        body = self.layout(self.elements)
   1.113+        footer = '\t</fieldset>\n</form>\n'
   1.114+        return header + body + footer
   1.115+
   1.116+class FormElement:
   1.117+    def __init__(self, name, **kwargs):
   1.118+        self.type = kwargs.get('type', 'text')
   1.119+        self.name = name
   1.120+        self.extra = kwargs.get('extra', None)
   1.121+        self.label = kwargs.get('label', None)
   1.122+        rmkeys(kwargs, ('type', 'extra', 'label'))
   1.123+        self.args = kwargs
   1.124+
   1.125+    def _inputFormatter(self):
   1.126+        s = '<input type="%s" name="%s"' % (self.type, self.name)
   1.127+        for arg in self.args:
   1.128+            s += ' %s="%s"' % (arg, self.args[arg])
   1.129+        return s + ' />'
   1.130+
   1.131+    def _textareaFormatter(self):
   1.132+        s = '<textarea name="%s"' % self.name
   1.133+        self.value = self.args.get('value', '')
   1.134+        rmkeys(self.args, ('value',))
   1.135+        for arg in self.args:
   1.136+            s += ' %s="%s"' % (arg, self.args[arg])
   1.137+        return s + '>%s</textarea>' % self.value
   1.138+
   1.139+    def __str__(self):
   1.140+        if self.type.lower() in ('text', 'hidden',):
   1.141+            return self._inputFormatter()
   1.142+        elif self.type.lower() in ('textarea'):
   1.143+            return self._textareaFormatter()
   1.144+
   1.145+    def __repr__(self):
   1.146+        return '<FormInput %s>'
   1.147+
   1.148+def ishidden(element):
   1.149+    return element.type.lower() == 'hidden'
   1.150+
   1.151+class Layout:
   1.152+    def __init__(self):
   1.153+        self.called = False
   1.154+        self.preElement = {}
   1.155+        self.postElement = {}
   1.156+
   1.157+    def __call__(self, elements, indent=1):
   1.158+        self.indent = indent
   1.159+        self.i = '\t' * (indent + 1)
   1.160+        i = self.i
   1.161+        s = ''
   1.162+        hidden = [e for e in elements if ishidden(e)]
   1.163+        rest = [e for e in elements if not ishidden(e)]
   1.164+        for elem in hidden:
   1.165+            s += self.hiddenElementFormatter(elem)
   1.166+        s += self.layoutHeader()
   1.167+        for elem in rest:
   1.168+            s += self.elementFormatter(elem)
   1.169+        s += '\n' + self.layoutFooter()
   1.170+        return  s
   1.171+
   1.172+    def hiddenElementFormatter(self, element):
   1.173+        i = self.i
   1.174+        s = ''
   1.175+        if element.name in self.preElement:
   1.176+            s += self.prePostFormatter(element.name, pre=True)
   1.177+        s += i + str(element) + '\n'
   1.178+        if element.name in self.postElement:
   1.179+            s += self.prePostFormatter(element.name, post=True)
   1.180+        return s
   1.181+
   1.182+    def elementFormatter(self, element):
   1.183+        i = self.i
   1.184+        s = ''
   1.185+        if element.name in self.preElement:
   1.186+            s += self.prePostFormatter(element.name, pre=True)
   1.187+        if element.label:
   1.188+            s += '%s<tr>\n%s\t<th><label for="%s">%s</label></th>\n' % (i, i, element.name, element.label)
   1.189+        else:
   1.190+            s += '%s<tr>\n%s\t<th></th>\n' % (i, i)
   1.191+        s += '%s\t<td>%s</td>\n%s</tr>\n' % (i, str(element), i)
   1.192+        if element.name in self.postElement:
   1.193+            s += self.prePostFormatter(element.name, post=True)
   1.194+        return s
   1.195+
   1.196+    def prePostFormatter(self, name, pre=False, post=False):
   1.197+        if pre: text = self.preElement[name]
   1.198+        elif post: text = self.postElement[name]
   1.199+        else: return ''
   1.200+        i = self.i
   1.201+        t = text.strip()
   1.202+        joinstr = '\n' + i
   1.203+        t = i + joinstr.join(t.split('\n')) + '\n'
   1.204+        return t
   1.205+
   1.206+    def layoutHeader(self):
   1.207+        i = '\t' * self.indent
   1.208+        return '%s<table>\n%s<tbody>\n' % (i,i)
   1.209+
   1.210+    def layoutFooter(self):
   1.211+        i = '\t' * self.indent
   1.212+        return '%s</tbody>\n%s</table>\n' % (i,i)
   1.213+
     2.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2+++ b/template.py	Tue Apr 29 23:31:20 2008 -0400
     2.3@@ -0,0 +1,93 @@
     2.4+#!/usr/bin/env python
     2.5+
     2.6+"""Template utilities."""
     2.7+
     2.8+import time
     2.9+
    2.10+try: from cStringIO import StringIO
    2.11+except: from StringIO import StringIO
    2.12+from pprint import pprint as _pprint
    2.13+
    2.14+from mako.template import Template
    2.15+from mako.lookup   import TemplateLookup
    2.16+
    2.17+lookup = TemplateLookup(directories=['./templates'],
    2.18+                        #module_directory='./templates/modules/',
    2.19+                        output_encoding='utf-8',
    2.20+                        encoding_errors='replace',
    2.21+                        filesystem_checks=True,
    2.22+                        collection_size=100)
    2.23+
    2.24+import markdown as _markdown
    2.25+
    2.26+def url(env):
    2.27+    return u'http://' + unicode(env['HTTP_HOST']) + unicode(env['SCRIPT_NAME'])
    2.28+
    2.29+def render(template_name):
    2.30+    """Convenience decorator to be used by controllers.
    2.31+
    2.32+    Decorate a controller with 'render("template_name")';  Since it will use
    2.33+    the lookup object specified here, it will find the templates at
    2.34+    compile time.  Return a dictionary representing the Context for the
    2.35+    template from the controller, and this will return the iterable."""
    2.36+    def decorater(func):
    2.37+        def decorated(*args, **kwargs):
    2.38+            template = lookup.get_template(template_name)
    2.39+            environ = args[0]
    2.40+            ret = func(*args, **kwargs)
    2.41+            if not isinstance(ret, dict): return ret
    2.42+            return [template.render(env=environ, **ret)]
    2.43+        decorated.__name__ = func.__name__
    2.44+        decorated.__doc__ = func.__doc__
    2.45+        return decorated
    2.46+    return decorater
    2.47+
    2.48+def pprint(text):
    2.49+    """Pretty print filter for debugging."""
    2.50+    s = StringIO()
    2.51+    try: obj = eval(text)
    2.52+    except: return text
    2.53+    _pprint(obj, stream=s)
    2.54+    s.seek(0)
    2.55+    return s.read()
    2.56+
    2.57+def markdown(text):
    2.58+    """Returns the HTML generated by markdown on markdown text."""
    2.59+    return unicode(_markdown.markdown(text))
    2.60+
    2.61+def lgroup(list_):
    2.62+    """Groups a list of lists by the sub-list index.  Ex.
    2.63+    [[1, 1], [1, 2], [2, 1]] ->  [(1, [1, 2]), (2, [1])]
    2.64+    """
    2.65+    d = {}
    2.66+    for a,b in list_:
    2.67+        if d.has_key(a): d[a].append(b)
    2.68+        else: d[a] = [b]
    2.69+    l = d.items()
    2.70+    l.sort(reverse=True)
    2.71+    return l
    2.72+
    2.73+def longmonth(month, format='%B'):
    2.74+    """Returns the month in a given format (default: %B)."""
    2.75+    tup = (2000, int(month), 1, 1, 1, 1, 1, 1, 1)
    2.76+    return time.strftime(format, tup)
    2.77+
    2.78+def strftime(timestamp, formatstr):
    2.79+    """Strftime for ISO 8601 times; follows time.strftime with the addition
    2.80+    of '%D' which returns an ordinal day (1st, 3rd, 13th, 22nd, etc)."""
    2.81+    def extraFormat(timeval, formatstring):
    2.82+        return formatstring.replace('%D', dayFormatter(timeval))
    2.83+    def dayFormatter(timeval):
    2.84+        # day is the 2nd in a timeval
    2.85+        day = timeval[2]
    2.86+        if day in (11, 12, 13): return u'%dth' % day
    2.87+        digit = day % 10
    2.88+        if digit == 1: return u'%dst' % day
    2.89+        elif digit == 2: return u'%dnd' % day
    2.90+        elif digit == 3: return u'%drd' % day
    2.91+        else: return u'%dth' % day
    2.92+    timestamp = timestamp.split('.')[0]
    2.93+    timeval = time.strptime(timestamp, '%Y-%m-%dT%H:%M:%S')
    2.94+    formatstr = extraFormat(timeval, formatstr)
    2.95+    return time.strftime(formatstr, timeval)
    2.96+