parent
b9651d1ed0
commit
11128bee14
@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
A microframework based on Werkzeug. It's extensively documented
|
||||||
|
and follows best practice patterns.
|
||||||
|
|
||||||
|
: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.1"
|
@ -0,0 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.__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)
|
@ -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
@ -0,0 +1,569 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
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 .helpers import _endpoint_from_view_func
|
||||||
|
from .helpers import _PackageBoundObject
|
||||||
|
|
||||||
|
# a singleton sentinel value for parameter defaults
|
||||||
|
_sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
|
class BlueprintSetupState(object):
|
||||||
|
"""Temporary holder object for registering a blueprint with the
|
||||||
|
application. An instance of this class is created by the
|
||||||
|
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
||||||
|
to all register callback functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, blueprint, app, options, first_registration):
|
||||||
|
#: a reference to the current application
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
#: a reference to the blueprint that created this setup state.
|
||||||
|
self.blueprint = blueprint
|
||||||
|
|
||||||
|
#: a dictionary with all options that were passed to the
|
||||||
|
#: :meth:`~flask.Flask.register_blueprint` method.
|
||||||
|
self.options = options
|
||||||
|
|
||||||
|
#: as blueprints can be registered multiple times with the
|
||||||
|
#: application and not everything wants to be registered
|
||||||
|
#: multiple times on it, this attribute can be used to figure
|
||||||
|
#: out if the blueprint was registered in the past already.
|
||||||
|
self.first_registration = first_registration
|
||||||
|
|
||||||
|
subdomain = self.options.get("subdomain")
|
||||||
|
if subdomain is None:
|
||||||
|
subdomain = self.blueprint.subdomain
|
||||||
|
|
||||||
|
#: The subdomain that the blueprint should be active for, ``None``
|
||||||
|
#: otherwise.
|
||||||
|
self.subdomain = subdomain
|
||||||
|
|
||||||
|
url_prefix = self.options.get("url_prefix")
|
||||||
|
if url_prefix is None:
|
||||||
|
url_prefix = self.blueprint.url_prefix
|
||||||
|
#: The prefix that should be used for all URLs defined on the
|
||||||
|
#: blueprint.
|
||||||
|
self.url_prefix = url_prefix
|
||||||
|
|
||||||
|
#: A dictionary with URL defaults that is added to each and every
|
||||||
|
#: URL that was defined with the blueprint.
|
||||||
|
self.url_defaults = dict(self.blueprint.url_values_defaults)
|
||||||
|
self.url_defaults.update(self.options.get("url_defaults", ()))
|
||||||
|
|
||||||
|
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||||
|
"""A helper method to register a rule (and optionally a view function)
|
||||||
|
to the application. The endpoint is automatically prefixed with the
|
||||||
|
blueprint's name.
|
||||||
|
"""
|
||||||
|
if self.url_prefix is not None:
|
||||||
|
if rule:
|
||||||
|
rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
|
||||||
|
else:
|
||||||
|
rule = self.url_prefix
|
||||||
|
options.setdefault("subdomain", self.subdomain)
|
||||||
|
if endpoint is None:
|
||||||
|
endpoint = _endpoint_from_view_func(view_func)
|
||||||
|
defaults = self.url_defaults
|
||||||
|
if "defaults" in options:
|
||||||
|
defaults = dict(defaults, **options.pop("defaults"))
|
||||||
|
self.app.add_url_rule(
|
||||||
|
rule,
|
||||||
|
"%s.%s" % (self.blueprint.name, endpoint),
|
||||||
|
view_func,
|
||||||
|
defaults=defaults,
|
||||||
|
**options
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Blueprint(_PackageBoundObject):
|
||||||
|
"""Represents a blueprint, a collection of routes and other
|
||||||
|
app-related functions that can be registered on a real application
|
||||||
|
later.
|
||||||
|
|
||||||
|
A blueprint is an object that allows defining application functions
|
||||||
|
without requiring an application object ahead of time. It uses the
|
||||||
|
same decorators as :class:`~flask.Flask`, but defers the need for an
|
||||||
|
application by recording them for later registration.
|
||||||
|
|
||||||
|
Decorating a function with a blueprint creates a deferred function
|
||||||
|
that is called with :class:`~flask.blueprints.BlueprintSetupState`
|
||||||
|
when the blueprint is registered on an application.
|
||||||
|
|
||||||
|
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
|
||||||
|
endpoint name.
|
||||||
|
:param import_name: The name of the blueprint package, usually
|
||||||
|
``__name__``. This helps locate the ``root_path`` for the
|
||||||
|
blueprint.
|
||||||
|
:param static_folder: A folder with static files that should be
|
||||||
|
served by the blueprint's static route. The path is relative to
|
||||||
|
the blueprint's root path. Blueprint static files are disabled
|
||||||
|
by default.
|
||||||
|
:param static_url_path: The url to serve static files from.
|
||||||
|
Defaults to ``static_folder``. If the blueprint does not have
|
||||||
|
a ``url_prefix``, the app's static route will take precedence,
|
||||||
|
and the blueprint's static files won't be accessible.
|
||||||
|
:param template_folder: A folder with templates that should be added
|
||||||
|
to the app's template search path. The path is relative to the
|
||||||
|
blueprint's root path. Blueprint templates are disabled by
|
||||||
|
default. Blueprint templates have a lower precedence than those
|
||||||
|
in the app's templates folder.
|
||||||
|
:param url_prefix: A path to prepend to all of the blueprint's URLs,
|
||||||
|
to make them distinct from the rest of the app's routes.
|
||||||
|
:param subdomain: A subdomain that blueprint routes will match on by
|
||||||
|
default.
|
||||||
|
:param url_defaults: A dict of default values that blueprint routes
|
||||||
|
will receive by default.
|
||||||
|
:param root_path: By default, the blueprint will automatically this
|
||||||
|
based on ``import_name``. In certain situations this automatic
|
||||||
|
detection can fail, so the path can be specified manually
|
||||||
|
instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
warn_on_modifications = False
|
||||||
|
_got_registered_once = False
|
||||||
|
|
||||||
|
#: Blueprint local JSON decoder class to use.
|
||||||
|
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
|
||||||
|
json_encoder = None
|
||||||
|
#: Blueprint local JSON decoder class to use.
|
||||||
|
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
|
||||||
|
json_decoder = None
|
||||||
|
|
||||||
|
# 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__(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
import_name,
|
||||||
|
static_folder=None,
|
||||||
|
static_url_path=None,
|
||||||
|
template_folder=None,
|
||||||
|
url_prefix=None,
|
||||||
|
subdomain=None,
|
||||||
|
url_defaults=None,
|
||||||
|
root_path=None,
|
||||||
|
cli_group=_sentinel,
|
||||||
|
):
|
||||||
|
_PackageBoundObject.__init__(
|
||||||
|
self, import_name, template_folder, root_path=root_path
|
||||||
|
)
|
||||||
|
self.name = name
|
||||||
|
self.url_prefix = url_prefix
|
||||||
|
self.subdomain = subdomain
|
||||||
|
self.static_folder = static_folder
|
||||||
|
self.static_url_path = static_url_path
|
||||||
|
self.deferred_functions = []
|
||||||
|
if url_defaults is None:
|
||||||
|
url_defaults = {}
|
||||||
|
self.url_values_defaults = url_defaults
|
||||||
|
self.cli_group = cli_group
|
||||||
|
|
||||||
|
def record(self, func):
|
||||||
|
"""Registers a function that is called when the blueprint is
|
||||||
|
registered on the application. This function is called with the
|
||||||
|
state as argument as returned by the :meth:`make_setup_state`
|
||||||
|
method.
|
||||||
|
"""
|
||||||
|
if self._got_registered_once and self.warn_on_modifications:
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
warn(
|
||||||
|
Warning(
|
||||||
|
"The blueprint was already registered once "
|
||||||
|
"but is getting modified now. These changes "
|
||||||
|
"will not show up."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.deferred_functions.append(func)
|
||||||
|
|
||||||
|
def record_once(self, func):
|
||||||
|
"""Works like :meth:`record` but wraps the function in another
|
||||||
|
function that will ensure the function is only called once. If the
|
||||||
|
blueprint is registered a second time on the application, the
|
||||||
|
function passed is not called.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def wrapper(state):
|
||||||
|
if state.first_registration:
|
||||||
|
func(state)
|
||||||
|
|
||||||
|
return self.record(update_wrapper(wrapper, func))
|
||||||
|
|
||||||
|
def make_setup_state(self, app, options, first_registration=False):
|
||||||
|
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
|
||||||
|
object that is later passed to the register callback functions.
|
||||||
|
Subclasses can override this to return a subclass of the setup state.
|
||||||
|
"""
|
||||||
|
return BlueprintSetupState(self, app, options, first_registration)
|
||||||
|
|
||||||
|
def register(self, app, options, first_registration=False):
|
||||||
|
"""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
|
||||||
|
:meth:`~Flask.register_blueprint`.
|
||||||
|
:param first_registration: Whether this is the first time this
|
||||||
|
blueprint has been registered on the application.
|
||||||
|
"""
|
||||||
|
self._got_registered_once = True
|
||||||
|
state = self.make_setup_state(app, options, first_registration)
|
||||||
|
|
||||||
|
if self.has_static_folder:
|
||||||
|
state.add_url_rule(
|
||||||
|
self.static_url_path + "/<path:filename>",
|
||||||
|
view_func=self.send_static_file,
|
||||||
|
endpoint="static",
|
||||||
|
)
|
||||||
|
|
||||||
|
for deferred in self.deferred_functions:
|
||||||
|
deferred(state)
|
||||||
|
|
||||||
|
cli_resolved_group = options.get("cli_group", self.cli_group)
|
||||||
|
|
||||||
|
if not self.cli.commands:
|
||||||
|
return
|
||||||
|
|
||||||
|
if cli_resolved_group is None:
|
||||||
|
app.cli.commands.update(self.cli.commands)
|
||||||
|
elif cli_resolved_group is _sentinel:
|
||||||
|
self.cli.name = self.name
|
||||||
|
app.cli.add_command(self.cli)
|
||||||
|
else:
|
||||||
|
self.cli.name = cli_resolved_group
|
||||||
|
app.cli.add_command(self.cli)
|
||||||
|
|
||||||
|
def route(self, rule, **options):
|
||||||
|
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
|
||||||
|
:func:`url_for` function is prefixed with the name of the blueprint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
endpoint = options.pop("endpoint", f.__name__)
|
||||||
|
self.add_url_rule(rule, endpoint, f, **options)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||||
|
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
||||||
|
the :func:`url_for` function is prefixed with the name of the blueprint.
|
||||||
|
"""
|
||||||
|
if endpoint:
|
||||||
|
assert "." not in endpoint, "Blueprint 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))
|
||||||
|
|
||||||
|
def endpoint(self, endpoint):
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
def register_endpoint(state):
|
||||||
|
state.app.view_functions[endpoint] = f
|
||||||
|
|
||||||
|
self.record_once(register_endpoint)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def app_template_filter(self, name=None):
|
||||||
|
"""Register a custom template filter, available application wide. Like
|
||||||
|
:meth:`Flask.template_filter` but for a blueprint.
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
self.add_app_template_filter(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def add_app_template_filter(self, f, name=None):
|
||||||
|
"""Register a custom template filter, available application wide. Like
|
||||||
|
:meth:`Flask.add_template_filter` but for a blueprint. Works exactly
|
||||||
|
like the :meth:`app_template_filter` decorator.
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_template(state):
|
||||||
|
state.app.jinja_env.filters[name or f.__name__] = f
|
||||||
|
|
||||||
|
self.record_once(register_template)
|
||||||
|
|
||||||
|
def app_template_test(self, name=None):
|
||||||
|
"""Register a custom template test, available application wide. Like
|
||||||
|
:meth:`Flask.template_test` but for a blueprint.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the test, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
self.add_app_template_test(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def add_app_template_test(self, f, name=None):
|
||||||
|
"""Register a custom template test, available application wide. Like
|
||||||
|
:meth:`Flask.add_template_test` but for a blueprint. Works exactly
|
||||||
|
like the :meth:`app_template_test` decorator.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the test, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_template(state):
|
||||||
|
state.app.jinja_env.tests[name or f.__name__] = f
|
||||||
|
|
||||||
|
self.record_once(register_template)
|
||||||
|
|
||||||
|
def app_template_global(self, name=None):
|
||||||
|
"""Register a custom template global, available application wide. Like
|
||||||
|
:meth:`Flask.template_global` but for a blueprint.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the global, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
self.add_app_template_global(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def add_app_template_global(self, f, name=None):
|
||||||
|
"""Register a custom template global, available application wide. Like
|
||||||
|
:meth:`Flask.add_template_global` but for a blueprint. Works exactly
|
||||||
|
like the :meth:`app_template_global` decorator.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the global, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_template(state):
|
||||||
|
state.app.jinja_env.globals[name or f.__name__] = f
|
||||||
|
|
||||||
|
self.record_once(register_template)
|
||||||
|
|
||||||
|
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
|
||||||
|
before each request, even if outside of a blueprint.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def before_app_first_request(self, f):
|
||||||
|
"""Like :meth:`Flask.before_first_request`. Such a function is
|
||||||
|
executed before the first request to the application.
|
||||||
|
"""
|
||||||
|
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
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
|
||||||
|
is executed after each request, even if outside of the blueprint.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
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
|
||||||
|
function is executed when tearing down each request, even if outside of
|
||||||
|
the blueprint.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def context_processor(self, f):
|
||||||
|
"""Like :meth:`Flask.context_processor` but for a blueprint. This
|
||||||
|
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
|
||||||
|
function is executed each request, even if outside of the blueprint.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def app_errorhandler(self, code):
|
||||||
|
"""Like :meth:`Flask.errorhandler` but for a blueprint. This
|
||||||
|
handler is used for all requests, even if outside of the blueprint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def url_value_preprocessor(self, f):
|
||||||
|
"""Registers a function as URL value preprocessor for this
|
||||||
|
blueprint. It's called before the view functions are called and
|
||||||
|
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(
|
||||||
|
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def app_url_defaults(self, f):
|
||||||
|
"""Same as :meth:`url_defaults` but application wide.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.url_default_functions.setdefault(None, []).append(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)
|
||||||
|
)
|
@ -0,0 +1,970 @@
|
|||||||
|
# -*- 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 inspect
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from functools import update_wrapper
|
||||||
|
from operator import attrgetter
|
||||||
|
from threading import Lock
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import click
|
||||||
|
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 .helpers import get_debug_flag
|
||||||
|
from .helpers import get_env
|
||||||
|
from .helpers import get_load_dotenv
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dotenv
|
||||||
|
except ImportError:
|
||||||
|
dotenv = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ssl
|
||||||
|
except ImportError:
|
||||||
|
ssl = None
|
||||||
|
|
||||||
|
|
||||||
|
class NoAppException(click.UsageError):
|
||||||
|
"""Raised if an application cannot be found or loaded."""
|
||||||
|
|
||||||
|
|
||||||
|
def find_best_app(script_info, module):
|
||||||
|
"""Given a module instance this tries to find the best possible
|
||||||
|
application in the module or raises an exception.
|
||||||
|
"""
|
||||||
|
from . import Flask
|
||||||
|
|
||||||
|
# Search for the most common names first.
|
||||||
|
for attr_name in ("app", "application"):
|
||||||
|
app = getattr(module, attr_name, None)
|
||||||
|
|
||||||
|
if isinstance(app, Flask):
|
||||||
|
return app
|
||||||
|
|
||||||
|
# Otherwise find the only object that is a Flask instance.
|
||||||
|
matches = [v for v in itervalues(module.__dict__) if isinstance(v, Flask)]
|
||||||
|
|
||||||
|
if len(matches) == 1:
|
||||||
|
return matches[0]
|
||||||
|
elif len(matches) > 1:
|
||||||
|
raise NoAppException(
|
||||||
|
'Detected multiple Flask applications in module "{module}". Use '
|
||||||
|
'"FLASK_APP={module}:name" to specify the correct '
|
||||||
|
"one.".format(module=module.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Search for app factory functions.
|
||||||
|
for attr_name in ("create_app", "make_app"):
|
||||||
|
app_factory = getattr(module, attr_name, None)
|
||||||
|
|
||||||
|
if inspect.isfunction(app_factory):
|
||||||
|
try:
|
||||||
|
app = call_factory(script_info, app_factory)
|
||||||
|
|
||||||
|
if isinstance(app, Flask):
|
||||||
|
return app
|
||||||
|
except TypeError:
|
||||||
|
if not _called_with_wrong_args(app_factory):
|
||||||
|
raise
|
||||||
|
raise NoAppException(
|
||||||
|
'Detected factory "{factory}" in module "{module}", but '
|
||||||
|
"could not call it without arguments. Use "
|
||||||
|
"\"FLASK_APP='{module}:{factory}(args)'\" to specify "
|
||||||
|
"arguments.".format(factory=attr_name, module=module.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
raise NoAppException(
|
||||||
|
'Failed to find Flask application or factory in module "{module}". '
|
||||||
|
'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def call_factory(script_info, app_factory, arguments=()):
|
||||||
|
"""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
|
||||||
|
the app_factory depending on that and the arguments provided.
|
||||||
|
"""
|
||||||
|
args_spec = getargspec(app_factory)
|
||||||
|
arg_names = args_spec.args
|
||||||
|
arg_defaults = args_spec.defaults
|
||||||
|
|
||||||
|
if "script_info" in arg_names:
|
||||||
|
return app_factory(*arguments, script_info=script_info)
|
||||||
|
elif arguments:
|
||||||
|
return app_factory(*arguments)
|
||||||
|
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
|
||||||
|
return app_factory(script_info)
|
||||||
|
|
||||||
|
return app_factory()
|
||||||
|
|
||||||
|
|
||||||
|
def _called_with_wrong_args(factory):
|
||||||
|
"""Check whether calling a function raised a ``TypeError`` because
|
||||||
|
the call failed or because something in the factory raised the
|
||||||
|
error.
|
||||||
|
|
||||||
|
:param factory: the factory function that was called
|
||||||
|
:return: true if the call failed
|
||||||
|
"""
|
||||||
|
tb = sys.exc_info()[2]
|
||||||
|
|
||||||
|
try:
|
||||||
|
while tb is not None:
|
||||||
|
if tb.tb_frame.f_code is factory.__code__:
|
||||||
|
# in the factory, it was called successfully
|
||||||
|
return False
|
||||||
|
|
||||||
|
tb = tb.tb_next
|
||||||
|
|
||||||
|
# didn't reach the factory
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
# explicitly delete tb as it is circular referenced
|
||||||
|
# https://docs.python.org/2/library/sys.html#sys.exc_info
|
||||||
|
del tb
|
||||||
|
|
||||||
|
|
||||||
|
def find_app_by_string(script_info, module, app_name):
|
||||||
|
"""Checks if the given string is a variable name or a function. If it is a
|
||||||
|
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
|
||||||
|
|
||||||
|
match = re.match(r"^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$", app_name)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
raise NoAppException(
|
||||||
|
'"{name}" is not a valid variable name or function '
|
||||||
|
"expression.".format(name=app_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
name, args = match.groups()
|
||||||
|
|
||||||
|
try:
|
||||||
|
attr = getattr(module, name)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise NoAppException(e.args[0])
|
||||||
|
|
||||||
|
if inspect.isfunction(attr):
|
||||||
|
if args:
|
||||||
|
try:
|
||||||
|
args = ast.literal_eval("({args},)".format(args=args))
|
||||||
|
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):
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise NoAppException(
|
||||||
|
'{e}\nThe factory "{app_name}" in module "{module}" could not '
|
||||||
|
"be called with the specified arguments.".format(
|
||||||
|
e=e, app_name=app_name, module=module.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
app = attr
|
||||||
|
|
||||||
|
if isinstance(app, Flask):
|
||||||
|
return app
|
||||||
|
|
||||||
|
raise NoAppException(
|
||||||
|
"A valid Flask application was not obtained from "
|
||||||
|
'"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_import(path):
|
||||||
|
"""Given a filename this will try to calculate the python path, add it
|
||||||
|
to the search path and return the actual module name that is expected.
|
||||||
|
"""
|
||||||
|
path = os.path.realpath(path)
|
||||||
|
|
||||||
|
fname, ext = os.path.splitext(path)
|
||||||
|
if ext == ".py":
|
||||||
|
path = fname
|
||||||
|
|
||||||
|
if os.path.basename(path) == "__init__":
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
|
||||||
|
module_name = []
|
||||||
|
|
||||||
|
# move up until outside package structure (no __init__.py)
|
||||||
|
while True:
|
||||||
|
path, name = os.path.split(path)
|
||||||
|
module_name.append(name)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(path, "__init__.py")):
|
||||||
|
break
|
||||||
|
|
||||||
|
if sys.path[0] != path:
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
return ".".join(module_name[::-1])
|
||||||
|
|
||||||
|
|
||||||
|
def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
||||||
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
|
||||||
|
try:
|
||||||
|
__import__(module_name)
|
||||||
|
except ImportError:
|
||||||
|
# Reraise the ImportError if it occurred within the imported module.
|
||||||
|
# Determine this by checking whether the trace has a depth > 1.
|
||||||
|
if sys.exc_info()[-1].tb_next:
|
||||||
|
raise NoAppException(
|
||||||
|
'While importing "{name}", an ImportError was raised:'
|
||||||
|
"\n\n{tb}".format(name=module_name, tb=traceback.format_exc())
|
||||||
|
)
|
||||||
|
elif raise_if_not_found:
|
||||||
|
raise NoAppException('Could not import "{name}".'.format(name=module_name))
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
module = sys.modules[module_name]
|
||||||
|
|
||||||
|
if app_name is None:
|
||||||
|
return find_best_app(script_info, module)
|
||||||
|
else:
|
||||||
|
return find_app_by_string(script_info, module, app_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_version(ctx, param, value):
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
|
||||||
|
import werkzeug
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
|
message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s"
|
||||||
|
click.echo(
|
||||||
|
message
|
||||||
|
% {
|
||||||
|
"python": platform.python_version(),
|
||||||
|
"flask": __version__,
|
||||||
|
"werkzeug": werkzeug.__version__,
|
||||||
|
},
|
||||||
|
color=ctx.color,
|
||||||
|
)
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
|
||||||
|
version_option = click.Option(
|
||||||
|
["--version"],
|
||||||
|
help="Show the flask version",
|
||||||
|
expose_value=False,
|
||||||
|
callback=get_version,
|
||||||
|
is_flag=True,
|
||||||
|
is_eager=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DispatchingApp(object):
|
||||||
|
"""Special application that dispatches to a Flask application which
|
||||||
|
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
|
||||||
|
of the Werkzeug debugger means that it shows up in the browser.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, loader, use_eager_loading=False):
|
||||||
|
self.loader = loader
|
||||||
|
self._app = None
|
||||||
|
self._lock = Lock()
|
||||||
|
self._bg_loading_exc_info = None
|
||||||
|
if use_eager_loading:
|
||||||
|
self._load_unlocked()
|
||||||
|
else:
|
||||||
|
self._load_in_background()
|
||||||
|
|
||||||
|
def _load_in_background(self):
|
||||||
|
def _load_app():
|
||||||
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
with self._lock:
|
||||||
|
try:
|
||||||
|
self._load_unlocked()
|
||||||
|
except Exception:
|
||||||
|
self._bg_loading_exc_info = sys.exc_info()
|
||||||
|
|
||||||
|
t = Thread(target=_load_app, args=())
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
def _flush_bg_loading_exception(self):
|
||||||
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
exc_info = self._bg_loading_exc_info
|
||||||
|
if exc_info is not None:
|
||||||
|
self._bg_loading_exc_info = None
|
||||||
|
reraise(*exc_info)
|
||||||
|
|
||||||
|
def _load_unlocked(self):
|
||||||
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
self._app = rv = self.loader()
|
||||||
|
self._bg_loading_exc_info = None
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
if self._app is not None:
|
||||||
|
return self._app(environ, start_response)
|
||||||
|
self._flush_bg_loading_exception()
|
||||||
|
with self._lock:
|
||||||
|
if self._app is not None:
|
||||||
|
rv = self._app
|
||||||
|
else:
|
||||||
|
rv = self._load_unlocked()
|
||||||
|
return rv(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptInfo(object):
|
||||||
|
"""Helper object to deal with Flask applications. This is usually not
|
||||||
|
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
|
||||||
|
a bigger role. Typically it's created automatically by the
|
||||||
|
:class:`FlaskGroup` but you can also manually create it and pass it
|
||||||
|
onwards as click object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app_import_path=None, create_app=None, set_debug_flag=True):
|
||||||
|
#: Optionally the import path for the Flask application.
|
||||||
|
self.app_import_path = app_import_path or os.environ.get("FLASK_APP")
|
||||||
|
#: Optionally a function that is passed the script info to create
|
||||||
|
#: the instance of the application.
|
||||||
|
self.create_app = create_app
|
||||||
|
#: A dictionary with arbitrary data that can be associated with
|
||||||
|
#: this script info.
|
||||||
|
self.data = {}
|
||||||
|
self.set_debug_flag = set_debug_flag
|
||||||
|
self._loaded_app = None
|
||||||
|
|
||||||
|
def load_app(self):
|
||||||
|
"""Loads the Flask app (if not yet loaded) and returns it. Calling
|
||||||
|
this multiple times will just result in the already loaded app to
|
||||||
|
be returned.
|
||||||
|
"""
|
||||||
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
|
||||||
|
if self._loaded_app is not None:
|
||||||
|
return self._loaded_app
|
||||||
|
|
||||||
|
app = None
|
||||||
|
|
||||||
|
if self.create_app is not None:
|
||||||
|
app = call_factory(self, self.create_app)
|
||||||
|
else:
|
||||||
|
if self.app_import_path:
|
||||||
|
path, name = (
|
||||||
|
re.split(r":(?![\\/])", self.app_import_path, 1) + [None]
|
||||||
|
)[:2]
|
||||||
|
import_name = prepare_import(path)
|
||||||
|
app = locate_app(self, import_name, name)
|
||||||
|
else:
|
||||||
|
for path in ("wsgi.py", "app.py"):
|
||||||
|
import_name = prepare_import(path)
|
||||||
|
app = locate_app(self, import_name, None, raise_if_not_found=False)
|
||||||
|
|
||||||
|
if app:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not app:
|
||||||
|
raise NoAppException(
|
||||||
|
"Could not locate a Flask application. You did not provide "
|
||||||
|
'the "FLASK_APP" environment variable, and a "wsgi.py" or '
|
||||||
|
'"app.py" module was not found in the current directory.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.set_debug_flag:
|
||||||
|
# Update the app's debug flag through the descriptor so that
|
||||||
|
# other values repopulate as well.
|
||||||
|
app.debug = get_debug_flag()
|
||||||
|
|
||||||
|
self._loaded_app = app
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
|
||||||
|
|
||||||
|
|
||||||
|
def with_appcontext(f):
|
||||||
|
"""Wraps a callback so that it's guaranteed to be executed with the
|
||||||
|
script's application context. If callbacks are registered directly
|
||||||
|
to the ``app.cli`` object then they are wrapped with this function
|
||||||
|
by default unless it's disabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@click.pass_context
|
||||||
|
def decorator(__ctx, *args, **kwargs):
|
||||||
|
with __ctx.ensure_object(ScriptInfo).load_app().app_context():
|
||||||
|
return __ctx.invoke(f, *args, **kwargs)
|
||||||
|
|
||||||
|
return update_wrapper(decorator, f)
|
||||||
|
|
||||||
|
|
||||||
|
class AppGroup(click.Group):
|
||||||
|
"""This works similar to a regular click :class:`~click.Group` but it
|
||||||
|
changes the behavior of the :meth:`command` decorator so that it
|
||||||
|
automatically wraps the functions in :func:`with_appcontext`.
|
||||||
|
|
||||||
|
Not to be confused with :class:`FlaskGroup`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def command(self, *args, **kwargs):
|
||||||
|
"""This works exactly like the method of the same name on a regular
|
||||||
|
:class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
|
||||||
|
unless it's disabled by passing ``with_appcontext=False``.
|
||||||
|
"""
|
||||||
|
wrap_for_ctx = kwargs.pop("with_appcontext", True)
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
if wrap_for_ctx:
|
||||||
|
f = with_appcontext(f)
|
||||||
|
return click.Group.command(self, *args, **kwargs)(f)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def group(self, *args, **kwargs):
|
||||||
|
"""This works exactly like the method of the same name on a regular
|
||||||
|
:class:`click.Group` but it defaults the group class to
|
||||||
|
:class:`AppGroup`.
|
||||||
|
"""
|
||||||
|
kwargs.setdefault("cls", AppGroup)
|
||||||
|
return click.Group.group(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FlaskGroup(AppGroup):
|
||||||
|
"""Special subclass of the :class:`AppGroup` group that supports
|
||||||
|
loading more commands from the configured Flask app. Normally a
|
||||||
|
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
|
||||||
|
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
|
||||||
|
shell commands will be added.
|
||||||
|
:param add_version_option: adds the ``--version`` option.
|
||||||
|
:param create_app: an optional callback that is passed the script info and
|
||||||
|
returns the loaded app.
|
||||||
|
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
|
||||||
|
files to set environment variables. Will also change the working
|
||||||
|
directory to the directory containing the first file found.
|
||||||
|
:param set_debug_flag: Set the app's debug flag based on the active
|
||||||
|
environment
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
If installed, python-dotenv will be used to load environment variables
|
||||||
|
from :file:`.env` and :file:`.flaskenv` files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
add_default_commands=True,
|
||||||
|
create_app=None,
|
||||||
|
add_version_option=True,
|
||||||
|
load_dotenv=True,
|
||||||
|
set_debug_flag=True,
|
||||||
|
**extra
|
||||||
|
):
|
||||||
|
params = list(extra.pop("params", None) or ())
|
||||||
|
|
||||||
|
if add_version_option:
|
||||||
|
params.append(version_option)
|
||||||
|
|
||||||
|
AppGroup.__init__(self, params=params, **extra)
|
||||||
|
self.create_app = create_app
|
||||||
|
self.load_dotenv = load_dotenv
|
||||||
|
self.set_debug_flag = set_debug_flag
|
||||||
|
|
||||||
|
if add_default_commands:
|
||||||
|
self.add_command(run_command)
|
||||||
|
self.add_command(shell_command)
|
||||||
|
self.add_command(routes_command)
|
||||||
|
|
||||||
|
self._loaded_plugin_commands = False
|
||||||
|
|
||||||
|
def _load_plugin_commands(self):
|
||||||
|
if self._loaded_plugin_commands:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import pkg_resources
|
||||||
|
except ImportError:
|
||||||
|
self._loaded_plugin_commands = True
|
||||||
|
return
|
||||||
|
|
||||||
|
for ep in pkg_resources.iter_entry_points("flask.commands"):
|
||||||
|
self.add_command(ep.load(), ep.name)
|
||||||
|
self._loaded_plugin_commands = True
|
||||||
|
|
||||||
|
def get_command(self, ctx, name):
|
||||||
|
self._load_plugin_commands()
|
||||||
|
|
||||||
|
# We load built-in commands first as these should always be the
|
||||||
|
# same no matter what the app does. If the app does want to
|
||||||
|
# override this it needs to make a custom instance of this group
|
||||||
|
# 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:
|
||||||
|
return rv
|
||||||
|
|
||||||
|
info = ctx.ensure_object(ScriptInfo)
|
||||||
|
try:
|
||||||
|
rv = info.load_app().cli.get_command(ctx, name)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
except NoAppException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def list_commands(self, ctx):
|
||||||
|
self._load_plugin_commands()
|
||||||
|
|
||||||
|
# The commands available is the list of both the application (if
|
||||||
|
# available) plus the builtin commands.
|
||||||
|
rv = set(click.Group.list_commands(self, ctx))
|
||||||
|
info = ctx.ensure_object(ScriptInfo)
|
||||||
|
try:
|
||||||
|
rv.update(info.load_app().cli.list_commands(ctx))
|
||||||
|
except Exception:
|
||||||
|
# Here we intentionally swallow all exceptions as we don't
|
||||||
|
# want the help page to break if the app does not exist.
|
||||||
|
# If someone attempts to use the command we try to create
|
||||||
|
# the app again and this will give us the error.
|
||||||
|
# However, we will not do so silently because that would confuse
|
||||||
|
# users.
|
||||||
|
traceback.print_exc()
|
||||||
|
return sorted(rv)
|
||||||
|
|
||||||
|
def main(self, *args, **kwargs):
|
||||||
|
# Set a global flag that indicates that we were invoked from the
|
||||||
|
# command line interface. This is detected by Flask.run to make the
|
||||||
|
# call into a no-op. This is necessary to avoid ugly errors when the
|
||||||
|
# script that is loaded here also attempts to start a server.
|
||||||
|
os.environ["FLASK_RUN_FROM_CLI"] = "true"
|
||||||
|
|
||||||
|
if get_load_dotenv(self.load_dotenv):
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
obj = kwargs.get("obj")
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
obj = ScriptInfo(
|
||||||
|
create_app=self.create_app, set_debug_flag=self.set_debug_flag
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs["obj"] = obj
|
||||||
|
kwargs.setdefault("auto_envvar_prefix", "FLASK")
|
||||||
|
return super(FlaskGroup, self).main(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _path_is_ancestor(path, other):
|
||||||
|
"""Take ``other`` and remove the length of ``path`` from it. Then join it
|
||||||
|
to ``path``. If it is the original value, ``path`` is an ancestor of
|
||||||
|
``other``."""
|
||||||
|
return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
|
||||||
|
|
||||||
|
|
||||||
|
def load_dotenv(path=None):
|
||||||
|
"""Load "dotenv" files in order of precedence to set environment variables.
|
||||||
|
|
||||||
|
If an env var is already set it is not overwritten, so earlier files in the
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||||
|
|
||||||
|
:param path: Load the file at this location instead of searching.
|
||||||
|
:return: ``True`` if a file was loaded.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.1.0
|
||||||
|
Returns ``False`` when python-dotenv is not installed, or when
|
||||||
|
the given path isn't a file.
|
||||||
|
|
||||||
|
.. versionadded:: 1.0
|
||||||
|
"""
|
||||||
|
if dotenv is None:
|
||||||
|
if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
|
||||||
|
click.secho(
|
||||||
|
" * Tip: There are .env or .flaskenv files present."
|
||||||
|
' Do "pip install python-dotenv" to use them.',
|
||||||
|
fg="yellow",
|
||||||
|
err=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if the given path specifies the actual file then return True,
|
||||||
|
# else False
|
||||||
|
if path is not None:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
return dotenv.load_dotenv(path)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_dir = None
|
||||||
|
|
||||||
|
for name in (".env", ".flaskenv"):
|
||||||
|
path = dotenv.find_dotenv(name, usecwd=True)
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if new_dir is None:
|
||||||
|
new_dir = os.path.dirname(path)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def show_server_banner(env, debug, app_import_path, eager_loading):
|
||||||
|
"""Show extra startup messages the first time the server is run,
|
||||||
|
ignoring the reloader.
|
||||||
|
"""
|
||||||
|
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||||
|
return
|
||||||
|
|
||||||
|
if app_import_path is not None:
|
||||||
|
message = ' * Serving Flask app "{0}"'.format(app_import_path)
|
||||||
|
|
||||||
|
if not eager_loading:
|
||||||
|
message += " (lazy loading)"
|
||||||
|
|
||||||
|
click.echo(message)
|
||||||
|
|
||||||
|
click.echo(" * Environment: {0}".format(env))
|
||||||
|
|
||||||
|
if env == "production":
|
||||||
|
click.secho(
|
||||||
|
" WARNING: This is a development server. "
|
||||||
|
"Do not use it in a production deployment.",
|
||||||
|
fg="red",
|
||||||
|
)
|
||||||
|
click.secho(" Use a production WSGI server instead.", dim=True)
|
||||||
|
|
||||||
|
if debug is not None:
|
||||||
|
click.echo(" * Debug mode: {0}".format("on" if debug else "off"))
|
||||||
|
|
||||||
|
|
||||||
|
class CertParamType(click.ParamType):
|
||||||
|
"""Click option type for the ``--cert`` option. Allows either an
|
||||||
|
existing file, the string ``'adhoc'``, or an import for a
|
||||||
|
:class:`~ssl.SSLContext` object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "path"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
if ssl is None:
|
||||||
|
raise click.BadParameter(
|
||||||
|
'Using "--cert" requires Python to be compiled with SSL support.',
|
||||||
|
ctx,
|
||||||
|
param,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.path_type(value, param, ctx)
|
||||||
|
except click.BadParameter:
|
||||||
|
value = click.STRING(value, param, ctx).lower()
|
||||||
|
|
||||||
|
if value == "adhoc":
|
||||||
|
try:
|
||||||
|
import OpenSSL # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
raise click.BadParameter(
|
||||||
|
"Using ad-hoc certificates requires pyOpenSSL.", ctx, param
|
||||||
|
)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
obj = import_string(value, silent=True)
|
||||||
|
|
||||||
|
if sys.version_info < (2, 7, 9):
|
||||||
|
if obj:
|
||||||
|
return obj
|
||||||
|
else:
|
||||||
|
if isinstance(obj, ssl.SSLContext):
|
||||||
|
return obj
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_key(ctx, param, value):
|
||||||
|
"""The ``--key`` option must be specified when ``--cert`` is a file.
|
||||||
|
Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
|
||||||
|
"""
|
||||||
|
cert = ctx.params.get("cert")
|
||||||
|
is_adhoc = cert == "adhoc"
|
||||||
|
|
||||||
|
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 is_adhoc:
|
||||||
|
raise click.BadParameter(
|
||||||
|
'When "--cert" is "adhoc", "--key" is not used.', ctx, param
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_context:
|
||||||
|
raise click.BadParameter(
|
||||||
|
'When "--cert" is an SSLContext object, "--key is not used.', ctx, param
|
||||||
|
)
|
||||||
|
|
||||||
|
if not cert:
|
||||||
|
raise click.BadParameter('"--cert" must also be specified.', ctx, param)
|
||||||
|
|
||||||
|
ctx.params["cert"] = cert, value
|
||||||
|
|
||||||
|
else:
|
||||||
|
if cert and not (is_adhoc or is_context):
|
||||||
|
raise click.BadParameter('Required when using "--cert".', ctx, param)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class SeparatedPathType(click.Path):
|
||||||
|
"""Click option type that accepts a list of values separated by the
|
||||||
|
OS's path separator (``:``, ``;`` on Windows). Each value is
|
||||||
|
validated as a :class:`click.Path` type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
items = self.split_envvar_value(value)
|
||||||
|
super_convert = super(SeparatedPathType, self).convert
|
||||||
|
return [super_convert(item, param, ctx) for item in items]
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("run", short_help="Run a development server.")
|
||||||
|
@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")
|
||||||
|
@click.option("--port", "-p", default=5000, help="The port to bind to.")
|
||||||
|
@click.option(
|
||||||
|
"--cert", type=CertParamType(), help="Specify a certificate file to use HTTPS."
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--key",
|
||||||
|
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
||||||
|
callback=_validate_key,
|
||||||
|
expose_value=False,
|
||||||
|
help="The key file to use when specifying a certificate.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--reload/--no-reload",
|
||||||
|
default=None,
|
||||||
|
help="Enable or disable the reloader. By default the reloader "
|
||||||
|
"is active if debug is enabled.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--debugger/--no-debugger",
|
||||||
|
default=None,
|
||||||
|
help="Enable or disable the debugger. By default the debugger "
|
||||||
|
"is active if debug is enabled.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--eager-loading/--lazy-loader",
|
||||||
|
default=None,
|
||||||
|
help="Enable or disable eager loading. By default eager "
|
||||||
|
"loading is enabled if the reloader is disabled.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--with-threads/--without-threads",
|
||||||
|
default=True,
|
||||||
|
help="Enable or disable multithreading.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--extra-files",
|
||||||
|
default=None,
|
||||||
|
type=SeparatedPathType(),
|
||||||
|
help=(
|
||||||
|
"Extra files that trigger a reload on change. Multiple paths"
|
||||||
|
" are separated by '{}'.".format(os.path.pathsep)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pass_script_info
|
||||||
|
def run_command(
|
||||||
|
info, host, port, reload, debugger, eager_loading, with_threads, cert, extra_files
|
||||||
|
):
|
||||||
|
"""Run a local development server.
|
||||||
|
|
||||||
|
This server is for development purposes only. It does not provide
|
||||||
|
the stability, security, or performance of production WSGI servers.
|
||||||
|
|
||||||
|
The reloader and debugger are enabled by default if
|
||||||
|
FLASK_ENV=development or FLASK_DEBUG=1.
|
||||||
|
"""
|
||||||
|
debug = get_debug_flag()
|
||||||
|
|
||||||
|
if reload is None:
|
||||||
|
reload = debug
|
||||||
|
|
||||||
|
if debugger is None:
|
||||||
|
debugger = debug
|
||||||
|
|
||||||
|
if eager_loading is None:
|
||||||
|
eager_loading = not reload
|
||||||
|
|
||||||
|
show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
|
||||||
|
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
|
||||||
|
|
||||||
|
from werkzeug.serving import run_simple
|
||||||
|
|
||||||
|
run_simple(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
app,
|
||||||
|
use_reloader=reload,
|
||||||
|
use_debugger=debugger,
|
||||||
|
threaded=with_threads,
|
||||||
|
ssl_context=cert,
|
||||||
|
extra_files=extra_files,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("shell", short_help="Run a shell in the app context.")
|
||||||
|
@with_appcontext
|
||||||
|
def shell_command():
|
||||||
|
"""Run an interactive Python shell in the context of a given
|
||||||
|
Flask application. The application will populate the default
|
||||||
|
namespace of this shell according to it's configuration.
|
||||||
|
|
||||||
|
This is useful for executing small snippets of management code
|
||||||
|
without having to manually configure the application.
|
||||||
|
"""
|
||||||
|
import code
|
||||||
|
from .globals import _app_ctx_stack
|
||||||
|
|
||||||
|
app = _app_ctx_stack.top.app
|
||||||
|
banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % (
|
||||||
|
sys.version,
|
||||||
|
sys.platform,
|
||||||
|
app.import_name,
|
||||||
|
app.env,
|
||||||
|
app.instance_path,
|
||||||
|
)
|
||||||
|
ctx = {}
|
||||||
|
|
||||||
|
# Support the regular Python interpreter startup script if someone
|
||||||
|
# is using it.
|
||||||
|
startup = os.environ.get("PYTHONSTARTUP")
|
||||||
|
if startup and os.path.isfile(startup):
|
||||||
|
with open(startup, "r") as f:
|
||||||
|
eval(compile(f.read(), startup, "exec"), ctx)
|
||||||
|
|
||||||
|
ctx.update(app.make_shell_context())
|
||||||
|
|
||||||
|
code.interact(banner=banner, local=ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("routes", short_help="Show the routes for the app.")
|
||||||
|
@click.option(
|
||||||
|
"--sort",
|
||||||
|
"-s",
|
||||||
|
type=click.Choice(("endpoint", "methods", "rule", "match")),
|
||||||
|
default="endpoint",
|
||||||
|
help=(
|
||||||
|
'Method to sort routes by. "match" is the order that Flask will match '
|
||||||
|
"routes when dispatching a request."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
|
||||||
|
@with_appcontext
|
||||||
|
def routes_command(sort, all_methods):
|
||||||
|
"""Show all registered routes with endpoints and methods."""
|
||||||
|
|
||||||
|
rules = list(current_app.url_map.iter_rules())
|
||||||
|
if not rules:
|
||||||
|
click.echo("No routes were registered.")
|
||||||
|
return
|
||||||
|
|
||||||
|
ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS"))
|
||||||
|
|
||||||
|
if sort in ("endpoint", "rule"):
|
||||||
|
rules = sorted(rules, key=attrgetter(sort))
|
||||||
|
elif sort == "methods":
|
||||||
|
rules = sorted(rules, key=lambda rule: sorted(rule.methods))
|
||||||
|
|
||||||
|
rule_methods = [", ".join(sorted(rule.methods - ignored_methods)) for rule in rules]
|
||||||
|
|
||||||
|
headers = ("Endpoint", "Methods", "Rule")
|
||||||
|
widths = (
|
||||||
|
max(len(rule.endpoint) for rule in rules),
|
||||||
|
max(len(methods) for methods in rule_methods),
|
||||||
|
max(len(rule.rule) for rule in rules),
|
||||||
|
)
|
||||||
|
widths = [max(len(h), w) for h, w in zip(headers, widths)]
|
||||||
|
row = "{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}".format(*widths)
|
||||||
|
|
||||||
|
click.echo(row.format(*headers).strip())
|
||||||
|
click.echo(row.format(*("-" * width for width in widths)))
|
||||||
|
|
||||||
|
for rule, methods in zip(rules, rule_methods):
|
||||||
|
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
|
||||||
|
|
||||||
|
|
||||||
|
cli = FlaskGroup(
|
||||||
|
help="""\
|
||||||
|
A general utility script for Flask applications.
|
||||||
|
|
||||||
|
Provides commands from Flask, extensions, and the application. Loads the
|
||||||
|
application defined in the FLASK_APP environment variable, or from a wsgi.py
|
||||||
|
file. Setting the FLASK_ENV environment variable to 'development' will enable
|
||||||
|
debug mode.
|
||||||
|
|
||||||
|
\b
|
||||||
|
{prefix}{cmd} FLASK_APP=hello.py
|
||||||
|
{prefix}{cmd} FLASK_ENV=development
|
||||||
|
{prefix}flask run
|
||||||
|
""".format(
|
||||||
|
cmd="export" if os.name == "posix" else "set",
|
||||||
|
prefix="$ " if os.name == "posix" else "> ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main(as_module=False):
|
||||||
|
cli.main(prog_name="python -m flask" if as_module else None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(as_module=True)
|
@ -0,0 +1,269 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.config
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the configuration related objects.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import types
|
||||||
|
|
||||||
|
from werkzeug.utils import import_string
|
||||||
|
|
||||||
|
from . import json
|
||||||
|
from ._compat import iteritems
|
||||||
|
from ._compat import string_types
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigAttribute(object):
|
||||||
|
"""Makes an attribute forward to the config"""
|
||||||
|
|
||||||
|
def __init__(self, name, get_converter=None):
|
||||||
|
self.__name__ = name
|
||||||
|
self.get_converter = get_converter
|
||||||
|
|
||||||
|
def __get__(self, obj, type=None):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
rv = obj.config[self.__name__]
|
||||||
|
if self.get_converter is not None:
|
||||||
|
rv = self.get_converter(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __set__(self, obj, value):
|
||||||
|
obj.config[self.__name__] = value
|
||||||
|
|
||||||
|
|
||||||
|
class Config(dict):
|
||||||
|
"""Works exactly like a dict but provides ways to fill it from files
|
||||||
|
or special dictionaries. There are two common patterns to populate the
|
||||||
|
config.
|
||||||
|
|
||||||
|
Either you can fill the config from a config file::
|
||||||
|
|
||||||
|
app.config.from_pyfile('yourconfig.cfg')
|
||||||
|
|
||||||
|
Or alternatively you can define the configuration options in the
|
||||||
|
module that calls :meth:`from_object` or provide an import path to
|
||||||
|
a module that should be loaded. It is also possible to tell it to
|
||||||
|
use the same module and with that provide the configuration values
|
||||||
|
just before the call::
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
SECRET_KEY = 'development key'
|
||||||
|
app.config.from_object(__name__)
|
||||||
|
|
||||||
|
In both cases (loading from any Python file or loading from modules),
|
||||||
|
only uppercase keys are added to the config. This makes it possible to use
|
||||||
|
lowercase values in the config file for temporary values that are not added
|
||||||
|
to the config or to define the config keys in the same file that implements
|
||||||
|
the application.
|
||||||
|
|
||||||
|
Probably the most interesting way to load configurations is from an
|
||||||
|
environment variable pointing to a file::
|
||||||
|
|
||||||
|
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||||
|
|
||||||
|
In this case before launching the application you have to set this
|
||||||
|
environment variable to the file you want to use. On Linux and OS X
|
||||||
|
use the export statement::
|
||||||
|
|
||||||
|
export YOURAPPLICATION_SETTINGS='/path/to/config/file'
|
||||||
|
|
||||||
|
On windows use `set` instead.
|
||||||
|
|
||||||
|
:param root_path: path to which files are read relative from. When the
|
||||||
|
config object is created by the application, this is
|
||||||
|
the application's :attr:`~flask.Flask.root_path`.
|
||||||
|
:param defaults: an optional dictionary of default values
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, root_path, defaults=None):
|
||||||
|
dict.__init__(self, defaults or {})
|
||||||
|
self.root_path = root_path
|
||||||
|
|
||||||
|
def from_envvar(self, variable_name, silent=False):
|
||||||
|
"""Loads a configuration from an environment variable pointing to
|
||||||
|
a configuration file. This is basically just a shortcut with nicer
|
||||||
|
error messages for this line of code::
|
||||||
|
|
||||||
|
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
|
||||||
|
|
||||||
|
:param variable_name: name of the environment variable
|
||||||
|
:param silent: set to ``True`` if you want silent failure for missing
|
||||||
|
files.
|
||||||
|
:return: bool. ``True`` if able to load config, ``False`` otherwise.
|
||||||
|
"""
|
||||||
|
rv = os.environ.get(variable_name)
|
||||||
|
if not rv:
|
||||||
|
if silent:
|
||||||
|
return False
|
||||||
|
raise RuntimeError(
|
||||||
|
"The environment variable %r is not set "
|
||||||
|
"and as such configuration could not be "
|
||||||
|
"loaded. Set this variable and make it "
|
||||||
|
"point to a configuration file" % variable_name
|
||||||
|
)
|
||||||
|
return self.from_pyfile(rv, silent=silent)
|
||||||
|
|
||||||
|
def from_pyfile(self, filename, silent=False):
|
||||||
|
"""Updates the values in the config from a Python file. This function
|
||||||
|
behaves as if the file was imported as module with the
|
||||||
|
:meth:`from_object` function.
|
||||||
|
|
||||||
|
:param filename: the filename of the config. 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.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
`silent` parameter.
|
||||||
|
"""
|
||||||
|
filename = os.path.join(self.root_path, filename)
|
||||||
|
d = types.ModuleType("config")
|
||||||
|
d.__file__ = filename
|
||||||
|
try:
|
||||||
|
with open(filename, mode="rb") as config_file:
|
||||||
|
exec(compile(config_file.read(), filename, "exec"), d.__dict__)
|
||||||
|
except IOError as e:
|
||||||
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
|
||||||
|
return False
|
||||||
|
e.strerror = "Unable to load configuration file (%s)" % e.strerror
|
||||||
|
raise
|
||||||
|
self.from_object(d)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def from_object(self, obj):
|
||||||
|
"""Updates the values from the given object. An object can be of one
|
||||||
|
of the following two types:
|
||||||
|
|
||||||
|
- a string: in this case the object with that name will be imported
|
||||||
|
- an actual object reference: that object is used directly
|
||||||
|
|
||||||
|
Objects are usually either modules or classes. :meth:`from_object`
|
||||||
|
loads only the uppercase attributes of the module/class. A ``dict``
|
||||||
|
object will not work with :meth:`from_object` because the keys of a
|
||||||
|
``dict`` are not attributes of the ``dict`` class.
|
||||||
|
|
||||||
|
Example of module-based configuration::
|
||||||
|
|
||||||
|
app.config.from_object('yourapplication.default_config')
|
||||||
|
from yourapplication import default_config
|
||||||
|
app.config.from_object(default_config)
|
||||||
|
|
||||||
|
Nothing is done to the object before loading. If the object is a
|
||||||
|
class and has ``@property`` attributes, it needs to be
|
||||||
|
instantiated before being passed to this method.
|
||||||
|
|
||||||
|
You should not use this function to load the actual configuration but
|
||||||
|
rather configuration defaults. The actual config should be loaded
|
||||||
|
with :meth:`from_pyfile` and ideally from a location not within the
|
||||||
|
package because the package might be installed system wide.
|
||||||
|
|
||||||
|
See :ref:`config-dev-prod` for an example of class-based configuration
|
||||||
|
using :meth:`from_object`.
|
||||||
|
|
||||||
|
:param obj: an import name or object
|
||||||
|
"""
|
||||||
|
if isinstance(obj, string_types):
|
||||||
|
obj = import_string(obj)
|
||||||
|
for key in dir(obj):
|
||||||
|
if key.isupper():
|
||||||
|
self[key] = getattr(obj, key)
|
||||||
|
|
||||||
|
def from_json(self, filename, silent=False):
|
||||||
|
"""Updates the values in the config from a JSON file. This function
|
||||||
|
behaves as if the JSON object was a dictionary and passed to the
|
||||||
|
:meth:`from_mapping` function.
|
||||||
|
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
filename = os.path.join(self.root_path, filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filename) as json_file:
|
||||||
|
obj = json.loads(json_file.read())
|
||||||
|
except IOError as e:
|
||||||
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
|
return False
|
||||||
|
e.strerror = "Unable to load configuration file (%s)" % e.strerror
|
||||||
|
raise
|
||||||
|
return self.from_mapping(obj)
|
||||||
|
|
||||||
|
def from_mapping(self, *mapping, **kwargs):
|
||||||
|
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||||
|
keys.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
mappings = []
|
||||||
|
if len(mapping) == 1:
|
||||||
|
if hasattr(mapping[0], "items"):
|
||||||
|
mappings.append(mapping[0].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():
|
||||||
|
self[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
|
||||||
|
"""Returns a dictionary containing a subset of configuration options
|
||||||
|
that match the specified namespace/prefix. Example usage::
|
||||||
|
|
||||||
|
app.config['IMAGE_STORE_TYPE'] = 'fs'
|
||||||
|
app.config['IMAGE_STORE_PATH'] = '/var/app/images'
|
||||||
|
app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
|
||||||
|
image_store_config = app.config.get_namespace('IMAGE_STORE_')
|
||||||
|
|
||||||
|
The resulting dictionary `image_store_config` would look like::
|
||||||
|
|
||||||
|
{
|
||||||
|
'type': 'fs',
|
||||||
|
'path': '/var/app/images',
|
||||||
|
'base_url': 'http://img.website.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
This is often useful when configuration options map directly to
|
||||||
|
keyword arguments in functions or class constructors.
|
||||||
|
|
||||||
|
:param namespace: a configuration namespace
|
||||||
|
:param lowercase: a flag indicating if the keys of the resulting
|
||||||
|
dictionary should be lowercase
|
||||||
|
:param trim_namespace: a flag indicating if the keys of the resulting
|
||||||
|
dictionary should not include the namespace
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
rv = {}
|
||||||
|
for k, v in iteritems(self):
|
||||||
|
if not k.startswith(namespace):
|
||||||
|
continue
|
||||||
|
if trim_namespace:
|
||||||
|
key = k[len(namespace) :]
|
||||||
|
else:
|
||||||
|
key = k
|
||||||
|
if lowercase:
|
||||||
|
key = key.lower()
|
||||||
|
rv[key] = v
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s %s>" % (self.__class__.__name__, dict.__repr__(self))
|
@ -0,0 +1,475 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.ctx
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the objects required to keep the context.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
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 _request_ctx_stack
|
||||||
|
from .signals import appcontext_popped
|
||||||
|
from .signals import appcontext_pushed
|
||||||
|
|
||||||
|
|
||||||
|
# a singleton sentinel value for parameter defaults
|
||||||
|
_sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
|
class _AppCtxGlobals(object):
|
||||||
|
"""A plain object. Used as a namespace for storing data during an
|
||||||
|
application context.
|
||||||
|
|
||||||
|
Creating an app context automatically creates this object, which is
|
||||||
|
made available as the :data:`g` proxy.
|
||||||
|
|
||||||
|
.. describe:: 'key' in g
|
||||||
|
|
||||||
|
Check whether an attribute is present.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
.. describe:: iter(g)
|
||||||
|
|
||||||
|
Return an iterator over the attribute names.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, name, default=None):
|
||||||
|
"""Get an attribute by name, or a default value. Like
|
||||||
|
:meth:`dict.get`.
|
||||||
|
|
||||||
|
:param name: Name of attribute to get.
|
||||||
|
:param default: Value to return if the attribute is not present.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
"""
|
||||||
|
return self.__dict__.get(name, default)
|
||||||
|
|
||||||
|
def pop(self, name, default=_sentinel):
|
||||||
|
"""Get and remove an attribute by name. Like :meth:`dict.pop`.
|
||||||
|
|
||||||
|
:param name: Name of attribute to pop.
|
||||||
|
:param default: Value to return if the attribute is not present,
|
||||||
|
instead of raise a ``KeyError``.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
if default is _sentinel:
|
||||||
|
return self.__dict__.pop(name)
|
||||||
|
else:
|
||||||
|
return self.__dict__.pop(name, default)
|
||||||
|
|
||||||
|
def setdefault(self, name, default=None):
|
||||||
|
"""Get the value of an attribute if it is present, otherwise
|
||||||
|
set and return a default value. Like :meth:`dict.setdefault`.
|
||||||
|
|
||||||
|
:param name: Name of attribute to get.
|
||||||
|
:param: default: Value to set and return if the attribute is not
|
||||||
|
present.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
return self.__dict__.setdefault(name, default)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self.__dict__
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__dict__)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
top = _app_ctx_stack.top
|
||||||
|
if top is not None:
|
||||||
|
return "<flask.g of %r>" % top.app.name
|
||||||
|
return object.__repr__(self)
|
||||||
|
|
||||||
|
|
||||||
|
def after_this_request(f):
|
||||||
|
"""Executes a function after this request. This is useful to modify
|
||||||
|
response objects. The function is passed the response object and has
|
||||||
|
to return the same or a new one.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
@after_this_request
|
||||||
|
def add_header(response):
|
||||||
|
response.headers['X-Foo'] = 'Parachute'
|
||||||
|
return response
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
This is more useful if a function other than the view function wants to
|
||||||
|
modify a response. For instance think of a decorator that wants to add
|
||||||
|
some headers without converting the return value into a response object.
|
||||||
|
|
||||||
|
.. versionadded:: 0.9
|
||||||
|
"""
|
||||||
|
_request_ctx_stack.top._after_request_functions.append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def copy_current_request_context(f):
|
||||||
|
"""A helper function that decorates a function to retain the current
|
||||||
|
request context. This is useful when working with greenlets. The moment
|
||||||
|
the function is decorated a copy of the request context is created and
|
||||||
|
then pushed when the function is called. The current session is also
|
||||||
|
included in the copied request context.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
import gevent
|
||||||
|
from flask import copy_current_request_context
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
@copy_current_request_context
|
||||||
|
def do_some_work():
|
||||||
|
# do some work here, it can access flask.request or
|
||||||
|
# flask.session like you would otherwise in the view function.
|
||||||
|
...
|
||||||
|
gevent.spawn(do_some_work)
|
||||||
|
return 'Regular response'
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
"""
|
||||||
|
top = _request_ctx_stack.top
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"This decorator can only be used at local scopes "
|
||||||
|
"when a request context is on the stack. For instance within "
|
||||||
|
"view functions."
|
||||||
|
)
|
||||||
|
reqctx = top.copy()
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with reqctx:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return update_wrapper(wrapper, f)
|
||||||
|
|
||||||
|
|
||||||
|
def has_request_context():
|
||||||
|
"""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
|
||||||
|
of request information if the request object is available, but fail
|
||||||
|
silently if it is unavailable.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
|
||||||
|
def __init__(self, username, remote_addr=None):
|
||||||
|
self.username = username
|
||||||
|
if remote_addr is None and has_request_context():
|
||||||
|
remote_addr = request.remote_addr
|
||||||
|
self.remote_addr = remote_addr
|
||||||
|
|
||||||
|
Alternatively you can also just test any of the context bound objects
|
||||||
|
(such as :class:`request` or :class:`g`) for truthness::
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
|
||||||
|
def __init__(self, username, remote_addr=None):
|
||||||
|
self.username = username
|
||||||
|
if remote_addr is None and request:
|
||||||
|
remote_addr = request.remote_addr
|
||||||
|
self.remote_addr = remote_addr
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
return _request_ctx_stack.top is not None
|
||||||
|
|
||||||
|
|
||||||
|
def has_app_context():
|
||||||
|
"""Works like :func:`has_request_context` but for the application
|
||||||
|
context. You can also just do a boolean check on the
|
||||||
|
:data:`current_app` object instead.
|
||||||
|
|
||||||
|
.. versionadded:: 0.9
|
||||||
|
"""
|
||||||
|
return _app_ctx_stack.top is not None
|
||||||
|
|
||||||
|
|
||||||
|
class AppContext(object):
|
||||||
|
"""The application context binds an application object implicitly
|
||||||
|
to the current thread or greenlet, similar to how the
|
||||||
|
:class:`RequestContext` binds request information. The application
|
||||||
|
context is also implicitly created if a request context is created
|
||||||
|
but the application is not on top of the individual application
|
||||||
|
context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.url_adapter = app.create_url_adapter(None)
|
||||||
|
self.g = app.app_ctx_globals_class()
|
||||||
|
|
||||||
|
# Like request context, app contexts can be pushed multiple times
|
||||||
|
# but there a basic "refcount" is enough to track them.
|
||||||
|
self._refcnt = 0
|
||||||
|
|
||||||
|
def push(self):
|
||||||
|
"""Binds the app context to the current context."""
|
||||||
|
self._refcnt += 1
|
||||||
|
if hasattr(sys, "exc_clear"):
|
||||||
|
sys.exc_clear()
|
||||||
|
_app_ctx_stack.push(self)
|
||||||
|
appcontext_pushed.send(self.app)
|
||||||
|
|
||||||
|
def pop(self, exc=_sentinel):
|
||||||
|
"""Pops the app context."""
|
||||||
|
try:
|
||||||
|
self._refcnt -= 1
|
||||||
|
if self._refcnt <= 0:
|
||||||
|
if exc is _sentinel:
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
self.app.do_teardown_appcontext(exc)
|
||||||
|
finally:
|
||||||
|
rv = _app_ctx_stack.pop()
|
||||||
|
assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self)
|
||||||
|
appcontext_popped.send(self.app)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.push()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
self.pop(exc_value)
|
||||||
|
|
||||||
|
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
||||||
|
reraise(exc_type, exc_value, tb)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestContext(object):
|
||||||
|
"""The request context contains all request relevant information. It is
|
||||||
|
created at the beginning of the request and pushed to the
|
||||||
|
`_request_ctx_stack` and removed at the end of it. It will create the
|
||||||
|
URL adapter and request object for the WSGI environment provided.
|
||||||
|
|
||||||
|
Do not attempt to use this class directly, instead use
|
||||||
|
:meth:`~flask.Flask.test_request_context` and
|
||||||
|
:meth:`~flask.Flask.request_context` to create this object.
|
||||||
|
|
||||||
|
When the request context is popped, it will evaluate all the
|
||||||
|
functions registered on the application for teardown execution
|
||||||
|
(:meth:`~flask.Flask.teardown_request`).
|
||||||
|
|
||||||
|
The request context is automatically popped at the end of the request
|
||||||
|
for you. In debug mode the request context is kept around if
|
||||||
|
exceptions happen so that interactive debuggers have a chance to
|
||||||
|
introspect the data. With 0.4 this can also be forced for requests
|
||||||
|
that did not fail and outside of ``DEBUG`` mode. By setting
|
||||||
|
``'flask._preserve_context'`` to ``True`` on the WSGI environment the
|
||||||
|
context will not pop itself at the end of the request. This is used by
|
||||||
|
the :meth:`~flask.Flask.test_client` for example to implement the
|
||||||
|
deferred cleanup functionality.
|
||||||
|
|
||||||
|
You might find this helpful for unittests where you need the
|
||||||
|
information from the context local around for a little longer. Make
|
||||||
|
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
||||||
|
that situation, otherwise your unittests will leak memory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app, environ, request=None, session=None):
|
||||||
|
self.app = app
|
||||||
|
if request is None:
|
||||||
|
request = app.request_class(environ)
|
||||||
|
self.request = request
|
||||||
|
self.url_adapter = None
|
||||||
|
try:
|
||||||
|
self.url_adapter = app.create_url_adapter(self.request)
|
||||||
|
except HTTPException as e:
|
||||||
|
self.request.routing_exception = e
|
||||||
|
self.flashes = None
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
# Request contexts can be pushed multiple times and interleaved with
|
||||||
|
# other request contexts. Now only if the last level is popped we
|
||||||
|
# get rid of them. Additionally if an application context is missing
|
||||||
|
# one is created implicitly so for each level we add this information
|
||||||
|
self._implicit_app_ctx_stack = []
|
||||||
|
|
||||||
|
# indicator if the context was preserved. Next time another context
|
||||||
|
# is pushed the preserved context is popped.
|
||||||
|
self.preserved = False
|
||||||
|
|
||||||
|
# remembers the exception for pop if there is one in case the context
|
||||||
|
# preservation kicks in.
|
||||||
|
self._preserved_exc = None
|
||||||
|
|
||||||
|
# Functions that should be executed after the request on the response
|
||||||
|
# object. These will be called before the regular "after_request"
|
||||||
|
# functions.
|
||||||
|
self._after_request_functions = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def g(self):
|
||||||
|
return _app_ctx_stack.top.g
|
||||||
|
|
||||||
|
@g.setter
|
||||||
|
def g(self, value):
|
||||||
|
_app_ctx_stack.top.g = value
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""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.
|
||||||
|
Because the actual request object is the same this cannot be used to
|
||||||
|
move a request context to a different thread unless access to the
|
||||||
|
request object is locked.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
.. versionchanged:: 1.1
|
||||||
|
The current session object is used instead of reloading the original
|
||||||
|
data. This prevents `flask.session` pointing to an out-of-date object.
|
||||||
|
"""
|
||||||
|
return self.__class__(
|
||||||
|
self.app,
|
||||||
|
environ=self.request.environ,
|
||||||
|
request=self.request,
|
||||||
|
session=self.session,
|
||||||
|
)
|
||||||
|
|
||||||
|
def match_request(self):
|
||||||
|
"""Can be overridden by a subclass to hook into the matching
|
||||||
|
of the request.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = self.url_adapter.match(return_rule=True)
|
||||||
|
self.request.url_rule, self.request.view_args = result
|
||||||
|
except HTTPException as e:
|
||||||
|
self.request.routing_exception = e
|
||||||
|
|
||||||
|
def push(self):
|
||||||
|
"""Binds the request context to the current context."""
|
||||||
|
# If an exception occurs in debug mode or if context preservation is
|
||||||
|
# activated under exception situations exactly one context stays
|
||||||
|
# on the stack. The rationale is that you want to access that
|
||||||
|
# information under debug situations. However if someone forgets to
|
||||||
|
# pop that context again we want to make sure that on the next push
|
||||||
|
# it's invalidated, otherwise we run at risk that something leaks
|
||||||
|
# memory. This is usually only a problem in test suite since this
|
||||||
|
# functionality is not active in production environments.
|
||||||
|
top = _request_ctx_stack.top
|
||||||
|
if top is not None and top.preserved:
|
||||||
|
top.pop(top._preserved_exc)
|
||||||
|
|
||||||
|
# Before we push the request context we have to ensure that there
|
||||||
|
# is an application context.
|
||||||
|
app_ctx = _app_ctx_stack.top
|
||||||
|
if app_ctx is None or app_ctx.app != self.app:
|
||||||
|
app_ctx = self.app.app_context()
|
||||||
|
app_ctx.push()
|
||||||
|
self._implicit_app_ctx_stack.append(app_ctx)
|
||||||
|
else:
|
||||||
|
self._implicit_app_ctx_stack.append(None)
|
||||||
|
|
||||||
|
if hasattr(sys, "exc_clear"):
|
||||||
|
sys.exc_clear()
|
||||||
|
|
||||||
|
_request_ctx_stack.push(self)
|
||||||
|
|
||||||
|
# Open the session at the moment that the request context is available.
|
||||||
|
# This allows a custom open_session method to use the request context.
|
||||||
|
# Only open a new session if this is the first time the request was
|
||||||
|
# pushed, otherwise stream_with_context loses the session.
|
||||||
|
if self.session is None:
|
||||||
|
session_interface = self.app.session_interface
|
||||||
|
self.session = session_interface.open_session(self.app, self.request)
|
||||||
|
|
||||||
|
if self.session is None:
|
||||||
|
self.session = session_interface.make_null_session(self.app)
|
||||||
|
|
||||||
|
if self.url_adapter is not None:
|
||||||
|
self.match_request()
|
||||||
|
|
||||||
|
def pop(self, exc=_sentinel):
|
||||||
|
"""Pops the request context and unbinds it by doing that. This will
|
||||||
|
also trigger the execution of functions registered by the
|
||||||
|
:meth:`~flask.Flask.teardown_request` decorator.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.9
|
||||||
|
Added the `exc` argument.
|
||||||
|
"""
|
||||||
|
app_ctx = self._implicit_app_ctx_stack.pop()
|
||||||
|
|
||||||
|
try:
|
||||||
|
clear_request = False
|
||||||
|
if not self._implicit_app_ctx_stack:
|
||||||
|
self.preserved = False
|
||||||
|
self._preserved_exc = None
|
||||||
|
if exc is _sentinel:
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
self.app.do_teardown_request(exc)
|
||||||
|
|
||||||
|
# If this interpreter supports clearing the exception information
|
||||||
|
# we do that now. This will only go into effect on Python 2.x,
|
||||||
|
# on 3.x it disappears automatically at the end of the exception
|
||||||
|
# stack.
|
||||||
|
if hasattr(sys, "exc_clear"):
|
||||||
|
sys.exc_clear()
|
||||||
|
|
||||||
|
request_close = getattr(self.request, "close", None)
|
||||||
|
if request_close is not None:
|
||||||
|
request_close()
|
||||||
|
clear_request = True
|
||||||
|
finally:
|
||||||
|
rv = _request_ctx_stack.pop()
|
||||||
|
|
||||||
|
# get rid of circular dependencies at the end of the request
|
||||||
|
# so that we don't require the GC to be active.
|
||||||
|
if clear_request:
|
||||||
|
rv.request.environ["werkzeug.request"] = None
|
||||||
|
|
||||||
|
# Get rid of the app as well if necessary.
|
||||||
|
if app_ctx is not None:
|
||||||
|
app_ctx.pop(exc)
|
||||||
|
|
||||||
|
assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
|
||||||
|
rv,
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
|
||||||
|
def auto_pop(self, exc):
|
||||||
|
if self.request.environ.get("flask._preserve_context") or (
|
||||||
|
exc is not None and self.app.preserve_context_on_exception
|
||||||
|
):
|
||||||
|
self.preserved = True
|
||||||
|
self._preserved_exc = exc
|
||||||
|
else:
|
||||||
|
self.pop(exc)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.push()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
# do not pop the request stack if we are in debug mode and an
|
||||||
|
# exception happened. This will allow the debugger to still
|
||||||
|
# access the request object in the interactive shell. Furthermore
|
||||||
|
# the context can be force kept alive for the test client.
|
||||||
|
# See flask.testing for how this works.
|
||||||
|
self.auto_pop(exc_value)
|
||||||
|
|
||||||
|
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
||||||
|
reraise(exc_type, exc_value, tb)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s '%s' [%s] of %s>" % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.request.url,
|
||||||
|
self.request.method,
|
||||||
|
self.app.name,
|
||||||
|
)
|
@ -0,0 +1,183 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.debughelpers
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Various helpers to make the development experience better.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
from ._compat import implements_to_string
|
||||||
|
from ._compat import text_type
|
||||||
|
from .app import Flask
|
||||||
|
from .blueprints import Blueprint
|
||||||
|
from .globals import _request_ctx_stack
|
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedUnicodeError(AssertionError, UnicodeError):
|
||||||
|
"""Raised in places where we want some better error reporting for
|
||||||
|
unexpected unicode or binary data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@implements_to_string
|
||||||
|
class DebugFilesKeyError(KeyError, AssertionError):
|
||||||
|
"""Raised from request.files during debugging. The idea is that it can
|
||||||
|
provide a better error message than just a generic KeyError/BadRequest.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request, key):
|
||||||
|
form_matches = request.form.getlist(key)
|
||||||
|
buf = [
|
||||||
|
'You tried to access the file "%s" in the request.files '
|
||||||
|
"dictionary but it does not exist. The mimetype for the request "
|
||||||
|
'is "%s" instead of "multipart/form-data" which means that no '
|
||||||
|
"file contents were transmitted. To fix this error you should "
|
||||||
|
'provide enctype="multipart/form-data" in your form.'
|
||||||
|
% (key, request.mimetype)
|
||||||
|
]
|
||||||
|
if form_matches:
|
||||||
|
buf.append(
|
||||||
|
"\n\nThe browser instead transmitted some file names. "
|
||||||
|
"This was submitted: %s" % ", ".join('"%s"' % x for x in form_matches)
|
||||||
|
)
|
||||||
|
self.msg = "".join(buf)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.msg
|
||||||
|
|
||||||
|
|
||||||
|
class FormDataRoutingRedirect(AssertionError):
|
||||||
|
"""This exception is raised by Flask in debug mode if it detects a
|
||||||
|
redirect caused by the routing system when the request method is not
|
||||||
|
GET, HEAD or OPTIONS. Reasoning: form data will be dropped.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request):
|
||||||
|
exc = request.routing_exception
|
||||||
|
buf = [
|
||||||
|
"A request was sent to this URL (%s) but a redirect was "
|
||||||
|
'issued automatically by the routing system to "%s".'
|
||||||
|
% (request.url, exc.new_url)
|
||||||
|
]
|
||||||
|
|
||||||
|
# In case just a slash was appended we can be extra helpful
|
||||||
|
if request.base_url + "/" == exc.new_url.split("?")[0]:
|
||||||
|
buf.append(
|
||||||
|
" The URL was defined with a trailing slash so "
|
||||||
|
"Flask will automatically redirect to the URL "
|
||||||
|
"with the trailing slash if it was accessed "
|
||||||
|
"without one."
|
||||||
|
)
|
||||||
|
|
||||||
|
buf.append(
|
||||||
|
" Make sure to directly send your %s-request to this URL "
|
||||||
|
"since we can't make browsers or HTTP clients redirect "
|
||||||
|
"with form data reliably or without user interaction." % request.method
|
||||||
|
)
|
||||||
|
buf.append("\n\nNote: this exception is only raised in debug mode")
|
||||||
|
AssertionError.__init__(self, "".join(buf).encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def attach_enctype_error_multidict(request):
|
||||||
|
"""Since Flask 0.8 we're monkeypatching the files object in case a
|
||||||
|
request is detected that does not use multipart form data but the files
|
||||||
|
object is accessed.
|
||||||
|
"""
|
||||||
|
oldcls = request.files.__class__
|
||||||
|
|
||||||
|
class newcls(oldcls):
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return oldcls.__getitem__(self, key)
|
||||||
|
except KeyError:
|
||||||
|
if key not in request.form:
|
||||||
|
raise
|
||||||
|
raise DebugFilesKeyError(request, key)
|
||||||
|
|
||||||
|
newcls.__name__ = oldcls.__name__
|
||||||
|
newcls.__module__ = oldcls.__module__
|
||||||
|
request.files.__class__ = newcls
|
||||||
|
|
||||||
|
|
||||||
|
def _dump_loader_info(loader):
|
||||||
|
yield "class: %s.%s" % (type(loader).__module__, type(loader).__name__)
|
||||||
|
for key, value in sorted(loader.__dict__.items()):
|
||||||
|
if key.startswith("_"):
|
||||||
|
continue
|
||||||
|
if isinstance(value, (tuple, list)):
|
||||||
|
if not all(isinstance(x, (str, text_type)) for x in value):
|
||||||
|
continue
|
||||||
|
yield "%s:" % key
|
||||||
|
for item in value:
|
||||||
|
yield " - %s" % item
|
||||||
|
continue
|
||||||
|
elif not isinstance(value, (str, text_type, int, float, bool)):
|
||||||
|
continue
|
||||||
|
yield "%s: %r" % (key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def explain_template_loading_attempts(app, template, attempts):
|
||||||
|
"""This should help developers understand what failed"""
|
||||||
|
info = ['Locating template "%s":' % template]
|
||||||
|
total_found = 0
|
||||||
|
blueprint = None
|
||||||
|
reqctx = _request_ctx_stack.top
|
||||||
|
if reqctx is not None and reqctx.request.blueprint is not None:
|
||||||
|
blueprint = reqctx.request.blueprint
|
||||||
|
|
||||||
|
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
||||||
|
if isinstance(srcobj, Flask):
|
||||||
|
src_info = 'application "%s"' % srcobj.import_name
|
||||||
|
elif isinstance(srcobj, Blueprint):
|
||||||
|
src_info = 'blueprint "%s" (%s)' % (srcobj.name, srcobj.import_name)
|
||||||
|
else:
|
||||||
|
src_info = repr(srcobj)
|
||||||
|
|
||||||
|
info.append("% 5d: trying loader of %s" % (idx + 1, src_info))
|
||||||
|
|
||||||
|
for line in _dump_loader_info(loader):
|
||||||
|
info.append(" %s" % line)
|
||||||
|
|
||||||
|
if triple is None:
|
||||||
|
detail = "no match"
|
||||||
|
else:
|
||||||
|
detail = "found (%r)" % (triple[1] or "<string>")
|
||||||
|
total_found += 1
|
||||||
|
info.append(" -> %s" % detail)
|
||||||
|
|
||||||
|
seems_fishy = False
|
||||||
|
if total_found == 0:
|
||||||
|
info.append("Error: the template could not be found.")
|
||||||
|
seems_fishy = True
|
||||||
|
elif total_found > 1:
|
||||||
|
info.append("Warning: multiple loaders returned a match for the template.")
|
||||||
|
seems_fishy = True
|
||||||
|
|
||||||
|
if blueprint is not None and seems_fishy:
|
||||||
|
info.append(
|
||||||
|
" The template was looked up from an endpoint that "
|
||||||
|
'belongs to the blueprint "%s".' % blueprint
|
||||||
|
)
|
||||||
|
info.append(" Maybe you did not place a template in the right folder?")
|
||||||
|
info.append(" See http://flask.pocoo.org/docs/blueprints/#templates")
|
||||||
|
|
||||||
|
app.logger.info("\n".join(info))
|
||||||
|
|
||||||
|
|
||||||
|
def explain_ignored_app_run():
|
||||||
|
if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
|
||||||
|
warn(
|
||||||
|
Warning(
|
||||||
|
"Silently ignoring app.run() because the "
|
||||||
|
"application is run from the flask command line "
|
||||||
|
"executable. Consider putting app.run() behind an "
|
||||||
|
'if __name__ == "__main__" guard to silence this '
|
||||||
|
"warning."
|
||||||
|
),
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
@ -0,0 +1,62 @@
|
|||||||
|
# -*- 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 werkzeug.local import LocalProxy
|
||||||
|
from werkzeug.local import LocalStack
|
||||||
|
|
||||||
|
|
||||||
|
_request_ctx_err_msg = """\
|
||||||
|
Working outside of request context.
|
||||||
|
|
||||||
|
This typically means that you attempted to use functionality that needed
|
||||||
|
an active HTTP request. Consult the documentation on testing for
|
||||||
|
information about how to avoid this problem.\
|
||||||
|
"""
|
||||||
|
_app_ctx_err_msg = """\
|
||||||
|
Working outside of application context.
|
||||||
|
|
||||||
|
This typically means that you attempted to use functionality that needed
|
||||||
|
to interface with the current application object in some way. To solve
|
||||||
|
this, set up an application context with app.app_context(). See the
|
||||||
|
documentation for more information.\
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_req_object(name):
|
||||||
|
top = _request_ctx_stack.top
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError(_request_ctx_err_msg)
|
||||||
|
return getattr(top, name)
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_app_object(name):
|
||||||
|
top = _app_ctx_stack.top
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError(_app_ctx_err_msg)
|
||||||
|
return getattr(top, name)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_app():
|
||||||
|
top = _app_ctx_stack.top
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError(_app_ctx_err_msg)
|
||||||
|
return top.app
|
||||||
|
|
||||||
|
|
||||||
|
# context locals
|
||||||
|
_request_ctx_stack = LocalStack()
|
||||||
|
_app_ctx_stack = LocalStack()
|
||||||
|
current_app = LocalProxy(_find_app)
|
||||||
|
request = LocalProxy(partial(_lookup_req_object, "request"))
|
||||||
|
session = LocalProxy(partial(_lookup_req_object, "session"))
|
||||||
|
g = LocalProxy(partial(_lookup_app_object, "g"))
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,376 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.json
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
: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 .._compat import PY2
|
||||||
|
from .._compat import text_type
|
||||||
|
from ..globals import current_app
|
||||||
|
from ..globals import request
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dataclasses
|
||||||
|
except ImportError:
|
||||||
|
dataclasses = None
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
"""The default Flask JSON encoder. This one extends the default
|
||||||
|
encoder by also supporting ``datetime``, ``UUID``, ``dataclasses``,
|
||||||
|
and ``Markup`` objects.
|
||||||
|
|
||||||
|
``datetime`` objects are serialized as RFC 822 datetime strings.
|
||||||
|
This is the same as the HTTP date format.
|
||||||
|
|
||||||
|
In order to support more data types, override the :meth:`default`
|
||||||
|
method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def default(self, o):
|
||||||
|
"""Implement this method in a subclass such that it returns a
|
||||||
|
serializable object for ``o``, or calls the base implementation (to
|
||||||
|
raise a :exc:`TypeError`).
|
||||||
|
|
||||||
|
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):
|
||||||
|
return http_date(o.timetuple())
|
||||||
|
if isinstance(o, uuid.UUID):
|
||||||
|
return str(o)
|
||||||
|
if dataclasses and dataclasses.is_dataclass(o):
|
||||||
|
return dataclasses.asdict(o)
|
||||||
|
if hasattr(o, "__html__"):
|
||||||
|
return text_type(o.__html__())
|
||||||
|
return _json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
|
class JSONDecoder(_json.JSONDecoder):
|
||||||
|
"""The default JSON decoder. This one does not change the behavior from
|
||||||
|
the default simplejson decoder. Consult the :mod:`json` documentation
|
||||||
|
for more information. This decoder is not only used for the load
|
||||||
|
functions of this module but also :attr:`~flask.Request`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _dump_arg_defaults(kwargs, app=None):
|
||||||
|
"""Inject default arguments for dump functions."""
|
||||||
|
if app is None:
|
||||||
|
app = current_app
|
||||||
|
|
||||||
|
if app:
|
||||||
|
bp = app.blueprints.get(request.blueprint) if request else None
|
||||||
|
kwargs.setdefault(
|
||||||
|
"cls", bp.json_encoder if bp and bp.json_encoder else app.json_encoder
|
||||||
|
)
|
||||||
|
|
||||||
|
if not app.config["JSON_AS_ASCII"]:
|
||||||
|
kwargs.setdefault("ensure_ascii", False)
|
||||||
|
|
||||||
|
kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
|
||||||
|
else:
|
||||||
|
kwargs.setdefault("sort_keys", True)
|
||||||
|
kwargs.setdefault("cls", JSONEncoder)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_arg_defaults(kwargs, app=None):
|
||||||
|
"""Inject default arguments for load functions."""
|
||||||
|
if app is None:
|
||||||
|
app = current_app
|
||||||
|
|
||||||
|
if app:
|
||||||
|
bp = app.blueprints.get(request.blueprint) if request else None
|
||||||
|
kwargs.setdefault(
|
||||||
|
"cls", bp.json_decoder if bp and bp.json_decoder else app.json_decoder
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
kwargs.setdefault("cls", JSONDecoder)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_encoding(data):
|
||||||
|
"""Detect which UTF codec was used to encode the given bytes.
|
||||||
|
|
||||||
|
The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is
|
||||||
|
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 app: App instance to use to configure the JSON encoder.
|
||||||
|
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`.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0.3
|
||||||
|
|
||||||
|
``app`` can be passed directly, rather than requiring an app
|
||||||
|
context for configuration.
|
||||||
|
"""
|
||||||
|
_dump_arg_defaults(kwargs, app=app)
|
||||||
|
encoding = kwargs.pop("encoding", None)
|
||||||
|
rv = _json.dumps(obj, **kwargs)
|
||||||
|
if encoding is not None and isinstance(rv, text_type):
|
||||||
|
rv = rv.encode(encoding)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def dump(obj, fp, app=None, **kwargs):
|
||||||
|
"""Like :func:`dumps` but writes into a file object."""
|
||||||
|
_dump_arg_defaults(kwargs, app=app)
|
||||||
|
encoding = kwargs.pop("encoding", None)
|
||||||
|
if encoding is not None:
|
||||||
|
fp = _wrap_writer_for_text(fp, encoding)
|
||||||
|
_json.dump(obj, fp, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def loads(s, app=None, **kwargs):
|
||||||
|
"""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`, and
|
||||||
|
does some extra configuration based on the application. If the
|
||||||
|
simplejson package is installed, it is preferred.
|
||||||
|
|
||||||
|
:param s: JSON string to deserialize.
|
||||||
|
:param app: App instance to use to configure the JSON decoder.
|
||||||
|
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`.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0.3
|
||||||
|
|
||||||
|
``app`` can be passed directly, rather than requiring an app
|
||||||
|
context for configuration.
|
||||||
|
"""
|
||||||
|
_load_arg_defaults(kwargs, app=app)
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
encoding = kwargs.pop("encoding", None)
|
||||||
|
if encoding is None:
|
||||||
|
encoding = detect_encoding(s)
|
||||||
|
s = s.decode(encoding)
|
||||||
|
return _json.loads(s, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def load(fp, app=None, **kwargs):
|
||||||
|
"""Like :func:`loads` but reads from a file object."""
|
||||||
|
_load_arg_defaults(kwargs, app=app)
|
||||||
|
if not PY2:
|
||||||
|
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
|
||||||
|
return _json.load(fp, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def htmlsafe_dumps(obj, **kwargs):
|
||||||
|
"""Works exactly like :func:`dumps` but is safe for use in ``<script>``
|
||||||
|
tags. It accepts the same arguments and returns a JSON string. Note that
|
||||||
|
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.
|
||||||
|
|
||||||
|
The following characters are escaped in strings:
|
||||||
|
|
||||||
|
- ``<``
|
||||||
|
- ``>``
|
||||||
|
- ``&``
|
||||||
|
- ``'``
|
||||||
|
|
||||||
|
This makes it safe to embed such strings in any place in HTML with the
|
||||||
|
notable exception of double quoted attributes. In that case single
|
||||||
|
quote your attributes or HTML escape it in addition.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.10
|
||||||
|
This function's return value is now always safe for HTML usage, even
|
||||||
|
if outside of script tags or if used in XHTML. This rule does not
|
||||||
|
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``.
|
||||||
|
"""
|
||||||
|
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, fp, **kwargs):
|
||||||
|
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
|
||||||
|
fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
|
||||||
|
|
||||||
|
|
||||||
|
def jsonify(*args, **kwargs):
|
||||||
|
"""This function wraps :func:`dumps` to add a few enhancements that make
|
||||||
|
life easier. It turns the JSON output into a :class:`~flask.Response`
|
||||||
|
object with the :mimetype:`application/json` mimetype. For convenience, it
|
||||||
|
also converts multiple arguments into an array or multiple keyword arguments
|
||||||
|
into a dict. This means that both ``jsonify(1,2,3)`` and
|
||||||
|
``jsonify([1,2,3])`` serialize to ``[1,2,3]``.
|
||||||
|
|
||||||
|
For clarity, the JSON serialization behavior has the following differences
|
||||||
|
from :func:`dumps`:
|
||||||
|
|
||||||
|
1. Single argument: Passed straight through to :func:`dumps`.
|
||||||
|
2. Multiple arguments: Converted to an array before being passed to
|
||||||
|
:func:`dumps`.
|
||||||
|
3. Multiple keyword arguments: Converted to a dict before being passed to
|
||||||
|
:func:`dumps`.
|
||||||
|
4. Both args and kwargs: Behavior undefined and will throw an exception.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
@app.route('/_get_current_user')
|
||||||
|
def get_current_user():
|
||||||
|
return jsonify(username=g.user.username,
|
||||||
|
email=g.user.email,
|
||||||
|
id=g.user.id)
|
||||||
|
|
||||||
|
This will send a JSON response like this to the browser::
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"email": "admin@localhost",
|
||||||
|
"id": 42
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.. versionchanged:: 0.11
|
||||||
|
Added support for serializing top-level arrays. This introduces a
|
||||||
|
security risk in ancient browsers. See :ref:`json-security` for details.
|
||||||
|
|
||||||
|
This function's response will be pretty printed if the
|
||||||
|
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
|
||||||
|
Flask app is running in debug mode. Compressed (not pretty) formatting
|
||||||
|
currently means no indents and no spaces after separators.
|
||||||
|
|
||||||
|
.. versionadded:: 0.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
indent = None
|
||||||
|
separators = (",", ":")
|
||||||
|
|
||||||
|
if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] or current_app.debug:
|
||||||
|
indent = 2
|
||||||
|
separators = (", ", ": ")
|
||||||
|
|
||||||
|
if args and kwargs:
|
||||||
|
raise TypeError("jsonify() behavior undefined when passed both args and kwargs")
|
||||||
|
elif len(args) == 1: # single args are passed directly to dumps()
|
||||||
|
data = args[0]
|
||||||
|
else:
|
||||||
|
data = args or kwargs
|
||||||
|
|
||||||
|
return current_app.response_class(
|
||||||
|
dumps(data, indent=indent, separators=separators) + "\n",
|
||||||
|
mimetype=current_app.config["JSONIFY_MIMETYPE"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tojson_filter(obj, **kwargs):
|
||||||
|
return Markup(htmlsafe_dumps(obj, **kwargs))
|
@ -0,0 +1,309 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Tagged JSON
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
A compact representation for lossless serialization of non-standard JSON types.
|
||||||
|
:class:`~flask.sessions.SecureCookieSessionInterface` uses this to serialize
|
||||||
|
the session data, but it may be useful in other places. It can be extended to
|
||||||
|
support other types.
|
||||||
|
|
||||||
|
.. autoclass:: TaggedJSONSerializer
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: JSONTag
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Let's seen an example that adds support for :class:`~collections.OrderedDict`.
|
||||||
|
Dicts don't have an order in Python or JSON, so to handle this we will dump
|
||||||
|
the items as a list of ``[key, value]`` pairs. Subclass :class:`JSONTag` and
|
||||||
|
give it the new key ``' od'`` to identify the type. The session serializer
|
||||||
|
processes dicts first, so insert the new tag at the front of the order since
|
||||||
|
``OrderedDict`` must be processed before ``dict``. ::
|
||||||
|
|
||||||
|
from flask.json.tag import JSONTag
|
||||||
|
|
||||||
|
class TagOrderedDict(JSONTag):
|
||||||
|
__slots__ = ('serializer',)
|
||||||
|
key = ' od'
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, OrderedDict)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return [[k, self.serializer.tag(v)] for k, v in iteritems(value)]
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return OrderedDict(value)
|
||||||
|
|
||||||
|
app.session_interface.serializer.register(TagOrderedDict, index=0)
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
from base64 import b64decode
|
||||||
|
from base64 import b64encode
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from jinja2 import Markup
|
||||||
|
from werkzeug.http import http_date
|
||||||
|
from werkzeug.http import parse_date
|
||||||
|
|
||||||
|
from .._compat import iteritems
|
||||||
|
from .._compat import text_type
|
||||||
|
from ..json import dumps
|
||||||
|
from ..json import loads
|
||||||
|
|
||||||
|
|
||||||
|
class JSONTag(object):
|
||||||
|
"""Base class for defining type tags for :class:`TaggedJSONSerializer`."""
|
||||||
|
|
||||||
|
__slots__ = ("serializer",)
|
||||||
|
|
||||||
|
#: The tag to mark the serialized object with. If ``None``, this tag is
|
||||||
|
#: only used as an intermediate step during tagging.
|
||||||
|
key = None
|
||||||
|
|
||||||
|
def __init__(self, serializer):
|
||||||
|
"""Create a tagger for the given serializer."""
|
||||||
|
self.serializer = serializer
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
"""Check if the given value should be tagged by this tag."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
"""Convert the Python object to an object that is a valid JSON type.
|
||||||
|
The tag will be added later."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
"""Convert the JSON representation back to the correct type. The tag
|
||||||
|
will already be removed."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def tag(self, value):
|
||||||
|
"""Convert the value to a valid JSON type and add the tag structure
|
||||||
|
around it."""
|
||||||
|
return {self.key: self.to_json(value)}
|
||||||
|
|
||||||
|
|
||||||
|
class TagDict(JSONTag):
|
||||||
|
"""Tag for 1-item dicts whose only key matches a registered tag.
|
||||||
|
|
||||||
|
Internally, the dict key is suffixed with `__`, and the suffix is removed
|
||||||
|
when deserializing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
key = " di"
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return (
|
||||||
|
isinstance(value, dict)
|
||||||
|
and len(value) == 1
|
||||||
|
and next(iter(value)) in self.serializer.tags
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
key = next(iter(value))
|
||||||
|
return {key + "__": self.serializer.tag(value[key])}
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
key = next(iter(value))
|
||||||
|
return {key[:-2]: value[key]}
|
||||||
|
|
||||||
|
|
||||||
|
class PassDict(JSONTag):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, dict)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
# JSON objects may only have string keys, so don't bother tagging the
|
||||||
|
# key here.
|
||||||
|
return dict((k, self.serializer.tag(v)) for k, v in iteritems(value))
|
||||||
|
|
||||||
|
tag = to_json
|
||||||
|
|
||||||
|
|
||||||
|
class TagTuple(JSONTag):
|
||||||
|
__slots__ = ()
|
||||||
|
key = " t"
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, tuple)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return [self.serializer.tag(item) for item in value]
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return tuple(value)
|
||||||
|
|
||||||
|
|
||||||
|
class PassList(JSONTag):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, list)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return [self.serializer.tag(item) for item in value]
|
||||||
|
|
||||||
|
tag = to_json
|
||||||
|
|
||||||
|
|
||||||
|
class TagBytes(JSONTag):
|
||||||
|
__slots__ = ()
|
||||||
|
key = " b"
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, bytes)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return b64encode(value).decode("ascii")
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return b64decode(value)
|
||||||
|
|
||||||
|
|
||||||
|
class TagMarkup(JSONTag):
|
||||||
|
"""Serialize anything matching the :class:`~flask.Markup` API by
|
||||||
|
having a ``__html__`` method to the result of that method. Always
|
||||||
|
deserializes to an instance of :class:`~flask.Markup`."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
key = " m"
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return callable(getattr(value, "__html__", None))
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return text_type(value.__html__())
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return Markup(value)
|
||||||
|
|
||||||
|
|
||||||
|
class TagUUID(JSONTag):
|
||||||
|
__slots__ = ()
|
||||||
|
key = " u"
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, UUID)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return value.hex
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return UUID(value)
|
||||||
|
|
||||||
|
|
||||||
|
class TagDateTime(JSONTag):
|
||||||
|
__slots__ = ()
|
||||||
|
key = " d"
|
||||||
|
|
||||||
|
def check(self, value):
|
||||||
|
return isinstance(value, datetime)
|
||||||
|
|
||||||
|
def to_json(self, value):
|
||||||
|
return http_date(value)
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return parse_date(value)
|
||||||
|
|
||||||
|
|
||||||
|
class TaggedJSONSerializer(object):
|
||||||
|
"""Serializer that uses a tag system to compactly represent objects that
|
||||||
|
are not JSON types. Passed as the intermediate serializer to
|
||||||
|
:class:`itsdangerous.Serializer`.
|
||||||
|
|
||||||
|
The following extra types are supported:
|
||||||
|
|
||||||
|
* :class:`dict`
|
||||||
|
* :class:`tuple`
|
||||||
|
* :class:`bytes`
|
||||||
|
* :class:`~flask.Markup`
|
||||||
|
* :class:`~uuid.UUID`
|
||||||
|
* :class:`~datetime.datetime`
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("tags", "order")
|
||||||
|
|
||||||
|
#: Tag classes to bind when creating the serializer. Other tags can be
|
||||||
|
#: added later using :meth:`~register`.
|
||||||
|
default_tags = [
|
||||||
|
TagDict,
|
||||||
|
PassDict,
|
||||||
|
TagTuple,
|
||||||
|
PassList,
|
||||||
|
TagBytes,
|
||||||
|
TagMarkup,
|
||||||
|
TagUUID,
|
||||||
|
TagDateTime,
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tags = {}
|
||||||
|
self.order = []
|
||||||
|
|
||||||
|
for cls in self.default_tags:
|
||||||
|
self.register(cls)
|
||||||
|
|
||||||
|
def register(self, tag_class, force=False, index=None):
|
||||||
|
"""Register a new tag with this serializer.
|
||||||
|
|
||||||
|
:param tag_class: tag class to register. Will be instantiated with this
|
||||||
|
serializer instance.
|
||||||
|
:param force: overwrite an existing tag. If false (default), a
|
||||||
|
:exc:`KeyError` is raised.
|
||||||
|
:param index: index to insert the new tag in the tag order. Useful when
|
||||||
|
the new tag is a special case of an existing tag. If ``None``
|
||||||
|
(default), the tag is appended to the end of the order.
|
||||||
|
|
||||||
|
:raise KeyError: if the tag key is already registered and ``force`` is
|
||||||
|
not true.
|
||||||
|
"""
|
||||||
|
tag = tag_class(self)
|
||||||
|
key = tag.key
|
||||||
|
|
||||||
|
if key is not None:
|
||||||
|
if not force and key in self.tags:
|
||||||
|
raise KeyError("Tag '{0}' is already registered.".format(key))
|
||||||
|
|
||||||
|
self.tags[key] = tag
|
||||||
|
|
||||||
|
if index is None:
|
||||||
|
self.order.append(tag)
|
||||||
|
else:
|
||||||
|
self.order.insert(index, tag)
|
||||||
|
|
||||||
|
def tag(self, value):
|
||||||
|
"""Convert a value to a tagged representation if necessary."""
|
||||||
|
for tag in self.order:
|
||||||
|
if tag.check(value):
|
||||||
|
return tag.tag(value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def untag(self, value):
|
||||||
|
"""Convert a tagged representation back to the original type."""
|
||||||
|
if len(value) != 1:
|
||||||
|
return value
|
||||||
|
|
||||||
|
key = next(iter(value))
|
||||||
|
|
||||||
|
if key not in self.tags:
|
||||||
|
return value
|
||||||
|
|
||||||
|
return self.tags[key].to_python(value[key])
|
||||||
|
|
||||||
|
def dumps(self, value):
|
||||||
|
"""Tag the value and dump it to a compact JSON string."""
|
||||||
|
return dumps(self.tag(value), separators=(",", ":"))
|
||||||
|
|
||||||
|
def loads(self, value):
|
||||||
|
"""Load data from a JSON string and deserialized any tagged objects."""
|
||||||
|
return loads(value, object_hook=self.untag)
|
@ -0,0 +1,109 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.logging
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from werkzeug.local import LocalProxy
|
||||||
|
|
||||||
|
from .globals import request
|
||||||
|
|
||||||
|
|
||||||
|
@LocalProxy
|
||||||
|
def wsgi_errors_stream():
|
||||||
|
"""Find the most appropriate error stream for the application. If a request
|
||||||
|
is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``.
|
||||||
|
|
||||||
|
If you configure your own :class:`logging.StreamHandler`, you may want to
|
||||||
|
use this for the stream. If you are using file or dict configuration and
|
||||||
|
can't import this directly, you can refer to it as
|
||||||
|
``ext://flask.logging.wsgi_errors_stream``.
|
||||||
|
"""
|
||||||
|
return request.environ["wsgi.errors"] if request else sys.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def has_level_handler(logger):
|
||||||
|
"""Check if there is a handler in the logging chain that will handle the
|
||||||
|
given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`.
|
||||||
|
"""
|
||||||
|
level = logger.getEffectiveLevel()
|
||||||
|
current = logger
|
||||||
|
|
||||||
|
while current:
|
||||||
|
if any(handler.level <= level for handler in current.handlers):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not current.propagate:
|
||||||
|
break
|
||||||
|
|
||||||
|
current = current.parent
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format
|
||||||
|
#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``.
|
||||||
|
default_handler = logging.StreamHandler(wsgi_errors_stream)
|
||||||
|
default_handler.setFormatter(
|
||||||
|
logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _has_config(logger):
|
||||||
|
"""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
|
||||||
|
:attr:`app.import_name <flask.Flask.name>`.
|
||||||
|
|
||||||
|
When :attr:`~flask.Flask.debug` is enabled, set the logger level to
|
||||||
|
:data:`logging.DEBUG` if it is not set.
|
||||||
|
|
||||||
|
If there is no handler for the logger's effective level, add a
|
||||||
|
:class:`~logging.StreamHandler` for
|
||||||
|
:func:`~flask.logging.wsgi_errors_stream` with a basic format.
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
if not has_level_handler(logger):
|
||||||
|
logger.addHandler(default_handler)
|
||||||
|
|
||||||
|
return logger
|
@ -0,0 +1,388 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.sessions
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements cookie based sessions based on itsdangerous.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import warnings
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from itsdangerous import BadSignature
|
||||||
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
|
from werkzeug.datastructures import CallbackDict
|
||||||
|
|
||||||
|
from ._compat import collections_abc
|
||||||
|
from .helpers import is_ip
|
||||||
|
from .helpers import total_seconds
|
||||||
|
from .json.tag import TaggedJSONSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SessionMixin(collections_abc.MutableMapping):
|
||||||
|
"""Expands a basic dictionary with session attributes."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def permanent(self):
|
||||||
|
"""This reflects the ``'_permanent'`` key in the dict."""
|
||||||
|
return self.get("_permanent", False)
|
||||||
|
|
||||||
|
@permanent.setter
|
||||||
|
def permanent(self, value):
|
||||||
|
self["_permanent"] = bool(value)
|
||||||
|
|
||||||
|
#: Some implementations can detect whether a session is newly
|
||||||
|
#: created, but that is not guaranteed. Use with caution. The mixin
|
||||||
|
# default is hard-coded ``False``.
|
||||||
|
new = False
|
||||||
|
|
||||||
|
#: Some implementations can detect changes to the session and set
|
||||||
|
#: this when that happens. The mixin default is hard coded to
|
||||||
|
#: ``True``.
|
||||||
|
modified = True
|
||||||
|
|
||||||
|
#: Some implementations can detect when session data is read or
|
||||||
|
#: written and set this when that happens. The mixin default is hard
|
||||||
|
#: coded to ``True``.
|
||||||
|
accessed = True
|
||||||
|
|
||||||
|
|
||||||
|
class SecureCookieSession(CallbackDict, SessionMixin):
|
||||||
|
"""Base class for sessions based on signed cookies.
|
||||||
|
|
||||||
|
This session backend will set the :attr:`modified` and
|
||||||
|
:attr:`accessed` attributes. It cannot reliably track whether a
|
||||||
|
session is new (vs. empty), so :attr:`new` remains hard coded to
|
||||||
|
``False``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: When data is changed, this is set to ``True``. Only the session
|
||||||
|
#: dictionary itself is tracked; if the session contains mutable
|
||||||
|
#: data (for example a nested dict) then this must be set to
|
||||||
|
#: ``True`` manually when modifying that data. The session cookie
|
||||||
|
#: will only be written to the response if this is ``True``.
|
||||||
|
modified = False
|
||||||
|
|
||||||
|
#: When data is read or written, this is set to ``True``. Used by
|
||||||
|
# :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
|
||||||
|
#: header, which allows caching proxies to cache different pages for
|
||||||
|
#: different users.
|
||||||
|
accessed = False
|
||||||
|
|
||||||
|
def __init__(self, initial=None):
|
||||||
|
def on_update(self):
|
||||||
|
self.modified = True
|
||||||
|
self.accessed = True
|
||||||
|
|
||||||
|
super(SecureCookieSession, self).__init__(initial, on_update)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
self.accessed = True
|
||||||
|
return super(SecureCookieSession, self).__getitem__(key)
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
self.accessed = True
|
||||||
|
return super(SecureCookieSession, self).get(key, default)
|
||||||
|
|
||||||
|
def setdefault(self, key, default=None):
|
||||||
|
self.accessed = True
|
||||||
|
return super(SecureCookieSession, self).setdefault(key, default)
|
||||||
|
|
||||||
|
|
||||||
|
class NullSession(SecureCookieSession):
|
||||||
|
"""Class used to generate nicer error messages if sessions are not
|
||||||
|
available. Will still allow read-only access to the empty session
|
||||||
|
but fail on setting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _fail(self, *args, **kwargs):
|
||||||
|
raise RuntimeError(
|
||||||
|
"The session is unavailable because no secret "
|
||||||
|
"key was set. Set the secret_key on the "
|
||||||
|
"application to something unique and secret."
|
||||||
|
)
|
||||||
|
|
||||||
|
__setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail
|
||||||
|
del _fail
|
||||||
|
|
||||||
|
|
||||||
|
class SessionInterface(object):
|
||||||
|
"""The basic interface you have to implement in order to replace the
|
||||||
|
default session interface which uses werkzeug's securecookie
|
||||||
|
implementation. The only methods you have to implement are
|
||||||
|
:meth:`open_session` and :meth:`save_session`, the others have
|
||||||
|
useful defaults which you don't need to change.
|
||||||
|
|
||||||
|
The session object returned by the :meth:`open_session` method has to
|
||||||
|
provide a dictionary like interface plus the properties and methods
|
||||||
|
from the :class:`SessionMixin`. We recommend just subclassing a dict
|
||||||
|
and adding that mixin::
|
||||||
|
|
||||||
|
class Session(dict, SessionMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
If :meth:`open_session` returns ``None`` Flask will call into
|
||||||
|
:meth:`make_null_session` to create a session that acts as replacement
|
||||||
|
if the session support cannot work because some requirement is not
|
||||||
|
fulfilled. The default :class:`NullSession` class that is created
|
||||||
|
will complain that the secret key was not set.
|
||||||
|
|
||||||
|
To replace the session interface on an application all you have to do
|
||||||
|
is to assign :attr:`flask.Flask.session_interface`::
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.session_interface = MySessionInterface()
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: :meth:`make_null_session` will look here for the class that should
|
||||||
|
#: be created when a null session is requested. Likewise the
|
||||||
|
#: :meth:`is_null_session` method will perform a typecheck against
|
||||||
|
#: this type.
|
||||||
|
null_session_class = NullSession
|
||||||
|
|
||||||
|
#: A flag that indicates if the session interface is pickle based.
|
||||||
|
#: This can be used by Flask extensions to make a decision in regards
|
||||||
|
#: to how to deal with the session object.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.10
|
||||||
|
pickle_based = False
|
||||||
|
|
||||||
|
def make_null_session(self, app):
|
||||||
|
"""Creates a null session which acts as a replacement object if the
|
||||||
|
real session support could not be loaded due to a configuration
|
||||||
|
error. This mainly aids the user experience because the job of the
|
||||||
|
null session is to still support lookup without complaining but
|
||||||
|
modifications are answered with a helpful error message of what
|
||||||
|
failed.
|
||||||
|
|
||||||
|
This creates an instance of :attr:`null_session_class` by default.
|
||||||
|
"""
|
||||||
|
return self.null_session_class()
|
||||||
|
|
||||||
|
def is_null_session(self, obj):
|
||||||
|
"""Checks if a given object is a null session. Null sessions are
|
||||||
|
not asked to be saved.
|
||||||
|
|
||||||
|
This checks if the object is an instance of :attr:`null_session_class`
|
||||||
|
by default.
|
||||||
|
"""
|
||||||
|
return isinstance(obj, self.null_session_class)
|
||||||
|
|
||||||
|
def get_cookie_domain(self, app):
|
||||||
|
"""Returns the domain that should be set for the session cookie.
|
||||||
|
|
||||||
|
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
|
||||||
|
falls back to detecting the domain based on ``SERVER_NAME``.
|
||||||
|
|
||||||
|
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
|
||||||
|
updated to avoid re-running the logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rv = app.config["SESSION_COOKIE_DOMAIN"]
|
||||||
|
|
||||||
|
# set explicitly, or cached from SERVER_NAME detection
|
||||||
|
# if False, return None
|
||||||
|
if rv is not None:
|
||||||
|
return rv if rv else None
|
||||||
|
|
||||||
|
rv = app.config["SERVER_NAME"]
|
||||||
|
|
||||||
|
# server name not set, cache False to return none next time
|
||||||
|
if not rv:
|
||||||
|
app.config["SESSION_COOKIE_DOMAIN"] = False
|
||||||
|
return None
|
||||||
|
|
||||||
|
# chop off the port which is usually not supported by browsers
|
||||||
|
# remove any leading '.' since we'll add that later
|
||||||
|
rv = rv.rsplit(":", 1)[0].lstrip(".")
|
||||||
|
|
||||||
|
if "." not in rv:
|
||||||
|
# Chrome doesn't allow names without a '.'
|
||||||
|
# this should only come up with localhost
|
||||||
|
# hack around this by not setting the name, and show a warning
|
||||||
|
warnings.warn(
|
||||||
|
'"{rv}" is not a valid cookie domain, it must contain a ".".'
|
||||||
|
" Add an entry to your hosts file, for example"
|
||||||
|
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
|
||||||
|
)
|
||||||
|
app.config["SESSION_COOKIE_DOMAIN"] = False
|
||||||
|
return None
|
||||||
|
|
||||||
|
ip = is_ip(rv)
|
||||||
|
|
||||||
|
if ip:
|
||||||
|
warnings.warn(
|
||||||
|
"The session cookie domain is an IP address. This may not work"
|
||||||
|
" as intended in some browsers. Add an entry to your hosts"
|
||||||
|
' file, for example "localhost.localdomain", and use that'
|
||||||
|
" instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# if this is not an ip and app is mounted at the root, allow subdomain
|
||||||
|
# matching by adding a '.' prefix
|
||||||
|
if self.get_cookie_path(app) == "/" and not ip:
|
||||||
|
rv = "." + rv
|
||||||
|
|
||||||
|
app.config["SESSION_COOKIE_DOMAIN"] = rv
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def get_cookie_path(self, app):
|
||||||
|
"""Returns the path for which the cookie should be valid. The
|
||||||
|
default implementation uses the value from the ``SESSION_COOKIE_PATH``
|
||||||
|
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
||||||
|
uses ``/`` if it's ``None``.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"]
|
||||||
|
|
||||||
|
def get_cookie_httponly(self, app):
|
||||||
|
"""Returns True if the session cookie should be httponly. This
|
||||||
|
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
||||||
|
config var.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_HTTPONLY"]
|
||||||
|
|
||||||
|
def get_cookie_secure(self, app):
|
||||||
|
"""Returns True if the cookie should be secure. This currently
|
||||||
|
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_SECURE"]
|
||||||
|
|
||||||
|
def get_cookie_samesite(self, app):
|
||||||
|
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
|
||||||
|
``SameSite`` attribute. This currently just returns the value of
|
||||||
|
the :data:`SESSION_COOKIE_SAMESITE` setting.
|
||||||
|
"""
|
||||||
|
return app.config["SESSION_COOKIE_SAMESITE"]
|
||||||
|
|
||||||
|
def get_expiration_time(self, app, session):
|
||||||
|
"""A helper method that returns an expiration date for the session
|
||||||
|
or ``None`` if the session is linked to the browser session. The
|
||||||
|
default implementation returns now + the permanent session
|
||||||
|
lifetime configured on the application.
|
||||||
|
"""
|
||||||
|
if session.permanent:
|
||||||
|
return datetime.utcnow() + app.permanent_session_lifetime
|
||||||
|
|
||||||
|
def should_set_cookie(self, app, session):
|
||||||
|
"""Used by session backends to determine if a ``Set-Cookie`` header
|
||||||
|
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
|
||||||
|
the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
|
||||||
|
always set.
|
||||||
|
|
||||||
|
This check is usually skipped if the session was deleted.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
|
||||||
|
return session.modified or (
|
||||||
|
session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def open_session(self, app, request):
|
||||||
|
"""This method has to be implemented and must either return ``None``
|
||||||
|
in case the loading failed because of a configuration error or an
|
||||||
|
instance of a session object which implements a dictionary like
|
||||||
|
interface + the methods and attributes on :class:`SessionMixin`.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def save_session(self, app, session, response):
|
||||||
|
"""This is called for actual sessions returned by :meth:`open_session`
|
||||||
|
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
|
||||||
|
that.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
session_json_serializer = TaggedJSONSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
class SecureCookieSessionInterface(SessionInterface):
|
||||||
|
"""The default session interface that stores sessions in signed cookies
|
||||||
|
through the :mod:`itsdangerous` module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: the salt that should be applied on top of the secret key for the
|
||||||
|
#: signing of cookie based sessions.
|
||||||
|
salt = "cookie-session"
|
||||||
|
#: the hash function to use for the signature. The default is sha1
|
||||||
|
digest_method = staticmethod(hashlib.sha1)
|
||||||
|
#: the name of the itsdangerous supported key derivation. The default
|
||||||
|
#: is hmac.
|
||||||
|
key_derivation = "hmac"
|
||||||
|
#: A python serializer for the payload. The default is a compact
|
||||||
|
#: JSON derived serializer with support for some extra Python types
|
||||||
|
#: such as datetime objects or tuples.
|
||||||
|
serializer = session_json_serializer
|
||||||
|
session_class = SecureCookieSession
|
||||||
|
|
||||||
|
def get_signing_serializer(self, app):
|
||||||
|
if not app.secret_key:
|
||||||
|
return None
|
||||||
|
signer_kwargs = dict(
|
||||||
|
key_derivation=self.key_derivation, digest_method=self.digest_method
|
||||||
|
)
|
||||||
|
return URLSafeTimedSerializer(
|
||||||
|
app.secret_key,
|
||||||
|
salt=self.salt,
|
||||||
|
serializer=self.serializer,
|
||||||
|
signer_kwargs=signer_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def open_session(self, app, request):
|
||||||
|
s = self.get_signing_serializer(app)
|
||||||
|
if s is None:
|
||||||
|
return None
|
||||||
|
val = request.cookies.get(app.session_cookie_name)
|
||||||
|
if not val:
|
||||||
|
return self.session_class()
|
||||||
|
max_age = total_seconds(app.permanent_session_lifetime)
|
||||||
|
try:
|
||||||
|
data = s.loads(val, max_age=max_age)
|
||||||
|
return self.session_class(data)
|
||||||
|
except BadSignature:
|
||||||
|
return self.session_class()
|
||||||
|
|
||||||
|
def save_session(self, app, session, response):
|
||||||
|
domain = self.get_cookie_domain(app)
|
||||||
|
path = self.get_cookie_path(app)
|
||||||
|
|
||||||
|
# If the session is modified to be empty, remove the cookie.
|
||||||
|
# If the session is empty, return without setting the cookie.
|
||||||
|
if not session:
|
||||||
|
if session.modified:
|
||||||
|
response.delete_cookie(
|
||||||
|
app.session_cookie_name, domain=domain, path=path
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add a "Vary: Cookie" header if the session was accessed at all.
|
||||||
|
if session.accessed:
|
||||||
|
response.vary.add("Cookie")
|
||||||
|
|
||||||
|
if not self.should_set_cookie(app, session):
|
||||||
|
return
|
||||||
|
|
||||||
|
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)
|
||||||
|
val = self.get_signing_serializer(app).dumps(dict(session))
|
||||||
|
response.set_cookie(
|
||||||
|
app.session_cookie_name,
|
||||||
|
val,
|
||||||
|
expires=expires,
|
||||||
|
httponly=httponly,
|
||||||
|
domain=domain,
|
||||||
|
path=path,
|
||||||
|
secure=secure,
|
||||||
|
samesite=samesite,
|
||||||
|
)
|
@ -0,0 +1,65 @@
|
|||||||
|
# -*- 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:
|
||||||
|
from blinker import Namespace
|
||||||
|
|
||||||
|
signals_available = True
|
||||||
|
except ImportError:
|
||||||
|
signals_available = False
|
||||||
|
|
||||||
|
class Namespace(object):
|
||||||
|
def signal(self, name, doc=None):
|
||||||
|
return _FakeSignal(name, doc)
|
||||||
|
|
||||||
|
class _FakeSignal(object):
|
||||||
|
"""If blinker is unavailable, create a fake class with the same
|
||||||
|
interface that allows sending of signals but will fail with an
|
||||||
|
error on anything else. Instead of doing anything on send, it
|
||||||
|
will just ignore the arguments and do nothing instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, doc=None):
|
||||||
|
self.name = name
|
||||||
|
self.__doc__ = doc
|
||||||
|
|
||||||
|
def send(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _fail(self, *args, **kwargs):
|
||||||
|
raise RuntimeError(
|
||||||
|
"Signalling support is unavailable because the blinker"
|
||||||
|
" library is not installed."
|
||||||
|
)
|
||||||
|
|
||||||
|
connect = connect_via = connected_to = temporarily_connected_to = _fail
|
||||||
|
disconnect = _fail
|
||||||
|
has_receivers_for = receivers_for = _fail
|
||||||
|
del _fail
|
||||||
|
|
||||||
|
|
||||||
|
# The namespace for code signals. If you are not Flask code, do
|
||||||
|
# not put signals in here. Create your own namespace instead.
|
||||||
|
_signals = Namespace()
|
||||||
|
|
||||||
|
|
||||||
|
# Core signals. For usage examples grep the source code or consult
|
||||||
|
# the API documentation in docs/api.rst as well as docs/signals.rst
|
||||||
|
template_rendered = _signals.signal("template-rendered")
|
||||||
|
before_render_template = _signals.signal("before-render-template")
|
||||||
|
request_started = _signals.signal("request-started")
|
||||||
|
request_finished = _signals.signal("request-finished")
|
||||||
|
request_tearing_down = _signals.signal("request-tearing-down")
|
||||||
|
got_request_exception = _signals.signal("got-request-exception")
|
||||||
|
appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
|
||||||
|
appcontext_pushed = _signals.signal("appcontext-pushed")
|
||||||
|
appcontext_popped = _signals.signal("appcontext-popped")
|
||||||
|
message_flashed = _signals.signal("message-flashed")
|
@ -0,0 +1,155 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.templating
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the bridge to Jinja2.
|
||||||
|
|
||||||
|
:copyright: 2010 Pallets
|
||||||
|
:license: BSD-3-Clause
|
||||||
|
"""
|
||||||
|
from jinja2 import BaseLoader
|
||||||
|
from jinja2 import Environment as BaseEnvironment
|
||||||
|
from jinja2 import TemplateNotFound
|
||||||
|
|
||||||
|
from .globals import _app_ctx_stack
|
||||||
|
from .globals import _request_ctx_stack
|
||||||
|
from .signals import before_render_template
|
||||||
|
from .signals import template_rendered
|
||||||
|
|
||||||
|
|
||||||
|
def _default_template_ctx_processor():
|
||||||
|
"""Default template context processor. Injects `request`,
|
||||||
|
`session` and `g`.
|
||||||
|
"""
|
||||||
|
reqctx = _request_ctx_stack.top
|
||||||
|
appctx = _app_ctx_stack.top
|
||||||
|
rv = {}
|
||||||
|
if appctx is not None:
|
||||||
|
rv["g"] = appctx.g
|
||||||
|
if reqctx is not None:
|
||||||
|
rv["request"] = reqctx.request
|
||||||
|
rv["session"] = reqctx.session
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(BaseEnvironment):
|
||||||
|
"""Works like a regular Jinja2 environment but has some additional
|
||||||
|
knowledge of how Flask's blueprint works so that it can prepend the
|
||||||
|
name of the blueprint to referenced templates if necessary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app, **options):
|
||||||
|
if "loader" not in options:
|
||||||
|
options["loader"] = app.create_global_jinja_loader()
|
||||||
|
BaseEnvironment.__init__(self, **options)
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
|
||||||
|
class DispatchingJinjaLoader(BaseLoader):
|
||||||
|
"""A loader that looks for templates in the application and all
|
||||||
|
the blueprint folders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
|
||||||
|
return self._get_source_explained(environment, template)
|
||||||
|
return self._get_source_fast(environment, template)
|
||||||
|
|
||||||
|
def _get_source_explained(self, environment, template):
|
||||||
|
attempts = []
|
||||||
|
trv = None
|
||||||
|
|
||||||
|
for srcobj, loader in self._iter_loaders(template):
|
||||||
|
try:
|
||||||
|
rv = loader.get_source(environment, template)
|
||||||
|
if trv is None:
|
||||||
|
trv = rv
|
||||||
|
except TemplateNotFound:
|
||||||
|
rv = None
|
||||||
|
attempts.append((loader, srcobj, rv))
|
||||||
|
|
||||||
|
from .debughelpers import explain_template_loading_attempts
|
||||||
|
|
||||||
|
explain_template_loading_attempts(self.app, template, attempts)
|
||||||
|
|
||||||
|
if trv is not None:
|
||||||
|
return trv
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def _get_source_fast(self, environment, template):
|
||||||
|
for _srcobj, loader in self._iter_loaders(template):
|
||||||
|
try:
|
||||||
|
return loader.get_source(environment, template)
|
||||||
|
except TemplateNotFound:
|
||||||
|
continue
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def _iter_loaders(self, template):
|
||||||
|
loader = self.app.jinja_loader
|
||||||
|
if loader is not None:
|
||||||
|
yield self.app, loader
|
||||||
|
|
||||||
|
for blueprint in self.app.iter_blueprints():
|
||||||
|
loader = blueprint.jinja_loader
|
||||||
|
if loader is not None:
|
||||||
|
yield blueprint, loader
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
result = set()
|
||||||
|
loader = self.app.jinja_loader
|
||||||
|
if loader is not None:
|
||||||
|
result.update(loader.list_templates())
|
||||||
|
|
||||||
|
for blueprint in self.app.iter_blueprints():
|
||||||
|
loader = blueprint.jinja_loader
|
||||||
|
if loader is not None:
|
||||||
|
for template in loader.list_templates():
|
||||||
|
result.add(template)
|
||||||
|
|
||||||
|
return list(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _render(template, context, app):
|
||||||
|
"""Renders the template and fires the signal"""
|
||||||
|
|
||||||
|
before_render_template.send(app, template=template, context=context)
|
||||||
|
rv = template.render(context)
|
||||||
|
template_rendered.send(app, template=template, context=context)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def render_template(template_name_or_list, **context):
|
||||||
|
"""Renders a template from the template folder with the given
|
||||||
|
context.
|
||||||
|
|
||||||
|
:param template_name_or_list: the name of the template to be
|
||||||
|
rendered, or an iterable with template names
|
||||||
|
the first one existing will be rendered
|
||||||
|
:param context: the variables that should be available in the
|
||||||
|
context of the template.
|
||||||
|
"""
|
||||||
|
ctx = _app_ctx_stack.top
|
||||||
|
ctx.app.update_template_context(context)
|
||||||
|
return _render(
|
||||||
|
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
|
||||||
|
context,
|
||||||
|
ctx.app,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render_template_string(source, **context):
|
||||||
|
"""Renders a template from the given template source string
|
||||||
|
with the given context. Template variables will be autoescaped.
|
||||||
|
|
||||||
|
:param source: the source code of the template to be
|
||||||
|
rendered
|
||||||
|
:param context: the variables that should be available in the
|
||||||
|
context of the template.
|
||||||
|
"""
|
||||||
|
ctx = _app_ctx_stack.top
|
||||||
|
ctx.app.update_template_context(context)
|
||||||
|
return _render(ctx.app.jinja_env.from_string(source), context, ctx.app)
|
@ -0,0 +1,283 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
import werkzeug.test
|
||||||
|
from click.testing import CliRunner
|
||||||
|
from werkzeug.test import Client
|
||||||
|
from werkzeug.urls import url_parse
|
||||||
|
|
||||||
|
from . import _request_ctx_stack
|
||||||
|
from .cli import ScriptInfo
|
||||||
|
from .json import dumps as json_dumps
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
||||||
|
"""An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the
|
||||||
|
application.
|
||||||
|
|
||||||
|
:param app: The Flask application to configure the environment from.
|
||||||
|
:param path: URL path being requested.
|
||||||
|
:param base_url: Base URL where the app is being served, which
|
||||||
|
``path`` is relative to. If not given, built from
|
||||||
|
:data:`PREFERRED_URL_SCHEME`, ``subdomain``,
|
||||||
|
:data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
|
||||||
|
:param subdomain: Subdomain name to append to :data:`SERVER_NAME`.
|
||||||
|
:param url_scheme: Scheme to use instead of
|
||||||
|
:data:`PREFERRED_URL_SCHEME`.
|
||||||
|
:param json: If given, this is serialized as JSON and passed as
|
||||||
|
``data``. Also defaults ``content_type`` to
|
||||||
|
``application/json``.
|
||||||
|
:param args: other positional arguments passed to
|
||||||
|
:class:`~werkzeug.test.EnvironBuilder`.
|
||||||
|
:param kwargs: other keyword arguments passed to
|
||||||
|
:class:`~werkzeug.test.EnvironBuilder`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
app,
|
||||||
|
path="/",
|
||||||
|
base_url=None,
|
||||||
|
subdomain=None,
|
||||||
|
url_scheme=None,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
assert not (base_url or subdomain or url_scheme) or (
|
||||||
|
base_url is not None
|
||||||
|
) != bool(
|
||||||
|
subdomain or url_scheme
|
||||||
|
), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
|
||||||
|
|
||||||
|
if base_url is None:
|
||||||
|
http_host = app.config.get("SERVER_NAME") or "localhost"
|
||||||
|
app_root = app.config["APPLICATION_ROOT"]
|
||||||
|
|
||||||
|
if subdomain:
|
||||||
|
http_host = "{0}.{1}".format(subdomain, http_host)
|
||||||
|
|
||||||
|
if url_scheme is None:
|
||||||
|
url_scheme = app.config["PREFERRED_URL_SCHEME"]
|
||||||
|
|
||||||
|
url = url_parse(path)
|
||||||
|
base_url = "{scheme}://{netloc}/{path}".format(
|
||||||
|
scheme=url.scheme or url_scheme,
|
||||||
|
netloc=url.netloc or http_host,
|
||||||
|
path=app_root.lstrip("/"),
|
||||||
|
)
|
||||||
|
path = url.path
|
||||||
|
|
||||||
|
if url.query:
|
||||||
|
sep = b"?" if isinstance(url.query, bytes) else "?"
|
||||||
|
path += sep + url.query
|
||||||
|
|
||||||
|
self.app = app
|
||||||
|
super(EnvironBuilder, self).__init__(path, base_url, *args, **kwargs)
|
||||||
|
|
||||||
|
def json_dumps(self, obj, **kwargs):
|
||||||
|
"""Serialize ``obj`` to a JSON-formatted string.
|
||||||
|
|
||||||
|
The serialization will be configured according to the config associated
|
||||||
|
with this EnvironBuilder's ``app``.
|
||||||
|
"""
|
||||||
|
kwargs.setdefault("app", self.app)
|
||||||
|
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 1.2. Construct ``flask.testing.EnvironBuilder``
|
||||||
|
directly instead.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
DeprecationWarning(
|
||||||
|
'"make_test_environ_builder()" is deprecated and will be removed '
|
||||||
|
'in 1.2. Construct "flask.testing.EnvironBuilder" directly '
|
||||||
|
"instead."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return EnvironBuilder(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FlaskClient(Client):
|
||||||
|
"""Works like a regular Werkzeug test client but has some knowledge about
|
||||||
|
how Flask works to defer the cleanup of the request context stack to the
|
||||||
|
end of a ``with`` body when used in a ``with`` statement. For general
|
||||||
|
information about how to use this class refer to
|
||||||
|
:class:`werkzeug.test.Client`.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.12
|
||||||
|
`app.test_client()` includes preset default environment, which can be
|
||||||
|
set after instantiation of the `app.test_client()` object in
|
||||||
|
`client.environ_base`.
|
||||||
|
|
||||||
|
Basic usage is outlined in the :ref:`testing` chapter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
preserve_context = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(FlaskClient, self).__init__(*args, **kwargs)
|
||||||
|
self.environ_base = {
|
||||||
|
"REMOTE_ADDR": "127.0.0.1",
|
||||||
|
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__,
|
||||||
|
}
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def session_transaction(self, *args, **kwargs):
|
||||||
|
"""When used in combination with a ``with`` statement this opens a
|
||||||
|
session transaction. This can be used to modify the session that
|
||||||
|
the test client uses. Once the ``with`` block is left the session is
|
||||||
|
stored back.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
with client.session_transaction() as session:
|
||||||
|
session['value'] = 42
|
||||||
|
|
||||||
|
Internally this is implemented by going through a temporary test
|
||||||
|
request context and since session handling could depend on
|
||||||
|
request variables this function accepts the same arguments as
|
||||||
|
:meth:`~flask.Flask.test_request_context` which are directly
|
||||||
|
passed through.
|
||||||
|
"""
|
||||||
|
if self.cookie_jar is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Session transactions only make sense with cookies enabled."
|
||||||
|
)
|
||||||
|
app = self.application
|
||||||
|
environ_overrides = kwargs.setdefault("environ_overrides", {})
|
||||||
|
self.cookie_jar.inject_wsgi(environ_overrides)
|
||||||
|
outer_reqctx = _request_ctx_stack.top
|
||||||
|
with app.test_request_context(*args, **kwargs) as c:
|
||||||
|
session_interface = app.session_interface
|
||||||
|
sess = session_interface.open_session(app, c.request)
|
||||||
|
if sess is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Session backend did not open a session. Check the configuration"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Since we have to open a new request context for the session
|
||||||
|
# handling we want to make sure that we hide out own context
|
||||||
|
# from the caller. By pushing the original request context
|
||||||
|
# (or None) on top of this and popping it we get exactly that
|
||||||
|
# behavior. It's important to not use the push and pop
|
||||||
|
# methods of the actual request context object since that would
|
||||||
|
# mean that cleanup handlers are called
|
||||||
|
_request_ctx_stack.push(outer_reqctx)
|
||||||
|
try:
|
||||||
|
yield sess
|
||||||
|
finally:
|
||||||
|
_request_ctx_stack.pop()
|
||||||
|
|
||||||
|
resp = app.response_class()
|
||||||
|
if not session_interface.is_null_session(sess):
|
||||||
|
session_interface.save_session(app, sess, resp)
|
||||||
|
headers = resp.get_wsgi_headers(c.request.environ)
|
||||||
|
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
||||||
|
|
||||||
|
def open(self, *args, **kwargs):
|
||||||
|
as_tuple = kwargs.pop("as_tuple", False)
|
||||||
|
buffered = kwargs.pop("buffered", False)
|
||||||
|
follow_redirects = kwargs.pop("follow_redirects", False)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not kwargs
|
||||||
|
and len(args) == 1
|
||||||
|
and isinstance(args[0], (werkzeug.test.EnvironBuilder, dict))
|
||||||
|
):
|
||||||
|
environ = self.environ_base.copy()
|
||||||
|
|
||||||
|
if isinstance(args[0], werkzeug.test.EnvironBuilder):
|
||||||
|
environ.update(args[0].get_environ())
|
||||||
|
else:
|
||||||
|
environ.update(args[0])
|
||||||
|
|
||||||
|
environ["flask._preserve_context"] = self.preserve_context
|
||||||
|
else:
|
||||||
|
kwargs.setdefault("environ_overrides", {})[
|
||||||
|
"flask._preserve_context"
|
||||||
|
] = self.preserve_context
|
||||||
|
kwargs.setdefault("environ_base", self.environ_base)
|
||||||
|
builder = EnvironBuilder(self.application, *args, **kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
environ = builder.get_environ()
|
||||||
|
finally:
|
||||||
|
builder.close()
|
||||||
|
|
||||||
|
return Client.open(
|
||||||
|
self,
|
||||||
|
environ,
|
||||||
|
as_tuple=as_tuple,
|
||||||
|
buffered=buffered,
|
||||||
|
follow_redirects=follow_redirects,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if self.preserve_context:
|
||||||
|
raise RuntimeError("Cannot nest client invocations")
|
||||||
|
self.preserve_context = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
self.preserve_context = False
|
||||||
|
|
||||||
|
# Normally the request context is preserved until the next
|
||||||
|
# request in the same thread comes. When the client exits we
|
||||||
|
# want to clean up earlier. Pop request contexts until the stack
|
||||||
|
# is empty or a non-preserved one is found.
|
||||||
|
while True:
|
||||||
|
top = _request_ctx_stack.top
|
||||||
|
|
||||||
|
if top is not None and top.preserved:
|
||||||
|
top.pop()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class FlaskCliRunner(CliRunner):
|
||||||
|
"""A :class:`~click.testing.CliRunner` for testing a Flask app's
|
||||||
|
CLI commands. Typically created using
|
||||||
|
:meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app, **kwargs):
|
||||||
|
self.app = app
|
||||||
|
super(FlaskCliRunner, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def invoke(self, cli=None, args=None, **kwargs):
|
||||||
|
"""Invokes a CLI command in an isolated environment. See
|
||||||
|
:meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for
|
||||||
|
full method documentation. See :ref:`testing-cli` for examples.
|
||||||
|
|
||||||
|
If the ``obj`` argument is not given, passes an instance of
|
||||||
|
:class:`~flask.cli.ScriptInfo` that knows how to load the Flask
|
||||||
|
app being tested.
|
||||||
|
|
||||||
|
:param cli: Command object to invoke. Default is the app's
|
||||||
|
:attr:`~flask.app.Flask.cli` group.
|
||||||
|
:param args: List of strings to invoke the command with.
|
||||||
|
|
||||||
|
:return: a :class:`~click.testing.Result` object.
|
||||||
|
"""
|
||||||
|
if cli is None:
|
||||||
|
cli = self.app.cli
|
||||||
|
|
||||||
|
if "obj" not in kwargs:
|
||||||
|
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
|
||||||
|
|
||||||
|
return super(FlaskCliRunner, self).invoke(cli, args, **kwargs)
|
@ -0,0 +1,163 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
|
||||||
|
http_method_funcs = frozenset(
|
||||||
|
["get", "post", "head", "options", "delete", "put", "trace", "patch"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class View(object):
|
||||||
|
"""Alternative way to use view functions. A subclass has to implement
|
||||||
|
:meth:`dispatch_request` which is called with the view arguments from
|
||||||
|
the URL routing system. If :attr:`methods` is provided the methods
|
||||||
|
do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
|
||||||
|
method explicitly::
|
||||||
|
|
||||||
|
class MyView(View):
|
||||||
|
methods = ['GET']
|
||||||
|
|
||||||
|
def dispatch_request(self, name):
|
||||||
|
return 'Hello %s!' % name
|
||||||
|
|
||||||
|
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
||||||
|
|
||||||
|
When you want to decorate a pluggable view you will have to either do that
|
||||||
|
when the view function is created (by wrapping the return value of
|
||||||
|
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
|
||||||
|
|
||||||
|
class SecretView(View):
|
||||||
|
methods = ['GET']
|
||||||
|
decorators = [superuser_required]
|
||||||
|
|
||||||
|
def dispatch_request(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
The decorators stored in the decorators list are applied one after another
|
||||||
|
when the view function is created. Note that you can *not* use the class
|
||||||
|
based decorators since those would decorate the view class and not the
|
||||||
|
generated view function!
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: A list of methods this view can handle.
|
||||||
|
methods = None
|
||||||
|
|
||||||
|
#: Setting this disables or force-enables the automatic options handling.
|
||||||
|
provide_automatic_options = None
|
||||||
|
|
||||||
|
#: The canonical way to decorate class-based views is to decorate the
|
||||||
|
#: return value of as_view(). However since this moves parts of the
|
||||||
|
#: logic from the class declaration to the place where it's hooked
|
||||||
|
#: into the routing system.
|
||||||
|
#:
|
||||||
|
#: You can place one or more decorators in this list and whenever the
|
||||||
|
#: view function is created the result is automatically decorated.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.8
|
||||||
|
decorators = ()
|
||||||
|
|
||||||
|
def dispatch_request(self):
|
||||||
|
"""Subclasses have to override this method to implement the
|
||||||
|
actual view function code. This method is called with all
|
||||||
|
the arguments from the URL rule.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_view(cls, name, *class_args, **class_kwargs):
|
||||||
|
"""Converts the class into an actual view function that can be used
|
||||||
|
with the routing system. Internally this generates a function on the
|
||||||
|
fly which will instantiate the :class:`View` on each request and call
|
||||||
|
the :meth:`dispatch_request` method on it.
|
||||||
|
|
||||||
|
The arguments passed to :meth:`as_view` are forwarded to the
|
||||||
|
constructor of the class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def view(*args, **kwargs):
|
||||||
|
self = view.view_class(*class_args, **class_kwargs)
|
||||||
|
return self.dispatch_request(*args, **kwargs)
|
||||||
|
|
||||||
|
if cls.decorators:
|
||||||
|
view.__name__ = name
|
||||||
|
view.__module__ = cls.__module__
|
||||||
|
for decorator in cls.decorators:
|
||||||
|
view = decorator(view)
|
||||||
|
|
||||||
|
# We attach the view class to the view function for two reasons:
|
||||||
|
# first of all it allows us to easily figure out what class-based
|
||||||
|
# view this thing came from, secondly it's also used for instantiating
|
||||||
|
# the view class so you can actually replace it with something else
|
||||||
|
# for testing purposes and debugging.
|
||||||
|
view.view_class = cls
|
||||||
|
view.__name__ = name
|
||||||
|
view.__doc__ = cls.__doc__
|
||||||
|
view.__module__ = cls.__module__
|
||||||
|
view.methods = cls.methods
|
||||||
|
view.provide_automatic_options = cls.provide_automatic_options
|
||||||
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
class MethodViewType(type):
|
||||||
|
"""Metaclass for :class:`MethodView` that determines what methods the view
|
||||||
|
defines.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(cls, name, bases, d):
|
||||||
|
super(MethodViewType, cls).__init__(name, bases, d)
|
||||||
|
|
||||||
|
if "methods" not in d:
|
||||||
|
methods = set()
|
||||||
|
|
||||||
|
for base in bases:
|
||||||
|
if getattr(base, "methods", None):
|
||||||
|
methods.update(base.methods)
|
||||||
|
|
||||||
|
for key in http_method_funcs:
|
||||||
|
if hasattr(cls, key):
|
||||||
|
methods.add(key.upper())
|
||||||
|
|
||||||
|
# If we have no method at all in there we don't want to add a
|
||||||
|
# method list. This is for instance the case for the base class
|
||||||
|
# or another subclass of a base method view that does not introduce
|
||||||
|
# new methods.
|
||||||
|
if methods:
|
||||||
|
cls.methods = methods
|
||||||
|
|
||||||
|
|
||||||
|
class MethodView(with_metaclass(MethodViewType, View)):
|
||||||
|
"""A class-based view that dispatches request methods to the corresponding
|
||||||
|
class methods. For example, if you implement a ``get`` method, it will be
|
||||||
|
used to handle ``GET`` requests. ::
|
||||||
|
|
||||||
|
class CounterAPI(MethodView):
|
||||||
|
def get(self):
|
||||||
|
return session.get('counter', 0)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
session['counter'] = session.get('counter', 0) + 1
|
||||||
|
return 'OK'
|
||||||
|
|
||||||
|
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
||||||
|
"""
|
||||||
|
|
||||||
|
def dispatch_request(self, *args, **kwargs):
|
||||||
|
meth = getattr(self, request.method.lower(), None)
|
||||||
|
|
||||||
|
# If the request method is HEAD and we don't have a handler for it
|
||||||
|
# retry with GET.
|
||||||
|
if meth is None and request.method == "HEAD":
|
||||||
|
meth = getattr(self, "get", None)
|
||||||
|
|
||||||
|
assert meth is not None, "Unimplemented method %r" % request.method
|
||||||
|
return meth(*args, **kwargs)
|
@ -0,0 +1,137 @@
|
|||||||
|
# -*- 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.wrappers import Request as RequestBase
|
||||||
|
from werkzeug.wrappers import Response as ResponseBase
|
||||||
|
from werkzeug.wrappers.json import JSONMixin as _JSONMixin
|
||||||
|
|
||||||
|
from . import json
|
||||||
|
from .globals import current_app
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
matched endpoint and view arguments.
|
||||||
|
|
||||||
|
It is what ends up as :class:`~flask.request`. If you want to replace
|
||||||
|
the request object used you can subclass this and set
|
||||||
|
:attr:`~flask.Flask.request_class` to your subclass.
|
||||||
|
|
||||||
|
The request object is a :class:`~werkzeug.wrappers.Request` subclass and
|
||||||
|
provides all of the attributes Werkzeug defines plus a few Flask
|
||||||
|
specific ones.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The internal URL rule that matched the request. This can be
|
||||||
|
#: useful to inspect which methods are allowed for the URL from
|
||||||
|
#: a before/after handler (``request.url_rule.methods``) etc.
|
||||||
|
#: Though if the request's method was invalid for the URL rule,
|
||||||
|
#: the valid list is available in ``routing_exception.valid_methods``
|
||||||
|
#: instead (an attribute of the Werkzeug exception
|
||||||
|
#: :exc:`~werkzeug.exceptions.MethodNotAllowed`)
|
||||||
|
#: because the request was never internally bound.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.6
|
||||||
|
url_rule = None
|
||||||
|
|
||||||
|
#: A dict of view arguments that matched the request. If an exception
|
||||||
|
#: happened when matching, this will be ``None``.
|
||||||
|
view_args = None
|
||||||
|
|
||||||
|
#: If matching the URL failed, this is the exception that will be
|
||||||
|
#: raised / was raised as part of the request handling. This is
|
||||||
|
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
|
||||||
|
#: something similar.
|
||||||
|
routing_exception = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_content_length(self):
|
||||||
|
"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
|
||||||
|
if current_app:
|
||||||
|
return current_app.config["MAX_CONTENT_LENGTH"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint(self):
|
||||||
|
"""The endpoint that matched the request. This in combination with
|
||||||
|
:attr:`view_args` can be used to reconstruct the same or a
|
||||||
|
modified URL. If an exception happened when matching, this will
|
||||||
|
be ``None``.
|
||||||
|
"""
|
||||||
|
if self.url_rule is not None:
|
||||||
|
return self.url_rule.endpoint
|
||||||
|
|
||||||
|
@property
|
||||||
|
def blueprint(self):
|
||||||
|
"""The name of the current blueprint"""
|
||||||
|
if self.url_rule and "." in self.url_rule.endpoint:
|
||||||
|
return self.url_rule.endpoint.rsplit(".", 1)[0]
|
||||||
|
|
||||||
|
def _load_form_data(self):
|
||||||
|
RequestBase._load_form_data(self)
|
||||||
|
|
||||||
|
# In debug mode we're replacing the files multidict with an ad-hoc
|
||||||
|
# subclass that raises a different error for key errors.
|
||||||
|
if (
|
||||||
|
current_app
|
||||||
|
and current_app.debug
|
||||||
|
and self.mimetype != "multipart/form-data"
|
||||||
|
and not self.files
|
||||||
|
):
|
||||||
|
from .debughelpers import attach_enctype_error_multidict
|
||||||
|
|
||||||
|
attach_enctype_error_multidict(self)
|
||||||
|
|
||||||
|
|
||||||
|
class Response(ResponseBase, JSONMixin):
|
||||||
|
"""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
|
||||||
|
default. Quite often you don't have to create this object yourself because
|
||||||
|
:meth:`~flask.Flask.make_response` will take care of that for you.
|
||||||
|
|
||||||
|
If you want to replace the response object used you can subclass this and
|
||||||
|
set :attr:`~flask.Flask.response_class` to your subclass.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
JSON support is added to the response, like the request. This is useful
|
||||||
|
when testing to get the test client response data as JSON.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
|
||||||
|
Added :attr:`max_cookie_size`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
default_mimetype = "text/html"
|
||||||
|
|
||||||
|
def _get_data_for_json(self, cache):
|
||||||
|
return self.get_data()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_cookie_size(self):
|
||||||
|
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
|
||||||
|
|
||||||
|
See :attr:`~werkzeug.wrappers.BaseResponse.max_cookie_size` in
|
||||||
|
Werkzeug's docs.
|
||||||
|
"""
|
||||||
|
if current_app:
|
||||||
|
return current_app.config["MAX_COOKIE_SIZE"]
|
||||||
|
|
||||||
|
# return Werkzeug's default when not in an app context
|
||||||
|
return super(Response, self).max_cookie_size
|
Loading…
Reference in new issue