Skip to content

Commit 00e2aac

Browse files
authored
Merge pull request #4580 from ionesu/move_url_for_to_the_flask_app_object
Move url_for to the Flask app object
2 parents fac6303 + 39f9363 commit 00e2aac

File tree

3 files changed

+204
-162
lines changed

3 files changed

+204
-162
lines changed

src/flask/app.py

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from werkzeug.routing import RequestRedirect
2424
from werkzeug.routing import RoutingException
2525
from werkzeug.routing import Rule
26+
from werkzeug.urls import url_quote
2627
from werkzeug.utils import redirect as _wz_redirect
2728
from werkzeug.wrappers import Response as BaseResponse
2829

@@ -33,6 +34,7 @@
3334
from .ctx import _AppCtxGlobals
3435
from .ctx import AppContext
3536
from .ctx import RequestContext
37+
from .globals import _app_ctx_stack
3638
from .globals import _request_ctx_stack
3739
from .globals import g
3840
from .globals import request
@@ -439,15 +441,16 @@ def __init__(
439441
#: .. versionadded:: 2.2
440442
self.aborter = self.make_aborter()
441443

442-
#: A list of functions that are called when :meth:`url_for` raises a
443-
#: :exc:`~werkzeug.routing.BuildError`. Each function registered here
444-
#: is called with `error`, `endpoint` and `values`. If a function
445-
#: returns ``None`` or raises a :exc:`BuildError` the next function is
446-
#: tried.
444+
#: A list of functions that are called by
445+
#: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
446+
#: :exc:`~werkzeug.routing.BuildError`. Each function is called
447+
#: with ``error``, ``endpoint`` and ``values``. If a function
448+
#: returns ``None`` or raises a ``BuildError``, it is skipped.
449+
#: Otherwise, its return value is returned by ``url_for``.
447450
#:
448451
#: .. versionadded:: 0.9
449452
self.url_build_error_handlers: t.List[
450-
t.Callable[[Exception, str, dict], str]
453+
t.Callable[[Exception, str, t.Dict[str, t.Any]], str]
451454
] = []
452455

453456
#: A list of functions that will be called at the beginning of the
@@ -1661,6 +1664,130 @@ def async_to_sync(
16611664

16621665
return asgiref_async_to_sync(func)
16631666

1667+
def url_for(
1668+
self,
1669+
endpoint: str,
1670+
*,
1671+
_anchor: t.Optional[str] = None,
1672+
_method: t.Optional[str] = None,
1673+
_scheme: t.Optional[str] = None,
1674+
_external: t.Optional[bool] = None,
1675+
**values: t.Any,
1676+
) -> str:
1677+
"""Generate a URL to the given endpoint with the given values.
1678+
1679+
This is called by :func:`flask.url_for`, and can be called
1680+
directly as well.
1681+
1682+
An *endpoint* is the name of a URL rule, usually added with
1683+
:meth:`@app.route() <route>`, and usually the same name as the
1684+
view function. A route defined in a :class:`~flask.Blueprint`
1685+
will prepend the blueprint's name separated by a ``.`` to the
1686+
endpoint.
1687+
1688+
In some cases, such as email messages, you want URLs to include
1689+
the scheme and domain, like ``https://example.com/hello``. When
1690+
not in an active request, URLs will be external by default, but
1691+
this requires setting :data:`SERVER_NAME` so Flask knows what
1692+
domain to use. :data:`APPLICATION_ROOT` and
1693+
:data:`PREFERRED_URL_SCHEME` should also be configured as
1694+
needed. This config is only used when not in an active request.
1695+
1696+
Functions can be decorated with :meth:`url_defaults` to modify
1697+
keyword arguments before the URL is built.
1698+
1699+
If building fails for some reason, such as an unknown endpoint
1700+
or incorrect values, the app's :meth:`handle_url_build_error`
1701+
method is called. If that returns a string, that is returned,
1702+
otherwise a :exc:`~werkzeug.routing.BuildError` is raised.
1703+
1704+
:param endpoint: The endpoint name associated with the URL to
1705+
generate. If this starts with a ``.``, the current blueprint
1706+
name (if any) will be used.
1707+
:param _anchor: If given, append this as ``#anchor`` to the URL.
1708+
:param _method: If given, generate the URL associated with this
1709+
method for the endpoint.
1710+
:param _scheme: If given, the URL will have this scheme if it
1711+
is external.
1712+
:param _external: If given, prefer the URL to be internal
1713+
(False) or require it to be external (True). External URLs
1714+
include the scheme and domain. When not in an active
1715+
request, URLs are external by default.
1716+
:param values: Values to use for the variable parts of the URL
1717+
rule. Unknown keys are appended as query string arguments,
1718+
like ``?a=b&c=d``.
1719+
1720+
.. versionadded:: 2.2
1721+
Moved from ``flask.url_for``, which calls this method.
1722+
"""
1723+
req_ctx = _request_ctx_stack.top
1724+
1725+
if req_ctx is not None:
1726+
url_adapter = req_ctx.url_adapter
1727+
blueprint_name = req_ctx.request.blueprint
1728+
1729+
# If the endpoint starts with "." and the request matches a
1730+
# blueprint, the endpoint is relative to the blueprint.
1731+
if endpoint[:1] == ".":
1732+
if blueprint_name is not None:
1733+
endpoint = f"{blueprint_name}{endpoint}"
1734+
else:
1735+
endpoint = endpoint[1:]
1736+
1737+
# When in a request, generate a URL without scheme and
1738+
# domain by default, unless a scheme is given.
1739+
if _external is None:
1740+
_external = _scheme is not None
1741+
else:
1742+
app_ctx = _app_ctx_stack.top
1743+
1744+
# If called by helpers.url_for, an app context is active,
1745+
# use its url_adapter. Otherwise, app.url_for was called
1746+
# directly, build an adapter.
1747+
if app_ctx is not None:
1748+
url_adapter = app_ctx.url_adapter
1749+
else:
1750+
url_adapter = self.create_url_adapter(None)
1751+
1752+
if url_adapter is None:
1753+
raise RuntimeError(
1754+
"Unable to build URLs outside an active request"
1755+
" without 'SERVER_NAME' configured. Also configure"
1756+
" 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"
1757+
" needed."
1758+
)
1759+
1760+
# When outside a request, generate a URL with scheme and
1761+
# domain by default.
1762+
if _external is None:
1763+
_external = True
1764+
1765+
# It is an error to set _scheme when _external=False, in order
1766+
# to avoid accidental insecure URLs.
1767+
if _scheme is not None and not _external:
1768+
raise ValueError("When specifying '_scheme', '_external' must be True.")
1769+
1770+
self.inject_url_defaults(endpoint, values)
1771+
1772+
try:
1773+
rv = url_adapter.build(
1774+
endpoint,
1775+
values,
1776+
method=_method,
1777+
url_scheme=_scheme,
1778+
force_external=_external,
1779+
)
1780+
except BuildError as error:
1781+
values.update(
1782+
_anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external
1783+
)
1784+
return self.handle_url_build_error(error, endpoint, values)
1785+
1786+
if _anchor is not None:
1787+
rv = f"{rv}#{url_quote(_anchor)}"
1788+
1789+
return rv
1790+
16641791
def redirect(self, location: str, code: int = 302) -> BaseResponse:
16651792
"""Create a redirect response object.
16661793
@@ -1860,10 +1987,21 @@ def inject_url_defaults(self, endpoint: str, values: dict) -> None:
18601987
func(endpoint, values)
18611988

18621989
def handle_url_build_error(
1863-
self, error: Exception, endpoint: str, values: dict
1990+
self, error: BuildError, endpoint: str, values: t.Dict[str, t.Any]
18641991
) -> str:
1865-
"""Handle :class:`~werkzeug.routing.BuildError` on
1866-
:meth:`url_for`.
1992+
"""Called by :meth:`.url_for` if a
1993+
:exc:`~werkzeug.routing.BuildError` was raised. If this returns
1994+
a value, it will be returned by ``url_for``, otherwise the error
1995+
will be re-raised.
1996+
1997+
Each function in :attr:`url_build_error_handlers` is called with
1998+
``error``, ``endpoint`` and ``values``. If a function returns
1999+
``None`` or raises a ``BuildError``, it is skipped. Otherwise,
2000+
its return value is returned by ``url_for``.
2001+
2002+
:param error: The active ``BuildError`` being handled.
2003+
:param endpoint: The endpoint being built.
2004+
:param values: The keyword arguments passed to ``url_for``.
18672005
"""
18682006
for handler in self.url_build_error_handlers:
18692007
try:

0 commit comments

Comments
 (0)