mirror of https://github.com/pallets/flask.git
finish moving url_for to app
move entire implementation to app make special build args actual keyword-only args handle no app context in method mention other config in server_name error implicit external with scheme use adapter.build url_scheme argument rewrite documentation
This commit is contained in:
parent
92acd05d9b
commit
39f9363296
171
src/flask/app.py
171
src/flask/app.py
|
@ -34,6 +34,7 @@ from .config import ConfigAttribute
|
|||
from .ctx import _AppCtxGlobals
|
||||
from .ctx import AppContext
|
||||
from .ctx import RequestContext
|
||||
from .globals import _app_ctx_stack
|
||||
from .globals import _request_ctx_stack
|
||||
from .globals import g
|
||||
from .globals import request
|
||||
|
@ -440,15 +441,16 @@ class Flask(Scaffold):
|
|||
#: .. versionadded:: 2.2
|
||||
self.aborter = self.make_aborter()
|
||||
|
||||
#: A list of functions that are called when :meth:`url_for` raises a
|
||||
#: :exc:`~werkzeug.routing.BuildError`. Each function registered here
|
||||
#: is called with `error`, `endpoint` and `values`. If a function
|
||||
#: returns ``None`` or raises a :exc:`BuildError` the next function is
|
||||
#: tried.
|
||||
#: 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: t.List[
|
||||
t.Callable[[Exception, str, dict], str]
|
||||
t.Callable[[Exception, str, t.Dict[str, t.Any]], str]
|
||||
] = []
|
||||
|
||||
#: A list of functions that will be called at the beginning of the
|
||||
|
@ -1665,45 +1667,125 @@ class Flask(Scaffold):
|
|||
def url_for(
|
||||
self,
|
||||
endpoint: str,
|
||||
external: bool,
|
||||
url_adapter,
|
||||
*,
|
||||
_anchor: t.Optional[str] = None,
|
||||
_method: t.Optional[str] = None,
|
||||
_scheme: t.Optional[str] = None,
|
||||
_external: t.Optional[bool] = None,
|
||||
**values: t.Any,
|
||||
) -> str:
|
||||
"""Generate a URL to the given endpoint with the given values.
|
||||
|
||||
This is called by :func:`flask.url_for`, and can be called
|
||||
directly as well.
|
||||
|
||||
An *endpoint* is the name of a URL rule, usually added with
|
||||
:meth:`@app.route() <route>`, and usually the same name as the
|
||||
view function. A route defined in a :class:`~flask.Blueprint`
|
||||
will prepend the blueprint's name separated by a ``.`` to the
|
||||
endpoint.
|
||||
|
||||
In some cases, such as email messages, you want URLs to include
|
||||
the scheme and domain, like ``https://example.com/hello``. When
|
||||
not in an active request, URLs will be external by default, but
|
||||
this requires setting :data:`SERVER_NAME` so Flask knows what
|
||||
domain to use. :data:`APPLICATION_ROOT` and
|
||||
:data:`PREFERRED_URL_SCHEME` should also be configured as
|
||||
needed. This config is only used when not in an active request.
|
||||
|
||||
Functions can be decorated with :meth:`url_defaults` to modify
|
||||
keyword arguments before the URL is built.
|
||||
|
||||
If building fails for some reason, such as an unknown endpoint
|
||||
or incorrect values, the app's :meth:`handle_url_build_error`
|
||||
method is called. If that returns a string, that is returned,
|
||||
otherwise a :exc:`~werkzeug.routing.BuildError` is raised.
|
||||
|
||||
:param endpoint: The endpoint name associated with the URL to
|
||||
generate. If this starts with a ``.``, the current blueprint
|
||||
name (if any) will be used.
|
||||
:param _anchor: If given, append this as ``#anchor`` to the URL.
|
||||
:param _method: If given, generate the URL associated with this
|
||||
method for the endpoint.
|
||||
:param _scheme: If given, the URL will have this scheme if it
|
||||
is external.
|
||||
:param _external: If given, prefer the URL to be internal
|
||||
(False) or require it to be external (True). External URLs
|
||||
include the scheme and domain. When not in an active
|
||||
request, URLs are external by default.
|
||||
:param values: Values to use for the variable parts of the URL
|
||||
rule. Unknown keys are appended as query string arguments,
|
||||
like ``?a=b&c=d``.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
Moved from ``flask.url_for``, which calls this method.
|
||||
"""
|
||||
req_ctx = _request_ctx_stack.top
|
||||
|
||||
if req_ctx is not None:
|
||||
url_adapter = req_ctx.url_adapter
|
||||
blueprint_name = req_ctx.request.blueprint
|
||||
|
||||
# If the endpoint starts with "." and the request matches a
|
||||
# blueprint, the endpoint is relative to the blueprint.
|
||||
if endpoint[:1] == ".":
|
||||
if blueprint_name is not None:
|
||||
endpoint = f"{blueprint_name}{endpoint}"
|
||||
else:
|
||||
endpoint = endpoint[1:]
|
||||
|
||||
# When in a request, generate a URL without scheme and
|
||||
# domain by default, unless a scheme is given.
|
||||
if _external is None:
|
||||
_external = _scheme is not None
|
||||
else:
|
||||
app_ctx = _app_ctx_stack.top
|
||||
|
||||
# If called by helpers.url_for, an app context is active,
|
||||
# use its url_adapter. Otherwise, app.url_for was called
|
||||
# directly, build an adapter.
|
||||
if app_ctx is not None:
|
||||
url_adapter = app_ctx.url_adapter
|
||||
else:
|
||||
url_adapter = self.create_url_adapter(None)
|
||||
|
||||
if url_adapter is None:
|
||||
raise RuntimeError(
|
||||
"Unable to build URLs outside an active request"
|
||||
" without 'SERVER_NAME' configured. Also configure"
|
||||
" 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"
|
||||
" needed."
|
||||
)
|
||||
|
||||
# When outside a request, generate a URL with scheme and
|
||||
# domain by default.
|
||||
if _external is None:
|
||||
_external = True
|
||||
|
||||
# It is an error to set _scheme when _external=False, in order
|
||||
# to avoid accidental insecure URLs.
|
||||
if _scheme is not None and not _external:
|
||||
raise ValueError("When specifying '_scheme', '_external' must be True.")
|
||||
|
||||
anchor = values.pop("_anchor", None)
|
||||
method = values.pop("_method", None)
|
||||
scheme = values.pop("_scheme", None)
|
||||
self.inject_url_defaults(endpoint, values)
|
||||
|
||||
# This is not the best way to deal with this but currently the
|
||||
# underlying Werkzeug router does not support overriding the scheme on
|
||||
# a per build call basis.
|
||||
old_scheme = None
|
||||
if scheme is not None:
|
||||
if not external:
|
||||
raise ValueError("When specifying _scheme, _external must be True")
|
||||
old_scheme = url_adapter.url_scheme
|
||||
url_adapter.url_scheme = scheme
|
||||
|
||||
try:
|
||||
try:
|
||||
rv = url_adapter.build(
|
||||
endpoint, values, method=method, force_external=external
|
||||
)
|
||||
finally:
|
||||
if old_scheme is not None:
|
||||
url_adapter.url_scheme = old_scheme
|
||||
rv = url_adapter.build(
|
||||
endpoint,
|
||||
values,
|
||||
method=_method,
|
||||
url_scheme=_scheme,
|
||||
force_external=_external,
|
||||
)
|
||||
except BuildError as error:
|
||||
# We need to inject the values again so that the app callback can
|
||||
# deal with that sort of stuff.
|
||||
values["_external"] = external
|
||||
values["_anchor"] = anchor
|
||||
values["_method"] = method
|
||||
values["_scheme"] = scheme
|
||||
values.update(
|
||||
_anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external
|
||||
)
|
||||
return self.handle_url_build_error(error, endpoint, values)
|
||||
|
||||
if anchor is not None:
|
||||
rv += f"#{url_quote(anchor)}"
|
||||
if _anchor is not None:
|
||||
rv = f"{rv}#{url_quote(_anchor)}"
|
||||
|
||||
return rv
|
||||
|
||||
def redirect(self, location: str, code: int = 302) -> BaseResponse:
|
||||
|
@ -1905,10 +1987,21 @@ class Flask(Scaffold):
|
|||
func(endpoint, values)
|
||||
|
||||
def handle_url_build_error(
|
||||
self, error: Exception, endpoint: str, values: dict
|
||||
self, error: BuildError, endpoint: str, values: t.Dict[str, t.Any]
|
||||
) -> str:
|
||||
"""Handle :class:`~werkzeug.routing.BuildError` on
|
||||
:meth:`url_for`.
|
||||
"""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:
|
||||
|
|
|
@ -13,7 +13,6 @@ import werkzeug.utils
|
|||
from werkzeug.exceptions import abort as _wz_abort
|
||||
from werkzeug.utils import redirect as _wz_redirect
|
||||
|
||||
from .globals import _app_ctx_stack
|
||||
from .globals import _request_ctx_stack
|
||||
from .globals import current_app
|
||||
from .globals import request
|
||||
|
@ -191,121 +190,58 @@ def make_response(*args: t.Any) -> "Response":
|
|||
return current_app.make_response(args) # type: ignore
|
||||
|
||||
|
||||
def url_for(endpoint: str, **values: t.Any) -> str:
|
||||
"""Generates a URL to the given endpoint with the method provided.
|
||||
def url_for(
|
||||
endpoint: str,
|
||||
*,
|
||||
_anchor: t.Optional[str] = None,
|
||||
_method: t.Optional[str] = None,
|
||||
_scheme: t.Optional[str] = None,
|
||||
_external: t.Optional[bool] = None,
|
||||
**values: t.Any,
|
||||
) -> str:
|
||||
"""Generate a URL to the given endpoint with the given values.
|
||||
|
||||
Variable arguments that are unknown to the target endpoint are appended
|
||||
to the generated URL as query arguments. If the value of a query argument
|
||||
is ``None``, the whole pair is skipped. In case blueprints are active
|
||||
you can shortcut references to the same blueprint by prefixing the
|
||||
local endpoint with a dot (``.``).
|
||||
This requires an active request or application context, and calls
|
||||
:meth:`current_app.url_for() <flask.Flask.url_for>`. See that method
|
||||
for full documentation.
|
||||
|
||||
This will reference the index function local to the current blueprint::
|
||||
:param endpoint: The endpoint name associated with the URL to
|
||||
generate. If this starts with a ``.``, the current blueprint
|
||||
name (if any) will be used.
|
||||
:param _anchor: If given, append this as ``#anchor`` to the URL.
|
||||
:param _method: If given, generate the URL associated with this
|
||||
method for the endpoint.
|
||||
:param _scheme: If given, the URL will have this scheme if it is
|
||||
external.
|
||||
:param _external: If given, prefer the URL to be internal (False) or
|
||||
require it to be external (True). External URLs include the
|
||||
scheme and domain. When not in an active request, URLs are
|
||||
external by default.
|
||||
:param values: Values to use for the variable parts of the URL rule.
|
||||
Unknown keys are appended as query string arguments, like
|
||||
``?a=b&c=d``.
|
||||
|
||||
url_for('.index')
|
||||
.. versionchanged:: 2.2
|
||||
Calls ``current_app.url_for``, allowing an app to override the
|
||||
behavior.
|
||||
|
||||
See :ref:`url-building`.
|
||||
.. versionchanged:: 0.10
|
||||
The ``_scheme`` parameter was added.
|
||||
|
||||
Configuration values ``APPLICATION_ROOT`` and ``SERVER_NAME`` are only used when
|
||||
generating URLs outside of a request context.
|
||||
.. versionchanged:: 0.9
|
||||
The ``_anchor`` and ``_method`` parameters were added.
|
||||
|
||||
To integrate applications, :class:`Flask` has a hook to intercept URL build
|
||||
errors through :attr:`Flask.url_build_error_handlers`. The `url_for`
|
||||
function results in a :exc:`~werkzeug.routing.BuildError` when the current
|
||||
app does not have a URL for the given endpoint and values. When it does, the
|
||||
:data:`~flask.current_app` calls its :attr:`~Flask.url_build_error_handlers` if
|
||||
it is not ``None``, which can return a string to use as the result of
|
||||
`url_for` (instead of `url_for`'s default to raise the
|
||||
:exc:`~werkzeug.routing.BuildError` exception) or re-raise the exception.
|
||||
An example::
|
||||
|
||||
def external_url_handler(error, endpoint, values):
|
||||
"Looks up an external URL when `url_for` cannot build a URL."
|
||||
# This is an example of hooking the build_error_handler.
|
||||
# Here, lookup_url is some utility function you've built
|
||||
# which looks up the endpoint in some external URL registry.
|
||||
url = lookup_url(endpoint, **values)
|
||||
if url is None:
|
||||
# External lookup did not have a URL.
|
||||
# Re-raise the BuildError, in context of original traceback.
|
||||
exc_type, exc_value, tb = sys.exc_info()
|
||||
if exc_value is error:
|
||||
raise exc_type(exc_value).with_traceback(tb)
|
||||
else:
|
||||
raise error
|
||||
# url_for will use this result, instead of raising BuildError.
|
||||
return url
|
||||
|
||||
app.url_build_error_handlers.append(external_url_handler)
|
||||
|
||||
Here, `error` is the instance of :exc:`~werkzeug.routing.BuildError`, and
|
||||
`endpoint` and `values` are the arguments passed into `url_for`. Note
|
||||
that this is for building URLs outside the current application, and not for
|
||||
handling 404 NotFound errors.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
The `_scheme` parameter was added.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
The `_anchor` and `_method` parameters were added.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
Calls :meth:`Flask.handle_build_error` on
|
||||
:exc:`~werkzeug.routing.BuildError`.
|
||||
|
||||
:param endpoint: the endpoint of the URL (name of the function)
|
||||
:param values: the variable arguments of the URL rule
|
||||
:param _external: if set to ``True``, an absolute URL is generated. Server
|
||||
address can be changed via ``SERVER_NAME`` configuration variable which
|
||||
falls back to the `Host` header, then to the IP and port of the request.
|
||||
:param _scheme: a string specifying the desired URL scheme. The `_external`
|
||||
parameter must be set to ``True`` or a :exc:`ValueError` is raised. The default
|
||||
behavior uses the same scheme as the current request, or
|
||||
:data:`PREFERRED_URL_SCHEME` if no request context is available.
|
||||
This also can be set to an empty string to build protocol-relative
|
||||
URLs.
|
||||
:param _anchor: if provided this is added as anchor to the URL.
|
||||
:param _method: if provided this explicitly specifies an HTTP method.
|
||||
.. versionchanged:: 0.9
|
||||
Calls ``app.handle_url_build_error`` on build errors.
|
||||
"""
|
||||
appctx = _app_ctx_stack.top
|
||||
reqctx = _request_ctx_stack.top
|
||||
|
||||
if appctx is None:
|
||||
raise RuntimeError(
|
||||
"Attempted to generate a URL without the application context being"
|
||||
" pushed. This has to be executed when application context is"
|
||||
" available."
|
||||
)
|
||||
|
||||
# If request specific information is available we have some extra
|
||||
# features that support "relative" URLs.
|
||||
if reqctx is not None:
|
||||
url_adapter = reqctx.url_adapter
|
||||
blueprint_name = request.blueprint
|
||||
|
||||
if endpoint[:1] == ".":
|
||||
if blueprint_name is not None:
|
||||
endpoint = f"{blueprint_name}{endpoint}"
|
||||
else:
|
||||
endpoint = endpoint[1:]
|
||||
|
||||
external = values.pop("_external", False)
|
||||
|
||||
# Otherwise go with the url adapter from the appctx and make
|
||||
# the URLs external by default.
|
||||
else:
|
||||
url_adapter = appctx.url_adapter
|
||||
|
||||
if url_adapter is None:
|
||||
raise RuntimeError(
|
||||
"Application was not able to create a URL adapter for request"
|
||||
" independent URL generation. You might be able to fix this by"
|
||||
" setting the SERVER_NAME config variable."
|
||||
)
|
||||
|
||||
external = values.pop("_external", True)
|
||||
|
||||
return current_app.url_for(endpoint, external, url_adapter, **values)
|
||||
return current_app.url_for(
|
||||
endpoint,
|
||||
_anchor=_anchor,
|
||||
_method=_method,
|
||||
_scheme=_scheme,
|
||||
_external=_external,
|
||||
**values,
|
||||
)
|
||||
|
||||
|
||||
def redirect(
|
||||
|
|
|
@ -119,11 +119,15 @@ class TestUrlFor:
|
|||
)
|
||||
|
||||
def test_url_for_with_scheme_not_external(self, app, req_ctx):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "42"
|
||||
app.add_url_rule("/", endpoint="index")
|
||||
|
||||
pytest.raises(ValueError, flask.url_for, "index", _scheme="https")
|
||||
# Implicit external with scheme.
|
||||
url = flask.url_for("index", _scheme="https")
|
||||
assert url == "https://localhost/"
|
||||
|
||||
# Error when external=False with scheme
|
||||
with pytest.raises(ValueError):
|
||||
flask.url_for("index", _scheme="https", _external=False)
|
||||
|
||||
def test_url_for_with_alternating_schemes(self, app, req_ctx):
|
||||
@app.route("/")
|
||||
|
|
Loading…
Reference in New Issue