cache blueprint path calculation

This commit is contained in:
David Lord 2021-05-20 10:32:28 -07:00 committed by pgjones
parent c2920e2bd9
commit 67b0b7e30d
3 changed files with 56 additions and 30 deletions

View file

@ -36,6 +36,7 @@ from .globals import _request_ctx_stack
from .globals import g from .globals import g
from .globals import request from .globals import request
from .globals import session from .globals import session
from .helpers import _split_blueprint_path
from .helpers import get_debug_flag from .helpers import get_debug_flag
from .helpers import get_env from .helpers import get_env
from .helpers import get_flashed_messages from .helpers import get_flashed_messages
@ -1790,13 +1791,11 @@ class Flask(Scaffold):
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None] funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
if "." in endpoint: if "." in endpoint:
bps: t.List[str] = [endpoint.rsplit(".", 1)[0]] # This is called by url_for, which can be called outside a
# request, can't use request.blueprints.
while "." in bps[-1]: bps = _split_blueprint_path(endpoint.rpartition(".")[0])
bps.append(bps[-1].rpartition(".")[0]) bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps)
funcs = chain(funcs, bp_funcs)
for bp in bps:
funcs = chain(funcs, self.url_default_functions[bp])
for func in funcs: for func in funcs:
func(endpoint, values) func(endpoint, values)

View file

@ -6,6 +6,7 @@ import typing as t
import warnings import warnings
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
from functools import lru_cache
from functools import update_wrapper from functools import update_wrapper
from threading import RLock from threading import RLock
@ -821,3 +822,13 @@ def is_ip(value: str) -> bool:
return True return True
return False 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

View file

@ -1,12 +1,12 @@
import typing as t import typing as t
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.utils import cached_property
from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Request as RequestBase
from werkzeug.wrappers import Response as ResponseBase from werkzeug.wrappers import Response as ResponseBase
from . import json from . import json
from .globals import current_app from .globals import current_app
from .helpers import _split_blueprint_path
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
import typing_extensions as te import typing_extensions as te
@ -60,38 +60,54 @@ class Request(RequestBase):
@property @property
def endpoint(self) -> t.Optional[str]: def endpoint(self) -> t.Optional[str]:
"""The endpoint that matched the request. This in combination with """The endpoint that matched the request URL.
:attr:`view_args` can be used to reconstruct the same or a
modified URL. If an exception happened when matching, this will This will be ``None`` if matching failed or has not been
be ``None``. 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: if self.url_rule is not None:
return self.url_rule.endpoint return self.url_rule.endpoint
else:
return None return None
@property @property
def blueprint(self) -> t.Optional[str]: def blueprint(self) -> t.Optional[str]:
"""The name of the current blueprint""" """The registered 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
@cached_property This will be ``None`` if the endpoint is not part of a
def blueprints(self) -> t.List[str]: blueprint, or if URL matching failed or has not been performed
"""The names of the current blueprint upwards through parent yet.
blueprints.
This does not necessarily match the name the blueprint was
created with. It may have been nested, or registered with a
different name.
""" """
if self.blueprint is None: 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 []
bps: t.List[str] = [self.blueprint] return _split_blueprint_path(name)
while "." in bps[-1]:
bps.append(bps[-1].rpartition(".")[0])
return bps
def _load_form_data(self) -> None: def _load_form_data(self) -> None:
RequestBase._load_form_data(self) RequestBase._load_form_data(self)