952 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			952 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Python
		
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
import typing as t
 | 
						|
import warnings
 | 
						|
from pprint import pformat
 | 
						|
from threading import Lock
 | 
						|
from urllib.parse import quote
 | 
						|
from urllib.parse import urljoin
 | 
						|
from urllib.parse import urlunsplit
 | 
						|
 | 
						|
from .._internal import _get_environ
 | 
						|
from .._internal import _wsgi_decoding_dance
 | 
						|
from ..datastructures import ImmutableDict
 | 
						|
from ..datastructures import MultiDict
 | 
						|
from ..exceptions import BadHost
 | 
						|
from ..exceptions import HTTPException
 | 
						|
from ..exceptions import MethodNotAllowed
 | 
						|
from ..exceptions import NotFound
 | 
						|
from ..urls import _urlencode
 | 
						|
from ..wsgi import get_host
 | 
						|
from .converters import DEFAULT_CONVERTERS
 | 
						|
from .exceptions import BuildError
 | 
						|
from .exceptions import NoMatch
 | 
						|
from .exceptions import RequestAliasRedirect
 | 
						|
from .exceptions import RequestPath
 | 
						|
from .exceptions import RequestRedirect
 | 
						|
from .exceptions import WebsocketMismatch
 | 
						|
from .matcher import StateMachineMatcher
 | 
						|
from .rules import _simple_rule_re
 | 
						|
from .rules import Rule
 | 
						|
 | 
						|
if t.TYPE_CHECKING:
 | 
						|
    from _typeshed.wsgi import WSGIApplication
 | 
						|
    from _typeshed.wsgi import WSGIEnvironment
 | 
						|
 | 
						|
    from ..wrappers.request import Request
 | 
						|
    from .converters import BaseConverter
 | 
						|
    from .rules import RuleFactory
 | 
						|
 | 
						|
 | 
						|
