diff --git a/CHANGES b/CHANGES index 394cd39e..92a147b0 100644 --- a/CHANGES +++ b/CHANGES @@ -38,11 +38,19 @@ Release date to be announced, codename to be selected - Added `create_jinja_loader` to override the loader creation process. - Implemented a silent flag for `config.from_pyfile`. - Added `teardown_request` decorator, for functions that should run at the end - of a request regardless of whether an exception occurred. + of a request regardless of whether an exception occurred. Also the behavior + for `after_request` was changed. It's now no longer executed when an exception + is raised. See :ref:`upgrading-to-new-teardown-handling` - Implemented :func:`flask.has_request_context` - Deprecated `init_jinja_globals`. Override the :meth:`~flask.Flask.create_jinja_environment` method instead to achieve the same functionality. +- Added :func:`safe_join` +- The automatic JSON request data unpacking now looks at the charset + mimetype parameter. +- Don't modify the session on :func:`flask.get_flashed_messages` if there + are no messages in the session. +- `before_request` handlers are now able to abort requests with errors. Version 0.6.1 ------------- diff --git a/docs/api.rst b/docs/api.rst index c2d90ce6..b3953537 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -244,6 +244,8 @@ Useful Functions and Classes .. autofunction:: send_from_directory +.. autofunction:: safe_join + .. autofunction:: escape .. autoclass:: Markup @@ -308,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 @@ -345,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 ------- @@ -399,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 @@ -416,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..dc761657 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 ---------------------- @@ -231,6 +240,7 @@ your configuration files. However here a list of good recommendations: and exports the development configuration for you. - Use a tool like `fabric`_ in production to push code and configurations separately to the production server(s). For some - details about how to do that, head over to the :ref:`deploy` pattern. + details about how to do that, head over to the + :ref:`fabric-deployment` pattern. .. _fabric: http://fabfile.org/ 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/deploying/others.rst b/docs/deploying/others.rst index 8f08ecd1..153fb7cf 100644 --- a/docs/deploying/others.rst +++ b/docs/deploying/others.rst @@ -72,7 +72,7 @@ problematic values in the WSGI environment usually are `REMOTE_ADDR` and but you might want to write your own WSGI middleware for specific setups. The most common setup invokes the host being set from `X-Forwarded-Host` -and the remote address from `X-Forward-For`:: +and the remote address from `X-Forwarded-For`:: from werkzeug.contrib.fixers import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index a5cc2aa4..6b407b34 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -182,7 +182,7 @@ Here's the contents of the `flaskext/sqlite3.py` for copy/paste:: def __init__(self, app): self.app = app self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') - self.app.after_request(self.after_request) + self.app.teardown_request(self.teardown_request) self.app.before_request(self.before_request) def connect(self): @@ -192,10 +192,9 @@ Here's the contents of the `flaskext/sqlite3.py` for copy/paste:: ctx = _request_ctx_stack.top ctx.sqlite3_db = self.connect() - def after_request(self, response): + def teardown_request(self, exception): ctx = _request_ctx_stack.top ctx.sqlite3_db.close() - return response def get_db(self): ctx = _request_ctx_stack.top @@ -211,7 +210,7 @@ So here's what these lines of code do: 2. We create a class for our extension that requires a supplied `app` object, sets a configuration for the database if it's not there (:meth:`dict.setdefault`), and attaches `before_request` and - `after_request` handlers. + `teardown_request` handlers. 3. Next, we define a `connect` function that opens a database connection. 4. Then we set up the request handlers we bound to the app above. Note here that we're attaching our database connection to the top request context via @@ -264,7 +263,7 @@ Our extension could add an `init_app` function as follows:: def init_app(self, app): self.app = app self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') - self.app.after_request(self.after_request) + self.app.teardown_request(self.teardown_request) self.app.before_request(self.before_request) def connect(self): @@ -274,10 +273,9 @@ Our extension could add an `init_app` function as follows:: ctx = _request_ctx_stack.top ctx.sqlite3_db = self.connect() - def after_request(self, response): + def teardown_request(self, exception): ctx = _request_ctx_stack.top ctx.sqlite3_db.close() - return response def get_db(self): ctx = _request_ctx_stack.top @@ -292,6 +290,29 @@ and bind their app to the extension in another file:: manager.init_app(app) +End-Of-Request Behavior +----------------------- + +Due to the change in Flask 0.7 regarding functions that are run at the end +of the request your extension will have to be extra careful there if it +wants to continue to support older versions of Flask. The following +pattern is a good way to support both:: + + def close_connection(response): + ctx = _request_ctx_stack.top + ctx.sqlite3_db.close() + return response + + if hasattr(app, 'teardown_request'): + app.teardown_request(close_connection) + else: + app.after_request(close_connection) + +Strictly speaking the above code is wrong, because teardown functions are +passed the exception and typically don't return anything. However because +the return value is discarded this will just work assuming that the code +in between does not touch the passed parameter. + Learn from Others ----------------- diff --git a/docs/patterns/fabric.rst b/docs/patterns/fabric.rst index 49be85ab..dbd4f913 100644 --- a/docs/patterns/fabric.rst +++ b/docs/patterns/fabric.rst @@ -32,7 +32,7 @@ hosts. These hosts can be defined either in the fabfile or on the command line. In this case we will add them to the fabfile. This is a basic first example that has the ability to upload the current -sourcecode to the server and install it into a already existing +sourcecode to the server and install it into a pre-existing virtual environment:: from fabric.api import * @@ -53,12 +53,12 @@ virtual environment:: put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz') # create a place where we can unzip the tarball, then enter # that directory and unzip it - run('mkdir yourapplication') + run('mkdir /tmp/yourapplication') with cd('/tmp/yourapplication'): run('tar xzf /tmp/yourapplication.tar.gz') - # now setup the package with our virtual environment's - # python interpreter - run('/var/www/yourapplication/env/bin/python setup.py install') + # now setup the package with our virtual environment's + # python interpreter + run('/var/www/yourapplication/env/bin/python setup.py install') # now that all is set up, delete the folder again run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz') # and finally touch the .wsgi file so that mod_wsgi triggers diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index e1b92c08..28cd70e4 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -57,7 +57,7 @@ following quick checklist: 2. all the view functions (the ones with a :meth:`~flask.Flask.route` decorator on top) have to be imported when in the `__init__.py` file. Not the object itself, but the module it is in. Import the view module - *after the application object is created*. + **after the application object is created**. Here's an example `__init__.py`:: diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 24e9f013..5a33d1f6 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -65,10 +65,9 @@ automatically remove database sessions at the end of the request for you:: from yourapplication.database import db_session - @app.after_request - def shutdown_session(response): + @app.teardown_request + def shutdown_session(exception=None): db_session.remove() - return response Here is an example model (put this into `models.py`, e.g.):: @@ -140,10 +139,9 @@ each request. Put this into your application module:: from yourapplication.database import db_session - @app.after_request - def shutdown_session(response): + @app.teardown_request + def shutdown_session(exception=None): db_session.remove() - return response Here is an example table and model (put this into `models.py`):: diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 68833234..d0ec5a27 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -5,7 +5,7 @@ Using SQLite 3 with Flask In Flask you can implement the opening of database connections at the beginning of the request and closing at the end with the -:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request` +:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.teardown_request` decorators in combination with the special :class:`~flask.g` object. So here is a simple example of how you can use SQLite 3 with Flask:: @@ -22,10 +22,34 @@ So here is a simple example of how you can use SQLite 3 with Flask:: def before_request(): g.db = connect_db() - @app.after_request - def after_request(response): + @app.teardown_request + def teardown_request(exception): g.db.close() - return response + +Connect on Demand +----------------- + +The downside of this approach is that this will only work if Flask +executed the before-request handlers for you. If you are attempting to +use the database from a script or the interactive Python shell you would +have to do something like this:: + + with app.test_request_context() + app.preprocess_request() + # now you can use the g.db object + +In order to trigger the execution of the connection code. You won't be +able to drop the dependency on the request context this way, but you could +make it so that the application connects when necessary:: + + def get_connection(): + db = getattr(g, '_db', None) + if db is None: + db = g._db = connect_db() + return db + +Downside here is that you have to use ``db = get_connection()`` instead of +just being able to use ``g.db`` directly. .. _easy-querying: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b4f6027f..fc5ae0f1 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -113,6 +113,11 @@ Screenshot of the debugger in action: :class: screenshot :alt: screenshot of debugger in action +.. admonition:: Working With Other Debuggers + + Debuggers interfere with each other. If you are using another debugger + (e.g. PyDev or IntelliJ), you may need to set ``app.debug = False``. + Routing ------- 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/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index f700a329..1d9d41f9 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -9,22 +9,8 @@ connection in all our functions so it makes sense to initialize them before each request and shut them down afterwards. Flask allows us to do that with the :meth:`~flask.Flask.before_request`, -:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request` -decorators. In debug mode, if an error is raised, -:meth:`~flask.Flask.after_request` won't be run, and you'll have access to the -db connection in the interactive debugger:: - - @app.before_request - def before_request(): - g.db = connect_db() - - @app.after_request - def after_request(response): - g.db.close() - return response - -If you want to guarantee that the connection is always closed in debug mode, you -can close it in a function decorated with :meth:`~flask.Flask.teardown_request`: +:meth:`~flask.Flask.teardown_request` and :meth:`~flask.Flask.teardown_request` +decorators:: @app.before_request def before_request(): @@ -36,18 +22,17 @@ can close it in a function decorated with :meth:`~flask.Flask.teardown_request`: Functions marked with :meth:`~flask.Flask.before_request` are called before a request and passed no arguments. Functions marked with -:meth:`~flask.Flask.after_request` are called after a request and +:meth:`~flask.Flask.teardown_request` are called after a request and passed the response that will be sent to the client. They have to return -that response object or a different one. In this case we just return it -unchanged. - -Functions marked with :meth:`~flask.Flask.teardown_request` get called after the +that response object or a different one. They are however not guaranteed +to be executed if an exception is raised, this is where functions marked with +:meth:`~flask.Flask.teardown_request` come in. They get called after the response has been constructed. They are not allowed to modify the request, and their return values are ignored. If an exception occurred while the request was -being processed, it is passed to each function; otherwise, None is passed in. +being processed, it is passed to each function; otherwise, `None` is passed in. We store our current database connection on the special :data:`~flask.g` -object that flask provides for us. This object stores information for one +object that Flask provides for us. This object stores information for one request only and is available from within each function. Never store such things on other objects because this would not work with threaded environments. That special :data:`~flask.g` object does some magic behind diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index 64bf3b66..e9e4d679 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -70,7 +70,14 @@ server if we want to run that file as a standalone application:: app.run() With that out of the way you should be able to start up the application -without problems. When you head over to the server you will get an 404 +without problems. Do this with the following command:: + + python flaskr.py + +You will see a message telling you that server has started along with +the address at which you can access it. + +When you head over to the server in your browser you will get an 404 page not found error because we don't have any views yet. But we will focus on that a little later. First we should get the database working. diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 17523290..7d6e0f0a 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -22,6 +22,11 @@ installation, make sure to pass it the ``-U`` parameter:: Version 0.7 ----------- +The following backwards incompatible changes exist from 0.6 to 0.7 + +Bug in Request Locals +````````````````````` + Due to a bug in earlier implementations the request local proxies now raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they are unbound. If you caught these exceptions with :exc:`AttributeError` @@ -44,6 +49,48 @@ New code:: return send_file(my_file_object, add_etags=False) +.. _upgrading-to-new-teardown-handling: + +Upgrading to new Teardown Handling +`````````````````````````````````` + +We streamlined the behavior of the callbacks for request handling. For +things that modify the response the :meth:`~flask.Flask.after_request` +decorators continue to work as expected, but for things that absolutely +must happen at the end of request we introduced the new +:meth:`~flask.Flask.teardown_request` decorator. Unfortunately that +change also made after-request work differently under error conditions. +It's not consistently skipped if exceptions happen whereas previously it +might have been called twice to ensure it is executed at the end of the +request. + +If you have database connection code that looks like this:: + + @app.after_request + def after_request(response): + g.db.close() + return response + +You are now encouraged to use this instead:: + + @app.teardown_request + def after_request(exception): + g.db.close() + +On the upside this change greatly improves the internal code flow and +makes it easier to customize the dispatching and error handling. This +makes it now a lot easier to write unit tests as you can prevent closing +down of database connections for a while. You can take advantage of the +fact that the teardown callbacks are called when the response context is +removed from the stack so a test can query the database after request +handling:: + + with app.test_client() as client: + resp = client.get('/') + # g.db is still bound if there is such a thing + + # and here it's gone + Version 0.6 ----------- diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 69953555..361c1aee 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -47,11 +47,10 @@ def before_request(): g.db = connect_db() -@app.after_request -def after_request(response): +@app.teardown_request +def teardown_request(exception): """Closes the database again at the end of the request.""" g.db.close() - return response @app.route('/') diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index 7726e9f4..f7c700d3 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -82,11 +82,10 @@ def before_request(): [session['user_id']], one=True) -@app.after_request -def after_request(response): +@app.teardown_request +def teardown_request(exception): """Closes the database again at the end of the request.""" g.db.close() - return response @app.route('/') diff --git a/flask/__init__.py b/flask/__init__.py index 3a232e5e..f9fcc09f 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -19,7 +19,7 @@ from .app import Flask, Request, Response from .config import Config from .helpers import url_for, jsonify, json_available, flash, \ send_file, send_from_directory, get_flashed_messages, \ - get_template_attribute, make_response + get_template_attribute, make_response, safe_join from .globals import current_app, g, request, session, _request_ctx_stack from .ctx import has_request_context from .module import Module @@ -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 6a637554..2ed8a65f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -25,13 +25,14 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ locked_cached_property, _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, Environment, \ _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() @@ -124,6 +125,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') @@ -194,6 +198,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), @@ -339,6 +344,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 @@ -771,13 +789,38 @@ class Flask(_PackageBoundObject): return f def after_request(self, f): - """Register a function to be run after each request.""" + """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 @@ -808,10 +851,21 @@ class Flask(_PackageBoundObject): .. versionadded: 0.3 """ + exc_type, exc_value, tb = sys.exc_info() + got_request_exception.send(self, exception=e) handler = self.error_handlers.get(500) + if self.propagate_exceptions: - raise + # if we want to repropagate the exception, we can attempt to + # raise it with the whole traceback in case we can do that + # (the function was actually called from the except part) + # otherwise, we just raise the error again + if exc_value is e: + raise exc_type, exc_value, tb + else: + raise e + self.logger.exception('Exception on %s [%s]' % ( request.path, request.method @@ -825,21 +879,41 @@ 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. + + .. versionadded:: 0.7 + """ 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. @@ -949,7 +1023,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 @@ -960,12 +1037,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:: @@ -983,22 +1061,13 @@ class Flask(_PackageBoundObject): finally: ctx.pop() - The big advantage of this approach is that you can use it without - the try/finally statement in a shell for interactive testing: - - >>> ctx = app.test_request_context() - >>> ctx.bind() - >>> request.path - u'/' - >>> ctx.unbind() - .. versionchanged:: 0.3 Added support for non-with statement usage and `with` statement is now passed the ctx object. :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 @@ -1033,16 +1102,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, @@ -1051,20 +1115,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/config.py b/flask/config.py index b588dfa1..bb2d6e9e 100644 --- a/flask/config.py +++ b/flask/config.py @@ -141,8 +141,8 @@ class Config(dict): Objects are usually either modules or classes. - Just the uppercase variables in that object are stored in the config - after lowercasing. Example usage:: + Just the uppercase variables in that object are stored in the config. + Example usage:: app.config.from_object('yourapplication.default_config') from yourapplication import default_config 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/helpers.py b/flask/helpers.py index 3f58b454..45ecf349 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -171,7 +171,8 @@ def url_for(endpoint, **values): ==================== ======================= ============================= Variable arguments that are unknown to the target endpoint are appended - to the generated URL as query arguments. + to the generated URL as query arguments. If the value of a query argument + is `None`, the whole pair is skipped. For more information, head over to the :ref:`Quickstart `. @@ -253,7 +254,8 @@ def get_flashed_messages(with_categories=False): """ flashes = _request_ctx_stack.top.flashes if flashes is None: - _request_ctx_stack.top.flashes = flashes = session.pop('_flashes', []) + _request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \ + if '_flashes' in session else [] if not with_categories: return [x[1] for x in flashes] return flashes @@ -326,7 +328,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if not attachment_filename and not mimetype \ and isinstance(filename, basestring): warn(DeprecationWarning('The filename support for file objects ' - 'passed to send_file is not deprecated. Pass an ' + 'passed to send_file is now deprecated. Pass an ' 'attach_filename if you want mimetypes to be guessed.'), stacklevel=2) if add_etags: @@ -393,6 +395,31 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, return rv +def safe_join(directory, filename): + """Safely join `directory` and `filename`. + + Example usage:: + + @app.route('/wiki/') + def wiki_page(filename): + filename = safe_join(app.config['WIKI_FOLDER'], filename) + with open(filename, 'rb') as fd: + content = fd.read() # Read and process the file content... + + :param directory: the base directory. + :param filename: the untrusted filename relative to that directory. + :raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path + would fall out of `directory`. + """ + filename = posixpath.normpath(filename) + for sep in _os_alt_seps: + if sep in filename: + raise NotFound() + if os.path.isabs(filename) or filename.startswith('../'): + raise NotFound() + return os.path.join(directory, filename) + + def send_from_directory(directory, filename, **options): """Send a file from a given directory with :func:`send_file`. This is a secure way to quickly expose static files from an upload folder @@ -420,13 +447,7 @@ def send_from_directory(directory, filename, **options): :param options: optional keyword arguments that are directly forwarded to :func:`send_file`. """ - filename = posixpath.normpath(filename) - for sep in _os_alt_seps: - if sep in filename: - raise NotFound() - if os.path.isabs(filename) or filename.startswith('../'): - raise NotFound() - filename = os.path.join(directory, filename) + filename = safe_join(directory, filename) if not os.path.isfile(filename): raise NotFound() return send_file(filename, conditional=True, **options) diff --git a/flask/logging.py b/flask/logging.py index 29caadce..8379ab66 100644 --- a/flask/logging.py +++ b/flask/logging.py @@ -11,7 +11,7 @@ from __future__ import absolute_import -from logging import getLogger, StreamHandler, Formatter, Logger, DEBUG +from logging import getLogger, StreamHandler, Formatter, getLoggerClass, DEBUG def create_logger(app): @@ -21,6 +21,7 @@ def create_logger(app): function also removes all attached handlers in case there was a logger with the log name before. """ + Logger = getLoggerClass() class DebugLogger(Logger): def getEffectiveLevel(x): 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/flask/wrappers.py b/flask/wrappers.py index 422085a0..cc35e086 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -81,7 +81,13 @@ class Request(RequestBase): if __debug__: _assert_have_json() if self.mimetype == 'application/json': - return json.loads(self.data) + request_charset = self.mimetype_params.get('charset') + if request_charset is not None: + j = json.loads(self.data, encoding=request_charset ) + else: + j = json.loads(self.data) + + return j class Response(ResponseBase): diff --git a/tests/flask_tests.py b/tests/flask_tests.py index f6454a88..8530df8a 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -415,37 +415,6 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert 'after' in evts assert rv == 'request|after' - def test_after_request_errors(self): - app = flask.Flask(__name__) - called = [] - @app.after_request - def after_request(response): - called.append(True) - return response - @app.route('/') - def fails(): - 1/0 - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert 'Internal Server Error' in rv.data - assert len(called) == 1 - - def test_after_request_handler_error(self): - called = [] - app = flask.Flask(__name__) - @app.after_request - def after_request(response): - called.append(True) - 1/0 - return response - @app.route('/') - def fails(): - 1/0 - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert 'Internal Server Error' in rv.data - assert len(called) == 1 - def test_teardown_request_handler(self): called = [] app = flask.Flask(__name__) @@ -477,7 +446,6 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert 'Response' in rv.data assert len(called) == 1 - def test_teardown_request_handler_error(self): called = [] app = flask.Flask(__name__) @@ -511,7 +479,6 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert 'Internal Server Error' in rv.data assert len(called) == 2 - def test_before_after_request_order(self): called = [] app = flask.Flask(__name__) @@ -564,6 +531,19 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert rv.status_code == 500 assert 'internal server error' == rv.data + def test_teardown_on_pop(self): + buffer = [] + app = flask.Flask(__name__) + @app.teardown_request + def end_of_request(exception): + buffer.append(exception) + + ctx = app.test_request_context() + ctx.push() + assert buffer == [] + ctx.pop() + assert buffer == [None] + def test_response_creation(self): app = flask.Flask(__name__) @app.route('/unicode') @@ -659,6 +639,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): app.config.update( SERVER_NAME='localhost.localdomain:5000' ) + @app.route('/') def index(): return None @@ -798,9 +779,40 @@ class BasicFunctionalityTestCase(unittest.TestCase): t.start() t.join() + def test_max_content_length(self): + app = flask.Flask(__name__) + 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'] + assert False + @app.errorhandler(413) + def catcher(error): + return '42' + + c = app.test_client() + rv = c.post('/accept', data={'myfile': 'foo' * 100}) + assert rv.data == '42' + class JSONTestCase(unittest.TestCase): + def test_json_body_encoding(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/') + def index(): + return flask.request.json + + c = app.test_client() + resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), + content_type='application/json; charset=iso-8859-15') + assert resp.data == u'Hällo Wörld'.encode('utf-8') + def test_jsonify(self): d = dict(a=23, b=42, c=[1, 2, 3]) app = flask.Flask(__name__)