forked from orbit-oss/flask
Split the App and Blueprint into Sansio and IO parts (#5127)
This commit is contained in:
commit
1d8b53f782
13 changed files with 1832 additions and 1549 deletions
|
|
@ -4,6 +4,8 @@ Version 3.0.0
|
||||||
Unreleased
|
Unreleased
|
||||||
|
|
||||||
- Remove previously deprecated code. :pr:`5223`
|
- Remove previously deprecated code. :pr:`5223`
|
||||||
|
- Restructure the code such that the Flask (app) and Blueprint
|
||||||
|
classes have Sans-IO bases. :pr:`5127`
|
||||||
|
|
||||||
|
|
||||||
Version 2.3.3
|
Version 2.3.3
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from . import json as json
|
from . import json as json
|
||||||
from .app import Flask as Flask
|
from .app import Flask as Flask
|
||||||
from .app import Request as Request
|
|
||||||
from .app import Response as Response
|
|
||||||
from .blueprints import Blueprint as Blueprint
|
from .blueprints import Blueprint as Blueprint
|
||||||
from .config import Config as Config
|
from .config import Config as Config
|
||||||
from .ctx import after_this_request as after_this_request
|
from .ctx import after_this_request as after_this_request
|
||||||
|
|
@ -37,5 +35,7 @@ from .templating import render_template as render_template
|
||||||
from .templating import render_template_string as render_template_string
|
from .templating import render_template_string as render_template_string
|
||||||
from .templating import stream_template as stream_template
|
from .templating import stream_template as stream_template
|
||||||
from .templating import stream_template_string as stream_template_string
|
from .templating import stream_template_string as stream_template_string
|
||||||
|
from .wrappers import Request as Request
|
||||||
|
from .wrappers import Response as Response
|
||||||
|
|
||||||
__version__ = "3.0.0.dev"
|
__version__ = "3.0.0.dev"
|
||||||
|
|
|
||||||
1000
src/flask/app.py
1000
src/flask/app.py
File diff suppressed because it is too large
Load diff
|
|
@ -2,625 +2,90 @@ from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import typing as t
|
import typing as t
|
||||||
from collections import defaultdict
|
from datetime import timedelta
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
from . import typing as ft
|
from .globals import current_app
|
||||||
from .scaffold import _endpoint_from_view_func
|
from .helpers import send_from_directory
|
||||||
from .scaffold import _sentinel
|
from .sansio.blueprints import Blueprint as SansioBlueprint
|
||||||
from .scaffold import Scaffold
|
from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa
|
||||||
from .scaffold import setupmethod
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
from .app import Flask
|
from .wrappers import Response
|
||||||
|
|
||||||
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
|
||||||
T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
|
|
||||||
T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
|
|
||||||
T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
|
|
||||||
T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
|
|
||||||
T_template_context_processor = t.TypeVar(
|
|
||||||
"T_template_context_processor", bound=ft.TemplateContextProcessorCallable
|
|
||||||
)
|
|
||||||
T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
|
|
||||||
T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
|
|
||||||
T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
|
|
||||||
T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
|
|
||||||
T_url_value_preprocessor = t.TypeVar(
|
|
||||||
"T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BlueprintSetupState:
|
class Blueprint(SansioBlueprint):
|
||||||
"""Temporary holder object for registering a blueprint with the
|
def get_send_file_max_age(self, filename: str | None) -> int | None:
|
||||||
application. An instance of this class is created by the
|
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
||||||
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
value for a given file path if it wasn't passed.
|
||||||
to all register callback functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
|
||||||
self,
|
the configuration of :data:`~flask.current_app`. This defaults
|
||||||
blueprint: Blueprint,
|
to ``None``, which tells the browser to use conditional requests
|
||||||
app: Flask,
|
instead of a timed cache, which is usually preferable.
|
||||||
options: t.Any,
|
|
||||||
first_registration: bool,
|
|
||||||
) -> None:
|
|
||||||
#: a reference to the current application
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
#: a reference to the blueprint that created this setup state.
|
Note this is a duplicate of the same method in the Flask
|
||||||
self.blueprint = blueprint
|
class.
|
||||||
|
|
||||||
#: a dictionary with all options that were passed to the
|
.. versionchanged:: 2.0
|
||||||
#: :meth:`~flask.Flask.register_blueprint` method.
|
The default configuration is ``None`` instead of 12 hours.
|
||||||
self.options = options
|
|
||||||
|
|
||||||
#: as blueprints can be registered multiple times with the
|
.. versionadded:: 0.9
|
||||||
#: 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
|
|
||||||
|
|
||||||
self.name = self.options.get("name", blueprint.name)
|
|
||||||
self.name_prefix = self.options.get("name_prefix", "")
|
|
||||||
|
|
||||||
#: A dictionary with URL defaults that is added to each and every
|
|
||||||
#: 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: str,
|
|
||||||
endpoint: str | None = None,
|
|
||||||
view_func: t.Callable | None = None,
|
|
||||||
**options: t.Any,
|
|
||||||
) -> None:
|
|
||||||
"""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:
|
value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
|
||||||
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) # type: ignore
|
|
||||||
defaults = self.url_defaults
|
|
||||||
if "defaults" in options:
|
|
||||||
defaults = dict(defaults, **options.pop("defaults"))
|
|
||||||
|
|
||||||
self.app.add_url_rule(
|
if value is None:
|
||||||
rule,
|
return None
|
||||||
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
|
|
||||||
view_func,
|
if isinstance(value, timedelta):
|
||||||
defaults=defaults,
|
return int(value.total_seconds())
|
||||||
**options,
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def send_static_file(self, filename: str) -> Response:
|
||||||
|
"""The view function used to serve files from
|
||||||
|
:attr:`static_folder`. A route is automatically registered for
|
||||||
|
this view at :attr:`static_url_path` if :attr:`static_folder` is
|
||||||
|
set.
|
||||||
|
|
||||||
|
Note this is a duplicate of the same method in the Flask
|
||||||
|
class.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.has_static_folder:
|
||||||
|
raise RuntimeError("'static_folder' must be set to serve static_files.")
|
||||||
|
|
||||||
|
# send_file only knows to call get_send_file_max_age on the app,
|
||||||
|
# call it here so it works for blueprints too.
|
||||||
|
max_age = self.get_send_file_max_age(filename)
|
||||||
|
return send_from_directory(
|
||||||
|
t.cast(str, self.static_folder), filename, max_age=max_age
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
|
||||||
|
"""Open a resource file relative to :attr:`root_path` for
|
||||||
|
reading.
|
||||||
|
|
||||||
class Blueprint(Scaffold):
|
For example, if the file ``schema.sql`` is next to the file
|
||||||
"""Represents a blueprint, a collection of routes and other
|
``app.py`` where the ``Flask`` app is defined, it can be opened
|
||||||
app-related functions that can be registered on a real application
|
with:
|
||||||
later.
|
|
||||||
|
|
||||||
A blueprint is an object that allows defining application functions
|
.. code-block:: python
|
||||||
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
|
with app.open_resource("schema.sql") as f:
|
||||||
that is called with :class:`~flask.blueprints.BlueprintSetupState`
|
conn.executescript(f.read())
|
||||||
when the blueprint is registered on an application.
|
|
||||||
|
|
||||||
See :doc:`/blueprints` for more information.
|
:param resource: Path to the resource relative to
|
||||||
|
:attr:`root_path`.
|
||||||
|
:param mode: Open the file in this mode. Only reading is
|
||||||
|
supported, valid values are "r" (or "rt") and "rb".
|
||||||
|
|
||||||
:param name: The name of the blueprint. Will be prepended to each
|
Note this is a duplicate of the same method in the Flask
|
||||||
endpoint name.
|
class.
|
||||||
: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 set
|
|
||||||
this based on ``import_name``. In certain situations this
|
|
||||||
automatic detection can fail, so the path can be specified
|
|
||||||
manually instead.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.1.0
|
|
||||||
Blueprints have a ``cli`` group to register nested CLI commands.
|
|
||||||
The ``cli_group`` parameter controls the name of the group under
|
|
||||||
the ``flask`` command.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
"""
|
|
||||||
|
|
||||||
_got_registered_once = False
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
import_name: str,
|
|
||||||
static_folder: str | os.PathLike | None = None,
|
|
||||||
static_url_path: str | None = None,
|
|
||||||
template_folder: str | os.PathLike | None = None,
|
|
||||||
url_prefix: str | None = None,
|
|
||||||
subdomain: str | None = None,
|
|
||||||
url_defaults: dict | None = None,
|
|
||||||
root_path: str | None = None,
|
|
||||||
cli_group: str | None = _sentinel, # type: ignore
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
import_name=import_name,
|
|
||||||
static_folder=static_folder,
|
|
||||||
static_url_path=static_url_path,
|
|
||||||
template_folder=template_folder,
|
|
||||||
root_path=root_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not name:
|
|
||||||
raise ValueError("'name' may not be empty.")
|
|
||||||
|
|
||||||
if "." in name:
|
|
||||||
raise ValueError("'name' may not contain a dot '.' character.")
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self.url_prefix = url_prefix
|
|
||||||
self.subdomain = subdomain
|
|
||||||
self.deferred_functions: list[DeferredSetupFunction] = []
|
|
||||||
|
|
||||||
if url_defaults is None:
|
|
||||||
url_defaults = {}
|
|
||||||
|
|
||||||
self.url_values_defaults = url_defaults
|
|
||||||
self.cli_group = cli_group
|
|
||||||
self._blueprints: list[tuple[Blueprint, dict]] = []
|
|
||||||
|
|
||||||
def _check_setup_finished(self, f_name: str) -> None:
|
|
||||||
if self._got_registered_once:
|
|
||||||
raise AssertionError(
|
|
||||||
f"The setup method '{f_name}' can no longer be called on the blueprint"
|
|
||||||
f" '{self.name}'. It has already been registered at least once, any"
|
|
||||||
" changes will not be applied consistently.\n"
|
|
||||||
"Make sure all imports, decorators, functions, etc. needed to set up"
|
|
||||||
" the blueprint are done before registering it."
|
|
||||||
)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def record(self, func: t.Callable) -> None:
|
|
||||||
"""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.
|
|
||||||
"""
|
"""
|
||||||
self.deferred_functions.append(func)
|
if mode not in {"r", "rt", "rb"}:
|
||||||
|
raise ValueError("Resources can only be opened for reading.")
|
||||||
|
|
||||||
@setupmethod
|
return open(os.path.join(self.root_path, resource), mode)
|
||||||
def record_once(self, func: t.Callable) -> None:
|
|
||||||
"""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: BlueprintSetupState) -> None:
|
|
||||||
if state.first_registration:
|
|
||||||
func(state)
|
|
||||||
|
|
||||||
self.record(update_wrapper(wrapper, func))
|
|
||||||
|
|
||||||
def make_setup_state(
|
|
||||||
self, app: Flask, options: dict, first_registration: bool = False
|
|
||||||
) -> BlueprintSetupState:
|
|
||||||
"""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)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
|
|
||||||
"""Register a :class:`~flask.Blueprint` on this blueprint. Keyword
|
|
||||||
arguments passed to this method will override the defaults set
|
|
||||||
on the blueprint.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0.1
|
|
||||||
The ``name`` option can be used to change the (pre-dotted)
|
|
||||||
name the blueprint is registered with. This allows the same
|
|
||||||
blueprint to be registered multiple times with unique names
|
|
||||||
for ``url_for``.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
"""
|
|
||||||
if blueprint is self:
|
|
||||||
raise ValueError("Cannot register a blueprint on itself")
|
|
||||||
self._blueprints.append((blueprint, options))
|
|
||||||
|
|
||||||
def register(self, app: Flask, options: dict) -> None:
|
|
||||||
"""Called by :meth:`Flask.register_blueprint` to register all
|
|
||||||
views and callbacks registered on the blueprint with the
|
|
||||||
application. Creates a :class:`.BlueprintSetupState` and calls
|
|
||||||
each :meth:`record` callback with it.
|
|
||||||
|
|
||||||
:param app: The application this blueprint is being registered
|
|
||||||
with.
|
|
||||||
:param options: Keyword arguments forwarded from
|
|
||||||
:meth:`~Flask.register_blueprint`.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
Nested blueprints now correctly apply subdomains.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.1
|
|
||||||
Registering the same blueprint with the same name multiple
|
|
||||||
times is an error.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0.1
|
|
||||||
Nested blueprints are registered with their dotted name.
|
|
||||||
This allows different blueprints with the same name to be
|
|
||||||
nested at different locations.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0.1
|
|
||||||
The ``name`` option can be used to change the (pre-dotted)
|
|
||||||
name the blueprint is registered with. This allows the same
|
|
||||||
blueprint to be registered multiple times with unique names
|
|
||||||
for ``url_for``.
|
|
||||||
"""
|
|
||||||
name_prefix = options.get("name_prefix", "")
|
|
||||||
self_name = options.get("name", self.name)
|
|
||||||
name = f"{name_prefix}.{self_name}".lstrip(".")
|
|
||||||
|
|
||||||
if name in app.blueprints:
|
|
||||||
bp_desc = "this" if app.blueprints[name] is self else "a different"
|
|
||||||
existing_at = f" '{name}'" if self_name != name else ""
|
|
||||||
|
|
||||||
raise ValueError(
|
|
||||||
f"The name '{self_name}' is already registered for"
|
|
||||||
f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
|
|
||||||
f" provide a unique name."
|
|
||||||
)
|
|
||||||
|
|
||||||
first_bp_registration = not any(bp is self for bp in app.blueprints.values())
|
|
||||||
first_name_registration = name not in app.blueprints
|
|
||||||
|
|
||||||
app.blueprints[name] = self
|
|
||||||
self._got_registered_once = True
|
|
||||||
state = self.make_setup_state(app, options, first_bp_registration)
|
|
||||||
|
|
||||||
if self.has_static_folder:
|
|
||||||
state.add_url_rule(
|
|
||||||
f"{self.static_url_path}/<path:filename>",
|
|
||||||
view_func=self.send_static_file,
|
|
||||||
endpoint="static",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Merge blueprint data into parent.
|
|
||||||
if first_bp_registration or first_name_registration:
|
|
||||||
|
|
||||||
def extend(bp_dict, parent_dict):
|
|
||||||
for key, values in bp_dict.items():
|
|
||||||
key = name if key is None else f"{name}.{key}"
|
|
||||||
parent_dict[key].extend(values)
|
|
||||||
|
|
||||||
for key, value in self.error_handler_spec.items():
|
|
||||||
key = name if key is None else f"{name}.{key}"
|
|
||||||
value = defaultdict(
|
|
||||||
dict,
|
|
||||||
{
|
|
||||||
code: {
|
|
||||||
exc_class: func for exc_class, func in code_values.items()
|
|
||||||
}
|
|
||||||
for code, code_values in value.items()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
app.error_handler_spec[key] = value
|
|
||||||
|
|
||||||
for endpoint, func in self.view_functions.items():
|
|
||||||
app.view_functions[endpoint] = func
|
|
||||||
|
|
||||||
extend(self.before_request_funcs, app.before_request_funcs)
|
|
||||||
extend(self.after_request_funcs, app.after_request_funcs)
|
|
||||||
extend(
|
|
||||||
self.teardown_request_funcs,
|
|
||||||
app.teardown_request_funcs,
|
|
||||||
)
|
|
||||||
extend(self.url_default_functions, app.url_default_functions)
|
|
||||||
extend(self.url_value_preprocessors, app.url_value_preprocessors)
|
|
||||||
extend(self.template_context_processors, app.template_context_processors)
|
|
||||||
|
|
||||||
for deferred in self.deferred_functions:
|
|
||||||
deferred(state)
|
|
||||||
|
|
||||||
cli_resolved_group = options.get("cli_group", self.cli_group)
|
|
||||||
|
|
||||||
if self.cli.commands:
|
|
||||||
if cli_resolved_group is None:
|
|
||||||
app.cli.commands.update(self.cli.commands)
|
|
||||||
elif cli_resolved_group is _sentinel:
|
|
||||||
self.cli.name = name
|
|
||||||
app.cli.add_command(self.cli)
|
|
||||||
else:
|
|
||||||
self.cli.name = cli_resolved_group
|
|
||||||
app.cli.add_command(self.cli)
|
|
||||||
|
|
||||||
for blueprint, bp_options in self._blueprints:
|
|
||||||
bp_options = bp_options.copy()
|
|
||||||
bp_url_prefix = bp_options.get("url_prefix")
|
|
||||||
bp_subdomain = bp_options.get("subdomain")
|
|
||||||
|
|
||||||
if bp_subdomain is None:
|
|
||||||
bp_subdomain = blueprint.subdomain
|
|
||||||
|
|
||||||
if state.subdomain is not None and bp_subdomain is not None:
|
|
||||||
bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
|
|
||||||
elif bp_subdomain is not None:
|
|
||||||
bp_options["subdomain"] = bp_subdomain
|
|
||||||
elif state.subdomain is not None:
|
|
||||||
bp_options["subdomain"] = state.subdomain
|
|
||||||
|
|
||||||
if bp_url_prefix is None:
|
|
||||||
bp_url_prefix = blueprint.url_prefix
|
|
||||||
|
|
||||||
if state.url_prefix is not None and bp_url_prefix is not None:
|
|
||||||
bp_options["url_prefix"] = (
|
|
||||||
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
|
|
||||||
)
|
|
||||||
elif bp_url_prefix is not None:
|
|
||||||
bp_options["url_prefix"] = bp_url_prefix
|
|
||||||
elif state.url_prefix is not None:
|
|
||||||
bp_options["url_prefix"] = state.url_prefix
|
|
||||||
|
|
||||||
bp_options["name_prefix"] = name
|
|
||||||
blueprint.register(app, bp_options)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def add_url_rule(
|
|
||||||
self,
|
|
||||||
rule: str,
|
|
||||||
endpoint: str | None = None,
|
|
||||||
view_func: ft.RouteCallable | None = None,
|
|
||||||
provide_automatic_options: bool | None = None,
|
|
||||||
**options: t.Any,
|
|
||||||
) -> None:
|
|
||||||
"""Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
|
|
||||||
full documentation.
|
|
||||||
|
|
||||||
The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
|
|
||||||
used with :func:`url_for`, is prefixed with the blueprint's name.
|
|
||||||
"""
|
|
||||||
if endpoint and "." in endpoint:
|
|
||||||
raise ValueError("'endpoint' may not contain a dot '.' character.")
|
|
||||||
|
|
||||||
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
|
|
||||||
raise ValueError("'view_func' name may not contain a dot '.' character.")
|
|
||||||
|
|
||||||
self.record(
|
|
||||||
lambda s: s.add_url_rule(
|
|
||||||
rule,
|
|
||||||
endpoint,
|
|
||||||
view_func,
|
|
||||||
provide_automatic_options=provide_automatic_options,
|
|
||||||
**options,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_template_filter(
|
|
||||||
self, name: str | None = None
|
|
||||||
) -> t.Callable[[T_template_filter], T_template_filter]:
|
|
||||||
"""Register a template filter, available in any template rendered by the
|
|
||||||
application. Equivalent to :meth:`.Flask.template_filter`.
|
|
||||||
|
|
||||||
:param name: the optional name of the filter, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: T_template_filter) -> T_template_filter:
|
|
||||||
self.add_app_template_filter(f, name=name)
|
|
||||||
return f
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def add_app_template_filter(
|
|
||||||
self, f: ft.TemplateFilterCallable, name: str | None = None
|
|
||||||
) -> None:
|
|
||||||
"""Register a template filter, available in any template rendered by the
|
|
||||||
application. Works like the :meth:`app_template_filter` decorator. Equivalent to
|
|
||||||
:meth:`.Flask.add_template_filter`.
|
|
||||||
|
|
||||||
:param name: the optional name of the filter, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def register_template(state: BlueprintSetupState) -> None:
|
|
||||||
state.app.jinja_env.filters[name or f.__name__] = f
|
|
||||||
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_template_test(
|
|
||||||
self, name: str | None = None
|
|
||||||
) -> t.Callable[[T_template_test], T_template_test]:
|
|
||||||
"""Register a template test, available in any template rendered by the
|
|
||||||
application. Equivalent to :meth:`.Flask.template_test`.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the test, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: T_template_test) -> T_template_test:
|
|
||||||
self.add_app_template_test(f, name=name)
|
|
||||||
return f
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def add_app_template_test(
|
|
||||||
self, f: ft.TemplateTestCallable, name: str | None = None
|
|
||||||
) -> None:
|
|
||||||
"""Register a template test, available in any template rendered by the
|
|
||||||
application. Works like the :meth:`app_template_test` decorator. Equivalent to
|
|
||||||
:meth:`.Flask.add_template_test`.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the test, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def register_template(state: BlueprintSetupState) -> None:
|
|
||||||
state.app.jinja_env.tests[name or f.__name__] = f
|
|
||||||
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_template_global(
|
|
||||||
self, name: str | None = None
|
|
||||||
) -> t.Callable[[T_template_global], T_template_global]:
|
|
||||||
"""Register a template global, available in any template rendered by the
|
|
||||||
application. Equivalent to :meth:`.Flask.template_global`.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the global, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: T_template_global) -> T_template_global:
|
|
||||||
self.add_app_template_global(f, name=name)
|
|
||||||
return f
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def add_app_template_global(
|
|
||||||
self, f: ft.TemplateGlobalCallable, name: str | None = None
|
|
||||||
) -> None:
|
|
||||||
"""Register a template global, available in any template rendered by the
|
|
||||||
application. Works like the :meth:`app_template_global` decorator. Equivalent to
|
|
||||||
:meth:`.Flask.add_template_global`.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the global, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def register_template(state: BlueprintSetupState) -> None:
|
|
||||||
state.app.jinja_env.globals[name or f.__name__] = f
|
|
||||||
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def before_app_request(self, f: T_before_request) -> T_before_request:
|
|
||||||
"""Like :meth:`before_request`, but before every request, not only those handled
|
|
||||||
by the blueprint. Equivalent to :meth:`.Flask.before_request`.
|
|
||||||
"""
|
|
||||||
self.record_once(
|
|
||||||
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
|
|
||||||
)
|
|
||||||
return f
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def after_app_request(self, f: T_after_request) -> T_after_request:
|
|
||||||
"""Like :meth:`after_request`, but after every request, not only those handled
|
|
||||||
by the blueprint. Equivalent to :meth:`.Flask.after_request`.
|
|
||||||
"""
|
|
||||||
self.record_once(
|
|
||||||
lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
|
|
||||||
)
|
|
||||||
return f
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def teardown_app_request(self, f: T_teardown) -> T_teardown:
|
|
||||||
"""Like :meth:`teardown_request`, but after every request, not only those
|
|
||||||
handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
|
|
||||||
"""
|
|
||||||
self.record_once(
|
|
||||||
lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
|
|
||||||
)
|
|
||||||
return f
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_context_processor(
|
|
||||||
self, f: T_template_context_processor
|
|
||||||
) -> T_template_context_processor:
|
|
||||||
"""Like :meth:`context_processor`, but for templates rendered by every view, not
|
|
||||||
only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
|
|
||||||
"""
|
|
||||||
self.record_once(
|
|
||||||
lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
|
|
||||||
)
|
|
||||||
return f
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_errorhandler(
|
|
||||||
self, code: type[Exception] | int
|
|
||||||
) -> t.Callable[[T_error_handler], T_error_handler]:
|
|
||||||
"""Like :meth:`errorhandler`, but for every request, not only those handled by
|
|
||||||
the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: T_error_handler) -> T_error_handler:
|
|
||||||
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_url_value_preprocessor(
|
|
||||||
self, f: T_url_value_preprocessor
|
|
||||||
) -> T_url_value_preprocessor:
|
|
||||||
"""Like :meth:`url_value_preprocessor`, but for every request, not only those
|
|
||||||
handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
|
|
||||||
"""
|
|
||||||
self.record_once(
|
|
||||||
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
|
|
||||||
)
|
|
||||||
return f
|
|
||||||
|
|
||||||
@setupmethod
|
|
||||||
def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
|
|
||||||
"""Like :meth:`url_defaults`, but for every request, not only those handled by
|
|
||||||
the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
|
|
||||||
"""
|
|
||||||
self.record_once(
|
|
||||||
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
|
|
||||||
)
|
|
||||||
return f
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ from __future__ import annotations
|
||||||
|
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from .app import Flask
|
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
from .globals import request_ctx
|
from .globals import request_ctx
|
||||||
|
from .sansio.app import App
|
||||||
|
|
||||||
|
|
||||||
class UnexpectedUnicodeError(AssertionError, UnicodeError):
|
class UnexpectedUnicodeError(AssertionError, UnicodeError):
|
||||||
|
|
@ -113,7 +113,7 @@ def _dump_loader_info(loader) -> t.Generator:
|
||||||
yield f"{key}: {value!r}"
|
yield f"{key}: {value!r}"
|
||||||
|
|
||||||
|
|
||||||
def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
|
def explain_template_loading_attempts(app: App, template, attempts) -> None:
|
||||||
"""This should help developers understand what failed"""
|
"""This should help developers understand what failed"""
|
||||||
info = [f"Locating template {template!r}:"]
|
info = [f"Locating template {template!r}:"]
|
||||||
total_found = 0
|
total_found = 0
|
||||||
|
|
@ -122,7 +122,7 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
|
||||||
blueprint = request_ctx.request.blueprint
|
blueprint = request_ctx.request.blueprint
|
||||||
|
|
||||||
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
||||||
if isinstance(srcobj, Flask):
|
if isinstance(srcobj, App):
|
||||||
src_info = f"application {srcobj.import_name!r}"
|
src_info = f"application {srcobj.import_name!r}"
|
||||||
elif isinstance(srcobj, Blueprint):
|
elif isinstance(srcobj, Blueprint):
|
||||||
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
|
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from datetime import date
|
||||||
from werkzeug.http import http_date
|
from werkzeug.http import http_date
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
from ..app import Flask
|
from ..sansio.app import App
|
||||||
from ..wrappers import Response
|
from ..wrappers import Response
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ class JSONProvider:
|
||||||
.. versionadded:: 2.2
|
.. versionadded:: 2.2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app: Flask) -> None:
|
def __init__(self, app: App) -> None:
|
||||||
self._app = weakref.proxy(app)
|
self._app = weakref.proxy(app)
|
||||||
|
|
||||||
def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
|
def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from werkzeug.local import LocalProxy
|
||||||
from .globals import request
|
from .globals import request
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
from .app import Flask
|
from .sansio.app import App
|
||||||
|
|
||||||
|
|
||||||
@LocalProxy
|
@LocalProxy
|
||||||
|
|
@ -52,7 +52,7 @@ default_handler.setFormatter(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_logger(app: Flask) -> logging.Logger:
|
def create_logger(app: App) -> logging.Logger:
|
||||||
"""Get the Flask app's logger and configure it if needed.
|
"""Get the Flask app's logger and configure it if needed.
|
||||||
|
|
||||||
The logger name will be the same as
|
The logger name will be the same as
|
||||||
|
|
|
||||||
6
src/flask/sansio/README.md
Normal file
6
src/flask/sansio/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Sansio
|
||||||
|
|
||||||
|
This folder contains code that can be used by alternative Flask
|
||||||
|
implementations, for example Quart. The code therefore cannot do any
|
||||||
|
IO, nor be part of a likely IO path. Finally this code cannot use the
|
||||||
|
Flask globals.
|
||||||
964
src/flask/sansio/app.py
Normal file
964
src/flask/sansio/app.py
Normal file
|
|
@ -0,0 +1,964 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
from datetime import timedelta
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from werkzeug.exceptions import Aborter
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
|
from werkzeug.exceptions import BadRequestKeyError
|
||||||
|
from werkzeug.routing import BuildError
|
||||||
|
from werkzeug.routing import Map
|
||||||
|
from werkzeug.routing import Rule
|
||||||
|
from werkzeug.sansio.response import Response
|
||||||
|
from werkzeug.utils import cached_property
|
||||||
|
from werkzeug.utils import redirect as _wz_redirect
|
||||||
|
|
||||||
|
from .. import typing as ft
|
||||||
|
from ..config import Config
|
||||||
|
from ..config import ConfigAttribute
|
||||||
|
from ..ctx import _AppCtxGlobals
|
||||||
|
from ..helpers import _split_blueprint_path
|
||||||
|
from ..helpers import get_debug_flag
|
||||||
|
from ..json.provider import DefaultJSONProvider
|
||||||
|
from ..json.provider import JSONProvider
|
||||||
|
from ..logging import create_logger
|
||||||
|
from ..templating import DispatchingJinjaLoader
|
||||||
|
from ..templating import Environment
|
||||||
|
from .scaffold import _endpoint_from_view_func
|
||||||
|
from .scaffold import find_package
|
||||||
|
from .scaffold import Scaffold
|
||||||
|
from .scaffold import setupmethod
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
|
from werkzeug.wrappers import Response as BaseResponse
|
||||||
|
from .blueprints import Blueprint
|
||||||
|
from ..testing import FlaskClient
|
||||||
|
from ..testing import FlaskCliRunner
|
||||||
|
|
||||||
|
T_shell_context_processor = t.TypeVar(
|
||||||
|
"T_shell_context_processor", bound=ft.ShellContextProcessorCallable
|
||||||
|
)
|
||||||
|
T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
|
||||||
|
T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
|
||||||
|
T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
|
||||||
|
T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
|
||||||
|
if value is None or isinstance(value, timedelta):
|
||||||
|
return value
|
||||||
|
|
||||||
|
return timedelta(seconds=value)
|
||||||
|
|
||||||
|
|
||||||
|
class App(Scaffold):
|
||||||
|
"""The flask object implements a WSGI application and acts as the central
|
||||||
|
object. It is passed the name of the module or package of the
|
||||||
|
application. Once it is created it will act as a central registry for
|
||||||
|
the view functions, the URL rules, template configuration and much more.
|
||||||
|
|
||||||
|
The name of the package is used to resolve resources from inside the
|
||||||
|
package or the folder the module is contained in depending on if the
|
||||||
|
package parameter resolves to an actual python package (a folder with
|
||||||
|
an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
|
||||||
|
|
||||||
|
For more information about resource loading, see :func:`open_resource`.
|
||||||
|
|
||||||
|
Usually you create a :class:`Flask` instance in your main module or
|
||||||
|
in the :file:`__init__.py` file of your package like this::
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
.. admonition:: About the First Parameter
|
||||||
|
|
||||||
|
The idea of the first parameter is to give Flask an idea of what
|
||||||
|
belongs to your application. This name is used to find resources
|
||||||
|
on the filesystem, can be used by extensions to improve debugging
|
||||||
|
information and a lot more.
|
||||||
|
|
||||||
|
So it's important what you provide there. If you are using a single
|
||||||
|
module, `__name__` is always the correct value. If you however are
|
||||||
|
using a package, it's usually recommended to hardcode the name of
|
||||||
|
your package there.
|
||||||
|
|
||||||
|
For example if your application is defined in :file:`yourapplication/app.py`
|
||||||
|
you should create it with one of the two versions below::
|
||||||
|
|
||||||
|
app = Flask('yourapplication')
|
||||||
|
app = Flask(__name__.split('.')[0])
|
||||||
|
|
||||||
|
Why is that? The application will work even with `__name__`, thanks
|
||||||
|
to how resources are looked up. However it will make debugging more
|
||||||
|
painful. Certain extensions can make assumptions based on the
|
||||||
|
import name of your application. For example the Flask-SQLAlchemy
|
||||||
|
extension will look for the code in your application that triggered
|
||||||
|
an SQL query in debug mode. If the import name is not properly set
|
||||||
|
up, that debugging information is lost. (For example it would only
|
||||||
|
pick up SQL queries in `yourapplication.app` and not
|
||||||
|
`yourapplication.views.frontend`)
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
The `static_url_path`, `static_folder`, and `template_folder`
|
||||||
|
parameters were added.
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
The `instance_path` and `instance_relative_config` parameters were
|
||||||
|
added.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
The `root_path` parameter was added.
|
||||||
|
|
||||||
|
.. versionadded:: 1.0
|
||||||
|
The ``host_matching`` and ``static_host`` parameters were added.
|
||||||
|
|
||||||
|
.. versionadded:: 1.0
|
||||||
|
The ``subdomain_matching`` parameter was added. Subdomain
|
||||||
|
matching needs to be enabled manually now. Setting
|
||||||
|
:data:`SERVER_NAME` does not implicitly enable it.
|
||||||
|
|
||||||
|
:param import_name: the name of the application package
|
||||||
|
:param static_url_path: can be used to specify a different path for the
|
||||||
|
static files on the web. Defaults to the name
|
||||||
|
of the `static_folder` folder.
|
||||||
|
:param static_folder: The folder with static files that is served at
|
||||||
|
``static_url_path``. Relative to the application ``root_path``
|
||||||
|
or an absolute path. Defaults to ``'static'``.
|
||||||
|
:param static_host: the host to use when adding the static route.
|
||||||
|
Defaults to None. Required when using ``host_matching=True``
|
||||||
|
with a ``static_folder`` configured.
|
||||||
|
:param host_matching: set ``url_map.host_matching`` attribute.
|
||||||
|
Defaults to False.
|
||||||
|
:param subdomain_matching: consider the subdomain relative to
|
||||||
|
:data:`SERVER_NAME` when matching routes. Defaults to False.
|
||||||
|
:param template_folder: the folder that contains the templates that should
|
||||||
|
be used by the application. Defaults to
|
||||||
|
``'templates'`` folder in the root path of the
|
||||||
|
application.
|
||||||
|
:param instance_path: An alternative instance path for the application.
|
||||||
|
By default the folder ``'instance'`` next to the
|
||||||
|
package or module is assumed to be the instance
|
||||||
|
path.
|
||||||
|
:param instance_relative_config: if set to ``True`` relative filenames
|
||||||
|
for loading the config are assumed to
|
||||||
|
be relative to the instance path instead
|
||||||
|
of the application root.
|
||||||
|
:param root_path: The path to the root of the application files.
|
||||||
|
This should only be set manually when it can't be detected
|
||||||
|
automatically, such as for namespace packages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The class of the object assigned to :attr:`aborter`, created by
|
||||||
|
#: :meth:`create_aborter`. That object is called by
|
||||||
|
#: :func:`flask.abort` to raise HTTP errors, and can be
|
||||||
|
#: called directly as well.
|
||||||
|
#:
|
||||||
|
#: Defaults to :class:`werkzeug.exceptions.Aborter`.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.2
|
||||||
|
aborter_class = Aborter
|
||||||
|
|
||||||
|
#: The class that is used for the Jinja environment.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.11
|
||||||
|
jinja_environment = Environment
|
||||||
|
|
||||||
|
#: The class that is used for the :data:`~flask.g` instance.
|
||||||
|
#:
|
||||||
|
#: Example use cases for a custom class:
|
||||||
|
#:
|
||||||
|
#: 1. Store arbitrary attributes on flask.g.
|
||||||
|
#: 2. Add a property for lazy per-request database connectors.
|
||||||
|
#: 3. Return None instead of AttributeError on unexpected attributes.
|
||||||
|
#: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
|
||||||
|
#:
|
||||||
|
#: In Flask 0.9 this property was called `request_globals_class` but it
|
||||||
|
#: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
|
||||||
|
#: flask.g object is now application context scoped.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.10
|
||||||
|
app_ctx_globals_class = _AppCtxGlobals
|
||||||
|
|
||||||
|
#: The class that is used for the ``config`` attribute of this app.
|
||||||
|
#: Defaults to :class:`~flask.Config`.
|
||||||
|
#:
|
||||||
|
#: Example use cases for a custom class:
|
||||||
|
#:
|
||||||
|
#: 1. Default values for certain config options.
|
||||||
|
#: 2. Access to config values through attributes in addition to keys.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.11
|
||||||
|
config_class = Config
|
||||||
|
|
||||||
|
#: The testing flag. Set this to ``True`` to enable the test mode of
|
||||||
|
#: Flask extensions (and in the future probably also Flask itself).
|
||||||
|
#: For example this might activate test helpers that have an
|
||||||
|
#: additional runtime cost which should not be enabled by default.
|
||||||
|
#:
|
||||||
|
#: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
|
||||||
|
#: default it's implicitly enabled.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: ``TESTING`` configuration key. Defaults to ``False``.
|
||||||
|
testing = ConfigAttribute("TESTING")
|
||||||
|
|
||||||
|
#: If a secret key is set, cryptographic components can use this to
|
||||||
|
#: sign cookies and other things. Set this to a complex random value
|
||||||
|
#: when you want to use the secure cookie for instance.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
|
||||||
|
secret_key = ConfigAttribute("SECRET_KEY")
|
||||||
|
|
||||||
|
#: A :class:`~datetime.timedelta` which is used to set the expiration
|
||||||
|
#: date of a permanent session. The default is 31 days which makes a
|
||||||
|
#: permanent session survive for roughly one month.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to
|
||||||
|
#: ``timedelta(days=31)``
|
||||||
|
permanent_session_lifetime = ConfigAttribute(
|
||||||
|
"PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta
|
||||||
|
)
|
||||||
|
|
||||||
|
json_provider_class: type[JSONProvider] = DefaultJSONProvider
|
||||||
|
"""A subclass of :class:`~flask.json.provider.JSONProvider`. An
|
||||||
|
instance is created and assigned to :attr:`app.json` when creating
|
||||||
|
the app.
|
||||||
|
|
||||||
|
The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
|
||||||
|
Python's built-in :mod:`json` library. A different provider can use
|
||||||
|
a different JSON library.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: Options that are passed to the Jinja environment in
|
||||||
|
#: :meth:`create_jinja_environment`. Changing these options after
|
||||||
|
#: the environment is created (accessing :attr:`jinja_env`) will
|
||||||
|
#: have no effect.
|
||||||
|
#:
|
||||||
|
#: .. versionchanged:: 1.1.0
|
||||||
|
#: This is a ``dict`` instead of an ``ImmutableDict`` to allow
|
||||||
|
#: easier configuration.
|
||||||
|
#:
|
||||||
|
jinja_options: dict = {}
|
||||||
|
|
||||||
|
#: The rule object to use for URL rules created. This is used by
|
||||||
|
#: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.7
|
||||||
|
url_rule_class = Rule
|
||||||
|
|
||||||
|
#: The map object to use for storing the URL rules and routing
|
||||||
|
#: configuration parameters. Defaults to :class:`werkzeug.routing.Map`.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 1.1.0
|
||||||
|
url_map_class = Map
|
||||||
|
|
||||||
|
#: The :meth:`test_client` method creates an instance of this test
|
||||||
|
#: client class. Defaults to :class:`~flask.testing.FlaskClient`.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.7
|
||||||
|
test_client_class: type[FlaskClient] | None = None
|
||||||
|
|
||||||
|
#: The :class:`~click.testing.CliRunner` subclass, by default
|
||||||
|
#: :class:`~flask.testing.FlaskCliRunner` that is used by
|
||||||
|
#: :meth:`test_cli_runner`. Its ``__init__`` method should take a
|
||||||
|
#: Flask app object as the first argument.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 1.0
|
||||||
|
test_cli_runner_class: type[FlaskCliRunner] | None = None
|
||||||
|
|
||||||
|
default_config: dict
|
||||||
|
response_class: type[Response]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
import_name: str,
|
||||||
|
static_url_path: str | None = None,
|
||||||
|
static_folder: str | os.PathLike | None = "static",
|
||||||
|
static_host: str | None = None,
|
||||||
|
host_matching: bool = False,
|
||||||
|
subdomain_matching: bool = False,
|
||||||
|
template_folder: str | os.PathLike | None = "templates",
|
||||||
|
instance_path: str | None = None,
|
||||||
|
instance_relative_config: bool = False,
|
||||||
|
root_path: str | None = None,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
import_name=import_name,
|
||||||
|
static_folder=static_folder,
|
||||||
|
static_url_path=static_url_path,
|
||||||
|
template_folder=template_folder,
|
||||||
|
root_path=root_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
if instance_path is None:
|
||||||
|
instance_path = self.auto_find_instance_path()
|
||||||
|
elif not os.path.isabs(instance_path):
|
||||||
|
raise ValueError(
|
||||||
|
"If an instance path is provided it must be absolute."
|
||||||
|
" A relative path was given instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
#: Holds the path to the instance folder.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.8
|
||||||
|
self.instance_path = instance_path
|
||||||
|
|
||||||
|
#: The configuration dictionary as :class:`Config`. This behaves
|
||||||
|
#: exactly like a regular dictionary but supports additional methods
|
||||||
|
#: to load a config from files.
|
||||||
|
self.config = self.make_config(instance_relative_config)
|
||||||
|
|
||||||
|
#: An instance of :attr:`aborter_class` created by
|
||||||
|
#: :meth:`make_aborter`. This is called by :func:`flask.abort`
|
||||||
|
#: to raise HTTP errors, and can be called directly as well.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.2
|
||||||
|
#: Moved from ``flask.abort``, which calls this object.
|
||||||
|
self.aborter = self.make_aborter()
|
||||||
|
|
||||||
|
self.json: JSONProvider = self.json_provider_class(self)
|
||||||
|
"""Provides access to JSON methods. Functions in ``flask.json``
|
||||||
|
will call methods on this provider when the application context
|
||||||
|
is active. Used for handling JSON requests and responses.
|
||||||
|
|
||||||
|
An instance of :attr:`json_provider_class`. Can be customized by
|
||||||
|
changing that attribute on a subclass, or by assigning to this
|
||||||
|
attribute afterwards.
|
||||||
|
|
||||||
|
The default, :class:`~flask.json.provider.DefaultJSONProvider`,
|
||||||
|
uses Python's built-in :mod:`json` library. A different provider
|
||||||
|
can use a different JSON library.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: A list of functions that are called by
|
||||||
|
#: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
|
||||||
|
#: :exc:`~werkzeug.routing.BuildError`. Each function is called
|
||||||
|
#: with ``error``, ``endpoint`` and ``values``. If a function
|
||||||
|
#: returns ``None`` or raises a ``BuildError``, it is skipped.
|
||||||
|
#: Otherwise, its return value is returned by ``url_for``.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.9
|
||||||
|
self.url_build_error_handlers: list[
|
||||||
|
t.Callable[[Exception, str, dict[str, t.Any]], str]
|
||||||
|
] = []
|
||||||
|
|
||||||
|
#: A list of functions that are called when the application context
|
||||||
|
#: is destroyed. Since the application context is also torn down
|
||||||
|
#: if the request ends this is the place to store code that disconnects
|
||||||
|
#: from databases.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.9
|
||||||
|
self.teardown_appcontext_funcs: list[ft.TeardownCallable] = []
|
||||||
|
|
||||||
|
#: A list of shell context processor functions that should be run
|
||||||
|
#: when a shell context is created.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.11
|
||||||
|
self.shell_context_processors: list[ft.ShellContextProcessorCallable] = []
|
||||||
|
|
||||||
|
#: Maps registered blueprint names to blueprint objects. The
|
||||||
|
#: dict retains the order the blueprints were registered in.
|
||||||
|
#: Blueprints can be registered multiple times, this dict does
|
||||||
|
#: not track how often they were attached.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.7
|
||||||
|
self.blueprints: dict[str, Blueprint] = {}
|
||||||
|
|
||||||
|
#: a place where extensions can store application specific state. For
|
||||||
|
#: example this is where an extension could store database engines and
|
||||||
|
#: similar things.
|
||||||
|
#:
|
||||||
|
#: The key must match the name of the extension module. For example in
|
||||||
|
#: case of a "Flask-Foo" extension in `flask_foo`, the key would be
|
||||||
|
#: ``'foo'``.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.7
|
||||||
|
self.extensions: dict = {}
|
||||||
|
|
||||||
|
#: The :class:`~werkzeug.routing.Map` for this instance. You can use
|
||||||
|
#: this to change the routing converters after the class was created
|
||||||
|
#: but before any routes are connected. Example::
|
||||||
|
#:
|
||||||
|
#: from werkzeug.routing import BaseConverter
|
||||||
|
#:
|
||||||
|
#: class ListConverter(BaseConverter):
|
||||||
|
#: def to_python(self, value):
|
||||||
|
#: return value.split(',')
|
||||||
|
#: def to_url(self, values):
|
||||||
|
#: return ','.join(super(ListConverter, self).to_url(value)
|
||||||
|
#: for value in values)
|
||||||
|
#:
|
||||||
|
#: app = Flask(__name__)
|
||||||
|
#: app.url_map.converters['list'] = ListConverter
|
||||||
|
self.url_map = self.url_map_class(host_matching=host_matching)
|
||||||
|
|
||||||
|
self.subdomain_matching = subdomain_matching
|
||||||
|
|
||||||
|
# tracks internally if the application already handled at least one
|
||||||
|
# request.
|
||||||
|
self._got_first_request = False
|
||||||
|
|
||||||
|
# Set the name of the Click group in case someone wants to add
|
||||||
|
# the app's commands to another CLI tool.
|
||||||
|
self.cli.name = self.name
|
||||||
|
|
||||||
|
def _check_setup_finished(self, f_name: str) -> None:
|
||||||
|
if self._got_first_request:
|
||||||
|
raise AssertionError(
|
||||||
|
f"The setup method '{f_name}' can no longer be called"
|
||||||
|
" on the application. It has already handled its first"
|
||||||
|
" request, any changes will not be applied"
|
||||||
|
" consistently.\n"
|
||||||
|
"Make sure all imports, decorators, functions, etc."
|
||||||
|
" needed to set up the application are done before"
|
||||||
|
" running it."
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def name(self) -> str: # type: ignore
|
||||||
|
"""The name of the application. This is usually the import name
|
||||||
|
with the difference that it's guessed from the run file if the
|
||||||
|
import name is main. This name is used as a display name when
|
||||||
|
Flask needs the name of the application. It can be set and overridden
|
||||||
|
to change the value.
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
"""
|
||||||
|
if self.import_name == "__main__":
|
||||||
|
fn = getattr(sys.modules["__main__"], "__file__", None)
|
||||||
|
if fn is None:
|
||||||
|
return "__main__"
|
||||||
|
return os.path.splitext(os.path.basename(fn))[0]
|
||||||
|
return self.import_name
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def logger(self) -> logging.Logger:
|
||||||
|
"""A standard Python :class:`~logging.Logger` for the app, with
|
||||||
|
the same name as :attr:`name`.
|
||||||
|
|
||||||
|
In debug mode, the logger's :attr:`~logging.Logger.level` will
|
||||||
|
be set to :data:`~logging.DEBUG`.
|
||||||
|
|
||||||
|
If there are no handlers configured, a default handler will be
|
||||||
|
added. See :doc:`/logging` for more information.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.1.0
|
||||||
|
The logger takes the same name as :attr:`name` rather than
|
||||||
|
hard-coding ``"flask.app"``.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0.0
|
||||||
|
Behavior was simplified. The logger is always named
|
||||||
|
``"flask.app"``. The level is only set during configuration,
|
||||||
|
it doesn't check ``app.debug`` each time. Only one format is
|
||||||
|
used, not different ones depending on ``app.debug``. No
|
||||||
|
handlers are removed, and a handler is only added if no
|
||||||
|
handlers are already configured.
|
||||||
|
|
||||||
|
.. versionadded:: 0.3
|
||||||
|
"""
|
||||||
|
return create_logger(self)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def jinja_env(self) -> Environment:
|
||||||
|
"""The Jinja environment used to load templates.
|
||||||
|
|
||||||
|
The environment is created the first time this property is
|
||||||
|
accessed. Changing :attr:`jinja_options` after that will have no
|
||||||
|
effect.
|
||||||
|
"""
|
||||||
|
return self.create_jinja_environment()
|
||||||
|
|
||||||
|
def create_jinja_environment(self) -> Environment:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def make_config(self, instance_relative: bool = False) -> Config:
|
||||||
|
"""Used to create the config attribute by the Flask constructor.
|
||||||
|
The `instance_relative` parameter is passed in from the constructor
|
||||||
|
of Flask (there named `instance_relative_config`) and indicates if
|
||||||
|
the config should be relative to the instance path or the root path
|
||||||
|
of the application.
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
"""
|
||||||
|
root_path = self.root_path
|
||||||
|
if instance_relative:
|
||||||
|
root_path = self.instance_path
|
||||||
|
defaults = dict(self.default_config)
|
||||||
|
defaults["DEBUG"] = get_debug_flag()
|
||||||
|
return self.config_class(root_path, defaults)
|
||||||
|
|
||||||
|
def make_aborter(self) -> Aborter:
|
||||||
|
"""Create the object to assign to :attr:`aborter`. That object
|
||||||
|
is called by :func:`flask.abort` to raise HTTP errors, and can
|
||||||
|
be called directly as well.
|
||||||
|
|
||||||
|
By default, this creates an instance of :attr:`aborter_class`,
|
||||||
|
which defaults to :class:`werkzeug.exceptions.Aborter`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
"""
|
||||||
|
return self.aborter_class()
|
||||||
|
|
||||||
|
def auto_find_instance_path(self) -> str:
|
||||||
|
"""Tries to locate the instance path if it was not provided to the
|
||||||
|
constructor of the application class. It will basically calculate
|
||||||
|
the path to a folder named ``instance`` next to your main file or
|
||||||
|
the package.
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
"""
|
||||||
|
prefix, package_path = find_package(self.import_name)
|
||||||
|
if prefix is None:
|
||||||
|
return os.path.join(package_path, "instance")
|
||||||
|
return os.path.join(prefix, "var", f"{self.name}-instance")
|
||||||
|
|
||||||
|
def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
|
||||||
|
"""Creates the loader for the Jinja2 environment. Can be used to
|
||||||
|
override just the loader and keeping the rest unchanged. It's
|
||||||
|
discouraged to override this function. Instead one should override
|
||||||
|
the :meth:`jinja_loader` function instead.
|
||||||
|
|
||||||
|
The global loader dispatches between the loaders of the application
|
||||||
|
and the individual blueprints.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
return DispatchingJinjaLoader(self)
|
||||||
|
|
||||||
|
def select_jinja_autoescape(self, filename: str) -> bool:
|
||||||
|
"""Returns ``True`` if autoescaping should be active for the given
|
||||||
|
template name. If no template name is given, returns `True`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.2
|
||||||
|
Autoescaping is now enabled by default for ``.svg`` files.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
if filename is None:
|
||||||
|
return True
|
||||||
|
return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def debug(self) -> bool:
|
||||||
|
"""Whether debug mode is enabled. When using ``flask run`` to start the
|
||||||
|
development server, an interactive debugger will be shown for unhandled
|
||||||
|
exceptions, and the server will be reloaded when code changes. This maps to the
|
||||||
|
:data:`DEBUG` config key. It may not behave as expected if set late.
|
||||||
|
|
||||||
|
**Do not enable debug mode when deploying in production.**
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
"""
|
||||||
|
return self.config["DEBUG"]
|
||||||
|
|
||||||
|
@debug.setter
|
||||||
|
def debug(self, value: bool) -> None:
|
||||||
|
self.config["DEBUG"] = value
|
||||||
|
|
||||||
|
if self.config["TEMPLATES_AUTO_RELOAD"] is None:
|
||||||
|
self.jinja_env.auto_reload = value
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
|
||||||
|
"""Register a :class:`~flask.Blueprint` on the application. Keyword
|
||||||
|
arguments passed to this method will override the defaults set on the
|
||||||
|
blueprint.
|
||||||
|
|
||||||
|
Calls the blueprint's :meth:`~flask.Blueprint.register` method after
|
||||||
|
recording the blueprint in the application's :attr:`blueprints`.
|
||||||
|
|
||||||
|
:param blueprint: The blueprint to register.
|
||||||
|
:param url_prefix: Blueprint routes will be prefixed with this.
|
||||||
|
:param subdomain: Blueprint routes will match on this subdomain.
|
||||||
|
:param url_defaults: Blueprint routes will use these default values for
|
||||||
|
view arguments.
|
||||||
|
:param options: Additional keyword arguments are passed to
|
||||||
|
:class:`~flask.blueprints.BlueprintSetupState`. They can be
|
||||||
|
accessed in :meth:`~flask.Blueprint.record` callbacks.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
The ``name`` option can be used to change the (pre-dotted)
|
||||||
|
name the blueprint is registered with. This allows the same
|
||||||
|
blueprint to be registered multiple times with unique names
|
||||||
|
for ``url_for``.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
blueprint.register(self, options)
|
||||||
|
|
||||||
|
def iter_blueprints(self) -> t.ValuesView[Blueprint]:
|
||||||
|
"""Iterates over all blueprints by the order they were registered.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
return self.blueprints.values()
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_url_rule(
|
||||||
|
self,
|
||||||
|
rule: str,
|
||||||
|
endpoint: str | None = None,
|
||||||
|
view_func: ft.RouteCallable | None = None,
|
||||||
|
provide_automatic_options: bool | None = None,
|
||||||
|
**options: t.Any,
|
||||||
|
) -> None:
|
||||||
|
if endpoint is None:
|
||||||
|
endpoint = _endpoint_from_view_func(view_func) # type: ignore
|
||||||
|
options["endpoint"] = endpoint
|
||||||
|
methods = options.pop("methods", None)
|
||||||
|
|
||||||
|
# if the methods are not given and the view_func object knows its
|
||||||
|
# methods we can use that instead. If neither exists, we go with
|
||||||
|
# a tuple of only ``GET`` as default.
|
||||||
|
if methods is None:
|
||||||
|
methods = getattr(view_func, "methods", None) or ("GET",)
|
||||||
|
if isinstance(methods, str):
|
||||||
|
raise TypeError(
|
||||||
|
"Allowed methods must be a list of strings, for"
|
||||||
|
' example: @app.route(..., methods=["POST"])'
|
||||||
|
)
|
||||||
|
methods = {item.upper() for item in methods}
|
||||||
|
|
||||||
|
# Methods that should always be added
|
||||||
|
required_methods = set(getattr(view_func, "required_methods", ()))
|
||||||
|
|
||||||
|
# starting with Flask 0.8 the view_func object can disable and
|
||||||
|
# force-enable the automatic options handling.
|
||||||
|
if provide_automatic_options is None:
|
||||||
|
provide_automatic_options = getattr(
|
||||||
|
view_func, "provide_automatic_options", None
|
||||||
|
)
|
||||||
|
|
||||||
|
if provide_automatic_options is None:
|
||||||
|
if "OPTIONS" not in methods:
|
||||||
|
provide_automatic_options = True
|
||||||
|
required_methods.add("OPTIONS")
|
||||||
|
else:
|
||||||
|
provide_automatic_options = False
|
||||||
|
|
||||||
|
# Add the required methods now.
|
||||||
|
methods |= required_methods
|
||||||
|
|
||||||
|
rule = self.url_rule_class(rule, methods=methods, **options)
|
||||||
|
rule.provide_automatic_options = provide_automatic_options # type: ignore
|
||||||
|
|
||||||
|
self.url_map.add(rule)
|
||||||
|
if view_func is not None:
|
||||||
|
old_func = self.view_functions.get(endpoint)
|
||||||
|
if old_func is not None and old_func != view_func:
|
||||||
|
raise AssertionError(
|
||||||
|
"View function mapping is overwriting an existing"
|
||||||
|
f" endpoint function: {endpoint}"
|
||||||
|
)
|
||||||
|
self.view_functions[endpoint] = view_func
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def template_filter(
|
||||||
|
self, name: str | None = None
|
||||||
|
) -> t.Callable[[T_template_filter], T_template_filter]:
|
||||||
|
"""A decorator that is used to register custom template filter.
|
||||||
|
You can specify a name for the filter, otherwise the function
|
||||||
|
name will be used. Example::
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def reverse(s):
|
||||||
|
return s[::-1]
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_template_filter) -> T_template_filter:
|
||||||
|
self.add_template_filter(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_template_filter(
|
||||||
|
self, f: ft.TemplateFilterCallable, name: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a custom template filter. Works exactly like the
|
||||||
|
:meth:`template_filter` decorator.
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
self.jinja_env.filters[name or f.__name__] = f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def template_test(
|
||||||
|
self, name: str | None = None
|
||||||
|
) -> t.Callable[[T_template_test], T_template_test]:
|
||||||
|
"""A decorator that is used to register custom template test.
|
||||||
|
You can specify a name for the test, otherwise the function
|
||||||
|
name will be used. Example::
|
||||||
|
|
||||||
|
@app.template_test()
|
||||||
|
def is_prime(n):
|
||||||
|
if n == 2:
|
||||||
|
return True
|
||||||
|
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
|
||||||
|
if n % i == 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the test, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_template_test) -> T_template_test:
|
||||||
|
self.add_template_test(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_template_test(
|
||||||
|
self, f: ft.TemplateTestCallable, name: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a custom template test. Works exactly like the
|
||||||
|
:meth:`template_test` decorator.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the test, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
self.jinja_env.tests[name or f.__name__] = f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def template_global(
|
||||||
|
self, name: str | None = None
|
||||||
|
) -> t.Callable[[T_template_global], T_template_global]:
|
||||||
|
"""A decorator that is used to register a custom template global function.
|
||||||
|
You can specify a name for the global function, otherwise the function
|
||||||
|
name will be used. Example::
|
||||||
|
|
||||||
|
@app.template_global()
|
||||||
|
def double(n):
|
||||||
|
return 2 * n
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the global function, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_template_global) -> T_template_global:
|
||||||
|
self.add_template_global(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_template_global(
|
||||||
|
self, f: ft.TemplateGlobalCallable, name: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a custom template global function. Works exactly like the
|
||||||
|
:meth:`template_global` decorator.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the global function, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
self.jinja_env.globals[name or f.__name__] = f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def teardown_appcontext(self, f: T_teardown) -> T_teardown:
|
||||||
|
"""Registers a function to be called when the application
|
||||||
|
context is popped. The application context is typically popped
|
||||||
|
after the request context for each request, at the end of CLI
|
||||||
|
commands, or after a manually pushed context ends.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
...
|
||||||
|
|
||||||
|
When the ``with`` block exits (or ``ctx.pop()`` is called), the
|
||||||
|
teardown functions are called just before the app context is
|
||||||
|
made inactive. Since a request context typically also manages an
|
||||||
|
application context it would also be called when you pop a
|
||||||
|
request context.
|
||||||
|
|
||||||
|
When a teardown function was called because of an unhandled
|
||||||
|
exception it will be passed an error object. If an
|
||||||
|
:meth:`errorhandler` is registered, it will handle the exception
|
||||||
|
and the teardown will not receive it.
|
||||||
|
|
||||||
|
Teardown functions must avoid raising exceptions. If they
|
||||||
|
execute code that might fail they must surround that code with a
|
||||||
|
``try``/``except`` block and log any errors.
|
||||||
|
|
||||||
|
The return values of teardown functions are ignored.
|
||||||
|
|
||||||
|
.. versionadded:: 0.9
|
||||||
|
"""
|
||||||
|
self.teardown_appcontext_funcs.append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def shell_context_processor(
|
||||||
|
self, f: T_shell_context_processor
|
||||||
|
) -> T_shell_context_processor:
|
||||||
|
"""Registers a shell context processor function.
|
||||||
|
|
||||||
|
.. versionadded:: 0.11
|
||||||
|
"""
|
||||||
|
self.shell_context_processors.append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def _find_error_handler(
|
||||||
|
self, e: Exception, blueprints: list[str]
|
||||||
|
) -> ft.ErrorHandlerCallable | None:
|
||||||
|
"""Return a registered error handler for an exception in this order:
|
||||||
|
blueprint handler for a specific code, app handler for a specific code,
|
||||||
|
blueprint handler for an exception class, app handler for an exception
|
||||||
|
class, or ``None`` if a suitable handler is not found.
|
||||||
|
"""
|
||||||
|
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||||
|
names = (*blueprints, None)
|
||||||
|
|
||||||
|
for c in (code, None) if code is not None else (None,):
|
||||||
|
for name in names:
|
||||||
|
handler_map = self.error_handler_spec[name][c]
|
||||||
|
|
||||||
|
if not handler_map:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for cls in exc_class.__mro__:
|
||||||
|
handler = handler_map.get(cls)
|
||||||
|
|
||||||
|
if handler is not None:
|
||||||
|
return handler
|
||||||
|
return None
|
||||||
|
|
||||||
|
def trap_http_exception(self, e: Exception) -> bool:
|
||||||
|
"""Checks if an HTTP exception should be trapped or not. By default
|
||||||
|
this will return ``False`` for all exceptions except for a bad request
|
||||||
|
key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It
|
||||||
|
also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``.
|
||||||
|
|
||||||
|
This is called for all HTTP exceptions raised by a view function.
|
||||||
|
If it returns ``True`` for any exception the error handler for this
|
||||||
|
exception is not called and it shows up as regular exception in the
|
||||||
|
traceback. This is helpful for debugging implicitly raised HTTP
|
||||||
|
exceptions.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
Bad request errors are not trapped by default in debug mode.
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
"""
|
||||||
|
if self.config["TRAP_HTTP_EXCEPTIONS"]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"]
|
||||||
|
|
||||||
|
# if unset, trap key errors in debug mode
|
||||||
|
if (
|
||||||
|
trap_bad_request is None
|
||||||
|
and self.debug
|
||||||
|
and isinstance(e, BadRequestKeyError)
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if trap_bad_request:
|
||||||
|
return isinstance(e, BadRequest)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def should_ignore_error(self, error: BaseException | None) -> bool:
|
||||||
|
"""This is called to figure out if an error should be ignored
|
||||||
|
or not as far as the teardown system is concerned. If this
|
||||||
|
function returns ``True`` then the teardown handlers will not be
|
||||||
|
passed the error.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def redirect(self, location: str, code: int = 302) -> BaseResponse:
|
||||||
|
"""Create a redirect response object.
|
||||||
|
|
||||||
|
This is called by :func:`flask.redirect`, and can be called
|
||||||
|
directly as well.
|
||||||
|
|
||||||
|
:param location: The URL to redirect to.
|
||||||
|
:param code: The status code for the redirect.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
Moved from ``flask.redirect``, which calls this method.
|
||||||
|
"""
|
||||||
|
return _wz_redirect(
|
||||||
|
location, code=code, Response=self.response_class # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
|
||||||
|
def inject_url_defaults(self, endpoint: str, values: dict) -> None:
|
||||||
|
"""Injects the URL defaults for the given endpoint directly into
|
||||||
|
the values dictionary passed. This is used internally and
|
||||||
|
automatically called on URL building.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
names: t.Iterable[str | None] = (None,)
|
||||||
|
|
||||||
|
# url_for may be called outside a request context, parse the
|
||||||
|
# passed endpoint instead of using request.blueprints.
|
||||||
|
if "." in endpoint:
|
||||||
|
names = chain(
|
||||||
|
names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
|
||||||
|
)
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
if name in self.url_default_functions:
|
||||||
|
for func in self.url_default_functions[name]:
|
||||||
|
func(endpoint, values)
|
||||||
|
|
||||||
|
def handle_url_build_error(
|
||||||
|
self, error: BuildError, endpoint: str, values: dict[str, t.Any]
|
||||||
|
) -> str:
|
||||||
|
"""Called by :meth:`.url_for` if a
|
||||||
|
:exc:`~werkzeug.routing.BuildError` was raised. If this returns
|
||||||
|
a value, it will be returned by ``url_for``, otherwise the error
|
||||||
|
will be re-raised.
|
||||||
|
|
||||||
|
Each function in :attr:`url_build_error_handlers` is called with
|
||||||
|
``error``, ``endpoint`` and ``values``. If a function returns
|
||||||
|
``None`` or raises a ``BuildError``, it is skipped. Otherwise,
|
||||||
|
its return value is returned by ``url_for``.
|
||||||
|
|
||||||
|
:param error: The active ``BuildError`` being handled.
|
||||||
|
:param endpoint: The endpoint being built.
|
||||||
|
:param values: The keyword arguments passed to ``url_for``.
|
||||||
|
"""
|
||||||
|
for handler in self.url_build_error_handlers:
|
||||||
|
try:
|
||||||
|
rv = handler(error, endpoint, values)
|
||||||
|
except BuildError as e:
|
||||||
|
# make error available outside except block
|
||||||
|
error = e
|
||||||
|
else:
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
|
||||||
|
# Re-raise if called with an active exception, otherwise raise
|
||||||
|
# the passed in exception.
|
||||||
|
if error is sys.exc_info()[1]:
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise error
|
||||||
626
src/flask/sansio/blueprints.py
Normal file
626
src/flask/sansio/blueprints.py
Normal file
|
|
@ -0,0 +1,626 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import typing as t
|
||||||
|
from collections import defaultdict
|
||||||
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
from .. import typing as ft
|
||||||
|
from .scaffold import _endpoint_from_view_func
|
||||||
|
from .scaffold import _sentinel
|
||||||
|
from .scaffold import Scaffold
|
||||||
|
from .scaffold import setupmethod
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
|
from .app import App
|
||||||
|
|
||||||
|
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
||||||
|
T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
|
||||||
|
T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
|
||||||
|
T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
|
||||||
|
T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
|
||||||
|
T_template_context_processor = t.TypeVar(
|
||||||
|
"T_template_context_processor", bound=ft.TemplateContextProcessorCallable
|
||||||
|
)
|
||||||
|
T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
|
||||||
|
T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
|
||||||
|
T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
|
||||||
|
T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
|
||||||
|
T_url_value_preprocessor = t.TypeVar(
|
||||||
|
"T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BlueprintSetupState:
|
||||||
|
"""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: Blueprint,
|
||||||
|
app: App,
|
||||||
|
options: t.Any,
|
||||||
|
first_registration: bool,
|
||||||
|
) -> None:
|
||||||
|
#: 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
|
||||||
|
|
||||||
|
self.name = self.options.get("name", blueprint.name)
|
||||||
|
self.name_prefix = self.options.get("name_prefix", "")
|
||||||
|
|
||||||
|
#: A dictionary with URL defaults that is added to each and every
|
||||||
|
#: 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: str,
|
||||||
|
endpoint: str | None = None,
|
||||||
|
view_func: t.Callable | None = None,
|
||||||
|
**options: t.Any,
|
||||||
|
) -> None:
|
||||||
|
"""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) # type: ignore
|
||||||
|
defaults = self.url_defaults
|
||||||
|
if "defaults" in options:
|
||||||
|
defaults = dict(defaults, **options.pop("defaults"))
|
||||||
|
|
||||||
|
self.app.add_url_rule(
|
||||||
|
rule,
|
||||||
|
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
|
||||||
|
view_func,
|
||||||
|
defaults=defaults,
|
||||||
|
**options,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Blueprint(Scaffold):
|
||||||
|
"""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 :doc:`/blueprints` for more information.
|
||||||
|
|
||||||
|
: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 set
|
||||||
|
this based on ``import_name``. In certain situations this
|
||||||
|
automatic detection can fail, so the path can be specified
|
||||||
|
manually instead.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.1.0
|
||||||
|
Blueprints have a ``cli`` group to register nested CLI commands.
|
||||||
|
The ``cli_group`` parameter controls the name of the group under
|
||||||
|
the ``flask`` command.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
|
||||||
|
_got_registered_once = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
import_name: str,
|
||||||
|
static_folder: str | os.PathLike | None = None,
|
||||||
|
static_url_path: str | None = None,
|
||||||
|
template_folder: str | os.PathLike | None = None,
|
||||||
|
url_prefix: str | None = None,
|
||||||
|
subdomain: str | None = None,
|
||||||
|
url_defaults: dict | None = None,
|
||||||
|
root_path: str | None = None,
|
||||||
|
cli_group: str | None = _sentinel, # type: ignore
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
import_name=import_name,
|
||||||
|
static_folder=static_folder,
|
||||||
|
static_url_path=static_url_path,
|
||||||
|
template_folder=template_folder,
|
||||||
|
root_path=root_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
raise ValueError("'name' may not be empty.")
|
||||||
|
|
||||||
|
if "." in name:
|
||||||
|
raise ValueError("'name' may not contain a dot '.' character.")
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.url_prefix = url_prefix
|
||||||
|
self.subdomain = subdomain
|
||||||
|
self.deferred_functions: list[DeferredSetupFunction] = []
|
||||||
|
|
||||||
|
if url_defaults is None:
|
||||||
|
url_defaults = {}
|
||||||
|
|
||||||
|
self.url_values_defaults = url_defaults
|
||||||
|
self.cli_group = cli_group
|
||||||
|
self._blueprints: list[tuple[Blueprint, dict]] = []
|
||||||
|
|
||||||
|
def _check_setup_finished(self, f_name: str) -> None:
|
||||||
|
if self._got_registered_once:
|
||||||
|
raise AssertionError(
|
||||||
|
f"The setup method '{f_name}' can no longer be called on the blueprint"
|
||||||
|
f" '{self.name}'. It has already been registered at least once, any"
|
||||||
|
" changes will not be applied consistently.\n"
|
||||||
|
"Make sure all imports, decorators, functions, etc. needed to set up"
|
||||||
|
" the blueprint are done before registering it."
|
||||||
|
)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def record(self, func: t.Callable) -> None:
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
self.deferred_functions.append(func)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def record_once(self, func: t.Callable) -> None:
|
||||||
|
"""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: BlueprintSetupState) -> None:
|
||||||
|
if state.first_registration:
|
||||||
|
func(state)
|
||||||
|
|
||||||
|
self.record(update_wrapper(wrapper, func))
|
||||||
|
|
||||||
|
def make_setup_state(
|
||||||
|
self, app: App, options: dict, first_registration: bool = False
|
||||||
|
) -> BlueprintSetupState:
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
|
||||||
|
"""Register a :class:`~flask.Blueprint` on this blueprint. Keyword
|
||||||
|
arguments passed to this method will override the defaults set
|
||||||
|
on the blueprint.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
The ``name`` option can be used to change the (pre-dotted)
|
||||||
|
name the blueprint is registered with. This allows the same
|
||||||
|
blueprint to be registered multiple times with unique names
|
||||||
|
for ``url_for``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
if blueprint is self:
|
||||||
|
raise ValueError("Cannot register a blueprint on itself")
|
||||||
|
self._blueprints.append((blueprint, options))
|
||||||
|
|
||||||
|
def register(self, app: App, options: dict) -> None:
|
||||||
|
"""Called by :meth:`Flask.register_blueprint` to register all
|
||||||
|
views and callbacks registered on the blueprint with the
|
||||||
|
application. Creates a :class:`.BlueprintSetupState` and calls
|
||||||
|
each :meth:`record` callback with it.
|
||||||
|
|
||||||
|
:param app: The application this blueprint is being registered
|
||||||
|
with.
|
||||||
|
:param options: Keyword arguments forwarded from
|
||||||
|
:meth:`~Flask.register_blueprint`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.3
|
||||||
|
Nested blueprints now correctly apply subdomains.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.1
|
||||||
|
Registering the same blueprint with the same name multiple
|
||||||
|
times is an error.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
Nested blueprints are registered with their dotted name.
|
||||||
|
This allows different blueprints with the same name to be
|
||||||
|
nested at different locations.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
The ``name`` option can be used to change the (pre-dotted)
|
||||||
|
name the blueprint is registered with. This allows the same
|
||||||
|
blueprint to be registered multiple times with unique names
|
||||||
|
for ``url_for``.
|
||||||
|
"""
|
||||||
|
name_prefix = options.get("name_prefix", "")
|
||||||
|
self_name = options.get("name", self.name)
|
||||||
|
name = f"{name_prefix}.{self_name}".lstrip(".")
|
||||||
|
|
||||||
|
if name in app.blueprints:
|
||||||
|
bp_desc = "this" if app.blueprints[name] is self else "a different"
|
||||||
|
existing_at = f" '{name}'" if self_name != name else ""
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
f"The name '{self_name}' is already registered for"
|
||||||
|
f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
|
||||||
|
f" provide a unique name."
|
||||||
|
)
|
||||||
|
|
||||||
|
first_bp_registration = not any(bp is self for bp in app.blueprints.values())
|
||||||
|
first_name_registration = name not in app.blueprints
|
||||||
|
|
||||||
|
app.blueprints[name] = self
|
||||||
|
self._got_registered_once = True
|
||||||
|
state = self.make_setup_state(app, options, first_bp_registration)
|
||||||
|
|
||||||
|
if self.has_static_folder:
|
||||||
|
state.add_url_rule(
|
||||||
|
f"{self.static_url_path}/<path:filename>",
|
||||||
|
view_func=self.send_static_file, # type: ignore[attr-defined]
|
||||||
|
endpoint="static",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Merge blueprint data into parent.
|
||||||
|
if first_bp_registration or first_name_registration:
|
||||||
|
self._merge_blueprint_funcs(app, name)
|
||||||
|
|
||||||
|
for deferred in self.deferred_functions:
|
||||||
|
deferred(state)
|
||||||
|
|
||||||
|
cli_resolved_group = options.get("cli_group", self.cli_group)
|
||||||
|
|
||||||
|
if self.cli.commands:
|
||||||
|
if cli_resolved_group is None:
|
||||||
|
app.cli.commands.update(self.cli.commands)
|
||||||
|
elif cli_resolved_group is _sentinel:
|
||||||
|
self.cli.name = name
|
||||||
|
app.cli.add_command(self.cli)
|
||||||
|
else:
|
||||||
|
self.cli.name = cli_resolved_group
|
||||||
|
app.cli.add_command(self.cli)
|
||||||
|
|
||||||
|
for blueprint, bp_options in self._blueprints:
|
||||||
|
bp_options = bp_options.copy()
|
||||||
|
bp_url_prefix = bp_options.get("url_prefix")
|
||||||
|
bp_subdomain = bp_options.get("subdomain")
|
||||||
|
|
||||||
|
if bp_subdomain is None:
|
||||||
|
bp_subdomain = blueprint.subdomain
|
||||||
|
|
||||||
|
if state.subdomain is not None and bp_subdomain is not None:
|
||||||
|
bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
|
||||||
|
elif bp_subdomain is not None:
|
||||||
|
bp_options["subdomain"] = bp_subdomain
|
||||||
|
elif state.subdomain is not None:
|
||||||
|
bp_options["subdomain"] = state.subdomain
|
||||||
|
|
||||||
|
if bp_url_prefix is None:
|
||||||
|
bp_url_prefix = blueprint.url_prefix
|
||||||
|
|
||||||
|
if state.url_prefix is not None and bp_url_prefix is not None:
|
||||||
|
bp_options["url_prefix"] = (
|
||||||
|
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
|
||||||
|
)
|
||||||
|
elif bp_url_prefix is not None:
|
||||||
|
bp_options["url_prefix"] = bp_url_prefix
|
||||||
|
elif state.url_prefix is not None:
|
||||||
|
bp_options["url_prefix"] = state.url_prefix
|
||||||
|
|
||||||
|
bp_options["name_prefix"] = name
|
||||||
|
blueprint.register(app, bp_options)
|
||||||
|
|
||||||
|
def _merge_blueprint_funcs(self, app: App, name: str) -> None:
|
||||||
|
def extend(bp_dict, parent_dict):
|
||||||
|
for key, values in bp_dict.items():
|
||||||
|
key = name if key is None else f"{name}.{key}"
|
||||||
|
parent_dict[key].extend(values)
|
||||||
|
|
||||||
|
for key, value in self.error_handler_spec.items():
|
||||||
|
key = name if key is None else f"{name}.{key}"
|
||||||
|
value = defaultdict(
|
||||||
|
dict,
|
||||||
|
{
|
||||||
|
code: {exc_class: func for exc_class, func in code_values.items()}
|
||||||
|
for code, code_values in value.items()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
app.error_handler_spec[key] = value
|
||||||
|
|
||||||
|
for endpoint, func in self.view_functions.items():
|
||||||
|
app.view_functions[endpoint] = func
|
||||||
|
|
||||||
|
extend(self.before_request_funcs, app.before_request_funcs)
|
||||||
|
extend(self.after_request_funcs, app.after_request_funcs)
|
||||||
|
extend(
|
||||||
|
self.teardown_request_funcs,
|
||||||
|
app.teardown_request_funcs,
|
||||||
|
)
|
||||||
|
extend(self.url_default_functions, app.url_default_functions)
|
||||||
|
extend(self.url_value_preprocessors, app.url_value_preprocessors)
|
||||||
|
extend(self.template_context_processors, app.template_context_processors)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_url_rule(
|
||||||
|
self,
|
||||||
|
rule: str,
|
||||||
|
endpoint: str | None = None,
|
||||||
|
view_func: ft.RouteCallable | None = None,
|
||||||
|
provide_automatic_options: bool | None = None,
|
||||||
|
**options: t.Any,
|
||||||
|
) -> None:
|
||||||
|
"""Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
|
||||||
|
full documentation.
|
||||||
|
|
||||||
|
The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
|
||||||
|
used with :func:`url_for`, is prefixed with the blueprint's name.
|
||||||
|
"""
|
||||||
|
if endpoint and "." in endpoint:
|
||||||
|
raise ValueError("'endpoint' may not contain a dot '.' character.")
|
||||||
|
|
||||||
|
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
|
||||||
|
raise ValueError("'view_func' name may not contain a dot '.' character.")
|
||||||
|
|
||||||
|
self.record(
|
||||||
|
lambda s: s.add_url_rule(
|
||||||
|
rule,
|
||||||
|
endpoint,
|
||||||
|
view_func,
|
||||||
|
provide_automatic_options=provide_automatic_options,
|
||||||
|
**options,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_template_filter(
|
||||||
|
self, name: str | None = None
|
||||||
|
) -> t.Callable[[T_template_filter], T_template_filter]:
|
||||||
|
"""Register a template filter, available in any template rendered by the
|
||||||
|
application. Equivalent to :meth:`.Flask.template_filter`.
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_template_filter) -> T_template_filter:
|
||||||
|
self.add_app_template_filter(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_app_template_filter(
|
||||||
|
self, f: ft.TemplateFilterCallable, name: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a template filter, available in any template rendered by the
|
||||||
|
application. Works like the :meth:`app_template_filter` decorator. Equivalent to
|
||||||
|
:meth:`.Flask.add_template_filter`.
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_template(state: BlueprintSetupState) -> None:
|
||||||
|
state.app.jinja_env.filters[name or f.__name__] = f
|
||||||
|
|
||||||
|
self.record_once(register_template)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_template_test(
|
||||||
|
self, name: str | None = None
|
||||||
|
) -> t.Callable[[T_template_test], T_template_test]:
|
||||||
|
"""Register a template test, available in any template rendered by the
|
||||||
|
application. Equivalent to :meth:`.Flask.template_test`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the test, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_template_test) -> T_template_test:
|
||||||
|
self.add_app_template_test(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_app_template_test(
|
||||||
|
self, f: ft.TemplateTestCallable, name: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a template test, available in any template rendered by the
|
||||||
|
application. Works like the :meth:`app_template_test` decorator. Equivalent to
|
||||||
|
:meth:`.Flask.add_template_test`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the test, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_template(state: BlueprintSetupState) -> None:
|
||||||
|
state.app.jinja_env.tests[name or f.__name__] = f
|
||||||
|
|
||||||
|
self.record_once(register_template)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_template_global(
|
||||||
|
self, name: str | None = None
|
||||||
|
) -> t.Callable[[T_template_global], T_template_global]:
|
||||||
|
"""Register a template global, available in any template rendered by the
|
||||||
|
application. Equivalent to :meth:`.Flask.template_global`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the global, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_template_global) -> T_template_global:
|
||||||
|
self.add_app_template_global(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def add_app_template_global(
|
||||||
|
self, f: ft.TemplateGlobalCallable, name: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a template global, available in any template rendered by the
|
||||||
|
application. Works like the :meth:`app_template_global` decorator. Equivalent to
|
||||||
|
:meth:`.Flask.add_template_global`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
|
||||||
|
:param name: the optional name of the global, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_template(state: BlueprintSetupState) -> None:
|
||||||
|
state.app.jinja_env.globals[name or f.__name__] = f
|
||||||
|
|
||||||
|
self.record_once(register_template)
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def before_app_request(self, f: T_before_request) -> T_before_request:
|
||||||
|
"""Like :meth:`before_request`, but before every request, not only those handled
|
||||||
|
by the blueprint. Equivalent to :meth:`.Flask.before_request`.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def after_app_request(self, f: T_after_request) -> T_after_request:
|
||||||
|
"""Like :meth:`after_request`, but after every request, not only those handled
|
||||||
|
by the blueprint. Equivalent to :meth:`.Flask.after_request`.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def teardown_app_request(self, f: T_teardown) -> T_teardown:
|
||||||
|
"""Like :meth:`teardown_request`, but after every request, not only those
|
||||||
|
handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_context_processor(
|
||||||
|
self, f: T_template_context_processor
|
||||||
|
) -> T_template_context_processor:
|
||||||
|
"""Like :meth:`context_processor`, but for templates rendered by every view, not
|
||||||
|
only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_errorhandler(
|
||||||
|
self, code: type[Exception] | int
|
||||||
|
) -> t.Callable[[T_error_handler], T_error_handler]:
|
||||||
|
"""Like :meth:`errorhandler`, but for every request, not only those handled by
|
||||||
|
the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: T_error_handler) -> T_error_handler:
|
||||||
|
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_url_value_preprocessor(
|
||||||
|
self, f: T_url_value_preprocessor
|
||||||
|
) -> T_url_value_preprocessor:
|
||||||
|
"""Like :meth:`url_value_preprocessor`, but for every request, not only those
|
||||||
|
handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
|
def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
|
||||||
|
"""Like :meth:`url_defaults`, but for every request, not only those handled by
|
||||||
|
the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
|
||||||
|
"""
|
||||||
|
self.record_once(
|
||||||
|
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
@ -6,7 +6,6 @@ import pathlib
|
||||||
import sys
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import timedelta
|
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
||||||
from jinja2 import FileSystemLoader
|
from jinja2 import FileSystemLoader
|
||||||
|
|
@ -14,15 +13,10 @@ from werkzeug.exceptions import default_exceptions
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
from werkzeug.utils import cached_property
|
from werkzeug.utils import cached_property
|
||||||
|
|
||||||
from . import typing as ft
|
from .. import typing as ft
|
||||||
from .cli import AppGroup
|
from ..cli import AppGroup
|
||||||
from .globals import current_app
|
from ..helpers import get_root_path
|
||||||
from .helpers import get_root_path
|
from ..templating import _default_template_ctx_processor
|
||||||
from .helpers import send_from_directory
|
|
||||||
from .templating import _default_template_ctx_processor
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
|
||||||
from .wrappers import Response
|
|
||||||
|
|
||||||
# a singleton sentinel value for parameter defaults
|
# a singleton sentinel value for parameter defaults
|
||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
@ -276,48 +270,6 @@ class Scaffold:
|
||||||
|
|
||||||
self._static_url_path = value
|
self._static_url_path = value
|
||||||
|
|
||||||
def get_send_file_max_age(self, filename: str | None) -> int | None:
|
|
||||||
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
|
||||||
value for a given file path if it wasn't passed.
|
|
||||||
|
|
||||||
By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
|
|
||||||
the configuration of :data:`~flask.current_app`. This defaults
|
|
||||||
to ``None``, which tells the browser to use conditional requests
|
|
||||||
instead of a timed cache, which is usually preferable.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
The default configuration is ``None`` instead of 12 hours.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
|
|
||||||
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if isinstance(value, timedelta):
|
|
||||||
return int(value.total_seconds())
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def send_static_file(self, filename: str) -> Response:
|
|
||||||
"""The view function used to serve files from
|
|
||||||
:attr:`static_folder`. A route is automatically registered for
|
|
||||||
this view at :attr:`static_url_path` if :attr:`static_folder` is
|
|
||||||
set.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
|
||||||
"""
|
|
||||||
if not self.has_static_folder:
|
|
||||||
raise RuntimeError("'static_folder' must be set to serve static_files.")
|
|
||||||
|
|
||||||
# send_file only knows to call get_send_file_max_age on the app,
|
|
||||||
# call it here so it works for blueprints too.
|
|
||||||
max_age = self.get_send_file_max_age(filename)
|
|
||||||
return send_from_directory(
|
|
||||||
t.cast(str, self.static_folder), filename, max_age=max_age
|
|
||||||
)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def jinja_loader(self) -> FileSystemLoader | None:
|
def jinja_loader(self) -> FileSystemLoader | None:
|
||||||
"""The Jinja loader for this object's templates. By default this
|
"""The Jinja loader for this object's templates. By default this
|
||||||
|
|
@ -331,29 +283,6 @@ class Scaffold:
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
|
|
||||||
"""Open a resource file relative to :attr:`root_path` for
|
|
||||||
reading.
|
|
||||||
|
|
||||||
For example, if the file ``schema.sql`` is next to the file
|
|
||||||
``app.py`` where the ``Flask`` app is defined, it can be opened
|
|
||||||
with:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
with app.open_resource("schema.sql") as f:
|
|
||||||
conn.executescript(f.read())
|
|
||||||
|
|
||||||
:param resource: Path to the resource relative to
|
|
||||||
:attr:`root_path`.
|
|
||||||
:param mode: Open the file in this mode. Only reading is
|
|
||||||
supported, valid values are "r" (or "rt") and "rb".
|
|
||||||
"""
|
|
||||||
if mode not in {"r", "rt", "rb"}:
|
|
||||||
raise ValueError("Resources can only be opened for reading.")
|
|
||||||
|
|
||||||
return open(os.path.join(self.root_path, resource), mode)
|
|
||||||
|
|
||||||
def _method_route(
|
def _method_route(
|
||||||
self,
|
self,
|
||||||
method: str,
|
method: str,
|
||||||
|
|
@ -17,7 +17,8 @@ from .signals import template_rendered
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .scaffold import Scaffold
|
from .sansio.app import App
|
||||||
|
from .sansio.scaffold import Scaffold
|
||||||
|
|
||||||
|
|
||||||
def _default_template_ctx_processor() -> dict[str, t.Any]:
|
def _default_template_ctx_processor() -> dict[str, t.Any]:
|
||||||
|
|
@ -41,7 +42,7 @@ class Environment(BaseEnvironment):
|
||||||
name of the blueprint to referenced templates if necessary.
|
name of the blueprint to referenced templates if necessary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app: Flask, **options: t.Any) -> None:
|
def __init__(self, app: App, **options: t.Any) -> None:
|
||||||
if "loader" not in options:
|
if "loader" not in options:
|
||||||
options["loader"] = app.create_global_jinja_loader()
|
options["loader"] = app.create_global_jinja_loader()
|
||||||
BaseEnvironment.__init__(self, **options)
|
BaseEnvironment.__init__(self, **options)
|
||||||
|
|
@ -53,7 +54,7 @@ class DispatchingJinjaLoader(BaseLoader):
|
||||||
the blueprint folders.
|
the blueprint folders.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app: Flask) -> None:
|
def __init__(self, app: App) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
def get_source( # type: ignore
|
def get_source( # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import typing as t
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
from _typeshed.wsgi import WSGIApplication # noqa: F401
|
from _typeshed.wsgi import WSGIApplication # noqa: F401
|
||||||
from werkzeug.datastructures import Headers # noqa: F401
|
from werkzeug.datastructures import Headers # noqa: F401
|
||||||
from werkzeug.wrappers import Response # noqa: F401
|
from werkzeug.sansio.response import Response # noqa: F401
|
||||||
|
|
||||||
# The possible types that are directly convertible or are a Response object.
|
# The possible types that are directly convertible or are a Response object.
|
||||||
ResponseValue = t.Union[
|
ResponseValue = t.Union[
|
||||||
|
|
@ -61,7 +61,10 @@ TeardownCallable = t.Union[
|
||||||
t.Callable[[t.Optional[BaseException]], None],
|
t.Callable[[t.Optional[BaseException]], None],
|
||||||
t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
|
t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
|
||||||
]
|
]
|
||||||
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
|
TemplateContextProcessorCallable = t.Union[
|
||||||
|
t.Callable[[], t.Dict[str, t.Any]],
|
||||||
|
t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]],
|
||||||
|
]
|
||||||
TemplateFilterCallable = t.Callable[..., t.Any]
|
TemplateFilterCallable = t.Callable[..., t.Any]
|
||||||
TemplateGlobalCallable = t.Callable[..., t.Any]
|
TemplateGlobalCallable = t.Callable[..., t.Any]
|
||||||
TemplateTestCallable = t.Callable[..., bool]
|
TemplateTestCallable = t.Callable[..., bool]
|
||||||
|
|
@ -74,7 +77,10 @@ URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], N
|
||||||
# https://github.com/pallets/flask/issues/4095
|
# https://github.com/pallets/flask/issues/4095
|
||||||
# https://github.com/pallets/flask/issues/4295
|
# https://github.com/pallets/flask/issues/4295
|
||||||
# https://github.com/pallets/flask/issues/4297
|
# https://github.com/pallets/flask/issues/4297
|
||||||
ErrorHandlerCallable = t.Callable[[t.Any], ResponseReturnValue]
|
ErrorHandlerCallable = t.Union[
|
||||||
|
t.Callable[[t.Any], ResponseReturnValue],
|
||||||
|
t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]],
|
||||||
|
]
|
||||||
|
|
||||||
RouteCallable = t.Union[
|
RouteCallable = t.Union[
|
||||||
t.Callable[..., ResponseReturnValue],
|
t.Callable[..., ResponseReturnValue],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue