up follow livre
This commit is contained in:
		
							parent
							
								
									b4b4398bb0
								
							
						
					
					
						commit
						3a7a3849ae
					
				
					 12242 changed files with 2564461 additions and 6914 deletions
				
			
		
							
								
								
									
										951
									
								
								venv/lib/python3.13/site-packages/werkzeug/routing/map.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										951
									
								
								venv/lib/python3.13/site-packages/werkzeug/routing/map.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,951 @@ | |||
| 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('/')}" | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tykayn
						Tykayn