summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbottle.py116
1 files changed, 101 insertions, 15 deletions
diff --git a/bottle.py b/bottle.py
index 6deac8a..8fba11b 100755
--- a/bottle.py
+++ b/bottle.py
@@ -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()