diff --git a/docs/api.rst b/docs/api.rst index 88d026ed..b3953537 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -310,6 +310,9 @@ Configuration Useful Internals ---------------- +.. autoclass:: flask.ctx.RequestContext + :members: + .. data:: _request_ctx_stack The internal :class:`~werkzeug.local.LocalStack` that is used to implement @@ -347,23 +350,6 @@ Useful Internals if ctx is not None: return ctx.session - .. versionchanged:: 0.4 - - The request context is automatically popped at the end of the request - for you. In debug mode the request context is kept around if - exceptions happen so that interactive debuggers have a chance to - introspect the data. With 0.4 this can also be forced for requests - that did not fail and outside of `DEBUG` mode. By setting - ``'flask._preserve_context'`` to `True` on the WSGI environment the - context will not pop itself at the end of the request. This is used by - the :meth:`~flask.Flask.test_client` for example to implement the - deferred cleanup functionality. - - You might find this helpful for unittests where you need the - information from the context local around for a little longer. Make - sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in - that situation, otherwise your unittests will leak memory. - Signals ------- @@ -401,6 +387,12 @@ Signals in debug mode, where no exception handling happens. The exception itself is passed to the subscriber as `exception`. +.. data:: request_tearing_down + + This signal is sent when the application is tearing down the request. + This is always called, even if an error happened. No arguments are + provided. + .. currentmodule:: None .. class:: flask.signals.Namespace @@ -418,28 +410,3 @@ Signals operations, including connecting. .. _blinker: http://pypi.python.org/pypi/blinker - -.. _notes-on-proxies: - -Notes On Proxies ----------------- - -Some of the objects provided by Flask are proxies to other objects. The -reason behind this is that these proxies are shared between threads and -they have to dispatch to the actual object bound to a thread behind the -scenes as necessary. - -Most of the time you don't have to care about that, but there are some -exceptions where it is good to know that this object is an actual proxy: - -- The proxy objects do not fake their inherited types, so if you want to - perform actual instance checks, you have to do that on the instance - that is being proxied (see `_get_current_object` below). -- if the object reference is important (so for example for sending - :ref:`signals`) - -If you need to get access to the underlying object that is proxied, you -can use the :meth:`~werkzeug.local.LocalProxy._get_current_object` method:: - - app = current_app._get_current_object() - my_signal.send(app) diff --git a/docs/config.rst b/docs/config.rst index 90a276cc..aedaae7d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -51,27 +51,36 @@ The following configuration values are used internally by Flask: .. tabularcolumns:: |p{6.5cm}|p{8.5cm}| -=============================== ========================================= -``DEBUG`` enable/disable debug mode -``TESTING`` enable/disable testing mode -``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the - propagation of exceptions. If not set or - explicitly set to `None` this is - implicitly true if either `TESTING` or - `DEBUG` is true. -``SECRET_KEY`` the secret key -``SESSION_COOKIE_NAME`` the name of the session cookie -``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as - :class:`datetime.timedelta` object. -``USE_X_SENDFILE`` enable/disable x-sendfile -``LOGGER_NAME`` the name of the logger -``SERVER_NAME`` the name of the server. Required for - subdomain support (e.g.: ``'localhost'``) -``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will - reject incoming requests with a - content length greater than this by - returning a 413 status code. -=============================== ========================================= +================================= ========================================= +``DEBUG`` enable/disable debug mode +``TESTING`` enable/disable testing mode +``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the + propagation of exceptions. If not set or + explicitly set to `None` this is + implicitly true if either `TESTING` or + `DEBUG` is true. +``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in + debug mode the request context is not + popped on exceptions to enable debuggers + to introspect the data. This can be + disabled by this key. You can also use + this setting to force-enable it for non + debug execution which might be useful to + debug production applications (but also + very risky). +``SECRET_KEY`` the secret key +``SESSION_COOKIE_NAME`` the name of the session cookie +``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as + :class:`datetime.timedelta` object. +``USE_X_SENDFILE`` enable/disable x-sendfile +``LOGGER_NAME`` the name of the logger +``SERVER_NAME`` the name of the server. Required for + subdomain support (e.g.: ``'localhost'``) +``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will + reject incoming requests with a + content length greater than this by + returning a 413 status code. +================================= ========================================= .. admonition:: More on ``SERVER_NAME`` @@ -102,7 +111,7 @@ The following configuration values are used internally by Flask: ``MAX_CONTENT_LENGTH`` .. versionadded:: 0.7 - ``PROPAGATE_EXCEPTIONS`` + ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` Configuring from Files ---------------------- diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index f32d1da5..2689b4b5 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -17,6 +17,7 @@ instructions for web development with Flask. errorhandling config signals + reqcontext shell patterns/index deploying/index diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst new file mode 100644 index 00000000..088502eb --- /dev/null +++ b/docs/reqcontext.rst @@ -0,0 +1,230 @@ +.. _request-context: + +The Request Context +=================== + +This document describes the behavior in Flask 0.7 which is mostly in line +with the old behavior but has some small, subtle differences. + +One of the design ideas behind Flask is that there are two different +“states” in which code is executed. The application setup state in which +the application implicitly is on the module level. It starts when the +:class:`Flask` object is instantiated, and it implicitly ends when the +first request comes in. While the application is in this state a few +assumptions are true: + +- the programmer can modify the application object safely. +- no request handling happened so far +- you have to have a reference to the application object in order to + modify it, there is no magic proxy that can give you a reference to + the application object you're currently creating or modifying. + +On the contrast, during request handling, a couple of other rules exist: + +- while a request is active, the context local objects + (:data:`flask.request` and others) point to the current request. +- any code can get hold of these objects at any time. + +The magic that makes this works is internally referred in Flask as the +“request context”. + +Diving into Context Locals +-------------------------- + +Say you have a utility function that returns the URL the user should be +redirected to. Imagine it would always redirect to the URL's ``next`` +parameter or the HTTP referrer or the index page:: + + from flask import request, url_for + + def redirect_url(): + return request.args.get('next') or \ + request.referrer or \ + url_for('index') + +As you can see, it accesses the request object. If you try to run this +from a plain Python shell, this is the exception you will see: + +>>> redirect_url() +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'NoneType' object has no attribute 'request' + +That makes a lot of sense because we currently do not have a request we +could access. So we have to make a request and bind it to the current +context. The :attr:`~flask.Flask.test_request_context` method can create +us a :class:`~flask.ctx.RequestContext`: + +>>> ctx = app.test_request_context('/?next=http://example.com/') + +This context can be used in two ways. Either with the `with` statement +or by calling the :meth:`~flask.ctx.RequestContext.push` and +:meth:`~flask.ctx.RequestContext.pop` methods: + +>>> ctx.push() + +From that point onwards you can work with the request object: + +>>> redirect_url() +u'http://example.com/' + +Until you call `pop`: + +>>> ctx.pop() + +Because the request context is internally maintained as a stack you can +push and pop multiple times. This is very handy to implement things like +internal redirects. + +For more information of how to utilize the request context from the +interactive Python shell, head over to the :ref:`shell` chapter. + +How the Context Works +--------------------- + +If you look into how the Flask WSGI application internally works, you will +find a piece of code that looks very much like this:: + + def wsgi_app(self, environ): + with self.request_context(environ): + try: + response = self.full_dispatch_request() + except Exception, e: + response = self.make_response(self.handle_exception(e)) + return response(environ, start_response) + +The method :meth:`~Flask.request_context` returns a new +:class:`~flask.ctx.RequestContext` object and uses it in combination with +the `with` statement to bind the context. Everything that is called from +the same thread from this point onwards until the end of the `with` +statement will have access to the request globals (:data:`flask.request` +and others). + +The request context internally works like a stack: The topmost level on +the stack is the current active request. +:meth:`~flask.ctx.RequestContext.push` adds the context to the stack on +the very top, :meth:`~flask.ctx.RequestContext.pop` removes it from the +stack again. On popping the application's +:func:`~flask.Flask.teardown_request` functions are also executed. + +.. _callbacks-and-errors: + +Callbacks and Errors +-------------------- + +What happens if an error occurs in Flask during request processing? This +particular behavior changed in 0.7 because we wanted to make it easier to +understand what is actually happening. The new behavior is quite simple: + +1. Before each request, :meth:`~flask.Flask.before_request` functions are + executed. If one of these functions return a response, the other + functions are no longer called. In any case however the return value + is treated as a replacement for the view's return value. + +2. If the :meth:`~flask.Flask.before_request` functions did not return a + response, the regular request handling kicks in and the view function + that was matched has the chance to return a response. + +3. The return value of the view is then converted into an actual response + object and handed over to the :meth:`~flask.Flask.after_request` + functions which have the chance to replace it or modify it in place. + +4. At the end of the request the :meth:`~flask.Flask.teardown_request` + functions are executed. This always happens, even in case of an + unhandled exception down the road. + +Now what happens on errors? In production mode if an exception is not +caught, the 500 internal server handler is called. In development mode +however the exception is not further processed and bubbles up to the WSGI +server. That way things like the interactive debugger can provide helpful +debug information. + +An important change in 0.7 is that the internal server error is now no +longer post processed by the after request callbacks and after request +callbacks are no longer guaranteed to be executed. This way the internal +dispatching code looks cleaner and is easier to customize and understand. + +The new teardown functions are supposed to be used as a replacement for +things that absolutely need to happen at the end of request. + +Teardown Callbacks +------------------ + +The teardown callbacks are special callbacks in that they are executed at +at different point. Strictly speaking they are independent of the actual +request handling as they are bound to the lifecycle of the +:class:`~flask.ctx.RequestContext` object. When the request context is +popped, the :meth:`~flask.Flask.teardown_request` functions are called. + +This is important to know if the life of the request context is prolonged +by using the test client in a with statement of when using the request +context from the command line:: + + with app.test_client() as client: + resp = client.get('/foo') + # the teardown functions are still not called at that point + # even though the response ended and you have the response + # object in your hand + + # only when the code reaches this point the teardown functions + # are called. Alternatively the same thing happens if another + # request was triggered from the test client + +It's easy to see the behavior from the command line: + +>>> app = Flask(__name__) +>>> @app.teardown_request +... def after_request(exception=None): +... print 'after request' +... +>>> ctx = app.test_request_context() +>>> ctx.push() +>>> ctx.pop() +after request + +.. _notes-on-proxies: + +Notes On Proxies +---------------- + +Some of the objects provided by Flask are proxies to other objects. The +reason behind this is that these proxies are shared between threads and +they have to dispatch to the actual object bound to a thread behind the +scenes as necessary. + +Most of the time you don't have to care about that, but there are some +exceptions where it is good to know that this object is an actual proxy: + +- The proxy objects do not fake their inherited types, so if you want to + perform actual instance checks, you have to do that on the instance + that is being proxied (see `_get_current_object` below). +- if the object reference is important (so for example for sending + :ref:`signals`) + +If you need to get access to the underlying object that is proxied, you +can use the :meth:`~werkzeug.local.LocalProxy._get_current_object` method:: + + app = current_app._get_current_object() + my_signal.send(app) + +Context Preservation on Error +----------------------------- + +If an error occurs or not, at the end of the request the request context +is popped and all data associated with it is destroyed. During +development however that can be problematic as you might want to have the +information around for a longer time in case an exception occurred. In +Flask 0.6 and earlier in debug mode, if an exception occurred, the +request context was not popped so that the interactive debugger can still +provide you with important information. + +Starting with Flask 0.7 you have finer control over that behavior by +setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By +default it's linked to the setting of ``DEBUG``. If the application is in +debug mode the context is preserved, in production mode it's not. + +Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` in production mode +as it will cause your application to leak memory on exceptions. However +it can be useful during development to get the same error preserving +behavior as in development mode when attempting to debug an error that +only occurs under production settings. diff --git a/docs/shell.rst b/docs/shell.rst index 470bceca..61b9dc05 100644 --- a/docs/shell.rst +++ b/docs/shell.rst @@ -1,3 +1,5 @@ +.. _shell: + Working with the Shell ====================== @@ -21,61 +23,37 @@ that these functions are not only there for interactive shell usage, but also for unittesting and other situations that require a faked request context. -Diving into Context Locals --------------------------- - -Say you have a utility function that returns the URL the user should be -redirected to. Imagine it would always redirect to the URL's ``next`` -parameter or the HTTP referrer or the index page:: - - from flask import request, url_for - - def redirect_url(): - return request.args.get('next') or \ - request.referrer or \ - url_for('index') - -As you can see, it accesses the request object. If you try to run this -from a plain Python shell, this is the exception you will see: +Generally it's recommended that you read the :ref:`request-context` +chapter of the documentation first. ->>> redirect_url() -Traceback (most recent call last): - File "", line 1, in -AttributeError: 'NoneType' object has no attribute 'request' +Creating a Request Context +-------------------------- -That makes a lot of sense because we currently do not have a request we -could access. So we have to make a request and bind it to the current -context. The :attr:`~flask.Flask.test_request_context` method can create -us a request context: +The easiest way to create a proper request context from the shell is by +using the :attr:`~flask.Flask.test_request_context` method which creates +us a :class:`~flask.ctx.RequestContext`: ->>> ctx = app.test_request_context('/?next=http://example.com/') +>>> ctx = app.test_request_context() -This context can be used in two ways. Either with the `with` statement -(which unfortunately is not very handy for shell sessions). The -alternative way is to call the `push` and `pop` methods: +Normally you would use the `with` statement to make this request object +active, but in the shell it's easier to use the +:meth:`~flask.ctx.RequestContext.push` and +:meth:`~flask.ctx.RequestContext.pop` methods by hand: >>> ctx.push() -From that point onwards you can work with the request object: - ->>> redirect_url() -u'http://example.com/' - -Until you call `pop`: +From that point onwards you can work with the request object until you +call `pop`: >>> ctx.pop() ->>> redirect_url() -Traceback (most recent call last): - File "", line 1, in -AttributeError: 'NoneType' object has no attribute 'request' - Firing Before/After Request --------------------------- By just creating a request context, you still don't have run the code that -is normally run before a request. This probably results in your database -being unavailable, the current user not being stored on the +is normally run before a request. This might result in your database +being unavailable if you are connecting to the database in a +before-request callback or the current user not being stored on the :data:`~flask.g` object etc. This however can easily be done yourself. Just call @@ -96,6 +74,11 @@ a response object: >>> ctx.pop() +The functions registered as :meth:`~flask.Flask.teardown_request` are +automatically called when the context is popped. So this is the perfect +place to automatically tear down resources that were needed by the request +context (such as database connections). + Further Improving the Shell Experience -------------------------------------- diff --git a/docs/signals.rst b/docs/signals.rst index ed5ecd51..a5821603 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -236,4 +236,20 @@ The following signals exist in Flask: from flask import got_request_exception got_request_exception.connect(log_exception, app) +.. data:: flask.request_tearing_down + :noindex: + + This signal is sent when the request is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender): + session.close() + + from flask import request_tearing_down + request_tearing_down.connect(close_db_connection, app) + .. _blinker: http://pypi.python.org/pypi/blinker diff --git a/flask/__init__.py b/flask/__init__.py index 1274e766..f9fcc09f 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -28,7 +28,7 @@ from .session import Session # the signals from .signals import signals_available, template_rendered, request_started, \ - request_finished, got_request_exception + request_finished, got_request_exception, request_tearing_down # only import json if it's available if json_available: diff --git a/flask/app.py b/flask/app.py index 1e068e41..99d7a266 100644 --- a/flask/app.py +++ b/flask/app.py @@ -27,13 +27,14 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ _tojson_filter, _endpoint_from_view_func from .wrappers import Request, Response from .config import ConfigAttribute, Config -from .ctx import _RequestContext +from .ctx import RequestContext from .globals import _request_ctx_stack, request from .session import Session, _NullSession from .module import _ModuleSetupState from .templating import _DispatchingJinjaLoader, \ _default_template_ctx_processor -from .signals import request_started, request_finished, got_request_exception +from .signals import request_started, request_finished, got_request_exception, \ + request_tearing_down # a lock used for logger initialization _logger_lock = Lock() @@ -126,6 +127,9 @@ class Flask(_PackageBoundObject): #: For example this might activate unittest helpers that have an #: additional runtime cost which should not be enabled by default. #: + #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the + #: default it's implicitly enabled. + #: #: This attribute can also be configured from the config with the #: `TESTING` configuration key. Defaults to `False`. testing = ConfigAttribute('TESTING') @@ -191,6 +195,7 @@ class Flask(_PackageBoundObject): 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, + 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'SESSION_COOKIE_NAME': 'session', 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), @@ -334,6 +339,19 @@ class Flask(_PackageBoundObject): return rv return self.testing or self.debug + @property + def preserve_context_on_exception(self): + """Returns the value of the `PRESERVE_CONTEXT_ON_EXCEPTION` + configuration value in case it's set, otherwise a sensible default + is returned. + + .. versionadded:: 0.7 + """ + rv = self.config['PRESERVE_CONTEXT_ON_EXCEPTION'] + if rv is not None: + return rv + return self.debug + @property def logger(self): """A :class:`logging.Logger` object for this application. The @@ -713,16 +731,38 @@ class Flask(_PackageBoundObject): return f def after_request(self, f): - """Register a function to be run after each request. Your function + """Register a function to be run after each request. Your function must take one parameter, a :attr:`response_class` object and return a new response object or the same (see :meth:`process_response`). + + As of Flask 0.7 this function might not be executed at the end of the + request in case an unhandled exception ocurred. """ self.after_request_funcs.setdefault(None, []).append(f) return f def teardown_request(self, f): """Register a function to be run at the end of each request, - regardless of whether there was an exception or not. + regardless of whether there was an exception or not. These functions + are executed when the request context is popped, even if not an + actual request was performed. + + Example:: + + ctx = app.test_request_context() + ctx.push() + ... + ctx.pop() + + When ``ctx.pop()`` is executed in the above example, the teardown + functions are called just before the request context moves from the + stack of active contexts. This becomes relevant if you are using + such constructs in tests. + + Generally teardown functions must take every necesary step to avoid + that they will fail. If they do execute code that might fail they + will have to surround the execution of these code by try/except + statements and log ocurring errors. """ self.teardown_request_funcs.setdefault(None, []).append(f) return f @@ -770,21 +810,39 @@ class Flask(_PackageBoundObject): return value of the view or error handler. This does not have to be a response object. In order to convert the return value to a proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. """ req = _request_ctx_stack.top.request + if req.routing_exception is not None: + raise req.routing_exception + rule = req.url_rule + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if getattr(rule, 'provide_automatic_options', False) \ + and req.method == 'OPTIONS': + return self.make_default_options_response() + # otherwise dispatch to the handler for that endpoint + return self.view_functions[rule.endpoint](**req.view_args) + + def full_dispatch_request(self): + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + """ try: - if req.routing_exception is not None: - raise req.routing_exception - rule = req.url_rule - # if we provide automatic options for this URL and the - # request came with the OPTIONS method, reply automatically - if getattr(rule, 'provide_automatic_options', False) \ - and req.method == 'OPTIONS': - return self.make_default_options_response() - # otherwise dispatch to the handler for that endpoint - return self.view_functions[rule.endpoint](**req.view_args) + request_started.send(self) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() except HTTPException, e: - return self.handle_http_exception(e) + rv = self.handle_http_exception(e) + response = self.make_response(rv) + response = self.process_response(response) + request_finished.send(self, response=response) + return response def make_default_options_response(self): """This method is called to create the default `OPTIONS` response. @@ -894,7 +952,10 @@ class Flask(_PackageBoundObject): def do_teardown_request(self): """Called after the actual request dispatching and will - call every as :meth:`teardown_request` decorated function. + call every as :meth:`teardown_request` decorated function. This is + not actually called by the :class:`Flask` object itself but is always + triggered when the request context is popped. That way we have a + tighter control over certain resources under testing environments. """ funcs = reversed(self.teardown_request_funcs.get(None, ())) mod = request.module @@ -905,12 +966,13 @@ class Flask(_PackageBoundObject): rv = func(exc) if rv is not None: return rv + request_tearing_down.send(self) def request_context(self, environ): - """Creates a request context from the given environment and binds - it to the current context. This must be used in combination with - the `with` statement because the request is only bound to the - current context for the duration of the `with` block. + """Creates a :class:`~flask.ctx.RequestContext` from the given + environment and binds it to the current context. This must be used in + combination with the `with` statement because the request is only bound + to the current context for the duration of the `with` block. Example usage:: @@ -934,7 +996,7 @@ class Flask(_PackageBoundObject): :param environ: a WSGI environment """ - return _RequestContext(self, environ) + return RequestContext(self, environ) def test_request_context(self, *args, **kwargs): """Creates a WSGI environment from the given values (see @@ -969,16 +1031,11 @@ class Flask(_PackageBoundObject): Then you still have the original application object around and can continue to call methods on it. - .. versionchanged:: 0.4 - The :meth:`after_request` functions are now called even if an - error handler took over request processing. This ensures that - even if an exception happens database have the chance to - properly close the connection. - .. versionchanged:: 0.7 - The :meth:`teardown_request` functions get called at the very end of - processing the request. If an exception was thrown, it gets passed to - each teardown_request function. + The behavior of the before and after request callbacks was changed + under error conditions and a new callback was added that will + always execute at the end of the request, independent on if an + error ocurred or not. See :ref:`callbacks-and-errors`. :param environ: a WSGI environment :param start_response: a callable accepting a status code, @@ -987,20 +1044,9 @@ class Flask(_PackageBoundObject): """ with self.request_context(environ): try: - request_started.send(self) - rv = self.preprocess_request() - if rv is None: - rv = self.dispatch_request() - response = self.make_response(rv) + response = self.full_dispatch_request() except Exception, e: response = self.make_response(self.handle_exception(e)) - try: - response = self.process_response(response) - except Exception, e: - response = self.make_response(self.handle_exception(e)) - finally: - self.do_teardown_request() - request_finished.send(self, response=response) return response(environ, start_response) def __call__(self, environ, start_response): diff --git a/flask/ctx.py b/flask/ctx.py index b63f09f1..bc4877cd 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -51,11 +51,34 @@ def has_request_context(): return _request_ctx_stack.top is not None -class _RequestContext(object): +class RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the request + for you. In debug mode the request context is kept around if + exceptions happen so that interactive debuggers have a chance to + introspect the data. With 0.4 this can also be forced for requests + that did not fail and outside of `DEBUG` mode. By setting + ``'flask._preserve_context'`` to `True` on the WSGI environment the + context will not pop itself at the end of the request. This is used by + the :meth:`~flask.Flask.test_client` for example to implement the + deferred cleanup functionality. + + You might find this helpful for unittests where you need the + information from the context local around for a little longer. Make + sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in + that situation, otherwise your unittests will leak memory. """ def __init__(self, app, environ): @@ -74,7 +97,7 @@ class _RequestContext(object): self.request.routing_exception = e def push(self): - """Binds the request context.""" + """Binds the request context to the current context.""" _request_ctx_stack.push(self) # Open the session at the moment that the request context is @@ -85,7 +108,11 @@ class _RequestContext(object): self.session = _NullSession() def pop(self): - """Pops the request context.""" + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + """ + self.app.do_teardown_request() _request_ctx_stack.pop() def __enter__(self): @@ -99,5 +126,5 @@ class _RequestContext(object): # the context can be force kept alive for the test client. # See flask.testing for how this works. if not self.request.environ.get('flask._preserve_context') and \ - (tb is None or not self.app.debug): + (tb is None or not self.app.preserve_context_on_exception): self.pop() diff --git a/flask/signals.py b/flask/signals.py index 22447c7c..4eedf68f 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -47,4 +47,5 @@ _signals = Namespace() template_rendered = _signals.signal('template-rendered') request_started = _signals.signal('request-started') request_finished = _signals.signal('request-finished') +request_tearing_down = _signals.signal('request-tearing-down') got_request_exception = _signals.signal('got-request-exception') diff --git a/tests/flask_tests.py b/tests/flask_tests.py index b723f217..ba3dbcdf 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -784,8 +784,11 @@ class BasicFunctionalityTestCase(unittest.TestCase): def test_max_content_length(self): app = flask.Flask(__name__) - app.debug = True app.config['MAX_CONTENT_LENGTH'] = 64 + @app.before_request + def always_first(): + flask.request.form['myfile'] + assert False @app.route('/accept', methods=['POST']) def accept_file(): flask.request.form['myfile']