class Map:
 | 
						|
    """The map class stores all the URL rules and some configuration
 | 
						|
    parameters.  Some of the configuration values are only stored on the
 | 
						|
    `Map` instance since those affect all rules, others are just defaults
 | 
						|
    and can be overridden for each rule.  Note that you have to specify all
 | 
						|
    arguments besides the `rules` as keyword arguments!
 | 
						|
 | 
						|
    :param rules: sequence of url rules for this map.
 | 
						|
    :param default_subdomain: The default subdomain for rules without a
 | 
						|
                              subdomain defined.
 | 
						|
    :param strict_slashes: If a rule ends with a slash but the matched
 | 
						|
        URL does not, redirect to the URL with a trailing slash.
 | 
						|
    :param merge_slashes: Merge consecutive slashes when matching or
 | 
						|
        building URLs. Matches will redirect to the normalized URL.
 | 
						|
        Slashes in variable parts are not merged.
 | 
						|
    :param redirect_defaults: This will redirect to the default rule if it
 | 
						|
                              wasn't visited that way. This helps creating
 | 
						|
                              unique URLs.
 | 
						|
    :param converters: A dict of converters that adds additional converters
 | 
						|
                       to the list of converters. If you redefine one
 | 
						|
                       converter this will override the original one.
 | 
						|
    :param sort_parameters: If set to `True` the url parameters are sorted.
 | 
						|
                            See `url_encode` for more details.
 | 
						|
    :param sort_key: The sort key function for `url_encode`.
 | 
						|
    :param host_matching: if set to `True` it enables the host matching
 | 
						|
                          feature and disables the subdomain one.  If
 | 
						|
                          enabled the `host` parameter to rules is used
 | 
						|
                          instead of the `subdomain` one.
 | 
						|
 | 
						|
    .. versionchanged:: 3.0
 | 
						|
        The ``charset`` and ``encoding_errors`` parameters were removed.
 | 
						|
 | 
						|
    .. versionchanged:: 1.0
 | 
						|
        If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules will match.
 | 
						|
 | 
						|
    .. versionchanged:: 1.0
 | 
						|
        The ``merge_slashes`` parameter was added.
 | 
						|
 | 
						|
    .. versionchanged:: 0.7
 | 
						|
        The ``encoding_errors`` and ``host_matching`` parameters were added.
 | 
						|
 | 
						|
    .. versionchanged:: 0.5
 | 
						|
        The ``sort_parameters`` and ``sort_key``  paramters were added.
 | 
						|
    """
 | 
						|
 | 
						|
    #: A dict of default converters to be used.
 | 
						|
    default_converters = ImmutableDict(DEFAULT_CONVERTERS)
 | 
						|
 | 
						|
    #: The type of lock to use when updating.
 | 
						|
    #:
 | 
						|
    #: .. versionadded:: 1.0
 | 
						|
    lock_class = Lock
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        rules: t.Iterable[RuleFactory] | None = None,
 | 
						|
        default_subdomain: str = "",
 | 
						|
        strict_slashes: bool = True,
 | 
						|
        merge_slashes: bool = True,
 | 
						|
        redirect_defaults: bool = True,
 | 
						|
        converters: t.Mapping[str, type[BaseConverter]] | None = None,
 | 
						|
        sort_parameters: bool = False,
 | 
						|
        sort_key: t.Callable[[t.Any], t.Any] | None = None,
 | 
						|
        host_matching: bool = False,
 | 
						|
    ) -> None:
 | 
						|
        self._matcher = StateMachineMatcher(merge_slashes)
 | 
						|
        self._rules_by_endpoint: dict[t.Any, list[Rule]] = {}
 | 
						|
        self._remap = True
 | 
						|
        self._remap_lock = self.lock_class()
 | 
						|
 | 
						|
        self.default_subdomain = default_subdomain
 | 
						|
        self.strict_slashes = strict_slashes
 | 
						|
        self.redirect_defaults = redirect_defaults
 | 
						|
        self.host_matching = host_matching
 | 
						|
 | 
						|
        self.converters = self.default_converters.copy()
 | 
						|
        if converters:
 | 
						|
            self.converters.update(converters)
 | 
						|
 | 
						|
        self.sort_parameters = sort_parameters
 | 
						|
        self.sort_key = sort_key
 | 
						|
 | 
						|
        for rulefactory in rules or ():
 | 
						|
            self.add(rulefactory)
 | 
						|
 | 
						|
    @property
 | 
						|
    def merge_slashes(self) -> bool:
 | 
						|
        return self._matcher.merge_slashes
 | 
						|
 | 
						|
    @merge_slashes.setter
 | 
						|
    def merge_slashes(self, value: bool) -> None:
 | 
						|
        self._matcher.merge_slashes = value
 | 
						|
 | 
						|
    def is_endpoint_expecting(self, endpoint: t.Any, *arguments: str) -> bool:
 | 
						|
        """Iterate over all rules and check if the endpoint expects
 | 
						|
        the arguments provided.  This is for example useful if you have
 | 
						|
        some URLs that expect a language code and others that do not and
 | 
						|
        you want to wrap the builder a bit so that the current language
 | 
						|
        code is automatically added if not provided but endpoints expect
 | 
						|
        it.
 | 
						|
 | 
						|
        :param endpoint: the endpoint to check.
 | 
						|
        :param arguments: this function accepts one or more arguments
 | 
						|
                          as positional arguments.  Each one of them is
 | 
						|
                          checked.
 | 
						|
        """
 | 
						|
        self.update()
 | 
						|
        arguments_set = set(arguments)
 | 
						|
        for rule in self._rules_by_endpoint[endpoint]:
 | 
						|
            if arguments_set.issubset(rule.arguments):
 | 
						|
                return True
 | 
						|
        return False
 | 
						|
 | 
						|
    @property
 | 
						|
    def _rules(self) -> list[Rule]:
 | 
						|
        return [rule for rules in self._rules_by_endpoint.values() for rule in rules]
 | 
						|
 | 
						|
    def iter_rules(self, endpoint: t.Any | None = None) -> t.Iterator[Rule]:
 | 
						|
        """Iterate over all rules or the rules of an endpoint.
 | 
						|
 | 
						|
        :param endpoint: if provided only the rules for that endpoint
 | 
						|
                         are returned.
 | 
						|
        :return: an iterator
 | 
						|
        """
 | 
						|
        self.update()
 | 
						|
        if endpoint is not None:
 | 
						|
            return iter(self._rules_by_endpoint[endpoint])
 | 
						|
        return iter(self._rules)
 | 
						|
 | 
						|
    def add(self, rulefactory: RuleFactory) -> None:
 | 
						|
        """Add a new rule or factory to the map and bind it.  Requires that the
 | 
						|
        rule is not bound to another map.
 | 
						|
 | 
						|
        :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
 | 
						|
        """
 | 
						|
        for rule in rulefactory.get_rules(self):
 | 
						|
            rule.bind(self)
 | 
						|
            if not rule.build_only:
 | 
						|
                self._matcher.add(rule)
 | 
						|
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
 | 
						|
        self._remap = True
 | 
						|
 | 
						|
    def bind(
 | 
						|
        self,
 | 
						|
        server_name: str,
 | 
						|
        script_name: str | None = None,
 | 
						|
        subdomain: str | None = None,
 | 
						|
        url_scheme: str = "http",
 | 
						|
        default_method: str = "GET",
 | 
						|
        path_info: str | None = None,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str | None = None,
 | 
						|
    ) -> MapAdapter:
 | 
						|
        """Return a new :class:`MapAdapter` with the details specified to the
 | 
						|
        call.  Note that `script_name` will default to ``'/'`` if not further
 | 
						|
        specified or `None`.  The `server_name` at least is a requirement
 | 
						|
        because the HTTP RFC requires absolute URLs for redirects and so all
 | 
						|
        redirect exceptions raised by Werkzeug will contain the full canonical
 | 
						|
        URL.
 | 
						|
 | 
						|
        If no path_info is passed to :meth:`match` it will use the default path
 | 
						|
        info passed to bind.  While this doesn't really make sense for
 | 
						|
        manual bind calls, it's useful if you bind a map to a WSGI
 | 
						|
        environment which already contains the path info.
 | 
						|
 | 
						|
        `subdomain` will default to the `default_subdomain` for this map if
 | 
						|
        no defined. If there is no `default_subdomain` you cannot use the
 | 
						|
        subdomain feature.
 | 
						|
 | 
						|
        .. versionchanged:: 1.0
 | 
						|
            If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules
 | 
						|
            will match.
 | 
						|
 | 
						|
        .. versionchanged:: 0.15
 | 
						|
            ``path_info`` defaults to ``'/'`` if ``None``.
 | 
						|
 | 
						|
        .. versionchanged:: 0.8
 | 
						|
            ``query_args`` can be a string.
 | 
						|
 | 
						|
        .. versionchanged:: 0.7
 | 
						|
            Added ``query_args``.
 | 
						|
        """
 | 
						|
        server_name = server_name.lower()
 | 
						|
        if self.host_matching:
 | 
						|
            if subdomain is not None:
 | 
						|
                raise RuntimeError("host matching enabled and a subdomain was provided")
 | 
						|
        elif subdomain is None:
 | 
						|
            subdomain = self.default_subdomain
 | 
						|
        if script_name is None:
 | 
						|
            script_name = "/"
 | 
						|
        if path_info is None:
 | 
						|
            path_info = "/"
 | 
						|
 | 
						|
        # Port isn't part of IDNA, and might push a name over the 63 octet limit.
 | 
						|
        server_name, port_sep, port = server_name.partition(":")
 | 
						|
 | 
						|
        try:
 | 
						|
            server_name = server_name.encode("idna").decode("ascii")
 | 
						|
        except UnicodeError as e:
 | 
						|
            raise BadHost() from e
 | 
						|
 | 
						|
        return MapAdapter(
 | 
						|
            self,
 | 
						|
            f"{server_name}{port_sep}{port}",
 | 
						|
            script_name,
 | 
						|
            subdomain,
 | 
						|
            url_scheme,
 | 
						|
            path_info,
 | 
						|
            default_method,
 | 
						|
            query_args,
 | 
						|
        )
 | 
						|
 | 
						|
    def bind_to_environ(
 | 
						|
        self,
 | 
						|
        environ: WSGIEnvironment | Request,
 | 
						|
        server_name: str | None = None,
 | 
						|
        subdomain: str | None = None,
 | 
						|
    ) -> MapAdapter:
 | 
						|
        """Like :meth:`bind` but you can pass it an WSGI environment and it
 | 
						|
        will fetch the information from that dictionary.  Note that because of
 | 
						|
        limitations in the protocol there is no way to get the current
 | 
						|
        subdomain and real `server_name` from the environment.  If you don't
 | 
						|
        provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or
 | 
						|
        `HTTP_HOST` if provided) as used `server_name` with disabled subdomain
 | 
						|
        feature.
 | 
						|
 | 
						|
        If `subdomain` is `None` but an environment and a server name is
 | 
						|
        provided it will calculate the current subdomain automatically.
 | 
						|
        Example: `server_name` is ``'example.com'`` and the `SERVER_NAME`
 | 
						|
        in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated
 | 
						|
        subdomain will be ``'staging.dev'``.
 | 
						|
 | 
						|
        If the object passed as environ has an environ attribute, the value of
 | 
						|
        this attribute is used instead.  This allows you to pass request
 | 
						|
        objects.  Additionally `PATH_INFO` added as a default of the
 | 
						|
        :class:`MapAdapter` so that you don't have to pass the path info to
 | 
						|
        the match method.
 | 
						|
 | 
						|
        .. versionchanged:: 1.0.0
 | 
						|
            If the passed server name specifies port 443, it will match
 | 
						|
            if the incoming scheme is ``https`` without a port.
 | 
						|
 | 
						|
        .. versionchanged:: 1.0.0
 | 
						|
            A warning is shown when the passed server name does not
 | 
						|
            match the incoming WSGI server name.
 | 
						|
 | 
						|
        .. versionchanged:: 0.8
 | 
						|
           This will no longer raise a ValueError when an unexpected server
 | 
						|
           name was passed.
 | 
						|
 | 
						|
        .. versionchanged:: 0.5
 | 
						|
            previously this method accepted a bogus `calculate_subdomain`
 | 
						|
            parameter that did not have any effect.  It was removed because
 | 
						|
            of that.
 | 
						|
 | 
						|
        :param environ: a WSGI environment.
 | 
						|
        :param server_name: an optional server name hint (see above).
 | 
						|
        :param subdomain: optionally the current subdomain (see above).
 | 
						|
        """
 | 
						|
        env = _get_environ(environ)
 | 
						|
        wsgi_server_name = get_host(env).lower()
 | 
						|
        scheme = env["wsgi.url_scheme"]
 | 
						|
        upgrade = any(
 | 
						|
            v.strip() == "upgrade"
 | 
						|
            for v in env.get("HTTP_CONNECTION", "").lower().split(",")
 | 
						|
        )
 | 
						|
 | 
						|
        if upgrade and env.get("HTTP_UPGRADE", "").lower() == "websocket":
 | 
						|
            scheme = "wss" if scheme == "https" else "ws"
 | 
						|
 | 
						|
        if server_name is None:
 | 
						|
            server_name = wsgi_server_name
 | 
						|
        else:
 | 
						|
            server_name = server_name.lower()
 | 
						|
 | 
						|
            # strip standard port to match get_host()
 | 
						|
            if scheme in {"http", "ws"} and server_name.endswith(":80"):
 | 
						|
                server_name = server_name[:-3]
 | 
						|
            elif scheme in {"https", "wss"} and server_name.endswith(":443"):
 | 
						|
                server_name = server_name[:-4]
 | 
						|
 | 
						|
        if subdomain is None and not self.host_matching:
 | 
						|
            cur_server_name = wsgi_server_name.split(".")
 | 
						|
            real_server_name = server_name.split(".")
 | 
						|
            offset = -len(real_server_name)
 | 
						|
 | 
						|
            if cur_server_name[offset:] != real_server_name:
 | 
						|
                # This can happen even with valid configs if the server was
 | 
						|
                # accessed directly by IP address under some situations.
 | 
						|
                # Instead of raising an exception like in Werkzeug 0.7 or
 | 
						|
                # earlier we go by an invalid subdomain which will result
 | 
						|
                # in a 404 error on matching.
 | 
						|
                warnings.warn(
 | 
						|
                    f"Current server name {wsgi_server_name!r} doesn't match configured"
 | 
						|
                    f" server name {server_name!r}",
 | 
						|
                    stacklevel=2,
 | 
						|
                )
 | 
						|
                subdomain = "<invalid>"
 | 
						|
            else:
 | 
						|
                subdomain = ".".join(filter(None, cur_server_name[:offset]))
 | 
						|
 | 
						|
        def _get_wsgi_string(name: str) -> str | None:
 | 
						|
            val = env.get(name)
 | 
						|
            if val is not None:
 | 
						|
                return _wsgi_decoding_dance(val)
 | 
						|
            return None
 | 
						|
 | 
						|
        script_name = _get_wsgi_string("SCRIPT_NAME")
 | 
						|
        path_info = _get_wsgi_string("PATH_INFO")
 | 
						|
        query_args = _get_wsgi_string("QUERY_STRING")
 | 
						|
        return Map.bind(
 | 
						|
            self,
 | 
						|
            server_name,
 | 
						|
            script_name,
 | 
						|
            subdomain,
 | 
						|
            scheme,
 | 
						|
            env["REQUEST_METHOD"],
 | 
						|
            path_info,
 | 
						|
            query_args=query_args,
 | 
						|
        )
 | 
						|
 | 
						|
    def update(self) -> None:
 | 
						|
        """Called before matching and building to keep the compiled rules
 | 
						|
        in the correct order after things changed.
 | 
						|
        """
 | 
						|
        if not self._remap:
 | 
						|
            return
 | 
						|
 | 
						|
        with self._remap_lock:
 | 
						|
            if not self._remap:
 | 
						|
                return
 | 
						|
 | 
						|
            self._matcher.update()
 | 
						|
            for rules in self._rules_by_endpoint.values():
 | 
						|
                rules.sort(key=lambda x: x.build_compare_key())
 | 
						|
            self._remap = False
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        rules = self.iter_rules()
 | 
						|
        return f"{type(self).__name__}({pformat(list(rules))})"
 | 
						|
 | 
						|
 | 
						|
class MapAdapter:
 | 
						|
    """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does
 | 
						|
    the URL matching and building based on runtime information.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        map: Map,
 | 
						|
        server_name: str,
 | 
						|
        script_name: str,
 | 
						|
        subdomain: str | None,
 | 
						|
        url_scheme: str,
 | 
						|
        path_info: str,
 | 
						|
        default_method: str,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str | None = None,
 | 
						|
    ):
 | 
						|
        self.map = map
 | 
						|
        self.server_name = server_name
 | 
						|
 | 
						|
        if not script_name.endswith("/"):
 | 
						|
            script_name += "/"
 | 
						|
 | 
						|
        self.script_name = script_name
 | 
						|
        self.subdomain = subdomain
 | 
						|
        self.url_scheme = url_scheme
 | 
						|
        self.path_info = path_info
 | 
						|
        self.default_method = default_method
 | 
						|
        self.query_args = query_args
 | 
						|
        self.websocket = self.url_scheme in {"ws", "wss"}
 | 
						|
 | 
						|
    def dispatch(
 | 
						|
        self,
 | 
						|
        view_func: t.Callable[[str, t.Mapping[str, t.Any]], WSGIApplication],
 | 
						|
        path_info: str | None = None,
 | 
						|
        method: str | None = None,
 | 
						|
        catch_http_exceptions: bool = False,
 | 
						|
    ) -> WSGIApplication:
 | 
						|
        """Does the complete dispatching process.  `view_func` is called with
 | 
						|
        the endpoint and a dict with the values for the view.  It should
 | 
						|
        look up the view function, call it, and return a response object
 | 
						|
        or WSGI application.  http exceptions are not caught by default
 | 
						|
        so that applications can display nicer error messages by just
 | 
						|
        catching them by hand.  If you want to stick with the default
 | 
						|
        error messages you can pass it ``catch_http_exceptions=True`` and
 | 
						|
        it will catch the http exceptions.
 | 
						|
 | 
						|
        Here a small example for the dispatch usage::
 | 
						|
 | 
						|
            from werkzeug.wrappers import Request, Response
 | 
						|
            from werkzeug.wsgi import responder
 | 
						|
            from werkzeug.routing import Map, Rule
 | 
						|
 | 
						|
            def on_index(request):
 | 
						|
                return Response('Hello from the index')
 | 
						|
 | 
						|
            url_map = Map([Rule('/', endpoint='index')])
 | 
						|
            views = {'index': on_index}
 | 
						|
 | 
						|
            @responder
 | 
						|
            def application(environ, start_response):
 | 
						|
                request = Request(environ)
 | 
						|
                urls = url_map.bind_to_environ(environ)
 | 
						|
                return urls.dispatch(lambda e, v: views[e](request, **v),
 | 
						|
                                     catch_http_exceptions=True)
 | 
						|
 | 
						|
        Keep in mind that this method might return exception objects, too, so
 | 
						|
        use :class:`Response.force_type` to get a response object.
 | 
						|
 | 
						|
        :param view_func: a function that is called with the endpoint as
 | 
						|
                          first argument and the value dict as second.  Has
 | 
						|
                          to dispatch to the actual view function with this
 | 
						|
                          information.  (see above)
 | 
						|
        :param path_info: the path info to use for matching.  Overrides the
 | 
						|
                          path info specified on binding.
 | 
						|
        :param method: the HTTP method used for matching.  Overrides the
 | 
						|
                       method specified on binding.
 | 
						|
        :param catch_http_exceptions: set to `True` to catch any of the
 | 
						|
                                      werkzeug :class:`HTTPException`\\s.
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            try:
 | 
						|
                endpoint, args = self.match(path_info, method)
 | 
						|
            except RequestRedirect as e:
 | 
						|
                return e
 | 
						|
            return view_func(endpoint, args)
 | 
						|
        except HTTPException as e:
 | 
						|
            if catch_http_exceptions:
 | 
						|
                return e
 | 
						|
            raise
 | 
						|
 | 
						|
    @t.overload
 | 
						|
    def match(
 | 
						|
        self,
 | 
						|
        path_info: str | None = None,
 | 
						|
        method: str | None = None,
 | 
						|
        return_rule: t.Literal[False] = False,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str | None = None,
 | 
						|
        websocket: bool | None = None,
 | 
						|
    ) -> tuple[t.Any, t.Mapping[str, t.Any]]: ...
 | 
						|
 | 
						|
    @t.overload
 | 
						|
    def match(
 | 
						|
        self,
 | 
						|
        path_info: str | None = None,
 | 
						|
        method: str | None = None,
 | 
						|
        return_rule: t.Literal[True] = True,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str | None = None,
 | 
						|
        websocket: bool | None = None,
 | 
						|
    ) -> tuple[Rule, t.Mapping[str, t.Any]]: ...
 | 
						|
 | 
						|
    def match(
 | 
						|
        self,
 | 
						|
        path_info: str | None = None,
 | 
						|
        method: str | None = None,
 | 
						|
        return_rule: bool = False,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str | None = None,
 | 
						|
        websocket: bool | None = None,
 | 
						|
    ) -> tuple[t.Any | Rule, t.Mapping[str, t.Any]]:
 | 
						|
        """The usage is simple: you just pass the match method the current
 | 
						|
        path info as well as the method (which defaults to `GET`).  The
 | 
						|
        following things can then happen:
 | 
						|
 | 
						|
        - you receive a `NotFound` exception that indicates that no URL is
 | 
						|
          matching.  A `NotFound` exception is also a WSGI application you
 | 
						|
          can call to get a default page not found page (happens to be the
 | 
						|
          same object as `werkzeug.exceptions.NotFound`)
 | 
						|
 | 
						|
        - you receive a `MethodNotAllowed` exception that indicates that there
 | 
						|
          is a match for this URL but not for the current request method.
 | 
						|
          This is useful for RESTful applications.
 | 
						|
 | 
						|
        - you receive a `RequestRedirect` exception with a `new_url`
 | 
						|
          attribute.  This exception is used to notify you about a request
 | 
						|
          Werkzeug requests from your WSGI application.  This is for example the
 | 
						|
          case if you request ``/foo`` although the correct URL is ``/foo/``
 | 
						|
          You can use the `RequestRedirect` instance as response-like object
 | 
						|
          similar to all other subclasses of `HTTPException`.
 | 
						|
 | 
						|
        - you receive a ``WebsocketMismatch`` exception if the only
 | 
						|
          match is a WebSocket rule but the bind is an HTTP request, or
 | 
						|
          if the match is an HTTP rule but the bind is a WebSocket
 | 
						|
          request.
 | 
						|
 | 
						|
        - you get a tuple in the form ``(endpoint, arguments)`` if there is
 | 
						|
          a match (unless `return_rule` is True, in which case you get a tuple
 | 
						|
          in the form ``(rule, arguments)``)
 | 
						|
 | 
						|
        If the path info is not passed to the match method the default path
 | 
						|
        info of the map is used (defaults to the root URL if not defined
 | 
						|
        explicitly).
 | 
						|
 | 
						|
        All of the exceptions raised are subclasses of `HTTPException` so they
 | 
						|
        can be used as WSGI responses. They will all render generic error or
 | 
						|
        redirect pages.
 | 
						|
 | 
						|
        Here is a small example for matching:
 | 
						|
 | 
						|
        >>> m = Map([
 | 
						|
        ...     Rule('/', endpoint='index'),
 | 
						|
        ...     Rule('/downloads/', endpoint='downloads/index'),
 | 
						|
        ...     Rule('/downloads/<int:id>', endpoint='downloads/show')
 | 
						|
        ... ])
 | 
						|
        >>> urls = m.bind("example.com", "/")
 | 
						|
        >>> urls.match("/", "GET")
 | 
						|
        ('index', {})
 | 
						|
        >>> urls.match("/downloads/42")
 | 
						|
        ('downloads/show', {'id': 42})
 | 
						|
 | 
						|
        And here is what happens on redirect and missing URLs:
 | 
						|
 | 
						|
        >>> urls.match("/downloads")
 | 
						|
        Traceback (most recent call last):
 | 
						|
          ...
 | 
						|
        RequestRedirect: http://example.com/downloads/
 | 
						|
        >>> urls.match("/missing")
 | 
						|
        Traceback (most recent call last):
 | 
						|
          ...
 | 
						|
        NotFound: 404 Not Found
 | 
						|
 | 
						|
        :param path_info: the path info to use for matching.  Overrides the
 | 
						|
                          path info specified on binding.
 | 
						|
        :param method: the HTTP method used for matching.  Overrides the
 | 
						|
                       method specified on binding.
 | 
						|
        :param return_rule: return the rule that matched instead of just the
 | 
						|
                            endpoint (defaults to `False`).
 | 
						|
        :param query_args: optional query arguments that are used for
 | 
						|
                           automatic redirects as string or dictionary.  It's
 | 
						|
                           currently not possible to use the query arguments
 | 
						|
                           for URL matching.
 | 
						|
        :param websocket: Match WebSocket instead of HTTP requests. A
 | 
						|
            websocket request has a ``ws`` or ``wss``
 | 
						|
            :attr:`url_scheme`. This overrides that detection.
 | 
						|
 | 
						|
        .. versionadded:: 1.0
 | 
						|
            Added ``websocket``.
 | 
						|
 | 
						|
        .. versionchanged:: 0.8
 | 
						|
            ``query_args`` can be a string.
 | 
						|
 | 
						|
        .. versionadded:: 0.7
 | 
						|
            Added ``query_args``.
 | 
						|
 | 
						|
        .. versionadded:: 0.6
 | 
						|
            Added ``return_rule``.
 | 
						|
        """
 | 
						|
        self.map.update()
 | 
						|
        if path_info is None:
 | 
						|
            path_info = self.path_info
 | 
						|
        if query_args is None:
 | 
						|
            query_args = self.query_args or {}
 | 
						|
        method = (method or self.default_method).upper()
 | 
						|
 | 
						|
        if websocket is None:
 | 
						|
            websocket = self.websocket
 | 
						|
 | 
						|
        domain_part = self.server_name
 | 
						|
 | 
						|
        if not self.map.host_matching and self.subdomain is not None:
 | 
						|
            domain_part = self.subdomain
 | 
						|
 | 
						|
        path_part = f"/{path_info.lstrip('/')}" if path_info else ""
 | 
						|
 | 
						|
        try:
 | 
						|
            result = self.map._matcher.match(domain_part, path_part, method, websocket)
 | 
						|
        except RequestPath as e:
 | 
						|
            # safe = https://url.spec.whatwg.org/#url-path-segment-string
 | 
						|
            new_path = quote(e.path_info, safe="!$&'()*+,/:;=@")
 | 
						|
            raise RequestRedirect(
 | 
						|
                self.make_redirect_url(new_path, query_args)
 | 
						|
            ) from None
 | 
						|
        except RequestAliasRedirect as e:
 | 
						|
            raise RequestRedirect(
 | 
						|
                self.make_alias_redirect_url(
 | 
						|
                    f"{domain_part}|{path_part}",
 | 
						|
                    e.endpoint,
 | 
						|
                    e.matched_values,
 | 
						|
                    method,
 | 
						|
                    query_args,
 | 
						|
                )
 | 
						|
            ) from None
 | 
						|
        except NoMatch as e:
 | 
						|
            if e.have_match_for:
 | 
						|
                raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
 | 
						|
 | 
						|
            if e.websocket_mismatch:
 | 
						|
                raise WebsocketMismatch() from None
 | 
						|
 | 
						|
            raise NotFound() from None
 | 
						|
        else:
 | 
						|
            rule, rv = result
 | 
						|
 | 
						|
            if self.map.redirect_defaults:
 | 
						|
                redirect_url = self.get_default_redirect(rule, method, rv, query_args)
 | 
						|
                if redirect_url is not None:
 | 
						|
                    raise RequestRedirect(redirect_url)
 | 
						|
 | 
						|
            if rule.redirect_to is not None:
 | 
						|
                if isinstance(rule.redirect_to, str):
 | 
						|
 | 
						|
                    def _handle_match(match: t.Match[str]) -> str:
 | 
						|
                        value = rv[match.group(1)]
 | 
						|
                        return rule._converters[match.group(1)].to_url(value)
 | 
						|
 | 
						|
                    redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to)
 | 
						|
                else:
 | 
						|
                    redirect_url = rule.redirect_to(self, **rv)
 | 
						|
 | 
						|
                if self.subdomain:
 | 
						|
                    netloc = f"{self.subdomain}.{self.server_name}"
 | 
						|
                else:
 | 
						|
                    netloc = self.server_name
 | 
						|
 | 
						|
                raise RequestRedirect(
 | 
						|
                    urljoin(
 | 
						|
                        f"{self.url_scheme or 'http'}://{netloc}{self.script_name}",
 | 
						|
                        redirect_url,
 | 
						|
                    )
 | 
						|
                )
 | 
						|
 | 
						|
            if return_rule:
 | 
						|
                return rule, rv
 | 
						|
            else:
 | 
						|
                return rule.endpoint, rv
 | 
						|
 | 
						|
    def test(self, path_info: str | None = None, method: str | None = None) -> bool:
 | 
						|
        """Test if a rule would match.  Works like `match` but returns `True`
 | 
						|
        if the URL matches, or `False` if it does not exist.
 | 
						|
 | 
						|
        :param path_info: the path info to use for matching.  Overrides the
 | 
						|
                          path info specified on binding.
 | 
						|
        :param method: the HTTP method used for matching.  Overrides the
 | 
						|
                       method specified on binding.
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            self.match(path_info, method)
 | 
						|
        except RequestRedirect:
 | 
						|
            pass
 | 
						|
        except HTTPException:
 | 
						|
            return False
 | 
						|
        return True
 | 
						|
 | 
						|
    def allowed_methods(self, path_info: str | None = None) -> t.Iterable[str]:
 | 
						|
        """Returns the valid methods that match for a given path.
 | 
						|
 | 
						|
        .. versionadded:: 0.7
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            self.match(path_info, method="--")
 | 
						|
        except MethodNotAllowed as e:
 | 
						|
            return e.valid_methods  # type: ignore
 | 
						|
        except HTTPException:
 | 
						|
            pass
 | 
						|
        return []
 | 
						|
 | 
						|
    def get_host(self, domain_part: str | None) -> str:
 | 
						|
        """Figures out the full host name for the given domain part.  The
 | 
						|
        domain part is a subdomain in case host matching is disabled or
 | 
						|
        a full host name.
 | 
						|
        """
 | 
						|
        if self.map.host_matching:
 | 
						|
            if domain_part is None:
 | 
						|
                return self.server_name
 | 
						|
 | 
						|
            return domain_part
 | 
						|
 | 
						|
        if domain_part is None:
 | 
						|
            subdomain = self.subdomain
 | 
						|
        else:
 | 
						|
            subdomain = domain_part
 | 
						|
 | 
						|
        if subdomain:
 | 
						|
            return f"{subdomain}.{self.server_name}"
 | 
						|
        else:
 | 
						|
            return self.server_name
 | 
						|
 | 
						|
    def get_default_redirect(
 | 
						|
        self,
 | 
						|
        rule: Rule,
 | 
						|
        method: str,
 | 
						|
        values: t.MutableMapping[str, t.Any],
 | 
						|
        query_args: t.Mapping[str, t.Any] | str,
 | 
						|
    ) -> str | None:
 | 
						|
        """A helper that returns the URL to redirect to if it finds one.
 | 
						|
        This is used for default redirecting only.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        assert self.map.redirect_defaults
 | 
						|
        for r in self.map._rules_by_endpoint[rule.endpoint]:
 | 
						|
            # every rule that comes after this one, including ourself
 | 
						|
            # has a lower priority for the defaults.  We order the ones
 | 
						|
            # with the highest priority up for building.
 | 
						|
            if r is rule:
 | 
						|
                break
 | 
						|
            if r.provides_defaults_for(rule) and r.suitable_for(values, method):
 | 
						|
                values.update(r.defaults)  # type: ignore
 | 
						|
                domain_part, path = r.build(values)  # type: ignore
 | 
						|
                return self.make_redirect_url(path, query_args, domain_part=domain_part)
 | 
						|
        return None
 | 
						|
 | 
						|
    def encode_query_args(self, query_args: t.Mapping[str, t.Any] | str) -> str:
 | 
						|
        if not isinstance(query_args, str):
 | 
						|
            return _urlencode(query_args)
 | 
						|
        return query_args
 | 
						|
 | 
						|
    def make_redirect_url(
 | 
						|
        self,
 | 
						|
        path_info: str,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str | None = None,
 | 
						|
        domain_part: str | None = None,
 | 
						|
    ) -> str:
 | 
						|
        """Creates a redirect URL.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        if query_args is None:
 | 
						|
            query_args = self.query_args
 | 
						|
 | 
						|
        if query_args:
 | 
						|
            query_str = self.encode_query_args(query_args)
 | 
						|
        else:
 | 
						|
            query_str = None
 | 
						|
 | 
						|
        scheme = self.url_scheme or "http"
 | 
						|
        host = self.get_host(domain_part)
 | 
						|
        path = "/".join((self.script_name.strip("/"), path_info.lstrip("/")))
 | 
						|
        return urlunsplit((scheme, host, path, query_str, None))
 | 
						|
 | 
						|
    def make_alias_redirect_url(
 | 
						|
        self,
 | 
						|
        path: str,
 | 
						|
        endpoint: t.Any,
 | 
						|
        values: t.Mapping[str, t.Any],
 | 
						|
        method: str,
 | 
						|
        query_args: t.Mapping[str, t.Any] | str,
 | 
						|
    ) -> str:
 | 
						|
        """Internally called to make an alias redirect URL."""
 | 
						|
        url = self.build(
 | 
						|
            endpoint, values, method, append_unknown=False, force_external=True
 | 
						|
        )
 | 
						|
        if query_args:
 | 
						|
            url += f"?{self.encode_query_args(query_args)}"
 | 
						|
        assert url != path, "detected invalid alias setting. No canonical URL found"
 | 
						|
        return url
 | 
						|
 | 
						|
    def _partial_build(
 | 
						|
        self,
 | 
						|
        endpoint: t.Any,
 | 
						|
        values: t.Mapping[str, t.Any],
 | 
						|
        method: str | None,
 | 
						|
        append_unknown: bool,
 | 
						|
    ) -> tuple[str, str, bool] | None:
 | 
						|
        """Helper for :meth:`build`.  Returns subdomain and path for the
 | 
						|
        rule that accepts this endpoint, values and method.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        # in case the method is none, try with the default method first
 | 
						|
        if method is None:
 | 
						|
            rv = self._partial_build(
 | 
						|
                endpoint, values, self.default_method, append_unknown
 | 
						|
            )
 | 
						|
            if rv is not None:
 | 
						|
                return rv
 | 
						|
 | 
						|
        # Default method did not match or a specific method is passed.
 | 
						|
        # Check all for first match with matching host. If no matching
 | 
						|
        # host is found, go with first result.
 | 
						|
        first_match = None
 | 
						|
 | 
						|
        for rule in self.map._rules_by_endpoint.get(endpoint, ()):
 | 
						|
            if rule.suitable_for(values, method):
 | 
						|
                build_rv = rule.build(values, append_unknown)
 | 
						|
 | 
						|
                if build_rv is not None:
 | 
						|
                    rv = (build_rv[0], build_rv[1], rule.websocket)
 | 
						|
                    if self.map.host_matching:
 | 
						|
                        if rv[0] == self.server_name:
 | 
						|
                            return rv
 | 
						|
                        elif first_match is None:
 | 
						|
                            first_match = rv
 | 
						|
                    else:
 | 
						|
                        return rv
 | 
						|
 | 
						|
        return first_match
 | 
						|
 | 
						|
    def build(
 | 
						|
        self,
 | 
						|
        endpoint: t.Any,
 | 
						|
        values: t.Mapping[str, t.Any] | None = None,
 | 
						|
        method: str | None = None,
 | 
						|
        force_external: bool = False,
 | 
						|
        append_unknown: bool = True,
 | 
						|
        url_scheme: str | None = None,
 | 
						|
    ) -> str:
 | 
						|
        """Building URLs works pretty much the other way round.  Instead of
 | 
						|
        `match` you call `build` and pass it the endpoint and a dict of
 | 
						|
        arguments for the placeholders.
 | 
						|
 | 
						|
        The `build` function also accepts an argument called `force_external`
 | 
						|
        which, if you set it to `True` will force external URLs. Per default
 | 
						|
        external URLs (include the server name) will only be used if the
 | 
						|
        target URL is on a different subdomain.
 | 
						|
 | 
						|
        >>> m = Map([
 | 
						|
        ...     Rule('/', endpoint='index'),
 | 
						|
        ...     Rule('/downloads/', endpoint='downloads/index'),
 | 
						|
        ...     Rule('/downloads/<int:id>', endpoint='downloads/show')
 | 
						|
        ... ])
 | 
						|
        >>> urls = m.bind("example.com", "/")
 | 
						|
        >>> urls.build("index", {})
 | 
						|
        '/'
 | 
						|
        >>> urls.build("downloads/show", {'id': 42})
 | 
						|
        '/downloads/42'
 | 
						|
        >>> urls.build("downloads/show", {'id': 42}, force_external=True)
 | 
						|
        'http://example.com/downloads/42'
 | 
						|
 | 
						|
        Because URLs cannot contain non ASCII data you will always get
 | 
						|
        bytes back.  Non ASCII characters are urlencoded with the
 | 
						|
        charset defined on the map instance.
 | 
						|
 | 
						|
        Additional values are converted to strings and appended to the URL as
 | 
						|
        URL querystring parameters:
 | 
						|
 | 
						|
        >>> urls.build("index", {'q': 'My Searchstring'})
 | 
						|
        '/?q=My+Searchstring'
 | 
						|
 | 
						|
        When processing those additional values, lists are furthermore
 | 
						|
        interpreted as multiple values (as per
 | 
						|
        :py:class:`werkzeug.datastructures.MultiDict`):
 | 
						|
 | 
						|
        >>> urls.build("index", {'q': ['a', 'b', 'c']})
 | 
						|
        '/?q=a&q=b&q=c'
 | 
						|
 | 
						|
        Passing a ``MultiDict`` will also add multiple values:
 | 
						|
 | 
						|
        >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b'))))
 | 
						|
        '/?p=z&q=a&q=b'
 | 
						|
 | 
						|
        If a rule does not exist when building a `BuildError` exception is
 | 
						|
        raised.
 | 
						|
 | 
						|
        The build method accepts an argument called `method` which allows you
 | 
						|
        to specify the method you want to have an URL built for if you have
 | 
						|
        different methods for the same endpoint specified.
 | 
						|
 | 
						|
        :param endpoint: the endpoint of the URL to build.
 | 
						|
        :param values: the values for the URL to build.  Unhandled values are
 | 
						|
                       appended to the URL as query parameters.
 | 
						|
        :param method: the HTTP method for the rule if there are different
 | 
						|
                       URLs for different methods on the same endpoint.
 | 
						|
        :param force_external: enforce full canonical external URLs. If the URL
 | 
						|
                               scheme is not provided, this will generate
 | 
						|
                               a protocol-relative URL.
 | 
						|
        :param append_unknown: unknown parameters are appended to the generated
 | 
						|
                               URL as query string argument.  Disable this
 | 
						|
                               if you want the builder to ignore those.
 | 
						|
        :param url_scheme: Scheme to use in place of the bound
 | 
						|
            :attr:`url_scheme`.
 | 
						|
 | 
						|
        .. versionchanged:: 2.0
 | 
						|
            Added the ``url_scheme`` parameter.
 | 
						|
 | 
						|
        .. versionadded:: 0.6
 | 
						|
           Added the ``append_unknown`` parameter.
 | 
						|
        """
 | 
						|
        self.map.update()
 | 
						|
 | 
						|
        if values:
 | 
						|
            if isinstance(values, MultiDict):
 | 
						|
                values = {
 | 
						|
                    k: (v[0] if len(v) == 1 else v)
 | 
						|
                    for k, v in dict.items(values)
 | 
						|
                    if len(v) != 0
 | 
						|
                }
 | 
						|
            else:  # plain dict
 | 
						|
                values = {k: v for k, v in values.items() if v is not None}
 | 
						|
        else:
 | 
						|
            values = {}
 | 
						|
 | 
						|
        rv = self._partial_build(endpoint, values, method, append_unknown)
 | 
						|
        if rv is None:
 | 
						|
            raise BuildError(endpoint, values, method, self)
 | 
						|
 | 
						|
        domain_part, path, websocket = rv
 | 
						|
        host = self.get_host(domain_part)
 | 
						|
 | 
						|
        if url_scheme is None:
 | 
						|
            url_scheme = self.url_scheme
 | 
						|
 | 
						|
        # Always build WebSocket routes with the scheme (browsers
 | 
						|
        # require full URLs). If bound to a WebSocket, ensure that HTTP
 | 
						|
        # routes are built with an HTTP scheme.
 | 
						|
        secure = url_scheme in {"https", "wss"}
 | 
						|
 | 
						|
        if websocket:
 | 
						|
            force_external = True
 | 
						|
            url_scheme = "wss" if secure else "ws"
 | 
						|
        elif url_scheme:
 | 
						|
            url_scheme = "https" if secure else "http"
 | 
						|
 | 
						|
        # shortcut this.
 | 
						|
        if not force_external and (
 | 
						|
            (self.map.host_matching and host == self.server_name)
 | 
						|
            or (not self.map.host_matching and domain_part == self.subdomain)
 | 
						|
        ):
 | 
						|
            return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}"
 | 
						|
 | 
						|
        scheme = f"{url_scheme}:" if url_scheme else ""
 | 
						|
        return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}"
 |