diff options
-rwxr-xr-x | bottle.py | 116 |
1 files changed, 101 insertions, 15 deletions
@@ -88,6 +88,7 @@ from tempfile import TemporaryFile from traceback import format_exc from urllib import quote as urlquote from urlparse import urlunsplit, urljoin +from weakref import WeakKeyDictionary try: from collections import MutableMapping as DictMixin @@ -685,7 +686,7 @@ class Bottle(object): return self.wsgi(environ, start_response) -class Request(threading.local, DictMixin): +class BaseRequest(DictMixin): """ Represents a single HTTP request using thread-local attributes. The Request object wraps a WSGI environment and can be used as such. """ @@ -918,7 +919,7 @@ class Request(threading.local, DictMixin): -class Response(threading.local): +class BaseResponse(): """ Represents a single HTTP response using thread-local attributes. """ @@ -1009,7 +1010,82 @@ class Response(threading.local): get_content_type.__doc__) +class ContextLocal(object): + ''' A flexible baseclass to build context local objects. ''' + __slots__ = ('_local_init', '_local_context') + + def __new__(cls, *args, **kwargs): + self = object.__new__(cls) + object.__setattr__(self, '_local_init', (args, kwargs)) + object.__setattr__(self, '_local_context', {}) + return self + + @staticmethod + def context_ident(): + """ Return a hashable identifier for the curent context """ + return 0 + + def set_context_ident(self, func, weakref=False): + """ Replace the :meth`context_ident` method with a new callable. """ + object.__setattr__(self, 'context_ident', func) + object.__setattr__(self, '_local_context', + WeakKeyDictionary() if weakref else {}) + + def _local(self): + cur = self.context_ident() + if cur not in self._local_context: + self._local_context[cur] = {} + self.__init__(*self._local_init[0], **self._local_init[1]) + return self._local_context[cur] + + def __getattr__(self, attr): + try: return self._local()[attr] + except KeyError: + raise AttributeError(attr) + def __delattr__(self, attr): + try: del self._local()[attr] + except KeyError: + raise AttributeError(attr) + + def __setattr__(self, attr, value): + self._local()[attr] = value + + +class Request(threading.local, BaseRequest): pass +class Response(threading.local, BaseResponse): pass +class LocalRequest(ContextLocal, BaseRequest): pass +class LocalResponse(ContextLocal, BaseResponse): pass + +def set_context_ident(ident=thread.get_ident, weakref=False): + ''' Change the function that identifies the current runtime context. + + This is done automatically by a server adapter. + + Some objects used by bottle need to be context local. By default, these + are local to the current thread: The instance’s values are different for + each separate threads. In environments where 'light' or 'mirco' threads + are used to parallelize requests, a singel thread may contain several + contexts and thread-locality is not sufficiand anymore. + + Example for greenlet:: + from eventlet import greenthread + set_context_ident(greenthread.getcurrent, weakref=True) + + :param ident: Callable that returns a context identifyer when called + within a context. (default: thread.get_ident) + :param weakref: If true, a weakref.WeakKeyDictionary() is used to store + the context local stat. This allows the garbage collector + to free memory as soon as the context terminates and + the return value of ident() is dereferenced. + ''' + global request, response + if not isinstance(request, LocalRequest): + request = LocalRequest() + if not isinstance(response, LocalResponse): + response = LocalResponse() + request.set_context_ident(ident, weakref) + response.set_context_ident(ident, weakref) @@ -1317,9 +1393,12 @@ class ServerAdapter(object): self.host = host self.port = int(port) - def run(self, handler): # pragma: no cover + def run(self, handler): pass - + + def set_context_ident(self, func, weakref=False): + set_context_ident(func, weakref) + def __repr__(self): args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) return "%s(%s)" % (self.__class__.__name__, args) @@ -1350,14 +1429,14 @@ class WSGIRefServer(ServerAdapter): class CherryPyServer(ServerAdapter): - def run(self, handler): # pragma: no cover + def run(self, handler): from cherrypy import wsgiserver server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) server.start() class PasteServer(ServerAdapter): - def run(self, handler): # pragma: no cover + def run(self, handler): from paste import httpserver if not self.quiet: from paste.translogger import TransLogger @@ -1371,7 +1450,7 @@ class FapwsServer(ServerAdapter): Extremly fast webserver using libev. See http://william-os4y.livejournal.com/ """ - def run(self, handler): # pragma: no cover + def run(self, handler): import fapws._evwsgi as evwsgi from fapws import base, config port = self.port @@ -1394,7 +1473,7 @@ class FapwsServer(ServerAdapter): class TornadoServer(ServerAdapter): """ Untested. As described here: http://github.com/facebook/tornado/blob/master/tornado/wsgi.py#L187 """ - def run(self, handler): # pragma: no cover + def run(self, handler): import tornado.wsgi import tornado.httpserver import tornado.ioloop @@ -1438,8 +1517,8 @@ class GeventServer(ServerAdapter): """ Untested. """ def run(self, handler): from gevent import wsgi - #from gevent.hub import getcurrent - #self.set_context_ident(getcurrent, weakref=True) # see contextlocal + from gevent.hub import getcurrent + self.set_context_ident(getcurrent, weakref=True) # see contextlocal wsgi.WSGIServer((self.host, self.port), handler).serve_forever() @@ -1453,12 +1532,22 @@ class GunicornServer(ServerAdapter): class EventletServer(ServerAdapter): - """ Untested """ + """ Untested. """ def run(self, handler): - from eventlet import wsgi, listen + from eventlet import wsgi, listen, greenthread + self.set_context_ident(greenthread.getcurrent, weakref=True) wsgi.server(listen((self.host, self.port)), handler) +class GeventServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from gevent import wsgi + from gevent.hub import getcurrent + self.set_context_ident(getcurrent, weakref=True) + wsgi.WSGIServer((self.host, self.port), handler).serve_forever() + + class RocketServer(ServerAdapter): """ Untested. As requested in issue 63 http://github.com/defnull/bottle/issues/#issue/63 """ @@ -2078,9 +2167,6 @@ response = Response() """ The :class:`Bottle` WSGI handler uses metadata assigned to this instance of :class:`Response` to generate the WSGI response. """ -local = threading.local() -""" Thread-local namespace. Not used by Bottle, but could get handy """ - # Initialize app stack (create first empty Bottle app) # BC: 0.6.4 and needed for run() app = default_app = AppStack() |