Flask update
This commit is contained in:
Regular → Executable
+2
-2
@@ -6,11 +6,11 @@
|
||||
A microframework based on Werkzeug. It's extensively documented
|
||||
and follows best practice patterns.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
__version__ = '0.8-dev'
|
||||
__version__ = '0.9-dev'
|
||||
|
||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||
# in the module but are exported as public interface.
|
||||
|
||||
Regular → Executable
+79
-97
@@ -5,7 +5,7 @@
|
||||
|
||||
This module implements the central WSGI application object.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -41,6 +41,12 @@ from .signals import request_started, request_finished, got_request_exception, \
|
||||
_logger_lock = Lock()
|
||||
|
||||
|
||||
def _make_timedelta(value):
|
||||
if not isinstance(value, timedelta):
|
||||
return timedelta(seconds=value)
|
||||
return value
|
||||
|
||||
|
||||
def setupmethod(f):
|
||||
"""Wraps a method so that it performs a check in debug mode if the
|
||||
first request was already handled.
|
||||
@@ -184,7 +190,8 @@ class Flask(_PackageBoundObject):
|
||||
#: This attribute can also be configured from the config with the
|
||||
#: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to
|
||||
#: ``timedelta(days=31)``
|
||||
permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME')
|
||||
permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME',
|
||||
get_converter=_make_timedelta)
|
||||
|
||||
#: Enable this if you want to use the X-Sendfile feature. Keep in
|
||||
#: mind that the server has to support this. This only affects files
|
||||
@@ -231,11 +238,16 @@ class Flask(_PackageBoundObject):
|
||||
'PROPAGATE_EXCEPTIONS': None,
|
||||
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
|
||||
'SECRET_KEY': None,
|
||||
'SESSION_COOKIE_NAME': 'session',
|
||||
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
|
||||
'USE_X_SENDFILE': False,
|
||||
'LOGGER_NAME': None,
|
||||
'SERVER_NAME': None,
|
||||
'APPLICATION_ROOT': None,
|
||||
'SESSION_COOKIE_NAME': 'session',
|
||||
'SESSION_COOKIE_DOMAIN': None,
|
||||
'SESSION_COOKIE_PATH': None,
|
||||
'SESSION_COOKIE_HTTPONLY': True,
|
||||
'SESSION_COOKIE_SECURE': False,
|
||||
'MAX_CONTENT_LENGTH': None,
|
||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||
'TRAP_HTTP_EXCEPTIONS': False
|
||||
@@ -461,7 +473,7 @@ class Flask(_PackageBoundObject):
|
||||
if self.import_name == '__main__':
|
||||
fn = getattr(sys.modules['__main__'], '__file__', None)
|
||||
if fn is None:
|
||||
return 'unknown'
|
||||
return '__main__'
|
||||
return os.path.splitext(os.path.basename(fn))[0]
|
||||
return self.import_name
|
||||
|
||||
@@ -601,7 +613,7 @@ class Flask(_PackageBoundObject):
|
||||
"""Creates the loader for the Jinja2 environment. Can be used to
|
||||
override just the loader and keeping the rest unchanged. It's
|
||||
discouraged to override this function. Instead one should override
|
||||
the :meth:`create_jinja_loader` function instead.
|
||||
the :meth:`jinja_loader` function instead.
|
||||
|
||||
The global loader dispatches between the loaders of the application
|
||||
and the individual blueprints.
|
||||
@@ -652,7 +664,7 @@ class Flask(_PackageBoundObject):
|
||||
# existing views.
|
||||
context.update(orig_ctx)
|
||||
|
||||
def run(self, host='127.0.0.1', port=5000, **options):
|
||||
def run(self, host='127.0.0.1', port=5000, debug=None, **options):
|
||||
"""Runs the application on a local development server. If the
|
||||
:attr:`debug` flag is set the server will automatically reload
|
||||
for code changes and show a debugger in case an exception happened.
|
||||
@@ -675,14 +687,16 @@ class Flask(_PackageBoundObject):
|
||||
:param host: the hostname to listen on. set this to ``'0.0.0.0'``
|
||||
to have the server available externally as well.
|
||||
:param port: the port of the webserver
|
||||
:param debug: if given, enable or disable debug mode.
|
||||
See :attr:`debug`.
|
||||
:param options: the options to be forwarded to the underlying
|
||||
Werkzeug server. See
|
||||
:func:`werkzeug.serving.run_simple` for more
|
||||
information.
|
||||
"""
|
||||
from werkzeug.serving import run_simple
|
||||
if 'debug' in options:
|
||||
self.debug = options.pop('debug')
|
||||
if debug is not None:
|
||||
self.debug = bool(debug)
|
||||
options.setdefault('use_reloader', self.debug)
|
||||
options.setdefault('use_debugger', self.debug)
|
||||
try:
|
||||
@@ -705,6 +719,8 @@ class Flask(_PackageBoundObject):
|
||||
rv = c.get('/?vodka=42')
|
||||
assert request.args['vodka'] == '42'
|
||||
|
||||
See :class:`~flask.testing.FlaskClient` for more information.
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
added support for `with` block usage for the client.
|
||||
|
||||
@@ -815,9 +831,11 @@ class Flask(_PackageBoundObject):
|
||||
|
||||
app.view_functions['index'] = index
|
||||
|
||||
If a view function is provided some defaults can be specified directly
|
||||
on the view function. For more information refer to
|
||||
:ref:`view-func-options`.
|
||||
Internally :meth:`route` invokes :meth:`add_url_rule` so if you want
|
||||
to customize the behavior via subclassing you only need to change
|
||||
this method.
|
||||
|
||||
For more information refer to :ref:`url-route-registrations`.
|
||||
|
||||
.. versionchanged:: 0.2
|
||||
`view_func` parameter added.
|
||||
@@ -876,73 +894,33 @@ class Flask(_PackageBoundObject):
|
||||
|
||||
def route(self, rule, **options):
|
||||
"""A decorator that is used to register a view function for a
|
||||
given URL rule. Example::
|
||||
given URL rule. This does the same thing as :meth:`add_url_rule`
|
||||
but is intended for decorator usage::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello World'
|
||||
|
||||
Variables parts in the route can be specified with angular
|
||||
brackets (``/user/<username>``). By default a variable part
|
||||
in the URL accepts any string without a slash however a different
|
||||
converter can be specified as well by using ``<converter:name>``.
|
||||
|
||||
Variable parts are passed to the view function as keyword
|
||||
arguments.
|
||||
|
||||
The following converters are possible:
|
||||
|
||||
=========== ===========================================
|
||||
`int` accepts integers
|
||||
`float` like `int` but for floating point values
|
||||
`path` like the default but also accepts slashes
|
||||
=========== ===========================================
|
||||
|
||||
Here some examples::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
pass
|
||||
|
||||
@app.route('/<username>')
|
||||
def show_user(username):
|
||||
pass
|
||||
|
||||
@app.route('/post/<int:post_id>')
|
||||
def show_post(post_id):
|
||||
pass
|
||||
|
||||
An important detail to keep in mind is how Flask deals with trailing
|
||||
slashes. The idea is to keep each URL unique so the following rules
|
||||
apply:
|
||||
|
||||
1. If a rule ends with a slash and is requested without a slash
|
||||
by the user, the user is automatically redirected to the same
|
||||
page with a trailing slash attached.
|
||||
2. If a rule does not end with a trailing slash and the user request
|
||||
the page with a trailing slash, a 404 not found is raised.
|
||||
|
||||
This is consistent with how web servers deal with static files. This
|
||||
also makes it possible to use relative link targets safely.
|
||||
|
||||
The :meth:`route` decorator accepts a couple of other arguments
|
||||
as well:
|
||||
For more information refer to :ref:`url-route-registrations`.
|
||||
|
||||
:param rule: the URL rule as string
|
||||
:param methods: a list of methods this rule should be limited
|
||||
:param endpoint: the endpoint for the registered URL rule. Flask
|
||||
itself assumes the name of the view function as
|
||||
endpoint
|
||||
:param view_func: the function to call when serving a request to the
|
||||
provided endpoint
|
||||
:param options: the options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object. A change
|
||||
to Werkzeug is handling of method options. methods
|
||||
is a list of methods this rule should be limited
|
||||
to (`GET`, `POST` etc.). By default a rule
|
||||
just listens for `GET` (and implicitly `HEAD`).
|
||||
Starting with Flask 0.6, `OPTIONS` is implicitly
|
||||
added and handled by the standard request handling.
|
||||
:param subdomain: specifies the rule for the subdomain in case
|
||||
subdomain matching is in use.
|
||||
:param strict_slashes: can be used to disable the strict slashes
|
||||
setting for this rule. See above.
|
||||
:param options: other options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object.
|
||||
"""
|
||||
def decorator(f):
|
||||
self.add_url_rule(rule, None, f, **options)
|
||||
endpoint = options.pop('endpoint', None)
|
||||
self.add_url_rule(rule, endpoint, f, **options)
|
||||
return f
|
||||
return decorator
|
||||
|
||||
@@ -1213,14 +1191,24 @@ class Flask(_PackageBoundObject):
|
||||
else:
|
||||
raise e
|
||||
|
||||
self.logger.exception('Exception on %s [%s]' % (
|
||||
request.path,
|
||||
request.method
|
||||
))
|
||||
self.log_exception((exc_type, exc_value, tb))
|
||||
if handler is None:
|
||||
return InternalServerError()
|
||||
return handler(e)
|
||||
|
||||
def log_exception(self, exc_info):
|
||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||
if debugging is disabled and right before the handler is called.
|
||||
The default implementation logs the exception as error on the
|
||||
:attr:`logger`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
self.logger.error('Exception on %s [%s]' % (
|
||||
request.path,
|
||||
request.method
|
||||
), exc_info=exc_info)
|
||||
|
||||
def raise_routing_exception(self, request):
|
||||
"""Exceptions that are recording during routing are reraised with
|
||||
this method. During debug we are not reraising redirect requests
|
||||
@@ -1302,17 +1290,18 @@ class Flask(_PackageBoundObject):
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
# This would be nicer in Werkzeug 0.7, which however currently
|
||||
# is not released. Werkzeug 0.7 provides a method called
|
||||
# allowed_methods() that returns all methods that are valid for
|
||||
# a given path.
|
||||
methods = []
|
||||
try:
|
||||
_request_ctx_stack.top.url_adapter.match(method='--')
|
||||
except MethodNotAllowed, e:
|
||||
methods = e.valid_methods
|
||||
except HTTPException, e:
|
||||
pass
|
||||
adapter = _request_ctx_stack.top.url_adapter
|
||||
if hasattr(adapter, 'allowed_methods'):
|
||||
methods = adapter.allowed_methods()
|
||||
else:
|
||||
# fallback for Werkzeug < 0.7
|
||||
methods = []
|
||||
try:
|
||||
adapter.match(method='--')
|
||||
except MethodNotAllowed, e:
|
||||
methods = e.valid_methods
|
||||
except HTTPException, e:
|
||||
pass
|
||||
rv = self.response_class()
|
||||
rv.allow.update(methods)
|
||||
return rv
|
||||
@@ -1383,7 +1372,7 @@ class Flask(_PackageBoundObject):
|
||||
This also triggers the :meth:`url_value_processor` functions before
|
||||
the actualy :meth:`before_request` functions are called.
|
||||
"""
|
||||
bp = request.blueprint
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
|
||||
funcs = self.url_value_preprocessors.get(None, ())
|
||||
if bp is not None and bp in self.url_value_preprocessors:
|
||||
@@ -1414,8 +1403,6 @@ class Flask(_PackageBoundObject):
|
||||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
bp = ctx.request.blueprint
|
||||
if not self.session_interface.is_null_session(ctx.session):
|
||||
self.save_session(ctx.session, response)
|
||||
funcs = ()
|
||||
if bp is not None and bp in self.after_request_funcs:
|
||||
funcs = reversed(self.after_request_funcs[bp])
|
||||
@@ -1423,6 +1410,8 @@ class Flask(_PackageBoundObject):
|
||||
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
||||
for handler in funcs:
|
||||
response = handler(response)
|
||||
if not self.session_interface.is_null_session(ctx.session):
|
||||
self.save_session(ctx.session, response)
|
||||
return response
|
||||
|
||||
def do_teardown_request(self):
|
||||
@@ -1433,7 +1422,7 @@ class Flask(_PackageBoundObject):
|
||||
tighter control over certain resources under testing environments.
|
||||
"""
|
||||
funcs = reversed(self.teardown_request_funcs.get(None, ()))
|
||||
bp = request.blueprint
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
if bp is not None and bp in self.teardown_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||
exc = sys.exc_info()[1]
|
||||
@@ -1478,19 +1467,12 @@ class Flask(_PackageBoundObject):
|
||||
:func:`werkzeug.test.EnvironBuilder` for more information, this
|
||||
function accepts the same arguments).
|
||||
"""
|
||||
from werkzeug.test import create_environ
|
||||
environ_overrides = kwargs.setdefault('environ_overrides', {})
|
||||
if self.config.get('SERVER_NAME'):
|
||||
server_name = self.config.get('SERVER_NAME')
|
||||
if ':' not in server_name:
|
||||
http_host, http_port = server_name, '80'
|
||||
else:
|
||||
http_host, http_port = server_name.split(':', 1)
|
||||
|
||||
environ_overrides.setdefault('SERVER_NAME', server_name)
|
||||
environ_overrides.setdefault('HTTP_HOST', server_name)
|
||||
environ_overrides.setdefault('SERVER_PORT', http_port)
|
||||
return self.request_context(create_environ(*args, **kwargs))
|
||||
from flask.testing import make_test_environ_builder
|
||||
builder = make_test_environ_builder(self, *args, **kwargs)
|
||||
try:
|
||||
return self.request_context(builder.get_environ())
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
"""The actual WSGI application. This is not implemented in
|
||||
|
||||
Regular → Executable
+16
-13
@@ -59,7 +59,7 @@ class BlueprintSetupState(object):
|
||||
self.url_defaults = dict(self.blueprint.url_values_defaults)
|
||||
self.url_defaults.update(self.options.get('url_defaults', ()))
|
||||
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
def add_url_rule(self, rule, endpoint = None, view_func = None, **options):
|
||||
"""A helper method to register a rule (and optionally a view function)
|
||||
to the application. The endpoint is automatically prefixed with the
|
||||
blueprint's name.
|
||||
@@ -73,7 +73,7 @@ class BlueprintSetupState(object):
|
||||
if 'defaults' in options:
|
||||
defaults = dict(defaults, **options.pop('defaults'))
|
||||
self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
|
||||
view_func, defaults=defaults, **options)
|
||||
view_func, defaults = defaults, **options)
|
||||
|
||||
|
||||
class Blueprint(_PackageBoundObject):
|
||||
@@ -89,9 +89,9 @@ class Blueprint(_PackageBoundObject):
|
||||
warn_on_modifications = False
|
||||
_got_registered_once = False
|
||||
|
||||
def __init__(self, name, import_name, static_folder=None,
|
||||
static_url_path=None, template_folder=None,
|
||||
url_prefix=None, subdomain=None, url_defaults=None):
|
||||
def __init__(self, name, import_name, static_folder = None,
|
||||
static_url_path = None, template_folder = None,
|
||||
url_prefix = None, subdomain = None, url_defaults = None):
|
||||
_PackageBoundObject.__init__(self, import_name, template_folder)
|
||||
self.name = name
|
||||
self.url_prefix = url_prefix
|
||||
@@ -128,14 +128,14 @@ class Blueprint(_PackageBoundObject):
|
||||
func(state)
|
||||
return self.record(update_wrapper(wrapper, func))
|
||||
|
||||
def make_setup_state(self, app, options, first_registration=False):
|
||||
def make_setup_state(self, app, options, first_registration = False):
|
||||
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
|
||||
object that is later passed to the register callback functions.
|
||||
Subclasses can override this to return a subclass of the setup state.
|
||||
"""
|
||||
return BlueprintSetupState(self, app, options, first_registration)
|
||||
|
||||
def register(self, app, options, first_registration=False):
|
||||
def register(self, app, options, first_registration = False):
|
||||
"""Called by :meth:`Flask.register_blueprint` to register a blueprint
|
||||
on the application. This can be overridden to customize the register
|
||||
behavior. Keyword arguments from
|
||||
@@ -146,8 +146,8 @@ class Blueprint(_PackageBoundObject):
|
||||
state = self.make_setup_state(app, options, first_registration)
|
||||
if self.has_static_folder:
|
||||
state.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||
view_func=self.send_static_file,
|
||||
endpoint='static')
|
||||
view_func = self.send_static_file,
|
||||
endpoint = 'static')
|
||||
|
||||
for deferred in self.deferred_functions:
|
||||
deferred(state)
|
||||
@@ -157,14 +157,17 @@ class Blueprint(_PackageBoundObject):
|
||||
:func:`url_for` function is prefixed with the name of the blueprint.
|
||||
"""
|
||||
def decorator(f):
|
||||
self.add_url_rule(rule, f.__name__, f, **options)
|
||||
endpoint = options.pop("endpoint", f.__name__)
|
||||
self.add_url_rule(rule, endpoint, f, **options)
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
def add_url_rule(self, rule, endpoint = None, view_func = None, **options):
|
||||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
||||
the :func:`url_for` function is prefixed with the name of the blueprint.
|
||||
"""
|
||||
if endpoint:
|
||||
assert '.' not in endpoint, "Blueprint endpoint's should not contain dot's"
|
||||
self.record(lambda s:
|
||||
s.add_url_rule(rule, endpoint, view_func, **options))
|
||||
|
||||
@@ -289,8 +292,8 @@ class Blueprint(_PackageBoundObject):
|
||||
def app_url_value_preprocessor(self, f):
|
||||
"""Same as :meth:`url_value_preprocessor` but application wide.
|
||||
"""
|
||||
self.record_once(lambda s: s.app.url_value_preprocessor
|
||||
.setdefault(self.name, []).append(f))
|
||||
self.record_once(lambda s: s.app.url_value_preprocessors
|
||||
.setdefault(None, []).append(f))
|
||||
return f
|
||||
|
||||
def app_url_defaults(self, f):
|
||||
|
||||
Regular → Executable
+7
-3
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements the configuration related objects.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -21,13 +21,17 @@ from werkzeug.utils import import_string
|
||||
class ConfigAttribute(object):
|
||||
"""Makes an attribute forward to the config"""
|
||||
|
||||
def __init__(self, name):
|
||||
def __init__(self, name, get_converter=None):
|
||||
self.__name__ = name
|
||||
self.get_converter = get_converter
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return obj.config[self.__name__]
|
||||
rv = obj.config[self.__name__]
|
||||
if self.get_converter is not None:
|
||||
rv = self.get_converter(rv)
|
||||
return rv
|
||||
|
||||
def __set__(self, obj, value):
|
||||
obj.config[self.__name__] = value
|
||||
|
||||
Regular → Executable
+38
-5
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements the objects required to keep the context.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -89,9 +89,13 @@ class RequestContext(object):
|
||||
self.flashes = None
|
||||
self.session = None
|
||||
|
||||
# indicator if the context was preserved. Next time another context
|
||||
# is pushed the preserved context is popped.
|
||||
self.preserved = False
|
||||
|
||||
self.match_request()
|
||||
|
||||
# Support for deprecated functionality. This is doing away with
|
||||
# XXX: Support for deprecated functionality. This is doing away with
|
||||
# Flask 1.0
|
||||
blueprint = self.request.blueprint
|
||||
if blueprint is not None:
|
||||
@@ -114,6 +118,18 @@ class RequestContext(object):
|
||||
|
||||
def push(self):
|
||||
"""Binds the request context to the current context."""
|
||||
# If an exception ocurrs in debug mode or if context preservation is
|
||||
# activated under exception situations exactly one context stays
|
||||
# on the stack. The rationale is that you want to access that
|
||||
# information under debug situations. However if someone forgets to
|
||||
# pop that context again we want to make sure that on the next push
|
||||
# it's invalidated otherwise we run at risk that something leaks
|
||||
# memory. This is usually only a problem in testsuite since this
|
||||
# functionality is not active in production environments.
|
||||
top = _request_ctx_stack.top
|
||||
if top is not None and top.preserved:
|
||||
top.pop()
|
||||
|
||||
_request_ctx_stack.push(self)
|
||||
|
||||
# Open the session at the moment that the request context is
|
||||
@@ -128,8 +144,15 @@ class RequestContext(object):
|
||||
also trigger the execution of functions registered by the
|
||||
:meth:`~flask.Flask.teardown_request` decorator.
|
||||
"""
|
||||
self.preserved = False
|
||||
self.app.do_teardown_request()
|
||||
_request_ctx_stack.pop()
|
||||
rv = _request_ctx_stack.pop()
|
||||
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
|
||||
% (rv, self)
|
||||
|
||||
# get rid of circular dependencies at the end of the request
|
||||
# so that we don't require the GC to be active.
|
||||
rv.request.environ['werkzeug.request'] = None
|
||||
|
||||
def __enter__(self):
|
||||
self.push()
|
||||
@@ -141,6 +164,16 @@ class RequestContext(object):
|
||||
# access the request object in the interactive shell. Furthermore
|
||||
# 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.preserve_context_on_exception):
|
||||
if self.request.environ.get('flask._preserve_context') or \
|
||||
(tb is not None and self.app.preserve_context_on_exception):
|
||||
self.preserved = True
|
||||
else:
|
||||
self.pop()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s \'%s\' [%s] of %s>' % (
|
||||
self.__class__.__name__,
|
||||
self.request.url,
|
||||
self.request.method,
|
||||
self.app.name
|
||||
)
|
||||
|
||||
Regular → Executable
+2
-1
@@ -54,7 +54,8 @@ class FormDataRoutingRedirect(AssertionError):
|
||||
|
||||
buf.append(' Make sure to directly send your %s-request to this URL '
|
||||
'since we can\'t make browsers or HTTP clients redirect '
|
||||
'with form data.' % request.method)
|
||||
'with form data reliably or without user interaction.' %
|
||||
request.method)
|
||||
buf.append('\n\nNote: this exception is only raised in debug mode')
|
||||
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
|
||||
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.ext
|
||||
~~~~~~~~~
|
||||
|
||||
Redirect imports for extensions. This module basically makes it possible
|
||||
for us to transition from flaskext.foo to flask_foo without having to
|
||||
force all extensions to upgrade at the same time.
|
||||
|
||||
When a user does ``from flask.ext.foo import bar`` it will attempt to
|
||||
import ``from flask_foo import bar`` first and when that fails it will
|
||||
try to import ``from flaskext.foo import bar``.
|
||||
|
||||
We're switching from namespace packages because it was just too painful for
|
||||
everybody involved.
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
def setup():
|
||||
from ..exthook import ExtensionImporter
|
||||
importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__)
|
||||
importer.install()
|
||||
|
||||
|
||||
setup()
|
||||
del setup
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.exthook
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Redirect imports for extensions. This module basically makes it possible
|
||||
for us to transition from flaskext.foo to flask_foo without having to
|
||||
force all extensions to upgrade at the same time.
|
||||
|
||||
When a user does ``from flask.ext.foo import bar`` it will attempt to
|
||||
import ``from flask_foo import bar`` first and when that fails it will
|
||||
try to import ``from flaskext.foo import bar``.
|
||||
|
||||
We're switching from namespace packages because it was just too painful for
|
||||
everybody involved.
|
||||
|
||||
This is used by `flask.ext`.
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
class ExtensionImporter(object):
|
||||
"""This importer redirects imports from this submodule to other locations.
|
||||
This makes it possible to transition from the old flaskext.name to the
|
||||
newer flask_name without people having a hard time.
|
||||
"""
|
||||
|
||||
def __init__(self, module_choices, wrapper_module):
|
||||
self.module_choices = module_choices
|
||||
self.wrapper_module = wrapper_module
|
||||
self.prefix = wrapper_module + '.'
|
||||
self.prefix_cutoff = wrapper_module.count('.') + 1
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__.__module__ == other.__class__.__module__ and \
|
||||
self.__class__.__name__ == other.__class__.__name__ and \
|
||||
self.wrapper_module == other.wrapper_module and \
|
||||
self.module_choices == other.module_choices
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def install(self):
|
||||
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname.startswith(self.prefix):
|
||||
return self
|
||||
|
||||
def load_module(self, fullname):
|
||||
if fullname in sys.modules:
|
||||
return sys.modules[fullname]
|
||||
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
|
||||
for path in self.module_choices:
|
||||
realname = path % modname
|
||||
try:
|
||||
__import__(realname)
|
||||
except ImportError:
|
||||
exc_type, exc_value, tb = sys.exc_info()
|
||||
# since we only establish the entry in sys.modules at the
|
||||
# very this seems to be redundant, but if recursive imports
|
||||
# happen we will call into the move import a second time.
|
||||
# On the second invocation we still don't have an entry for
|
||||
# fullname in sys.modules, but we will end up with the same
|
||||
# fake module name and that import will succeed since this
|
||||
# one already has a temporary entry in the modules dict.
|
||||
# Since this one "succeeded" temporarily that second
|
||||
# invocation now will have created a fullname entry in
|
||||
# sys.modules which we have to kill.
|
||||
sys.modules.pop(fullname, None)
|
||||
|
||||
# If it's an important traceback we reraise it, otherwise
|
||||
# we swallow it and try the next choice. The skipped frame
|
||||
# is the one from __import__ above which we don't care about
|
||||
if self.is_important_traceback(realname, tb):
|
||||
raise exc_type, exc_value, tb.tb_next
|
||||
continue
|
||||
module = sys.modules[fullname] = sys.modules[realname]
|
||||
if '.' not in modname:
|
||||
setattr(sys.modules[self.wrapper_module], modname, module)
|
||||
return module
|
||||
raise ImportError('No module named %s' % fullname)
|
||||
|
||||
def is_important_traceback(self, important_module, tb):
|
||||
"""Walks a traceback's frames and checks if any of the frames
|
||||
originated in the given important module. If that is the case then we
|
||||
were able to import the module itself but apparently something went
|
||||
wrong when the module was imported. (Eg: import of an import failed).
|
||||
"""
|
||||
while tb is not None:
|
||||
if self.is_important_frame(important_module, tb):
|
||||
return True
|
||||
tb = tb.tb_next
|
||||
return False
|
||||
|
||||
def is_important_frame(self, important_module, tb):
|
||||
"""Checks a single frame if it's important."""
|
||||
g = tb.tb_frame.f_globals
|
||||
if '__name__' not in g:
|
||||
return False
|
||||
|
||||
module_name = g['__name__']
|
||||
|
||||
# Python 2.7 Behavior. Modules are cleaned up late so the
|
||||
# name shows up properly here. Success!
|
||||
if module_name == important_module:
|
||||
return True
|
||||
|
||||
# Some python verisons will will clean up modules so early that the
|
||||
# module name at that point is no longer set. Try guessing from
|
||||
# the filename then.
|
||||
filename = os.path.abspath(tb.tb_frame.f_code.co_filename)
|
||||
test_string = os.path.sep + important_module.replace('.', os.path.sep)
|
||||
return test_string + '.py' in filename or \
|
||||
test_string + os.path.sep + '__init__.py' in filename
|
||||
Regular → Executable
+2
-1
@@ -6,7 +6,7 @@
|
||||
Defines all the global objects that are proxies to the current
|
||||
active context.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -19,6 +19,7 @@ def _lookup_object(name):
|
||||
raise RuntimeError('working outside of request context')
|
||||
return getattr(top, name)
|
||||
|
||||
|
||||
# context locals
|
||||
_request_ctx_stack = LocalStack()
|
||||
current_app = LocalProxy(partial(_lookup_object, 'app'))
|
||||
|
||||
Regular → Executable
+25
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements various helpers.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -18,6 +18,7 @@ import mimetypes
|
||||
from time import time
|
||||
from zlib import adler32
|
||||
from threading import RLock
|
||||
from werkzeug.urls import url_quote
|
||||
|
||||
# try to load the best simplejson implementation available. If JSON
|
||||
# is not installed, we add a failing class.
|
||||
@@ -55,6 +56,7 @@ def _assert_have_json():
|
||||
if not json_available:
|
||||
raise RuntimeError('simplejson not installed')
|
||||
|
||||
|
||||
# figure out if simplejson escapes slashes. This behaviour was changed
|
||||
# from one version to another without reason.
|
||||
if not json_available or '\\/' not in json.dumps('/'):
|
||||
@@ -145,6 +147,13 @@ def make_response(*args):
|
||||
|
||||
response = make_response(render_template('not_found.html'), 404)
|
||||
|
||||
The other use case of this function is to force the return value of a
|
||||
view function into a response which is helpful with view
|
||||
decorators::
|
||||
|
||||
response = make_response(view_function())
|
||||
response.headers['X-Parachutes'] = 'parachutes are cool'
|
||||
|
||||
Internally this function does the following things:
|
||||
|
||||
- if no arguments are passed, it creates a new response argument
|
||||
@@ -177,9 +186,14 @@ def url_for(endpoint, **values):
|
||||
|
||||
For more information, head over to the :ref:`Quickstart <url-building>`.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
The `_anchor` and `_method` parameters were added.
|
||||
|
||||
:param endpoint: the endpoint of the URL (name of the function)
|
||||
:param values: the variable arguments of the URL rule
|
||||
:param _external: if set to `True`, an absolute URL is generated.
|
||||
:param _anchor: if provided this is added as anchor to the URL.
|
||||
:param _method: if provided this explicitly specifies an HTTP method.
|
||||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
blueprint_name = request.blueprint
|
||||
@@ -197,8 +211,14 @@ def url_for(endpoint, **values):
|
||||
elif endpoint.startswith('.'):
|
||||
endpoint = endpoint[1:]
|
||||
external = values.pop('_external', False)
|
||||
anchor = values.pop('_anchor', None)
|
||||
method = values.pop('_method', None)
|
||||
ctx.app.inject_url_defaults(endpoint, values)
|
||||
return ctx.url_adapter.build(endpoint, values, force_external=external)
|
||||
rv = ctx.url_adapter.build(endpoint, values, method=method,
|
||||
force_external=external)
|
||||
if anchor is not None:
|
||||
rv += '#' + url_quote(anchor)
|
||||
return rv
|
||||
|
||||
|
||||
def get_template_attribute(template_name, attribute):
|
||||
@@ -477,6 +497,8 @@ def get_root_path(import_name):
|
||||
directory = os.path.dirname(sys.modules[import_name].__file__)
|
||||
return os.path.abspath(directory)
|
||||
except AttributeError:
|
||||
# this is necessary in case we are running from the interactive
|
||||
# python shell. It will never be used for production code however
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
@@ -492,6 +514,7 @@ def find_package(import_name):
|
||||
root_mod = sys.modules[import_name.split('.')[0]]
|
||||
package_path = getattr(root_mod, '__file__', None)
|
||||
if package_path is None:
|
||||
# support for the interactive python shell
|
||||
package_path = os.getcwd()
|
||||
else:
|
||||
package_path = os.path.abspath(os.path.dirname(package_path))
|
||||
|
||||
Regular → Executable
+4
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements the logging support for Flask.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -25,7 +25,9 @@ def create_logger(app):
|
||||
|
||||
class DebugLogger(Logger):
|
||||
def getEffectiveLevel(x):
|
||||
return DEBUG if app.debug else Logger.getEffectiveLevel(x)
|
||||
if x.level == 0 and app.debug:
|
||||
return DEBUG
|
||||
return Logger.getEffectiveLevel(x)
|
||||
|
||||
class DebugHandler(StreamHandler):
|
||||
def emit(x, record):
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements a class that represents module blueprints.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
Regular → Executable
+2
-2
@@ -6,14 +6,14 @@
|
||||
This module used to flask with the session global so we moved it
|
||||
over to flask.sessions
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('please use flask.sessions instead'))
|
||||
|
||||
from .sessions import *
|
||||
from .sessions import SecureCookieSession, NullSession
|
||||
|
||||
Session = SecureCookieSession
|
||||
_NullSession = NullSession
|
||||
|
||||
Regular → Executable
+32
-4
@@ -6,7 +6,7 @@
|
||||
Implements cookie based sessions based on Werkzeug's secure cookie
|
||||
system.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -123,10 +123,34 @@ class SessionInterface(object):
|
||||
"""Helpful helper method that returns the cookie domain that should
|
||||
be used for the session cookie if session cookies are used.
|
||||
"""
|
||||
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
|
||||
return app.config['SESSION_COOKIE_DOMAIN']
|
||||
if app.config['SERVER_NAME'] is not None:
|
||||
# chop of the port which is usually not supported by browsers
|
||||
return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
|
||||
|
||||
def get_cookie_path(self, app):
|
||||
"""Returns the path for which the cookie should be valid. The
|
||||
default implementation uses the value from the SESSION_COOKIE_PATH``
|
||||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
||||
uses ``/`` if it's `None`.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_PATH'] or \
|
||||
app.config['APPLICATION_ROOT'] or '/'
|
||||
|
||||
def get_cookie_httponly(self, app):
|
||||
"""Returns True if the session cookie should be httponly. This
|
||||
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
||||
config var.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_HTTPONLY']
|
||||
|
||||
def get_cookie_secure(self, app):
|
||||
"""Returns True if the cookie should be secure. This currently
|
||||
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_SECURE']
|
||||
|
||||
def get_expiration_time(self, app, session):
|
||||
"""A helper method that returns an expiration date for the session
|
||||
or `None` if the session is linked to the browser session. The
|
||||
@@ -169,9 +193,13 @@ class SecureCookieSessionInterface(SessionInterface):
|
||||
def save_session(self, app, session, response):
|
||||
expires = self.get_expiration_time(app, session)
|
||||
domain = self.get_cookie_domain(app)
|
||||
path = self.get_cookie_path(app)
|
||||
httponly = self.get_cookie_httponly(app)
|
||||
secure = self.get_cookie_secure(app)
|
||||
if session.modified and not session:
|
||||
response.delete_cookie(app.session_cookie_name,
|
||||
response.delete_cookie(app.session_cookie_name, path=path,
|
||||
domain=domain)
|
||||
else:
|
||||
session.save_cookie(response, app.session_cookie_name,
|
||||
expires=expires, httponly=True, domain=domain)
|
||||
session.save_cookie(response, app.session_cookie_name, path=path,
|
||||
expires=expires, httponly=httponly,
|
||||
secure=secure, domain=domain)
|
||||
|
||||
Regular → Executable
+2
-2
@@ -6,7 +6,7 @@
|
||||
Implements signals based on blinker if available, otherwise
|
||||
falls silently back to a noop
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
signals_available = False
|
||||
@@ -34,7 +34,7 @@ except ImportError:
|
||||
'not installed.')
|
||||
send = lambda *a, **kw: None
|
||||
connect = disconnect = has_receivers_for = receivers_for = \
|
||||
temporarily_connected_to = _fail
|
||||
temporarily_connected_to = connected_to = _fail
|
||||
del _fail
|
||||
|
||||
# the namespace for code signals. If you are not flask code, do
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements the bridge to Jinja2.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import posixpath
|
||||
|
||||
Regular → Executable
+84
-33
@@ -6,62 +6,113 @@
|
||||
Implements test support helpers. This module is lazily imported
|
||||
and usually not used in production environments.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from contextlib import contextmanager
|
||||
from werkzeug.test import Client, EnvironBuilder
|
||||
from flask import _request_ctx_stack
|
||||
|
||||
|
||||
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
|
||||
"""Creates a new test builder with some application defaults thrown in."""
|
||||
http_host = app.config.get('SERVER_NAME')
|
||||
app_root = app.config.get('APPLICATION_ROOT')
|
||||
if base_url is None:
|
||||
base_url = 'http://%s/' % (http_host or 'localhost')
|
||||
if app_root:
|
||||
base_url += app_root.lstrip('/')
|
||||
return EnvironBuilder(path, base_url, *args, **kwargs)
|
||||
|
||||
|
||||
class FlaskClient(Client):
|
||||
"""Works like a regular Werkzeug test client but has some
|
||||
knowledge about how Flask works to defer the cleanup of the
|
||||
request context stack to the end of a with body when used
|
||||
in a with statement.
|
||||
"""Works like a regular Werkzeug test client but has some knowledge about
|
||||
how Flask works to defer the cleanup of the request context stack to the
|
||||
end of a with body when used in a with statement. For general information
|
||||
about how to use this class refer to :class:`werkzeug.test.Client`.
|
||||
|
||||
Basic usage is outlined in the :ref:`testing` chapter.
|
||||
"""
|
||||
|
||||
preserve_context = context_preserved = False
|
||||
preserve_context = False
|
||||
|
||||
@contextmanager
|
||||
def session_transaction(self, *args, **kwargs):
|
||||
"""When used in combination with a with statement this opens a
|
||||
session transaction. This can be used to modify the session that
|
||||
the test client uses. Once the with block is left the session is
|
||||
stored back.
|
||||
|
||||
with client.session_transaction() as session:
|
||||
session['value'] = 42
|
||||
|
||||
Internally this is implemented by going through a temporary test
|
||||
request context and since session handling could depend on
|
||||
request variables this function accepts the same arguments as
|
||||
:meth:`~flask.Flask.test_request_context` which are directly
|
||||
passed through.
|
||||
"""
|
||||
if self.cookie_jar is None:
|
||||
raise RuntimeError('Session transactions only make sense '
|
||||
'with cookies enabled.')
|
||||
app = self.application
|
||||
environ_overrides = kwargs.setdefault('environ_overrides', {})
|
||||
self.cookie_jar.inject_wsgi(environ_overrides)
|
||||
outer_reqctx = _request_ctx_stack.top
|
||||
with app.test_request_context(*args, **kwargs) as c:
|
||||
sess = app.open_session(c.request)
|
||||
if sess is None:
|
||||
raise RuntimeError('Session backend did not open a session. '
|
||||
'Check the configuration')
|
||||
|
||||
# Since we have to open a new request context for the session
|
||||
# handling we want to make sure that we hide out own context
|
||||
# from the caller. By pushing the original request context
|
||||
# (or None) on top of this and popping it we get exactly that
|
||||
# behavior. It's important to not use the push and pop
|
||||
# methods of the actual request context object since that would
|
||||
# mean that cleanup handlers are called
|
||||
_request_ctx_stack.push(outer_reqctx)
|
||||
try:
|
||||
yield sess
|
||||
finally:
|
||||
_request_ctx_stack.pop()
|
||||
|
||||
resp = app.response_class()
|
||||
if not app.session_interface.is_null_session(sess):
|
||||
app.save_session(sess, resp)
|
||||
headers = resp.get_wsgi_headers(c.request.environ)
|
||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
self.context_preserved = False
|
||||
kwargs.setdefault('environ_overrides', {}) \
|
||||
['flask._preserve_context'] = self.preserve_context
|
||||
|
||||
as_tuple = kwargs.pop('as_tuple', False)
|
||||
buffered = kwargs.pop('buffered', False)
|
||||
follow_redirects = kwargs.pop('follow_redirects', False)
|
||||
builder = make_test_environ_builder(self.application, *args, **kwargs)
|
||||
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
|
||||
if self.application.config.get('SERVER_NAME'):
|
||||
server_name = self.application.config.get('SERVER_NAME')
|
||||
if ':' not in server_name:
|
||||
http_host, http_port = server_name, None
|
||||
else:
|
||||
http_host, http_port = server_name.split(':', 1)
|
||||
if builder.base_url == 'http://localhost/':
|
||||
# Default Generated Base URL
|
||||
if http_port != None:
|
||||
builder.host = http_host + ':' + http_port
|
||||
else:
|
||||
builder.host = http_host
|
||||
old = _request_ctx_stack.top
|
||||
try:
|
||||
return Client.open(self, builder,
|
||||
as_tuple=as_tuple,
|
||||
buffered=buffered,
|
||||
follow_redirects=follow_redirects)
|
||||
finally:
|
||||
self.context_preserved = _request_ctx_stack.top is not old
|
||||
return Client.open(self, builder,
|
||||
as_tuple=as_tuple,
|
||||
buffered=buffered,
|
||||
follow_redirects=follow_redirects)
|
||||
|
||||
def __enter__(self):
|
||||
if self.preserve_context:
|
||||
raise RuntimeError('Cannot nest client invocations')
|
||||
self.preserve_context = True
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.preserve_context = False
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
|
||||
# on exit we want to clean up earlier. Normally the request context
|
||||
# stays preserved until the next request in the same thread comes
|
||||
# in. See RequestGlobals.push() for the general behavior.
|
||||
top = _request_ctx_stack.top
|
||||
if top is not None and top.preserved:
|
||||
top.pop()
|
||||
|
||||
Regular → Executable
+41
-3
@@ -15,7 +15,6 @@ http_method_funcs = frozenset(['get', 'post', 'head', 'options',
|
||||
'delete', 'put', 'trace'])
|
||||
|
||||
|
||||
|
||||
class View(object):
|
||||
"""Alternative way to use view functions. A subclass has to implement
|
||||
:meth:`dispatch_request` which is called with the view arguments from
|
||||
@@ -30,13 +29,41 @@ class View(object):
|
||||
return 'Hello %s!' % name
|
||||
|
||||
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
||||
|
||||
When you want to decorate a pluggable view you will have to either do that
|
||||
when the view function is created (by wrapping the return value of
|
||||
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
|
||||
|
||||
class SecretView(View):
|
||||
methods = ['GET']
|
||||
decorators = [superuser_required]
|
||||
|
||||
def dispatch_request(self):
|
||||
...
|
||||
|
||||
The decorators stored in the decorators list are applied one after another
|
||||
when the view function is created. Note that you can *not* use the class
|
||||
based decorators since those would decorate the view class and not the
|
||||
generated view function!
|
||||
"""
|
||||
|
||||
#: A for which methods this pluggable view can handle.
|
||||
methods = None
|
||||
|
||||
#: The canonical way to decorate class based views is to decorate the
|
||||
#: return value of as_view(). However since this moves parts of the
|
||||
#: logic from the class declaration to the place where it's hooked
|
||||
#: into the routing system.
|
||||
#:
|
||||
#: You can place one or more decorators in this list and whenever the
|
||||
#: view function is created the result is automatically decorated.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
decorators = []
|
||||
|
||||
def dispatch_request(self):
|
||||
"""Subclasses have to override this method to implement the
|
||||
actual view functionc ode. This method is called with all
|
||||
actual view function code. This method is called with all
|
||||
the arguments from the URL rule.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -54,6 +81,13 @@ class View(object):
|
||||
def view(*args, **kwargs):
|
||||
self = view.view_class(*class_args, **class_kwargs)
|
||||
return self.dispatch_request(*args, **kwargs)
|
||||
|
||||
if cls.decorators:
|
||||
view.__name__ = name
|
||||
view.__module__ = cls.__module__
|
||||
for decorator in cls.decorators:
|
||||
view = decorator(view)
|
||||
|
||||
# we attach the view class to the view function for two reasons:
|
||||
# first of all it allows us to easily figure out what class based
|
||||
# view this thing came from, secondly it's also used for instanciating
|
||||
@@ -108,5 +142,9 @@ class MethodView(View):
|
||||
|
||||
def dispatch_request(self, *args, **kwargs):
|
||||
meth = getattr(self, request.method.lower(), None)
|
||||
assert meth is not None, 'Not implemented method'
|
||||
# if the request method is HEAD and we don't have a handler for it
|
||||
# retry with GET
|
||||
if meth is None and request.method == 'HEAD':
|
||||
meth = getattr(self, 'get', None)
|
||||
assert meth is not None, 'Not implemented method %r' % request.method
|
||||
return meth(*args, **kwargs)
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
Implements the WSGI wrappers (request and response).
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user