[SCM] watchdog branch, master, updated. 0f52e799322b101c324d906df6e0670e0141322a

8 views
Skip to first unread message

aaronsw

unread,
Aug 13, 2009, 3:57:10 PM8/13/09
to watchdo...@googlegroups.com
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "watchdog".

The branch, master has been updated
via 0f52e799322b101c324d906df6e0670e0141322a (commit)
from 481c657e340a912f0aaa5d3251a809d50f897594 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 0f52e799322b101c324d906df6e0670e0141322a
Author: Aaron Swartz <m...@aaronsw.com>
Date: Thu Aug 13 15:56:51 2009 -0400

update web.py

-----------------------------------------------------------------------

Summary of changes:
vendor/webpy/setup.py | 4 +-
vendor/webpy/test.py | 58 ----
vendor/webpy/test/application.py | 162 +++++++++++-
vendor/webpy/test/db.py | 74 +++++-
vendor/webpy/test/doctests.py | 5 +-
vendor/webpy/test/session.py | 67 +----
vendor/webpy/test/webtest.py | 130 +--------
vendor/webpy/web/__init__.py | 52 +---
vendor/webpy/web/application.py | 164 +++++++----
vendor/webpy/web/browser.py | 14 +-
vendor/webpy/web/cheetah.py | 98 -------
vendor/webpy/web/contrib/template.py | 14 +-
vendor/webpy/web/db.py | 184 ++++++++-----
vendor/webpy/web/debugerror.py | 13 +-
vendor/webpy/web/form.py | 21 ++-
vendor/webpy/web/http.py | 109 +-------
vendor/webpy/web/httpserver.py | 3 +-
vendor/webpy/web/net.py | 39 ++-
vendor/webpy/web/session.py | 58 +++-
vendor/webpy/web/template.py | 235 +++++++++++++---
vendor/webpy/web/utils.py | 459 ++++++++++++++++++++-----------
vendor/webpy/web/webapi.py | 131 ++++++---
vendor/webpy/web/wsgi.py | 14 +-
vendor/webpy/web/wsgiserver/__init__.py | 124 ++++++---
24 files changed, 1309 insertions(+), 923 deletions(-)
delete mode 100644 vendor/webpy/test.py
delete mode 100644 vendor/webpy/web/cheetah.py

diff --git a/vendor/webpy/setup.py b/vendor/webpy/setup.py
index 761c632..db6f945 100644
--- a/vendor/webpy/setup.py
+++ b/vendor/webpy/setup.py
@@ -5,10 +5,12 @@
from distutils.core import setup

setup(name='web.py',
- version='0.3',
+ version='0.32',
description='web.py: makes web apps',
author='Aaron Swartz',
author_email='m...@aaronsw.com',
+ maintainer='Anand Chitipothu',
+ maintainer_email='anand...@gmail.com',
url=' http://webpy.org/',
packages=['web', 'web.wsgiserver', 'web.contrib'],
long_description="Think about the ideal way to write a web app. Write the code to make it happen.",
diff --git a/vendor/webpy/test.py b/vendor/webpy/test.py
deleted file mode 100644
index 2036de0..0000000
--- a/vendor/webpy/test.py
+++ /dev/null
@@ -1,58 +0,0 @@
-"""
-web.py test suite.
-"""
-
-import sys
-import web
-import unittest
-
-def commit(value):
- web.transact()
- web.insert("test", seqname=False, value=value)
- web.commit()
-
-def rollback(value):
- web.transact()
- web.insert("test", seqname=False, value=value)
- web.rollback()
-
-def getrow(value):
- d = web.select('test', where='value=$value', vars=locals())
- a = (d and d[0].value) or None
- return a
-
-def assert_presence(*values):
- for v in values:
- assert getrow(v) is not None
-
-def assert_absence(*values):
- for v in values:
- assert getrow(v) is None
-
-class PostgresTest(unittest.TestCase):
- parameters = dict(dbn="postgres", db="webtest", user="postgres", pw="")
-
- def setUp(self):
- web.config.db_parameters = self.parameters
- #web.config.db_printing = True
- web.load()
- web.delete("test", where="1=1")
-
- def tearDown(self):
- sys.stdout.flush()
- sys.stderr.flush()
-
- def testCommit(self):
- commit(1)
- assert_presence(1)
-
- def testRollback(self):
- rollback(1)
- assert_absence(1)
-
-class MySQLTest(PostgresTest):
- parameters = dict(dbn="mysql", db="webtest", user="webpy", pw="")
-
-if __name__ == "__main__":
- unittest.main()
-
diff --git a/vendor/webpy/test/application.py b/vendor/webpy/test/application.py
index c6ccb4a..9129afd 100644
--- a/vendor/webpy/test/application.py
+++ b/vendor/webpy/test/application.py
@@ -2,6 +2,7 @@ import webtest
import time

import web
+import urllib

data = """
import web
@@ -15,6 +16,18 @@ class %(classname)s:

"""

+urls = (
+ "/iter", "do_iter",
+)
+app = web.application(urls, globals())
+
+class do_iter:
+ def GET(self):
+ yield 'hello, '
+ yield web.input(name='world').name
+
+ POST = GET
+
def write(filename, data):
f = open(filename, 'w')
f.write(data)
@@ -97,6 +110,30 @@ class ApplicationTest(webtest.TestCase):
return web.ctx.path + ":" + handler()
app.add_processor(processor)
self.assertEquals(app.request('/blog/foo').data, '/blog/foo:blog foo')
+
+ def test_subdomains(self):
+ def create_app(name):
+ urls = ("/", "index")
+ class index:
+ def GET(self):
+ return name
+ return web.application(urls, locals())
+
+ urls = (
+ "a.example.com", create_app('a'),
+ "b.example.com", create_app('b'),
+ ".*.example.com", create_app('*')
+ )
+ app = web.subdomain_application(urls, locals())
+
+ def test(host, expected_result):
+ result = app.request('/', host=host)
+ self.assertEquals(result.data, expected_result)
+
+ test('a.example.com', 'a')
+ test('b.example.com', 'b')
+ test('c.example.com', '*')
+ test('d.example.com', '*')

def test_redirect(self):
urls = (
@@ -131,7 +168,130 @@ class ApplicationTest(webtest.TestCase):
response = app.request('/blog/foo2', env={'SCRIPT_NAME': '/x'})
self.assertEquals(response.headers['Location'], 'http://0.0.0.0:8080/x/blog/bar')

+ def test_processors(self):
+ urls = (
+ "/(.*)", "blog"
+ )
+ class blog:
+ def GET(self, path):
+ return 'blog ' + path
+
+ state = web.storage(x=0, y=0)
+ def f():
+ state.x += 1
+
+ app_blog = web.application(urls, locals())
+ app_blog.add_processor(web.loadhook(f))
+
+ urls = (
+ "/blog", app_blog,
+ "/(.*)", "index"
+ )
+ class index:
+ def GET(self, path):
+ return "hello " + path
+ app = web.application(urls, locals())
+ def g():
+ state.y += 1
+ app.add_processor(web.loadhook(g))
+
+ app.request('/blog/foo')
+ assert state.x == 1 and state.y == 1, repr(state)
+ app.request('/foo')
+ assert state.x == 1 and state.y == 2, repr(state)
+
+ def testUnicodeInput(self):
+ urls = (
+ "(/.*)", "foo"
+ )
+ class foo:
+ def GET(self, path):
+ i = web.input(name='')
+ return repr(i.name)
+
+ def POST(self, path):
+ if path == '/multipart':
+ i = web.input(file={})
+ return i.file.value
+ else:
+ i = web.input()
+ return repr(dict(i))
+
+ app = web.application(urls, locals())
+
+ def f(name):
+ path = '/?' + urllib.urlencode({"name": name.encode('utf-8')})
+ self.assertEquals(app.request(path).data, repr(name))
+
+ f(u'\u1234')
+ f(u'foo')
+
+ response = app.request('/', method='POST', data=dict(name='foo'))
+ self.assertEquals(response.data, "{'name': u'foo'}")
+
+ data = '--boundary\r\nContent-Disposition: form-data; name="x"\r\nfoo\r\n--boundary\r\nContent-Disposition: form-data; name="file"; filename="a.txt"\r\nContent-Type: text/plain\r\n\r\na\r\n--boundary--\r\n'
+ headers = {'Content-Type': 'multipart/form-data; boundary=boundary'}
+ response = app.request('/multipart', method="POST", data=data, headers=headers)
+ self.assertEquals(response.data, 'a')
+
+ def testCustomNotFound(self):
+ urls_a = ("/", "a")
+ urls_b = ("/", "b")
+
+ app_a = web.application(urls_a, locals())
+ app_b = web.application(urls_b, locals())
+
+ app_a.notfound = lambda: web.HTTPError("404 Not Found", {}, "not found 1")
+
+ urls = (
+ "/a", app_a,
+ "/b", app_b
+ )
+ app = web.application(urls, locals())
+
+ def assert_notfound(path, message):
+ response = app.request(path)
+ self.assertEquals(response.status.split()[0], "404")
+ self.assertEquals(response.data, message)
+
+ assert_notfound("/a/foo", "not found 1")
+ assert_notfound("/b/foo", "not found")
+
+ app.notfound = lambda: web.HTTPError("404 Not Found", {}, "not found 2")
+ assert_notfound("/a/foo", "not found 1")
+ assert_notfound("/b/foo", "not found 2")
+
+ def testIter(self):
+ self.assertEquals(app.request('/iter').data, 'hello, world')
+ self.assertEquals(app.request('/iter?name=web').data, 'hello, web')
+
+ self.assertEquals(app.request('/iter', method='POST').data, 'hello, world')
+ self.assertEquals(app.request('/iter', method='POST', data='name=web').data, 'hello, web')
+
+ def testUnload(self):
+ x = web.storage(a=0)
+
+ urls = (
+ "/foo", "foo",
+ "/bar", "bar"
+ )
+ class foo:
+ def GET(self):
+ return "foo"
+ class bar:
+ def GET(self):
+ raise web.notfound()
+
+ app = web.application(urls, locals())
+ def unload():
+ x.a += 1
+ app.add_processor(web.unloadhook(unload))
+
+ app.request('/foo')
+ self.assertEquals(x.a, 1)
+
+ app.request('/bar')
+ self.assertEquals(x.a, 2)

if __name__ == '__main__':
webtest.main()
-
diff --git a/vendor/webpy/test/db.py b/vendor/webpy/test/db.py
index a988aff..39a45f9 100644
--- a/vendor/webpy/test/db.py
+++ b/vendor/webpy/test/db.py
@@ -4,18 +4,27 @@ import web

class DBTest(webtest.TestCase):
dbname = 'postgres'
+ driver = None

def setUp(self):
- self.db = webtest.setup_database(self.dbname)
- self.db.query("CREATE TABLE person (name text, email text)")
+ self.db = webtest.setup_database(self.dbname, driver=self.driver)
+ self.db.query("CREATE TABLE person (name text, email text, active boolean)")

def tearDown(self):
# there might be some error with the current connection, delete from a new connection
- self.db = webtest.setup_database(self.dbname)
+ self.db = webtest.setup_database(self.dbname, driver=self.driver)
self.db.query('DROP TABLE person')
+
+ def _testable(self):
+ try:
+ webtest.setup_database(self.dbname, driver=self.driver)
+ return True
+ except ImportError, e:
+ print >> web.debug, str(e), "(ignoring %s)" % self.__class__.__name__
+ return False

def testUnicode(self):
- """Bug#177265: unicode queries throw errors"""
+ # Bug#177265: unicode queries throw errors
self.db.select('person', where='name=$name', vars={'name': u'\xf4'})

def assertRows(self, n):
@@ -68,6 +77,11 @@ class DBTest(webtest.TestCase):
self.assertRows(2)

def testPooling(self):
+ # can't test pooling if DBUtils is not installed
+ try:
+ import DBUtils
+ except ImportError:
+ return
db = webtest.setup_database(self.dbname, pooling=True)
self.assertEquals(db.ctx.db.__class__.__module__, 'DBUtils.PooledDB')
db.select('person', limit=1)
@@ -79,13 +93,42 @@ class DBTest(webtest.TestCase):
assert db.select("person", where="name='a'")
assert db.select("person", where="name='b'")

+ def test_result_is_unicode(self):
+ db = webtest.setup_database(self.dbname)
+ self.db.insert('person', False, name='user')
+ name = db.select('person')[0].name
+ self.assertEquals(type(name), unicode)
+
+ def testBoolean(self):
+ def t(active):
+ name ='name-%s' % active
+ self.db.insert('person', False, name=name, active=active)
+ a = self.db.select('person', where='name=$name', vars=locals())[0].active
+ self.assertEquals(a, active)
+ t(False)
+ t(True)
+
+class PostgresTest(DBTest):
+ dbname = "postgres"
+ driver = "psycopg2"
+
+class PostgresTest_psycopg(PostgresTest):
+ driver = "psycopg"
+
+class PostgresTest_pgdb(PostgresTest):
+ driver = "pgdb"
+
class SqliteTest(DBTest):
dbname = "sqlite"
+ driver = "sqlite3"

def testNestedTransactions(self):
#nested transactions does not work with sqlite
pass
-
+
+class SqliteTest_pysqlite2(SqliteTest):
+ driver = "pysqlite2.dbapi2"
+
class MySQLTest(DBTest):
dbname = "mysql"

@@ -94,5 +137,26 @@ class MySQLTest(DBTest):
# In mysql, transactions are supported only with INNODB engine.
self.db.query("CREATE TABLE person (name text, email text) ENGINE=INNODB")

+ def testBoolean(self):
+ # boolean datatype is not suppoted in MySQL (at least until v5.0)
+ pass
+
+del DBTest
+
+def is_test(cls):
+ import inspect
+ return inspect.isclass(cls) and webtest.TestCase in inspect.getmro(cls)
+
+# ignore db tests when the required db adapter is not found.
+for t in globals().values():
+ if is_test(t) and not t('_testable')._testable():
+ del globals()[t.__name__]
+del t
+
+try:
+ import DBUtils
+except ImportError, e:
+ print >> web.debug, str(e) + "(ignoring testPooling)"
+
if __name__ == '__main__':
webtest.main()
diff --git a/vendor/webpy/test/doctests.py b/vendor/webpy/test/doctests.py
index 6cdb970..d795131 100644
--- a/vendor/webpy/test/doctests.py
+++ b/vendor/webpy/test/doctests.py
@@ -8,12 +8,11 @@ def suite():
"web.db",
"web.http",
"web.net",
- "web.request",
"web.session",
"web.template",
"web.utils",
- "web.webapi",
- "web.wsgi",
+# "web.webapi",
+# "web.wsgi",
]
return webtest.doctest_suite(modules)

diff --git a/vendor/webpy/test/session.py b/vendor/webpy/test/session.py
index ca88f5d..e0493fc 100644
--- a/vendor/webpy/test/session.py
+++ b/vendor/webpy/test/session.py
@@ -2,42 +2,6 @@ import webtest
import web
import tempfile

-class Browser:
- """Browser simulation.
- Stores cookies across requests.
- """
- def __init__(self, app):
- self.app = app
- self.cookies = {}
- self.response = None
-
- def open(self, path):
- headers = {}
- if self.cookies:
- headers['cookie'] = self.cookie_header()
- self.response = self.app.request(path, headers=headers)
- if 'Set-Cookie' in self.response.headers:
- self.read_cookie(self.response.headers['Set-Cookie'])
- return self.response.data
-
- def cookie_header(self):
- return "; ".join(["%s=%s" % (k, v) for k, v in self.cookies.items()])
-
- def read_cookie(self, header):
- tokens = header.split('; ')
- d = {}
- name, value = tokens[0].split("=")
- for t in tokens[1:]:
- k, v = t.split("=")
- d[k.lower()] = v
-
- #@@ fix this
- if 'expires' in d:
- d.pop('name', None)
- return
-
- self.cookies[name] = value
-
class SessionTest(webtest.TestCase):
def setUp(self):
app = web.auto_application()
@@ -61,30 +25,31 @@ class SessionTest(webtest.TestCase):
return web.session.Session(app, store, {'count': 0})

def testSession(self):
- b = Browser(self.app)
- self.assertEquals(b.open('/count'), '1')
- self.assertEquals(b.open('/count'), '2')
- self.assertEquals(b.open('/count'), '3')
+ b = self.app.browser()
+ self.assertEquals(b.open('/count').read(), '1')
+ self.assertEquals(b.open('/count').read(), '2')
+ self.assertEquals(b.open('/count').read(), '3')
b.open('/reset')
- self.assertEquals(b.open('/count'), '1')
+ self.assertEquals(b.open('/count').read(), '1')

def testParallelSessions(self):
- b1 = Browser(self.app)
- b2 = Browser(self.app)
+ b1 = self.app.browser()
+ b2 = self.app.browser()

b1.open('/count')

for i in range(1, 10):
- self.assertEquals(b1.open('/count'), str(i+1))
- self.assertEquals(b2.open('/count'), str(i))
+ self.assertEquals(b1.open('/count').read(), str(i+1))
+ self.assertEquals(b2.open('/count').read(), str(i))

def testBadSessionId(self):
- b = Browser(self.app)
- self.assertEquals(b.open('/count'), '1')
- self.assertEquals(b.open('/count'), '2')
-
- b.cookies['webpy_session_id'] = '/etc/password'
- self.assertEquals(b.open('/count'), '1')
+ b = self.app.browser()
+ self.assertEquals(b.open('/count').read(), '1')
+ self.assertEquals(b.open('/count').read(), '2')
+
+ cookie = b.cookiejar._cookies['0.0.0.0']['/']['webpy_session_id']
+ cookie.value = '/etc/password'
+ self.assertEquals(b.open('/count').read(), '1')

class DBSessionTest(SessionTest):
"""Session test with db store."""
diff --git a/vendor/webpy/test/webtest.py b/vendor/webpy/test/webtest.py
index 74de9d4..1cbc6ca 100644
--- a/vendor/webpy/test/webtest.py
+++ b/vendor/webpy/test/webtest.py
@@ -1,130 +1,20 @@
"""webtest: test utilities.
"""
-import unittest
import sys, os

-# adding current directory to path to make sure local copy of web module is used.
+# adding current directory to path to make sure local modules can be imported
sys.path.insert(0, '.')

-import web
-
-
-class TestCase(unittest.TestCase):
- def setUpAll(self):
- pass
-
- def tearDownAll(self):
- pass
-
- def shortDescription(self):
- """overridden to not return docstrings"""
- return None
-
-class TestSuite(unittest.TestSuite):
- """A TestSuite with once per TestCase setUpAll() and tearDownAll().
- Adopted from test/testlib/testing.py file in SQLAlchemy test suite.
- """
-
- def __init__(self, tests=()):
- if len(tests) >0 and isinstance(tests[0], TestCase):
- self._initTest = tests[0]
- else:
- self._initTest = None
- unittest.TestSuite.__init__(self, tests)
-
- def do_run(self, result):
- # nice job unittest ! you switched __call__ and run() between py2.3
- # and 2.4 thereby making straight subclassing impossible !
- for test in self._tests:
- if result.shouldStop:
- break
- test(result)
- return result
-
- def run(self, result):
- return self(result)
-
- def __call__(self, result):
- try:
- if self._initTest is not None:
- self._initTest.setUpAll()
- except:
- # skip tests if global setup fails
- ex = self.__exc_info()
- for test in self._tests:
- result.addError(test, ex)
- return False
- try:
- return self.do_run(result)
- finally:
- try:
- if self._initTest is not None:
- self._initTest.tearDownAll()
- except:
- result.addError(self._initTest, self.__exc_info())
- pass
-
- def __exc_info(self):
- """Return a version of sys.exc_info() with the traceback frame
- minimised; usually the top level of the traceback frame is not
- needed.
- ripped off out of unittest module since its double __
- """
- exctype, excvalue, tb = sys.exc_info()
- if sys.platform[:4] == 'java': ## tracebacks look different in Jython
- return (exctype, excvalue, tb)
- return (exctype, excvalue, tb)
-
-# monkeypatch
-unittest.TestLoader.suiteClass = TestSuite
-
-def runTests(suite):
- runner = unittest.TextTestRunner()
- return runner.run(suite)
-
-def main(suite=None):
- if not suite:
- main_module = __import__('__main__')
- # allow command line switches
- args = [a for a in sys.argv[1:] if not a.startswith('-')]
- suite = module_suite(main_module, args or None)
-
- result = runTests(suite)
- sys.exit(not result.wasSuccessful())
-
-def load_modules(names):
- return [__import__(name, None, None, "x") for name in names]
-
-def module_suite(module, classnames=None):
- """Makes a suite from a module."""
- if hasattr(module, 'suite'):
- return module.suite()
- elif classnames:
- return unittest.TestLoader().loadTestsFromNames(classnames, module)
- else:
- return unittest.TestLoader().loadTestsFromModule(module)
-
-def doctest_suite(module_names):
- """Makes a test suite from doctests."""
- import doctest
- suite = unittest.TestSuite()
- for mod in load_modules(module_names):
- suite.addTest(doctest.DocTestSuite(mod))
- return suite
+from web.test import *

-def suite(module_names):
- """Creates a suite from multiple modules."""
- suite = unittest.TestSuite()
- for mod in load_modules(module_names):
- suite.addTest(module_suite(mod))
- return suite
-
-def setup_database(dbname, pooling=False):
+def setup_database(dbname, driver=None, pooling=False):
if dbname == 'sqlite':
- db = web.database(dbn=dbname, db='webpy.db', pooling=pooling)
+ db = web.database(dbn=dbname, db='webpy.db', pooling=pooling, driver=driver)
+ elif dbname == 'postgres':
+ user = os.getenv('USER')
+ db = web.database(dbn=dbname, db='webpy', user=user, pw='', pooling=pooling, driver=driver)
else:
- db = web.database(dbn=dbname, db='webpy', user='scott', pw='tiger', pooling=pooling)
-
- if '-v' in sys.argv:
- db.printing = True
+ db = web.database(dbn=dbname, db='webpy', user='scott', pw='tiger', pooling=pooling, driver=driver)
+
+ db.printing = '-v' in sys.argv
return db
diff --git a/vendor/webpy/web/__init__.py b/vendor/webpy/web/__init__.py
index 266a701..d85d30e 100644
--- a/vendor/webpy/web/__init__.py
+++ b/vendor/webpy/web/__init__.py
@@ -1,16 +1,16 @@
#!/usr/bin/env python
+"""web.py: makes web apps (http://webpy.org)"""
+
from __future__ import generators

-"""web.py: makes web apps (http://webpy.org)"""
-__version__ = "0.3"
-__revision__ = "$Rev$"
-__author__ = "Aaron Swartz <m...@aaronsw.com>"
+__version__ = "0.32"
+__author__ = [
+ "Aaron Swartz <m...@aaronsw.com>",
+ "Anand Chitipothu <anand...@gmail.com>"
+]
__license__ = "public domain"
__contributors__ = "see http://webpy.org/changes"

-# todo:
-# - some sort of accounts system
-
import utils, db, net, wsgi, http, webapi, httpserver, debugerror
import template, form

@@ -25,44 +25,10 @@ from webapi import *
from httpserver import *
from debugerror import *
from application import *
+from browser import *
+import test
try:
import webopenid as openid
except ImportError:
pass # requires openid module

-try:
- import cheetah
- from cheetah import *
-except ImportError:
- pass
-
-def main():
- import doctest
-
- doctest.testmod(utils)
- doctest.testmod(db)
- doctest.testmod(net)
- doctest.testmod(wsgi)
- doctest.testmod(http)
- doctest.testmod(webapi)
-
- try:
- doctest.testmod(cheetah)
- except NameError:
- pass
-
- template.test()
-
- import sys
- urls = ('/web.py', 'source')
- class source:
- def GET(self):
- header('Content-Type', 'text/python')
- return open(sys.argv[0]).read()
-
- if listget(sys.argv, 1) != 'test':
- app = application(urls, locals())
- app.run()
-
-if __name__ == "__main__": main()
-
diff --git a/vendor/webpy/web/application.py b/vendor/webpy/web/application.py
index adfcd12..f65ea09 100755
--- a/vendor/webpy/web/application.py
+++ b/vendor/webpy/web/application.py
@@ -15,6 +15,7 @@ import itertools
import os
import re
import types
+from exceptions import SystemExit

try:
import wsgiref.handlers
@@ -46,7 +47,10 @@ class application:
self.mapping = mapping
self.fvars = fvars
self.processors = []
-
+
+ self.add_processor(loadhook(self._load))
+ self.add_processor(unloadhook(self._unload))
+
if autoreload:
def main_module_name():
mod = sys.modules['__main__']
@@ -86,7 +90,33 @@ class application:
__import__(main_module_name())
except ImportError:
pass
-
+
+ def _load(self):
+ web.ctx.app_stack.append(self)
+
+ def _unload(self):
+ web.ctx.app_stack = web.ctx.app_stack[:-1]
+
+ if web.ctx.app_stack:
+ # this is a sub-application, revert ctx to earlier state.
+ oldctx = web.ctx.get('_oldctx')
+ if oldctx:
+ web.ctx.home = oldctx.home
+ web.ctx.homepath = oldctx.homepath
+ web.ctx.path = oldctx.path
+ web.ctx.fullpath = oldctx.fullpath
+
+ def _cleanup(self):
+ #@@@
+ # Since the CherryPy Webserver uses thread pool, the thread-local state is never cleared.
+ # This interferes with the other requests.
+ # clearing the thread-local storage to avoid that.
+ # see utils.ThreadedDict for details
+ import threading
+ t = threading.currentThread()
+ if hasattr(t, '_d'):
+ del t._d
+
def add_mapping(self, pattern, classname):
self.mapping += (pattern, classname)

@@ -101,6 +131,7 @@ class application:
...
>>>
>>> def hello(handler): return "hello, " + handler()
+ ...
>>> app.add_processor(hello)
>>> app.request("/web.py").data
'hello, web.py'
@@ -175,7 +206,8 @@ class application:
if 'HTTP_CONTENT_TYPE' in env:
env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE')

- if data:
+ if method in ["POST", "PUT"]:
+ data = data or ''
import StringIO
if isinstance(data, dict):
q = urllib.urlencode(data)
@@ -189,7 +221,7 @@ class application:
response.status = status
response.headers = dict(headers)
response.header_items = headers
- response.data = "".join(self.wsgifunc(cleanup_threadlocal=False)(env, start_response))
+ response.data = "".join(self.wsgifunc()(env, start_response))
return response

def browser(self):
@@ -202,25 +234,24 @@ class application:

def handle_with_processors(self):
def process(processors):
- web.ctx.app_stack.append(self)
- if processors:
- p, processors = processors[0], processors[1:]
- return p(lambda: process(processors))
- else:
- return self.handle()
-
- try:
- # processors must be applied in the resvere order. (??)
- return process(self.processors)
- except web.HTTPError:
- raise
- except:
- print >> web.debug, traceback.format_exc()
- raise self.internalerror()
- finally:
- web.ctx.app_stack = web.ctx.app_stack[:-1]
+ try:
+ if processors:
+ p, processors = processors[0], processors[1:]
+ return p(lambda: process(processors))
+ else:
+ return self.handle()
+ except web.HTTPError:
+ raise
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ print >> web.debug, traceback.format_exc()
+ raise self.internalerror()
+
+ # processors must be applied in the resvere order. (??)
+ return process(self.processors)

- def wsgifunc(self, *middleware, **kw):
+ def wsgifunc(self, *middleware):
"""Returns a WSGI-compatible function for this application."""
def peep(iterator):
"""Peeps into an iterator by doing an iteration
@@ -233,42 +264,36 @@ class application:
firstchunk = iterator.next()
except StopIteration:
firstchunk = ''
-
+
return itertools.chain([firstchunk], iterator)

def is_generator(x): return x and hasattr(x, 'next')

def wsgi(env, start_resp):
self.load(env)
-
try:
# allow uppercase methods only
if web.ctx.method.upper() != web.ctx.method:
raise web.nomethod()

result = self.handle_with_processors()
+ if is_generator(result):
+ result = peep(result)
+ else:
+ result = [result]
except web.HTTPError, e:
- result = e.data
+ result = [e.data]

- if is_generator(result):
- result = peep(result)
- else:
- result = [utils.utf8(result)]
+ result = web.utf8(iter(result))

status, headers = web.ctx.status, web.ctx.headers
start_resp(status, headers)
-
- #@@@
- # Since the CherryPy Webserver uses thread pool, the thread-local state is never cleared.
- # This interferes with the other requests.
- # clearing the thread-local storage to avoid that.
- # see utils.ThreadedDict for details
- import threading
- t = threading.currentThread()
- if kw.get('cleanup_threadlocal', True) and hasattr(t, '_d'):
- del t._d
-
- return result
+
+ def cleanup():
+ self._cleanup()
+ yield '' # force this function to be a generator
+
+ return itertools.chain(result, cleanup())

for m in middleware:
wsgi = m(wsgi)
@@ -315,7 +340,7 @@ class application:

if env.get('wsgi.url_scheme') in ['http', 'https']:
ctx.protocol = env['wsgi.url_scheme']
- elif env.get('HTTPS', '').lower() in ['on', 'true']:
+ elif env.get('HTTPS', '').lower() in ['on', 'true', '1']:
ctx.protocol = 'https'
else:
ctx.protocol = 'http'
@@ -331,6 +356,9 @@ class application:
# http://trac.lighttpd.net/trac/ticket/406 requires:
if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.homepath)
+ # Apache and CherryPy webservers unquote the url but lighttpd doesn't.
+ # unquote explicitly for lighttpd to make ctx.path uniform across all servers.
+ ctx.path = urllib.unquote(ctx.path)

if env.get('QUERY_STRING'):
ctx.query = '?' + env.get('QUERY_STRING', '')
@@ -401,7 +429,7 @@ class application:
result = utils.re_compile('^' + pat + '$').match(value)

if result: # it's a match
- return what, [x and urllib.unquote(x) for x in result.groups()]
+ return what, [x for x in result.groups()]
return None, None

def _delegate_sub_application(self, dir, app):
@@ -411,18 +439,12 @@ class application:

@@Any issues with when used with yield?
"""
- try:
- oldctx = web.storage(web.ctx)
- web.ctx.home += dir
- web.ctx.homepath += dir
- web.ctx.path = web.ctx.path[len(dir):]
- web.ctx.fullpath = web.ctx.fullpath[len(dir):]
- return app.handle_with_processors()
- finally:
- web.ctx.home = oldctx.home
- web.ctx.homepath = oldctx.homepath
- web.ctx.path = oldctx.path
- web.ctx.fullpath = oldctx.fullpath
+ web.ctx._oldctx = web.storage(web.ctx)
+ web.ctx.home += dir
+ web.ctx.homepath += dir
+ web.ctx.path = web.ctx.path[len(dir):]
+ web.ctx.fullpath = web.ctx.fullpath[len(dir):]
+ return app.handle_with_processors()

def get_parent_app(self):
if self in web.ctx.app_stack:
@@ -496,7 +518,7 @@ class subdomain_application(application):
>>> class hello:
... def GET(self): return "hello"
>>>
- >>> mapping = ("hello.example.com", app)
+ >>> mapping = (r"hello\.example\.com", app)
>>> app2 = subdomain_application(mapping)
>>> app2.request("/hello", host="hello.example.com").data
'hello'
@@ -519,7 +541,7 @@ class subdomain_application(application):
result = utils.re_compile('^' + pat + '$').match(value)

if result: # it's a match
- return what, [x and urllib.unquote(x) for x in result.groups()]
+ return what, [x for x in result.groups()]
return None, None

def loadhook(h):
@@ -548,9 +570,31 @@ def unloadhook(h):
"""
def processor(handler):
try:
- return handler()
- finally:
+ result = handler()
+ is_generator = result and hasattr(result, 'next')
+ except:
+ # run the hook even when handler raises some exception
h()
+ raise
+
+ if is_generator:
+ return wrap(result)
+ else:
+ h()
+ return result
+
+ def wrap(result):
+ def next():
+ try:
+ return result.next()
+ except:
+ # call the hook at the and of iterator
+ h()
+ raise
+
+ result = iter(result)
+ while True:
+ yield next()

return processor

@@ -617,7 +661,7 @@ class Reloader:
self.mtimes[mod] = mtime
except ImportError:
pass
-
+
if __name__ == "__main__":
import doctest
doctest.testmod()
diff --git a/vendor/webpy/web/browser.py b/vendor/webpy/web/browser.py
index 36da3df..66d859e 100644
--- a/vendor/webpy/web/browser.py
+++ b/vendor/webpy/web/browser.py
@@ -1,11 +1,10 @@
"""Browser to test web applications.
(from web.py)
"""
-from utils import re_compile, utf8
+from utils import re_compile
from net import htmlunquote

import httplib, urllib, urllib2
-import cookielib
import copy
from StringIO import StringIO

@@ -22,6 +21,7 @@ class BrowserError(Exception):

class Browser:
def __init__(self):
+ import cookielib
self.cookiejar = cookielib.CookieJar()
self._cookie_processor = urllib2.HTTPCookieProcessor(self.cookiejar)
self.form = None
@@ -65,7 +65,7 @@ class Browser:
def open(self, url, data=None, headers={}):
"""Opens the specified url."""
url = urllib.basejoin(self.url, url)
- req = urllib2.Request(utf8(url), data, headers)
+ req = urllib2.Request(url, data, headers)
return self.do_request(req)

def show(self):
@@ -220,8 +220,12 @@ class AppHandler(urllib2.HTTPHandler):

def https_open(self, req):
return self.http_open(req)
-
- https_request = urllib2.HTTPHandler.do_request_
+
+ try:
+ https_request = urllib2.HTTPHandler.do_request_
+ except AttributeError:
+ # for python 2.3
+ pass

def _make_response(self, result, url):
data = "\r\n".join(["%s: %s" % (k, v) for k, v in result.header_items])
diff --git a/vendor/webpy/web/cheetah.py b/vendor/webpy/web/cheetah.py
deleted file mode 100644
index db9fbf3..0000000
--- a/vendor/webpy/web/cheetah.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-Cheetah API
-(from web.py)
-"""
-
-__all__ = ["render"]
-
-import re, urlparse, pprint, traceback, sys
-from Cheetah.Compiler import Compiler
-from Cheetah.Filters import Filter
-from utils import re_compile, memoize, dictadd
-from net import htmlquote, websafe
-from webapi import ctx, header, output, input, cookies, loadhooks
-
-def upvars(level=2):
- """Guido van Rossum sez: don't use this function."""
- return dictadd(
- sys._getframe(level).f_globals,
- sys._getframe(level).f_locals)
-
-r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M)
-def __compiletemplate(template, base=None, isString=False):
- if isString:
- text = template
- else:
- text = open('templates/'+template).read()
- # implement #include at compile-time
- def do_include(match):
- text = open('templates/'+match.groups()[0]).read()
- return text
- while r_include.findall(text):
- text = r_include.sub(do_include, text)
-
- execspace = _compiletemplate.bases.copy()
- tmpl_compiler = Compiler(source=text, mainClassName='GenTemplate')
- tmpl_compiler.addImportedVarNames(execspace.keys())
- exec str(tmpl_compiler) in execspace
- if base:
- _compiletemplate.bases[base] = execspace['GenTemplate']
-
- return execspace['GenTemplate']
-
-_compiletemplate = memoize(__compiletemplate)
-_compiletemplate.bases = {}
-
-def render(template, terms=None, asTemplate=False, base=None,
- isString=False):
- """
- Renders a template, caching where it can.
-
- `template` is the name of a file containing the a template in
- the `templates/` folder, unless `isString`, in which case it's the
- template itself.
-
- `terms` is a dictionary used to fill the template. If it's None, then
- the caller's local variables are used instead, plus context, if it's not
- already set, is set to `context`.
-
- If asTemplate is False, it `output`s the template directly. Otherwise,
- it returns the template object.
-
- If the template is a potential base template (that is, something other templates)
- can extend, then base should be a string with the name of the template. The
- template will be cached and made available for future calls to `render`.
-
- Requires [Cheetah](http://cheetahtemplate.org/).
- """
- # terms=['var1', 'var2'] means grab those variables
- if isinstance(terms, list):
- new = {}
- old = upvars()
- for k in terms:
- new[k] = old[k]
- terms = new
- # default: grab all locals
- elif terms is None:
- terms = {'context': ctx, 'ctx':ctx}
- terms.update(sys._getframe(1).f_locals)
- # terms=d means use d as the searchList
- if not isinstance(terms, tuple):
- terms = (terms,)
-
- if 'headers' in ctx and not isString and template.endswith('.html'):
- header('Content-Type','text/html; charset=utf-8', unique=True)
-
- if loadhooks.has_key('reloader'):
- compiled_tmpl = __compiletemplate(template, base=base, isString=isString)
- else:
- compiled_tmpl = _compiletemplate(template, base=base, isString=isString)
- compiled_tmpl = compiled_tmpl(searchList=terms, filter=WebSafe)
- if asTemplate:
- return compiled_tmpl
- else:
- return output(str(compiled_tmpl))
-
-class WebSafe(Filter):
- def filter(self, val, **keywords):
- return websafe(val)
diff --git a/vendor/webpy/web/contrib/template.py b/vendor/webpy/web/contrib/template.py
index 59d6135..7495d39 100644
--- a/vendor/webpy/web/contrib/template.py
+++ b/vendor/webpy/web/contrib/template.py
@@ -12,6 +12,7 @@ class render_cheetah:
"""Rendering interface to Cheetah Templates.

Example:
+
render = render_cheetah('templates')
render.hello(name="cheetah")
"""
@@ -32,13 +33,15 @@ class render_cheetah:

class render_genshi:
"""Rendering interface genshi templates.
-
Example:
+
for xml/html templates.
+
render = render_genshi(['templates/'])
render.hello(name='genshi')

For text templates:
+
render = render_genshi(['templates/'], type='text')
render.hello(name='genshi')
"""
@@ -74,12 +77,17 @@ class render_jinja:
"""Rendering interface to Jinja2 Templates

Example:
+
render= render_jinja('templates')
render.hello(name='jinja2')
"""
def __init__(self, *a, **kwargs):
+ extensions = kwargs.pop('extensions', [])
+ globals = kwargs.pop('globals', {})
+
from jinja2 import Environment,FileSystemLoader
- self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs))
+ self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions)
+ self._lookup.globals.update(globals)

def __getattr__(self, name):
# Assuming all templates end with .html
@@ -91,6 +99,7 @@ class render_mako:
"""Rendering interface to Mako Templates.

Example:
+
render = render_mako(directories=['templates'])
render.hello(name="mako")
"""
@@ -108,6 +117,7 @@ class cache:
"""Cache for any rendering interface.

Example:
+
render = cache(render_cheetah("templates/"))
render.hello(name='cache')
"""
diff --git a/vendor/webpy/web/db.py b/vendor/webpy/web/db.py
index 74d9da3..31940d8 100644
--- a/vendor/webpy/web/db.py
+++ b/vendor/webpy/web/db.py
@@ -21,10 +21,11 @@ from utils import threadeddict, storage, iters, iterbetter

try:
# db module can work independent of web.py
- from webapi import debug
+ from webapi import debug, config
except:
import sys
debug = sys.stderr
+ config = storage()

class UnknownDB(Exception):
"""raised for unsupported dbms"""
@@ -50,7 +51,8 @@ class UnknownParamstyle(Exception):
pass

class SQLParam:
- """Parameter in SQLQuery.
+ """
+ Parameter in SQLQuery.

>>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam("joe")])
>>> q
@@ -143,6 +145,16 @@ class SQLQuery:

return SQLQuery(items + self.items)

+ def __iadd__(self, other):
+ if isinstance(other, basestring):
+ items = [other]
+ elif isinstance(other, SQLQuery):
+ items = other.items
+ else:
+ return NotImplemented
+ self.items.extend(items)
+ return self
+
def __len__(self):
return len(self.query())

@@ -180,11 +192,11 @@ class SQLQuery:
"""
if len(items) == 0:
return SQLQuery("")
-
+
q = SQLQuery(items[0])
- for i in items[1:]:
- q = q + sep + i
-
+ for item in items[1:]:
+ q += sep
+ q += item
return q

join = staticmethod(join)
@@ -215,6 +227,20 @@ class SQLLiteral:

sqlliteral = SQLLiteral

+def _sqllist(values):
+ """
+ >>> _sqllist([1, 2, 3])
+ <sql: '(1, 2, 3)'>
+ """
+ items = []
+ items.append('(')
+ for i, v in enumerate(values):
+ if i != 0:
+ items.append(', ')
+ items.append(sqlparam(v))
+ items.append(')')
+ return SQLQuery(items)
+
def reparam(string_, dictionary):
"""
Takes a string and a dictionary and interpolates the string
@@ -222,6 +248,8 @@ def reparam(string_, dictionary):

>>> reparam("s = $s", dict(s=True))
<sql: "s = 't'">
+ >>> reparam("s IN $s", dict(s=[1, 2]))
+ <sql: 's IN (1, 2)'>
"""
dictionary = dictionary.copy() # eval mucks with it
vals = []
@@ -229,7 +257,7 @@ def reparam(string_, dictionary):
for live, chunk in _interpolate(string_):
if live:
v = eval(chunk, dictionary)
- result.append(sqlparam(v))
+ result.append(sqlquote(v))
else:
result.append(chunk)
return SQLQuery.join(result, '')
@@ -283,13 +311,13 @@ def sqlors(left, lst):
for each item in the lst.

>>> sqlors('foo = ', [])
- <sql: '2+2=5'>
+ <sql: '1=2'>
>>> sqlors('foo = ', [1])
<sql: 'foo = 1'>
>>> sqlors('foo = ', 1)
<sql: 'foo = 1'>
>>> sqlors('foo = ', [1,2,3])
- <sql: '(foo = 1 OR foo = 2 OR foo = 3)'>
+ <sql: '(foo = 1 OR foo = 2 OR foo = 3 OR 1=2)'>
"""
if isinstance(lst, iters):
lst = list(lst)
@@ -326,8 +354,13 @@ def sqlquote(a):

