forked from orbit-oss/flask
Merge branch '2.0.x'
This commit is contained in:
commit
7161776824
10 changed files with 262 additions and 98 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,6 +4,8 @@
|
|||
*.pyc
|
||||
*.pyo
|
||||
env/
|
||||
venv/
|
||||
.venv/
|
||||
env*
|
||||
dist/
|
||||
build/
|
||||
|
|
|
|||
19
CHANGES.rst
19
CHANGES.rst
|
|
@ -8,11 +8,17 @@ Unreleased
|
|||
- Update Click dependency to >= 8.0.
|
||||
|
||||
|
||||
Version 2.0.1
|
||||
Version 2.0.2
|
||||
-------------
|
||||
|
||||
Unreleased
|
||||
|
||||
|
||||
Version 2.0.1
|
||||
-------------
|
||||
|
||||
Released 2021-05-21
|
||||
|
||||
- Re-add the ``filename`` parameter in ``send_from_directory``. The
|
||||
``filename`` parameter has been renamed to ``path``, the old name
|
||||
is deprecated. :pr:`4019`
|
||||
|
|
@ -33,6 +39,17 @@ Unreleased
|
|||
available in custom URL converters. :issue:`4053`
|
||||
- Re-add deprecated ``Config.from_json``, which was accidentally
|
||||
removed early. :issue:`4078`
|
||||
- Improve typing for some functions using ``Callable`` in their type
|
||||
signatures, focusing on decorator factories. :issue:`4060`
|
||||
- Nested blueprints are registered with their dotted name. This allows
|
||||
different blueprints with the same name to be nested at different
|
||||
locations. :issue:`4069`
|
||||
- ``register_blueprint`` takes a ``name`` option 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``. Registering the same blueprint with the same name
|
||||
multiple times is deprecated. :issue:`1091`
|
||||
- Improve typing for ``stream_with_context``. :issue:`4052`
|
||||
|
||||
|
||||
Version 2.0.0
|
||||
|
|
|
|||
|
|
@ -112,6 +112,12 @@ First time setup
|
|||
> py -3 -m venv env
|
||||
> env\Scripts\activate
|
||||
|
||||
- Upgrade pip and setuptools.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python -m pip install --upgrade pip setuptools
|
||||
|
||||
- Install the development dependencies, then install Flask in editable
|
||||
mode.
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ Here is the code for that decorator::
|
|||
def decorated_function(*args, **kwargs):
|
||||
template_name = template
|
||||
if template_name is None:
|
||||
template_name = f"'{request.endpoint.replace('.', '/')}.html'"
|
||||
template_name = f"{request.endpoint.replace('.', '/')}.html"
|
||||
ctx = f(*args, **kwargs)
|
||||
if ctx is None:
|
||||
ctx = {}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ from .globals import _request_ctx_stack
|
|||
from .globals import g
|
||||
from .globals import request
|
||||
from .globals import session
|
||||
from .helpers import _split_blueprint_path
|
||||
from .helpers import get_debug_flag
|
||||
from .helpers import get_env
|
||||
from .helpers import get_flashed_messages
|
||||
|
|
@ -747,7 +748,7 @@ class Flask(Scaffold):
|
|||
] = self.template_context_processors[None]
|
||||
reqctx = _request_ctx_stack.top
|
||||
if reqctx is not None:
|
||||
for bp in self._request_blueprints():
|
||||
for bp in request.blueprints:
|
||||
if bp in self.template_context_processors:
|
||||
funcs = chain(funcs, self.template_context_processors[bp])
|
||||
orig_ctx = context.copy()
|
||||
|
|
@ -1018,6 +1019,12 @@ class Flask(Scaffold):
|
|||
: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)
|
||||
|
|
@ -1089,7 +1096,9 @@ class Flask(Scaffold):
|
|||
self.view_functions[endpoint] = view_func
|
||||
|
||||
@setupmethod
|
||||
def template_filter(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
def template_filter(
|
||||
self, name: t.Optional[str] = None
|
||||
) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]:
|
||||
"""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::
|
||||
|
|
@ -1121,7 +1130,9 @@ class Flask(Scaffold):
|
|||
self.jinja_env.filters[name or f.__name__] = f
|
||||
|
||||
@setupmethod
|
||||
def template_test(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
def template_test(
|
||||
self, name: t.Optional[str] = None
|
||||
) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]:
|
||||
"""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::
|
||||
|
|
@ -1162,7 +1173,9 @@ class Flask(Scaffold):
|
|||
self.jinja_env.tests[name or f.__name__] = f
|
||||
|
||||
@setupmethod
|
||||
def template_global(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
def template_global(
|
||||
self, name: t.Optional[str] = None
|
||||
) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]:
|
||||
"""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::
|
||||
|
|
@ -1261,7 +1274,7 @@ class Flask(Scaffold):
|
|||
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||
|
||||
for c in [code, None]:
|
||||
for name in chain(self._request_blueprints(), [None]):
|
||||
for name in chain(request.blueprints, [None]):
|
||||
handler_map = self.error_handler_spec[name][c]
|
||||
|
||||
if not handler_map:
|
||||
|
|
@ -1782,9 +1795,14 @@ class Flask(Scaffold):
|
|||
.. versionadded:: 0.7
|
||||
"""
|
||||
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
|
||||
|
||||
if "." in endpoint:
|
||||
bp = endpoint.rsplit(".", 1)[0]
|
||||
funcs = chain(funcs, self.url_default_functions[bp])
|
||||
# This is called by url_for, which can be called outside a
|
||||
# request, can't use request.blueprints.
|
||||
bps = _split_blueprint_path(endpoint.rpartition(".")[0])
|
||||
bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps)
|
||||
funcs = chain(funcs, bp_funcs)
|
||||
|
||||
for func in funcs:
|
||||
func(endpoint, values)
|
||||
|
||||
|
|
@ -1825,14 +1843,14 @@ class Flask(Scaffold):
|
|||
funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
|
||||
None
|
||||
]
|
||||
for bp in self._request_blueprints():
|
||||
for bp in request.blueprints:
|
||||
if bp in self.url_value_preprocessors:
|
||||
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
||||
for func in funcs:
|
||||
func(request.endpoint, request.view_args)
|
||||
|
||||
funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
|
||||
for bp in self._request_blueprints():
|
||||
for bp in request.blueprints:
|
||||
if bp in self.before_request_funcs:
|
||||
funcs = chain(funcs, self.before_request_funcs[bp])
|
||||
for func in funcs:
|
||||
|
|
@ -1857,7 +1875,7 @@ class Flask(Scaffold):
|
|||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
|
||||
for bp in self._request_blueprints():
|
||||
for bp in request.blueprints:
|
||||
if bp in self.after_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
||||
if None in self.after_request_funcs:
|
||||
|
|
@ -1896,7 +1914,7 @@ class Flask(Scaffold):
|
|||
funcs: t.Iterable[TeardownCallable] = reversed(
|
||||
self.teardown_request_funcs[None]
|
||||
)
|
||||
for bp in self._request_blueprints():
|
||||
for bp in request.blueprints:
|
||||
if bp in self.teardown_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||
for func in funcs:
|
||||
|
|
@ -2068,9 +2086,3 @@ class Flask(Scaffold):
|
|||
wrapped to apply middleware.
|
||||
"""
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
def _request_blueprints(self) -> t.Iterable[str]:
|
||||
if _request_ctx_stack.top.request.blueprint is None:
|
||||
return []
|
||||
else:
|
||||
return reversed(_request_ctx_stack.top.request.blueprint.split("."))
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class BlueprintSetupState:
|
|||
#: 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
|
||||
|
|
@ -96,9 +97,10 @@ class BlueprintSetupState:
|
|||
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.blueprint.name}.{endpoint}",
|
||||
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
|
||||
view_func,
|
||||
defaults=defaults,
|
||||
**options,
|
||||
|
|
@ -252,8 +254,16 @@ class Blueprint(Scaffold):
|
|||
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:
|
||||
|
|
@ -266,23 +276,48 @@ class Blueprint(Scaffold):
|
|||
with.
|
||||
:param options: Keyword arguments forwarded from
|
||||
:meth:`~Flask.register_blueprint`.
|
||||
:param first_registration: Whether this is the first time this
|
||||
blueprint has been registered on the application.
|
||||
|
||||
.. 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``.
|
||||
|
||||
.. versionchanged:: 2.0.1
|
||||
Registering the same blueprint with the same name multiple
|
||||
times is deprecated and will become an error in Flask 2.1.
|
||||
"""
|
||||
first_registration = False
|
||||
first_registration = not any(bp is self for bp in app.blueprints.values())
|
||||
name_prefix = options.get("name_prefix", "")
|
||||
self_name = options.get("name", self.name)
|
||||
name = f"{name_prefix}.{self_name}".lstrip(".")
|
||||
|
||||
if self.name in app.blueprints:
|
||||
assert app.blueprints[self.name] is self, (
|
||||
"A name collision occurred between blueprints"
|
||||
f" {self!r} and {app.blueprints[self.name]!r}."
|
||||
f" Both share the same name {self.name!r}."
|
||||
f" Blueprints that are created on the fly need unique"
|
||||
f" names."
|
||||
)
|
||||
else:
|
||||
app.blueprints[self.name] = self
|
||||
first_registration = True
|
||||
if name in app.blueprints:
|
||||
existing_at = f" '{name}'" if self_name != name else ""
|
||||
|
||||
if app.blueprints[name] is not self:
|
||||
raise ValueError(
|
||||
f"The name '{self_name}' is already registered for"
|
||||
f" a different blueprint{existing_at}. Use 'name='"
|
||||
" to provide a unique name."
|
||||
)
|
||||
else:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
f"The name '{self_name}' is already registered for"
|
||||
f" this blueprint{existing_at}. Use 'name=' to"
|
||||
" provide a unique name. This will become an error"
|
||||
" in Flask 2.1.",
|
||||
stacklevel=4,
|
||||
)
|
||||
|
||||
app.blueprints[name] = self
|
||||
self._got_registered_once = True
|
||||
state = self.make_setup_state(app, options, first_registration)
|
||||
|
||||
|
|
@ -298,12 +333,11 @@ class Blueprint(Scaffold):
|
|||
|
||||
def extend(bp_dict, parent_dict):
|
||||
for key, values in bp_dict.items():
|
||||
key = self.name if key is None else f"{self.name}.{key}"
|
||||
|
||||
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 = self.name if key is None else f"{self.name}.{key}"
|
||||
key = name if key is None else f"{name}.{key}"
|
||||
value = defaultdict(
|
||||
dict,
|
||||
{
|
||||
|
|
@ -337,7 +371,7 @@ class Blueprint(Scaffold):
|
|||
if cli_resolved_group is None:
|
||||
app.cli.commands.update(self.cli.commands)
|
||||
elif cli_resolved_group is _sentinel:
|
||||
self.cli.name = self.name
|
||||
self.cli.name = name
|
||||
app.cli.add_command(self.cli)
|
||||
else:
|
||||
self.cli.name = cli_resolved_group
|
||||
|
|
@ -354,10 +388,12 @@ class Blueprint(Scaffold):
|
|||
bp_options["url_prefix"] = (
|
||||
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
|
||||
)
|
||||
else:
|
||||
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"] = options.get("name_prefix", "") + self.name + "."
|
||||
bp_options["name_prefix"] = name
|
||||
blueprint.register(app, bp_options)
|
||||
|
||||
def add_url_rule(
|
||||
|
|
@ -365,6 +401,7 @@ class Blueprint(Scaffold):
|
|||
rule: str,
|
||||
endpoint: t.Optional[str] = None,
|
||||
view_func: t.Optional[t.Callable] = None,
|
||||
provide_automatic_options: t.Optional[bool] = None,
|
||||
**options: t.Any,
|
||||
) -> None:
|
||||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
||||
|
|
@ -376,9 +413,19 @@ class Blueprint(Scaffold):
|
|||
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, **options))
|
||||
self.record(
|
||||
lambda s: s.add_url_rule(
|
||||
rule,
|
||||
endpoint,
|
||||
view_func,
|
||||
provide_automatic_options=provide_automatic_options,
|
||||
**options,
|
||||
)
|
||||
)
|
||||
|
||||
def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
def app_template_filter(
|
||||
self, name: t.Optional[str] = None
|
||||
) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]:
|
||||
"""Register a custom template filter, available application wide. Like
|
||||
:meth:`Flask.template_filter` but for a blueprint.
|
||||
|
||||
|
|
@ -408,7 +455,9 @@ class Blueprint(Scaffold):
|
|||
|
||||
self.record_once(register_template)
|
||||
|
||||
def app_template_test(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
def app_template_test(
|
||||
self, name: t.Optional[str] = None
|
||||
) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]:
|
||||
"""Register a custom template test, available application wide. Like
|
||||
:meth:`Flask.template_test` but for a blueprint.
|
||||
|
||||
|
|
@ -442,7 +491,9 @@ class Blueprint(Scaffold):
|
|||
|
||||
self.record_once(register_template)
|
||||
|
||||
def app_template_global(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
def app_template_global(
|
||||
self, name: t.Optional[str] = None
|
||||
) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]:
|
||||
"""Register a custom template global, available application wide. Like
|
||||
:meth:`Flask.template_global` but for a blueprint.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import typing as t
|
|||
import warnings
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from functools import lru_cache
|
||||
from functools import update_wrapper
|
||||
from threading import RLock
|
||||
|
||||
|
|
@ -63,8 +64,10 @@ def get_load_dotenv(default: bool = True) -> bool:
|
|||
|
||||
|
||||
def stream_with_context(
|
||||
generator_or_function: t.Union[t.Generator, t.Callable]
|
||||
) -> t.Generator:
|
||||
generator_or_function: t.Union[
|
||||
t.Iterator[t.AnyStr], t.Callable[..., t.Iterator[t.AnyStr]]
|
||||
]
|
||||
) -> t.Iterator[t.AnyStr]:
|
||||
"""Request contexts disappear when the response is started on the server.
|
||||
This is done for efficiency reasons and to make it less likely to encounter
|
||||
memory leaks with badly written WSGI middlewares. The downside is that if
|
||||
|
|
@ -821,3 +824,13 @@ def is_ip(value: str) -> bool:
|
|||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _split_blueprint_path(name: str) -> t.List[str]:
|
||||
out: t.List[str] = [name]
|
||||
|
||||
if "." in name:
|
||||
out.extend(_split_blueprint_path(name.rpartition(".")[0]))
|
||||
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ if t.TYPE_CHECKING:
|
|||
# a singleton sentinel value for parameter defaults
|
||||
_sentinel = object()
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
def setupmethod(f: t.Callable) -> t.Callable:
|
||||
|
||||
def setupmethod(f: F) -> F:
|
||||
"""Wraps a method so that it performs a check in debug mode if the
|
||||
first request was already handled.
|
||||
"""
|
||||
|
|
@ -53,7 +55,7 @@ def setupmethod(f: t.Callable) -> t.Callable:
|
|||
)
|
||||
return f(self, *args, **kwargs)
|
||||
|
||||
return update_wrapper(wrapper_func, f)
|
||||
return t.cast(F, update_wrapper(wrapper_func, f))
|
||||
|
||||
|
||||
class Scaffold:
|
||||
|
|
@ -443,7 +445,7 @@ class Scaffold:
|
|||
view_func: t.Optional[t.Callable] = None,
|
||||
provide_automatic_options: t.Optional[bool] = None,
|
||||
**options: t.Any,
|
||||
) -> t.Callable:
|
||||
) -> None:
|
||||
"""Register a rule for routing incoming requests and building
|
||||
URLs. The :meth:`route` decorator is a shortcut to call this
|
||||
with the ``view_func`` argument. These are equivalent:
|
||||
|
|
@ -642,7 +644,7 @@ class Scaffold:
|
|||
@setupmethod
|
||||
def errorhandler(
|
||||
self, code_or_exception: t.Union[t.Type[Exception], int]
|
||||
) -> t.Callable:
|
||||
) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]:
|
||||
"""Register a function to handle errors by code or exception class.
|
||||
|
||||
A decorator that is used to register a function given an
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from werkzeug.wrappers import Response as ResponseBase
|
|||
|
||||
from . import json
|
||||
from .globals import current_app
|
||||
from .helpers import _split_blueprint_path
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
|
@ -59,23 +60,54 @@ class Request(RequestBase):
|
|||
|
||||
@property
|
||||
def endpoint(self) -> t.Optional[str]:
|
||||
"""The endpoint that matched the request. This in combination with
|
||||
:attr:`view_args` can be used to reconstruct the same or a
|
||||
modified URL. If an exception happened when matching, this will
|
||||
be ``None``.
|
||||
"""The endpoint that matched the request URL.
|
||||
|
||||
This will be ``None`` if matching failed or has not been
|
||||
performed yet.
|
||||
|
||||
This in combination with :attr:`view_args` can be used to
|
||||
reconstruct the same URL or a modified URL.
|
||||
"""
|
||||
if self.url_rule is not None:
|
||||
return self.url_rule.endpoint
|
||||
else:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def blueprint(self) -> t.Optional[str]:
|
||||
"""The name of the current blueprint"""
|
||||
if self.url_rule and "." in self.url_rule.endpoint:
|
||||
return self.url_rule.endpoint.rsplit(".", 1)[0]
|
||||
else:
|
||||
return None
|
||||
"""The registered name of the current blueprint.
|
||||
|
||||
This will be ``None`` if the endpoint is not part of a
|
||||
blueprint, or if URL matching failed or has not been performed
|
||||
yet.
|
||||
|
||||
This does not necessarily match the name the blueprint was
|
||||
created with. It may have been nested, or registered with a
|
||||
different name.
|
||||
"""
|
||||
endpoint = self.endpoint
|
||||
|
||||
if endpoint is not None and "." in endpoint:
|
||||
return endpoint.rpartition(".")[0]
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def blueprints(self) -> t.List[str]:
|
||||
"""The registered names of the current blueprint upwards through
|
||||
parent blueprints.
|
||||
|
||||
This will be an empty list if there is no current blueprint, or
|
||||
if URL matching failed.
|
||||
|
||||
.. versionadded:: 2.0.1
|
||||
"""
|
||||
name = self.blueprint
|
||||
|
||||
if name is None:
|
||||
return []
|
||||
|
||||
return _split_blueprint_path(name)
|
||||
|
||||
def _load_form_data(self) -> None:
|
||||
RequestBase._load_form_data(self)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ def test_blueprint_url_defaults(app, client):
|
|||
return str(bar)
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
|
||||
app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19})
|
||||
app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19})
|
||||
|
||||
assert client.get("/1/foo").data == b"23/42"
|
||||
assert client.get("/2/foo").data == b"19/42"
|
||||
|
|
@ -837,48 +837,77 @@ def test_nested_blueprint(app, client):
|
|||
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
|
||||
|
||||
|
||||
def test_nested_blueprint_url_prefix(app, client):
|
||||
parent = flask.Blueprint("parent", __name__, url_prefix="/parent")
|
||||
child = flask.Blueprint("child", __name__, url_prefix="/child")
|
||||
grandchild = flask.Blueprint("grandchild", __name__, url_prefix="/grandchild")
|
||||
apple = flask.Blueprint("apple", __name__, url_prefix="/apple")
|
||||
|
||||
@parent.route("/")
|
||||
def parent_index():
|
||||
return "Parent"
|
||||
@pytest.mark.parametrize(
|
||||
"parent_init, child_init, parent_registration, child_registration",
|
||||
[
|
||||
("/parent", "/child", None, None),
|
||||
("/parent", None, None, "/child"),
|
||||
(None, None, "/parent", "/child"),
|
||||
("/other", "/something", "/parent", "/child"),
|
||||
],
|
||||
)
|
||||
def test_nesting_url_prefixes(
|
||||
parent_init,
|
||||
child_init,
|
||||
parent_registration,
|
||||
child_registration,
|
||||
app,
|
||||
client,
|
||||
) -> None:
|
||||
parent = flask.Blueprint("parent", __name__, url_prefix=parent_init)
|
||||
child = flask.Blueprint("child", __name__, url_prefix=child_init)
|
||||
|
||||
@child.route("/")
|
||||
def child_index():
|
||||
return "Child"
|
||||
def index():
|
||||
return "index"
|
||||
|
||||
@grandchild.route("/")
|
||||
def grandchild_index():
|
||||
return "Grandchild"
|
||||
parent.register_blueprint(child, url_prefix=child_registration)
|
||||
app.register_blueprint(parent, url_prefix=parent_registration)
|
||||
|
||||
@apple.route("/")
|
||||
def apple_index():
|
||||
return "Apple"
|
||||
|
||||
child.register_blueprint(grandchild)
|
||||
child.register_blueprint(apple, url_prefix="/orange") # test overwrite
|
||||
parent.register_blueprint(child)
|
||||
app.register_blueprint(parent)
|
||||
|
||||
assert client.get("/parent/").data == b"Parent"
|
||||
assert client.get("/parent/child/").data == b"Child"
|
||||
assert client.get("/parent/child/grandchild/").data == b"Grandchild"
|
||||
assert client.get("/parent/child/orange/").data == b"Apple"
|
||||
response = client.get("/parent/child/")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_nested_blueprint_url_prefix_only_parent_prefix(app, client):
|
||||
parent = flask.Blueprint("parent", __name__)
|
||||
child = flask.Blueprint("child", __name__)
|
||||
def test_unique_blueprint_names(app, client) -> None:
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
bp2 = flask.Blueprint("bp", __name__)
|
||||
|
||||
@child.route("/child-endpoint")
|
||||
def child_index():
|
||||
return "Child"
|
||||
app.register_blueprint(bp)
|
||||
|
||||
parent.register_blueprint(child)
|
||||
app.register_blueprint(parent, url_prefix="/parent")
|
||||
with pytest.warns(UserWarning):
|
||||
app.register_blueprint(bp) # same bp, same name, warning
|
||||
|
||||
assert client.get("/parent/child-endpoint").data == b"Child"
|
||||
app.register_blueprint(bp, name="again") # same bp, different name, ok
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
app.register_blueprint(bp2) # different bp, same name, error
|
||||
|
||||
app.register_blueprint(bp2, name="alt") # different bp, different name, ok
|
||||
|
||||
|
||||
def test_self_registration(app, client) -> None:
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
with pytest.raises(ValueError):
|
||||
bp.register_blueprint(bp)
|
||||
|
||||
|
||||
def test_blueprint_renaming(app, client) -> None:
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
bp2 = flask.Blueprint("bp2", __name__)
|
||||
|
||||
@bp.get("/")
|
||||
def index():
|
||||
return flask.request.endpoint
|
||||
|
||||
@bp2.get("/")
|
||||
def index2():
|
||||
return flask.request.endpoint
|
||||
|
||||
bp.register_blueprint(bp2, url_prefix="/a", name="sub")
|
||||
app.register_blueprint(bp, url_prefix="/a")
|
||||
app.register_blueprint(bp, url_prefix="/b", name="alt")
|
||||
|
||||
assert client.get("/a/").data == b"bp.index"
|
||||
assert client.get("/b/").data == b"alt.index"
|
||||
assert client.get("/a/a/").data == b"bp.sub.index2"
|
||||
assert client.get("/b/a/").data == b"alt.sub.index2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue