forked from orbit-oss/flask
Merge branch 'new-request-dispatching' into blueprints
This commit is contained in:
commit
673fa18e6d
28 changed files with 689 additions and 266 deletions
|
|
@ -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:
|
||||
|
|
|
|||
157
flask/app.py
157
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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
35
flask/ctx.py
35
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()
|
||||
|
|
|
|||
|
|
@ -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 <url-building>`.
|
||||
|
||||
|
|
@ -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/<path:filename>')
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue