Flask Update.

Flask Update, works for CPython 3.10.0 Beta 3.
This commit is contained in:
Fernando Ochoa O 2021-06-24 14:43:08 -05:00
parent aac67289e5
commit 529962d6b5
41 changed files with 2882 additions and 2092 deletions

26
.gitignore vendored
View file

@ -1,26 +0,0 @@
.DS_Store
.env
.flaskenv
*.pyc
*.pyo
env/
venv/
.venv/
env*
dist/
build/
*.egg
*.egg-info/
_mailinglist
.tox/
.cache/
.pytest_cache/
.idea/
docs/_build/
.vscode
# Coverage reports
htmlcov/
.coverage
.coverage.*
*,cover

View file

@ -1,46 +1,60 @@
from markupsafe import escape # -*- coding: utf-8 -*-
from markupsafe import Markup """
from werkzeug.exceptions import abort as abort flask
from werkzeug.utils import redirect as redirect ~~~~~
from . import json as json A microframework based on Werkzeug. It's extensively documented
from .app import Flask as Flask and follows best practice patterns.
from .app import Request as Request
from .app import Response as Response
from .blueprints import Blueprint as Blueprint
from .config import Config as Config
from .ctx import after_this_request as after_this_request
from .ctx import copy_current_request_context as copy_current_request_context
from .ctx import has_app_context as has_app_context
from .ctx import has_request_context as has_request_context
from .globals import _app_ctx_stack as _app_ctx_stack
from .globals import _request_ctx_stack as _request_ctx_stack
from .globals import current_app as current_app
from .globals import g as g
from .globals import request as request
from .globals import session as session
from .helpers import flash as flash
from .helpers import get_flashed_messages as get_flashed_messages
from .helpers import get_template_attribute as get_template_attribute
from .helpers import make_response as make_response
from .helpers import safe_join as safe_join
from .helpers import send_file as send_file
from .helpers import send_from_directory as send_from_directory
from .helpers import stream_with_context as stream_with_context
from .helpers import url_for as url_for
from .json import jsonify as jsonify
from .signals import appcontext_popped as appcontext_popped
from .signals import appcontext_pushed as appcontext_pushed
from .signals import appcontext_tearing_down as appcontext_tearing_down
from .signals import before_render_template as before_render_template
from .signals import got_request_exception as got_request_exception
from .signals import message_flashed as message_flashed
from .signals import request_finished as request_finished
from .signals import request_started as request_started
from .signals import request_tearing_down as request_tearing_down
from .signals import signals_available as signals_available
from .signals import template_rendered as template_rendered
from .templating import render_template as render_template
from .templating import render_template_string as render_template_string
__version__ = "2.1.0.dev0" :copyright: 2010 Pallets
:license: BSD-3-Clause
"""
# utilities we import from Werkzeug and Jinja2 that are unused
# in the module but are exported as public interface.
from jinja2 import escape
from jinja2 import Markup
from werkzeug.exceptions import abort
from werkzeug.utils import redirect
from . import json
from ._compat import json_available
from .app import Flask
from .app import Request
from .app import Response
from .blueprints import Blueprint
from .config import Config
from .ctx import after_this_request
from .ctx import copy_current_request_context
from .ctx import has_app_context
from .ctx import has_request_context
from .globals import _app_ctx_stack
from .globals import _request_ctx_stack
from .globals import current_app
from .globals import g
from .globals import request
from .globals import session
from .helpers import flash
from .helpers import get_flashed_messages
from .helpers import get_template_attribute
from .helpers import make_response
from .helpers import safe_join
from .helpers import send_file
from .helpers import send_from_directory
from .helpers import stream_with_context
from .helpers import url_for
from .json import jsonify
from .signals import appcontext_popped
from .signals import appcontext_pushed
from .signals import appcontext_tearing_down
from .signals import before_render_template
from .signals import got_request_exception
from .signals import message_flashed
from .signals import request_finished
from .signals import request_started
from .signals import request_tearing_down
from .signals import signals_available
from .signals import template_rendered
from .templating import render_template
from .templating import render_template_string
__version__ = "1.1.4"

View file

@ -1,3 +1,15 @@
from .cli import main # -*- coding: utf-8 -*-
"""
flask.__main__
~~~~~~~~~~~~~~
main() Alias for flask.run for the command line.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
if __name__ == "__main__":
from .cli import main
main(as_module=True)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

145
src/flask/_compat.py Normal file
View file

@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
"""
flask._compat
~~~~~~~~~~~~~
Some py2/py3 compatibility support based on a stripped down
version of six so we don't have to depend on a specific version
of it.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import sys
PY2 = sys.version_info[0] == 2
_identity = lambda x: x
try: # Python 2
text_type = unicode
string_types = (str, unicode)
integer_types = (int, long)
except NameError: # Python 3
text_type = str
string_types = (str,)
integer_types = (int,)
if not PY2:
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
iteritems = lambda d: iter(d.items())
from inspect import getfullargspec as getargspec
from io import StringIO
import collections.abc as collections_abc
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
implements_to_string = _identity
else:
iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues()
iteritems = lambda d: d.iteritems()
from inspect import getargspec
from cStringIO import StringIO
import collections as collections_abc
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
def implements_to_string(cls):
cls.__unicode__ = cls.__str__
cls.__str__ = lambda x: x.__unicode__().encode("utf-8")
return cls
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a
# dummy metaclass for one level of class instantiation that replaces
# itself with the actual metaclass.
class metaclass(type):
def __new__(metacls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, "temporary_class", (), {})
# Certain versions of pypy have a bug where clearing the exception stack
# 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):
if hasattr(sys, "exc_clear"):
# Python 3 (PyPy3) doesn't have exc_clear
sys.exc_clear()
try:
try:
with _Mgr():
raise AssertionError()
except: # noqa: B001
# We intentionally use a bare except here. See the comment above
# regarding a pypy bug as to why.
raise
except TypeError:
BROKEN_PYPY_CTXMGR_EXIT = True
except AssertionError:
pass
try:
from os import fspath
except ImportError:
# Backwards compatibility as proposed in PEP 0519:
# https://www.python.org/dev/peps/pep-0519/#backwards-compatibility
def fspath(path):
return path.__fspath__() if hasattr(path, "__fspath__") else path
class _DeprecatedBool(object):
def __init__(self, name, version, value):
self.message = "'{}' is deprecated and will be removed in version {}.".format(
name, version
)
self.value = value
def _warn(self):
import warnings
warnings.warn(self.message, DeprecationWarning, stacklevel=2)
def __eq__(self, other):
self._warn()
return other == self.value
def __ne__(self, other):
self._warn()
return other != self.value
def __bool__(self):
self._warn()
return self.value
__nonzero__ = __bool__
json_available = _DeprecatedBool("flask.json_available", "2.0.0", True)

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,31 @@
import typing as t # -*- coding: utf-8 -*-
from collections import defaultdict """
flask.blueprints
~~~~~~~~~~~~~~~~
Blueprints are the recommended way to implement larger or more
pluggable applications in Flask 0.7 and later.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from functools import update_wrapper from functools import update_wrapper
from .scaffold import _endpoint_from_view_func from .helpers import _endpoint_from_view_func
from .scaffold import _sentinel from .helpers import _PackageBoundObject
from .scaffold import Scaffold
from .typing import AfterRequestCallable
from .typing import BeforeFirstRequestCallable
from .typing import BeforeRequestCallable
from .typing import ErrorHandlerCallable
from .typing import TeardownCallable
from .typing import TemplateContextProcessorCallable
from .typing import TemplateFilterCallable
from .typing import TemplateGlobalCallable
from .typing import TemplateTestCallable
from .typing import URLDefaultCallable
from .typing import URLValuePreprocessorCallable
if t.TYPE_CHECKING: # a singleton sentinel value for parameter defaults
from .app import Flask _sentinel = object()
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
class BlueprintSetupState: class BlueprintSetupState(object):
"""Temporary holder object for registering a blueprint with the """Temporary holder object for registering a blueprint with the
application. An instance of this class is created by the application. An instance of this class is created by the
:meth:`~flask.Blueprint.make_setup_state` method and later passed :meth:`~flask.Blueprint.make_setup_state` method and later passed
to all register callback functions. to all register callback functions.
""" """
def __init__( def __init__(self, blueprint, app, options, first_registration):
self,
blueprint: "Blueprint",
app: "Flask",
options: t.Any,
first_registration: bool,
) -> None:
#: a reference to the current application #: a reference to the current application
self.app = app self.app = app
@ -68,21 +57,12 @@ class BlueprintSetupState:
#: blueprint. #: blueprint.
self.url_prefix = url_prefix self.url_prefix = url_prefix
self.name = self.options.get("name", blueprint.name)
self.name_prefix = self.options.get("name_prefix", "")
#: A dictionary with URL defaults that is added to each and every #: A dictionary with URL defaults that is added to each and every
#: URL that was defined with the blueprint. #: URL that was defined with the blueprint.
self.url_defaults = dict(self.blueprint.url_values_defaults) self.url_defaults = dict(self.blueprint.url_values_defaults)
self.url_defaults.update(self.options.get("url_defaults", ())) self.url_defaults.update(self.options.get("url_defaults", ()))
def add_url_rule( def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
**options: t.Any,
) -> None:
"""A helper method to register a rule (and optionally a view function) """A helper method to register a rule (and optionally a view function)
to the application. The endpoint is automatically prefixed with the to the application. The endpoint is automatically prefixed with the
blueprint's name. blueprint's name.
@ -94,21 +74,20 @@ class BlueprintSetupState:
rule = self.url_prefix rule = self.url_prefix
options.setdefault("subdomain", self.subdomain) options.setdefault("subdomain", self.subdomain)
if endpoint is None: if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore endpoint = _endpoint_from_view_func(view_func)
defaults = self.url_defaults defaults = self.url_defaults
if "defaults" in options: if "defaults" in options:
defaults = dict(defaults, **options.pop("defaults")) defaults = dict(defaults, **options.pop("defaults"))
self.app.add_url_rule( self.app.add_url_rule(
rule, rule,
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), "%s.%s" % (self.blueprint.name, endpoint),
view_func, view_func,
defaults=defaults, defaults=defaults,
**options, **options
) )
class Blueprint(Scaffold): class Blueprint(_PackageBoundObject):
"""Represents a blueprint, a collection of routes and other """Represents a blueprint, a collection of routes and other
app-related functions that can be registered on a real application app-related functions that can be registered on a real application
later. later.
@ -122,7 +101,14 @@ class Blueprint(Scaffold):
that is called with :class:`~flask.blueprints.BlueprintSetupState` that is called with :class:`~flask.blueprints.BlueprintSetupState`
when the blueprint is registered on an application. when the blueprint is registered on an application.
See :doc:`/blueprints` for more information. See :ref:`blueprints` for more information.
.. versionchanged:: 1.1.0
Blueprints have a ``cli`` group to register nested CLI commands.
The ``cli_group`` parameter controls the name of the group under
the ``flask`` command.
.. versionadded:: 0.7
:param name: The name of the blueprint. Will be prepended to each :param name: The name of the blueprint. Will be prepended to each
endpoint name. endpoint name.
@ -148,69 +134,65 @@ class Blueprint(Scaffold):
default. default.
:param url_defaults: A dict of default values that blueprint routes :param url_defaults: A dict of default values that blueprint routes
will receive by default. will receive by default.
:param root_path: By default, the blueprint will automatically set :param root_path: By default, the blueprint will automatically this
this based on ``import_name``. In certain situations this based on ``import_name``. In certain situations this automatic
automatic detection can fail, so the path can be specified detection can fail, so the path can be specified manually
manually instead. instead.
.. versionchanged:: 1.1.0
Blueprints have a ``cli`` group to register nested CLI commands.
The ``cli_group`` parameter controls the name of the group under
the ``flask`` command.
.. versionadded:: 0.7
""" """
warn_on_modifications = False warn_on_modifications = False
_got_registered_once = False _got_registered_once = False
#: Blueprint local JSON encoder class to use. Set to ``None`` to use #: Blueprint local JSON decoder class to use.
#: the app's :class:`~flask.Flask.json_encoder`. #: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
json_encoder = None json_encoder = None
#: Blueprint local JSON decoder class to use. Set to ``None`` to use #: Blueprint local JSON decoder class to use.
#: the app's :class:`~flask.Flask.json_decoder`. #: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
json_decoder = None json_decoder = None
# TODO remove the next three attrs when Sphinx :inherited-members: works
# https://github.com/sphinx-doc/sphinx/issues/741
#: The name of the package or module that this app belongs to. Do not
#: change this once it is set by the constructor.
import_name = None
#: Location of the template files to be added to the template lookup.
#: ``None`` if templates should not be added.
template_folder = None
#: Absolute path to the package on the filesystem. Used to look up
#: resources contained in the package.
root_path = None
def __init__( def __init__(
self, self,
name: str, name,
import_name: str, import_name,
static_folder: t.Optional[str] = None, static_folder=None,
static_url_path: t.Optional[str] = None, static_url_path=None,
template_folder: t.Optional[str] = None, template_folder=None,
url_prefix: t.Optional[str] = None, url_prefix=None,
subdomain: t.Optional[str] = None, subdomain=None,
url_defaults: t.Optional[dict] = None, url_defaults=None,
root_path: t.Optional[str] = None, root_path=None,
cli_group: t.Optional[str] = _sentinel, # type: ignore cli_group=_sentinel,
): ):
super().__init__( _PackageBoundObject.__init__(
import_name=import_name, self, import_name, template_folder, root_path=root_path
static_folder=static_folder,
static_url_path=static_url_path,
template_folder=template_folder,
root_path=root_path,
) )
if "." in name:
raise ValueError("'name' may not contain a dot '.' character.")
self.name = name self.name = name
self.url_prefix = url_prefix self.url_prefix = url_prefix
self.subdomain = subdomain self.subdomain = subdomain
self.deferred_functions: t.List[DeferredSetupFunction] = [] self.static_folder = static_folder
self.static_url_path = static_url_path
self.deferred_functions = []
if url_defaults is None: if url_defaults is None:
url_defaults = {} url_defaults = {}
self.url_values_defaults = url_defaults self.url_values_defaults = url_defaults
self.cli_group = cli_group self.cli_group = cli_group
self._blueprints: t.List[t.Tuple["Blueprint", dict]] = []
def _is_setup_finished(self) -> bool: def record(self, func):
return self.warn_on_modifications and self._got_registered_once
def record(self, func: t.Callable) -> None:
"""Registers a function that is called when the blueprint is """Registers a function that is called when the blueprint is
registered on the application. This function is called with the registered on the application. This function is called with the
state as argument as returned by the :meth:`make_setup_state` state as argument as returned by the :meth:`make_setup_state`
@ -221,212 +203,114 @@ class Blueprint(Scaffold):
warn( warn(
Warning( Warning(
"The blueprint was already registered once but is" "The blueprint was already registered once "
" getting modified now. These changes will not show" "but is getting modified now. These changes "
" up." "will not show up."
) )
) )
self.deferred_functions.append(func) self.deferred_functions.append(func)
def record_once(self, func: t.Callable) -> None: def record_once(self, func):
"""Works like :meth:`record` but wraps the function in another """Works like :meth:`record` but wraps the function in another
function that will ensure the function is only called once. If the function that will ensure the function is only called once. If the
blueprint is registered a second time on the application, the blueprint is registered a second time on the application, the
function passed is not called. function passed is not called.
""" """
def wrapper(state: BlueprintSetupState) -> None: def wrapper(state):
if state.first_registration: if state.first_registration:
func(state) func(state)
return self.record(update_wrapper(wrapper, func)) return self.record(update_wrapper(wrapper, func))
def make_setup_state( def make_setup_state(self, app, options, first_registration=False):
self, app: "Flask", options: dict, first_registration: bool = False
) -> BlueprintSetupState:
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
object that is later passed to the register callback functions. object that is later passed to the register callback functions.
Subclasses can override this to return a subclass of the setup state. Subclasses can override this to return a subclass of the setup state.
""" """
return BlueprintSetupState(self, app, options, first_registration) return BlueprintSetupState(self, app, options, first_registration)
def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: def register(self, app, options, first_registration=False):
"""Register a :class:`~flask.Blueprint` on this blueprint. Keyword """Called by :meth:`Flask.register_blueprint` to register all views
arguments passed to this method will override the defaults set and callbacks registered on the blueprint with the application. Creates
on the blueprint. a :class:`.BlueprintSetupState` and calls each :meth:`record` callback
with it.
.. versionchanged:: 2.0.1 :param app: The application this blueprint is being registered with.
The ``name`` option can be used to change the (pre-dotted)
name the blueprint is registered with. This allows the same
blueprint to be registered multiple times with unique names
for ``url_for``.
.. versionadded:: 2.0
"""
if blueprint is self:
raise ValueError("Cannot register a blueprint on itself")
self._blueprints.append((blueprint, options))
def register(self, app: "Flask", options: dict) -> None:
"""Called by :meth:`Flask.register_blueprint` to register all
views and callbacks registered on the blueprint with the
application. Creates a :class:`.BlueprintSetupState` and calls
each :meth:`record` callback with it.
:param app: The application this blueprint is being registered
with.
:param options: Keyword arguments forwarded from :param options: Keyword arguments forwarded from
:meth:`~Flask.register_blueprint`. :meth:`~Flask.register_blueprint`.
:param first_registration: Whether this is the first time this
.. versionchanged:: 2.0.1 blueprint has been registered on the application.
Nested blueprints are registered with their dotted name.
This allows different blueprints with the same name to be
nested at different locations.
.. versionchanged:: 2.0.1
The ``name`` option can be used to change the (pre-dotted)
name the blueprint is registered with. This allows the same
blueprint to be registered multiple times with unique names
for ``url_for``.
.. versionchanged:: 2.0.1
Registering the same blueprint with the same name multiple
times is deprecated and will become an error in Flask 2.1.
""" """
first_registration = not any(bp is self for bp in app.blueprints.values())
name_prefix = options.get("name_prefix", "")
self_name = options.get("name", self.name)
name = f"{name_prefix}.{self_name}".lstrip(".")
if name in app.blueprints:
existing_at = f" '{name}'" if self_name != name else ""
if app.blueprints[name] is not self:
raise ValueError(
f"The name '{self_name}' is already registered for"
f" a different blueprint{existing_at}. Use 'name='"
" to provide a unique name."
)
else:
import warnings
warnings.warn(
f"The name '{self_name}' is already registered for"
f" this blueprint{existing_at}. Use 'name=' to"
" provide a unique name. This will become an error"
" in Flask 2.1.",
stacklevel=4,
)
app.blueprints[name] = self
self._got_registered_once = True self._got_registered_once = True
state = self.make_setup_state(app, options, first_registration) state = self.make_setup_state(app, options, first_registration)
if self.has_static_folder: if self.has_static_folder:
state.add_url_rule( state.add_url_rule(
f"{self.static_url_path}/<path:filename>", self.static_url_path + "/<path:filename>",
view_func=self.send_static_file, view_func=self.send_static_file,
endpoint="static", endpoint="static",
) )
# Merge blueprint data into parent.
if first_registration:
def extend(bp_dict, parent_dict):
for key, values in bp_dict.items():
key = name if key is None else f"{name}.{key}"
parent_dict[key].extend(values)
for key, value in self.error_handler_spec.items():
key = name if key is None else f"{name}.{key}"
value = defaultdict(
dict,
{
code: {
exc_class: func for exc_class, func in code_values.items()
}
for code, code_values in value.items()
},
)
app.error_handler_spec[key] = value
for endpoint, func in self.view_functions.items():
app.view_functions[endpoint] = func
extend(self.before_request_funcs, app.before_request_funcs)
extend(self.after_request_funcs, app.after_request_funcs)
extend(
self.teardown_request_funcs,
app.teardown_request_funcs,
)
extend(self.url_default_functions, app.url_default_functions)
extend(self.url_value_preprocessors, app.url_value_preprocessors)
extend(self.template_context_processors, app.template_context_processors)
for deferred in self.deferred_functions: for deferred in self.deferred_functions:
deferred(state) deferred(state)
cli_resolved_group = options.get("cli_group", self.cli_group) cli_resolved_group = options.get("cli_group", self.cli_group)
if self.cli.commands: if not self.cli.commands:
return
if cli_resolved_group is None: if cli_resolved_group is None:
app.cli.commands.update(self.cli.commands) app.cli.commands.update(self.cli.commands)
elif cli_resolved_group is _sentinel: elif cli_resolved_group is _sentinel:
self.cli.name = name self.cli.name = self.name
app.cli.add_command(self.cli) app.cli.add_command(self.cli)
else: else:
self.cli.name = cli_resolved_group self.cli.name = cli_resolved_group
app.cli.add_command(self.cli) app.cli.add_command(self.cli)
for blueprint, bp_options in self._blueprints: def route(self, rule, **options):
bp_options = bp_options.copy() """Like :meth:`Flask.route` but for a blueprint. The endpoint for the
bp_url_prefix = bp_options.get("url_prefix") :func:`url_for` function is prefixed with the name of the blueprint.
"""
if bp_url_prefix is None: def decorator(f):
bp_url_prefix = blueprint.url_prefix endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
return f
if state.url_prefix is not None and bp_url_prefix is not None: return decorator
bp_options["url_prefix"] = (
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
)
elif bp_url_prefix is not None:
bp_options["url_prefix"] = bp_url_prefix
elif state.url_prefix is not None:
bp_options["url_prefix"] = state.url_prefix
bp_options["name_prefix"] = name def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
blueprint.register(app, bp_options)
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[t.Callable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for """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. the :func:`url_for` function is prefixed with the name of the blueprint.
""" """
if endpoint and "." in endpoint: if endpoint:
raise ValueError("'endpoint' may not contain a dot '.' character.") assert "." not in endpoint, "Blueprint endpoints should not contain dots"
if view_func and hasattr(view_func, "__name__"):
assert (
"." not in view_func.__name__
), "Blueprint view function name should not contain dots"
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: def endpoint(self, endpoint):
raise ValueError("'view_func' name may not contain a dot '.' character.") """Like :meth:`Flask.endpoint` but for a blueprint. This does not
prefix the endpoint with the blueprint name, this has to be done
explicitly by the user of this method. If the endpoint is prefixed
with a `.` it will be registered to the current blueprint, otherwise
it's an application independent endpoint.
"""
self.record( def decorator(f):
lambda s: s.add_url_rule( def register_endpoint(state):
rule, state.app.view_functions[endpoint] = f
endpoint,
view_func,
provide_automatic_options=provide_automatic_options,
**options,
)
)
def app_template_filter( self.record_once(register_endpoint)
self, name: t.Optional[str] = None return f
) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]:
return decorator
def app_template_filter(self, name=None):
"""Register a custom template filter, available application wide. Like """Register a custom template filter, available application wide. Like
:meth:`Flask.template_filter` but for a blueprint. :meth:`Flask.template_filter` but for a blueprint.
@ -434,15 +318,13 @@ class Blueprint(Scaffold):
function name will be used. function name will be used.
""" """
def decorator(f: TemplateFilterCallable) -> TemplateFilterCallable: def decorator(f):
self.add_app_template_filter(f, name=name) self.add_app_template_filter(f, name=name)
return f return f
return decorator return decorator
def add_app_template_filter( def add_app_template_filter(self, f, name=None):
self, f: TemplateFilterCallable, name: t.Optional[str] = None
) -> None:
"""Register a custom template filter, available application wide. Like """Register a custom template filter, available application wide. Like
:meth:`Flask.add_template_filter` but for a blueprint. Works exactly :meth:`Flask.add_template_filter` but for a blueprint. Works exactly
like the :meth:`app_template_filter` decorator. like the :meth:`app_template_filter` decorator.
@ -451,14 +333,12 @@ class Blueprint(Scaffold):
function name will be used. function name will be used.
""" """
def register_template(state: BlueprintSetupState) -> None: def register_template(state):
state.app.jinja_env.filters[name or f.__name__] = f state.app.jinja_env.filters[name or f.__name__] = f
self.record_once(register_template) self.record_once(register_template)
def app_template_test( def app_template_test(self, name=None):
self, name: t.Optional[str] = None
) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]:
"""Register a custom template test, available application wide. Like """Register a custom template test, available application wide. Like
:meth:`Flask.template_test` but for a blueprint. :meth:`Flask.template_test` but for a blueprint.
@ -468,15 +348,13 @@ class Blueprint(Scaffold):
function name will be used. function name will be used.
""" """
def decorator(f: TemplateTestCallable) -> TemplateTestCallable: def decorator(f):
self.add_app_template_test(f, name=name) self.add_app_template_test(f, name=name)
return f return f
return decorator return decorator
def add_app_template_test( def add_app_template_test(self, f, name=None):
self, f: TemplateTestCallable, name: t.Optional[str] = None
) -> None:
"""Register a custom template test, available application wide. Like """Register a custom template test, available application wide. Like
:meth:`Flask.add_template_test` but for a blueprint. Works exactly :meth:`Flask.add_template_test` but for a blueprint. Works exactly
like the :meth:`app_template_test` decorator. like the :meth:`app_template_test` decorator.
@ -487,14 +365,12 @@ class Blueprint(Scaffold):
function name will be used. function name will be used.
""" """
def register_template(state: BlueprintSetupState) -> None: def register_template(state):
state.app.jinja_env.tests[name or f.__name__] = f state.app.jinja_env.tests[name or f.__name__] = f
self.record_once(register_template) self.record_once(register_template)
def app_template_global( def app_template_global(self, name=None):
self, name: t.Optional[str] = None
) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]:
"""Register a custom template global, available application wide. Like """Register a custom template global, available application wide. Like
:meth:`Flask.template_global` but for a blueprint. :meth:`Flask.template_global` but for a blueprint.
@ -504,15 +380,13 @@ class Blueprint(Scaffold):
function name will be used. function name will be used.
""" """
def decorator(f: TemplateGlobalCallable) -> TemplateGlobalCallable: def decorator(f):
self.add_app_template_global(f, name=name) self.add_app_template_global(f, name=name)
return f return f
return decorator return decorator
def add_app_template_global( def add_app_template_global(self, f, name=None):
self, f: TemplateGlobalCallable, name: t.Optional[str] = None
) -> None:
"""Register a custom template global, available application wide. Like """Register a custom template global, available application wide. Like
:meth:`Flask.add_template_global` but for a blueprint. Works exactly :meth:`Flask.add_template_global` but for a blueprint. Works exactly
like the :meth:`app_template_global` decorator. like the :meth:`app_template_global` decorator.
@ -523,12 +397,22 @@ class Blueprint(Scaffold):
function name will be used. function name will be used.
""" """
def register_template(state: BlueprintSetupState) -> None: def register_template(state):
state.app.jinja_env.globals[name or f.__name__] = f state.app.jinja_env.globals[name or f.__name__] = f
self.record_once(register_template) self.record_once(register_template)
def before_app_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable: def before_request(self, f):
"""Like :meth:`Flask.before_request` but for a blueprint. This function
is only executed before each request that is handled by a function of
that blueprint.
"""
self.record_once(
lambda s: s.app.before_request_funcs.setdefault(self.name, []).append(f)
)
return f
def before_app_request(self, f):
"""Like :meth:`Flask.before_request`. Such a function is executed """Like :meth:`Flask.before_request`. Such a function is executed
before each request, even if outside of a blueprint. before each request, even if outside of a blueprint.
""" """
@ -537,16 +421,24 @@ class Blueprint(Scaffold):
) )
return f return f
def before_app_first_request( def before_app_first_request(self, f):
self, f: BeforeFirstRequestCallable
) -> BeforeFirstRequestCallable:
"""Like :meth:`Flask.before_first_request`. Such a function is """Like :meth:`Flask.before_first_request`. Such a function is
executed before the first request to the application. executed before the first request to the application.
""" """
self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
return f return f
def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable: def after_request(self, f):
"""Like :meth:`Flask.after_request` but for a blueprint. This function
is only executed after each request that is handled by a function of
that blueprint.
"""
self.record_once(
lambda s: s.app.after_request_funcs.setdefault(self.name, []).append(f)
)
return f
def after_app_request(self, f):
"""Like :meth:`Flask.after_request` but for a blueprint. Such a function """Like :meth:`Flask.after_request` but for a blueprint. Such a function
is executed after each request, even if outside of the blueprint. is executed after each request, even if outside of the blueprint.
""" """
@ -555,7 +447,19 @@ class Blueprint(Scaffold):
) )
return f return f
def teardown_app_request(self, f: TeardownCallable) -> TeardownCallable: def teardown_request(self, f):
"""Like :meth:`Flask.teardown_request` but for a blueprint. This
function is only executed when tearing down requests handled by a
function of that blueprint. Teardown request functions are executed
when the request context is popped, even when no actual request was
performed.
"""
self.record_once(
lambda s: s.app.teardown_request_funcs.setdefault(self.name, []).append(f)
)
return f
def teardown_app_request(self, f):
"""Like :meth:`Flask.teardown_request` but for a blueprint. Such a """Like :meth:`Flask.teardown_request` but for a blueprint. Such a
function is executed when tearing down each request, even if outside of function is executed when tearing down each request, even if outside of
the blueprint. the blueprint.
@ -565,9 +469,18 @@ class Blueprint(Scaffold):
) )
return f return f
def app_context_processor( def context_processor(self, f):
self, f: TemplateContextProcessorCallable """Like :meth:`Flask.context_processor` but for a blueprint. This
) -> TemplateContextProcessorCallable: function is only executed for requests handled by a blueprint.
"""
self.record_once(
lambda s: s.app.template_context_processors.setdefault(
self.name, []
).append(f)
)
return f
def app_context_processor(self, f):
"""Like :meth:`Flask.context_processor` but for a blueprint. Such a """Like :meth:`Flask.context_processor` but for a blueprint. Such a
function is executed each request, even if outside of the blueprint. function is executed each request, even if outside of the blueprint.
""" """
@ -576,29 +489,81 @@ class Blueprint(Scaffold):
) )
return f return f
def app_errorhandler(self, code: t.Union[t.Type[Exception], int]) -> t.Callable: def app_errorhandler(self, code):
"""Like :meth:`Flask.errorhandler` but for a blueprint. This """Like :meth:`Flask.errorhandler` but for a blueprint. This
handler is used for all requests, even if outside of the blueprint. handler is used for all requests, even if outside of the blueprint.
""" """
def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable: def decorator(f):
self.record_once(lambda s: s.app.errorhandler(code)(f)) self.record_once(lambda s: s.app.errorhandler(code)(f))
return f return f
return decorator return decorator
def app_url_value_preprocessor( def url_value_preprocessor(self, f):
self, f: URLValuePreprocessorCallable """Registers a function as URL value preprocessor for this
) -> URLValuePreprocessorCallable: blueprint. It's called before the view functions are called and
"""Same as :meth:`url_value_preprocessor` but application wide.""" can modify the url values provided.
"""
self.record_once(
lambda s: s.app.url_value_preprocessors.setdefault(self.name, []).append(f)
)
return f
def url_defaults(self, f):
"""Callback function for URL defaults for this blueprint. It's called
with the endpoint and values and should update the values passed
in place.
"""
self.record_once(
lambda s: s.app.url_default_functions.setdefault(self.name, []).append(f)
)
return f
def app_url_value_preprocessor(self, f):
"""Same as :meth:`url_value_preprocessor` but application wide.
"""
self.record_once( self.record_once(
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
) )
return f return f
def app_url_defaults(self, f: URLDefaultCallable) -> URLDefaultCallable: def app_url_defaults(self, f):
"""Same as :meth:`url_defaults` but application wide.""" """Same as :meth:`url_defaults` but application wide.
"""
self.record_once( self.record_once(
lambda s: s.app.url_default_functions.setdefault(None, []).append(f) lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
) )
return f return f
def errorhandler(self, code_or_exception):
"""Registers an error handler that becomes active for this blueprint
only. Please be aware that routing does not happen local to a
blueprint so an error handler for 404 usually is not handled by
a blueprint unless it is caused inside a view function. Another
special case is the 500 internal server error which is always looked
up from the application.
Otherwise works as the :meth:`~flask.Flask.errorhandler` decorator
of the :class:`~flask.Flask` object.
"""
def decorator(f):
self.record_once(
lambda s: s.app._register_error_handler(self.name, code_or_exception, f)
)
return f
return decorator
def register_error_handler(self, code_or_exception, f):
"""Non-decorator version of the :meth:`errorhandler` error attach
function, akin to the :meth:`~flask.Flask.register_error_handler`
application-wide function of the :class:`~flask.Flask` object but
for error handlers limited to this blueprint.
.. versionadded:: 0.11
"""
self.record_once(
lambda s: s.app._register_error_handler(self.name, code_or_exception, f)
)

View file

@ -1,3 +1,15 @@
# -*- coding: utf-8 -*-
"""
flask.cli
~~~~~~~~~
A simple command line application to run flask apps.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from __future__ import print_function
import ast import ast
import inspect import inspect
import os import os
@ -5,7 +17,6 @@ import platform
import re import re
import sys import sys
import traceback import traceback
import warnings
from functools import update_wrapper from functools import update_wrapper
from operator import attrgetter from operator import attrgetter
from threading import Lock from threading import Lock
@ -14,6 +25,10 @@ from threading import Thread
import click import click
from werkzeug.utils import import_string from werkzeug.utils import import_string
from ._compat import getargspec
from ._compat import itervalues
from ._compat import reraise
from ._compat import text_type
from .globals import current_app from .globals import current_app
from .helpers import get_debug_flag from .helpers import get_debug_flag
from .helpers import get_env from .helpers import get_env
@ -27,7 +42,7 @@ except ImportError:
try: try:
import ssl import ssl
except ImportError: except ImportError:
ssl = None # type: ignore ssl = None
class NoAppException(click.UsageError): class NoAppException(click.UsageError):
@ -48,15 +63,15 @@ def find_best_app(script_info, module):
return app return app
# Otherwise find the only object that is a Flask instance. # Otherwise find the only object that is a Flask instance.
matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] matches = [v for v in itervalues(module.__dict__) if isinstance(v, Flask)]
if len(matches) == 1: if len(matches) == 1:
return matches[0] return matches[0]
elif len(matches) > 1: elif len(matches) > 1:
raise NoAppException( raise NoAppException(
"Detected multiple Flask applications in module" 'Detected multiple Flask applications in module "{module}". Use '
f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" '"FLASK_APP={module}:name" to specify the correct '
f" to specify the correct one." "one.".format(module=module.__name__)
) )
# Search for app factory functions. # Search for app factory functions.
@ -73,140 +88,109 @@ def find_best_app(script_info, module):
if not _called_with_wrong_args(app_factory): if not _called_with_wrong_args(app_factory):
raise raise
raise NoAppException( raise NoAppException(
f"Detected factory {attr_name!r} in module {module.__name__!r}," 'Detected factory "{factory}" in module "{module}", but '
" but could not call it without arguments. Use" "could not call it without arguments. Use "
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\"" "\"FLASK_APP='{module}:{factory}(args)'\" to specify "
" to specify arguments." "arguments.".format(factory=attr_name, module=module.__name__)
) )
raise NoAppException( raise NoAppException(
"Failed to find Flask application or factory in module" 'Failed to find Flask application or factory in module "{module}". '
f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" 'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__)
" to specify one."
) )
def call_factory(script_info, app_factory, args=None, kwargs=None): def call_factory(script_info, app_factory, arguments=()):
"""Takes an app factory, a ``script_info` object and optionally a tuple """Takes an app factory, a ``script_info` object and optionally a tuple
of arguments. Checks for the existence of a script_info argument and calls of arguments. Checks for the existence of a script_info argument and calls
the app_factory depending on that and the arguments provided. the app_factory depending on that and the arguments provided.
""" """
sig = inspect.signature(app_factory) args_spec = getargspec(app_factory)
args = [] if args is None else args arg_names = args_spec.args
kwargs = {} if kwargs is None else kwargs arg_defaults = args_spec.defaults
if "script_info" in sig.parameters: if "script_info" in arg_names:
warnings.warn( return app_factory(*arguments, script_info=script_info)
"The 'script_info' argument is deprecated and will not be" elif arguments:
" passed to the app factory function in Flask 2.1.", return app_factory(*arguments)
DeprecationWarning, elif not arguments and len(arg_names) == 1 and arg_defaults is None:
) return app_factory(script_info)
kwargs["script_info"] = script_info
if ( return app_factory()
not args
and len(sig.parameters) == 1
and next(iter(sig.parameters.values())).default is inspect.Parameter.empty
):
warnings.warn(
"Script info is deprecated and will not be passed as the"
" single argument to the app factory function in Flask"
" 2.1.",
DeprecationWarning,
)
args.append(script_info)
return app_factory(*args, **kwargs)
def _called_with_wrong_args(f): def _called_with_wrong_args(factory):
"""Check whether calling a function raised a ``TypeError`` because """Check whether calling a function raised a ``TypeError`` because
the call failed or because something in the factory raised the the call failed or because something in the factory raised the
error. error.
:param f: The function that was called. :param factory: the factory function that was called
:return: ``True`` if the call failed. :return: true if the call failed
""" """
tb = sys.exc_info()[2] tb = sys.exc_info()[2]
try: try:
while tb is not None: while tb is not None:
if tb.tb_frame.f_code is f.__code__: if tb.tb_frame.f_code is factory.__code__:
# In the function, it was called successfully. # in the factory, it was called successfully
return False return False
tb = tb.tb_next tb = tb.tb_next
# Didn't reach the function. # didn't reach the factory
return True return True
finally: finally:
# Delete tb to break a circular reference. # explicitly delete tb as it is circular referenced
# https://docs.python.org/2/library/sys.html#sys.exc_info # https://docs.python.org/2/library/sys.html#sys.exc_info
del tb del tb
def find_app_by_string(script_info, module, app_name): def find_app_by_string(script_info, module, app_name):
"""Check if the given string is a variable name or a function. Call """Checks if the given string is a variable name or a function. If it is a
a function to get the app instance, or return the variable directly. function, it checks for specified arguments and whether it takes a
``script_info`` argument and calls the function with the appropriate
arguments.
""" """
from . import Flask from . import Flask
# Parse app_name as a single expression to determine if it's a valid match = re.match(r"^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$", app_name)
# attribute name or function call.
try: if not match:
expr = ast.parse(app_name.strip(), mode="eval").body
except SyntaxError:
raise NoAppException( raise NoAppException(
f"Failed to parse {app_name!r} as an attribute name or function call." '"{name}" is not a valid variable name or function '
"expression.".format(name=app_name)
) )
if isinstance(expr, ast.Name): name, args = match.groups()
name = expr.id
args = kwargs = None
elif isinstance(expr, ast.Call):
# Ensure the function name is an attribute name only.
if not isinstance(expr.func, ast.Name):
raise NoAppException(
f"Function reference must be a simple name: {app_name!r}."
)
name = expr.func.id
# Parse the positional and keyword arguments as literals.
try:
args = [ast.literal_eval(arg) for arg in expr.args]
kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
except ValueError:
# literal_eval gives cryptic error messages, show a generic
# message with the full expression instead.
raise NoAppException(
f"Failed to parse arguments as literal values: {app_name!r}."
)
else:
raise NoAppException(
f"Failed to parse {app_name!r} as an attribute name or function call."
)
try: try:
attr = getattr(module, name) attr = getattr(module, name)
except AttributeError: except AttributeError as e:
raise NoAppException( raise NoAppException(e.args[0])
f"Failed to find attribute {name!r} in {module.__name__!r}."
)
# If the attribute is a function, call it with any args and kwargs
# to get the real application.
if inspect.isfunction(attr): if inspect.isfunction(attr):
if args:
try: try:
app = call_factory(script_info, attr, args, kwargs) args = ast.literal_eval("({args},)".format(args=args))
except TypeError: except (ValueError, SyntaxError) as e:
raise NoAppException(
"Could not parse the arguments in "
'"{app_name}".'.format(e=e, app_name=app_name)
)
else:
args = ()
try:
app = call_factory(script_info, attr, args)
except TypeError as e:
if not _called_with_wrong_args(attr): if not _called_with_wrong_args(attr):
raise raise
raise NoAppException( raise NoAppException(
f"The factory {app_name!r} in module" '{e}\nThe factory "{app_name}" in module "{module}" could not '
f" {module.__name__!r} could not be called with the" "be called with the specified arguments.".format(
" specified arguments." e=e, app_name=app_name, module=module.__name__
)
) )
else: else:
app = attr app = attr
@ -215,8 +199,8 @@ def find_app_by_string(script_info, module, app_name):
return app return app
raise NoAppException( raise NoAppException(
"A valid Flask application was not obtained from" "A valid Flask application was not obtained from "
f" '{module.__name__}:{app_name}'." '"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name)
) )
@ -257,13 +241,13 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
except ImportError: except ImportError:
# Reraise the ImportError if it occurred within the imported module. # Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1. # Determine this by checking whether the trace has a depth > 1.
if sys.exc_info()[2].tb_next: if sys.exc_info()[-1].tb_next:
raise NoAppException( raise NoAppException(
f"While importing {module_name!r}, an ImportError was" 'While importing "{name}", an ImportError was raised:'
f" raised:\n\n{traceback.format_exc()}" "\n\n{tb}".format(name=module_name, tb=traceback.format_exc())
) )
elif raise_if_not_found: elif raise_if_not_found:
raise NoAppException(f"Could not import {module_name!r}.") raise NoAppException('Could not import "{name}".'.format(name=module_name))
else: else:
return return
@ -282,10 +266,14 @@ def get_version(ctx, param, value):
import werkzeug import werkzeug
from . import __version__ from . import __version__
message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s"
click.echo( click.echo(
f"Python {platform.python_version()}\n" message
f"Flask {__version__}\n" % {
f"Werkzeug {werkzeug.__version__}", "python": platform.python_version(),
"flask": __version__,
"werkzeug": werkzeug.__version__,
},
color=ctx.color, color=ctx.color,
) )
ctx.exit() ctx.exit()
@ -301,22 +289,18 @@ version_option = click.Option(
) )
class DispatchingApp: 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 is imported by name in a background thread. If an error happens
it is recorded and shown 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. of the Werkzeug debugger means that it shows up in the browser.
""" """
def __init__(self, loader, use_eager_loading=None): def __init__(self, loader, use_eager_loading=False):
self.loader = loader self.loader = loader
self._app = None self._app = None
self._lock = Lock() self._lock = Lock()
self._bg_loading_exc_info = None self._bg_loading_exc_info = None
if use_eager_loading is None:
use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
if use_eager_loading: if use_eager_loading:
self._load_unlocked() self._load_unlocked()
else: else:
@ -339,7 +323,7 @@ class DispatchingApp:
exc_info = self._bg_loading_exc_info exc_info = self._bg_loading_exc_info
if exc_info is not None: if exc_info is not None:
self._bg_loading_exc_info = None self._bg_loading_exc_info = None
raise exc_info reraise(*exc_info)
def _load_unlocked(self): def _load_unlocked(self):
__traceback_hide__ = True # noqa: F841 __traceback_hide__ = True # noqa: F841
@ -360,7 +344,7 @@ class DispatchingApp:
return rv(environ, start_response) return rv(environ, start_response)
class ScriptInfo: class ScriptInfo(object):
"""Helper object to deal with Flask applications. This is usually not """Helper object to deal with Flask applications. This is usually not
necessary to interface with as it's used internally in the dispatching necessary to interface with as it's used internally in the dispatching
to click. In future versions of Flask this object will most likely play to click. In future versions of Flask this object will most likely play
@ -391,6 +375,8 @@ class ScriptInfo:
if self._loaded_app is not None: if self._loaded_app is not None:
return self._loaded_app return self._loaded_app
app = None
if self.create_app is not None: if self.create_app is not None:
app = call_factory(self, self.create_app) app = call_factory(self, self.create_app)
else: else:
@ -478,7 +464,9 @@ class FlaskGroup(AppGroup):
loading more commands from the configured Flask app. Normally a loading more commands from the configured Flask app. Normally a
developer does not have to interface with this class but there are developer does not have to interface with this class but there are
some very advanced use cases for which it makes sense to create an some very advanced use cases for which it makes sense to create an
instance of this. see :ref:`custom-scripts`. instance of this.
For information as of why this is useful see :ref:`custom-scripts`.
:param add_default_commands: if this is True then the default run and :param add_default_commands: if this is True then the default run and
shell commands will be added. shell commands will be added.
@ -503,7 +491,7 @@ class FlaskGroup(AppGroup):
add_version_option=True, add_version_option=True,
load_dotenv=True, load_dotenv=True,
set_debug_flag=True, set_debug_flag=True,
**extra, **extra
): ):
params = list(extra.pop("params", None) or ()) params = list(extra.pop("params", None) or ())
@ -537,41 +525,43 @@ class FlaskGroup(AppGroup):
def get_command(self, ctx, name): def get_command(self, ctx, name):
self._load_plugin_commands() self._load_plugin_commands()
# Look up built-in and plugin commands, which should be
# available even if the app fails to load.
rv = super().get_command(ctx, name)
# 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
# and not attach the default commands.
#
# This also means that the script stays functional in case the
# application completely fails.
rv = AppGroup.get_command(self, ctx, name)
if rv is not None: if rv is not None:
return rv return rv
info = ctx.ensure_object(ScriptInfo) info = ctx.ensure_object(ScriptInfo)
# Look up commands provided by the app, showing an error and
# continuing if the app couldn't be loaded.
try: try:
return info.load_app().cli.get_command(ctx, name) rv = info.load_app().cli.get_command(ctx, name)
except NoAppException as e: if rv is not None:
click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") return rv
except NoAppException:
pass
def list_commands(self, ctx): def list_commands(self, ctx):
self._load_plugin_commands() self._load_plugin_commands()
# Start with the built-in and plugin commands.
rv = set(super().list_commands(ctx))
info = ctx.ensure_object(ScriptInfo)
# Add commands provided by the app, showing an error and # The commands available is the list of both the application (if
# continuing if the app couldn't be loaded. # available) plus the builtin commands.
rv = set(click.Group.list_commands(self, ctx))
info = ctx.ensure_object(ScriptInfo)
try: try:
rv.update(info.load_app().cli.list_commands(ctx)) rv.update(info.load_app().cli.list_commands(ctx))
except NoAppException as e:
# When an app couldn't be loaded, show the error message
# without the traceback.
click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
except Exception: except Exception:
# When any other errors occurred during loading, show the # Here we intentionally swallow all exceptions as we don't
# full traceback. # want the help page to break if the app does not exist.
click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") # If someone attempts to use the command we try to create
# the app again and this will give us the error.
# However, we will not do so silently because that would confuse
# users.
traceback.print_exc()
return sorted(rv) return sorted(rv)
def main(self, *args, **kwargs): def main(self, *args, **kwargs):
@ -593,7 +583,7 @@ class FlaskGroup(AppGroup):
kwargs["obj"] = obj kwargs["obj"] = obj
kwargs.setdefault("auto_envvar_prefix", "FLASK") kwargs.setdefault("auto_envvar_prefix", "FLASK")
return super().main(*args, **kwargs) return super(FlaskGroup, self).main(*args, **kwargs)
def _path_is_ancestor(path, other): def _path_is_ancestor(path, other):
@ -609,6 +599,10 @@ def load_dotenv(path=None):
If an env var is already set it is not overwritten, so earlier files in the If an env var is already set it is not overwritten, so earlier files in the
list are preferred over later files. list are preferred over later files.
Changes the current working directory to the location of the first file
found, with the assumption that it is in the top level project directory
and will be where the Python path should import local packages from.
This is a no-op if `python-dotenv`_ is not installed. This is a no-op if `python-dotenv`_ is not installed.
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
@ -620,9 +614,6 @@ def load_dotenv(path=None):
Returns ``False`` when python-dotenv is not installed, or when Returns ``False`` when python-dotenv is not installed, or when
the given path isn't a file. the given path isn't a file.
.. versionchanged:: 2.0
When loading the env files, set the default encoding to UTF-8.
.. versionadded:: 1.0 .. versionadded:: 1.0
""" """
if dotenv is None: if dotenv is None:
@ -640,7 +631,7 @@ def load_dotenv(path=None):
# else False # else False
if path is not None: if path is not None:
if os.path.isfile(path): if os.path.isfile(path):
return dotenv.load_dotenv(path, encoding="utf-8") return dotenv.load_dotenv(path)
return False return False
@ -655,7 +646,10 @@ def load_dotenv(path=None):
if new_dir is None: if new_dir is None:
new_dir = os.path.dirname(path) new_dir = os.path.dirname(path)
dotenv.load_dotenv(path, encoding="utf-8") dotenv.load_dotenv(path)
if new_dir and os.getcwd() != new_dir:
os.chdir(new_dir)
return new_dir is not None # at least one file was located and loaded return new_dir is not None # at least one file was located and loaded
@ -668,25 +662,25 @@ def show_server_banner(env, debug, app_import_path, eager_loading):
return return
if app_import_path is not None: if app_import_path is not None:
message = f" * Serving Flask app {app_import_path!r}" message = ' * Serving Flask app "{0}"'.format(app_import_path)
if not eager_loading: if not eager_loading:
message += " (lazy loading)" message += " (lazy loading)"
click.echo(message) click.echo(message)
click.echo(f" * Environment: {env}") click.echo(" * Environment: {0}".format(env))
if env == "production": if env == "production":
click.secho( click.secho(
" WARNING: This is a development server. Do not use it in" " WARNING: This is a development server. "
" a production deployment.", "Do not use it in a production deployment.",
fg="red", fg="red",
) )
click.secho(" Use a production WSGI server instead.", dim=True) click.secho(" Use a production WSGI server instead.", dim=True)
if debug is not None: if debug is not None:
click.echo(f" * Debug mode: {'on' if debug else 'off'}") click.echo(" * Debug mode: {0}".format("on" if debug else "off"))
class CertParamType(click.ParamType): class CertParamType(click.ParamType):
@ -715,18 +709,20 @@ class CertParamType(click.ParamType):
if value == "adhoc": if value == "adhoc":
try: try:
import cryptography # noqa: F401 import OpenSSL # noqa: F401
except ImportError: except ImportError:
raise click.BadParameter( raise click.BadParameter(
"Using ad-hoc certificates requires the cryptography library.", "Using ad-hoc certificates requires pyOpenSSL.", ctx, param
ctx,
param,
) )
return value return value
obj = import_string(value, silent=True) obj = import_string(value, silent=True)
if sys.version_info < (2, 7, 9):
if obj:
return obj
else:
if isinstance(obj, ssl.SSLContext): if isinstance(obj, ssl.SSLContext):
return obj return obj
@ -739,7 +735,11 @@ def _validate_key(ctx, param, value):
""" """
cert = ctx.params.get("cert") cert = ctx.params.get("cert")
is_adhoc = cert == "adhoc" is_adhoc = cert == "adhoc"
is_context = ssl and isinstance(cert, ssl.SSLContext)
if sys.version_info < (2, 7, 9):
is_context = cert and not isinstance(cert, (text_type, bytes))
else:
is_context = isinstance(cert, ssl.SSLContext)
if value is not None: if value is not None:
if is_adhoc: if is_adhoc:
@ -772,7 +772,7 @@ class SeparatedPathType(click.Path):
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
items = self.split_envvar_value(value) items = self.split_envvar_value(value)
super_convert = super().convert super_convert = super(SeparatedPathType, self).convert
return [super_convert(item, param, ctx) for item in items] return [super_convert(item, param, ctx) for item in items]
@ -802,7 +802,7 @@ class SeparatedPathType(click.Path):
"is active if debug is enabled.", "is active if debug is enabled.",
) )
@click.option( @click.option(
"--eager-loading/--lazy-loading", "--eager-loading/--lazy-loader",
default=None, default=None,
help="Enable or disable eager loading. By default eager " help="Enable or disable eager loading. By default eager "
"loading is enabled if the reloader is disabled.", "loading is enabled if the reloader is disabled.",
@ -818,7 +818,7 @@ class SeparatedPathType(click.Path):
type=SeparatedPathType(), type=SeparatedPathType(),
help=( help=(
"Extra files that trigger a reload on change. Multiple paths" "Extra files that trigger a reload on change. Multiple paths"
f" are separated by {os.path.pathsep!r}." " are separated by '{}'.".format(os.path.pathsep)
), ),
) )
@pass_script_info @pass_script_info
@ -841,6 +841,9 @@ def run_command(
if debugger is None: if debugger is None:
debugger = debug debugger = debug
if eager_loading is None:
eager_loading = not reload
show_server_banner(get_env(), debug, info.app_import_path, eager_loading) show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
@ -860,10 +863,10 @@ def run_command(
@click.command("shell", short_help="Run a shell in the app context.") @click.command("shell", short_help="Run a shell in the app context.")
@with_appcontext @with_appcontext
def shell_command() -> None: def shell_command():
"""Run an interactive Python shell in the context of a given """Run an interactive Python shell in the context of a given
Flask application. The application will populate the default Flask application. The application will populate the default
namespace of this shell according to its configuration. namespace of this shell according to it's configuration.
This is useful for executing small snippets of management code This is useful for executing small snippets of management code
without having to manually configure the application. without having to manually configure the application.
@ -872,40 +875,24 @@ def shell_command() -> None:
from .globals import _app_ctx_stack from .globals import _app_ctx_stack
app = _app_ctx_stack.top.app app = _app_ctx_stack.top.app
banner = ( banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % (
f"Python {sys.version} on {sys.platform}\n" sys.version,
f"App: {app.import_name} [{app.env}]\n" sys.platform,
f"Instance: {app.instance_path}" app.import_name,
app.env,
app.instance_path,
) )
ctx: dict = {} ctx = {}
# Support the regular Python interpreter startup script if someone # Support the regular Python interpreter startup script if someone
# is using it. # is using it.
startup = os.environ.get("PYTHONSTARTUP") startup = os.environ.get("PYTHONSTARTUP")
if startup and os.path.isfile(startup): if startup and os.path.isfile(startup):
with open(startup) as f: with open(startup, "r") as f:
eval(compile(f.read(), startup, "exec"), ctx) eval(compile(f.read(), startup, "exec"), ctx)
ctx.update(app.make_shell_context()) ctx.update(app.make_shell_context())
# Site, customize, or startup script can set a hook to call when
# entering interactive mode. The default one sets up readline with
# tab and history completion.
interactive_hook = getattr(sys, "__interactivehook__", None)
if interactive_hook is not None:
try:
import readline
from rlcompleter import Completer
except ImportError:
pass
else:
# rlcompleter uses __main__.__dict__ by default, which is
# flask.__main__. Use the shell context instead.
readline.set_completer(Completer(ctx).complete)
interactive_hook()
code.interact(banner=banner, local=ctx) code.interact(banner=banner, local=ctx)
@ -922,7 +909,7 @@ def shell_command() -> None:
) )
@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") @click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
@with_appcontext @with_appcontext
def routes_command(sort: str, all_methods: bool) -> None: def routes_command(sort, all_methods):
"""Show all registered routes with endpoints and methods.""" """Show all registered routes with endpoints and methods."""
rules = list(current_app.url_map.iter_rules()) rules = list(current_app.url_map.iter_rules())
@ -935,12 +922,9 @@ def routes_command(sort: str, all_methods: bool) -> None:
if sort in ("endpoint", "rule"): if sort in ("endpoint", "rule"):
rules = sorted(rules, key=attrgetter(sort)) rules = sorted(rules, key=attrgetter(sort))
elif sort == "methods": elif sort == "methods":
rules = sorted(rules, key=lambda rule: sorted(rule.methods)) # type: ignore rules = sorted(rules, key=lambda rule: sorted(rule.methods))
rule_methods = [ rule_methods = [", ".join(sorted(rule.methods - ignored_methods)) for rule in rules]
", ".join(sorted(rule.methods - ignored_methods)) # type: ignore
for rule in rules
]
headers = ("Endpoint", "Methods", "Rule") headers = ("Endpoint", "Methods", "Rule")
widths = ( widths = (
@ -978,17 +962,10 @@ debug mode.
) )
def main() -> None: def main(as_module=False):
if int(click.__version__[0]) < 8:
warnings.warn(
"Using the `flask` cli with Click 7 is deprecated and"
" will not be supported starting with Flask 2.1."
" Please upgrade to Click 8 as soon as possible.",
DeprecationWarning,
)
# TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
cli.main(args=sys.argv[1:]) cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
if __name__ == "__main__": if __name__ == "__main__":
main() main(as_module=True)

View file

@ -1,19 +1,32 @@
# -*- coding: utf-8 -*-
"""
flask.config
~~~~~~~~~~~~
Implements the configuration related objects.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import errno import errno
import os import os
import types import types
import typing as t
from werkzeug.utils import import_string from werkzeug.utils import import_string
from . import json
from ._compat import iteritems
from ._compat import string_types
class ConfigAttribute:
class ConfigAttribute(object):
"""Makes an attribute forward to the config""" """Makes an attribute forward to the config"""
def __init__(self, name: str, get_converter: t.Optional[t.Callable] = None) -> None: def __init__(self, name, get_converter=None):
self.__name__ = name self.__name__ = name
self.get_converter = get_converter self.get_converter = get_converter
def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any: def __get__(self, obj, type=None):
if obj is None: if obj is None:
return self return self
rv = obj.config[self.__name__] rv = obj.config[self.__name__]
@ -21,7 +34,7 @@ class ConfigAttribute:
rv = self.get_converter(rv) rv = self.get_converter(rv)
return rv return rv
def __set__(self, obj: t.Any, value: t.Any) -> None: def __set__(self, obj, value):
obj.config[self.__name__] = value obj.config[self.__name__] = value
@ -69,11 +82,11 @@ class Config(dict):
:param defaults: an optional dictionary of default values :param defaults: an optional dictionary of default values
""" """
def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None: def __init__(self, root_path, defaults=None):
dict.__init__(self, defaults or {}) dict.__init__(self, defaults or {})
self.root_path = root_path self.root_path = root_path
def from_envvar(self, variable_name: str, silent: bool = False) -> bool: def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to """Loads a configuration from an environment variable pointing to
a configuration file. This is basically just a shortcut with nicer a configuration file. This is basically just a shortcut with nicer
error messages for this line of code:: error messages for this line of code::
@ -90,14 +103,14 @@ class Config(dict):
if silent: if silent:
return False return False
raise RuntimeError( raise RuntimeError(
f"The environment variable {variable_name!r} is not set" "The environment variable %r is not set "
" and as such configuration could not be loaded. Set" "and as such configuration could not be "
" this variable and make it point to a configuration" "loaded. Set this variable and make it "
" file" "point to a configuration file" % variable_name
) )
return self.from_pyfile(rv, silent=silent) return self.from_pyfile(rv, silent=silent)
def from_pyfile(self, filename: str, silent: bool = False) -> bool: def from_pyfile(self, filename, silent=False):
"""Updates the values in the config from a Python file. This function """Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the behaves as if the file was imported as module with the
:meth:`from_object` function. :meth:`from_object` function.
@ -117,15 +130,15 @@ class Config(dict):
try: try:
with open(filename, mode="rb") as config_file: with open(filename, mode="rb") as config_file:
exec(compile(config_file.read(), filename, "exec"), d.__dict__) exec(compile(config_file.read(), filename, "exec"), d.__dict__)
except OSError as e: except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
return False return False
e.strerror = f"Unable to load configuration file ({e.strerror})" e.strerror = "Unable to load configuration file (%s)" % e.strerror
raise raise
self.from_object(d) self.from_object(d)
return True return True
def from_object(self, obj: t.Union[object, str]) -> None: def from_object(self, obj):
"""Updates the values from the given object. An object can be of one """Updates the values from the given object. An object can be of one
of the following two types: of the following two types:
@ -157,96 +170,61 @@ class Config(dict):
:param obj: an import name or object :param obj: an import name or object
""" """
if isinstance(obj, str): if isinstance(obj, string_types):
obj = import_string(obj) obj = import_string(obj)
for key in dir(obj): for key in dir(obj):
if key.isupper(): if key.isupper():
self[key] = getattr(obj, key) self[key] = getattr(obj, key)
def from_file( def from_json(self, filename, silent=False):
self, """Updates the values in the config from a JSON file. This function
filename: str, behaves as if the JSON object was a dictionary and passed to the
load: t.Callable[[t.IO[t.Any]], t.Mapping], :meth:`from_mapping` function.
silent: bool = False,
) -> bool:
"""Update the values in the config from a file that is loaded
using the ``load`` parameter. The loaded data is passed to the
:meth:`from_mapping` method.
.. code-block:: python :param filename: the filename of the JSON file. This can either be an
absolute filename or a filename relative to the
root path.
:param silent: set to ``True`` if you want silent failure for missing
files.
import toml .. versionadded:: 0.11
app.config.from_file("config.toml", load=toml.load)
:param filename: The path to the data file. This can be an
absolute path or relative to the config root path.
:param load: A callable that takes a file handle and returns a
mapping of loaded data from the file.
:type load: ``Callable[[Reader], Mapping]`` where ``Reader``
implements a ``read`` method.
:param silent: Ignore the file if it doesn't exist.
.. versionadded:: 2.0
""" """
filename = os.path.join(self.root_path, filename) filename = os.path.join(self.root_path, filename)
try: try:
with open(filename) as f: with open(filename) as json_file:
obj = load(f) obj = json.loads(json_file.read())
except OSError as e: except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR): if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False return False
e.strerror = "Unable to load configuration file (%s)" % e.strerror
e.strerror = f"Unable to load configuration file ({e.strerror})"
raise raise
return self.from_mapping(obj) return self.from_mapping(obj)
def from_json(self, filename: str, silent: bool = False) -> bool: def from_mapping(self, *mapping, **kwargs):
"""Update the values in the config from a JSON file. The loaded
data is passed to the :meth:`from_mapping` method.
:param filename: The path to the JSON file. This can be an
absolute path or relative to the config root path.
:param silent: Ignore the file if it doesn't exist.
.. deprecated:: 2.0.0
Will be removed in Flask 2.1. Use :meth:`from_file` instead.
This was removed early in 2.0.0, was added back in 2.0.1.
.. versionadded:: 0.11
"""
import warnings
from . import json
warnings.warn(
"'from_json' is deprecated and will be removed in Flask"
" 2.1. Use 'from_file(path, json.load)' instead.",
DeprecationWarning,
stacklevel=2,
)
return self.from_file(filename, json.load, silent=silent)
def from_mapping(
self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any
) -> bool:
"""Updates the config like :meth:`update` ignoring items with non-upper """Updates the config like :meth:`update` ignoring items with non-upper
keys. keys.
.. versionadded:: 0.11 .. versionadded:: 0.11
""" """
mappings: t.Dict[str, t.Any] = {} mappings = []
if mapping is not None: if len(mapping) == 1:
mappings.update(mapping) if hasattr(mapping[0], "items"):
mappings.update(kwargs) mappings.append(mapping[0].items())
for key, value in mappings.items(): else:
mappings.append(mapping[0])
elif len(mapping) > 1:
raise TypeError(
"expected at most 1 positional argument, got %d" % len(mapping)
)
mappings.append(kwargs.items())
for mapping in mappings:
for (key, value) in mapping:
if key.isupper(): if key.isupper():
self[key] = value self[key] = value
return True return True
def get_namespace( def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
self, namespace: str, lowercase: bool = True, trim_namespace: bool = True
) -> t.Dict[str, t.Any]:
"""Returns a dictionary containing a subset of configuration options """Returns a dictionary containing a subset of configuration options
that match the specified namespace/prefix. Example usage:: that match the specified namespace/prefix. Example usage::
@ -275,7 +253,7 @@ class Config(dict):
.. versionadded:: 0.11 .. versionadded:: 0.11
""" """
rv = {} rv = {}
for k, v in self.items(): for k, v in iteritems(self):
if not k.startswith(namespace): if not k.startswith(namespace):
continue continue
if trim_namespace: if trim_namespace:
@ -287,5 +265,5 @@ class Config(dict):
rv[key] = v rv[key] = v
return rv return rv
def __repr__(self) -> str: def __repr__(self):
return f"<{type(self).__name__} {dict.__repr__(self)}>" return "<%s %s>" % (self.__class__.__name__, dict.__repr__(self))

View file

@ -1,27 +1,31 @@
# -*- coding: utf-8 -*-
"""
flask.ctx
~~~~~~~~~
Implements the objects required to keep the context.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import sys import sys
import typing as t
from functools import update_wrapper from functools import update_wrapper
from types import TracebackType
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from ._compat import BROKEN_PYPY_CTXMGR_EXIT
from ._compat import reraise
from .globals import _app_ctx_stack from .globals import _app_ctx_stack
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
from .signals import appcontext_popped from .signals import appcontext_popped
from .signals import appcontext_pushed from .signals import appcontext_pushed
from .typing import AfterRequestCallable
if t.TYPE_CHECKING:
from .app import Flask
from .sessions import SessionMixin
from .wrappers import Request
# a singleton sentinel value for parameter defaults # a singleton sentinel value for parameter defaults
_sentinel = object() _sentinel = object()
class _AppCtxGlobals: class _AppCtxGlobals(object):
"""A plain object. Used as a namespace for storing data during an """A plain object. Used as a namespace for storing data during an
application context. application context.
@ -41,25 +45,7 @@ class _AppCtxGlobals:
.. versionadded:: 0.10 .. versionadded:: 0.10
""" """
# Define attr methods to let mypy know this is a namespace object def get(self, name, default=None):
# that has arbitrary attributes.
def __getattr__(self, name: str) -> t.Any:
try:
return self.__dict__[name]
except KeyError:
raise AttributeError(name) from None
def __setattr__(self, name: str, value: t.Any) -> None:
self.__dict__[name] = value
def __delattr__(self, name: str) -> None:
try:
del self.__dict__[name]
except KeyError:
raise AttributeError(name) from None
def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any:
"""Get an attribute by name, or a default value. Like """Get an attribute by name, or a default value. Like
:meth:`dict.get`. :meth:`dict.get`.
@ -70,12 +56,12 @@ class _AppCtxGlobals:
""" """
return self.__dict__.get(name, default) return self.__dict__.get(name, default)
def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: def pop(self, name, default=_sentinel):
"""Get and remove an attribute by name. Like :meth:`dict.pop`. """Get and remove an attribute by name. Like :meth:`dict.pop`.
:param name: Name of attribute to pop. :param name: Name of attribute to pop.
:param default: Value to return if the attribute is not present, :param default: Value to return if the attribute is not present,
instead of raising a ``KeyError``. instead of raise a ``KeyError``.
.. versionadded:: 0.11 .. versionadded:: 0.11
""" """
@ -84,32 +70,32 @@ class _AppCtxGlobals:
else: else:
return self.__dict__.pop(name, default) return self.__dict__.pop(name, default)
def setdefault(self, name: str, default: t.Any = None) -> t.Any: def setdefault(self, name, default=None):
"""Get the value of an attribute if it is present, otherwise """Get the value of an attribute if it is present, otherwise
set and return a default value. Like :meth:`dict.setdefault`. set and return a default value. Like :meth:`dict.setdefault`.
:param name: Name of attribute to get. :param name: Name of attribute to get.
:param default: Value to set and return if the attribute is not :param: default: Value to set and return if the attribute is not
present. present.
.. versionadded:: 0.11 .. versionadded:: 0.11
""" """
return self.__dict__.setdefault(name, default) return self.__dict__.setdefault(name, default)
def __contains__(self, item: str) -> bool: def __contains__(self, item):
return item in self.__dict__ return item in self.__dict__
def __iter__(self) -> t.Iterator[str]: def __iter__(self):
return iter(self.__dict__) return iter(self.__dict__)
def __repr__(self) -> str: def __repr__(self):
top = _app_ctx_stack.top top = _app_ctx_stack.top
if top is not None: if top is not None:
return f"<flask.g of {top.app.name!r}>" return "<flask.g of %r>" % top.app.name
return object.__repr__(self) return object.__repr__(self)
def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable: def after_this_request(f):
"""Executes a function after this request. This is useful to modify """Executes a function after this request. This is useful to modify
response objects. The function is passed the response object and has response objects. The function is passed the response object and has
to return the same or a new one. to return the same or a new one.
@ -134,7 +120,7 @@ def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable:
return f return f
def copy_current_request_context(f: t.Callable) -> t.Callable: def copy_current_request_context(f):
"""A helper function that decorates a function to retain the current """A helper function that decorates a function to retain the current
request context. This is useful when working with greenlets. The moment request context. This is useful when working with greenlets. The moment
the function is decorated a copy of the request context is created and the function is decorated a copy of the request context is created and
@ -174,7 +160,7 @@ def copy_current_request_context(f: t.Callable) -> t.Callable:
return update_wrapper(wrapper, f) return update_wrapper(wrapper, f)
def has_request_context() -> bool: def has_request_context():
"""If you have code that wants to test if a request context is there or """If you have code that wants to test if a request context is there or
not this function can be used. For instance, you may want to take advantage not this function can be used. For instance, you may want to take advantage
of request information if the request object is available, but fail of request information if the request object is available, but fail
@ -206,7 +192,7 @@ def has_request_context() -> bool:
return _request_ctx_stack.top is not None return _request_ctx_stack.top is not None
def has_app_context() -> bool: def has_app_context():
"""Works like :func:`has_request_context` but for the application """Works like :func:`has_request_context` but for the application
context. You can also just do a boolean check on the context. You can also just do a boolean check on the
:data:`current_app` object instead. :data:`current_app` object instead.
@ -216,7 +202,7 @@ def has_app_context() -> bool:
return _app_ctx_stack.top is not None return _app_ctx_stack.top is not None
class AppContext: class AppContext(object):
"""The application context binds an application object implicitly """The application context binds an application object implicitly
to the current thread or greenlet, similar to how the to the current thread or greenlet, similar to how the
:class:`RequestContext` binds request information. The application :class:`RequestContext` binds request information. The application
@ -225,7 +211,7 @@ class AppContext:
context. context.
""" """
def __init__(self, app: "Flask") -> None: def __init__(self, app):
self.app = app self.app = app
self.url_adapter = app.create_url_adapter(None) self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class() self.g = app.app_ctx_globals_class()
@ -234,13 +220,15 @@ class AppContext:
# but there a basic "refcount" is enough to track them. # but there a basic "refcount" is enough to track them.
self._refcnt = 0 self._refcnt = 0
def push(self) -> None: def push(self):
"""Binds the app context to the current context.""" """Binds the app context to the current context."""
self._refcnt += 1 self._refcnt += 1
if hasattr(sys, "exc_clear"):
sys.exc_clear()
_app_ctx_stack.push(self) _app_ctx_stack.push(self)
appcontext_pushed.send(self.app) appcontext_pushed.send(self.app)
def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore def pop(self, exc=_sentinel):
"""Pops the app context.""" """Pops the app context."""
try: try:
self._refcnt -= 1 self._refcnt -= 1
@ -250,20 +238,21 @@ class AppContext:
self.app.do_teardown_appcontext(exc) self.app.do_teardown_appcontext(exc)
finally: finally:
rv = _app_ctx_stack.pop() rv = _app_ctx_stack.pop()
assert rv is self, f"Popped wrong app context. ({rv!r} instead of {self!r})" assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self)
appcontext_popped.send(self.app) appcontext_popped.send(self.app)
def __enter__(self) -> "AppContext": def __enter__(self):
self.push() self.push()
return self return self
def __exit__( def __exit__(self, exc_type, exc_value, tb):
self, exc_type: type, exc_value: BaseException, tb: TracebackType
) -> None:
self.pop(exc_value) self.pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
class RequestContext:
class RequestContext(object):
"""The request context contains all request relevant information. It is """The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the created at the beginning of the request and pushed to the
`_request_ctx_stack` and removed at the end of it. It will create the `_request_ctx_stack` and removed at the end of it. It will create the
@ -293,13 +282,7 @@ class RequestContext:
that situation, otherwise your unittests will leak memory. that situation, otherwise your unittests will leak memory.
""" """
def __init__( def __init__(self, app, environ, request=None, session=None):
self,
app: "Flask",
environ: dict,
request: t.Optional["Request"] = None,
session: t.Optional["SessionMixin"] = None,
) -> None:
self.app = app self.app = app
if request is None: if request is None:
request = app.request_class(environ) request = app.request_class(environ)
@ -316,7 +299,7 @@ class RequestContext:
# other request contexts. Now only if the last level is popped we # other request contexts. Now only if the last level is popped we
# get rid of them. Additionally if an application context is missing # get rid of them. Additionally if an application context is missing
# one is created implicitly so for each level we add this information # one is created implicitly so for each level we add this information
self._implicit_app_ctx_stack: t.List[t.Optional["AppContext"]] = [] self._implicit_app_ctx_stack = []
# indicator if the context was preserved. Next time another context # indicator if the context was preserved. Next time another context
# is pushed the preserved context is popped. # is pushed the preserved context is popped.
@ -329,17 +312,17 @@ class RequestContext:
# Functions that should be executed after the request on the response # Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request" # object. These will be called before the regular "after_request"
# functions. # functions.
self._after_request_functions: t.List[AfterRequestCallable] = [] self._after_request_functions = []
@property @property
def g(self) -> AppContext: def g(self):
return _app_ctx_stack.top.g return _app_ctx_stack.top.g
@g.setter @g.setter
def g(self, value: AppContext) -> None: def g(self, value):
_app_ctx_stack.top.g = value _app_ctx_stack.top.g = value
def copy(self) -> "RequestContext": def copy(self):
"""Creates a copy of this request context with the same request object. """Creates a copy of this request context with the same request object.
This can be used to move a request context to a different greenlet. This can be used to move a request context to a different greenlet.
Because the actual request object is the same this cannot be used to Because the actual request object is the same this cannot be used to
@ -359,17 +342,17 @@ class RequestContext:
session=self.session, session=self.session,
) )
def match_request(self) -> None: def match_request(self):
"""Can be overridden by a subclass to hook into the matching """Can be overridden by a subclass to hook into the matching
of the request. of the request.
""" """
try: try:
result = self.url_adapter.match(return_rule=True) # type: ignore result = self.url_adapter.match(return_rule=True)
self.request.url_rule, self.request.view_args = result # type: ignore self.request.url_rule, self.request.view_args = result
except HTTPException as e: except HTTPException as e:
self.request.routing_exception = e self.request.routing_exception = e
def push(self) -> None: def push(self):
"""Binds the request context to the current context.""" """Binds the request context to the current context."""
# If an exception occurs in debug mode or if context preservation is # If an exception occurs in debug mode or if context preservation is
# activated under exception situations exactly one context stays # activated under exception situations exactly one context stays
@ -393,6 +376,9 @@ class RequestContext:
else: else:
self._implicit_app_ctx_stack.append(None) self._implicit_app_ctx_stack.append(None)
if hasattr(sys, "exc_clear"):
sys.exc_clear()
_request_ctx_stack.push(self) _request_ctx_stack.push(self)
# Open the session at the moment that the request context is available. # Open the session at the moment that the request context is available.
@ -406,12 +392,10 @@ class RequestContext:
if self.session is None: if self.session is None:
self.session = session_interface.make_null_session(self.app) self.session = session_interface.make_null_session(self.app)
# Match the request URL after loading the session, so that the
# session is available in custom URL converters.
if self.url_adapter is not None: if self.url_adapter is not None:
self.match_request() self.match_request()
def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore def pop(self, exc=_sentinel):
"""Pops the request context and unbinds it by doing that. This will """Pops the request context and unbinds it by doing that. This will
also trigger the execution of functions registered by the also trigger the execution of functions registered by the
:meth:`~flask.Flask.teardown_request` decorator. :meth:`~flask.Flask.teardown_request` decorator.
@ -420,9 +404,9 @@ class RequestContext:
Added the `exc` argument. Added the `exc` argument.
""" """
app_ctx = self._implicit_app_ctx_stack.pop() app_ctx = self._implicit_app_ctx_stack.pop()
clear_request = False
try: try:
clear_request = False
if not self._implicit_app_ctx_stack: if not self._implicit_app_ctx_stack:
self.preserved = False self.preserved = False
self._preserved_exc = None self._preserved_exc = None
@ -430,6 +414,13 @@ class RequestContext:
exc = sys.exc_info()[1] exc = sys.exc_info()[1]
self.app.do_teardown_request(exc) 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()
request_close = getattr(self.request, "close", None) request_close = getattr(self.request, "close", None)
if request_close is not None: if request_close is not None:
request_close() request_close()
@ -446,26 +437,25 @@ class RequestContext:
if app_ctx is not None: if app_ctx is not None:
app_ctx.pop(exc) app_ctx.pop(exc)
assert ( assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
rv is self rv,
), f"Popped wrong request context. ({rv!r} instead of {self!r})" self,
)
def auto_pop(self, exc: t.Optional[BaseException]) -> None: def auto_pop(self, exc):
if self.request.environ.get("flask._preserve_context") or ( if self.request.environ.get("flask._preserve_context") or (
exc is not None and self.app.preserve_context_on_exception exc is not None and self.app.preserve_context_on_exception
): ):
self.preserved = True self.preserved = True
self._preserved_exc = exc # type: ignore self._preserved_exc = exc
else: else:
self.pop(exc) self.pop(exc)
def __enter__(self) -> "RequestContext": def __enter__(self):
self.push() self.push()
return self return self
def __exit__( def __exit__(self, exc_type, exc_value, tb):
self, exc_type: type, exc_value: BaseException, tb: TracebackType
) -> None:
# do not pop the request stack if we are in debug mode and an # do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still # exception happened. This will allow the debugger to still
# access the request object in the interactive shell. Furthermore # access the request object in the interactive shell. Furthermore
@ -473,8 +463,13 @@ class RequestContext:
# See flask.testing for how this works. # See flask.testing for how this works.
self.auto_pop(exc_value) self.auto_pop(exc_value)
def __repr__(self) -> str: if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
return ( reraise(exc_type, exc_value, tb)
f"<{type(self).__name__} {self.request.url!r}"
f" [{self.request.method}] of {self.app.name}>" def __repr__(self):
return "<%s '%s' [%s] of %s>" % (
self.__class__.__name__,
self.request.url,
self.request.method,
self.app.name,
) )

View file

@ -1,7 +1,18 @@
# -*- coding: utf-8 -*-
"""
flask.debughelpers
~~~~~~~~~~~~~~~~~~
Various helpers to make the development experience better.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import os import os
import typing as t
from warnings import warn from warnings import warn
from ._compat import implements_to_string
from ._compat import text_type
from .app import Flask from .app import Flask
from .blueprints import Blueprint from .blueprints import Blueprint
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
@ -13,6 +24,7 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError):
""" """
@implements_to_string
class DebugFilesKeyError(KeyError, AssertionError): class DebugFilesKeyError(KeyError, AssertionError):
"""Raised from request.files during debugging. The idea is that it can """Raised from request.files during debugging. The idea is that it can
provide a better error message than just a generic KeyError/BadRequest. provide a better error message than just a generic KeyError/BadRequest.
@ -21,18 +33,17 @@ class DebugFilesKeyError(KeyError, AssertionError):
def __init__(self, request, key): def __init__(self, request, key):
form_matches = request.form.getlist(key) form_matches = request.form.getlist(key)
buf = [ buf = [
f"You tried to access the file {key!r} in the request.files" 'You tried to access the file "%s" in the request.files '
" dictionary but it does not exist. The mimetype for the" "dictionary but it does not exist. The mimetype for the request "
f" request is {request.mimetype!r} instead of" 'is "%s" instead of "multipart/form-data" which means that no '
" 'multipart/form-data' which means that no file contents" "file contents were transmitted. To fix this error you should "
" were transmitted. To fix this error you should provide" 'provide enctype="multipart/form-data" in your form.'
' enctype="multipart/form-data" in your form.' % (key, request.mimetype)
] ]
if form_matches: if form_matches:
names = ", ".join(repr(x) for x in form_matches)
buf.append( buf.append(
"\n\nThe browser instead transmitted some file names. " "\n\nThe browser instead transmitted some file names. "
f"This was submitted: {names}" "This was submitted: %s" % ", ".join('"%s"' % x for x in form_matches)
) )
self.msg = "".join(buf) self.msg = "".join(buf)
@ -49,24 +60,24 @@ class FormDataRoutingRedirect(AssertionError):
def __init__(self, request): def __init__(self, request):
exc = request.routing_exception exc = request.routing_exception
buf = [ buf = [
f"A request was sent to this URL ({request.url}) but a" "A request was sent to this URL (%s) but a redirect was "
" redirect was issued automatically by the routing system" 'issued automatically by the routing system to "%s".'
f" to {exc.new_url!r}." % (request.url, exc.new_url)
] ]
# In case just a slash was appended we can be extra helpful # In case just a slash was appended we can be extra helpful
if f"{request.base_url}/" == exc.new_url.split("?")[0]: if request.base_url + "/" == exc.new_url.split("?")[0]:
buf.append( buf.append(
" The URL was defined with a trailing slash so Flask" " The URL was defined with a trailing slash so "
" will automatically redirect to the URL with the" "Flask will automatically redirect to the URL "
" trailing slash if it was accessed without one." "with the trailing slash if it was accessed "
"without one."
) )
buf.append( buf.append(
" Make sure to directly send your" " Make sure to directly send your %s-request to this URL "
f" {request.method}-request to this URL since we can't make" "since we can't make browsers or HTTP clients redirect "
" browsers or HTTP clients redirect with form data reliably" "with form data reliably or without user interaction." % request.method
" or without user interaction."
) )
buf.append("\n\nNote: this exception is only raised in debug mode") buf.append("\n\nNote: this exception is only raised in debug mode")
AssertionError.__init__(self, "".join(buf).encode("utf-8")) AssertionError.__init__(self, "".join(buf).encode("utf-8"))
@ -93,26 +104,26 @@ def attach_enctype_error_multidict(request):
request.files.__class__ = newcls request.files.__class__ = newcls
def _dump_loader_info(loader) -> t.Generator: def _dump_loader_info(loader):
yield f"class: {type(loader).__module__}.{type(loader).__name__}" yield "class: %s.%s" % (type(loader).__module__, type(loader).__name__)
for key, value in sorted(loader.__dict__.items()): for key, value in sorted(loader.__dict__.items()):
if key.startswith("_"): if key.startswith("_"):
continue continue
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
if not all(isinstance(x, str) for x in value): if not all(isinstance(x, (str, text_type)) for x in value):
continue continue
yield f"{key}:" yield "%s:" % key
for item in value: for item in value:
yield f" - {item}" yield " - %s" % item
continue continue
elif not isinstance(value, (str, int, float, bool)): elif not isinstance(value, (str, text_type, int, float, bool)):
continue continue
yield f"{key}: {value!r}" yield "%s: %r" % (key, value)
def explain_template_loading_attempts(app: Flask, template, attempts) -> None: def explain_template_loading_attempts(app, template, attempts):
"""This should help developers understand what failed""" """This should help developers understand what failed"""
info = [f"Locating template {template!r}:"] info = ['Locating template "%s":' % template]
total_found = 0 total_found = 0
blueprint = None blueprint = None
reqctx = _request_ctx_stack.top reqctx = _request_ctx_stack.top
@ -121,23 +132,23 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
for idx, (loader, srcobj, triple) in enumerate(attempts): for idx, (loader, srcobj, triple) in enumerate(attempts):
if isinstance(srcobj, Flask): if isinstance(srcobj, Flask):
src_info = f"application {srcobj.import_name!r}" src_info = 'application "%s"' % srcobj.import_name
elif isinstance(srcobj, Blueprint): elif isinstance(srcobj, Blueprint):
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" src_info = 'blueprint "%s" (%s)' % (srcobj.name, srcobj.import_name)
else: else:
src_info = repr(srcobj) src_info = repr(srcobj)
info.append(f"{idx + 1:5}: trying loader of {src_info}") info.append("% 5d: trying loader of %s" % (idx + 1, src_info))
for line in _dump_loader_info(loader): for line in _dump_loader_info(loader):
info.append(f" {line}") info.append(" %s" % line)
if triple is None: if triple is None:
detail = "no match" detail = "no match"
else: else:
detail = f"found ({triple[1] or '<string>'!r})" detail = "found (%r)" % (triple[1] or "<string>")
total_found += 1 total_found += 1
info.append(f" -> {detail}") info.append(" -> %s" % detail)
seems_fishy = False seems_fishy = False
if total_found == 0: if total_found == 0:
@ -149,23 +160,24 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
if blueprint is not None and seems_fishy: if blueprint is not None and seems_fishy:
info.append( info.append(
" The template was looked up from an endpoint that belongs" " The template was looked up from an endpoint that "
f" to the blueprint {blueprint!r}." 'belongs to the blueprint "%s".' % blueprint
) )
info.append(" Maybe you did not place a template in the right folder?") info.append(" Maybe you did not place a template in the right folder?")
info.append(" See https://flask.palletsprojects.com/blueprints/#templates") info.append(" See http://flask.pocoo.org/docs/blueprints/#templates")
app.logger.info("\n".join(info)) app.logger.info("\n".join(info))
def explain_ignored_app_run() -> None: def explain_ignored_app_run():
if os.environ.get("WERKZEUG_RUN_MAIN") != "true": if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
warn( warn(
Warning( Warning(
"Silently ignoring app.run() because the application is" "Silently ignoring app.run() because the "
" run from the flask command line executable. Consider" "application is run from the flask command line "
' putting app.run() behind an if __name__ == "__main__"' "executable. Consider putting app.run() behind an "
" guard to silence this warning." 'if __name__ == "__main__" guard to silence this '
"warning."
), ),
stacklevel=3, stacklevel=3,
) )

View file

@ -1,14 +1,19 @@
import typing as t # -*- coding: utf-8 -*-
"""
flask.globals
~~~~~~~~~~~~~
Defines all the global objects that are proxies to the current
active context.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from functools import partial from functools import partial
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack from werkzeug.local import LocalStack
if t.TYPE_CHECKING:
from .app import Flask
from .ctx import _AppCtxGlobals
from .sessions import SessionMixin
from .wrappers import Request
_request_ctx_err_msg = """\ _request_ctx_err_msg = """\
Working outside of request context. Working outside of request context.
@ -51,9 +56,7 @@ def _find_app():
# context locals # context locals
_request_ctx_stack = LocalStack() _request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack() _app_ctx_stack = LocalStack()
current_app: "Flask" = LocalProxy(_find_app) # type: ignore current_app = LocalProxy(_find_app)
request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore request = LocalProxy(partial(_lookup_req_object, "request"))
session: "SessionMixin" = LocalProxy( # type: ignore session = LocalProxy(partial(_lookup_req_object, "session"))
partial(_lookup_req_object, "session") g = LocalProxy(partial(_lookup_app_object, "g"))
)
g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore

File diff suppressed because it is too large Load diff

View file

@ -1,318 +1,337 @@
import io # -*- coding: utf-8 -*-
import json as _json """
import typing as t flask.json
import uuid ~~~~~~~~~~
import warnings
from datetime import date
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps :copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import codecs
import io
import uuid
from datetime import date
from datetime import datetime
from itsdangerous import json as _json
from jinja2 import Markup
from werkzeug.http import http_date from werkzeug.http import http_date
from .._compat import PY2
from .._compat import text_type
from ..globals import current_app from ..globals import current_app
from ..globals import request from ..globals import request
if t.TYPE_CHECKING:
from ..app import Flask
from ..wrappers import Response
try: try:
import dataclasses import dataclasses
except ImportError: except ImportError:
# Python < 3.7 dataclasses = None
dataclasses = None # type: ignore
# Figure out if simplejson escapes slashes. This behavior was changed
# from one version to another without reason.
_slash_escape = "\\/" not in _json.dumps("/")
__all__ = [
"dump",
"dumps",
"load",
"loads",
"htmlsafe_dump",
"htmlsafe_dumps",
"JSONDecoder",
"JSONEncoder",
"jsonify",
]
def _wrap_reader_for_text(fp, encoding):
if isinstance(fp.read(0), bytes):
fp = io.TextIOWrapper(io.BufferedReader(fp), encoding)
return fp
def _wrap_writer_for_text(fp, encoding):
try:
fp.write("")
except TypeError:
fp = io.TextIOWrapper(fp, encoding)
return fp
class JSONEncoder(_json.JSONEncoder): class JSONEncoder(_json.JSONEncoder):
"""The default JSON encoder. Handles extra types compared to the """The default Flask JSON encoder. This one extends the default
built-in :class:`json.JSONEncoder`. encoder by also supporting ``datetime``, ``UUID``, ``dataclasses``,
and ``Markup`` objects.
- :class:`datetime.datetime` and :class:`datetime.date` are ``datetime`` objects are serialized as RFC 822 datetime strings.
serialized to :rfc:`822` strings. This is the same as the HTTP This is the same as the HTTP date format.
date format.
- :class:`uuid.UUID` is serialized to a string.
- :class:`dataclasses.dataclass` is passed to
:func:`dataclasses.asdict`.
- :class:`~markupsafe.Markup` (or any object with a ``__html__``
method) will call the ``__html__`` method to get a string.
Assign a subclass of this to :attr:`flask.Flask.json_encoder` or In order to support more data types, override the :meth:`default`
:attr:`flask.Blueprint.json_encoder` to override the default. method.
""" """
def default(self, o: t.Any) -> t.Any: def default(self, o):
"""Convert ``o`` to a JSON serializable type. See """Implement this method in a subclass such that it returns a
:meth:`json.JSONEncoder.default`. Python does not support serializable object for ``o``, or calls the base implementation (to
overriding how basic types like ``str`` or ``list`` are raise a :exc:`TypeError`).
serialized, they are handled before this method.
For example, to support arbitrary iterators, you could implement
default like this::
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
""" """
if isinstance(o, datetime):
return http_date(o.utctimetuple())
if isinstance(o, date): if isinstance(o, date):
return http_date(o) return http_date(o.timetuple())
if isinstance(o, uuid.UUID): if isinstance(o, uuid.UUID):
return str(o) return str(o)
if dataclasses and dataclasses.is_dataclass(o): if dataclasses and dataclasses.is_dataclass(o):
return dataclasses.asdict(o) return dataclasses.asdict(o)
if hasattr(o, "__html__"): if hasattr(o, "__html__"):
return str(o.__html__()) return text_type(o.__html__())
return super().default(o) return _json.JSONEncoder.default(self, o)
class JSONDecoder(_json.JSONDecoder): class JSONDecoder(_json.JSONDecoder):
"""The default JSON decoder. """The default JSON decoder. This one does not change the behavior from
the default simplejson decoder. Consult the :mod:`json` documentation
This does not change any behavior from the built-in for more information. This decoder is not only used for the load
:class:`json.JSONDecoder`. functions of this module but also :attr:`~flask.Request`.
Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
:attr:`flask.Blueprint.json_decoder` to override the default.
""" """
def _dump_arg_defaults( def _dump_arg_defaults(kwargs, app=None):
kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
) -> None:
"""Inject default arguments for dump functions.""" """Inject default arguments for dump functions."""
if app is None: if app is None:
app = current_app app = current_app
if app: if app:
cls = app.json_encoder bp = app.blueprints.get(request.blueprint) if request else None
bp = app.blueprints.get(request.blueprint) if request else None # type: ignore kwargs.setdefault(
if bp is not None and bp.json_encoder is not None: "cls", bp.json_encoder if bp and bp.json_encoder else app.json_encoder
cls = bp.json_encoder )
if not app.config["JSON_AS_ASCII"]:
kwargs.setdefault("ensure_ascii", False)
kwargs.setdefault("cls", cls)
kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"]) kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
else: else:
kwargs.setdefault("sort_keys", True) kwargs.setdefault("sort_keys", True)
kwargs.setdefault("cls", JSONEncoder) kwargs.setdefault("cls", JSONEncoder)
def _load_arg_defaults( def _load_arg_defaults(kwargs, app=None):
kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
) -> None:
"""Inject default arguments for load functions.""" """Inject default arguments for load functions."""
if app is None: if app is None:
app = current_app app = current_app
if app: if app:
cls = app.json_decoder bp = app.blueprints.get(request.blueprint) if request else None
bp = app.blueprints.get(request.blueprint) if request else None # type: ignore kwargs.setdefault(
if bp is not None and bp.json_decoder is not None: "cls", bp.json_decoder if bp and bp.json_decoder else app.json_decoder
cls = bp.json_decoder )
kwargs.setdefault("cls", cls)
else: else:
kwargs.setdefault("cls", JSONDecoder) kwargs.setdefault("cls", JSONDecoder)
def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str: def detect_encoding(data):
"""Serialize an object to a string of JSON. """Detect which UTF codec was used to encode the given bytes.
Takes the same arguments as the built-in :func:`json.dumps`, with The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is
some defaults from application configuration. accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big
or little endian. Some editors or libraries may prepend a BOM.
:param data: Bytes in unknown UTF encoding.
:return: UTF encoding name
"""
head = data[:4]
if head[:3] == codecs.BOM_UTF8:
return "utf-8-sig"
if b"\x00" not in head:
return "utf-8"
if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE):
return "utf-32"
if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE):
return "utf-16"
if len(head) == 4:
if head[:3] == b"\x00\x00\x00":
return "utf-32-be"
if head[::2] == b"\x00\x00":
return "utf-16-be"
if head[1:] == b"\x00\x00\x00":
return "utf-32-le"
if head[1::2] == b"\x00\x00":
return "utf-16-le"
if len(head) == 2:
return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le"
return "utf-8"
def dumps(obj, app=None, **kwargs):
"""Serialize ``obj`` to a JSON-formatted string. If there is an
app context pushed, use the current app's configured encoder
(:attr:`~flask.Flask.json_encoder`), or fall back to the default
:class:`JSONEncoder`.
Takes the same arguments as the built-in :func:`json.dumps`, and
does some extra configuration based on the application. If the
simplejson package is installed, it is preferred.
:param obj: Object to serialize to JSON. :param obj: Object to serialize to JSON.
:param app: Use this app's config instead of the active app context :param app: App instance to use to configure the JSON encoder.
or defaults. Uses ``current_app`` if not given, and falls back to the default
encoder when not in an app context.
:param kwargs: Extra arguments passed to :func:`json.dumps`. :param kwargs: Extra arguments passed to :func:`json.dumps`.
.. versionchanged:: 2.0
``encoding`` is deprecated and will be removed in Flask 2.1.
.. versionchanged:: 1.0.3 .. versionchanged:: 1.0.3
``app`` can be passed directly, rather than requiring an app ``app`` can be passed directly, rather than requiring an app
context for configuration. context for configuration.
""" """
_dump_arg_defaults(kwargs, app=app) _dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
rv = _json.dumps(obj, **kwargs) rv = _json.dumps(obj, **kwargs)
if encoding is not None and isinstance(rv, text_type):
if encoding is not None: rv = rv.encode(encoding)
warnings.warn(
"'encoding' is deprecated and will be removed in Flask 2.1.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(rv, str):
return rv.encode(encoding) # type: ignore
return rv return rv
def dump( def dump(obj, fp, app=None, **kwargs):
obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any """Like :func:`dumps` but writes into a file object."""
) -> None:
"""Serialize an object to JSON written to a file object.
Takes the same arguments as the built-in :func:`json.dump`, with
some defaults from application configuration.
:param obj: Object to serialize to JSON.
:param fp: File object to write JSON to.
:param app: Use this app's config instead of the active app context
or defaults.
:param kwargs: Extra arguments passed to :func:`json.dump`.
.. versionchanged:: 2.0
Writing to a binary file, and the ``encoding`` argument, is
deprecated and will be removed in Flask 2.1.
"""
_dump_arg_defaults(kwargs, app=app) _dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
show_warning = encoding is not None if encoding is not None:
fp = _wrap_writer_for_text(fp, encoding)
try:
fp.write("")
except TypeError:
show_warning = True
fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore
if show_warning:
warnings.warn(
"Writing to a binary file, and the 'encoding' argument, is"
" deprecated and will be removed in Flask 2.1.",
DeprecationWarning,
stacklevel=2,
)
_json.dump(obj, fp, **kwargs) _json.dump(obj, fp, **kwargs)
def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: def loads(s, app=None, **kwargs):
"""Deserialize an object from a string of JSON. """Deserialize an object from a JSON-formatted string ``s``. If
there is an app context pushed, use the current app's configured
decoder (:attr:`~flask.Flask.json_decoder`), or fall back to the
default :class:`JSONDecoder`.
Takes the same arguments as the built-in :func:`json.loads`, with Takes the same arguments as the built-in :func:`json.loads`, and
some defaults from application configuration. does some extra configuration based on the application. If the
simplejson package is installed, it is preferred.
:param s: JSON string to deserialize. :param s: JSON string to deserialize.
:param app: Use this app's config instead of the active app context :param app: App instance to use to configure the JSON decoder.
or defaults. Uses ``current_app`` if not given, and falls back to the default
encoder when not in an app context.
:param kwargs: Extra arguments passed to :func:`json.loads`. :param kwargs: Extra arguments passed to :func:`json.loads`.
.. versionchanged:: 2.0
``encoding`` is deprecated and will be removed in Flask 2.1. The
data must be a string or UTF-8 bytes.
.. versionchanged:: 1.0.3 .. versionchanged:: 1.0.3
``app`` can be passed directly, rather than requiring an app ``app`` can be passed directly, rather than requiring an app
context for configuration. context for configuration.
""" """
_load_arg_defaults(kwargs, app=app) _load_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None)
if encoding is not None:
warnings.warn(
"'encoding' is deprecated and will be removed in Flask 2.1."
" The data must be a string or UTF-8 bytes.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(s, bytes): if isinstance(s, bytes):
encoding = kwargs.pop("encoding", None)
if encoding is None:
encoding = detect_encoding(s)
s = s.decode(encoding) s = s.decode(encoding)
return _json.loads(s, **kwargs) return _json.loads(s, **kwargs)
def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: def load(fp, app=None, **kwargs):
"""Deserialize an object from JSON read from a file object. """Like :func:`loads` but reads from a file object."""
Takes the same arguments as the built-in :func:`json.load`, with
some defaults from application configuration.
:param fp: File object to read JSON from.
:param app: Use this app's config instead of the active app context
or defaults.
:param kwargs: Extra arguments passed to :func:`json.load`.
.. versionchanged:: 2.0
``encoding`` is deprecated and will be removed in Flask 2.1. The
file must be text mode, or binary mode with UTF-8 bytes.
"""
_load_arg_defaults(kwargs, app=app) _load_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) if not PY2:
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
if encoding is not None:
warnings.warn(
"'encoding' is deprecated and will be removed in Flask 2.1."
" The file must be text mode, or binary mode with UTF-8"
" bytes.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(fp.read(0), bytes):
fp = io.TextIOWrapper(fp, encoding) # type: ignore
return _json.load(fp, **kwargs) return _json.load(fp, **kwargs)
def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str: def htmlsafe_dumps(obj, **kwargs):
"""Serialize an object to a string of JSON with :func:`dumps`, then """Works exactly like :func:`dumps` but is safe for use in ``<script>``
replace HTML-unsafe characters with Unicode escapes and mark the tags. It accepts the same arguments and returns a JSON string. Note that
result safe with :class:`~markupsafe.Markup`. this is available in templates through the ``|tojson`` filter which will
also mark the result as safe. Due to how this function escapes certain
characters this is safe even if used outside of ``<script>`` tags.
This is available in templates as the ``|tojson`` filter. The following characters are escaped in strings:
The returned string is safe to render in HTML documents and - ``<``
``<script>`` tags. The exception is in HTML attributes that are - ``>``
double quoted; either use single quotes or the ``|forceescape`` - ``&``
filter. - ``'``
.. versionchanged:: 2.0 This makes it safe to embed such strings in any place in HTML with the
Uses :func:`jinja2.utils.htmlsafe_json_dumps`. The returned notable exception of double quoted attributes. In that case single
value is marked safe by wrapping in :class:`~markupsafe.Markup`. quote your attributes or HTML escape it in addition.
.. versionchanged:: 0.10 .. versionchanged:: 0.10
Single quotes are escaped, making this safe to use in HTML, This function's return value is now always safe for HTML usage, even
``<script>`` tags, and single-quoted attributes without further if outside of script tags or if used in XHTML. This rule does not
escaping. hold true when using this function in HTML attributes that are double
quoted. Always single quote attributes if you use the ``|tojson``
filter. Alternatively use ``|tojson|forceescape``.
""" """
return _jinja_htmlsafe_dumps(obj, dumps=dumps, **kwargs) rv = (
dumps(obj, **kwargs)
.replace(u"<", u"\\u003c")
.replace(u">", u"\\u003e")
.replace(u"&", u"\\u0026")
.replace(u"'", u"\\u0027")
)
if not _slash_escape:
rv = rv.replace("\\/", "/")
return rv
def htmlsafe_dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: def htmlsafe_dump(obj, fp, **kwargs):
"""Serialize an object to JSON written to a file object, replacing """Like :func:`htmlsafe_dumps` but writes into a file object."""
HTML-unsafe characters with Unicode escapes. See fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
:func:`htmlsafe_dumps` and :func:`dumps`.
"""
fp.write(htmlsafe_dumps(obj, **kwargs))
def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response": def jsonify(*args, **kwargs):
"""Serialize data to JSON and wrap it in a :class:`~flask.Response` """This function wraps :func:`dumps` to add a few enhancements that make
with the :mimetype:`application/json` mimetype. 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]``.
Uses :func:`dumps` to serialize the data, but ``args`` and For clarity, the JSON serialization behavior has the following differences
``kwargs`` are treated as data rather than arguments to from :func:`dumps`:
:func:`json.dumps`.
1. Single argument: Treated as a single value. 1. Single argument: Passed straight through to :func:`dumps`.
2. Multiple arguments: Treated as a list of values. 2. Multiple arguments: Converted to an array before being passed to
``jsonify(1, 2, 3)`` is the same as ``jsonify([1, 2, 3])``. :func:`dumps`.
3. Keyword arguments: Treated as a dict of values. 3. Multiple keyword arguments: Converted to a dict before being passed to
``jsonify(data=data, errors=errors)`` is the same as :func:`dumps`.
``jsonify({"data": data, "errors": errors})``. 4. Both args and kwargs: Behavior undefined and will throw an exception.
4. Passing both arguments and keyword arguments is not allowed as
it's not clear what should happen.
.. code-block:: python Example usage::
from flask import jsonify from flask import jsonify
@app.route("/users/me") @app.route('/_get_current_user')
def get_current_user(): def get_current_user():
return jsonify( return jsonify(username=g.user.username,
username=g.user.username,
email=g.user.email, email=g.user.email,
id=g.user.id, id=g.user.id)
)
Will return a JSON response like this: This will send a JSON response like this to the browser::
.. code-block:: javascript
{ {
"username": "admin", "username": "admin",
@ -320,16 +339,19 @@ def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
"id": 42 "id": 42
} }
The default output omits indents and spaces after separators. In
debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
the output will be formatted to be easier to read.
.. versionchanged:: 0.11 .. versionchanged:: 0.11
Added support for serializing top-level arrays. This introduces Added support for serializing top-level arrays. This introduces a
a security risk in ancient browsers. See :ref:`security-json`. 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 .. versionadded:: 0.2
""" """
indent = None indent = None
separators = (",", ":") separators = (",", ":")
@ -345,6 +367,10 @@ def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
data = args or kwargs data = args or kwargs
return current_app.response_class( return current_app.response_class(
f"{dumps(data, indent=indent, separators=separators)}\n", dumps(data, indent=indent, separators=separators) + "\n",
mimetype=current_app.config["JSONIFY_MIMETYPE"], mimetype=current_app.config["JSONIFY_MIMETYPE"],
) )
def tojson_filter(obj, **kwargs):
return Markup(htmlsafe_dumps(obj, **kwargs))

Binary file not shown.

Binary file not shown.

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
""" """
Tagged JSON Tagged JSON
~~~~~~~~~~~ ~~~~~~~~~~~
A compact representation for lossless serialization of non-standard JSON A compact representation for lossless serialization of non-standard JSON types.
types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this :class:`~flask.sessions.SecureCookieSessionInterface` uses this to serialize
to serialize the session data, but it may be useful in other places. It the session data, but it may be useful in other places. It can be extended to
can be extended to support other types. support other types.
.. autoclass:: TaggedJSONSerializer .. autoclass:: TaggedJSONSerializer
:members: :members:
@ -13,15 +14,12 @@ can be extended to support other types.
.. autoclass:: JSONTag .. autoclass:: JSONTag
:members: :members:
Let's see an example that adds support for Let's seen an example that adds support for :class:`~collections.OrderedDict`.
:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so Dicts don't have an order in Python or JSON, so to handle this we will dump
to handle this we will dump the items as a list of ``[key, value]`` the items as a list of ``[key, value]`` pairs. Subclass :class:`JSONTag` and
pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to give it the new key ``' od'`` to identify the type. The session serializer
identify the type. The session serializer processes dicts first, so processes dicts first, so insert the new tag at the front of the order since
insert the new tag at the front of the order since ``OrderedDict`` must ``OrderedDict`` must be processed before ``dict``. ::
be processed before ``dict``.
.. code-block:: python
from flask.json.tag import JSONTag from flask.json.tag import JSONTag
@ -39,49 +37,53 @@ be processed before ``dict``.
return OrderedDict(value) return OrderedDict(value)
app.session_interface.serializer.register(TagOrderedDict, index=0) app.session_interface.serializer.register(TagOrderedDict, index=0)
:copyright: 2010 Pallets
:license: BSD-3-Clause
""" """
import typing as t
from base64 import b64decode from base64 import b64decode
from base64 import b64encode from base64 import b64encode
from datetime import datetime from datetime import datetime
from uuid import UUID from uuid import UUID
from markupsafe import Markup from jinja2 import Markup
from werkzeug.http import http_date from werkzeug.http import http_date
from werkzeug.http import parse_date from werkzeug.http import parse_date
from .._compat import iteritems
from .._compat import text_type
from ..json import dumps from ..json import dumps
from ..json import loads from ..json import loads
class JSONTag: class JSONTag(object):
"""Base class for defining type tags for :class:`TaggedJSONSerializer`.""" """Base class for defining type tags for :class:`TaggedJSONSerializer`."""
__slots__ = ("serializer",) __slots__ = ("serializer",)
#: The tag to mark the serialized object with. If ``None``, this tag is #: The tag to mark the serialized object with. If ``None``, this tag is
#: only used as an intermediate step during tagging. #: only used as an intermediate step during tagging.
key: t.Optional[str] = None key = None
def __init__(self, serializer: "TaggedJSONSerializer") -> None: def __init__(self, serializer):
"""Create a tagger for the given serializer.""" """Create a tagger for the given serializer."""
self.serializer = serializer self.serializer = serializer
def check(self, value: t.Any) -> bool: def check(self, value):
"""Check if the given value should be tagged by this tag.""" """Check if the given value should be tagged by this tag."""
raise NotImplementedError raise NotImplementedError
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
"""Convert the Python object to an object that is a valid JSON type. """Convert the Python object to an object that is a valid JSON type.
The tag will be added later.""" The tag will be added later."""
raise NotImplementedError raise NotImplementedError
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
"""Convert the JSON representation back to the correct type. The tag """Convert the JSON representation back to the correct type. The tag
will already be removed.""" will already be removed."""
raise NotImplementedError raise NotImplementedError
def tag(self, value: t.Any) -> t.Any: def tag(self, value):
"""Convert the value to a valid JSON type and add the tag structure """Convert the value to a valid JSON type and add the tag structure
around it.""" around it."""
return {self.key: self.to_json(value)} return {self.key: self.to_json(value)}
@ -97,18 +99,18 @@ class TagDict(JSONTag):
__slots__ = () __slots__ = ()
key = " di" key = " di"
def check(self, value: t.Any) -> bool: def check(self, value):
return ( return (
isinstance(value, dict) isinstance(value, dict)
and len(value) == 1 and len(value) == 1
and next(iter(value)) in self.serializer.tags and next(iter(value)) in self.serializer.tags
) )
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
key = next(iter(value)) key = next(iter(value))
return {f"{key}__": self.serializer.tag(value[key])} return {key + "__": self.serializer.tag(value[key])}
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
key = next(iter(value)) key = next(iter(value))
return {key[:-2]: value[key]} return {key[:-2]: value[key]}
@ -116,13 +118,13 @@ class TagDict(JSONTag):
class PassDict(JSONTag): class PassDict(JSONTag):
__slots__ = () __slots__ = ()
def check(self, value: t.Any) -> bool: def check(self, value):
return isinstance(value, dict) return isinstance(value, dict)
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
# JSON objects may only have string keys, so don't bother tagging the # JSON objects may only have string keys, so don't bother tagging the
# key here. # key here.
return {k: self.serializer.tag(v) for k, v in value.items()} return dict((k, self.serializer.tag(v)) for k, v in iteritems(value))
tag = to_json tag = to_json
@ -131,23 +133,23 @@ class TagTuple(JSONTag):
__slots__ = () __slots__ = ()
key = " t" key = " t"
def check(self, value: t.Any) -> bool: def check(self, value):
return isinstance(value, tuple) return isinstance(value, tuple)
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
return [self.serializer.tag(item) for item in value] return [self.serializer.tag(item) for item in value]
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
return tuple(value) return tuple(value)
class PassList(JSONTag): class PassList(JSONTag):
__slots__ = () __slots__ = ()
def check(self, value: t.Any) -> bool: def check(self, value):
return isinstance(value, list) return isinstance(value, list)
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
return [self.serializer.tag(item) for item in value] return [self.serializer.tag(item) for item in value]
tag = to_json tag = to_json
@ -157,31 +159,31 @@ class TagBytes(JSONTag):
__slots__ = () __slots__ = ()
key = " b" key = " b"
def check(self, value: t.Any) -> bool: def check(self, value):
return isinstance(value, bytes) return isinstance(value, bytes)
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
return b64encode(value).decode("ascii") return b64encode(value).decode("ascii")
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
return b64decode(value) return b64decode(value)
class TagMarkup(JSONTag): class TagMarkup(JSONTag):
"""Serialize anything matching the :class:`~markupsafe.Markup` API by """Serialize anything matching the :class:`~flask.Markup` API by
having a ``__html__`` method to the result of that method. Always having a ``__html__`` method to the result of that method. Always
deserializes to an instance of :class:`~markupsafe.Markup`.""" deserializes to an instance of :class:`~flask.Markup`."""
__slots__ = () __slots__ = ()
key = " m" key = " m"
def check(self, value: t.Any) -> bool: def check(self, value):
return callable(getattr(value, "__html__", None)) return callable(getattr(value, "__html__", None))
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
return str(value.__html__()) return text_type(value.__html__())
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
return Markup(value) return Markup(value)
@ -189,13 +191,13 @@ class TagUUID(JSONTag):
__slots__ = () __slots__ = ()
key = " u" key = " u"
def check(self, value: t.Any) -> bool: def check(self, value):
return isinstance(value, UUID) return isinstance(value, UUID)
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
return value.hex return value.hex
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
return UUID(value) return UUID(value)
@ -203,17 +205,17 @@ class TagDateTime(JSONTag):
__slots__ = () __slots__ = ()
key = " d" key = " d"
def check(self, value: t.Any) -> bool: def check(self, value):
return isinstance(value, datetime) return isinstance(value, datetime)
def to_json(self, value: t.Any) -> t.Any: def to_json(self, value):
return http_date(value) return http_date(value)
def to_python(self, value: t.Any) -> t.Any: def to_python(self, value):
return parse_date(value) return parse_date(value)
class TaggedJSONSerializer: class TaggedJSONSerializer(object):
"""Serializer that uses a tag system to compactly represent objects that """Serializer that uses a tag system to compactly represent objects that
are not JSON types. Passed as the intermediate serializer to are not JSON types. Passed as the intermediate serializer to
:class:`itsdangerous.Serializer`. :class:`itsdangerous.Serializer`.
@ -223,7 +225,7 @@ class TaggedJSONSerializer:
* :class:`dict` * :class:`dict`
* :class:`tuple` * :class:`tuple`
* :class:`bytes` * :class:`bytes`
* :class:`~markupsafe.Markup` * :class:`~flask.Markup`
* :class:`~uuid.UUID` * :class:`~uuid.UUID`
* :class:`~datetime.datetime` * :class:`~datetime.datetime`
""" """
@ -243,19 +245,14 @@ class TaggedJSONSerializer:
TagDateTime, TagDateTime,
] ]
def __init__(self) -> None: def __init__(self):
self.tags: t.Dict[str, JSONTag] = {} self.tags = {}
self.order: t.List[JSONTag] = [] self.order = []
for cls in self.default_tags: for cls in self.default_tags:
self.register(cls) self.register(cls)
def register( def register(self, tag_class, force=False, index=None):
self,
tag_class: t.Type[JSONTag],
force: bool = False,
index: t.Optional[int] = None,
) -> None:
"""Register a new tag with this serializer. """Register a new tag with this serializer.
:param tag_class: tag class to register. Will be instantiated with this :param tag_class: tag class to register. Will be instantiated with this
@ -274,7 +271,7 @@ class TaggedJSONSerializer:
if key is not None: if key is not None:
if not force and key in self.tags: if not force and key in self.tags:
raise KeyError(f"Tag '{key}' is already registered.") raise KeyError("Tag '{0}' is already registered.".format(key))
self.tags[key] = tag self.tags[key] = tag
@ -283,7 +280,7 @@ class TaggedJSONSerializer:
else: else:
self.order.insert(index, tag) self.order.insert(index, tag)
def tag(self, value: t.Any) -> t.Dict[str, t.Any]: def tag(self, value):
"""Convert a value to a tagged representation if necessary.""" """Convert a value to a tagged representation if necessary."""
for tag in self.order: for tag in self.order:
if tag.check(value): if tag.check(value):
@ -291,7 +288,7 @@ class TaggedJSONSerializer:
return value return value
def untag(self, value: t.Dict[str, t.Any]) -> t.Any: def untag(self, value):
"""Convert a tagged representation back to the original type.""" """Convert a tagged representation back to the original type."""
if len(value) != 1: if len(value) != 1:
return value return value
@ -303,10 +300,10 @@ class TaggedJSONSerializer:
return self.tags[key].to_python(value[key]) return self.tags[key].to_python(value[key])
def dumps(self, value: t.Any) -> str: def dumps(self, value):
"""Tag the value and dump it to a compact JSON string.""" """Tag the value and dump it to a compact JSON string."""
return dumps(self.tag(value), separators=(",", ":")) return dumps(self.tag(value), separators=(",", ":"))
def loads(self, value: str) -> t.Any: def loads(self, value):
"""Load data from a JSON string and deserialized any tagged objects.""" """Load data from a JSON string and deserialized any tagged objects."""
return loads(value, object_hook=self.untag) return loads(value, object_hook=self.untag)

View file

@ -1,17 +1,24 @@
# -*- coding: utf-8 -*-
"""
flask.logging
~~~~~~~~~~~~~
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from __future__ import absolute_import
import logging import logging
import sys import sys
import typing as t import warnings
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from .globals import request from .globals import request
if t.TYPE_CHECKING:
from .app import Flask
@LocalProxy @LocalProxy
def wsgi_errors_stream() -> t.TextIO: def wsgi_errors_stream():
"""Find the most appropriate error stream for the application. If a request """Find the most appropriate error stream for the application. If a request
is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``.
@ -23,7 +30,7 @@ def wsgi_errors_stream() -> t.TextIO:
return request.environ["wsgi.errors"] if request else sys.stderr return request.environ["wsgi.errors"] if request else sys.stderr
def has_level_handler(logger: logging.Logger) -> bool: def has_level_handler(logger):
"""Check if there is a handler in the logging chain that will handle the """Check if there is a handler in the logging chain that will handle the
given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`.
""" """
@ -37,21 +44,35 @@ def has_level_handler(logger: logging.Logger) -> bool:
if not current.propagate: if not current.propagate:
break break
current = current.parent # type: ignore current = current.parent
return False return False
#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format #: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format
#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. #: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``.
default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore default_handler = logging.StreamHandler(wsgi_errors_stream)
default_handler.setFormatter( default_handler.setFormatter(
logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s")
) )
def create_logger(app: "Flask") -> logging.Logger: def _has_config(logger):
"""Get the Flask app's logger and configure it if needed. """Decide if a logger has direct configuration applied by checking
its properties against the defaults.
:param logger: The :class:`~logging.Logger` to inspect.
"""
return (
logger.level != logging.NOTSET
or logger.handlers
or logger.filters
or not logger.propagate
)
def create_logger(app):
"""Get the the Flask apps's logger and configure it if needed.
The logger name will be the same as The logger name will be the same as
:attr:`app.import_name <flask.Flask.name>`. :attr:`app.import_name <flask.Flask.name>`.
@ -65,6 +86,20 @@ def create_logger(app: "Flask") -> logging.Logger:
""" """
logger = logging.getLogger(app.name) logger = logging.getLogger(app.name)
# 1.1.0 changes name of logger, warn if config is detected for old
# name and not new name
for old_name in ("flask.app", "flask"):
old_logger = logging.getLogger(old_name)
if _has_config(old_logger) and not _has_config(logger):
warnings.warn(
"'app.logger' is named '{name}' for this application,"
" but configuration was found for '{old_name}', which"
" no longer has an effect. The logging configuration"
" should be moved to '{name}'.".format(name=app.name, old_name=old_name)
)
break
if app.debug and not logger.level: if app.debug and not logger.level:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)

View file

@ -1,32 +1,37 @@
# -*- coding: utf-8 -*-
"""
flask.sessions
~~~~~~~~~~~~~~
Implements cookie based sessions based on itsdangerous.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import hashlib import hashlib
import typing as t
import warnings import warnings
from collections.abc import MutableMapping
from datetime import datetime from datetime import datetime
from itsdangerous import BadSignature from itsdangerous import BadSignature
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
from ._compat import collections_abc
from .helpers import is_ip from .helpers import is_ip
from .helpers import total_seconds
from .json.tag import TaggedJSONSerializer from .json.tag import TaggedJSONSerializer
if t.TYPE_CHECKING:
import typing_extensions as te
from .app import Flask
from .wrappers import Request, Response
class SessionMixin(collections_abc.MutableMapping):
class SessionMixin(MutableMapping):
"""Expands a basic dictionary with session attributes.""" """Expands a basic dictionary with session attributes."""
@property @property
def permanent(self) -> bool: def permanent(self):
"""This reflects the ``'_permanent'`` key in the dict.""" """This reflects the ``'_permanent'`` key in the dict."""
return self.get("_permanent", False) return self.get("_permanent", False)
@permanent.setter @permanent.setter
def permanent(self, value: bool) -> None: def permanent(self, value):
self["_permanent"] = bool(value) self["_permanent"] = bool(value)
#: Some implementations can detect whether a session is newly #: Some implementations can detect whether a session is newly
@ -67,24 +72,24 @@ class SecureCookieSession(CallbackDict, SessionMixin):
#: different users. #: different users.
accessed = False accessed = False
def __init__(self, initial: t.Any = None) -> None: def __init__(self, initial=None):
def on_update(self) -> None: def on_update(self):
self.modified = True self.modified = True
self.accessed = True self.accessed = True
super().__init__(initial, on_update) super(SecureCookieSession, self).__init__(initial, on_update)
def __getitem__(self, key: str) -> t.Any: def __getitem__(self, key):
self.accessed = True self.accessed = True
return super().__getitem__(key) return super(SecureCookieSession, self).__getitem__(key)
def get(self, key: str, default: t.Any = None) -> t.Any: def get(self, key, default=None):
self.accessed = True self.accessed = True
return super().get(key, default) return super(SecureCookieSession, self).get(key, default)
def setdefault(self, key: str, default: t.Any = None) -> t.Any: def setdefault(self, key, default=None):
self.accessed = True self.accessed = True
return super().setdefault(key, default) return super(SecureCookieSession, self).setdefault(key, default)
class NullSession(SecureCookieSession): class NullSession(SecureCookieSession):
@ -93,18 +98,18 @@ class NullSession(SecureCookieSession):
but fail on setting. but fail on setting.
""" """
def _fail(self, *args: t.Any, **kwargs: t.Any) -> "te.NoReturn": def _fail(self, *args, **kwargs):
raise RuntimeError( raise RuntimeError(
"The session is unavailable because no secret " "The session is unavailable because no secret "
"key was set. Set the secret_key on the " "key was set. Set the secret_key on the "
"application to something unique and secret." "application to something unique and secret."
) )
__setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950 __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail
del _fail del _fail
class SessionInterface: class SessionInterface(object):
"""The basic interface you have to implement in order to replace the """The basic interface you have to implement in order to replace the
default session interface which uses werkzeug's securecookie default session interface which uses werkzeug's securecookie
implementation. The only methods you have to implement are implementation. The only methods you have to implement are
@ -147,7 +152,7 @@ class SessionInterface:
#: .. versionadded:: 0.10 #: .. versionadded:: 0.10
pickle_based = False pickle_based = False
def make_null_session(self, app: "Flask") -> NullSession: def make_null_session(self, app):
"""Creates a null session which acts as a replacement object if the """Creates a null session which acts as a replacement object if the
real session support could not be loaded due to a configuration real session support could not be loaded due to a configuration
error. This mainly aids the user experience because the job of the error. This mainly aids the user experience because the job of the
@ -159,7 +164,7 @@ class SessionInterface:
""" """
return self.null_session_class() return self.null_session_class()
def is_null_session(self, obj: object) -> bool: def is_null_session(self, obj):
"""Checks if a given object is a null session. Null sessions are """Checks if a given object is a null session. Null sessions are
not asked to be saved. not asked to be saved.
@ -168,14 +173,7 @@ class SessionInterface:
""" """
return isinstance(obj, self.null_session_class) return isinstance(obj, self.null_session_class)
def get_cookie_name(self, app: "Flask") -> str: def get_cookie_domain(self, app):
"""Returns the name of the session cookie.
Uses ``app.session_cookie_name`` which is set to ``SESSION_COOKIE_NAME``
"""
return app.session_cookie_name
def get_cookie_domain(self, app: "Flask") -> t.Optional[str]:
"""Returns the domain that should be set for the session cookie. """Returns the domain that should be set for the session cookie.
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
@ -204,13 +202,13 @@ class SessionInterface:
rv = rv.rsplit(":", 1)[0].lstrip(".") rv = rv.rsplit(":", 1)[0].lstrip(".")
if "." not in rv: if "." not in rv:
# Chrome doesn't allow names without a '.'. This should only # Chrome doesn't allow names without a '.'
# come up with localhost. Hack around this by not setting # this should only come up with localhost
# the name, and show a warning. # hack around this by not setting the name, and show a warning
warnings.warn( warnings.warn(
f"{rv!r} is not a valid cookie domain, it must contain" '"{rv}" is not a valid cookie domain, it must contain a ".".'
" a '.'. Add an entry to your hosts file, for example" " Add an entry to your hosts file, for example"
f" '{rv}.localdomain', and use that instead." ' "{rv}.localdomain", and use that instead.'.format(rv=rv)
) )
app.config["SESSION_COOKIE_DOMAIN"] = False app.config["SESSION_COOKIE_DOMAIN"] = False
return None return None
@ -228,12 +226,12 @@ class SessionInterface:
# if this is not an ip and app is mounted at the root, allow subdomain # if this is not an ip and app is mounted at the root, allow subdomain
# matching by adding a '.' prefix # matching by adding a '.' prefix
if self.get_cookie_path(app) == "/" and not ip: if self.get_cookie_path(app) == "/" and not ip:
rv = f".{rv}" rv = "." + rv
app.config["SESSION_COOKIE_DOMAIN"] = rv app.config["SESSION_COOKIE_DOMAIN"] = rv
return rv return rv
def get_cookie_path(self, app: "Flask") -> str: def get_cookie_path(self, app):
"""Returns the path for which the cookie should be valid. The """Returns the path for which the cookie should be valid. The
default implementation uses the value from the ``SESSION_COOKIE_PATH`` default implementation uses the value from the ``SESSION_COOKIE_PATH``
config var if it's set, and falls back to ``APPLICATION_ROOT`` or config var if it's set, and falls back to ``APPLICATION_ROOT`` or
@ -241,29 +239,27 @@ class SessionInterface:
""" """
return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"]
def get_cookie_httponly(self, app: "Flask") -> bool: def get_cookie_httponly(self, app):
"""Returns True if the session cookie should be httponly. This """Returns True if the session cookie should be httponly. This
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
config var. config var.
""" """
return app.config["SESSION_COOKIE_HTTPONLY"] return app.config["SESSION_COOKIE_HTTPONLY"]
def get_cookie_secure(self, app: "Flask") -> bool: def get_cookie_secure(self, app):
"""Returns True if the cookie should be secure. This currently """Returns True if the cookie should be secure. This currently
just returns the value of the ``SESSION_COOKIE_SECURE`` setting. just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
""" """
return app.config["SESSION_COOKIE_SECURE"] return app.config["SESSION_COOKIE_SECURE"]
def get_cookie_samesite(self, app: "Flask") -> str: def get_cookie_samesite(self, app):
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
``SameSite`` attribute. This currently just returns the value of ``SameSite`` attribute. This currently just returns the value of
the :data:`SESSION_COOKIE_SAMESITE` setting. the :data:`SESSION_COOKIE_SAMESITE` setting.
""" """
return app.config["SESSION_COOKIE_SAMESITE"] return app.config["SESSION_COOKIE_SAMESITE"]
def get_expiration_time( def get_expiration_time(self, app, session):
self, app: "Flask", session: SessionMixin
) -> t.Optional[datetime]:
"""A helper method that returns an expiration date for the session """A helper method that returns an expiration date for the session
or ``None`` if the session is linked to the browser session. The or ``None`` if the session is linked to the browser session. The
default implementation returns now + the permanent session default implementation returns now + the permanent session
@ -271,9 +267,8 @@ class SessionInterface:
""" """
if session.permanent: if session.permanent:
return datetime.utcnow() + app.permanent_session_lifetime return datetime.utcnow() + app.permanent_session_lifetime
return None
def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool: def should_set_cookie(self, app, session):
"""Used by session backends to determine if a ``Set-Cookie`` header """Used by session backends to determine if a ``Set-Cookie`` header
should be set for this session cookie for this response. If the session should be set for this session cookie for this response. If the session
has been modified, the cookie is set. If the session is permanent and has been modified, the cookie is set. If the session is permanent and
@ -289,9 +284,7 @@ class SessionInterface:
session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"]
) )
def open_session( def open_session(self, app, request):
self, app: "Flask", request: "Request"
) -> t.Optional[SessionMixin]:
"""This method has to be implemented and must either return ``None`` """This method has to be implemented and must either return ``None``
in case the loading failed because of a configuration error or an in case the loading failed because of a configuration error or an
instance of a session object which implements a dictionary like instance of a session object which implements a dictionary like
@ -299,9 +292,7 @@ class SessionInterface:
""" """
raise NotImplementedError() raise NotImplementedError()
def save_session( def save_session(self, app, session, response):
self, app: "Flask", session: SessionMixin, response: "Response"
) -> None:
"""This is called for actual sessions returned by :meth:`open_session` """This is called for actual sessions returned by :meth:`open_session`
at the end of the request. This is still called during a request at the end of the request. This is still called during a request
context so if you absolutely need access to the request you can do context so if you absolutely need access to the request you can do
@ -332,9 +323,7 @@ class SecureCookieSessionInterface(SessionInterface):
serializer = session_json_serializer serializer = session_json_serializer
session_class = SecureCookieSession session_class = SecureCookieSession
def get_signing_serializer( def get_signing_serializer(self, app):
self, app: "Flask"
) -> t.Optional[URLSafeTimedSerializer]:
if not app.secret_key: if not app.secret_key:
return None return None
signer_kwargs = dict( signer_kwargs = dict(
@ -347,37 +336,30 @@ class SecureCookieSessionInterface(SessionInterface):
signer_kwargs=signer_kwargs, signer_kwargs=signer_kwargs,
) )
def open_session( def open_session(self, app, request):
self, app: "Flask", request: "Request"
) -> t.Optional[SecureCookieSession]:
s = self.get_signing_serializer(app) s = self.get_signing_serializer(app)
if s is None: if s is None:
return None return None
val = request.cookies.get(self.get_cookie_name(app)) val = request.cookies.get(app.session_cookie_name)
if not val: if not val:
return self.session_class() return self.session_class()
max_age = int(app.permanent_session_lifetime.total_seconds()) max_age = total_seconds(app.permanent_session_lifetime)
try: try:
data = s.loads(val, max_age=max_age) data = s.loads(val, max_age=max_age)
return self.session_class(data) return self.session_class(data)
except BadSignature: except BadSignature:
return self.session_class() return self.session_class()
def save_session( def save_session(self, app, session, response):
self, app: "Flask", session: SessionMixin, response: "Response"
) -> None:
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app) domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app) path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
# If the session is modified to be empty, remove the cookie. # If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie. # If the session is empty, return without setting the cookie.
if not session: if not session:
if session.modified: if session.modified:
response.delete_cookie( response.delete_cookie(
name, domain=domain, path=path, secure=secure, samesite=samesite app.session_cookie_name, domain=domain, path=path
) )
return return
@ -390,11 +372,13 @@ class SecureCookieSessionInterface(SessionInterface):
return return
httponly = self.get_cookie_httponly(app) httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session) expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie( response.set_cookie(
name, app.session_cookie_name,
val, # type: ignore val,
expires=expires, expires=expires,
httponly=httponly, httponly=httponly,
domain=domain, domain=domain,

View file

@ -1,5 +1,14 @@
import typing as t # -*- coding: utf-8 -*-
"""
flask.signals
~~~~~~~~~~~~~
Implements signals based on blinker if available, otherwise
falls silently back to a noop.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
try: try:
from blinker import Namespace from blinker import Namespace
@ -7,25 +16,25 @@ try:
except ImportError: except ImportError:
signals_available = False signals_available = False
class Namespace: # type: ignore class Namespace(object):
def signal(self, name: str, doc: t.Optional[str] = None) -> "_FakeSignal": def signal(self, name, doc=None):
return _FakeSignal(name, doc) return _FakeSignal(name, doc)
class _FakeSignal: class _FakeSignal(object):
"""If blinker is unavailable, create a fake class with the same """If blinker is unavailable, create a fake class with the same
interface that allows sending of signals but will fail with an interface that allows sending of signals but will fail with an
error on anything else. Instead of doing anything on send, it error on anything else. Instead of doing anything on send, it
will just ignore the arguments and do nothing instead. will just ignore the arguments and do nothing instead.
""" """
def __init__(self, name: str, doc: t.Optional[str] = None) -> None: def __init__(self, name, doc=None):
self.name = name self.name = name
self.__doc__ = doc self.__doc__ = doc
def send(self, *args: t.Any, **kwargs: t.Any) -> t.Any: def send(self, *args, **kwargs):
pass pass
def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.Any: def _fail(self, *args, **kwargs):
raise RuntimeError( raise RuntimeError(
"Signalling support is unavailable because the blinker" "Signalling support is unavailable because the blinker"
" library is not installed." " library is not installed."

View file

@ -1,8 +1,15 @@
import typing as t # -*- coding: utf-8 -*-
"""
flask.templating
~~~~~~~~~~~~~~~~
Implements the bridge to Jinja2.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from jinja2 import BaseLoader from jinja2 import BaseLoader
from jinja2 import Environment as BaseEnvironment from jinja2 import Environment as BaseEnvironment
from jinja2 import Template
from jinja2 import TemplateNotFound from jinja2 import TemplateNotFound
from .globals import _app_ctx_stack from .globals import _app_ctx_stack
@ -10,12 +17,8 @@ from .globals import _request_ctx_stack
from .signals import before_render_template from .signals import before_render_template
from .signals import template_rendered from .signals import template_rendered
if t.TYPE_CHECKING:
from .app import Flask
from .scaffold import Scaffold
def _default_template_ctx_processor():
def _default_template_ctx_processor() -> t.Dict[str, t.Any]:
"""Default template context processor. Injects `request`, """Default template context processor. Injects `request`,
`session` and `g`. `session` and `g`.
""" """
@ -36,7 +39,7 @@ class Environment(BaseEnvironment):
name of the blueprint to referenced templates if necessary. name of the blueprint to referenced templates if necessary.
""" """
def __init__(self, app: "Flask", **options: t.Any) -> None: def __init__(self, app, **options):
if "loader" not in options: if "loader" not in options:
options["loader"] = app.create_global_jinja_loader() options["loader"] = app.create_global_jinja_loader()
BaseEnvironment.__init__(self, **options) BaseEnvironment.__init__(self, **options)
@ -48,24 +51,17 @@ class DispatchingJinjaLoader(BaseLoader):
the blueprint folders. the blueprint folders.
""" """
def __init__(self, app: "Flask") -> None: def __init__(self, app):
self.app = app self.app = app
def get_source( # type: ignore def get_source(self, environment, template):
self, environment: Environment, template: str
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
return self._get_source_explained(environment, template) return self._get_source_explained(environment, template)
return self._get_source_fast(environment, template) return self._get_source_fast(environment, template)
def _get_source_explained( def _get_source_explained(self, environment, template):
self, environment: Environment, template: str
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
attempts = [] attempts = []
rv: t.Optional[t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]] trv = None
trv: t.Optional[
t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
] = None
for srcobj, loader in self._iter_loaders(template): for srcobj, loader in self._iter_loaders(template):
try: try:
@ -84,9 +80,7 @@ class DispatchingJinjaLoader(BaseLoader):
return trv return trv
raise TemplateNotFound(template) raise TemplateNotFound(template)
def _get_source_fast( def _get_source_fast(self, environment, template):
self, environment: Environment, template: str
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
for _srcobj, loader in self._iter_loaders(template): for _srcobj, loader in self._iter_loaders(template):
try: try:
return loader.get_source(environment, template) return loader.get_source(environment, template)
@ -94,9 +88,7 @@ class DispatchingJinjaLoader(BaseLoader):
continue continue
raise TemplateNotFound(template) raise TemplateNotFound(template)
def _iter_loaders( def _iter_loaders(self, template):
self, template: str
) -> t.Generator[t.Tuple["Scaffold", BaseLoader], None, None]:
loader = self.app.jinja_loader loader = self.app.jinja_loader
if loader is not None: if loader is not None:
yield self.app, loader yield self.app, loader
@ -106,7 +98,7 @@ class DispatchingJinjaLoader(BaseLoader):
if loader is not None: if loader is not None:
yield blueprint, loader yield blueprint, loader
def list_templates(self) -> t.List[str]: def list_templates(self):
result = set() result = set()
loader = self.app.jinja_loader loader = self.app.jinja_loader
if loader is not None: if loader is not None:
@ -121,7 +113,7 @@ class DispatchingJinjaLoader(BaseLoader):
return list(result) return list(result)
def _render(template: Template, context: dict, app: "Flask") -> str: def _render(template, context, app):
"""Renders the template and fires the signal""" """Renders the template and fires the signal"""
before_render_template.send(app, template=template, context=context) before_render_template.send(app, template=template, context=context)
@ -130,9 +122,7 @@ def _render(template: Template, context: dict, app: "Flask") -> str:
return rv return rv
def render_template( def render_template(template_name_or_list, **context):
template_name_or_list: t.Union[str, t.List[str]], **context: t.Any
) -> str:
"""Renders a template from the template folder with the given """Renders a template from the template folder with the given
context. context.
@ -151,7 +141,7 @@ def render_template(
) )
def render_template_string(source: str, **context: t.Any) -> str: def render_template_string(source, **context):
"""Renders a template from the given template source string """Renders a template from the given template source string
with the given context. Template variables will be autoescaped. with the given context. Template variables will be autoescaped.

View file

@ -1,22 +1,25 @@
import typing as t # -*- coding: utf-8 -*-
"""
flask.testing
~~~~~~~~~~~~~
Implements test support helpers. This module is lazily imported
and usually not used in production environments.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import warnings
from contextlib import contextmanager from contextlib import contextmanager
from copy import copy
from types import TracebackType
import werkzeug.test import werkzeug.test
from click.testing import CliRunner from click.testing import CliRunner
from werkzeug.test import Client from werkzeug.test import Client
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
from werkzeug.wrappers import Request as BaseRequest
from . import _request_ctx_stack from . import _request_ctx_stack
from .cli import ScriptInfo from .cli import ScriptInfo
from .json import dumps as json_dumps from .json import dumps as json_dumps
from .sessions import SessionMixin
if t.TYPE_CHECKING:
from .app import Flask
from .wrappers import Response
class EnvironBuilder(werkzeug.test.EnvironBuilder): class EnvironBuilder(werkzeug.test.EnvironBuilder):
@ -43,14 +46,14 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
def __init__( def __init__(
self, self,
app: "Flask", app,
path: str = "/", path="/",
base_url: t.Optional[str] = None, base_url=None,
subdomain: t.Optional[str] = None, subdomain=None,
url_scheme: t.Optional[str] = None, url_scheme=None,
*args: t.Any, *args,
**kwargs: t.Any, **kwargs
) -> None: ):
assert not (base_url or subdomain or url_scheme) or ( assert not (base_url or subdomain or url_scheme) or (
base_url is not None base_url is not None
) != bool( ) != bool(
@ -62,15 +65,16 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
app_root = app.config["APPLICATION_ROOT"] app_root = app.config["APPLICATION_ROOT"]
if subdomain: if subdomain:
http_host = f"{subdomain}.{http_host}" http_host = "{0}.{1}".format(subdomain, http_host)
if url_scheme is None: if url_scheme is None:
url_scheme = app.config["PREFERRED_URL_SCHEME"] url_scheme = app.config["PREFERRED_URL_SCHEME"]
url = url_parse(path) url = url_parse(path)
base_url = ( base_url = "{scheme}://{netloc}/{path}".format(
f"{url.scheme or url_scheme}://{url.netloc or http_host}" scheme=url.scheme or url_scheme,
f"/{app_root.lstrip('/')}" netloc=url.netloc or http_host,
path=app_root.lstrip("/"),
) )
path = url.path path = url.path
@ -79,9 +83,9 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
path += sep + url.query path += sep + url.query
self.app = app self.app = app
super().__init__(path, base_url, *args, **kwargs) super(EnvironBuilder, self).__init__(path, base_url, *args, **kwargs)
def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore def json_dumps(self, obj, **kwargs):
"""Serialize ``obj`` to a JSON-formatted string. """Serialize ``obj`` to a JSON-formatted string.
The serialization will be configured according to the config associated The serialization will be configured according to the config associated
@ -91,6 +95,23 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
return json_dumps(obj, **kwargs) return json_dumps(obj, **kwargs)
def make_test_environ_builder(*args, **kwargs):
"""Create a :class:`flask.testing.EnvironBuilder`.
.. deprecated: 1.1
Will be removed in 2.0. Construct
``flask.testing.EnvironBuilder`` directly instead.
"""
warnings.warn(
DeprecationWarning(
'"make_test_environ_builder()" is deprecated and will be'
' removed in 2.0. Construct "flask.testing.EnvironBuilder"'
" directly instead."
)
)
return EnvironBuilder(*args, **kwargs)
class FlaskClient(Client): class FlaskClient(Client):
"""Works like a regular Werkzeug test client but has some knowledge about """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 how Flask works to defer the cleanup of the request context stack to the
@ -103,23 +124,20 @@ class FlaskClient(Client):
set after instantiation of the `app.test_client()` object in set after instantiation of the `app.test_client()` object in
`client.environ_base`. `client.environ_base`.
Basic usage is outlined in the :doc:`/testing` chapter. Basic usage is outlined in the :ref:`testing` chapter.
""" """
application: "Flask"
preserve_context = False preserve_context = False
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super(FlaskClient, self).__init__(*args, **kwargs)
self.environ_base = { self.environ_base = {
"REMOTE_ADDR": "127.0.0.1", "REMOTE_ADDR": "127.0.0.1",
"HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}", "HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__,
} }
@contextmanager @contextmanager
def session_transaction( def session_transaction(self, *args, **kwargs):
self, *args: t.Any, **kwargs: t.Any
) -> t.Generator[SessionMixin, None, None]:
"""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 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
@ -171,64 +189,51 @@ class FlaskClient(Client):
headers = resp.get_wsgi_headers(c.request.environ) headers = resp.get_wsgi_headers(c.request.environ)
self.cookie_jar.extract_wsgi(c.request.environ, headers) self.cookie_jar.extract_wsgi(c.request.environ, headers)
def open( # type: ignore def open(self, *args, **kwargs):
self, as_tuple = kwargs.pop("as_tuple", False)
*args: t.Any, buffered = kwargs.pop("buffered", False)
as_tuple: bool = False, follow_redirects = kwargs.pop("follow_redirects", False)
buffered: bool = False,
follow_redirects: bool = False,
**kwargs: t.Any,
) -> "Response":
# Same logic as super.open, but apply environ_base and preserve_context.
request = None
def copy_environ(other): if (
return { not kwargs
**self.environ_base, and len(args) == 1
**other, and isinstance(args[0], (werkzeug.test.EnvironBuilder, dict))
"flask._preserve_context": self.preserve_context, ):
} environ = self.environ_base.copy()
if not kwargs and len(args) == 1: if isinstance(args[0], werkzeug.test.EnvironBuilder):
arg = args[0] environ.update(args[0].get_environ())
else:
environ.update(args[0])
if isinstance(arg, werkzeug.test.EnvironBuilder): environ["flask._preserve_context"] = self.preserve_context
builder = copy(arg) else:
builder.environ_base = copy_environ(builder.environ_base or {}) kwargs.setdefault("environ_overrides", {})[
request = builder.get_request() "flask._preserve_context"
elif isinstance(arg, dict): ] = self.preserve_context
request = EnvironBuilder.from_environ( kwargs.setdefault("environ_base", self.environ_base)
arg, app=self.application, environ_base=copy_environ({})
).get_request()
elif isinstance(arg, BaseRequest):
request = copy(arg)
request.environ = copy_environ(request.environ)
if request is None:
kwargs["environ_base"] = copy_environ(kwargs.get("environ_base", {}))
builder = EnvironBuilder(self.application, *args, **kwargs) builder = EnvironBuilder(self.application, *args, **kwargs)
try: try:
request = builder.get_request() environ = builder.get_environ()
finally: finally:
builder.close() builder.close()
return super().open( # type: ignore return Client.open(
request, self,
environ,
as_tuple=as_tuple, as_tuple=as_tuple,
buffered=buffered, buffered=buffered,
follow_redirects=follow_redirects, follow_redirects=follow_redirects,
) )
def __enter__(self) -> "FlaskClient": def __enter__(self):
if self.preserve_context: if self.preserve_context:
raise RuntimeError("Cannot nest client invocations") raise RuntimeError("Cannot nest client invocations")
self.preserve_context = True self.preserve_context = True
return self return self
def __exit__( def __exit__(self, exc_type, exc_value, tb):
self, exc_type: type, exc_value: BaseException, tb: TracebackType
) -> None:
self.preserve_context = False self.preserve_context = False
# Normally the request context is preserved until the next # Normally the request context is preserved until the next
@ -250,13 +255,11 @@ class FlaskCliRunner(CliRunner):
:meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
""" """
def __init__(self, app: "Flask", **kwargs: t.Any) -> None: def __init__(self, app, **kwargs):
self.app = app self.app = app
super().__init__(**kwargs) super(FlaskCliRunner, self).__init__(**kwargs)
def invoke( # type: ignore def invoke(self, cli=None, args=None, **kwargs):
self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any
) -> t.Any:
"""Invokes a CLI command in an isolated environment. See """Invokes a CLI command in an isolated environment. See
:meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for :meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for
full method documentation. See :ref:`testing-cli` for examples. full method documentation. See :ref:`testing-cli` for examples.
@ -277,4 +280,4 @@ class FlaskCliRunner(CliRunner):
if "obj" not in kwargs: if "obj" not in kwargs:
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app) kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
return super().invoke(cli, args, **kwargs) return super(FlaskCliRunner, self).invoke(cli, args, **kwargs)

View file

@ -1,7 +1,15 @@
import typing as t # -*- coding: utf-8 -*-
"""
flask.views
~~~~~~~~~~~
This module provides class-based views inspired by the ones in Django.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from ._compat import with_metaclass
from .globals import request from .globals import request
from .typing import ResponseReturnValue
http_method_funcs = frozenset( http_method_funcs = frozenset(
@ -9,7 +17,7 @@ http_method_funcs = frozenset(
) )
class View: class View(object):
"""Alternative way to use view functions. A subclass has to implement """Alternative way to use view functions. A subclass has to implement
:meth:`dispatch_request` which is called with the view arguments from :meth:`dispatch_request` which is called with the view arguments from
the URL routing system. If :attr:`methods` is provided the methods the URL routing system. If :attr:`methods` is provided the methods
@ -20,7 +28,7 @@ class View:
methods = ['GET'] methods = ['GET']
def dispatch_request(self, name): def dispatch_request(self, name):
return f"Hello {name}!" return 'Hello %s!' % name
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview')) app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
@ -42,10 +50,10 @@ class View:
""" """
#: A list of methods this view can handle. #: A list of methods this view can handle.
methods: t.Optional[t.List[str]] = None methods = None
#: Setting this disables or force-enables the automatic options handling. #: Setting this disables or force-enables the automatic options handling.
provide_automatic_options: t.Optional[bool] = None provide_automatic_options = None
#: The canonical way to decorate class-based views is to decorate the #: The canonical way to decorate class-based views is to decorate the
#: return value of as_view(). However since this moves parts of the #: return value of as_view(). However since this moves parts of the
@ -56,9 +64,9 @@ class View:
#: view function is created the result is automatically decorated. #: view function is created the result is automatically decorated.
#: #:
#: .. versionadded:: 0.8 #: .. versionadded:: 0.8
decorators: t.List[t.Callable] = [] decorators = ()
def dispatch_request(self) -> ResponseReturnValue: def dispatch_request(self):
"""Subclasses have to override this method to implement the """Subclasses have to override this method to implement the
actual view function code. This method is called with all actual view function code. This method is called with all
the arguments from the URL rule. the arguments from the URL rule.
@ -66,9 +74,7 @@ class View:
raise NotImplementedError() raise NotImplementedError()
@classmethod @classmethod
def as_view( def as_view(cls, name, *class_args, **class_kwargs):
cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
) -> t.Callable:
"""Converts the class into an actual view function that can be used """Converts the class into an actual view function that can be used
with the routing system. Internally this generates a function on the with the routing system. Internally this generates a function on the
fly which will instantiate the :class:`View` on each request and call fly which will instantiate the :class:`View` on each request and call
@ -78,8 +84,8 @@ class View:
constructor of the class. constructor of the class.
""" """
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs) # type: ignore self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs) return self.dispatch_request(*args, **kwargs)
if cls.decorators: if cls.decorators:
@ -93,12 +99,12 @@ class View:
# view this thing came from, secondly it's also used for instantiating # view this thing came from, secondly it's also used for instantiating
# the view class so you can actually replace it with something else # the view class so you can actually replace it with something else
# for testing purposes and debugging. # for testing purposes and debugging.
view.view_class = cls # type: ignore view.view_class = cls
view.__name__ = name view.__name__ = name
view.__doc__ = cls.__doc__ view.__doc__ = cls.__doc__
view.__module__ = cls.__module__ view.__module__ = cls.__module__
view.methods = cls.methods # type: ignore view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options # type: ignore view.provide_automatic_options = cls.provide_automatic_options
return view return view
@ -108,7 +114,7 @@ class MethodViewType(type):
""" """
def __init__(cls, name, bases, d): def __init__(cls, name, bases, d):
super().__init__(name, bases, d) super(MethodViewType, cls).__init__(name, bases, d)
if "methods" not in d: if "methods" not in d:
methods = set() methods = set()
@ -129,7 +135,7 @@ class MethodViewType(type):
cls.methods = methods cls.methods = methods
class MethodView(View, metaclass=MethodViewType): class MethodView(with_metaclass(MethodViewType, View)):
"""A class-based view that dispatches request methods to the corresponding """A class-based view that dispatches request methods to the corresponding
class methods. For example, if you implement a ``get`` method, it will be class methods. For example, if you implement a ``get`` method, it will be
used to handle ``GET`` requests. :: used to handle ``GET`` requests. ::
@ -145,7 +151,7 @@ class MethodView(View, metaclass=MethodViewType):
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter')) app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
""" """
def dispatch_request(self, *args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None) meth = getattr(self, request.method.lower(), None)
# If the request method is HEAD and we don't have a handler for it # If the request method is HEAD and we don't have a handler for it
@ -153,5 +159,5 @@ class MethodView(View, metaclass=MethodViewType):
if meth is None and request.method == "HEAD": if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None) meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}" assert meth is not None, "Unimplemented method %r" % request.method
return meth(*args, **kwargs) return meth(*args, **kwargs)

View file

@ -1,19 +1,33 @@
import typing as t # -*- coding: utf-8 -*-
"""
flask.wrappers
~~~~~~~~~~~~~~
Implements the WSGI wrappers (request and response).
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Request as RequestBase
from werkzeug.wrappers import Response as ResponseBase from werkzeug.wrappers import Response as ResponseBase
from werkzeug.wrappers.json import JSONMixin as _JSONMixin
from . import json from . import json
from .globals import current_app from .globals import current_app
from .helpers import _split_blueprint_path
if t.TYPE_CHECKING:
import typing_extensions as te
from werkzeug.routing import Rule
class Request(RequestBase): class JSONMixin(_JSONMixin):
json_module = json
def on_json_loading_failed(self, e):
if current_app and current_app.debug:
raise BadRequest("Failed to decode JSON object: {0}".format(e))
raise BadRequest()
class Request(RequestBase, JSONMixin):
"""The request object used by default in Flask. Remembers the """The request object used by default in Flask. Remembers the
matched endpoint and view arguments. matched endpoint and view arguments.
@ -26,8 +40,6 @@ class Request(RequestBase):
specific ones. specific ones.
""" """
json_module = json
#: The internal URL rule that matched the request. This can be #: The internal URL rule that matched the request. This can be
#: useful to inspect which methods are allowed for the URL from #: useful to inspect which methods are allowed for the URL from
#: a before/after handler (``request.url_rule.methods``) etc. #: a before/after handler (``request.url_rule.methods``) etc.
@ -38,78 +50,41 @@ class Request(RequestBase):
#: because the request was never internally bound. #: because the request was never internally bound.
#: #:
#: .. versionadded:: 0.6 #: .. versionadded:: 0.6
url_rule: t.Optional["Rule"] = None url_rule = None
#: A dict of view arguments that matched the request. If an exception #: A dict of view arguments that matched the request. If an exception
#: happened when matching, this will be ``None``. #: happened when matching, this will be ``None``.
view_args: t.Optional[t.Dict[str, t.Any]] = None view_args = None
#: If matching the URL failed, this is the exception that will be #: If matching the URL failed, this is the exception that will be
#: raised / was raised as part of the request handling. This is #: raised / was raised as part of the request handling. This is
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
#: something similar. #: something similar.
routing_exception: t.Optional[Exception] = None routing_exception = None
@property @property
def max_content_length(self) -> t.Optional[int]: # type: ignore def max_content_length(self):
"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key.""" """Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
if current_app: if current_app:
return current_app.config["MAX_CONTENT_LENGTH"] return current_app.config["MAX_CONTENT_LENGTH"]
else:
return None
@property @property
def endpoint(self) -> t.Optional[str]: def endpoint(self):
"""The endpoint that matched the request URL. """The endpoint that matched the request. This in combination with
:attr:`view_args` can be used to reconstruct the same or a
This will be ``None`` if matching failed or has not been modified URL. If an exception happened when matching, this will
performed yet. be ``None``.
This in combination with :attr:`view_args` can be used to
reconstruct the same URL or a modified URL.
""" """
if self.url_rule is not None: if self.url_rule is not None:
return self.url_rule.endpoint return self.url_rule.endpoint
return None
@property @property
def blueprint(self) -> t.Optional[str]: def blueprint(self):
"""The registered name of the current blueprint. """The name of the current blueprint"""
if self.url_rule and "." in self.url_rule.endpoint:
return self.url_rule.endpoint.rsplit(".", 1)[0]
This will be ``None`` if the endpoint is not part of a def _load_form_data(self):
blueprint, or if URL matching failed or has not been performed
yet.
This does not necessarily match the name the blueprint was
created with. It may have been nested, or registered with a
different name.
"""
endpoint = self.endpoint
if endpoint is not None and "." in endpoint:
return endpoint.rpartition(".")[0]
return None
@property
def blueprints(self) -> t.List[str]:
"""The registered names of the current blueprint upwards through
parent blueprints.
This will be an empty list if there is no current blueprint, or
if URL matching failed.
.. versionadded:: 2.0.1
"""
name = self.blueprint
if name is None:
return []
return _split_blueprint_path(name)
def _load_form_data(self) -> None:
RequestBase._load_form_data(self) RequestBase._load_form_data(self)
# In debug mode we're replacing the files multidict with an ad-hoc # In debug mode we're replacing the files multidict with an ad-hoc
@ -124,14 +99,8 @@ class Request(RequestBase):
attach_enctype_error_multidict(self) attach_enctype_error_multidict(self)
def on_json_loading_failed(self, e: Exception) -> "te.NoReturn":
if current_app and current_app.debug:
raise BadRequest(f"Failed to decode JSON object: {e}")
raise BadRequest() class Response(ResponseBase, JSONMixin):
class Response(ResponseBase):
"""The response object that is used by default in Flask. Works like the """The response object that is used by default in Flask. Works like the
response object from Werkzeug but is set to have an HTML mimetype by response object from Werkzeug but is set to have an HTML mimetype by
default. Quite often you don't have to create this object yourself because default. Quite often you don't have to create this object yourself because
@ -151,17 +120,18 @@ class Response(ResponseBase):
default_mimetype = "text/html" default_mimetype = "text/html"
json_module = json def _get_data_for_json(self, cache):
return self.get_data()
@property @property
def max_cookie_size(self) -> int: # type: ignore def max_cookie_size(self):
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key. """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in See :attr:`~werkzeug.wrappers.BaseResponse.max_cookie_size` in
Werkzeug's docs. Werkzeug's docs.
""" """
if current_app: if current_app:
return current_app.config["MAX_COOKIE_SIZE"] return current_app.config["MAX_COOKIE_SIZE"]
# return Werkzeug's default when not in an app context # return Werkzeug's default when not in an app context
return super().max_cookie_size return super(Response, self).max_cookie_size