>>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3)
<sql: "WHERE x = 't' AND y = 3">
+ >>> 'WHERE x = ' + sqlquote(True) + ' AND y IN ' + sqlquote([2, 3])
+ <sql: "WHERE x = 't' AND y IN (2, 3)">
"""
- return sqlparam(a).sqlquery()
+ if isinstance(a, list):
+ return _sqllist(a)
+ else:
+ return sqlparam(a).sqlquery()

class Transaction:
"""Database transaction."""
@@ -402,12 +435,17 @@ class DB:
def __init__(self, db_module, keywords):
"""Creates a database.
"""
+ # some DB implementaions take optional paramater `driver` to use a specific driver modue
+ # but it should not be passed to connect
+ keywords.pop('driver', None)
+
self.db_module = db_module
self.keywords = keywords
+

self._ctx = threadeddict()
# flag to enable/disable printing queries
- self.printing = False
+ self.printing = config.get('debug', False)
self.supports_multiple_insert = False

try:
@@ -418,7 +456,7 @@ class DB:
self.has_pooling = False

# Pooling can be disabled by passing pooling=False in the keywords.
- self.has_pooling = self.has_pooling and self.keywords.pop('pooling', True)
+ self.has_pooling = self.keywords.pop('pooling', True) and self.has_pooling

def _getctx(self):
if not self._ctx.get('db'):
@@ -495,17 +533,6 @@ class DB:
return '%s'
raise UnknownParamstyle, style

- def _py2sql(self, val):
- """
- Transforms a Python value into a value to pass to cursor.execute.
-
- This exists specifically for a workaround in SqliteDB.
-
- """
- if isinstance(val, unicode):
- val = val.encode('UTF-8')
- return val
-
def _db_execute(self, cur, sql_query):
"""executes an sql query"""
self.ctx.dbq_count += 1
@@ -513,9 +540,7 @@ class DB:
try:
a = time.time()
paramstyle = getattr(self, 'paramstyle', 'pyformat')
- out = cur.execute(sql_query.query(paramstyle),
- [self._py2sql(x)
- for x in sql_query.values()])
+ out = cur.execute(sql_query.query(paramstyle), sql_query.values())
b = time.time()
except:
if self.printing:
@@ -836,26 +861,17 @@ class PostgresDB(DB):
keywords['password'] = keywords['pw']
del keywords['pw']

- db_module = self.get_db_module()
+ db_module = import_driver(["psycopg2", "psycopg", "pgdb"], preferred=keywords.pop('driver', None))
+ if db_module.__name__ == "psycopg2":
+ import psycopg2.extensions
+ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+
keywords['database'] = keywords.pop('db')
self.dbname = "postgres"
self.paramstyle = db_module.paramstyle
- self.supports_multiple_insert = True
-
DB.__init__(self, db_module, keywords)
+ self.supports_multiple_insert = True

- def get_db_module(self):
- try:
- import psycopg2 as db
- import psycopg2.extensions
- psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
- except ImportError:
- try:
- import psycopg as db
- except ImportError:
- import pgdb as db
- return db
-
def _process_insert_query(self, query, tablename, seqname):
if seqname is None:
seqname = tablename + "_id_seq"
@@ -877,25 +893,40 @@ class MySQLDB(DB):
if 'pw' in keywords:
keywords['passwd'] = keywords['pw']
del keywords['pw']
+
+ if 'charset' not in keywords:
+ keywords['charset'] = 'utf8'
+ elif keywords['charset'] is None:
+ del keywords['charset']
+
self.paramstyle = db.paramstyle = 'pyformat' # it's both, like psycopg
self.dbname = "mysql"
- self.supports_multiple_insert = True
DB.__init__(self, db, keywords)
+ self.supports_multiple_insert = True

def _process_insert_query(self, query, tablename, seqname):
return query, SQLQuery('SELECT last_insert_id();')

+def import_driver(drivers, preferred=None):
+ """Import the first available driver or preferred driver.
+ """
+ if preferred:
+ drivers = [preferred]
+
+ for d in drivers:
+ try:
+ return __import__(d, None, None, ['x'])
+ except ImportError:
+ pass
+ raise ImportError("Unable to import " + " or ".join(drivers))
+
class SqliteDB(DB):
def __init__(self, **keywords):
- try:
- import sqlite3 as db
+ db = import_driver(["sqlite3", "pysqlite2.dbapi2", "sqlite"], preferred=keywords.pop('driver', None))
+
+ if db.__name__ in ["sqlite3", "pysqlite2.dbapi2"]:
db.paramstyle = 'qmark'
- except ImportError:
- try:
- from pysqlite2 import dbapi2 as db
- db.paramstyle = 'qmark'
- except ImportError:
- import sqlite as db
+
self.paramstyle = db.paramstyle
keywords['database'] = keywords.pop('db')
self.dbname = "sqlite"
@@ -911,29 +942,6 @@ class SqliteDB(DB):
del out.__len__
return out

- # as with PostgresDB, the database is assumed to be in UTF-8.
- # This doesn't mean we turn byte-strings coming out of it into
- # Unicode objects, but we avoid trying to put Unicode objects into
- # it.
- encoding = 'UTF-8'
-
- def _py2sql(self, val):
- r"""
- Work around a couple of problems in SQLite that maybe pysqlite
- should take care of: give it True and False and it thinks
- they're column names; give it Unicode and it tries to insert
- it in, possibly, ASCII.
-
- >>> meth = SqliteDB(db='nonexistent')._py2sql
- >>> [meth(x) for x in [True, False, 1, 2, 'foo', u'souffl\xe9']]
- [1, 0, 1, 2, 'foo', 'souffl\xc3\xa9']
-
- """
- if val is True: return 1
- elif val is False: return 0
- elif isinstance(val, unicode): return val.encode(self.encoding)
- else: return val
-
class FirebirdDB(DB):
"""Firebird Database.
"""
@@ -971,11 +979,35 @@ class MSSQLDB(DB):
def __init__(self, **keywords):
import pymssql as db
if 'pw' in keywords:
- keywords['password'] = keywords.pop('kw')
+ keywords['password'] = keywords.pop('pw')
keywords['database'] = keywords.pop('db')
self.dbname = "mssql"
DB.__init__(self, db, keywords)

+ def sql_clauses(self, what, tables, where, group, order, limit, offset):
+ return (
+ ('SELECT', what),
+ ('TOP', limit),
+ ('FROM', sqllist(tables)),
+ ('WHERE', where),
+ ('GROUP BY', group),
+ ('ORDER BY', order),
+ ('OFFSET', offset))
+
+ def _test(self):
+ """Test LIMIT.
+
+ Fake presence of pymssql module for running tests.
+ >>> import sys
+ >>> sys.modules['pymssql'] = sys.modules['sys']
+
+ MSSQL has TOP clause instead of LIMIT clause.
+ >>> db = MSSQLDB(db='test', user='joe', pw='secret')
+ >>> db.select('foo', limit=4, _test=True)
+ <sql: 'SELECT * TOP 4 FROM foo'>
+ """
+ pass
+
class OracleDB(DB):
def __init__(self, **keywords):
import cx_Oracle as db
@@ -986,6 +1018,7 @@ class OracleDB(DB):
keywords['dsn'] = keywords.pop('db')
self.dbname = 'oracle'
db.paramstyle = 'numeric'
+ self.paramstyle = db.paramstyle

# oracle doesn't support pooling
keywords.pop('pooling', None)
@@ -1012,7 +1045,8 @@ def database(dburl=None, **params):
raise UnknownDB, dbn

def register_database(name, clazz):
- """Register a database.
+ """
+ Register a database.

>>> class LegacyDB(DB):
... def __init__(self, **params):
diff --git a/vendor/webpy/web/debugerror.py b/vendor/webpy/web/debugerror.py
index 5ff62ff..831be4a 100644
--- a/vendor/webpy/web/debugerror.py
+++ b/vendor/webpy/web/debugerror.py
@@ -298,15 +298,17 @@ def debugerror():
"""
return web._InternalError(djangoerror())

-def emailerrors(email_address, olderror):
+def emailerrors(to_address, olderror, from_address=None):
"""
Wraps the old `internalerror` handler (pass as `olderror`) to
- additionally email all errors to `email_address`, to aid in
+ additionally email all errors to `to_address`, to aid in
debugging production websites.

Emails contain a normal text traceback as well as an
attachment containing the nice `debugerror` page.
"""
+ from_address = from_address or to_address
+
def emailerrors_internal():
error = olderror()
tb = sys.exc_info()
@@ -314,8 +316,7 @@ def emailerrors(email_address, olderror):
error_value = tb[1]
tb_txt = ''.join(traceback.format_exception(*tb))
path = web.ctx.path
- request = web.ctx.method+' '+web.ctx.home+web.ctx.fullpath
- eaddr = email_address
+ request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
text = ("""\
------here----
Content-Type: text/plain
@@ -331,8 +332,8 @@ Content-Disposition: attachment; filename="bug.html"

""" % locals()) + str(djangoerror())
sendmail(
- "your buggy site <%s>" % eaddr,
- "the bugfixer <%s>" % eaddr,
+ "your buggy site <%s>" % from_address,
+ "the bugfixer <%s>" % to_address,
"bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
text,
headers={'Content-Type': 'multipart/mixed; boundary="----here----"'})
diff --git a/vendor/webpy/web/form.py b/vendor/webpy/web/form.py
index fdd7152..f3dceee 100644
--- a/vendor/webpy/web/form.py
+++ b/vendor/webpy/web/form.py
@@ -13,7 +13,8 @@ def attrget(obj, attr, value=None):
return value

class Form:
- r"""HTML form.
+ r"""
+ HTML form.

>>> f = Form(Textbox("x"))
>>> f.render()
@@ -39,7 +40,18 @@ class Form:
out += "<td>"+i.pre+i.render()+i.post+"</td></tr>\n"
out += "</table>"
return out
-
+
+ def render_css(self):
+ out = []
+ out.append(self.rendernote(self.note))
+ for i in self.inputs:
+ out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description)))
+ out.append(i.pre)
+ out.append(i.render())
+ out.append(i.post)
+ out.append('\n')
+ return ''.join(out)
+
def rendernote(self, note):
if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
else: return ""
@@ -162,7 +174,7 @@ class Dropdown(Input):
def render(self):
x = '<select name="%s"%s>\n' % (net.websafe(self.name), self.addatts())
for arg in self.args:
- if type(arg) == tuple:
+ if isinstance(arg, (tuple, list)):
value, desc= arg
else:
value, desc = arg, arg
@@ -218,6 +230,7 @@ class Hidden(Input):
def render(self):
x = '<input type="hidden" name="%s"' % net.websafe(self.name)
if self.value: x += ' value="%s"' % net.websafe(self.value)
+ x += self.addatts()
x += ' />'
return x

@@ -248,4 +261,4 @@ class regexp(Validator):

if __name__ == "__main__":
import doctest
- doctest.testmod()
\ No newline at end of file
+ doctest.testmod()
diff --git a/vendor/webpy/web/http.py b/vendor/webpy/web/http.py
index 5e99e39..5a32436 100644
--- a/vendor/webpy/web/http.py
+++ b/vendor/webpy/web/http.py
@@ -8,8 +8,7 @@ __all__ = [
"prefixurl", "modified",
"write",
"changequery", "url",
- "background", "backgrounder",
- "Reloader", "reloader", "profiler",
+ "profiler",
]

import sys, os, threading, urllib, urlparse
@@ -61,7 +60,13 @@ def modified(date=None, etag=None):
`True` and sets the response status to `304 Not Modified`. It also
sets `Last-Modified and `ETag` output headers.
"""
- n = set(x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(','))
+ try:
+ from __builtin__ import set
+ except ImportError:
+ # for python 2.3
+ from sets import Set as set
+
+ n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')])
m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
validate = False
if etag:
@@ -74,8 +79,8 @@ def modified(date=None, etag=None):
validate = True

if validate: web.ctx.status = '304 Not Modified'
- lastmodified(date)
- web.header('ETag', '"' + etag + '"')
+ if date: lastmodified(date)
+ if etag: web.header('ETag', '"' + etag + '"')
return not validate

def write(cgi_response):
@@ -145,104 +150,12 @@ def url(path=None, **kw):

return out

-def background(func):
- """A function decorator to run a long-running function as a background thread."""
- def internal(*a, **kw):
- web.data() # cache it
-
- tmpctx = web._context[threading.currentThread()]
- web._context[threading.currentThread()] = utils.storage(web.ctx.copy())
-
- def newfunc():
- web._context[threading.currentThread()] = tmpctx
- func(*a, **kw)
- myctx = web._context[threading.currentThread()]
- for k in myctx.keys():
- if k not in ['status', 'headers', 'output']:
- try: del myctx[k]
- except KeyError: pass
-
- t = threading.Thread(target=newfunc)
- background.threaddb[id(t)] = t
- t.start()
- web.ctx.headers = []
- return seeother(changequery(_t=id(t)))
- return internal
-background.threaddb = {}
-
-def backgrounder(func):
- def internal(*a, **kw):
- i = web.input(_method='get')
- if '_t' in i:
- try:
- t = background.threaddb[int(i._t)]
- except KeyError:
- return web.notfound()
- web._context[threading.currentThread()] = web._context[t]
- return
- else:
- return func(*a, **kw)
- return internal
-
-class Reloader:
- """
- Before every request, checks to see if any loaded modules have changed on
- disk and, if so, reloads them.
- """
- def __init__(self, func):
- self.func = func
- self.mtimes = {}
- # cheetah:
- # b = _compiletemplate.bases
- # _compiletemplate = globals()['__compiletemplate']
- # _compiletemplate.bases = b
-
- #web.loadhooks['reloader'] = self.check
- # todo:
- # - replace relrcheck with a loadhook
- #if reloader in middleware:
- # relr = reloader(None)
- # relrcheck = relr.check
- # middleware.remove(reloader)
- #else:
- # relr = None
- # relrcheck = lambda: None
- # if relr:
- # relr.func = wsgifunc
- # return wsgifunc
- #
-
- def check(self):
- for mod in sys.modules.values():
- try:
- mtime = os.stat(mod.__file__).st_mtime
- except (AttributeError, OSError, IOError):
- continue
- if mod.__file__.endswith('.pyc') and \
- os.path.exists(mod.__file__[:-1]):
- mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime)
- if mod not in self.mtimes:
- self.mtimes[mod] = mtime
- elif self.mtimes[mod] < mtime:
- try:
- reload(mod)
- self.mtimes[mod] = mtime
- except ImportError:
- pass
- return True
-
- def __call__(self, e, o):
- self.check()
- return self.func(e, o)
-
-reloader = Reloader
-
def profiler(app):
"""Outputs basic profiling information at the bottom of each response."""
from utils import profile
def profile_internal(e, o):
out, result = profile(app)(e, o)
- return out + ['<pre>' + net.websafe(result) + '</pre>']
+ return list(out) + ['<pre>' + net.websafe(result) + '</pre>']
return profile_internal

if __name__ == "__main__":
diff --git a/vendor/webpy/web/httpserver.py b/vendor/webpy/web/httpserver.py
index 99ea4e2..317601e 100644
--- a/vendor/webpy/web/httpserver.py
+++ b/vendor/webpy/web/httpserver.py
@@ -3,6 +3,7 @@ __all__ = ["runsimple"]
import sys, os
import webapi as web
import net
+import utils

def runbasic(func, server_address=("0.0.0.0", 8080)):
"""
@@ -212,7 +213,7 @@ def runsimple(func, server_address=("0.0.0.0", 8080)):
time = self.log_date_time_string()

msg = self.format % (host, time, protocol, method, req, status)
- print >> outfile, msg.encode('utf-8')
+ print >> outfile, utils.safestr(msg)

func = WSGIWrapper(func)
server = CherryPyWSGIServer(server_address, func, server_name="localhost")
diff --git a/vendor/webpy/web/net.py b/vendor/webpy/web/net.py
index 1e6bb8a..6c3ee85 100644
--- a/vendor/webpy/web/net.py
+++ b/vendor/webpy/web/net.py
@@ -15,26 +15,47 @@ try: import datetime
except ImportError: pass

def validipaddr(address):
- """returns True if `address` is a valid IPv4 address"""
+ """
+ Returns True if `address` is a valid IPv4 address.
+
+ >>> validipaddr('192.168.1.1')
+ True
+ >>> validipaddr('192.168.1.800')
+ False
+ >>> validipaddr('192.168.1')
+ False
+ """
try:
octets = address.split('.')
- assert len(octets) == 4
+ if len(octets) != 4:
+ return False
for x in octets:
- assert 0 <= int(x) <= 255
- except (AssertionError, ValueError):
+ if not (0 <= int(x) <= 255):
+ return False
+ except ValueError:
return False
return True

def validipport(port):
- """returns True if `port` is a valid IPv4 port"""
+ """
+ Returns True if `port` is a valid IPv4 port.
+
+ >>> validipport('9000')
+ True
+ >>> validipport('foo')
+ False
+ >>> validipport('1000000')
+ False
+ """
try:
- assert 0 <= int(port) <= 65535
- except (AssertionError, ValueError):
+ if not (0 <= int(port) <= 65535):
+ return False
+ except ValueError:
return False
return True

def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
- """returns `(ip_address, port)` from string `ip_addr_port`"""
+ """Returns `(ip_address, port)` from string `ip_addr_port`"""
addr = defaultaddr
port = defaultport

@@ -59,7 +80,7 @@ def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):

def validaddr(string_):
"""
- returns either (ip_address, port) or "/path/to/socket" from string_
+ Returns either (ip_address, port) or "/path/to/socket" from string_

>>> validaddr('/path/to/socket')
'/path/to/socket'
diff --git a/vendor/webpy/web/session.py b/vendor/webpy/web/session.py
index fc4b482..a260318 100644
--- a/vendor/webpy/web/session.py
+++ b/vendor/webpy/web/session.py
@@ -137,31 +137,30 @@ class Session(utils.ThreadedDict):

def expired(self):
"""Called when an expired session is atime"""
+ self._killed = True
+ self._save()
raise SessionExpired(self._config.expired_message)

def kill(self):
"""Kill the session, make it no longer available"""
- try:
- del self.store[self.session_id]
- except KeyError:
- pass
+ del self.store[self.session_id]
self._killed = True

class Store:
"""Base class for session stores"""

def __contains__(self, key):
- raise NotImplemented
+ raise NotImplementedError

def __getitem__(self, key):
- raise NotImplemented
+ raise NotImplementedError

def __setitem__(self, key, value):
- raise NotImplemented
+ raise NotImplementedError

def cleanup(self, timeout):
"""removes all the expired sessions"""
- raise NotImplemented
+ raise NotImplementedError

def encode(self, session_dict):
"""encodes session dict as a string"""
@@ -171,13 +170,11 @@ class Store:
def decode(self, session_data):
"""decodes the data to get back the session dict """
pickled = base64.decodestring(session_data)
- try:
- return pickle.loads(pickled)
- except:
- return {}
+ return pickle.loads(pickled)

class DiskStore(Store):
- """Store for saving a session on disk
+ """
+ Store for saving a session on disk.

>>> import tempfile
>>> root = tempfile.mkdtemp()
@@ -282,6 +279,41 @@ class DBStore(Store):
last_allowed_time = datetime.datetime.now() - timeout
self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())

+class ShelfStore:
+ """Store for saving session using `shelve` module.
+
+ import shelve
+ store = ShelfStore(shelve.open('session.shelf'))
+
+ XXX: is shelve thread-safe?
+ """
+ def __init__(self, shelf):
+ self.shelf = shelf
+
+ def __contains__(self, key):
+ return key in self.shelf
+
+ def __getitem__(self, key):
+ atime, v = self.shelf[key]
+ self[key] = v # update atime
+ return v
+
+ def __setitem__(self, key, value):
+ self.shelf[key] = time.time(), value
+
+ def __delitem__(self, key):
+ try:
+ del self.shelf[key]
+ except KeyError:
+ pass
+
+ def cleanup(self, timeout):
+ now = time.time()
+ for k in self.shelf.keys():
+ atime, v = self.shelf[k]
+ if now - atime > timeout :
+ del self[k]
+
if __name__ == '__main__' :
import doctest
doctest.testmod()
diff --git a/vendor/webpy/web/template.py b/vendor/webpy/web/template.py
index b11dfb3..e9dbdb1 100644
--- a/vendor/webpy/web/template.py
+++ b/vendor/webpy/web/template.py
@@ -36,16 +36,18 @@ __all__ = [
"test"
]

-import tokenize, compiler
+import tokenize
import os
import glob
+import re

from utils import storage, safeunicode, safestr, re_compile
from webapi import config
from net import websafe

def splitline(text):
- r"""Splits the given text at newline.
+ r"""
+ Splits the given text at newline.

>>> splitline('foo\nbar')
('foo\n', 'bar')
@@ -418,7 +420,7 @@ class Parser:
r"""
>>> read_block_section = Parser('').read_block_section
>>> read_block_section('for i in range(10): hello $i\nfoo')
- (<block: 'for i in range(10):', [<line: [t' hello ', $i, t'\n']>]>, 'foo')
+ (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
>>> read_block_section('for i in range(10):\n hello $i\n foo', begin_indent=' ')
(<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, ' foo')
>>> read_block_section('for i in range(10):\n hello $i\nfoo')
@@ -430,7 +432,7 @@ class Parser:

# if there is some thing left in the line
if line.strip():
- block = line
+ block = line.lstrip()
else:
def find_indent(text):
rx = re_compile(' +')
@@ -475,6 +477,12 @@ class PythonTokenizer:
t = self.next()
if t.value == delim:
break
+ elif t.value == '(':
+ self.consume_till(')')
+ elif t.value == '[':
+ self.consume_till(']')
+ elif t.value == '{':
+ self.consume_till('}')

# if end of line is found, it is an exception.
# Since there is no easy way to report the line number,
@@ -662,14 +670,17 @@ TEMPLATE_BUILTIN_NAMES = [
"set", "slice", "tuple", "xrange",
"abs", "all", "any", "callable", "chr", "cmp", "divmod", "filter", "hex",
"id", "isinstance", "iter", "len", "max", "min", "oct", "ord", "pow", "range",
- "True", "False"
+ "True", "False",
+ "None",
+ "__import__", # some c-libraries like datetime requires __import__ to present in the namespace
]

import __builtin__
TEMPLATE_BUILTINS = dict([(name, getattr(__builtin__, name)) for name in TEMPLATE_BUILTIN_NAMES if name in __builtin__.__dict__])

class ForLoop:
- """Wrapper for expression in for stament to support loop.xxx helpers.
+ """
+ Wrapper for expression in for stament to support loop.xxx helpers.

>>> loop = ForLoop()
>>> for x in loop.setup(['a', 'b', 'c']):
@@ -746,16 +757,18 @@ class ForLoopContext:

class BaseTemplate:
def __init__(self, code, filename, filter, globals, builtins):
- self.code = code
self.filename = filename
self.filter = filter
self._globals = globals
self._builtins = builtins
- self.t = self._compile()
+ if code:
+ self.t = self._compile(code)
+ else:
+ self.t = lambda: ''

- def _compile(self):
+ def _compile(self, code):
env = self.make_env(self._globals or {}, self._builtins)
- exec(self.code, env)
+ exec(code, env)
return env['__template__']

def __call__(self, *a, **kw):
@@ -801,22 +814,18 @@ class BaseTemplate:
class Template(BaseTemplate):
CONTENT_TYPES = {
'.html' : 'text/html; charset=utf-8',
+ '.xhtml' : 'application/xhtml+xml; charset=utf-8',
'.txt' : 'text/plain',
}
FILTERS = {
'.html': websafe,
+ '.xhtml': websafe,
'.xml': websafe
}
globals = {}

def __init__(self, text, filename='<template>', filter=None, globals=None, builtins=None):
- text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
- if not text.endswith('\n'):
- text += '\n'
-
- # support fort \$ for backward-compatibility
- text = text.replace(r'\$', '$$')
-
+ text = Template.normalize_text(text)
code = self.compile_template(text, filename)

_, ext = os.path.splitext(filename)
@@ -830,10 +839,26 @@ class Template(BaseTemplate):

BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)

+ def normalize_text(text):
+ """Normalizes template text by correcting \r\n, tabs and BOM chars."""
+ text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
+ if not text.endswith('\n'):
+ text += '\n'
+
+ # ignore BOM chars at the begining of template
+ BOM = '\xef\xbb\xbf'
+ if isinstance(text, str) and text.startswith(BOM):
+ text = text[len(BOM):]
+
+ # support fort \$ for backward-compatibility
+ text = text.replace(r'\$', '$$')
+ return text
+ normalize_text = staticmethod(normalize_text)
+
def __call__(self, *a, **kw):
import webapi as web
if 'headers' in web.ctx and self.content_type:
- web.header('Content-Type', self.content_type)
+ web.header('Content-Type', self.content_type, unique=True)

return BaseTemplate.__call__(self, *a, **kw)

@@ -870,10 +895,22 @@ class Template(BaseTemplate):
raise

# make sure code is safe
+ import compiler
ast = compiler.parse(code)
SafeVisitor().walk(ast, filename)

return compiled_code
+
+class CompiledTemplate(Template):
+ def __init__(self, f, filename):
+ Template.__init__(self, '', filename)
+ self.t = f
+
+ def compile_template(self, *a):
+ return None
+
+ def _compile(self, *a):
+ return None

class Render:
"""The most preferred way of using templates.
@@ -889,28 +926,41 @@ class Render:
def __init__(self, loc='templates', cache=None, base=None, **keywords):
self._loc = loc
self._keywords = keywords
+
+ if cache is None:
+ cache = not config.get('debug', False)

- if not cache or config.get('debug', False):
- self._cache = None
- else:
+ if cache:
self._cache = {}
+ else:
+ self._cache = None

if base and not hasattr(base, '__call__'):
# make base a function, so that it can be passed to sub-renders
self._base = lambda page: self._template(base)(page)
else:
self._base = base
-
- def _load_template(self, name):
+
+ def _lookup(self, name):
path = os.path.join(self._loc, name)
if os.path.isdir(path):
- return Render(path, cache=self._cache is not None, base=self._base, **self._keywords)
+ return 'dir', path
else:
path = self._findfile(path)
if path:
- return Template(open(path).read(), filename=path, **self._keywords)
+ return 'file', path
else:
- raise AttributeError, "No template named " + name
+ return 'none', None
+
+ def _load_template(self, name):
+ kind, path = self._lookup(name)
+
+ if kind == 'dir':
+ return Render(path, cache=self._cache is not None, base=self._base, **self._keywords)
+ elif kind == 'file':
+ return Template(open(path).read(), filename=path, **self._keywords)
+ else:
+ raise AttributeError, "No template named " + name

def _findfile(self, path_prefix):
p = [f for f in glob.glob(path_prefix + '.*') if not f.endswith('~')] # skip backup files
@@ -932,14 +982,93 @@ class Render:
return template
else:
return self._template(name)
+
+class GAE_Render(Render):
+ # Render gets over-written. make a copy here.
+ super = Render
+ def __init__(self, loc, *a, **kw):
+ GAE_Render.super.__init__(self, loc, *a, **kw)

-render = Render
+ import types
+ if isinstance(loc, types.ModuleType):
+ self.mod = loc
+ else:
+ name = loc.rstrip('/').replace('/', '.')
+ self.mod = __import__(name, None, None, ['x'])
+
+ self.mod.__dict__.update(kw.get('builtins', TEMPLATE_BUILTINS))
+ self.mod.__dict__.update(Template.globals)
+ self.mod.__dict__.update(kw.get('globals', {}))
+
+ def _load_template(self, name):
+ t = getattr(self.mod, name)
+ import types
+ if isinstance(t, types.ModuleType):
+ return GAE_Render(t, cache=self._cache is not None, base=self._base, **self._keywords)
+ else:
+ return t

+render = Render
+# setup render for Google App Engine.
+try:
+ from google import appengine
+ render = Render = GAE_Render
+except ImportError:
+ pass
+
def frender(path, **keywords):
"""Creates a template from the given file path.
"""
return Template(open(path).read(), filename=path, **keywords)
+
+def compile_templates(root):
+ """Compiles templates to python code."""
+ re_start = re_compile('^', re.M)
+
+ for dirpath, dirnames, filenames in os.walk(root):
+ filenames = [f for f in filenames if not f.startswith('.') and not f.endswith('~') and not f.startswith('__init__.py')]
+
+ for d in dirnames[:]:
+ if d.startswith('.'):
+ dirnames.remove(d) # don't visit this dir
+
+ out = open(os.path.join(dirpath, '__init__.py'), 'w')
+ out.write('from web.template import CompiledTemplate, ForLoop\n\n')
+ if dirnames:
+ out.write("import " + ", ".join(dirnames))

+ for f in filenames:
+ path = os.path.join(dirpath, f)
+
+ if '.' in f:
+ name, _ = f.split('.', 1)
+ else:
+ name = f
+
+ text = open(path).read()
+ text = Template.normalize_text(text)
+ code = Template.generate_code(text, path)
+ code = re_start.sub(' ', code)
+
+ _gen = '' + \
+ '\ndef %s():' + \
+ '\n loop = ForLoop()' + \
+ '\n _dummy = CompiledTemplate(lambda: None, "dummy")' + \
+ '\n join_ = _dummy._join' + \
+ '\n escape_ = _dummy._escape' + \
+ '\n' + \
+ '\n%s' + \
+ '\n return __template__'
+
+ gen_code = _gen % (name, code)
+ out.write(gen_code)
+ out.write('\n\n')
+ out.write('%s = CompiledTemplate(%s(), %s)\n\n' % (name, name, repr(path)))
+
+ # create template to make sure it compiles
+ t = Template(open(path).read(), path)
+ out.close()
+
class ParseError(Exception):
pass

@@ -1120,25 +1249,30 @@ def test():
Test if, for and while.

>>> t('$if 1: 1')()
- u' 1\n'
+ u'1\n'
>>> t('$if 1:\n 1')()
u'1\n'
>>> t('$if 1:\n 1\\')()
u'1'
>>> t('$if 0: 0\n$elif 1: 1')()
- u' 1\n'
+ u'1\n'
>>> t('$if 0: 0\n$elif None: 0\n$else: 1')()
- u' 1\n'
+ u'1\n'
>>> t('$if 0 < 1 and 1 < 2: 1')()
- u' 1\n'
+ u'1\n'
>>> t('$for x in [1, 2, 3]: $x')()
- u' 1\n 2\n 3\n'
+ u'1\n2\n3\n'
>>> t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1})
- u' 1\n'
+ u'1\n'
>>> t('$for x in [1, 2, 3]:\n\t$x')()
u' 1\n 2\n 3\n'
>>> t('$def with (a)\n$while a and a.pop():1')([1, 2, 3])
u'1\n1\n1\n'
+
+ The space after : must be ignored.
+
+ >>> t('$if True: foo')()
+ u'foo\n'

Test loop.xxx.

@@ -1156,9 +1290,9 @@ def test():
>>> t('$ a = {1: 1}\n$a.keys()[0]')()
u'1\n'
>>> t('$ a = []\n$if not a: 1')()
- u' 1\n'
+ u'1\n'
>>> t('$ a = {}\n$if not a: 1')()
- u' 1\n'
+ u'1\n'
>>> t('$ a = -1\n$a')()
u'-1\n'
>>> t('$ a = "1"\n$a')()
@@ -1234,6 +1368,7 @@ def test():
NameError: global name 'min' is not defined

Test vars.
+
>>> x = t('$var x: 1')()
>>> x.x
u'1'
@@ -1243,9 +1378,35 @@ def test():
>>> x = t('$var x: \n foo\n bar')()
>>> x.x
u'foo\nbar\n'
+
+ Test BOM chars.
+
+ >>> t('\xef\xbb\xbf$def with(x)\n$x')('foo')
+ u'foo\n'
+
+ Test for with weird cases.
+
+ >>> t('$for i in range(10)[1:5]:\n $i')()
+ u'1\n2\n3\n4\n'
+ >>> t("$for k, v in {'a': 1, 'b': 2}.items():\n $k $v")()
+ u'a 1\nb 2\n'
+ >>> t("$for k, v in ({'a': 1, 'b': 2}.items():\n $k $v")()
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+ Test datetime.
+
+ >>> import datetime
+ >>> t("$def with (date)\n$date.strftime('%m %Y')")(datetime.datetime(2009, 1, 1))
+ u'01 2009\n'
"""
pass

if __name__ == "__main__":
- import doctest
- doctest.testmod()
+ import sys
+ if '--compile' in sys.argv:
+ compile_templates(sys.argv[2])
+ else:
+ import doctest
+ doctest.testmod()
diff --git a/vendor/webpy/web/utils.py b/vendor/webpy/web/utils.py
index 3dc53a8..d047fb3 100755
--- a/vendor/webpy/web/utils.py
+++ b/vendor/webpy/web/utils.py
@@ -5,14 +5,14 @@ General Utilities
"""

__all__ = [
- "Storage", "storage", "storify",
- "iters",
- "rstrips", "lstrips", "strips",
+ "Storage", "storage", "storify",
+ "iters",
+ "rstrips", "lstrips", "strips",
"safeunicode", "safestr", "utf8",
"TimeoutError", "timelimit",
"Memoize", "memoize",
"re_compile", "re_subm",
- "group",
+ "group", "uniq", "iterview",
"IterBetter", "iterbetter",
"dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd",
"listget", "intget", "datestr",
@@ -27,15 +27,25 @@ __all__ = [
"sendmail"
]

-import re, sys, time, threading, subprocess
+import re, sys, time, threading, itertools
+
+try:
+ import subprocess
+except ImportError:
+ subprocess = None
+
try: import datetime
except ImportError: pass

+try: set
+except NameError:
+ from sets import Set as set
+
class Storage(dict):
"""
A Storage object is like a dictionary except `obj.foo` can be used
in addition to `obj['foo']`.
-
+
>>> o = storage(a=1)
>>> o.a
1
@@ -49,24 +59,24 @@ class Storage(dict):
Traceback (most recent call last):
...
AttributeError: 'a'
-
+
"""
- def __getattr__(self, key):
+ def __getattr__(self, key):
try:
return self[key]
except KeyError, k:
raise AttributeError, k
-
- def __setattr__(self, key, value):
+
+ def __setattr__(self, key, value):
self[key] = value
-
+
def __delattr__(self, key):
try:
del self[key]
except KeyError, k:
raise AttributeError, k
-
- def __repr__(self):
+
+ def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>'

storage = Storage
@@ -74,16 +84,16 @@ storage = Storage
def storify(mapping, *requireds, **defaults):
"""
Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
- d doesn't have all of the keys in `requireds` and using the default
+ d doesn't have all of the keys in `requireds` and using the default
values for keys found in `defaults`.

For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
`storage({'a':1, 'b':2, 'c':3})`.
-
- If a `storify` value is a list (e.g. multiple values in a form submission),
- `storify` returns the last element of the list, unless the key appears in
+
+ If a `storify` value is a list (e.g. multiple values in a form submission),
+ `storify` returns the last element of the list, unless the key appears in
`defaults` as a list. Thus:
-
+
>>> storify({'a':[1, 2]}).a
2
>>> storify({'a':[1, 2]}, a=[]).a
@@ -92,33 +102,37 @@ def storify(mapping, *requireds, **defaults):
[1]
>>> storify({}, a=[]).a
[]
-
+
Similarly, if the value has a `value` attribute, `storify will return _its_
value, unless the key appears in `defaults` as a dictionary.
-
+
>>> storify({'a':storage(value=1)}).a
1
>>> storify({'a':storage(value=1)}, a={}).a
<Storage {'value': 1}>
>>> storify({}, a={}).a
{}
-
+
Optionally, keyword parameter `_unicode` can be passed to convert all values to unicode.
-
+
>>> storify({'x': 'a'}, _unicode=True)
<Storage {'x': u'a'}>
+ >>> storify({'x': storage(value='a')}, x={}, _unicode=True)
+ <Storage {'x': <Storage {'value': 'a'}>}>
+ >>> storify({'x': storage(value='a')}, _unicode=True)
+ <Storage {'x': u'a'}>
"""
_unicode = defaults.pop('_unicode', False)
def unicodify(s):
if _unicode and isinstance(s, str): return safeunicode(s)
else: return s
-
+
def getvalue(x):
if hasattr(x, 'value'):
return unicodify(x.value)
else:
return unicodify(x)
-
+
stor = Storage()
for key in requireds + tuple(mapping.keys()):
value = mapping[key]
@@ -135,12 +149,12 @@ def storify(mapping, *requireds, **defaults):

for (key, value) in defaults.iteritems():
result = value
- if hasattr(stor, key):
+ if hasattr(stor, key):
result = stor[key]
- if value == () and not isinstance(result, tuple):
+ if value == () and not isinstance(result, tuple):
result = (result,)
setattr(stor, key, result)
-
+
return stor

iters = [list, tuple]
@@ -153,9 +167,9 @@ if sys.version_info < (2,6): # sets module deprecated in 2.6
try:
from sets import Set
iters.append(Set)
- except ImportError:
+ except ImportError:
pass
-
+
class _hack(tuple): pass
iters = _hack(iters)
iters.__doc__ = """
@@ -164,13 +178,13 @@ of lists, tuples, sets, and Sets are available in this version of Python.
"""

def _strips(direction, text, remove):
- if direction == 'l':
- if text.startswith(remove):
+ if direction == 'l':
+ if text.startswith(remove):
return text[len(remove):]
elif direction == 'r':
- if text.endswith(remove):
+ if text.endswith(remove):
return text[:-len(remove)]
- else:
+ else:
raise ValueError, "Direction needs to be r or l."
return text

@@ -180,32 +194,34 @@ def rstrips(text, remove):

>>> rstrips("foobar", "bar")
'foo'
-
+
"""
return _strips('r', text, remove)

