Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

support return instead of print

  • Loading branch information...
commit 9a31faab1211db871bac72a68e9d461bb82d4472 1 parent fd1d695
Anand Chitipothu anandology authored
3  web/__init__.py
View
@@ -13,7 +13,8 @@
import utils, db, net, wsgi, http, webapi, request, httpserver, debugerror
import template, form
-import session
+
+#import session
from utils import *
from db import *
229 web/application.py
View
@@ -1,11 +1,17 @@
"""
-Web application.
+Web application
+(from web.py)
"""
import webapi as web
import webapi, wsgi, utils
from request import nomethod
+from utils import lstrips
import urllib
+import traceback
+import itertools
+import os
+import types
__all__ = [
"application", "auto_application",
@@ -26,8 +32,8 @@ class application:
>>> class hello:
... def GET(self): return "hello"
>>>
- >>> app.request("/hello")
- "hello"
+ >>> app.request("/hello").data
+ 'hello'
"""
def __init__(self, mapping=(), fvars={}):
self.mapping = mapping
@@ -41,48 +47,48 @@ def add_processor(self, processor):
"""Adds a processor to the application.
>>> urls = ("/(.*)", "echo")
- >>> app = web.application(urls, globals())
+ >>> app = application(urls, globals())
>>> class echo:
... def GET(self, name): return name
...
>>>
>>> def hello(handler): return "hello, " + handler()
>>> app.add_processor(hello)
- >>> app.request("/web.py")
- "hello, web.py"
+ >>> app.request("/web.py").data
+ 'hello, web.py'
"""
self.processors.append(processor)
def request(self, path='/', method='GET', host="0.0.0.0:8080"):
- path, query = urllib.splitquery(path)
- query = query or ""
-
- homepath = ""
- d = {
- 'home': "http://" + host + homepath,
- 'homedomain': "http://" + host,
- 'homepath': homepath,
- 'host': host,
- 'ip': '127.0.0.1',
- 'method': method,
- 'path': path,
- 'query': query,
- 'output': '',
- }
- env = {
- 'REQUEST_METHOD': method,
- 'QUERY_STRING': query
- }
- web.ctx.environ = web.ctx.env = env
- web.ctx.update(d)
- return self.handle()
+ """Makes request to this application for the specified path and method.
+ Response will be a storage object with data, status and headers.
+
+ >>> urls = ("/hello", "hello")
+ >>> app = application(urls, globals())
+ >>> class hello:
+ ... def GET(self):
+ ... web.header('Content-Type', 'text/plain')
+ ... return "hello"
+ ...
+ >>> response = app.request("/hello")
+ >>> response.data
+ 'hello'
+ >>> response.status
+ '200 OK'
+ >>> response.headers['Content-Type']
+ 'text/plain'
+ """
+ query = urllib.splitquery(path)[1] or ""
+ env = dict(HTTP_HOST=host, REQUEST_METHOD=method, PATH_INFO=path, QUERY_STRING=query)
+ self.load(env)
+ data = self.handle_with_processors()
+ return web.storage(data=data, status=web.ctx.status, headers=dict(web.ctx.headers))
- def handle(self, autoreload=False):
- #TODO: take care of reloading
+ def handle(self):
fn, args = self._match(self.mapping, web.ctx.path)
return self._delegate(fn, self.fvars, args)
- def xhandle(self):
+ def handle_with_processors(self):
def process(processors):
if processors:
p, processors = processors[0], processors[1:]
@@ -91,7 +97,47 @@ def process(processors):
return self.handle()
# processors must be applied in the resvere order.
- return process(self.processors[::-1])
+ return process(self.processors)
+
+ def wsgifunc(self, *middleware):
+ """Returns a WSGI-compatible function for this application."""
+ def peep(iterator):
+ """Peeps into an iterator by doing an iteration
+ and returns an equivalent iterator.
+ """
+ # wsgi requires the headers first
+ # so we need to do an iteration
+ # and save the result for later
+ try:
+ 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:
+ result = self.handle_with_processors()
+ except:
+ print >> web.debug, traceback.format_exc()
+ result = web.internalerror()
+
+ if is_generator(result):
+ result = peep(result)
+ else:
+ result = [str(result)]
+
+ status, headers = web.ctx.status, web.ctx.headers
+ start_resp(status, headers)
+ return result
+
+ for m in middleware:
+ wsgi = m(wsgi)
+
+ return wsgi
def run(self, *middleware):
"""
@@ -103,24 +149,52 @@ def run(self, *middleware):
`middleware` is a list of WSGI middleware which is applied to the resulting WSGI
function.
"""
- def handle():
- try:
- result = self.xhandle()
- # web.py expects print
- if result:
- print result
- except NotFound:
- web.notfound()
-
- return wsgi.runwsgi(webapi.wsgifunc(handle, *middleware))
+ return wsgi.runwsgi(self.wsgifunc(*middleware))
+
+ def load(self, env):
+ """Initializes ctx using env."""
+ ctx = web.ctx
+
+ ctx.status = '200 OK'
+ ctx.headers = []
+ ctx.output = ''
+ ctx.environ = ctx.env = env
+ ctx.host = env.get('HTTP_HOST')
+ ctx.homedomain = 'http://' + env.get('HTTP_HOST', '[unknown]')
+ ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))
+ ctx.home = ctx.homedomain + ctx.homepath
+ ctx.ip = env.get('REMOTE_ADDR')
+ ctx.method = env.get('REQUEST_METHOD')
+ ctx.path = env.get('PATH_INFO')
+ # 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)
+
+ if env.get('QUERY_STRING'):
+ ctx.query = '?' + env.get('QUERY_STRING', '')
+ else:
+ ctx.query = ''
+
+ ctx.fullpath = ctx.path + ctx.query
def _delegate(self, f, fvars, args=[]):
+ def handle_class(cls):
+ meth = web.ctx.method
+ if meth == 'HEAD' and not hasattr(cls, meth):
+ meth = 'GET'
+ if not hasattr(cls, meth):
+ return nomethod(cls)
+ tocall = getattr(cls(), meth)
+ return tocall(*args)
+
+ def is_class(o): return isinstance(o, (types.ClassType, type))
+
if f is None:
raise NotFound
elif isinstance(f, application):
return f.handle()
- elif hasattr(f, '__call__'):
- return f()
+ elif is_class(f):
+ return handle_class(f)
elif isinstance(f, str):
if '.' in f:
x = f.split('.')
@@ -129,14 +203,9 @@ def _delegate(self, f, fvars, args=[]):
cls = getattr(mod, cls)
else:
cls = fvars[f]
-
- meth = web.ctx.method
- if meth == 'HEAD' and not hasattr(cls, meth):
- meth = 'GET'
- if not hasattr(cls, meth):
- return nomethod(cls)
- tocall = getattr(cls(), meth)
- return tocall(*args)
+ return handle_class(cls)
+ elif hasattr(f, '__call__'):
+ return f()
else:
return web.notfound()
@@ -159,27 +228,23 @@ class auto_application(application):
>>> class foo(app.page):
... path = '/foo/.*'
... def GET(self): return "foo"
- >>>
- >>> app.request('/hello')
- "hello, world"
- >>> app.request('/foo/bar')
- "foo"
+ >>> app.request("/hello").data
+ 'hello, world'
+ >>> app.request('/foo/bar').data
+ 'foo'
"""
def __init__(self):
- self.urls = urls = []
- application.__init__(self, self.urls)
+ application.__init__(self)
class metapage(type):
def __init__(klass, name, bases, attrs):
type.__init__(klass, name, bases, attrs)
- mod, name = klass.__module__, klass.__name__
- path = getattr(klass, 'path', '/' + name)
+ path = attrs.get('path', '/' + name)
# path can be specified as None to ignore that class
# typically required to create a abstract base class.
if path is not None:
- urls.append(path)
- urls.append(mod + '.' + name)
+ self.add_mapping(path, klass)
class page:
path = None
@@ -191,17 +256,16 @@ class subdir_application(application):
"""Application to delegate requests based on directory.
>>> urls = ("/hello", "hello")
- >>> app = application(urls)
+ >>> app = application(urls, globals())
>>> class hello:
... def GET(self): return "hello"
>>>
>>> mapping = ("/foo", app)
>>> app2 = subdir_application(mapping)
- >>> app2.request("/foo/hello")
- "hello"
+ >>> app2.request("/foo/hello").data
+ 'hello'
"""
- def handle(self, autoreload=False):
- #TODO: take care of reloading
+ def handle(self):
for dir, what in utils.group(self.mapping, 2):
if web.ctx.path.startswith(dir + '/'):
# change paths to make path relative to dir
@@ -217,17 +281,20 @@ class subdomain_application(application):
"""Application to delegate requests based on the host.
>>> urls = ("/hello", "hello")
- >>> app = application(urls)
+ >>> app = application(urls, globals())
>>> class hello:
... def GET(self): return "hello"
>>>
>>> mapping = ("hello.example.com", app)
>>> app2 = subdomain_application(mapping)
- >>> app2.request("/hello", host="hello.example.com")
- "hello2"
+ >>> app2.request("/hello", host="hello.example.com").data
+ 'hello'
+ >>> app2.request("/hello", host="something.example.com").data
+ Traceback (most recent call last):
+ ...
+ NotFound
"""
- def handle(self, autoreload=False):
- #TODO: take care of reloading
+ def handle(self):
host = web.ctx.host.split(':')[0] #strip port
fn, args = self._match(self.mapping, host)
return self._delegate(fn, self.fvars, args)
@@ -244,11 +311,11 @@ class combine_applications(application):
... def GET(self): return "bar"
...
>>> app = combine_applications(app1, app2)
- >>> app.request('/foo')
- "foo"
- >>> app.request('/bar')
- "bar"
- >>> app.request("/hello")
+ >>> app.request('/foo').data
+ 'foo'
+ >>> app.request('/bar').data
+ 'bar'
+ >>> app.request("/hello").data
Traceback (most recent call last):
...
NotFound
@@ -257,7 +324,7 @@ def __init__(self, *apps):
self.apps = apps
application.__init__(self)
- def handle(self, autoreload=False):
+ def handle(self):
for a in self.apps:
try:
return a.handle()
@@ -268,7 +335,7 @@ def handle(self, autoreload=False):
def loadhook(h):
"""Converts a load hook into an application processor.
- >>> app = web.auto_application()
+ >>> app = auto_application()
>>> def f(): "something done before handling request"
...
>>> app.add_processor(loadhook(f))
@@ -282,7 +349,7 @@ def processor(handler):
def unloadhook(h):
"""Converts an unload hook into an application processor.
- >>> app = web.auto_application()
+ >>> app = auto_application()
>>> def f(): "something done after handling request"
...
>>> app.add_processor(unloadhook(f))
1  web/http.py
View
@@ -230,7 +230,6 @@ def __init__(self, func):
# return wsgifunc
#
-
def check(self):
for mod in sys.modules.values():
try:
68 web/utils.py
View
@@ -691,41 +691,43 @@ def tryall(context, prefix=None):
print ' '*2, str(key)+':', value
class ThreadedDict:
- """
- Takes a dictionary that maps threads to objects.
- When a thread tries to get or set an attribute or item
- of the threadeddict, it passes it on to the object
- for that thread in dictionary.
- """
- def __init__(self, dictionary):
- self.__dict__['_ThreadedDict__d'] = dictionary
-
- def __getattr__(self, attr):
- return getattr(self.__d[threading.currentThread()], attr)
-
- def __getitem__(self, item):
- return self.__d[threading.currentThread()][item]
-
- def __setattr__(self, attr, value):
- if attr == '__doc__':
- self.__dict__[attr] = value
- else:
- return setattr(self.__d[threading.currentThread()], attr, value)
-
- def __delattr__(self, item):
- try:
- del self.__d[threading.currentThread()][item]
- except KeyError, k:
- raise AttributeError, k
-
- def __delitem__(self, item):
- del self.__d[threading.currentThread()][item]
-
- def __setitem__(self, item, value):
- self.__d[threading.currentThread()][item] = value
+ """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()
+ >>> d.x
+ 1
+ """
+ def __getattr__(self, key):
+ return getattr(self._getd(), key)
+
+ def __setattr__(self, key, value):
+ return setattr(self._getd(), key, value)
+
+ def __delattr__(self, key):
+ return delattr(self._getd(), key)
+
def __hash__(self):
- return hash(self.__d[threading.currentThread()])
+ return id(self)
+
+ def _getd(self):
+ t = threading.currentThread()
+ if not hasattr(t, '_d'):
+ # using __dict__ of thread as thread local storage
+ t._d = {}
+
+ # there could be multiple instances of ThreadedDict.
+ # use self as key
+ if self not in t._d:
+ t._d[self] = storage()
+ return t._d[self]
threadeddict = ThreadedDict
190 web/webapi.py
View
@@ -6,18 +6,14 @@
__all__ = [
"config",
"badrequest", "notfound", "gone", "internalerror",
- "header", "output", "flush", "debug",
+ "header", "debug",
"input", "data",
"setcookie", "cookies",
"ctx",
- "loadhooks", "load", "unloadhooks", "unload", "_loadhooks",
- "wsgifunc"
]
-import sys, os, cgi, threading, Cookie, pprint, traceback
-try: import itertools
-except ImportError: pass
-from utils import storage, storify, threadeddict, dictadd, intget, lstrips, utf8
+import sys, cgi, Cookie, pprint
+from utils import storage, storify, threadeddict, dictadd, intget, utf8
config = storage()
config.__doc__ = """
@@ -45,25 +41,25 @@ def badrequest():
"""Return a `400 Bad Request` error."""
ctx.status = '400 Bad Request'
header('Content-Type', 'text/html')
- return output('bad request')
+ return 'bad request'
def notfound():
"""Returns a `404 Not Found` error."""
ctx.status = '404 Not Found'
header('Content-Type', 'text/html')
- return output('not found')
+ return 'not found'
def gone():
"""Returns a `410 Gone` error."""
ctx.status = '410 Gone'
header('Content-Type', 'text/html')
- return output("gone")
+ return "gone"
def internalerror():
"""Returns a `500 Internal Server` error."""
ctx.status = "500 Internal Server Error"
ctx.headers = [('Content-Type', 'text/html')]
- ctx.output = "internal server error"
+ return "internal server error"
def header(hdr, value, unique=False):
"""
@@ -83,18 +79,6 @@ def header(hdr, value, unique=False):
ctx.headers.append((hdr, value))
-def output(string_):
- """Appends `string_` to the response."""
- if isinstance(string_, unicode): string_ = string_.encode('utf8')
- if ctx.get('flush'):
- ctx._write(string_)
- else:
- ctx.output += str(string_)
-
-def flush():
- ctx.flush = True
- return flush
-
def input(*requireds, **defaults):
"""
Returns a `storage` object with the GET and POST arguments.
@@ -179,30 +163,7 @@ def _debugwrite(x):
out.write(x)
debug.write = _debugwrite
-class _outputter:
- """Wraps `sys.stdout` so that print statements go into the response."""
- def __init__(self, file): self.file = file
- def write(self, string_):
- if hasattr(ctx, 'output'):
- return output(string_)
- else:
- self.file.write(string_)
- def __getattr__(self, attr): return getattr(self.file, attr)
- def __getitem__(self, item): return self.file[item]
-
-def _capturedstdout():
- sysstd = sys.stdout
- while hasattr(sysstd, 'file'):
- if isinstance(sys.stdout, _outputter): return True
- sysstd = sysstd.file
- if isinstance(sys.stdout, _outputter): return True
- return False
-
-if not _capturedstdout():
- sys.stdout = _outputter(sys.stdout)
-
-_context = {threading.currentThread(): storage()}
-ctx = context = threadeddict(_context)
+ctx = context = threadeddict()
ctx.__doc__ = """
A `storage` object containing various information about the request:
@@ -242,137 +203,4 @@ def _capturedstdout():
`output`
: A string to be used as the response.
-"""
-
-loadhooks = {}
-_loadhooks = {}
-
-def load():
- """
- Loads a new context for the thread.
-
- You can ask for a function to be run at loadtime by
- adding it to the dictionary `loadhooks`.
- """
- _context[threading.currentThread()] = storage()
- ctx.status = '200 OK'
- ctx.headers = []
- if config.get('db_parameters'):
- import db
- db.connect(**config.db_parameters)
-
- for x in loadhooks.values(): x()
-
-def _load(env):
- load()
- ctx.output = ''
- ctx.environ = ctx.env = env
- ctx.host = env.get('HTTP_HOST')
- ctx.homedomain = 'http://' + env.get('HTTP_HOST', '[unknown]')
- ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))
- ctx.home = ctx.homedomain + ctx.homepath
- ctx.ip = env.get('REMOTE_ADDR')
- ctx.method = env.get('REQUEST_METHOD')
- ctx.path = env.get('PATH_INFO')
- # http://trac.lighttpd.net/trac/ticket/406 requires:
- if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
- ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0],
- os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')))
-
- if env.get('QUERY_STRING'):
- ctx.query = '?' + env.get('QUERY_STRING', '')
- else:
- ctx.query = ''
-
- ctx.fullpath = ctx.path + ctx.query
- for x in _loadhooks.values(): x()
-
-unloadhooks = {}
-
-def unload():
- """
- Unloads the context for the thread.
-
- You can ask for a function to be run at loadtime by
- adding it ot the dictionary `unloadhooks`.
- """
- for x in unloadhooks.values(): x()
- # ensures db cursors and such are GCed promptly
- del _context[threading.currentThread()]
-
-def _unload():
- unload()
-
-def wsgifunc(func, *middleware):
- """Returns a WSGI-compatible function from a webpy-function."""
- middleware = list(middleware)
-
- def wsgifunc(env, start_resp):
- _load(env)
- try:
- result = func()
- except StopIteration:
- result = None
- except:
- print >> debug, traceback.format_exc()
- result = internalerror()
-
- is_generator = result and hasattr(result, 'next')
- if is_generator:
- # wsgi requires the headers first
- # so we need to do an iteration
- # and save the result for later
- try:
- firstchunk = result.next()
- except StopIteration:
- firstchunk = ''
-
- status, headers, output = ctx.status, ctx.headers, ctx.output
- ctx._write = start_resp(status, headers)
-
- # and now, the fun:
-
- def cleanup():
- # we insert this little generator
- # at the end of our itertools.chain
- # so that it unloads the request
- # when everything else is done
-
- yield '' # force it to be a generator
- _unload()
-
- # result is the output of calling the webpy function
- # it could be a generator...
-
- if is_generator:
- if firstchunk is flush:
- # oh, it's just our special flush mode
- # ctx._write is set up, so just continue execution
- try:
- result.next()
- except StopIteration:
- pass
-
- _unload()
- return []
- else:
- return itertools.chain([firstchunk], result, cleanup())
-
- # ... but it's usually just None
- #
- # output is the stuff in ctx.output
- # it's usually a string...
- if isinstance(output, str): #@@ other stringlikes?
- _unload()
- return [output]
- # it could be a generator...
- elif hasattr(output, 'next'):
- return itertools.chain(output, cleanup())
- else:
- _unload()
- raise Exception, "Invalid ctx.output"
-
- for mw_func in middleware:
- wsgifunc = mw_func(wsgifunc)
-
- return wsgifunc
+"""
Please sign in to comment.
Something went wrong with that request. Please try again.