From 47288231fe8f9c6b2c413d50160c32c3884d5785 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 14:34:12 +0100 Subject: [PATCH] Implemented a separate application context. --- flask/app.py | 17 ++++++++++++++- flask/ctx.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++- flask/globals.py | 10 ++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 15e432de..38d31f95 100644 --- a/flask/app.py +++ b/flask/app.py @@ -28,7 +28,7 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ find_package from .wrappers import Request, Response from .config import ConfigAttribute, Config -from .ctx import RequestContext +from .ctx import RequestContext, AppContext from .globals import _request_ctx_stack, request from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module @@ -1458,6 +1458,21 @@ class Flask(_PackageBoundObject): return rv request_tearing_down.send(self) + def app_context(self): + """Binds the application only. For as long as the application is bound + to the current context the :data:`flask.current_app` points to that + application. An application context is automatically created when a + request context is pushed if necessary. + + Example usage:: + + with app.app_context(): + ... + + .. versionadded:: 0.9 + """ + return AppContext(self) + def request_context(self, environ): """Creates a :class:`~flask.ctx.RequestContext` from the given environment and binds it to the current context. This must be used in diff --git a/flask/ctx.py b/flask/ctx.py index f9558d2e..7bfd598e 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -11,7 +11,7 @@ from werkzeug.exceptions import HTTPException -from .globals import _request_ctx_stack +from .globals import _request_ctx_stack, _app_ctx_stack from .module import blueprint_is_module @@ -19,6 +19,14 @@ class _RequestGlobals(object): pass +def _push_app_if_necessary(app): + top = _app_ctx_stack.top + if top is None or top.app != app: + ctx = app.app_context() + ctx.push() + return ctx + + def has_request_context(): """If you have code that wants to test if a request context is there or not this function can be used. For instance, you may want to take advantage @@ -51,6 +59,36 @@ def has_request_context(): return _request_ctx_stack.top is not None +class AppContext(object): + """The application context binds an application object implicitly + to the current thread or greenlet, similar to how the + :class:`RequestContext` binds request information. The application + context is also implicitly created if a request context is created + but the application is not on top of the individual application + context. + """ + + def __init__(self, app): + self.app = app + + def push(self): + """Binds the app context to the current context.""" + _app_ctx_stack.push(self) + + def pop(self): + """Pops the app context.""" + rv = _app_ctx_stack.pop() + assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ + % (rv, self) + + def __enter__(self): + self.push() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.pop() + + class RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the @@ -93,6 +131,11 @@ class RequestContext(object): # is pushed the preserved context is popped. self.preserved = False + # Indicates if pushing this request context also triggered the pushing + # of an application context. If it implicitly pushed an application + # context, it will be stored there + self._pushed_application_context = None + self.match_request() # XXX: Support for deprecated functionality. This is going away with @@ -130,6 +173,10 @@ class RequestContext(object): if top is not None and top.preserved: top.pop() + # Before we push the request context we have to ensure that there + # is an application context. + self._pushed_application_context = _push_app_if_necessary(self.app) + _request_ctx_stack.push(self) # Open the session at the moment that the request context is @@ -154,6 +201,11 @@ class RequestContext(object): # so that we don't require the GC to be active. rv.request.environ['werkzeug.request'] = None + # Get rid of the app as well if necessary. + if self._pushed_application_context: + self._pushed_application_context.pop() + self._pushed_application_context = None + def __enter__(self): self.push() return self diff --git a/flask/globals.py b/flask/globals.py index 16580d16..f6d62485 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -20,9 +20,17 @@ def _lookup_object(name): return getattr(top, name) +def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError('working outside of application context') + return top.app + + # context locals _request_ctx_stack = LocalStack() -current_app = LocalProxy(partial(_lookup_object, 'app')) +_app_ctx_stack = LocalStack() +current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_object, 'request')) session = LocalProxy(partial(_lookup_object, 'session')) g = LocalProxy(partial(_lookup_object, 'g'))