def lstrips(text, remove):
"""
removes the string `remove` from the left of `text`
-
+
>>> lstrips("foobar", "foo")
'bar'
-
+
"""
return _strips('l', text, remove)

def strips(text, remove):
- """removes the string `remove` from the both sides of `text`
+ """
+ removes the string `remove` from the both sides of `text`

>>> strips("foobarfoo", "foo")
'bar'
-
+
"""
return rstrips(lstrips(text, remove), remove)

def safeunicode(obj, encoding='utf-8'):
- r"""Converts any given object to unicode string.
-
+ r"""
+ Converts any given object to unicode string.
+
>>> safeunicode('hello')
u'hello'
>>> safeunicode(2)
@@ -222,10 +238,11 @@ def safeunicode(obj, encoding='utf-8'):
return unicode(obj)
else:
return str(obj).decode(encoding)
-
+
def safestr(obj, encoding='utf-8'):
- r"""Converts any given object to utf-8 encoded string.
-
+ r"""
+ Converts any given object to utf-8 encoded string.
+
>>> safestr('hello')
'hello'
>>> safestr(u'\u1234')
@@ -237,23 +254,25 @@ def safestr(obj, encoding='utf-8'):
return obj.encode('utf-8')
elif isinstance(obj, str):
return obj
+ elif hasattr(obj, 'next') and hasattr(obj, '__iter__'): # iterator
+ return itertools.imap(safestr, obj)
else:
return str(obj)

# for backward-compatibility
utf8 = safestr
-
+
class TimeoutError(Exception): pass
def timelimit(timeout):
"""
A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
if it takes longer.
-
+
>>> import time
>>> def meaningoflife():
... time.sleep(.2)
... return 42
- >>>
+ >>>
>>> timelimit(.1)(meaningoflife)()
Traceback (most recent call last):
...
@@ -261,7 +280,7 @@ def timelimit(timeout):
>>> timelimit(1)(meaningoflife)()
42

- _Caveat:_ The function isn't stopped after `timeout` seconds but continues
+ _Caveat:_ The function isn't stopped after `timeout` seconds but continues
executing in a separate thread. (There seems to be no way to kill a thread.)

inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
@@ -296,32 +315,80 @@ def timelimit(timeout):
class Memoize:
"""
'Memoizes' a function, caching its return values for each input.
-
+ If `expires` is specified, values are recalculated after `expires` seconds.
+ If `background` is specified, values are recalculated in a separate thread.
+
+ >>> calls = 0
+ >>> def howmanytimeshaveibeencalled():
+ ... global calls
+ ... calls += 1
+ ... return calls
+ >>> fastcalls = memoize(howmanytimeshaveibeencalled)
+ >>> howmanytimeshaveibeencalled()
+ 1
+ >>> howmanytimeshaveibeencalled()
+ 2
+ >>> fastcalls()
+ 3
+ >>> fastcalls()
+ 3
>>> import time
- >>> def meaningoflife():
- ... time.sleep(.2)
- ... return 42
- >>> fastlife = memoize(meaningoflife)
- >>> meaningoflife()
- 42
- >>> timelimit(.1)(meaningoflife)()
- Traceback (most recent call last):
- ...
- TimeoutError: took too long
- >>> fastlife()
- 42
- >>> timelimit(.1)(fastlife)()
- 42
-
+ >>> fastcalls = memoize(howmanytimeshaveibeencalled, .1, background=False)
+ >>> fastcalls()
+ 4
+ >>> fastcalls()
+ 4
+ >>> time.sleep(.2)
+ >>> fastcalls()
+ 5
+ >>> def slowfunc():
+ ... time.sleep(.1)
+ ... return howmanytimeshaveibeencalled()
+ >>> fastcalls = memoize(slowfunc, .2, background=True)
+ >>> fastcalls()
+ 6
+ >>> timelimit(.05)(fastcalls)()
+ 6
+ >>> time.sleep(.2)
+ >>> timelimit(.05)(fastcalls)()
+ 6
+ >>> timelimit(.05)(fastcalls)()
+ 6
+ >>> time.sleep(.2)
+ >>> timelimit(.05)(fastcalls)()
+ 7
+ >>> fastcalls = memoize(slowfunc, None, background=True)
+ >>> threading.Thread(target=fastcalls).start()
+ >>> time.sleep(.01)
+ >>> fastcalls()
+ 9
"""
- def __init__(self, func):
+ def __init__(self, func, expires=None, background=True):
self.func = func
self.cache = {}
+ self.expires = expires
+ self.background = background
+ self.running = {}
+
def __call__(self, *args, **keywords):
key = (args, tuple(keywords.items()))
- if key not in self.cache:
- self.cache[key] = self.func(*args, **keywords)
- return self.cache[key]
+ if not self.running.get(key):
+ self.running[key] = threading.Lock()
+ def update(block=False):
+ if self.running[key].acquire(block):
+ try:
+ self.cache[key] = (self.func(*args, **keywords), time.time())
+ finally:
+ self.running[key].release()
+
+ if key not in self.cache:
+ update(block=True)
+ elif self.expires and (time.time() - self.cache[key][1]) > self.expires:
+ if self.background:
+ threading.Thread(target=update).start()
+ else:
+ update()
+ return self.cache[key][0]

memoize = Memoize

@@ -331,16 +398,16 @@ A memoized version of re.compile.
"""

class _re_subm_proxy:
- def __init__(self):
+ def __init__(self):
self.match = None
- def __call__(self, match):
+ def __call__(self, match):
self.match = match
return ''

def re_subm(pat, repl, string):
"""
Like re.sub, but returns the replacement _and_ the match object.
-
+
>>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
>>> t
'foooooolish'
@@ -352,25 +419,88 @@ def re_subm(pat, repl, string):
compiled_pat.sub(proxy.__call__, string)
return compiled_pat.sub(repl, string), proxy.match

-def group(seq, size):
+def group(seq, size):
"""
Returns an iterator over a series of lists of length size from iterable.

>>> list(group([1,2,3,4], 2))
[[1, 2], [3, 4]]
"""
- if not hasattr(seq, 'next'):
+ if not hasattr(seq, 'next'):
seq = iter(seq)
- while True:
+ while True:
yield [seq.next() for i in xrange(size)]

+def uniq(seq):
+ """
+ Removes duplicate elements from a list.
+
+ >>> uniq([1,2,3,1,4,5,6])
+ [1, 2, 3, 4, 5, 6]
+ """
+ seen = set()
+ result = []
+ for item in seq:
+ if item in seen: continue
+ seen.add(item)
+ result.append(item)
+ return result
+
+def iterview(x):
+ """
+ Takes an iterable `x` and returns an iterator over it
+ which prints its progress to stderr as it iterates through.
+ """
+ WIDTH = 70
+
+ def plainformat(n, lenx):
+ return '%5.1f%% (%*d/%d)' % ((float(n)/lenx)*100, len(str(lenx)), n, lenx)
+
+ def bars(size, n, lenx):
+ val = int((float(n)*size)/lenx + 0.5)
+ if size - val:
+ spacing = ">" + (" "*(size-val))[1:]
+ else:
+ spacing = ""
+ return "[%s%s]" % ("="*val, spacing)
+
+ def eta(elapsed, n, lenx):
+ if n == 0:
+ return '--:--:--'
+ if n == lenx:
+ secs = int(elapsed)
+ else:
+ secs = int((elapsed/n) * (lenx-n))
+ mins, secs = divmod(secs, 60)
+ hrs, mins = divmod(mins, 60)
+
+ return '%02d:%02d:%02d' % (hrs, mins, secs)
+
+ def format(starttime, n, lenx):
+ out = plainformat(n, lenx) + ' '
+ if n == lenx:
+ end = ' '
+ else:
+ end = ' ETA '
+ end += eta(time.time() - starttime, n, lenx)
+ out += bars(WIDTH - len(out) - len(end), n, lenx)
+ out += end
+ return out
+
+ starttime = time.time()
+ lenx = len(x)
+ for n, y in enumerate(x):
+ sys.stderr.write('\r' + format(starttime, n, lenx))
+ yield y
+ sys.stderr.write('\r' + format(starttime, n+1, lenx) + '\n')
+
class IterBetter:
"""
- Returns an object that can be used as an iterator
- but can also be used via __getitem__ (although it
- cannot go backwards -- that is, you cannot request
+ Returns an object that can be used as an iterator
+ but can also be used via __getitem__ (although it
+ cannot go backwards -- that is, you cannot request
`iterbetter[0]` after requesting `iterbetter[1]`).
-
+
>>> import itertools
>>> c = iterbetter(itertools.count())
>>> c[1]
@@ -382,29 +512,31 @@ class IterBetter:
...
IndexError: already passed 3
"""
- def __init__(self, iterator):
+ def __init__(self, iterator):
self.i, self.c = iterator, 0
- def __iter__(self):
- while 1:
+ def __iter__(self):
+ while 1:
yield self.i.next()
self.c += 1
def __getitem__(self, i):
#todo: slices
- if i < self.c:
+ if i < self.c:
raise IndexError, "already passed "+str(i)
try:
- while i > self.c:
+ while i > self.c:
self.i.next()
self.c += 1
# now self.c == i
self.c += 1
return self.i.next()
- except StopIteration:
+ except StopIteration:
raise IndexError, str(i)
iterbetter = IterBetter

def dictreverse(mapping):
"""
+ Returns a new dictionary with keys and values swapped.
+
>>> dictreverse({1: 2, 3: 4})
{2: 1, 4: 3}
"""
@@ -412,23 +544,23 @@ def dictreverse(mapping):

def dictfind(dictionary, element):
"""
- Returns a key whose value in `dictionary` is `element`
+ Returns a key whose value in `dictionary` is `element`
or, if none exists, None.
-
+
>>> d = {1:2, 3:4}
>>> dictfind(d, 4)
3
>>> dictfind(d, 5)
"""
for (key, value) in dictionary.iteritems():
- if element is value:
+ if element is value:
return key

def dictfindall(dictionary, element):
"""
Returns the keys whose values in `dictionary` are `element`
or, if none exists, [].
-
+
>>> d = {1:4, 3:4}
>>> dictfindall(d, 4)
[1, 3]
@@ -443,9 +575,9 @@ def dictfindall(dictionary, element):

def dictincr(dictionary, element):
"""
- Increments `element` in `dictionary`,
+ Increments `element` in `dictionary`,
setting it to one if it doesn't exist.
-
+
>>> d = {1:2, 3:4}
>>> dictincr(d, 1)
3
@@ -464,7 +596,7 @@ def dictadd(*dicts):
"""
Returns a dictionary consisting of the keys in the argument dictionaries.
If they share a key, the value from the last argument is used.
-
+
>>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
{1: 0, 2: 1, 3: 1}
"""
@@ -476,21 +608,21 @@ def dictadd(*dicts):
def listget(lst, ind, default=None):
"""
Returns `lst[ind]` if it exists, `default` otherwise.
-
+
>>> listget(['a'], 0)
'a'
>>> listget(['a'], 1)
>>> listget(['a'], 1, 'b')
'b'
"""
- if len(lst)-1 < ind:
+ if len(lst)-1 < ind:
return default
return lst[ind]

def intget(integer, default=None):
"""
Returns `integer` as an int or `default` if it can't.
-
+
>>> intget('3')
3
>>> intget('3a')
@@ -505,7 +637,7 @@ def intget(integer, default=None):
def datestr(then, now=None):
"""
Converts a (UTC) datetime object to a nice string representation.
-
+
>>> from datetime import datetime, timedelta
>>> d = datetime(1970, 5, 1)
>>> datestr(d, now=d)
@@ -528,6 +660,8 @@ def datestr(then, now=None):
'January 1, 1969'
>>> datestr(datetime(1970, 6, 1), now=d)
'June 1, 1970'
+ >>> datestr(None)
+ ''
"""
def agohence(n, what, divisor=None):
if divisor: n = n // divisor
@@ -541,9 +675,9 @@ def datestr(then, now=None):
out += 'ago'
return out # '2 days ago'

- if not then: return ""
oneday = 24 * 60 * 60

+ if not then: return ""
if not now: now = datetime.datetime.utcnow()
if type(now).__name__ == "DateTime":
now = datetime.datetime.fromtimestamp(now)
@@ -551,6 +685,7 @@ def datestr(then, now=None):
then = datetime.datetime.fromtimestamp(then)
elif type(then).__name__ == "date":
then = datetime.datetime(then.year, then.month, then.day)
+
delta = now - then
deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06)
deltadays = abs(deltaseconds) // oneday
@@ -583,12 +718,12 @@ def datestr(then, now=None):
def numify(string):
"""
Removes all non-digit characters from `string`.
-
+
>>> numify('800-555-1212')
'8005551212'
>>> numify('800.555.1212')
'8005551212'
-
+
"""
return ''.join([c for c in str(string) if c.isdigit()])

@@ -596,10 +731,10 @@ def denumify(string, pattern):
"""
Formats `string` according to `pattern`, where the letter X gets replaced
by characters from `string`.
-
+
>>> denumify("8005551212", "(XXX) XXX-XXXX")
'(800) 555-1212'
-
+
"""
out = []
for c in pattern:
@@ -613,7 +748,7 @@ def denumify(string, pattern):
def commify(n):
"""
Add commas to an integer `n`.
-
+
>>> commify(1)
'1'
>>> commify(123)
@@ -632,7 +767,7 @@ def commify(n):
'1,234.50'
>>> commify(None)
>>>
-
+
"""
if n is None: return None
n = str(n)
@@ -640,9 +775,9 @@ def commify(n):
dollars, cents = n.split('.')
else:
dollars, cents = n, None
-
+
r = []
- for i, c in enumerate(reversed(str(dollars))):
+ for i, c in enumerate(str(dollars)[::-1]):
if i and (not (i % 3)):
r.insert(0, ',')
r.insert(0, c)
@@ -650,6 +785,7 @@ def commify(n):
if cents:
out += '.' + cents
return out
+
def dateify(datestring):
"""
Formats a numified `datestring` properly.
@@ -674,14 +810,15 @@ def nthstr(n):
['111th', '112th', '113th', '114th', '115th']

"""
-
+
assert n >= 0
if n % 100 in [11, 12, 13]: return '%sth' % n
return {1: '%sst', 2: '%snd', 3: '%srd'}.get(n % 10, '%sth') % n

def cond(predicate, consequence, alternative=None):
- """Function replacement for if-else to use in expressions.
-
+ """
+ Function replacement for if-else to use in expressions.
+
>>> x = 2
>>> cond(x % 2 == 0, "even", "odd")
'even'
@@ -696,15 +833,15 @@ def cond(predicate, consequence, alternative=None):
class CaptureStdout:
"""
Captures everything `func` prints to stdout and returns it instead.
-
+
>>> def idiot():
... print "foo"
>>> capturestdout(idiot)()
'foo\\n'
-
+
**WARNING:** Not threadsafe!
"""
- def __init__(self, func):
+ def __init__(self, func):
self.func = func
def __call__(self, *args, **keywords):
from cStringIO import StringIO
@@ -712,9 +849,9 @@ class CaptureStdout:
out = StringIO()
oldstdout = sys.stdout
sys.stdout = out
- try:
+ try:
self.func(*args, **keywords)
- finally:
+ finally:
sys.stdout = oldstdout
return out.getvalue()

@@ -724,14 +861,14 @@ class Profile:
"""
Profiles `func` and returns a tuple containing its output
and a string with human-readable profiling information.
-
+
>>> import time
>>> out, inf = profile(time.sleep)(.001)
>>> out
>>> inf[:10].strip()
'took 0.0'
"""
- def __init__(self, func):
+ def __init__(self, func):
self.func = func
def __call__(self, *args): ##, **kw): kw unused
import hotshot, hotshot.stats, tempfile ##, time already imported
@@ -743,15 +880,17 @@ class Profile:
stime = time.time() - stime
prof.close()

- def print_stats():
- stats = hotshot.stats.load(temp.name)
- stats.strip_dirs()
- stats.sort_stats('time', 'calls')
- stats.print_stats(40)
- stats.print_callers()
+ import cStringIO
+ out = cStringIO.StringIO()
+ stats = hotshot.stats.load(temp.name)
+ stats.stream = out
+ stats.strip_dirs()
+ stats.sort_stats('time', 'calls')
+ stats.print_stats(40)
+ stats.print_callers()

x = '\n\ntook '+ str(stime) + ' seconds\n'
- x += capturestdout(print_stats)()
+ x += out.getvalue()

return result, x

@@ -770,31 +909,31 @@ if not hasattr(traceback, 'format_exc'):

def tryall(context, prefix=None):
"""
- Tries a series of functions and prints their results.
- `context` is a dictionary mapping names to values;
+ Tries a series of functions and prints their results.
+ `context` is a dictionary mapping names to values;
the value will only be tried if it's callable.
-
+
>>> tryall(dict(j=lambda: True))
j: True
----------------------------------------
results:
True: 1

- For example, you might have a file `test/stuff.py`
- with a series of functions testing various things in it.
+ For example, you might have a file `test/stuff.py`
+ with a series of functions testing various things in it.
At the bottom, have a line:

if __name__ == "__main__": tryall(globals())

- Then you can run `python test/stuff.py` and get the results of
+ Then you can run `python test/stuff.py` and get the results of
all the tests.
"""
context = context.copy() # vars() would update
results = {}
for (key, value) in context.iteritems():
- if not hasattr(value, '__call__'):
+ if not hasattr(value, '__call__'):
continue
- if prefix and not key.startswith(prefix):
+ if prefix and not key.startswith(prefix):
continue
print key + ':',
try:
@@ -805,21 +944,23 @@ def tryall(context, prefix=None):
print 'ERROR'
dictincr(results, 'ERROR')
print ' ' + '\n '.join(traceback.format_exc().split('\n'))
-
+
print '-'*40
print 'results:'
for (key, value) in results.iteritems():
print ' '*2, str(key)+':', value
-
+
class ThreadedDict:
- """Thread local storage.
-
+ """
+ Thread local storage.
+
>>> d = ThreadedDict()
>>> d.x = 1
>>> d.x
1
>>> import threading
>>> def f(): d.x = 2
+ ...
>>> t = threading.Thread(target=f)
>>> t.start()
>>> t.join()
@@ -835,7 +976,7 @@ class ThreadedDict:
def __delattr__(self, key):
return delattr(self._getd(), key)

- def __hash__(self):
+ def __hash__(self):
return id(self)

def _getd(self):
@@ -855,25 +996,25 @@ threadeddict = ThreadedDict
def autoassign(self, locals):
"""
Automatically assigns local variables to `self`.
-
+
>>> self = storage()
>>> autoassign(self, dict(a=1, b=2))
>>> self
<Storage {'a': 1, 'b': 2}>
-
+
Generally used in `__init__` methods, as in:

def __init__(self, foo, bar, baz=1): autoassign(self, locals())
"""
for (key, value) in locals.iteritems():
- if key == 'self':
+ if key == 'self':
continue
setattr(self, key, value)

def to36(q):
"""
Converts an integer to base 36 (a useful scheme for human-sayable IDs).
-
+
>>> to36(35)
'z'
>>> to36(119292)
@@ -884,9 +1025,9 @@ def to36(q):
'0'
>>> to36(-393)
Traceback (most recent call last):
- ...
+ ...
ValueError: must supply a positive integer
-
+
"""
if q < 0: raise ValueError, "must supply a positive integer"
letters = "0123456789abcdefghijklmnopqrstuvwxyz"
@@ -917,12 +1058,12 @@ def safemarkdown(text):
def sendmail(from_address, to_address, subject, message, headers=None, **kw):
"""
Sends the email message `message` with mail and envelope headers
- for from `from_address_` to `to_address` with `subject`.
- Additional email headers can be specified with the dictionary
+ for from `from_address_` to `to_address` with `subject`.
+ Additional email headers can be specified with the dictionary
`headers.

If `web.config.smtp_server` is set, it will send the message
- to that SMTP server. Otherwise it will look for
+ to that SMTP server. Otherwise it will look for
`/usr/sbin/sendmail`, the typical location for the sendmail-style
binary. To use sendmail from a different path, set `web.config.sendmail_path`.
"""
@@ -930,12 +1071,12 @@ def sendmail(from_address, to_address, subject, message, headers=None, **kw):
import webapi
except ImportError:
webapi = Storage(config=Storage())
-
+
if headers is None: headers = {}
-
+
cc = kw.get('cc', [])
bcc = kw.get('bcc', [])
-
+
def listify(x):
if not isinstance(x, list):
return [safestr(x)]
@@ -949,7 +1090,7 @@ def sendmail(from_address, to_address, subject, message, headers=None, **kw):
bcc = listify(bcc)

recipients = to_address + cc + bcc
-
+
headers = dictadd({
'MIME-Version': '1.0',
'Content-Type': 'text/plain; charset=UTF-8',
@@ -961,7 +1102,7 @@ def sendmail(from_address, to_address, subject, message, headers=None, **kw):

if cc:
headers['Cc'] = ", ".join(cc)
-
+
import email.Utils
from_address = email.Utils.parseaddr(from_address)[1]
recipients = [email.Utils.parseaddr(r)[1] for r in recipients]
@@ -971,7 +1112,7 @@ def sendmail(from_address, to_address, subject, message, headers=None, **kw):
if webapi.config.get('smtp_server'):
server = webapi.config.get('smtp_server')
port = webapi.config.get('smtp_port', 0)
- username = webapi.config.get('smtp_username')
+ username = webapi.config.get('smtp_username')
password = webapi.config.get('smtp_password')
debug_level = webapi.config.get('smtp_debuglevel', None)
starttls = webapi.config.get('smtp_starttls', False)
@@ -994,15 +1135,25 @@ def sendmail(from_address, to_address, subject, message, headers=None, **kw):
smtpserver.quit()
else:
sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
-
+
assert not from_address.startswith('-'), 'security'
for r in recipients:
assert not r.startswith('-'), 'security'
-
- p = subprocess.Popen(['/usr/sbin/sendmail', '-f', from_address] + recipients, stdin=subprocess.PIPE)
- p.stdin.write(message)
- p.stdin.close()
- p.wait()
+
+ cmd = [sendmail, '-f', from_address] + recipients
+
+ if subprocess:
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+ p.stdin.write(message)
+ p.stdin.close()
+ p.wait()
+ else:
+ import os
+ i, o = os.popen2(cmd)
+ i.write(message)
+ i.close()
+ o.close()
+ del i, o

if __name__ == "__main__":
import doctest
diff --git a/vendor/webpy/web/webapi.py b/vendor/webpy/web/webapi.py
index 62f4824..06a4672 100644
--- a/vendor/webpy/web/webapi.py
+++ b/vendor/webpy/web/webapi.py
@@ -10,11 +10,22 @@ __all__ = [
"setcookie", "cookies",
"ctx",
"HTTPError",
- "BadRequest", "NotFound", "Gone", "InternalError",
- "badrequest", "notfound", "gone", "internalerror",
- "Redirect", "Found", "SeeOther", "TempRedirect",
- "redirect", "found", "seeother", "tempredirect",
- "NoMethod", "nomethod",
+
+ # 200, 201, 202
+ "OK", "Created", "Accepted",
+ "ok", "created", "accepted",
+
+ # 301, 302, 303, 304, 407
+ "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect",
+ "redirect", "found", "seeother", "notmodified", "tempredirect",
+
+ # 400, 401, 403, 404, 405, 406, 409, 410, 412
+ "BadRequest", "Unauthorized", "Forbidden", "NoMethod", "NotFound", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed",
+ "badrequest", "unauthorized", "forbidden", "nomethod", "notfound", "notacceptable", "conflict", "gone", "preconditionfailed",
+
+ # 500
+ "InternalError",
+ "internalerror",
]

import sys, cgi, Cookie, pprint, urlparse, urllib
@@ -29,52 +40,31 @@ A configuration object for various aspects of web.py.
"""

class HTTPError(Exception):
- def __init__(self, status, headers, data=""):
+ def __init__(self, status, headers={}, data=""):
ctx.status = status
for k, v in headers.items():
header(k, v)
self.data = data
Exception.__init__(self, status)
+
+def _status_code(status, data=None, classname=None, docstring=None):
+ if data is None:
+ data = status.split(" ", 1)[1]
+ classname = status.split(" ", 1)[1].replace(' ', '') # 304 Not Modified -> NotModified
+ docstring = docstring or '`%s` status' % status

-class BadRequest(HTTPError):
- """`400 Bad Request` error."""
- message = "bad request"
- def __init__(self):
- status = "400 Bad Request"
- headers = {'Content-Type': 'text/html'}
- HTTPError.__init__(self, status, headers, self.message)
-
-badrequest = BadRequest
-
-class _NotFound(HTTPError):
- """`404 Not Found` error."""
- message = "not found"
- def __init__(self, message=None):
- status = '404 Not Found'
- headers = {'Content-Type': 'text/html'}
- HTTPError.__init__(self, status, headers, message or self.message)
-
-def NotFound(message=None):
- """Returns HTTPError with '404 Not Found' error from the active application.
- """
- if message:
- return _NotFound(message)
- elif ctx.get('app_stack'):
- return ctx.app_stack[-1].notfound()
- else:
- return _NotFound()
-
-notfound = NotFound
-
-class Gone(HTTPError):
- """`410 Gone` error."""
- message = "gone"
- def __init__(self):
- status = '410 Gone'
- headers = {'Content-Type': 'text/html'}
- HTTPError.__init__(self, status, headers, self.message)
+ def __init__(self, data=data, headers={}):
+ HTTPError.__init__(self, status, headers, data)
+
+ # trick to create class dynamically with dynamic docstring.
+ return type(classname, (HTTPError, object), {
+ '__doc__': docstring,
+ '__init__': __init__
+ })

-gone = Gone
+ok = OK = _status_code("200 OK", data="")
+created = Created = _status_code("201 Created")
+accepted = Accepted = _status_code("202 Accepted")

class Redirect(HTTPError):
"""A `301 Moved Permanently` redirect."""
@@ -115,6 +105,13 @@ class SeeOther(Redirect):

seeother = SeeOther

+class NotModified(HTTPError):
+ """A `304 Not Modified` status."""
+ def __init__(self):
+ HTTPError.__init__(self, "304 Not Modified")
+
+notmodified = NotModified
+
class TempRedirect(Redirect):
"""A `307 Temporary Redirect` redirect."""
def __init__(self, url, absolute=False):
@@ -122,6 +119,42 @@ class TempRedirect(Redirect):

tempredirect = TempRedirect

+class BadRequest(HTTPError):
+ """`400 Bad Request` error."""
+ message = "bad request"
+ def __init__(self):
+ status = "400 Bad Request"
+ headers = {'Content-Type': 'text/html'}
+ HTTPError.__init__(self, status, headers, self.message)
+
+badrequest = BadRequest
+
+class _NotFound(HTTPError):
+ """`404 Not Found` error."""
+ message = "not found"
+ def __init__(self, message=None):
+ status = '404 Not Found'
+ headers = {'Content-Type': 'text/html'}
+ HTTPError.__init__(self, status, headers, message or self.message)
+
+def NotFound(message=None):
+ """Returns HTTPError with '404 Not Found' error from the active application.
+ """
+ if message:
+ return _NotFound(message)
+ elif ctx.get('app_stack'):
+ return ctx.app_stack[-1].notfound()
+ else:
+ return _NotFound()
+
+notfound = NotFound
+
+unauthorized = Unauthorized = _status_code("401 Unauthorized")
+forbidden = Forbidden = _status_code("403 Forbidden")
+notacceptable = NotAcceptable = _status_code("406 Not Acceptable")
+conflict = Conflict = _status_code("409 Conflict")
+preconditionfailed = PreconditionFailed = _status_code("412 Precondition Failed")
+
class NoMethod(HTTPError):
"""A `405 Method Not Allowed` error."""
def __init__(self, cls=None):
@@ -139,6 +172,16 @@ class NoMethod(HTTPError):

nomethod = NoMethod

+class Gone(HTTPError):
+ """`410 Gone` error."""
+ message = "gone"
+ def __init__(self):
+ status = '410 Gone'
+ headers = {'Content-Type': 'text/html'}
+ HTTPError.__init__(self, status, headers, self.message)
+
+gone = Gone
+
class _InternalError(HTTPError):
"""500 Internal Server Error`."""
message = "internal server error"
diff --git a/vendor/webpy/web/wsgi.py b/vendor/webpy/web/wsgi.py
index 8616ba7..f3f0488 100644
--- a/vendor/webpy/web/wsgi.py
+++ b/vendor/webpy/web/wsgi.py
@@ -51,6 +51,16 @@ def runwsgi(func):
else:
return runscgi(func)

- # When running the builtin-server, enable debug mode if not already set.
- web.config.setdefault('debug', True)
return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
+
+def _is_dev_mode():
+ # quick hack to check if the program is running in dev mode.
+ if os.environ.has_key('SERVER_SOFTWARE') \
+ or os.environ.has_key('PHP_FCGI_CHILDREN') \
+ or 'fcgi' in sys.argv or 'fastcgi' in sys.argv \
+ or 'mod_wsgi' in sys.argv:
+ return False
+ return True
+
+# When running the builtin-server, enable debug mode if not already set.
+web.config.setdefault('debug', _is_dev_mode())
diff --git a/vendor/webpy/web/wsgiserver/__init__.py b/vendor/webpy/web/wsgiserver/__init__.py
index d8ab40c..c380e18 100644
--- a/vendor/webpy/web/wsgiserver/__init__.py
+++ b/vendor/webpy/web/wsgiserver/__init__.py
@@ -88,6 +88,9 @@ try:
import cStringIO as StringIO
except ImportError:
import StringIO
+
+_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
+
import sys
import threading
import time
@@ -332,7 +335,12 @@ class HTTPRequest(object):

environ = self.environ

- method, path, req_protocol = request_line.strip().split(" ", 2)
+ try:
+ method, path, req_protocol = request_line.strip().split(" ", 2)
+ except ValueError:
+ self.simple_response(400, "Malformed Request-Line")
+ return
+
environ["REQUEST_METHOD"] = method

# path may be an abs_path (including "http://host.domain.tld");
@@ -402,13 +410,6 @@ class HTTPRequest(object):
self.simple_response("413 Request Entity Too Large")
return

- # Set AUTH_TYPE, REMOTE_USER
- creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1)
- environ["AUTH_TYPE"] = creds[0]
- if creds[0].lower() == 'basic':
- user, pw = base64.decodestring(creds[1]).split(":", 1)
- environ["REMOTE_USER"] = user
-
# Persistent connection support
if self.response_protocol == "HTTP/1.1":
# Both server and client are HTTP/1.1
@@ -588,7 +589,12 @@ class HTTPRequest(object):
buf.append("\r\n")
if msg:
buf.append(msg)
- self.wfile.sendall("".join(buf))
+
+ try:
+ self.wfile.sendall("".join(buf))
+ except socket.error, x:
+ if x.args[0] not in socket_errors_to_ignore:
+ raise

def start_response(self, status, headers, exc_info = None):
"""WSGI callable to begin the HTTP response."""
@@ -646,7 +652,8 @@ class HTTPRequest(object):
if status < 200 or status in (204, 205, 304):
pass
else:
- if self.response_protocol == 'HTTP/1.1':
+ if (self.response_protocol == 'HTTP/1.1'
+ and self.environ["REQUEST_METHOD"] != 'HEAD'):
# Use the chunked transfer-coding
self.chunked_write = True
self.outheaders.append(("Transfer-Encoding", "chunked"))
@@ -710,7 +717,8 @@ class FatalSSLAlert(Exception):
"""Exception raised when the SSL implementation signals a fatal alert."""
pass

-if sys.version_info[:2] >= (2, 6):
+
+if not _fileobject_uses_str_type:
class CP_fileobject(socket._fileobject):
"""Faux file object attached to a socket object."""

@@ -738,7 +746,8 @@ if sys.version_info[:2] >= (2, 6):
try:
return self._sock.recv(size)
except socket.error, e:
- if e.args[0] not in socket_errors_nonblocking:
+ if (e.args[0] not in socket_errors_nonblocking
+ and e.args[0] not in socket_error_eintr):
raise

def read(self, size=-1):
@@ -838,8 +847,8 @@ if sys.version_info[:2] >= (2, 6):
nl = data.find('\n')
if nl >= 0:
nl += 1
- buf.write(buffer(data, 0, nl))
- self._rbuf.write(buffer(data, nl))
+ buf.write(data[:nl])
+ self._rbuf.write(data[nl:])
del data
break
buf.write(data)
@@ -865,9 +874,9 @@ if sys.version_info[:2] >= (2, 6):
if nl >= 0:
nl += 1
# save the excess data to _rbuf
- self._rbuf.write(buffer(data, nl))
+ self._rbuf.write(data[nl:])
if buf_len:
- buf.write(buffer(data, 0, nl))
+ buf.write(data[:nl])
break
else:
# Shortcut. Avoid data copy through buf when returning
@@ -879,8 +888,8 @@ if sys.version_info[:2] >= (2, 6):
# returning exactly all of our first recv().
return data
if n >= left:
- buf.write(buffer(data, 0, left))
- self._rbuf.write(buffer(data, left))
+ buf.write(data[:left])
+ self._rbuf.write(data[left:])
break
buf.write(data)
buf_len += n
@@ -915,7 +924,8 @@ else:
try:
return self._sock.recv(size)
except socket.error, e:
- if e.args[0] not in socket_errors_nonblocking:
+ if (e.args[0] not in socket_errors_nonblocking
+ and e.args[0] not in socket_error_eintr):
raise

def read(self, size=-1):
@@ -958,7 +968,7 @@ else:
buffers[-1] = data[:left]
break
buf_len += n
- return "".join(buffers)
+ return "".join(buffers)

def readline(self, size=-1):
data = self._rbuf
@@ -1029,6 +1039,7 @@ else:
break
buf_len += n
return "".join(buffers)
+

class SSL_fileobject(CP_fileobject):
"""SSL file object attached to a socket object."""
@@ -1180,28 +1191,39 @@ class HTTPConnection(object):
# Close the connection.
return
except NoSSLError:
- # Unwrap our wfile
- req.wfile = CP_fileobject(self.socket, "wb", -1)
if req and not req.sent_headers:
+ # Unwrap our wfile
+ req.wfile = CP_fileobject(self.socket._sock, "wb", -1)
req.simple_response("400 Bad Request",
"The client sent a plain HTTP request, but "
"this server only speaks HTTPS on this port.")
+ self.linger = True
except Exception, e:
if req and not req.sent_headers:
req.simple_response("500 Internal Server Error", format_exc())

+ linger = False
+
def close(self):
"""Close the socket underlying this connection."""
self.rfile.close()

- # Python's socket module does NOT call close on the kernel socket
- # when you call socket.close(). We do so manually here because we
- # want this server to send a FIN TCP segment immediately. Note this
- # must be called *before* calling socket.close(), because the latter
- # drops its reference to the kernel socket.
- self.socket._sock.close()
-
- self.socket.close()
+ if not self.linger:
+ # Python's socket module does NOT call close on the kernel socket
+ # when you call socket.close(). We do so manually here because we
+ # want this server to send a FIN TCP segment immediately. Note this
+ # must be called *before* calling socket.close(), because the latter
+ # drops its reference to the kernel socket.
+ self.socket._sock.close()
+ self.socket.close()
+ else:
+ # On the other hand, sometimes we want to hang around for a bit
+ # to make sure the client has a chance to read our entire
+ # response. Skipping the close() calls here delays the FIN
+ # packet until the socket object is garbage-collected later.
+ # Someday, perhaps, we'll do the full lingering_close that
+ # Apache does, but not today.
+ pass


def format_exc(limit=None):
@@ -1379,6 +1401,27 @@ class SSLConnection:
""" % (f, f)


+try:
+ import fcntl
+except ImportError:
+ try:
+ from ctypes import windll, WinError
+ except ImportError:
+ def prevent_socket_inheritance(sock):
+ """Dummy function, since neither fcntl nor ctypes are available."""
+ pass
+ else:
+ def prevent_socket_inheritance(sock):
+ """Mark the given socket fd as non-inheritable (Windows)."""
+ if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
+ raise WinError()
+else:
+ def prevent_socket_inheritance(sock):
+ """Mark the given socket fd as non-inheritable (POSIX)."""
+ fd = sock.fileno()
+ old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
+

class CherryPyWSGIServer(object):
"""An HTTP server for WSGI.
@@ -1425,7 +1468,7 @@ class CherryPyWSGIServer(object):

protocol = "HTTP/1.1"
_bind_addr = "127.0.0.1"
- version = "CherryPy/3.1.0"
+ version = "CherryPy/3.1.2"
ready = False
_interrupt = None

@@ -1572,6 +1615,7 @@ class CherryPyWSGIServer(object):
def bind(self, family, type, proto=0):
"""Create (or recreate) the actual socket object."""
self.socket = socket.socket(family, type, proto)
+ prevent_socket_inheritance(self.socket)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if self.nodelay:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
@@ -1585,12 +1629,25 @@ class CherryPyWSGIServer(object):
ctx.use_certificate_file(self.ssl_certificate)
self.socket = SSLConnection(ctx, self.socket)
self.populate_ssl_environ()
+
+ # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
+ # activate dual-stack. See http://www.cherrypy.org/ticket/871.
+ if (not isinstance(self.bind_addr, basestring)
+ and self.bind_addr[0] == '::' and family == socket.AF_INET6):
+ try:
+ self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+ except (AttributeError, socket.error):
+ # Apparently, the socket option is not available in
+ # this machine's TCP stack
+ pass
+
self.socket.bind(self.bind_addr)

def tick(self):
"""Accept a new connection and put it on the Queue."""
try:
s, addr = self.socket.accept()
+ prevent_socket_inheritance(s)
if not self.ready:
return
if hasattr(s, 'settimeout'):
@@ -1599,7 +1656,8 @@ class CherryPyWSGIServer(object):
environ = self.environ.copy()
# SERVER_SOFTWARE is common for IIS. It's also helpful for
# us to pass a default value for the "Server" response header.
- environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
+ if environ.get("SERVER_SOFTWARE") is None:
+ environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
# set a non-standard environ entry so the WSGI app can know what
# the *real* server protocol is (and what features to support).
# See http://www.faqs.org/rfcs/rfc2145.html.
@@ -1662,7 +1720,7 @@ class CherryPyWSGIServer(object):
try:
host, port = sock.getsockname()[:2]
except socket.error, x:
- if x.args[1] != "Bad file descriptor":
+ if x.args[0] not in socket_errors_to_ignore:
raise
else:
# Note that we're explicitly NOT using AI_PASSIVE,


hooks/post-receive
--
watchdog

Reply all
Reply to author
Forward
0 new messages