forked from orbit-oss/flask
Merge branch 'master' into AvivC-improve-docs-in-Flask.preprocess_request
This commit is contained in:
commit
bc7dae3d5a
156 changed files with 3327 additions and 2059 deletions
|
|
@ -10,7 +10,7 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
__version__ = '0.11.dev0'
|
||||
__version__ = '0.13-dev'
|
||||
|
||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||
# in the module but are exported as public interface.
|
||||
|
|
@ -40,7 +40,7 @@ from .signals import signals_available, template_rendered, request_started, \
|
|||
# it.
|
||||
from . import json
|
||||
|
||||
# This was the only thing that flask used to export at one point and it had
|
||||
# This was the only thing that Flask used to export at one point and it had
|
||||
# a more generic name.
|
||||
jsonify = json.jsonify
|
||||
|
||||
|
|
|
|||
|
|
@ -65,17 +65,25 @@ def with_metaclass(meta, *bases):
|
|||
|
||||
|
||||
# Certain versions of pypy have a bug where clearing the exception stack
|
||||
# breaks the __exit__ function in a very peculiar way. This is currently
|
||||
# true for pypy 2.2.1 for instance. The second level of exception blocks
|
||||
# is necessary because pypy seems to forget to check if an exception
|
||||
# happened until the next bytecode instruction?
|
||||
# breaks the __exit__ function in a very peculiar way. The second level of
|
||||
# exception blocks is necessary because pypy seems to forget to check if an
|
||||
# exception happened until the next bytecode instruction?
|
||||
#
|
||||
# Relevant PyPy bugfix commit:
|
||||
# https://bitbucket.org/pypy/pypy/commits/77ecf91c635a287e88e60d8ddb0f4e9df4003301
|
||||
# According to ronan on #pypy IRC, it is released in PyPy2 2.3 and later
|
||||
# versions.
|
||||
#
|
||||
# Ubuntu 14.04 has PyPy 2.2.1, which does exhibit this bug.
|
||||
BROKEN_PYPY_CTXMGR_EXIT = False
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
class _Mgr(object):
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *args):
|
||||
sys.exc_clear()
|
||||
if hasattr(sys, 'exc_clear'):
|
||||
# Python 3 (PyPy3) doesn't have exc_clear
|
||||
sys.exc_clear()
|
||||
try:
|
||||
try:
|
||||
with _Mgr():
|
||||
|
|
|
|||
335
flask/app.py
335
flask/app.py
|
|
@ -10,30 +10,30 @@
|
|||
"""
|
||||
import os
|
||||
import sys
|
||||
from threading import Lock
|
||||
from datetime import timedelta
|
||||
from itertools import chain
|
||||
from functools import update_wrapper
|
||||
from collections import deque
|
||||
from itertools import chain
|
||||
from threading import Lock
|
||||
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
||||
MethodNotAllowed, BadRequest, default_exceptions
|
||||
from werkzeug.datastructures import ImmutableDict, Headers
|
||||
from werkzeug.exceptions import BadRequest, HTTPException, \
|
||||
InternalServerError, MethodNotAllowed, default_exceptions
|
||||
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
||||
|
||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||
locked_cached_property, _endpoint_from_view_func, find_package
|
||||
from . import json, cli
|
||||
from .wrappers import Request, Response
|
||||
from .config import ConfigAttribute, Config
|
||||
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
||||
from .globals import _request_ctx_stack, request, session, g
|
||||
from . import cli, json
|
||||
from ._compat import integer_types, reraise, string_types, text_type
|
||||
from .config import Config, ConfigAttribute
|
||||
from .ctx import AppContext, RequestContext, _AppCtxGlobals
|
||||
from .globals import _request_ctx_stack, g, request, session
|
||||
from .helpers import _PackageBoundObject, \
|
||||
_endpoint_from_view_func, find_package, get_debug_flag, \
|
||||
get_flashed_messages, locked_cached_property, url_for
|
||||
from .sessions import SecureCookieSessionInterface
|
||||
from .signals import appcontext_tearing_down, got_request_exception, \
|
||||
request_finished, request_started, request_tearing_down
|
||||
from .templating import DispatchingJinjaLoader, Environment, \
|
||||
_default_template_ctx_processor
|
||||
from .signals import request_started, request_finished, got_request_exception, \
|
||||
request_tearing_down, appcontext_tearing_down
|
||||
from ._compat import reraise, string_types, text_type, integer_types
|
||||
_default_template_ctx_processor
|
||||
from .wrappers import Request, Response
|
||||
|
||||
# a lock used for logger initialization
|
||||
_logger_lock = Lock()
|
||||
|
|
@ -120,9 +120,12 @@ class Flask(_PackageBoundObject):
|
|||
The `instance_path` and `instance_relative_config` parameters were
|
||||
added.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
The `root_path` parameter was added.
|
||||
|
||||
.. versionadded:: 0.13
|
||||
The `host_matching` and `static_host` parameters were added.
|
||||
|
||||
:param import_name: the name of the application package
|
||||
:param static_url_path: can be used to specify a different path for the
|
||||
static files on the web. Defaults to the name
|
||||
|
|
@ -130,6 +133,13 @@ class Flask(_PackageBoundObject):
|
|||
:param static_folder: the folder with static files that should be served
|
||||
at `static_url_path`. Defaults to the ``'static'``
|
||||
folder in the root path of the application.
|
||||
folder in the root path of the application. Defaults
|
||||
to None.
|
||||
:param host_matching: sets the app's ``url_map.host_matching`` to the given
|
||||
given value. Defaults to False.
|
||||
:param static_host: the host to use when adding the static route. Defaults
|
||||
to None. Required when using ``host_matching=True``
|
||||
with a ``static_folder`` configured.
|
||||
:param template_folder: the folder that contains the templates that should
|
||||
be used by the application. Defaults to
|
||||
``'templates'`` folder in the root path of the
|
||||
|
|
@ -159,7 +169,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
#: The class that is used for the Jinja environment.
|
||||
#:
|
||||
#: .. versionadded:: 1.0
|
||||
#: .. versionadded:: 0.11
|
||||
jinja_environment = Environment
|
||||
|
||||
#: The class that is used for the :data:`~flask.g` instance.
|
||||
|
|
@ -198,7 +208,7 @@ class Flask(_PackageBoundObject):
|
|||
#: 1. Default values for certain config options.
|
||||
#: 2. Access to config values through attributes in addition to keys.
|
||||
#:
|
||||
#: .. versionadded:: 1.0
|
||||
#: .. versionadded:: 0.11
|
||||
config_class = Config
|
||||
|
||||
#: The debug flag. Set this to ``True`` to enable debugging of the
|
||||
|
|
@ -289,7 +299,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
#: Default configuration parameters.
|
||||
default_config = ImmutableDict({
|
||||
'DEBUG': False,
|
||||
'DEBUG': get_debug_flag(default=False),
|
||||
'TESTING': False,
|
||||
'PROPAGATE_EXCEPTIONS': None,
|
||||
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
|
||||
|
|
@ -314,7 +324,8 @@ class Flask(_PackageBoundObject):
|
|||
'PREFERRED_URL_SCHEME': 'http',
|
||||
'JSON_AS_ASCII': True,
|
||||
'JSON_SORT_KEYS': True,
|
||||
'JSONIFY_PRETTYPRINT_REGULAR': True,
|
||||
'JSONIFY_PRETTYPRINT_REGULAR': False,
|
||||
'JSONIFY_MIMETYPE': 'application/json',
|
||||
'TEMPLATES_AUTO_RELOAD': None,
|
||||
})
|
||||
|
||||
|
|
@ -336,7 +347,8 @@ class Flask(_PackageBoundObject):
|
|||
session_interface = SecureCookieSessionInterface()
|
||||
|
||||
def __init__(self, import_name, static_path=None, static_url_path=None,
|
||||
static_folder='static', template_folder='templates',
|
||||
static_folder='static', static_host=None,
|
||||
host_matching=False, template_folder='templates',
|
||||
instance_path=None, instance_relative_config=False,
|
||||
root_path=None):
|
||||
_PackageBoundObject.__init__(self, import_name,
|
||||
|
|
@ -390,7 +402,7 @@ class Flask(_PackageBoundObject):
|
|||
#: is the class for the instance check and the second the error handler
|
||||
#: function.
|
||||
#:
|
||||
#: To register a error handler, use the :meth:`errorhandler`
|
||||
#: To register an error handler, use the :meth:`errorhandler`
|
||||
#: decorator.
|
||||
self.error_handler_spec = {None: self._error_handlers}
|
||||
|
||||
|
|
@ -421,9 +433,8 @@ class Flask(_PackageBoundObject):
|
|||
#: A dictionary with lists of functions that should be called after
|
||||
#: each request. The key of the dictionary is the name of the blueprint
|
||||
#: this function is active for, ``None`` for all requests. This can for
|
||||
#: example be used to open database connections or getting hold of the
|
||||
#: currently logged in user. To register a function here, use the
|
||||
#: :meth:`after_request` decorator.
|
||||
#: example be used to close database connections. To register a function
|
||||
#: here, use the :meth:`after_request` decorator.
|
||||
self.after_request_funcs = {}
|
||||
|
||||
#: A dictionary with lists of functions that are called after
|
||||
|
|
@ -481,7 +492,7 @@ class Flask(_PackageBoundObject):
|
|||
#: A list of shell context processor functions that should be run
|
||||
#: when a shell context is created.
|
||||
#:
|
||||
#: .. versionadded:: 1.0
|
||||
#: .. versionadded:: 0.11
|
||||
self.shell_context_processors = []
|
||||
|
||||
#: all the attached blueprints in a dictionary by name. Blueprints
|
||||
|
|
@ -518,26 +529,29 @@ class Flask(_PackageBoundObject):
|
|||
#: def to_python(self, value):
|
||||
#: return value.split(',')
|
||||
#: def to_url(self, values):
|
||||
#: return ','.join(BaseConverter.to_url(value)
|
||||
#: return ','.join(super(ListConverter, self).to_url(value)
|
||||
#: for value in values)
|
||||
#:
|
||||
#: app = Flask(__name__)
|
||||
#: app.url_map.converters['list'] = ListConverter
|
||||
self.url_map = Map()
|
||||
|
||||
self.url_map.host_matching = host_matching
|
||||
|
||||
# tracks internally if the application already handled at least one
|
||||
# request.
|
||||
self._got_first_request = False
|
||||
self._before_request_lock = Lock()
|
||||
|
||||
# register the static folder for the application. Do that even
|
||||
# if the folder does not exist. First of all it might be created
|
||||
# while the server is running (usually happens during development)
|
||||
# but also because google appengine stores static files somewhere
|
||||
# else when mapped with the .yml file.
|
||||
# Add a static route using the provided static_url_path, static_host,
|
||||
# and static_folder if there is a configured static_folder.
|
||||
# Note we do this without checking if static_folder exists.
|
||||
# For one, it might be created while the server is running (e.g. during
|
||||
# development). Also, Google App Engine stores static files somewhere
|
||||
if self.has_static_folder:
|
||||
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
|
||||
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||
endpoint='static',
|
||||
endpoint='static', host=static_host,
|
||||
view_func=self.send_static_file)
|
||||
|
||||
#: The click command line context for this application. Commands
|
||||
|
|
@ -546,7 +560,7 @@ class Flask(_PackageBoundObject):
|
|||
#: provided by Flask itself and can be overridden.
|
||||
#:
|
||||
#: This is an instance of a :class:`click.Group` object.
|
||||
self.cli = cli.AppGroup(self)
|
||||
self.cli = cli.AppGroup(self.name)
|
||||
|
||||
def _get_error_handlers(self):
|
||||
from warnings import warn
|
||||
|
|
@ -683,7 +697,7 @@ class Flask(_PackageBoundObject):
|
|||
this function to customize the behavior.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
.. versionchanged:: 1.0
|
||||
.. versionchanged:: 0.11
|
||||
``Environment.auto_reload`` set in accordance with
|
||||
``TEMPLATES_AUTO_RELOAD`` configuration option.
|
||||
"""
|
||||
|
|
@ -772,7 +786,7 @@ class Flask(_PackageBoundObject):
|
|||
application. This runs all the registered shell context
|
||||
processors.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
rv = {'app': self, 'g': g}
|
||||
for processor in self.shell_context_processors:
|
||||
|
|
@ -780,8 +794,13 @@ class Flask(_PackageBoundObject):
|
|||
return rv
|
||||
|
||||
def run(self, host=None, port=None, debug=None, **options):
|
||||
"""Runs the application on a local development server. If the
|
||||
:attr:`debug` flag is set the server will automatically reload
|
||||
"""Runs the application on a local development server.
|
||||
|
||||
Do not use ``run()`` in a production setting. It is not intended to
|
||||
meet security and performance requirements for a production server.
|
||||
Instead, see :ref:`deployment` for WSGI server recommendations.
|
||||
|
||||
If the :attr:`debug` flag is set the server will automatically reload
|
||||
for code changes and show a debugger in case an exception happened.
|
||||
|
||||
If you want to run the application in debug mode, but disable the
|
||||
|
|
@ -791,8 +810,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
It is not recommended to use this function for development with
|
||||
automatic reloading as this is badly supported. Instead you should
|
||||
be using the :command:`flask` command line script's ``runserver``
|
||||
support.
|
||||
be using the :command:`flask` command line script's ``run`` support.
|
||||
|
||||
.. admonition:: Keep in Mind
|
||||
|
||||
|
|
@ -809,7 +827,8 @@ 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. Defaults to
|
||||
``'127.0.0.1'``.
|
||||
``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config
|
||||
variable if present.
|
||||
:param port: the port of the webserver. Defaults to ``5000`` or the
|
||||
port defined in the ``SERVER_NAME`` config variable if
|
||||
present.
|
||||
|
|
@ -820,15 +839,22 @@ class Flask(_PackageBoundObject):
|
|||
:func:`werkzeug.serving.run_simple` for more
|
||||
information.
|
||||
"""
|
||||
# Change this into a no-op if the server is invoked from the
|
||||
# command line. Have a look at cli.py for more information.
|
||||
if os.environ.get('FLASK_RUN_FROM_CLI_SERVER') == '1':
|
||||
from .debughelpers import explain_ignored_app_run
|
||||
explain_ignored_app_run()
|
||||
return
|
||||
|
||||
from werkzeug.serving import run_simple
|
||||
if host is None:
|
||||
host = '127.0.0.1'
|
||||
if port is None:
|
||||
server_name = self.config['SERVER_NAME']
|
||||
if server_name and ':' in server_name:
|
||||
port = int(server_name.rsplit(':', 1)[1])
|
||||
else:
|
||||
port = 5000
|
||||
_host = '127.0.0.1'
|
||||
_port = 5000
|
||||
server_name = self.config.get("SERVER_NAME")
|
||||
sn_host, sn_port = None, None
|
||||
if server_name:
|
||||
sn_host, _, sn_port = server_name.partition(':')
|
||||
host = host or sn_host or _host
|
||||
port = int(port or sn_port or _port)
|
||||
if debug is not None:
|
||||
self.debug = bool(debug)
|
||||
options.setdefault('use_reloader', self.debug)
|
||||
|
|
@ -837,7 +863,7 @@ class Flask(_PackageBoundObject):
|
|||
run_simple(host, port, self, **options)
|
||||
finally:
|
||||
# reset the first request information if the development server
|
||||
# resetted normally. This makes it possible to restart the server
|
||||
# reset normally. This makes it possible to restart the server
|
||||
# without reloader and that stuff from an interactive shell.
|
||||
self._got_first_request = False
|
||||
|
||||
|
|
@ -871,9 +897,9 @@ class Flask(_PackageBoundObject):
|
|||
from flask.testing import FlaskClient
|
||||
|
||||
class CustomClient(FlaskClient):
|
||||
def __init__(self, authentication=None, *args, **kwargs):
|
||||
FlaskClient.__init__(*args, **kwargs)
|
||||
self._authentication = authentication
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._authentication = kwargs.pop("authentication")
|
||||
super(CustomClient,self).__init__( *args, **kwargs)
|
||||
|
||||
app.test_client_class = CustomClient
|
||||
client = app.test_client(authentication='Basic ....')
|
||||
|
|
@ -888,7 +914,7 @@ class Flask(_PackageBoundObject):
|
|||
to override the client to be used by setting the
|
||||
:attr:`test_client_class` attribute.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
.. versionchanged:: 0.11
|
||||
Added `**kwargs` to support passing additional keyword arguments to
|
||||
the constructor of :attr:`test_client_class`.
|
||||
"""
|
||||
|
|
@ -949,12 +975,12 @@ class Flask(_PackageBoundObject):
|
|||
def iter_blueprints(self):
|
||||
"""Iterates over all blueprints by the order they were registered.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
return iter(self._blueprint_order)
|
||||
|
||||
@setupmethod
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
|
||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||
decorator. If a view_func is provided it will be registered with the
|
||||
endpoint.
|
||||
|
|
@ -994,6 +1020,10 @@ class Flask(_PackageBoundObject):
|
|||
endpoint
|
||||
:param view_func: the function to call when serving a request to the
|
||||
provided endpoint
|
||||
:param provide_automatic_options: controls whether the ``OPTIONS``
|
||||
method should be added automatically. This can also be controlled
|
||||
by setting the ``view_func.provide_automatic_options = False``
|
||||
before adding the rule.
|
||||
: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
|
||||
|
|
@ -1023,8 +1053,9 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
# starting with Flask 0.8 the view_func object can disable and
|
||||
# force-enable the automatic options handling.
|
||||
provide_automatic_options = getattr(view_func,
|
||||
'provide_automatic_options', None)
|
||||
if provide_automatic_options is None:
|
||||
provide_automatic_options = getattr(view_func,
|
||||
'provide_automatic_options', None)
|
||||
|
||||
if provide_automatic_options is None:
|
||||
if 'OPTIONS' not in methods:
|
||||
|
|
@ -1110,7 +1141,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
@setupmethod
|
||||
def errorhandler(self, code_or_exception):
|
||||
"""A decorator that is used to register a function give a given
|
||||
"""A decorator that is used to register a function given an
|
||||
error code. Example::
|
||||
|
||||
@app.errorhandler(404)
|
||||
|
|
@ -1148,7 +1179,8 @@ class Flask(_PackageBoundObject):
|
|||
that do not necessarily have to be a subclass of the
|
||||
:class:`~werkzeug.exceptions.HTTPException` class.
|
||||
|
||||
:param code: the code as integer for the handler
|
||||
:param code_or_exception: the code as integer for the handler, or
|
||||
an arbitrary exception
|
||||
"""
|
||||
def decorator(f):
|
||||
self._register_error_handler(None, code_or_exception, f)
|
||||
|
|
@ -1342,7 +1374,7 @@ class Flask(_PackageBoundObject):
|
|||
will have to surround the execution of these code by try/except
|
||||
statements and log occurring errors.
|
||||
|
||||
When a teardown function was called because of a exception it will
|
||||
When a teardown function was called because of an exception it will
|
||||
be passed an error object.
|
||||
|
||||
The return values of teardown functions are ignored.
|
||||
|
|
@ -1398,7 +1430,7 @@ class Flask(_PackageBoundObject):
|
|||
def shell_context_processor(self, f):
|
||||
"""Registers a shell context processor function.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
self.shell_context_processors.append(f)
|
||||
return f
|
||||
|
|
@ -1431,24 +1463,13 @@ class Flask(_PackageBoundObject):
|
|||
def find_handler(handler_map):
|
||||
if not handler_map:
|
||||
return
|
||||
queue = deque(exc_class.__mro__)
|
||||
# Protect from geniuses who might create circular references in
|
||||
# __mro__
|
||||
done = set()
|
||||
|
||||
while queue:
|
||||
cls = queue.popleft()
|
||||
if cls in done:
|
||||
continue
|
||||
done.add(cls)
|
||||
for cls in exc_class.__mro__:
|
||||
handler = handler_map.get(cls)
|
||||
if handler is not None:
|
||||
# cache for next time exc_class is raised
|
||||
handler_map[exc_class] = handler
|
||||
return handler
|
||||
|
||||
queue.extend(cls.__mro__)
|
||||
|
||||
# try blueprint handlers
|
||||
handler = find_handler(self.error_handler_spec
|
||||
.get(request.blueprint, {})
|
||||
|
|
@ -1550,7 +1571,7 @@ class Flask(_PackageBoundObject):
|
|||
self.log_exception((exc_type, exc_value, tb))
|
||||
if handler is None:
|
||||
return InternalServerError()
|
||||
return handler(e)
|
||||
return self.finalize_request(handler(e), from_error_handler=True)
|
||||
|
||||
def log_exception(self, exc_info):
|
||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||
|
|
@ -1618,9 +1639,30 @@ class Flask(_PackageBoundObject):
|
|||
rv = self.dispatch_request()
|
||||
except Exception as e:
|
||||
rv = self.handle_user_exception(e)
|
||||
return self.finalize_request(rv)
|
||||
|
||||
def finalize_request(self, rv, from_error_handler=False):
|
||||
"""Given the return value from a view function this finalizes
|
||||
the request by converting it into a response and invoking the
|
||||
postprocessing functions. This is invoked for both normal
|
||||
request dispatching as well as error handlers.
|
||||
|
||||
Because this means that it might be called as a result of a
|
||||
failure a special safe mode is available which can be enabled
|
||||
with the `from_error_handler` flag. If enabled, failures in
|
||||
response processing will be logged and otherwise ignored.
|
||||
|
||||
:internal:
|
||||
"""
|
||||
response = self.make_response(rv)
|
||||
response = self.process_response(response)
|
||||
request_finished.send(self, response=response)
|
||||
try:
|
||||
response = self.process_response(response)
|
||||
request_finished.send(self, response=response)
|
||||
except Exception:
|
||||
if not from_error_handler:
|
||||
raise
|
||||
self.logger.exception('Request finalizing failed with an '
|
||||
'error while handling an error')
|
||||
return response
|
||||
|
||||
def try_trigger_before_first_request_functions(self):
|
||||
|
|
@ -1673,62 +1715,106 @@ class Flask(_PackageBoundObject):
|
|||
return False
|
||||
|
||||
def make_response(self, rv):
|
||||
"""Converts the return value from a view function to a real
|
||||
response object that is an instance of :attr:`response_class`.
|
||||
"""Convert the return value from a view function to an instance of
|
||||
:attr:`response_class`.
|
||||
|
||||
The following types are allowed for `rv`:
|
||||
:param rv: the return value from the view function. The view function
|
||||
must return a response. Returning ``None``, or the view ending
|
||||
without returning, is not allowed. The following types are allowed
|
||||
for ``view_rv``:
|
||||
|
||||
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
|
||||
|
||||
======================= ===========================================
|
||||
:attr:`response_class` the object is returned unchanged
|
||||
:class:`str` a response object is created with the
|
||||
string as body
|
||||
:class:`unicode` a response object is created with the
|
||||
string encoded to utf-8 as body
|
||||
a WSGI function the function is called as WSGI application
|
||||
and buffered as response object
|
||||
:class:`tuple` A tuple in the form ``(response, status,
|
||||
headers)`` or ``(response, headers)``
|
||||
where `response` is any of the
|
||||
types defined here, `status` is a string
|
||||
or an integer and `headers` is a list or
|
||||
a dictionary with header values.
|
||||
======================= ===========================================
|
||||
|
||||
:param rv: the return value from the view function
|
||||
``str`` (``unicode`` in Python 2)
|
||||
A response object is created with the string encoded to UTF-8
|
||||
as the body.
|
||||
|
||||
``bytes`` (``str`` in Python 2)
|
||||
A response object is created with the bytes as the body.
|
||||
|
||||
``tuple``
|
||||
Either ``(body, status, headers)``, ``(body, status)``, or
|
||||
``(body, headers)``, where ``body`` is any of the other types
|
||||
allowed here, ``status`` is a string or an integer, and
|
||||
``headers`` is a dictionary or a list of ``(key, value)``
|
||||
tuples. If ``body`` is a :attr:`response_class` instance,
|
||||
``status`` overwrites the exiting value and ``headers`` are
|
||||
extended.
|
||||
|
||||
:attr:`response_class`
|
||||
The object is returned unchanged.
|
||||
|
||||
other :class:`~werkzeug.wrappers.Response` class
|
||||
The object is coerced to :attr:`response_class`.
|
||||
|
||||
:func:`callable`
|
||||
The function is called as a WSGI application. The result is
|
||||
used to create a response object.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Previously a tuple was interpreted as the arguments for the
|
||||
response object.
|
||||
"""
|
||||
status_or_headers = headers = None
|
||||
if isinstance(rv, tuple):
|
||||
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
|
||||
|
||||
status = headers = None
|
||||
|
||||
# unpack tuple returns
|
||||
if isinstance(rv, (tuple, list)):
|
||||
len_rv = len(rv)
|
||||
|
||||
# a 3-tuple is unpacked directly
|
||||
if len_rv == 3:
|
||||
rv, status, headers = rv
|
||||
# decide if a 2-tuple has status or headers
|
||||
elif len_rv == 2:
|
||||
if isinstance(rv[1], (Headers, dict, tuple, list)):
|
||||
rv, headers = rv
|
||||
else:
|
||||
rv, status = rv
|
||||
# other sized tuples are not allowed
|
||||
else:
|
||||
raise TypeError(
|
||||
'The view function did not return a valid response tuple.'
|
||||
' The tuple must have the form (body, status, headers),'
|
||||
' (body, status), or (body, headers).'
|
||||
)
|
||||
|
||||
# the body must not be None
|
||||
if rv is None:
|
||||
raise ValueError('View function did not return a response')
|
||||
|
||||
if isinstance(status_or_headers, (dict, list)):
|
||||
headers, status_or_headers = status_or_headers, None
|
||||
raise TypeError(
|
||||
'The view function did not return a valid response. The'
|
||||
' function either returned None or ended without a return'
|
||||
' statement.'
|
||||
)
|
||||
|
||||
# make sure the body is an instance of the response class
|
||||
if not isinstance(rv, self.response_class):
|
||||
# When we create a response object directly, we let the constructor
|
||||
# set the headers and status. We do this because there can be
|
||||
# some extra logic involved when creating these objects with
|
||||
# specific values (like default content type selection).
|
||||
if isinstance(rv, (text_type, bytes, bytearray)):
|
||||
rv = self.response_class(rv, headers=headers,
|
||||
status=status_or_headers)
|
||||
headers = status_or_headers = None
|
||||
# let the response class set the status and headers instead of
|
||||
# waiting to do it manually, so that the class can handle any
|
||||
# special logic
|
||||
rv = self.response_class(rv, status=status, headers=headers)
|
||||
status = headers = None
|
||||
else:
|
||||
rv = self.response_class.force_type(rv, request.environ)
|
||||
# evaluate a WSGI callable, or coerce a different response
|
||||
# class to the correct type
|
||||
try:
|
||||
rv = self.response_class.force_type(rv, request.environ)
|
||||
except TypeError as e:
|
||||
new_error = TypeError(
|
||||
'{e}\nThe view function did not return a valid'
|
||||
' response. The return type must be a string, tuple,'
|
||||
' Response instance, or WSGI callable, but it was a'
|
||||
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
|
||||
)
|
||||
reraise(TypeError, new_error, sys.exc_info()[2])
|
||||
|
||||
if status_or_headers is not None:
|
||||
if isinstance(status_or_headers, string_types):
|
||||
rv.status = status_or_headers
|
||||
# prefer the status if it was provided
|
||||
if status is not None:
|
||||
if isinstance(status, (text_type, bytes, bytearray)):
|
||||
rv.status = status
|
||||
else:
|
||||
rv.status_code = status_or_headers
|
||||
rv.status_code = status
|
||||
|
||||
# extend existing headers with provided headers
|
||||
if headers:
|
||||
rv.headers.extend(headers)
|
||||
|
||||
|
|
@ -1962,14 +2048,17 @@ class Flask(_PackageBoundObject):
|
|||
exception context to start the response
|
||||
"""
|
||||
ctx = self.request_context(environ)
|
||||
ctx.push()
|
||||
error = None
|
||||
try:
|
||||
try:
|
||||
ctx.push()
|
||||
response = self.full_dispatch_request()
|
||||
except Exception as e:
|
||||
error = e
|
||||
response = self.make_response(self.handle_exception(e))
|
||||
response = self.handle_exception(e)
|
||||
except:
|
||||
error = sys.exc_info()[1]
|
||||
raise
|
||||
return response(environ, start_response)
|
||||
finally:
|
||||
if self.should_ignore_error(error):
|
||||
|
|
|
|||
|
|
@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject):
|
|||
warn_on_modifications = False
|
||||
_got_registered_once = False
|
||||
|
||||
#: Blueprint local JSON decoder class to use.
|
||||
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
|
||||
json_encoder = None
|
||||
#: Blueprint local JSON decoder class to use.
|
||||
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
|
||||
json_decoder = 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,
|
||||
|
|
@ -407,7 +414,7 @@ class Blueprint(_PackageBoundObject):
|
|||
application-wide function of the :class:`~flask.Flask` object but
|
||||
for error handlers limited to this blueprint.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
self.record_once(lambda s: s.app._register_error_handler(
|
||||
self.name, code_or_exception, f))
|
||||
|
|
|
|||
215
flask/cli.py
215
flask/cli.py
|
|
@ -11,13 +11,15 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from threading import Lock, Thread
|
||||
from functools import update_wrapper
|
||||
|
||||
import click
|
||||
|
||||
from ._compat import iteritems, reraise
|
||||
|
||||
from .helpers import get_debug_flag
|
||||
from . import __version__
|
||||
|
||||
class NoAppException(click.UsageError):
|
||||
"""Raised if an application cannot be found or loaded."""
|
||||
|
|
@ -54,10 +56,10 @@ def prepare_exec_for_file(filename):
|
|||
module = []
|
||||
|
||||
# Chop off file extensions or package markers
|
||||
if filename.endswith('.py'):
|
||||
filename = filename[:-3]
|
||||
elif os.path.split(filename)[1] == '__init__.py':
|
||||
if os.path.split(filename)[1] == '__init__.py':
|
||||
filename = os.path.dirname(filename)
|
||||
elif filename.endswith('.py'):
|
||||
filename = filename[:-3]
|
||||
else:
|
||||
raise NoAppException('The file provided (%s) does exist but is not a '
|
||||
'valid Python file. This means that it cannot '
|
||||
|
|
@ -85,7 +87,21 @@ def locate_app(app_id):
|
|||
module = app_id
|
||||
app_obj = None
|
||||
|
||||
__import__(module)
|
||||
try:
|
||||
__import__(module)
|
||||
except ImportError:
|
||||
# Reraise the ImportError if it occurred within the imported module.
|
||||
# Determine this by checking whether the trace has a depth > 1.
|
||||
if sys.exc_info()[-1].tb_next:
|
||||
stack_trace = traceback.format_exc()
|
||||
raise NoAppException('There was an error trying to import'
|
||||
' the app (%s):\n%s' % (module, stack_trace))
|
||||
else:
|
||||
raise NoAppException('The file/path provided (%s) does not appear'
|
||||
' to exist. Please verify the path is '
|
||||
'correct. If app is not on PYTHONPATH, '
|
||||
'ensure the extension is .py' % module)
|
||||
|
||||
mod = sys.modules[module]
|
||||
if app_obj is None:
|
||||
app = find_best_app(mod)
|
||||
|
|
@ -98,10 +114,35 @@ def locate_app(app_id):
|
|||
return app
|
||||
|
||||
|
||||
def find_default_import_path():
|
||||
app = os.environ.get('FLASK_APP')
|
||||
if app is None:
|
||||
return
|
||||
if os.path.isfile(app):
|
||||
return prepare_exec_for_file(app)
|
||||
return app
|
||||
|
||||
|
||||
def get_version(ctx, param, value):
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
message = 'Flask %(version)s\nPython %(python_version)s'
|
||||
click.echo(message % {
|
||||
'version': __version__,
|
||||
'python_version': sys.version,
|
||||
}, color=ctx.color)
|
||||
ctx.exit()
|
||||
|
||||
version_option = click.Option(['--version'],
|
||||
help='Show the flask version',
|
||||
expose_value=False,
|
||||
callback=get_version,
|
||||
is_flag=True, is_eager=True)
|
||||
|
||||
class DispatchingApp(object):
|
||||
"""Special application that dispatches to a flask application which
|
||||
"""Special application that dispatches to a Flask application which
|
||||
is imported by name in a background thread. If an error happens
|
||||
it is is recorded and shows as part of the WSGI handling which in case
|
||||
it is recorded and shown as part of the WSGI handling which in case
|
||||
of the Werkzeug debugger means that it shows up in the browser.
|
||||
"""
|
||||
|
||||
|
|
@ -155,15 +196,22 @@ class DispatchingApp(object):
|
|||
class ScriptInfo(object):
|
||||
"""Help object to deal with Flask applications. This is usually not
|
||||
necessary to interface with as it's used internally in the dispatching
|
||||
to click.
|
||||
to click. In future versions of Flask this object will most likely play
|
||||
a bigger role. Typically it's created automatically by the
|
||||
:class:`FlaskGroup` but you can also manually create it and pass it
|
||||
onwards as click object.
|
||||
"""
|
||||
|
||||
def __init__(self, app_import_path=None, debug=None, create_app=None):
|
||||
#: The application import path
|
||||
def __init__(self, app_import_path=None, create_app=None):
|
||||
if create_app is None:
|
||||
if app_import_path is None:
|
||||
app_import_path = find_default_import_path()
|
||||
self.app_import_path = app_import_path
|
||||
else:
|
||||
app_import_path = None
|
||||
|
||||
#: Optionally the import path for the Flask application.
|
||||
self.app_import_path = app_import_path
|
||||
#: The debug flag. If this is not None, the application will
|
||||
#: automatically have it's debug flag overridden with this value.
|
||||
self.debug = debug
|
||||
#: Optionally a function that is passed the script info to create
|
||||
#: the instance of the application.
|
||||
self.create_app = create_app
|
||||
|
|
@ -183,13 +231,16 @@ class ScriptInfo(object):
|
|||
if self.create_app is not None:
|
||||
rv = self.create_app(self)
|
||||
else:
|
||||
if self.app_import_path is None:
|
||||
raise NoAppException('Could not locate Flask application. '
|
||||
'You did not provide FLASK_APP or the '
|
||||
'--app parameter.')
|
||||
if not self.app_import_path:
|
||||
raise NoAppException(
|
||||
'Could not locate Flask application. You did not provide '
|
||||
'the FLASK_APP environment variable.\n\nFor more '
|
||||
'information see '
|
||||
'http://flask.pocoo.org/docs/latest/quickstart/')
|
||||
rv = locate_app(self.app_import_path)
|
||||
if self.debug is not None:
|
||||
rv.debug = self.debug
|
||||
debug = get_debug_flag()
|
||||
if debug is not None:
|
||||
rv.debug = debug
|
||||
self._loaded_app = rv
|
||||
return rv
|
||||
|
||||
|
|
@ -210,29 +261,6 @@ def with_appcontext(f):
|
|||
return update_wrapper(decorator, f)
|
||||
|
||||
|
||||
def set_debug_value(ctx, param, value):
|
||||
ctx.ensure_object(ScriptInfo).debug = value
|
||||
|
||||
|
||||
def set_app_value(ctx, param, value):
|
||||
if value is not None:
|
||||
if os.path.isfile(value):
|
||||
value = prepare_exec_for_file(value)
|
||||
elif '.' not in sys.path:
|
||||
sys.path.insert(0, '.')
|
||||
ctx.ensure_object(ScriptInfo).app_import_path = value
|
||||
|
||||
|
||||
debug_option = click.Option(['--debug/--no-debug'],
|
||||
help='Enable or disable debug mode.',
|
||||
default=None, callback=set_debug_value)
|
||||
|
||||
|
||||
app_option = click.Option(['-a', '--app'],
|
||||
help='The application to run',
|
||||
callback=set_app_value, is_eager=True)
|
||||
|
||||
|
||||
class AppGroup(click.Group):
|
||||
"""This works similar to a regular click :class:`~click.Group` but it
|
||||
changes the behavior of the :meth:`command` decorator so that it
|
||||
|
|
@ -273,23 +301,17 @@ class FlaskGroup(AppGroup):
|
|||
|
||||
:param add_default_commands: if this is True then the default run and
|
||||
shell commands wil be added.
|
||||
:param add_app_option: adds the default :option:`--app` option. This gets
|
||||
automatically disabled if a `create_app`
|
||||
callback is defined.
|
||||
:param add_debug_option: adds the default :option:`--debug` option.
|
||||
:param add_version_option: adds the ``--version`` option.
|
||||
:param create_app: an optional callback that is passed the script info
|
||||
and returns the loaded app.
|
||||
"""
|
||||
|
||||
def __init__(self, add_default_commands=True, add_app_option=None,
|
||||
add_debug_option=True, create_app=None, **extra):
|
||||
def __init__(self, add_default_commands=True, create_app=None,
|
||||
add_version_option=True, **extra):
|
||||
params = list(extra.pop('params', None) or ())
|
||||
if add_app_option is None:
|
||||
add_app_option = create_app is None
|
||||
if add_app_option:
|
||||
params.append(app_option)
|
||||
if add_debug_option:
|
||||
params.append(debug_option)
|
||||
|
||||
if add_version_option:
|
||||
params.append(version_option)
|
||||
|
||||
AppGroup.__init__(self, params=params, **extra)
|
||||
self.create_app = create_app
|
||||
|
|
@ -298,7 +320,24 @@ class FlaskGroup(AppGroup):
|
|||
self.add_command(run_command)
|
||||
self.add_command(shell_command)
|
||||
|
||||
self._loaded_plugin_commands = False
|
||||
|
||||
def _load_plugin_commands(self):
|
||||
if self._loaded_plugin_commands:
|
||||
return
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
self._loaded_plugin_commands = True
|
||||
return
|
||||
|
||||
for ep in pkg_resources.iter_entry_points('flask.commands'):
|
||||
self.add_command(ep.load(), ep.name)
|
||||
self._loaded_plugin_commands = True
|
||||
|
||||
def get_command(self, ctx, name):
|
||||
self._load_plugin_commands()
|
||||
|
||||
# We load built-in commands first as these should always be the
|
||||
# same no matter what the app does. If the app does want to
|
||||
# override this it needs to make a custom instance of this group
|
||||
|
|
@ -319,6 +358,8 @@ class FlaskGroup(AppGroup):
|
|||
pass
|
||||
|
||||
def list_commands(self, ctx):
|
||||
self._load_plugin_commands()
|
||||
|
||||
# The commands available is the list of both the application (if
|
||||
# available) plus the builtin commands.
|
||||
rv = set(click.Group.list_commands(self, ctx))
|
||||
|
|
@ -330,7 +371,9 @@ class FlaskGroup(AppGroup):
|
|||
# want the help page to break if the app does not exist.
|
||||
# If someone attempts to use the command we try to create
|
||||
# the app again and this will give us the error.
|
||||
pass
|
||||
# However, we will not do so silently because that would confuse
|
||||
# users.
|
||||
traceback.print_exc()
|
||||
return sorted(rv)
|
||||
|
||||
def main(self, *args, **kwargs):
|
||||
|
|
@ -342,33 +385,6 @@ class FlaskGroup(AppGroup):
|
|||
return AppGroup.main(self, *args, **kwargs)
|
||||
|
||||
|
||||
def script_info_option(*args, **kwargs):
|
||||
"""This decorator works exactly like :func:`click.option` but is eager
|
||||
by default and stores the value in the :attr:`ScriptInfo.data`. This
|
||||
is useful to further customize an application factory in very complex
|
||||
situations.
|
||||
|
||||
:param script_info_key: this is a mandatory keyword argument which
|
||||
defines under which data key the value should
|
||||
be stored.
|
||||
"""
|
||||
try:
|
||||
key = kwargs.pop('script_info_key')
|
||||
except LookupError:
|
||||
raise TypeError('script_info_key not provided.')
|
||||
|
||||
real_callback = kwargs.get('callback')
|
||||
def callback(ctx, param, value):
|
||||
if real_callback is not None:
|
||||
value = real_callback(ctx, value)
|
||||
ctx.ensure_object(ScriptInfo).data[key] = value
|
||||
return value
|
||||
|
||||
kwargs['callback'] = callback
|
||||
kwargs.setdefault('is_eager', True)
|
||||
return click.option(*args, **kwargs)
|
||||
|
||||
|
||||
@click.command('run', short_help='Runs a development server.')
|
||||
@click.option('--host', '-h', default='127.0.0.1',
|
||||
help='The interface to bind to.')
|
||||
|
|
@ -400,16 +416,25 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
|||
Flask is enabled and disabled otherwise.
|
||||
"""
|
||||
from werkzeug.serving import run_simple
|
||||
|
||||
# Set a global flag that indicates that we were invoked from the
|
||||
# command line interface provided server command. This is detected
|
||||
# by Flask.run to make the call into a no-op. This is necessary to
|
||||
# avoid ugly errors when the script that is loaded here also attempts
|
||||
# to start a server.
|
||||
os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1'
|
||||
|
||||
debug = get_debug_flag()
|
||||
if reload is None:
|
||||
reload = info.debug
|
||||
reload = bool(debug)
|
||||
if debugger is None:
|
||||
debugger = info.debug
|
||||
debugger = bool(debug)
|
||||
if eager_loading is None:
|
||||
eager_loading = not reload
|
||||
|
||||
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
|
||||
|
||||
# Extra startup messages. This depends a but on Werkzeug internals to
|
||||
# Extra startup messages. This depends a bit on Werkzeug internals to
|
||||
# not double execute when the reloader kicks in.
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
||||
# If we have an import path we can print it out now which can help
|
||||
|
|
@ -418,8 +443,8 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
|||
# we won't print anything.
|
||||
if info.app_import_path is not None:
|
||||
print(' * Serving Flask app "%s"' % info.app_import_path)
|
||||
if info.debug is not None:
|
||||
print(' * Forcing debug %s' % (info.debug and 'on' or 'off'))
|
||||
if debug is not None:
|
||||
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
|
||||
|
||||
run_simple(host, port, app, use_reloader=reload,
|
||||
use_debugger=debugger, threaded=with_threads)
|
||||
|
|
@ -462,16 +487,22 @@ def shell_command():
|
|||
cli = FlaskGroup(help="""\
|
||||
This shell command acts as general utility script for Flask applications.
|
||||
|
||||
It loads the application configured (either through the FLASK_APP environment
|
||||
variable or the --app parameter) and then provides commands either provided
|
||||
by the application or Flask itself.
|
||||
It loads the application configured (through the FLASK_APP environment
|
||||
variable) and then provides commands either provided by the application or
|
||||
Flask itself.
|
||||
|
||||
The most useful commands are the "run" and "shell" command.
|
||||
|
||||
Example usage:
|
||||
|
||||
flask --app=hello --debug run
|
||||
""")
|
||||
\b
|
||||
%(prefix)s%(cmd)s FLASK_APP=hello.py
|
||||
%(prefix)s%(cmd)s FLASK_DEBUG=1
|
||||
%(prefix)sflask run
|
||||
""" % {
|
||||
'cmd': os.name == 'posix' and 'export' or 'set',
|
||||
'prefix': os.name == 'posix' and '$ ' or '',
|
||||
})
|
||||
|
||||
|
||||
def main(as_module=False):
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ class Config(dict):
|
|||
d = types.ModuleType('config')
|
||||
d.__file__ = filename
|
||||
try:
|
||||
with open(filename) as config_file:
|
||||
with open(filename, mode='rb') as config_file:
|
||||
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
||||
except IOError as e:
|
||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||
|
|
@ -143,10 +143,12 @@ class Config(dict):
|
|||
- a string: in this case the object with that name will be imported
|
||||
- an actual object reference: that object is used directly
|
||||
|
||||
Objects are usually either modules or classes.
|
||||
Objects are usually either modules or classes. :meth:`from_object`
|
||||
loads only the uppercase attributes of the module/class. A ``dict``
|
||||
object will not work with :meth:`from_object` because the keys of a
|
||||
``dict`` are not attributes of the ``dict`` class.
|
||||
|
||||
Just the uppercase variables in that object are stored in the config.
|
||||
Example usage::
|
||||
Example of module-based configuration::
|
||||
|
||||
app.config.from_object('yourapplication.default_config')
|
||||
from yourapplication import default_config
|
||||
|
|
@ -157,6 +159,9 @@ class Config(dict):
|
|||
with :meth:`from_pyfile` and ideally from a location not within the
|
||||
package because the package might be installed system wide.
|
||||
|
||||
See :ref:`config-dev-prod` for an example of class-based configuration
|
||||
using :meth:`from_object`.
|
||||
|
||||
:param obj: an import name or object
|
||||
"""
|
||||
if isinstance(obj, string_types):
|
||||
|
|
@ -176,7 +181,7 @@ class Config(dict):
|
|||
:param silent: set to ``True`` if you want silent failure for missing
|
||||
files.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
filename = os.path.join(self.root_path, filename)
|
||||
|
||||
|
|
@ -194,7 +199,7 @@ class Config(dict):
|
|||
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||
keys.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
mappings = []
|
||||
if len(mapping) == 1:
|
||||
|
|
@ -222,7 +227,7 @@ class Config(dict):
|
|||
app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
|
||||
image_store_config = app.config.get_namespace('IMAGE_STORE_')
|
||||
|
||||
The resulting dictionary `image_store` would look like::
|
||||
The resulting dictionary `image_store_config` would look like::
|
||||
|
||||
{
|
||||
'type': 'fs',
|
||||
|
|
@ -239,7 +244,7 @@ class Config(dict):
|
|||
:param trim_namespace: a flag indicating if the keys of the resulting
|
||||
dictionary should not include the namespace
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
rv = {}
|
||||
for k, v in iteritems(self):
|
||||
|
|
|
|||
72
flask/ctx.py
72
flask/ctx.py
|
|
@ -36,7 +36,7 @@ class _AppCtxGlobals(object):
|
|||
return self.__dict__.pop(name, default)
|
||||
|
||||
def setdefault(self, name, default=None):
|
||||
self.__dict__.setdefault(name, default)
|
||||
return self.__dict__.setdefault(name, default)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.__dict__
|
||||
|
|
@ -181,12 +181,14 @@ class AppContext(object):
|
|||
|
||||
def pop(self, exc=_sentinel):
|
||||
"""Pops the app context."""
|
||||
self._refcnt -= 1
|
||||
if self._refcnt <= 0:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_appcontext(exc)
|
||||
rv = _app_ctx_stack.pop()
|
||||
try:
|
||||
self._refcnt -= 1
|
||||
if self._refcnt <= 0:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_appcontext(exc)
|
||||
finally:
|
||||
rv = _app_ctx_stack.pop()
|
||||
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
|
||||
% (rv, self)
|
||||
appcontext_popped.send(self.app)
|
||||
|
|
@ -341,38 +343,40 @@ class RequestContext(object):
|
|||
"""
|
||||
app_ctx = self._implicit_app_ctx_stack.pop()
|
||||
|
||||
clear_request = False
|
||||
if not self._implicit_app_ctx_stack:
|
||||
self.preserved = False
|
||||
self._preserved_exc = None
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_request(exc)
|
||||
try:
|
||||
clear_request = False
|
||||
if not self._implicit_app_ctx_stack:
|
||||
self.preserved = False
|
||||
self._preserved_exc = None
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_request(exc)
|
||||
|
||||
# If this interpreter supports clearing the exception information
|
||||
# we do that now. This will only go into effect on Python 2.x,
|
||||
# on 3.x it disappears automatically at the end of the exception
|
||||
# stack.
|
||||
if hasattr(sys, 'exc_clear'):
|
||||
sys.exc_clear()
|
||||
# If this interpreter supports clearing the exception information
|
||||
# we do that now. This will only go into effect on Python 2.x,
|
||||
# on 3.x it disappears automatically at the end of the exception
|
||||
# stack.
|
||||
if hasattr(sys, 'exc_clear'):
|
||||
sys.exc_clear()
|
||||
|
||||
request_close = getattr(self.request, 'close', None)
|
||||
if request_close is not None:
|
||||
request_close()
|
||||
clear_request = True
|
||||
request_close = getattr(self.request, 'close', None)
|
||||
if request_close is not None:
|
||||
request_close()
|
||||
clear_request = True
|
||||
finally:
|
||||
rv = _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.
|
||||
if clear_request:
|
||||
rv.request.environ['werkzeug.request'] = None
|
||||
|
||||
# get rid of circular dependencies at the end of the request
|
||||
# so that we don't require the GC to be active.
|
||||
if clear_request:
|
||||
rv.request.environ['werkzeug.request'] = None
|
||||
# Get rid of the app as well if necessary.
|
||||
if app_ctx is not None:
|
||||
app_ctx.pop(exc)
|
||||
|
||||
# Get rid of the app as well if necessary.
|
||||
if app_ctx is not None:
|
||||
app_ctx.pop(exc)
|
||||
assert rv is self, 'Popped wrong request context. ' \
|
||||
'(%r instead of %r)' % (rv, self)
|
||||
|
||||
def auto_pop(self, exc):
|
||||
if self.request.environ.get('flask._preserve_context') or \
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
:copyright: (c) 2015 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
from warnings import warn
|
||||
|
||||
from ._compat import implements_to_string, text_type
|
||||
from .app import Flask
|
||||
from .blueprints import Blueprint
|
||||
|
|
@ -153,3 +156,12 @@ def explain_template_loading_attempts(app, template, attempts):
|
|||
info.append(' See http://flask.pocoo.org/docs/blueprints/#templates')
|
||||
|
||||
app.logger.info('\n'.join(info))
|
||||
|
||||
|
||||
def explain_ignored_app_run():
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
||||
warn(Warning('Silently ignoring app.run() because the '
|
||||
'application is run from the flask command line '
|
||||
'executable. Consider putting app.run() behind an '
|
||||
'if __name__ == "__main__" guard to silence this '
|
||||
'warning.'), stacklevel=3)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,16 @@
|
|||
"""
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
from ._compat import reraise
|
||||
|
||||
|
||||
class ExtDeprecationWarning(DeprecationWarning):
|
||||
pass
|
||||
|
||||
warnings.simplefilter('always', ExtDeprecationWarning)
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -49,13 +56,21 @@ class ExtensionImporter(object):
|
|||
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):
|
||||
if fullname.startswith(self.prefix) and \
|
||||
fullname != 'flask.ext.ExtDeprecationWarning':
|
||||
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]
|
||||
|
||||
warnings.warn(
|
||||
"Importing flask.ext.{x} is deprecated, use flask_{x} instead."
|
||||
.format(x=modname), ExtDeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
for path in self.module_choices:
|
||||
realname = path % modname
|
||||
try:
|
||||
|
|
@ -83,6 +98,14 @@ class ExtensionImporter(object):
|
|||
module = sys.modules[fullname] = sys.modules[realname]
|
||||
if '.' not in modname:
|
||||
setattr(sys.modules[self.wrapper_module], modname, module)
|
||||
|
||||
if realname.startswith('flaskext.'):
|
||||
warnings.warn(
|
||||
"Detected extension named flaskext.{x}, please rename it "
|
||||
"to flask_{x}. The old form is deprecated."
|
||||
.format(x=modname), ExtDeprecationWarning
|
||||
)
|
||||
|
||||
return module
|
||||
raise ImportError('No module named %s' % fullname)
|
||||
|
||||
|
|
|
|||
204
flask/helpers.py
204
flask/helpers.py
|
|
@ -14,10 +14,10 @@ import sys
|
|||
import pkgutil
|
||||
import posixpath
|
||||
import mimetypes
|
||||
from datetime import timedelta
|
||||
from time import time
|
||||
from zlib import adler32
|
||||
from threading import RLock
|
||||
import unicodedata
|
||||
from werkzeug.routing import BuildError
|
||||
from functools import update_wrapper
|
||||
|
||||
|
|
@ -26,8 +26,9 @@ try:
|
|||
except ImportError:
|
||||
from urlparse import quote as url_quote
|
||||
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.exceptions import NotFound
|
||||
from werkzeug.datastructures import Headers, Range
|
||||
from werkzeug.exceptions import BadRequest, NotFound, \
|
||||
RequestedRangeNotSatisfiable
|
||||
|
||||
# this was moved in 0.7
|
||||
try:
|
||||
|
|
@ -54,6 +55,13 @@ _os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
|
|||
if sep not in (None, '/'))
|
||||
|
||||
|
||||
def get_debug_flag(default=None):
|
||||
val = os.environ.get('FLASK_DEBUG')
|
||||
if not val:
|
||||
return default
|
||||
return val.lower() not in ('0', 'false', 'no')
|
||||
|
||||
|
||||
def _endpoint_from_view_func(view_func):
|
||||
"""Internal helper that returns the default endpoint for a given
|
||||
function. This always is the function name.
|
||||
|
|
@ -101,7 +109,7 @@ def stream_with_context(generator_or_function):
|
|||
gen = iter(generator_or_function)
|
||||
except TypeError:
|
||||
def decorator(*args, **kwargs):
|
||||
gen = generator_or_function()
|
||||
gen = generator_or_function(*args, **kwargs)
|
||||
return stream_with_context(gen)
|
||||
return update_wrapper(decorator, generator_or_function)
|
||||
|
||||
|
|
@ -300,20 +308,30 @@ def url_for(endpoint, **values):
|
|||
scheme = values.pop('_scheme', None)
|
||||
appctx.app.inject_url_defaults(endpoint, values)
|
||||
|
||||
# This is not the best way to deal with this but currently the
|
||||
# underlying Werkzeug router does not support overriding the scheme on
|
||||
# a per build call basis.
|
||||
old_scheme = None
|
||||
if scheme is not None:
|
||||
if not external:
|
||||
raise ValueError('When specifying _scheme, _external must be True')
|
||||
old_scheme = url_adapter.url_scheme
|
||||
url_adapter.url_scheme = scheme
|
||||
|
||||
try:
|
||||
rv = url_adapter.build(endpoint, values, method=method,
|
||||
force_external=external)
|
||||
try:
|
||||
rv = url_adapter.build(endpoint, values, method=method,
|
||||
force_external=external)
|
||||
finally:
|
||||
if old_scheme is not None:
|
||||
url_adapter.url_scheme = old_scheme
|
||||
except BuildError as error:
|
||||
# We need to inject the values again so that the app callback can
|
||||
# deal with that sort of stuff.
|
||||
values['_external'] = external
|
||||
values['_anchor'] = anchor
|
||||
values['_method'] = method
|
||||
values['_scheme'] = scheme
|
||||
return appctx.app.handle_url_build_error(error, endpoint, values)
|
||||
|
||||
if anchor is not None:
|
||||
|
|
@ -414,7 +432,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]):
|
|||
|
||||
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||
attachment_filename=None, add_etags=True,
|
||||
cache_timeout=None, conditional=False):
|
||||
cache_timeout=None, conditional=False, last_modified=None):
|
||||
"""Sends the contents of a file to the client. This will use the
|
||||
most efficient method available and configured. By default it will
|
||||
try to use the WSGI server's file_wrapper support. Alternatively
|
||||
|
|
@ -428,6 +446,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
guessing requires a `filename` or an `attachment_filename` to be
|
||||
provided.
|
||||
|
||||
ETags will also be attached automatically if a `filename` is provided. You
|
||||
can turn this off by setting `add_etags=False`.
|
||||
|
||||
If `conditional=True` and `filename` is provided, this method will try to
|
||||
upgrade the response stream to support range requests. This will allow
|
||||
the request to be answered with partial content response.
|
||||
|
||||
Please never pass filenames to this function from user sources;
|
||||
you should use :func:`send_from_directory` instead.
|
||||
|
||||
|
|
@ -446,7 +471,21 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
.. versionchanged:: 0.9
|
||||
cache_timeout pulls its default from application config, when None.
|
||||
|
||||
:param filename_or_fp: the filename of the file to send in `latin-1`.
|
||||
.. versionchanged:: 0.12
|
||||
The filename is no longer automatically inferred from file objects. If
|
||||
you want to use automatic mimetype and etag support, pass a filepath via
|
||||
`filename_or_fp` or `attachment_filename`.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
The `attachment_filename` is preferred over `filename` for MIME-type
|
||||
detection.
|
||||
|
||||
.. versionchanged:: 0.13
|
||||
UTF-8 filenames, as specified in `RFC 2231`_, are supported.
|
||||
|
||||
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
|
||||
|
||||
:param filename_or_fp: the filename of the file to send.
|
||||
This is relative to the :attr:`~Flask.root_path`
|
||||
if a relative path is specified.
|
||||
Alternatively a file object might be provided in
|
||||
|
|
@ -454,8 +493,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
back to the traditional method. Make sure that the
|
||||
file pointer is positioned at the start of data to
|
||||
send before calling :func:`send_file`.
|
||||
:param mimetype: the mimetype of the file if provided, otherwise
|
||||
auto detection happens.
|
||||
:param mimetype: the mimetype of the file if provided. If a file path is
|
||||
given, auto detection happens as fallback, otherwise an
|
||||
error will be raised.
|
||||
:param as_attachment: set to ``True`` if you want to send this file with
|
||||
a ``Content-Disposition: attachment`` header.
|
||||
:param attachment_filename: the filename for the attachment if it
|
||||
|
|
@ -467,69 +507,76 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
(default), this value is set by
|
||||
:meth:`~Flask.get_send_file_max_age` of
|
||||
:data:`~flask.current_app`.
|
||||
:param last_modified: set the ``Last-Modified`` header to this value,
|
||||
a :class:`~datetime.datetime` or timestamp.
|
||||
If a file was passed, this overrides its mtime.
|
||||
"""
|
||||
mtime = None
|
||||
fsize = None
|
||||
if isinstance(filename_or_fp, string_types):
|
||||
filename = filename_or_fp
|
||||
file = None
|
||||
else:
|
||||
from warnings import warn
|
||||
file = filename_or_fp
|
||||
filename = getattr(file, 'name', None)
|
||||
|
||||
# XXX: this behavior is now deprecated because it was unreliable.
|
||||
# removed in Flask 1.0
|
||||
if not attachment_filename and not mimetype \
|
||||
and isinstance(filename, string_types):
|
||||
warn(DeprecationWarning('The filename support for file objects '
|
||||
'passed to send_file is now deprecated. Pass an '
|
||||
'attach_filename if you want mimetypes to be guessed.'),
|
||||
stacklevel=2)
|
||||
if add_etags:
|
||||
warn(DeprecationWarning('In future flask releases etags will no '
|
||||
'longer be generated for file objects passed to the send_file '
|
||||
'function because this behavior was unreliable. Pass '
|
||||
'filenames instead if possible, otherwise attach an etag '
|
||||
'yourself based on another value'), stacklevel=2)
|
||||
|
||||
if filename is not None:
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(current_app.root_path, filename)
|
||||
if mimetype is None and (filename or attachment_filename):
|
||||
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
|
||||
file = None
|
||||
if attachment_filename is None:
|
||||
attachment_filename = os.path.basename(filename)
|
||||
else:
|
||||
file = filename_or_fp
|
||||
filename = None
|
||||
|
||||
if mimetype is None:
|
||||
mimetype = 'application/octet-stream'
|
||||
if attachment_filename is not None:
|
||||
mimetype = mimetypes.guess_type(attachment_filename)[0] \
|
||||
or 'application/octet-stream'
|
||||
|
||||
if mimetype is None:
|
||||
raise ValueError(
|
||||
'Unable to infer MIME-type because no filename is available. '
|
||||
'Please set either `attachment_filename`, pass a filepath to '
|
||||
'`filename_or_fp` or set your own MIME-type via `mimetype`.'
|
||||
)
|
||||
|
||||
headers = Headers()
|
||||
if as_attachment:
|
||||
if attachment_filename is None:
|
||||
if filename is None:
|
||||
raise TypeError('filename unavailable, required for '
|
||||
'sending as attachment')
|
||||
attachment_filename = os.path.basename(filename)
|
||||
headers.add('Content-Disposition', 'attachment',
|
||||
filename=attachment_filename)
|
||||
raise TypeError('filename unavailable, required for '
|
||||
'sending as attachment')
|
||||
|
||||
try:
|
||||
attachment_filename = attachment_filename.encode('latin-1')
|
||||
except UnicodeEncodeError:
|
||||
filenames = {
|
||||
'filename': unicodedata.normalize(
|
||||
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
|
||||
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
|
||||
}
|
||||
else:
|
||||
filenames = {'filename': attachment_filename}
|
||||
|
||||
headers.add('Content-Disposition', 'attachment', **filenames)
|
||||
|
||||
if current_app.use_x_sendfile and filename:
|
||||
if file is not None:
|
||||
file.close()
|
||||
headers['X-Sendfile'] = filename
|
||||
headers['Content-Length'] = os.path.getsize(filename)
|
||||
fsize = os.path.getsize(filename)
|
||||
headers['Content-Length'] = fsize
|
||||
data = None
|
||||
else:
|
||||
if file is None:
|
||||
file = open(filename, 'rb')
|
||||
mtime = os.path.getmtime(filename)
|
||||
headers['Content-Length'] = os.path.getsize(filename)
|
||||
fsize = os.path.getsize(filename)
|
||||
headers['Content-Length'] = fsize
|
||||
data = wrap_file(request.environ, file)
|
||||
|
||||
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
||||
direct_passthrough=True)
|
||||
|
||||
# if we know the file modification date, we can store it as
|
||||
# the time of the last modification.
|
||||
if mtime is not None:
|
||||
rv.last_modified = int(mtime)
|
||||
if last_modified is not None:
|
||||
rv.last_modified = last_modified
|
||||
elif mtime is not None:
|
||||
rv.last_modified = mtime
|
||||
|
||||
rv.cache_control.public = True
|
||||
if cache_timeout is None:
|
||||
|
|
@ -539,8 +586,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
rv.expires = int(time() + cache_timeout)
|
||||
|
||||
if add_etags and filename is not None:
|
||||
from warnings import warn
|
||||
|
||||
try:
|
||||
rv.set_etag('flask-%s-%s-%s' % (
|
||||
rv.set_etag('%s-%s-%s' % (
|
||||
os.path.getmtime(filename),
|
||||
os.path.getsize(filename),
|
||||
adler32(
|
||||
|
|
@ -552,17 +601,28 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
warn('Access %s failed, maybe it does not exist, so ignore etags in '
|
||||
'headers' % filename, stacklevel=2)
|
||||
|
||||
if conditional:
|
||||
if conditional:
|
||||
if callable(getattr(Range, 'to_content_range_header', None)):
|
||||
# Werkzeug supports Range Requests
|
||||
# Remove this test when support for Werkzeug <0.12 is dropped
|
||||
try:
|
||||
rv = rv.make_conditional(request, accept_ranges=True,
|
||||
complete_length=fsize)
|
||||
except RequestedRangeNotSatisfiable:
|
||||
file.close()
|
||||
raise
|
||||
else:
|
||||
rv = rv.make_conditional(request)
|
||||
# make sure we don't send x-sendfile for servers that
|
||||
# ignore the 304 status code for x-sendfile.
|
||||
if rv.status_code == 304:
|
||||
rv.headers.pop('x-sendfile', None)
|
||||
# make sure we don't send x-sendfile for servers that
|
||||
# ignore the 304 status code for x-sendfile.
|
||||
if rv.status_code == 304:
|
||||
rv.headers.pop('x-sendfile', None)
|
||||
return rv
|
||||
|
||||
|
||||
def safe_join(directory, filename):
|
||||
"""Safely join `directory` and `filename`.
|
||||
def safe_join(directory, *pathnames):
|
||||
"""Safely join `directory` and zero or more untrusted `pathnames`
|
||||
components.
|
||||
|
||||
Example usage::
|
||||
|
||||
|
|
@ -572,20 +632,23 @@ def safe_join(directory, 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 resulting path
|
||||
would fall out of `directory`.
|
||||
:param directory: the trusted base directory.
|
||||
:param pathnames: the untrusted pathnames relative to that directory.
|
||||
:raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
|
||||
paths fall out of its boundaries.
|
||||
"""
|
||||
filename = posixpath.normpath(filename)
|
||||
for sep in _os_alt_seps:
|
||||
if sep in filename:
|
||||
for filename in pathnames:
|
||||
if filename != '':
|
||||
filename = posixpath.normpath(filename)
|
||||
for sep in _os_alt_seps:
|
||||
if sep in filename:
|
||||
raise NotFound()
|
||||
if os.path.isabs(filename) or \
|
||||
filename == '..' or \
|
||||
filename.startswith('../'):
|
||||
raise NotFound()
|
||||
if os.path.isabs(filename) or \
|
||||
filename == '..' or \
|
||||
filename.startswith('../'):
|
||||
raise NotFound()
|
||||
return os.path.join(directory, filename)
|
||||
directory = os.path.join(directory, filename)
|
||||
return directory
|
||||
|
||||
|
||||
def send_from_directory(directory, filename, **options):
|
||||
|
|
@ -618,8 +681,11 @@ def send_from_directory(directory, filename, **options):
|
|||
filename = safe_join(directory, filename)
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(current_app.root_path, filename)
|
||||
if not os.path.isfile(filename):
|
||||
raise NotFound()
|
||||
try:
|
||||
if not os.path.isfile(filename):
|
||||
raise NotFound()
|
||||
except (TypeError, ValueError):
|
||||
raise BadRequest()
|
||||
options.setdefault('conditional', True)
|
||||
return send_file(filename, **options)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.jsonimpl
|
||||
~~~~~~~~~~~~~~
|
||||
flask.json
|
||||
~~~~~~~~~~
|
||||
|
||||
Implementation helpers for the JSON support in Flask.
|
||||
|
||||
|
|
@ -13,16 +13,14 @@ import uuid
|
|||
from datetime import date
|
||||
from .globals import current_app, request
|
||||
from ._compat import text_type, PY2
|
||||
from .ctx import has_request_context
|
||||
|
||||
from werkzeug.http import http_date
|
||||
from jinja2 import Markup
|
||||
|
||||
# Use the same json implementation as itsdangerous on which we
|
||||
# depend anyways.
|
||||
try:
|
||||
from itsdangerous import simplejson as _json
|
||||
except ImportError:
|
||||
from itsdangerous import json as _json
|
||||
from itsdangerous import json as _json
|
||||
|
||||
|
||||
# Figure out if simplejson escapes slashes. This behavior was changed
|
||||
|
|
@ -94,9 +92,16 @@ class JSONDecoder(_json.JSONDecoder):
|
|||
def _dump_arg_defaults(kwargs):
|
||||
"""Inject default arguments for dump functions."""
|
||||
if current_app:
|
||||
kwargs.setdefault('cls', current_app.json_encoder)
|
||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||
kwargs.setdefault(
|
||||
'cls',
|
||||
bp.json_encoder if bp and bp.json_encoder
|
||||
else current_app.json_encoder
|
||||
)
|
||||
|
||||
if not current_app.config['JSON_AS_ASCII']:
|
||||
kwargs.setdefault('ensure_ascii', False)
|
||||
|
||||
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
||||
else:
|
||||
kwargs.setdefault('sort_keys', True)
|
||||
|
|
@ -106,7 +111,12 @@ def _dump_arg_defaults(kwargs):
|
|||
def _load_arg_defaults(kwargs):
|
||||
"""Inject default arguments for load functions."""
|
||||
if current_app:
|
||||
kwargs.setdefault('cls', current_app.json_decoder)
|
||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||
kwargs.setdefault(
|
||||
'cls',
|
||||
bp.json_decoder if bp and bp.json_decoder
|
||||
else current_app.json_decoder
|
||||
)
|
||||
else:
|
||||
kwargs.setdefault('cls', JSONDecoder)
|
||||
|
||||
|
|
@ -195,14 +205,26 @@ def htmlsafe_dumps(obj, **kwargs):
|
|||
|
||||
def htmlsafe_dump(obj, fp, **kwargs):
|
||||
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
|
||||
fp.write(unicode(htmlsafe_dumps(obj, **kwargs)))
|
||||
fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
|
||||
|
||||
|
||||
def jsonify(*args, **kwargs):
|
||||
"""Creates a :class:`~flask.Response` with the JSON representation of
|
||||
the given arguments with an :mimetype:`application/json` mimetype. The
|
||||
arguments to this function are the same as to the :class:`dict`
|
||||
constructor.
|
||||
"""This function wraps :func:`dumps` to add a few enhancements that make
|
||||
life easier. It turns the JSON output into a :class:`~flask.Response`
|
||||
object with the :mimetype:`application/json` mimetype. For convenience, it
|
||||
also converts multiple arguments into an array or multiple keyword arguments
|
||||
into a dict. This means that both ``jsonify(1,2,3)`` and
|
||||
``jsonify([1,2,3])`` serialize to ``[1,2,3]``.
|
||||
|
||||
For clarity, the JSON serialization behavior has the following differences
|
||||
from :func:`dumps`:
|
||||
|
||||
1. Single argument: Passed straight through to :func:`dumps`.
|
||||
2. Multiple arguments: Converted to an array before being passed to
|
||||
:func:`dumps`.
|
||||
3. Multiple keyword arguments: Converted to a dict before being passed to
|
||||
:func:`dumps`.
|
||||
4. Both args and kwargs: Behavior undefined and will throw an exception.
|
||||
|
||||
Example usage::
|
||||
|
||||
|
|
@ -222,14 +244,15 @@ def jsonify(*args, **kwargs):
|
|||
"id": 42
|
||||
}
|
||||
|
||||
For security reasons only objects are supported toplevel. For more
|
||||
information about this, have a look at :ref:`json-security`.
|
||||
|
||||
This function's response will be pretty printed if it was not requested
|
||||
with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless
|
||||
the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false.
|
||||
Compressed (not pretty) formatting currently means no indents and no
|
||||
spaces after separators.
|
||||
.. versionchanged:: 0.11
|
||||
Added support for serializing top-level arrays. This introduces a
|
||||
security risk in ancient browsers. See :ref:`json-security` for details.
|
||||
|
||||
This function's response will be pretty printed if the
|
||||
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
|
||||
Flask app is running in debug mode. Compressed (not pretty) formatting
|
||||
currently means no indents and no spaces after separators.
|
||||
|
||||
.. versionadded:: 0.2
|
||||
"""
|
||||
|
|
@ -237,18 +260,21 @@ def jsonify(*args, **kwargs):
|
|||
indent = None
|
||||
separators = (',', ':')
|
||||
|
||||
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] \
|
||||
and not request.is_xhr:
|
||||
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug:
|
||||
indent = 2
|
||||
separators = (', ', ': ')
|
||||
|
||||
# Note that we add '\n' to end of response
|
||||
# (see https://github.com/mitsuhiko/flask/pull/1262)
|
||||
rv = current_app.response_class(
|
||||
(dumps(dict(*args, **kwargs), indent=indent, separators=separators),
|
||||
'\n'),
|
||||
mimetype='application/json')
|
||||
return rv
|
||||
if args and kwargs:
|
||||
raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
|
||||
elif len(args) == 1: # single args are passed directly to dumps()
|
||||
data = args[0]
|
||||
else:
|
||||
data = args or kwargs
|
||||
|
||||
return current_app.response_class(
|
||||
(dumps(data, indent=indent, separators=separators), '\n'),
|
||||
mimetype=current_app.config['JSONIFY_MIMETYPE']
|
||||
)
|
||||
|
||||
|
||||
def tojson_filter(obj, **kwargs):
|
||||
|
|
|
|||
|
|
@ -87,4 +87,8 @@ def create_logger(app):
|
|||
logger.__class__ = DebugLogger
|
||||
logger.addHandler(debug_handler)
|
||||
logger.addHandler(prod_handler)
|
||||
|
||||
# Disable propagation by default
|
||||
logger.propagate = False
|
||||
|
||||
return logger
|
||||
|
|
|
|||
|
|
@ -84,21 +84,25 @@ class TaggedJSONSerializer(object):
|
|||
def dumps(self, value):
|
||||
return json.dumps(_tag(value), separators=(',', ':'))
|
||||
|
||||
LOADS_MAP = {
|
||||
' t': tuple,
|
||||
' u': uuid.UUID,
|
||||
' b': b64decode,
|
||||
' m': Markup,
|
||||
' d': parse_date,
|
||||
}
|
||||
|
||||
def loads(self, value):
|
||||
def object_hook(obj):
|
||||
if len(obj) != 1:
|
||||
return obj
|
||||
the_key, the_value = next(iteritems(obj))
|
||||
if the_key == ' t':
|
||||
return tuple(the_value)
|
||||
elif the_key == ' u':
|
||||
return uuid.UUID(the_value)
|
||||
elif the_key == ' b':
|
||||
return b64decode(the_value)
|
||||
elif the_key == ' m':
|
||||
return Markup(the_value)
|
||||
elif the_key == ' d':
|
||||
return parse_date(the_value)
|
||||
# Check the key for a corresponding function
|
||||
return_function = self.LOADS_MAP.get(the_key)
|
||||
if return_function:
|
||||
# Pass the value to the function
|
||||
return return_function(the_value)
|
||||
# Didn't find a function for this object
|
||||
return obj
|
||||
return json.loads(value, object_hook=object_hook)
|
||||
|
||||
|
|
@ -168,7 +172,7 @@ class SessionInterface(object):
|
|||
null_session_class = NullSession
|
||||
|
||||
#: A flag that indicates if the session interface is pickle based.
|
||||
#: This can be used by flask extensions to make a decision in regards
|
||||
#: This can be used by Flask extensions to make a decision in regards
|
||||
#: to how to deal with the session object.
|
||||
#:
|
||||
#: .. versionadded:: 0.10
|
||||
|
|
@ -263,7 +267,7 @@ class SessionInterface(object):
|
|||
|
||||
This check is usually skipped if sessions get deleted.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
if session.modified:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ except ImportError:
|
|||
temporarily_connected_to = connected_to = _fail
|
||||
del _fail
|
||||
|
||||
# The namespace for code signals. If you are not flask code, do
|
||||
# The namespace for code signals. If you are not Flask code, do
|
||||
# not put signals in here. Create your own namespace instead.
|
||||
_signals = Namespace()
|
||||
|
||||
|
|
|
|||
|
|
@ -52,27 +52,36 @@ class DispatchingJinjaLoader(BaseLoader):
|
|||
self.app = app
|
||||
|
||||
def get_source(self, environment, template):
|
||||
explain = self.app.config['EXPLAIN_TEMPLATE_LOADING']
|
||||
if self.app.config['EXPLAIN_TEMPLATE_LOADING']:
|
||||
return self._get_source_explained(environment, template)
|
||||
return self._get_source_fast(environment, template)
|
||||
|
||||
def _get_source_explained(self, environment, template):
|
||||
attempts = []
|
||||
tmplrv = None
|
||||
trv = None
|
||||
|
||||
for srcobj, loader in self._iter_loaders(template):
|
||||
try:
|
||||
rv = loader.get_source(environment, template)
|
||||
if tmplrv is None:
|
||||
tmplrv = rv
|
||||
if not explain:
|
||||
break
|
||||
if trv is None:
|
||||
trv = rv
|
||||
except TemplateNotFound:
|
||||
rv = None
|
||||
attempts.append((loader, srcobj, rv))
|
||||
|
||||
if explain:
|
||||
from .debughelpers import explain_template_loading_attempts
|
||||
explain_template_loading_attempts(self.app, template, attempts)
|
||||
from .debughelpers import explain_template_loading_attempts
|
||||
explain_template_loading_attempts(self.app, template, attempts)
|
||||
|
||||
if tmplrv is not None:
|
||||
return tmplrv
|
||||
if trv is not None:
|
||||
return trv
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def _get_source_fast(self, environment, template):
|
||||
for srcobj, loader in self._iter_loaders(template):
|
||||
try:
|
||||
return loader.get_source(environment, template)
|
||||
except TemplateNotFound:
|
||||
continue
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def _iter_loaders(self, template):
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import werkzeug
|
||||
from contextlib import contextmanager
|
||||
from werkzeug.test import Client, EnvironBuilder
|
||||
from flask import _request_ctx_stack
|
||||
|
|
@ -39,19 +40,32 @@ def make_test_environ_builder(app, path='/', base_url=None, *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. For general information
|
||||
about how to use this class refer to :class:`werkzeug.test.Client`.
|
||||
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`.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
`app.test_client()` includes preset default environment, which can be
|
||||
set after instantiation of the `app.test_client()` object in
|
||||
`client.environ_base`.
|
||||
|
||||
Basic usage is outlined in the :ref:`testing` chapter.
|
||||
"""
|
||||
|
||||
preserve_context = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FlaskClient, self).__init__(*args, **kwargs)
|
||||
self.environ_base = {
|
||||
"REMOTE_ADDR": "127.0.0.1",
|
||||
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__
|
||||
}
|
||||
|
||||
@contextmanager
|
||||
def session_transaction(self, *args, **kwargs):
|
||||
"""When used in combination with a with statement this opens a
|
||||
"""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
|
||||
the test client uses. Once the ``with`` block is left the session is
|
||||
stored back.
|
||||
|
||||
::
|
||||
|
|
@ -100,6 +114,7 @@ class FlaskClient(Client):
|
|||
def open(self, *args, **kwargs):
|
||||
kwargs.setdefault('environ_overrides', {}) \
|
||||
['flask._preserve_context'] = self.preserve_context
|
||||
kwargs.setdefault('environ_base', self.environ_base)
|
||||
|
||||
as_tuple = kwargs.pop('as_tuple', False)
|
||||
buffered = kwargs.pop('buffered', False)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class View(object):
|
|||
generated view function!
|
||||
"""
|
||||
|
||||
#: A for which methods this pluggable view can handle.
|
||||
#: A list of methods this view can handle.
|
||||
methods = None
|
||||
|
||||
#: The canonical way to decorate class-based views is to decorate the
|
||||
|
|
@ -103,33 +103,34 @@ class View(object):
|
|||
|
||||
|
||||
class MethodViewType(type):
|
||||
"""Metaclass for :class:`MethodView` that determines what methods the view
|
||||
defines.
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, d):
|
||||
super(MethodViewType, cls).__init__(name, bases, d)
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
rv = type.__new__(cls, name, bases, d)
|
||||
if 'methods' not in d:
|
||||
methods = set(rv.methods or [])
|
||||
for key in d:
|
||||
if key in http_method_funcs:
|
||||
methods = set()
|
||||
|
||||
for key in http_method_funcs:
|
||||
if hasattr(cls, key):
|
||||
methods.add(key.upper())
|
||||
# If we have no method at all in there we don't want to
|
||||
# add a method list. (This is for instance the case for
|
||||
# the base class or another subclass of a base method view
|
||||
# that does not introduce new methods).
|
||||
|
||||
# If we have no method at all in there we don't want to add a
|
||||
# method list. This is for instance the case for the base class
|
||||
# or another subclass of a base method view that does not introduce
|
||||
# new methods.
|
||||
if methods:
|
||||
rv.methods = sorted(methods)
|
||||
return rv
|
||||
cls.methods = methods
|
||||
|
||||
|
||||
class MethodView(with_metaclass(MethodViewType, View)):
|
||||
"""Like a regular class-based view but that dispatches requests to
|
||||
particular methods. For instance if you implement a method called
|
||||
:meth:`get` it means you will response to ``'GET'`` requests and
|
||||
the :meth:`dispatch_request` implementation will automatically
|
||||
forward your request to that. Also :attr:`options` is set for you
|
||||
automatically::
|
||||
"""A class-based view that dispatches request methods to the corresponding
|
||||
class methods. For example, if you implement a ``get`` method, it will be
|
||||
used to handle ``GET`` requests. ::
|
||||
|
||||
class CounterAPI(MethodView):
|
||||
|
||||
def get(self):
|
||||
return session.get('counter', 0)
|
||||
|
||||
|
|
@ -139,11 +140,14 @@ class MethodView(with_metaclass(MethodViewType, View)):
|
|||
|
||||
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
||||
"""
|
||||
|
||||
def dispatch_request(self, *args, **kwargs):
|
||||
meth = getattr(self, request.method.lower(), None)
|
||||
|
||||
# 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, 'Unimplemented method %r' % request.method
|
||||
return meth(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class Request(RequestBase):
|
|||
is considered to include JSON data if the mimetype is
|
||||
:mimetype:`application/json` or :mimetype:`application/*+json`.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionadded:: 0.11
|
||||
"""
|
||||
mt = self.mimetype
|
||||
if mt == 'application/json':
|
||||
|
|
@ -123,11 +123,12 @@ class Request(RequestBase):
|
|||
return False
|
||||
|
||||
def get_json(self, force=False, silent=False, cache=True):
|
||||
"""Parses the incoming JSON request data and returns it. If
|
||||
parsing fails the :meth:`on_json_loading_failed` method on the
|
||||
request object will be invoked. By default this function will
|
||||
only load the json data if the mimetype is :mimetype:`application/json`
|
||||
but this can be overridden by the `force` parameter.
|
||||
"""Parses the incoming JSON request data and returns it. By default
|
||||
this function will return ``None`` if the mimetype is not
|
||||
:mimetype:`application/json` but this can be overridden by the
|
||||
``force`` parameter. If parsing fails the
|
||||
:meth:`on_json_loading_failed` method on the request object will be
|
||||
invoked.
|
||||
|
||||
:param force: if set to ``True`` the mimetype is ignored.
|
||||
:param silent: if set to ``True`` this method will fail silently
|
||||
|
|
@ -136,7 +137,8 @@ class Request(RequestBase):
|
|||
on the request.
|
||||
"""
|
||||
rv = getattr(self, '_cached_json', _missing)
|
||||
if rv is not _missing:
|
||||
# We return cached JSON only when the cache is enabled.
|
||||
if cache and rv is not _missing:
|
||||
return rv
|
||||
|
||||
if not (force or self.is_json):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue