up follow livre
This commit is contained in:
		
							parent
							
								
									b4b4398bb0
								
							
						
					
					
						commit
						3a7a3849ae
					
				
					 12242 changed files with 2564461 additions and 6914 deletions
				
			
		
							
								
								
									
										928
									
								
								venv/lib/python3.13/site-packages/werkzeug/routing/rules.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										928
									
								
								venv/lib/python3.13/site-packages/werkzeug/routing/rules.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,928 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import ast | ||||
| import re | ||||
| import typing as t | ||||
| from dataclasses import dataclass | ||||
| from string import Template | ||||
| from types import CodeType | ||||
| from urllib.parse import quote | ||||
| 
 | ||||
| from ..datastructures import iter_multi_items | ||||
| from ..urls import _urlencode | ||||
| from .converters import ValidationError | ||||
| 
 | ||||
| if t.TYPE_CHECKING: | ||||
|     from .converters import BaseConverter | ||||
|     from .map import Map | ||||
| 
 | ||||
| 
 | ||||
| class Weighting(t.NamedTuple): | ||||
|     number_static_weights: int | ||||
|     static_weights: list[tuple[int, int]] | ||||
|     number_argument_weights: int | ||||
|     argument_weights: list[int] | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class RulePart: | ||||
|     """A part of a rule. | ||||
| 
 | ||||
|     Rules can be represented by parts as delimited by `/` with | ||||
|     instances of this class representing those parts. The *content* is | ||||
|     either the raw content if *static* or a regex string to match | ||||
|     against. The *weight* can be used to order parts when matching. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     content: str | ||||
|     final: bool | ||||
|     static: bool | ||||
|     suffixed: bool | ||||
|     weight: Weighting | ||||
| 
 | ||||
| 
 | ||||
| _part_re = re.compile( | ||||
|     r""" | ||||
|     (?: | ||||
|         (?P<slash>/)                                 # a slash | ||||
|       | | ||||
|         (?P<static>[^</]+)                           # static rule data | ||||
|       | | ||||
|         (?: | ||||
|           < | ||||
|             (?: | ||||
|               (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)   # converter name | ||||
|               (?:\((?P<arguments>.*?)\))?             # converter arguments | ||||
|               :                                       # variable delimiter | ||||
|             )? | ||||
|             (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)      # variable name | ||||
|            > | ||||
|         ) | ||||
|     ) | ||||
|     """, | ||||
|     re.VERBOSE, | ||||
| ) | ||||
| 
 | ||||
| _simple_rule_re = re.compile(r"<([^>]+)>") | ||||
| _converter_args_re = re.compile( | ||||
|     r""" | ||||
|     \s* | ||||
|     ((?P<name>\w+)\s*=\s*)? | ||||
|     (?P<value> | ||||
|         True|False| | ||||
|         \d+.\d+| | ||||
|         \d+.| | ||||
|         \d+| | ||||
|         [\w\d_.]+| | ||||
|         [urUR]?(?P<stringval>"[^"]*?"|'[^']*') | ||||
|     )\s*, | ||||
|     """, | ||||
|     re.VERBOSE, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| _PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} | ||||
| 
 | ||||
| 
 | ||||
| def _find(value: str, target: str, pos: int) -> int: | ||||
|     """Find the *target* in *value* after *pos*. | ||||
| 
 | ||||
|     Returns the *value* length if *target* isn't found. | ||||
|     """ | ||||
|     try: | ||||
|         return value.index(target, pos) | ||||
|     except ValueError: | ||||
|         return len(value) | ||||
| 
 | ||||
| 
 | ||||
| def _pythonize(value: str) -> None | bool | int | float | str: | ||||
|     if value in _PYTHON_CONSTANTS: | ||||
|         return _PYTHON_CONSTANTS[value] | ||||
|     for convert in int, float: | ||||
|         try: | ||||
|             return convert(value) | ||||
|         except ValueError: | ||||
|             pass | ||||
|     if value[:1] == value[-1:] and value[0] in "\"'": | ||||
|         value = value[1:-1] | ||||
|     return str(value) | ||||
| 
 | ||||
| 
 | ||||
| def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.Any]]: | ||||
|     argstr += "," | ||||
|     args = [] | ||||
|     kwargs = {} | ||||
|     position = 0 | ||||
| 
 | ||||
|     for item in _converter_args_re.finditer(argstr): | ||||
|         if item.start() != position: | ||||
|             raise ValueError( | ||||
|                 f"Cannot parse converter argument '{argstr[position:item.start()]}'" | ||||
|             ) | ||||
| 
 | ||||
|         value = item.group("stringval") | ||||
|         if value is None: | ||||
|             value = item.group("value") | ||||
|         value = _pythonize(value) | ||||
|         if not item.group("name"): | ||||
|             args.append(value) | ||||
|         else: | ||||
|             name = item.group("name") | ||||
|             kwargs[name] = value | ||||
|         position = item.end() | ||||
| 
 | ||||
|     return tuple(args), kwargs | ||||
| 
 | ||||
| 
 | ||||
| class RuleFactory: | ||||
|     """As soon as you have more complex URL setups it's a good idea to use rule | ||||
|     factories to avoid repetitive tasks.  Some of them are builtin, others can | ||||
|     be added by subclassing `RuleFactory` and overriding `get_rules`. | ||||
|     """ | ||||
| 
 | ||||
|     def get_rules(self, map: Map) -> t.Iterable[Rule]: | ||||
|         """Subclasses of `RuleFactory` have to override this method and return | ||||
|         an iterable of rules.""" | ||||
|         raise NotImplementedError() | ||||
| 
 | ||||
| 
 | ||||
| class Subdomain(RuleFactory): | ||||
|     """All URLs provided by this factory have the subdomain set to a | ||||
|     specific domain. For example if you want to use the subdomain for | ||||
|     the current language this can be a good setup:: | ||||
| 
 | ||||
|         url_map = Map([ | ||||
|             Rule('/', endpoint='#select_language'), | ||||
|             Subdomain('<string(length=2):lang_code>', [ | ||||
|                 Rule('/', endpoint='index'), | ||||
|                 Rule('/about', endpoint='about'), | ||||
|                 Rule('/help', endpoint='help') | ||||
|             ]) | ||||
|         ]) | ||||
| 
 | ||||
|     All the rules except for the ``'#select_language'`` endpoint will now | ||||
|     listen on a two letter long subdomain that holds the language code | ||||
|     for the current request. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None: | ||||
|         self.subdomain = subdomain | ||||
|         self.rules = rules | ||||
| 
 | ||||
|     def get_rules(self, map: Map) -> t.Iterator[Rule]: | ||||
|         for rulefactory in self.rules: | ||||
|             for rule in rulefactory.get_rules(map): | ||||
|                 rule = rule.empty() | ||||
|                 rule.subdomain = self.subdomain | ||||
|                 yield rule | ||||
| 
 | ||||
| 
 | ||||
| class Submount(RuleFactory): | ||||
|     """Like `Subdomain` but prefixes the URL rule with a given string:: | ||||
| 
 | ||||
|         url_map = Map([ | ||||
|             Rule('/', endpoint='index'), | ||||
|             Submount('/blog', [ | ||||
|                 Rule('/', endpoint='blog/index'), | ||||
|                 Rule('/entry/<entry_slug>', endpoint='blog/show') | ||||
|             ]) | ||||
|         ]) | ||||
| 
 | ||||
|     Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None: | ||||
|         self.path = path.rstrip("/") | ||||
|         self.rules = rules | ||||
| 
 | ||||
|     def get_rules(self, map: Map) -> t.Iterator[Rule]: | ||||
|         for rulefactory in self.rules: | ||||
|             for rule in rulefactory.get_rules(map): | ||||
|                 rule = rule.empty() | ||||
|                 rule.rule = self.path + rule.rule | ||||
|                 yield rule | ||||
| 
 | ||||
| 
 | ||||
| class EndpointPrefix(RuleFactory): | ||||
|     """Prefixes all endpoints (which must be strings for this factory) with | ||||
|     another string. This can be useful for sub applications:: | ||||
| 
 | ||||
|         url_map = Map([ | ||||
|             Rule('/', endpoint='index'), | ||||
|             EndpointPrefix('blog/', [Submount('/blog', [ | ||||
|                 Rule('/', endpoint='index'), | ||||
|                 Rule('/entry/<entry_slug>', endpoint='show') | ||||
|             ])]) | ||||
|         ]) | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None: | ||||
|         self.prefix = prefix | ||||
|         self.rules = rules | ||||
| 
 | ||||
|     def get_rules(self, map: Map) -> t.Iterator[Rule]: | ||||
|         for rulefactory in self.rules: | ||||
|             for rule in rulefactory.get_rules(map): | ||||
|                 rule = rule.empty() | ||||
|                 rule.endpoint = self.prefix + rule.endpoint | ||||
|                 yield rule | ||||
| 
 | ||||
| 
 | ||||
| class RuleTemplate: | ||||
|     """Returns copies of the rules wrapped and expands string templates in | ||||
|     the endpoint, rule, defaults or subdomain sections. | ||||
| 
 | ||||
|     Here a small example for such a rule template:: | ||||
| 
 | ||||
|         from werkzeug.routing import Map, Rule, RuleTemplate | ||||
| 
 | ||||
|         resource = RuleTemplate([ | ||||
|             Rule('/$name/', endpoint='$name.list'), | ||||
|             Rule('/$name/<int:id>', endpoint='$name.show') | ||||
|         ]) | ||||
| 
 | ||||
|         url_map = Map([resource(name='user'), resource(name='page')]) | ||||
| 
 | ||||
|     When a rule template is called the keyword arguments are used to | ||||
|     replace the placeholders in all the string parameters. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, rules: t.Iterable[Rule]) -> None: | ||||
|         self.rules = list(rules) | ||||
| 
 | ||||
|     def __call__(self, *args: t.Any, **kwargs: t.Any) -> RuleTemplateFactory: | ||||
|         return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) | ||||
| 
 | ||||
| 
 | ||||
| class RuleTemplateFactory(RuleFactory): | ||||
|     """A factory that fills in template variables into rules.  Used by | ||||
|     `RuleTemplate` internally. | ||||
| 
 | ||||
|     :internal: | ||||
|     """ | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, rules: t.Iterable[RuleFactory], context: dict[str, t.Any] | ||||
|     ) -> None: | ||||
|         self.rules = rules | ||||
|         self.context = context | ||||
| 
 | ||||
|     def get_rules(self, map: Map) -> t.Iterator[Rule]: | ||||
|         for rulefactory in self.rules: | ||||
|             for rule in rulefactory.get_rules(map): | ||||
|                 new_defaults = subdomain = None | ||||
|                 if rule.defaults: | ||||
|                     new_defaults = {} | ||||
|                     for key, value in rule.defaults.items(): | ||||
|                         if isinstance(value, str): | ||||
|                             value = Template(value).substitute(self.context) | ||||
|                         new_defaults[key] = value | ||||
|                 if rule.subdomain is not None: | ||||
|                     subdomain = Template(rule.subdomain).substitute(self.context) | ||||
|                 new_endpoint = rule.endpoint | ||||
|                 if isinstance(new_endpoint, str): | ||||
|                     new_endpoint = Template(new_endpoint).substitute(self.context) | ||||
|                 yield Rule( | ||||
|                     Template(rule.rule).substitute(self.context), | ||||
|                     new_defaults, | ||||
|                     subdomain, | ||||
|                     rule.methods, | ||||
|                     rule.build_only, | ||||
|                     new_endpoint, | ||||
|                     rule.strict_slashes, | ||||
|                 ) | ||||
| 
 | ||||
| 
 | ||||
| _ASTT = t.TypeVar("_ASTT", bound=ast.AST) | ||||
| 
 | ||||
| 
 | ||||
| def _prefix_names(src: str, expected_type: type[_ASTT]) -> _ASTT: | ||||
|     """ast parse and prefix names with `.` to avoid collision with user vars""" | ||||
|     tree: ast.AST = ast.parse(src).body[0] | ||||
|     if isinstance(tree, ast.Expr): | ||||
|         tree = tree.value | ||||
|     if not isinstance(tree, expected_type): | ||||
|         raise TypeError( | ||||
|             f"AST node is of type {type(tree).__name__}, not {expected_type.__name__}" | ||||
|         ) | ||||
|     for node in ast.walk(tree): | ||||
|         if isinstance(node, ast.Name): | ||||
|             node.id = f".{node.id}" | ||||
|     return tree | ||||
| 
 | ||||
| 
 | ||||
| _CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" | ||||
| _IF_KWARGS_URL_ENCODE_CODE = """\ | ||||
| if kwargs: | ||||
|     params = self._encode_query_vars(kwargs) | ||||
|     q = "?" if params else "" | ||||
| else: | ||||
|     q = params = "" | ||||
| """ | ||||
| _IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE, ast.If) | ||||
| _URL_ENCODE_AST_NAMES = ( | ||||
|     _prefix_names("q", ast.Name), | ||||
|     _prefix_names("params", ast.Name), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class Rule(RuleFactory): | ||||
|     """A Rule represents one URL pattern.  There are some options for `Rule` | ||||
|     that change the way it behaves and are passed to the `Rule` constructor. | ||||
|     Note that besides the rule-string all arguments *must* be keyword arguments | ||||
|     in order to not break the application on Werkzeug upgrades. | ||||
| 
 | ||||
|     `string` | ||||
|         Rule strings basically are just normal URL paths with placeholders in | ||||
|         the format ``<converter(arguments):name>`` where the converter and the | ||||
|         arguments are optional.  If no converter is defined the `default` | ||||
|         converter is used which means `string` in the normal configuration. | ||||
| 
 | ||||
|         URL rules that end with a slash are branch URLs, others are leaves. | ||||
|         If you have `strict_slashes` enabled (which is the default), all | ||||
|         branch URLs that are matched without a trailing slash will trigger a | ||||
|         redirect to the same URL with the missing slash appended. | ||||
| 
 | ||||
|         The converters are defined on the `Map`. | ||||
| 
 | ||||
|     `endpoint` | ||||
|         The endpoint for this rule. This can be anything. A reference to a | ||||
|         function, a string, a number etc.  The preferred way is using a string | ||||
|         because the endpoint is used for URL generation. | ||||
| 
 | ||||
|     `defaults` | ||||
|         An optional dict with defaults for other rules with the same endpoint. | ||||
|         This is a bit tricky but useful if you want to have unique URLs:: | ||||
| 
 | ||||
|             url_map = Map([ | ||||
|                 Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), | ||||
|                 Rule('/all/page/<int:page>', endpoint='all_entries') | ||||
|             ]) | ||||
| 
 | ||||
|         If a user now visits ``http://example.com/all/page/1`` they will be | ||||
|         redirected to ``http://example.com/all/``.  If `redirect_defaults` is | ||||
|         disabled on the `Map` instance this will only affect the URL | ||||
|         generation. | ||||
| 
 | ||||
|     `subdomain` | ||||
|         The subdomain rule string for this rule. If not specified the rule | ||||
|         only matches for the `default_subdomain` of the map.  If the map is | ||||
|         not bound to a subdomain this feature is disabled. | ||||
| 
 | ||||
|         Can be useful if you want to have user profiles on different subdomains | ||||
|         and all subdomains are forwarded to your application:: | ||||
| 
 | ||||
|             url_map = Map([ | ||||
|                 Rule('/', subdomain='<username>', endpoint='user/homepage'), | ||||
|                 Rule('/stats', subdomain='<username>', endpoint='user/stats') | ||||
|             ]) | ||||
| 
 | ||||
|     `methods` | ||||
|         A sequence of http methods this rule applies to.  If not specified, all | ||||
|         methods are allowed. For example this can be useful if you want different | ||||
|         endpoints for `POST` and `GET`.  If methods are defined and the path | ||||
|         matches but the method matched against is not in this list or in the | ||||
|         list of another rule for that path the error raised is of the type | ||||
|         `MethodNotAllowed` rather than `NotFound`.  If `GET` is present in the | ||||
|         list of methods and `HEAD` is not, `HEAD` is added automatically. | ||||
| 
 | ||||
|     `strict_slashes` | ||||
|         Override the `Map` setting for `strict_slashes` only for this rule. If | ||||
|         not specified the `Map` setting is used. | ||||
| 
 | ||||
|     `merge_slashes` | ||||
|         Override :attr:`Map.merge_slashes` for this rule. | ||||
| 
 | ||||
|     `build_only` | ||||
|         Set this to True and the rule will never match but will create a URL | ||||
|         that can be build. This is useful if you have resources on a subdomain | ||||
|         or folder that are not handled by the WSGI application (like static data) | ||||
| 
 | ||||
|     `redirect_to` | ||||
|         If given this must be either a string or callable.  In case of a | ||||
|         callable it's called with the url adapter that triggered the match and | ||||
|         the values of the URL as keyword arguments and has to return the target | ||||
|         for the redirect, otherwise it has to be a string with placeholders in | ||||
|         rule syntax:: | ||||
| 
 | ||||
|             def foo_with_slug(adapter, id): | ||||
|                 # ask the database for the slug for the old id.  this of | ||||
|                 # course has nothing to do with werkzeug. | ||||
|                 return f'foo/{Foo.get_slug_for_id(id)}' | ||||
| 
 | ||||
|             url_map = Map([ | ||||
|                 Rule('/foo/<slug>', endpoint='foo'), | ||||
|                 Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'), | ||||
|                 Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug) | ||||
|             ]) | ||||
| 
 | ||||
|         When the rule is matched the routing system will raise a | ||||
|         `RequestRedirect` exception with the target for the redirect. | ||||
| 
 | ||||
|         Keep in mind that the URL will be joined against the URL root of the | ||||
|         script so don't use a leading slash on the target URL unless you | ||||
|         really mean root of that domain. | ||||
| 
 | ||||
|     `alias` | ||||
|         If enabled this rule serves as an alias for another rule with the same | ||||
|         endpoint and arguments. | ||||
| 
 | ||||
|     `host` | ||||
|         If provided and the URL map has host matching enabled this can be | ||||
|         used to provide a match rule for the whole host.  This also means | ||||
|         that the subdomain feature is disabled. | ||||
| 
 | ||||
|     `websocket` | ||||
|         If ``True``, this rule is only matches for WebSocket (``ws://``, | ||||
|         ``wss://``) requests. By default, rules will only match for HTTP | ||||
|         requests. | ||||
| 
 | ||||
|     .. versionchanged:: 2.1 | ||||
|         Percent-encoded newlines (``%0a``), which are decoded by WSGI | ||||
|         servers, are considered when routing instead of terminating the | ||||
|         match early. | ||||
| 
 | ||||
|     .. versionadded:: 1.0 | ||||
|         Added ``websocket``. | ||||
| 
 | ||||
|     .. versionadded:: 1.0 | ||||
|         Added ``merge_slashes``. | ||||
| 
 | ||||
|     .. versionadded:: 0.7 | ||||
|         Added ``alias`` and ``host``. | ||||
| 
 | ||||
|     .. versionchanged:: 0.6.1 | ||||
|        ``HEAD`` is added to ``methods`` if ``GET`` is present. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         string: str, | ||||
|         defaults: t.Mapping[str, t.Any] | None = None, | ||||
|         subdomain: str | None = None, | ||||
|         methods: t.Iterable[str] | None = None, | ||||
|         build_only: bool = False, | ||||
|         endpoint: t.Any | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         merge_slashes: bool | None = None, | ||||
|         redirect_to: str | t.Callable[..., str] | None = None, | ||||
|         alias: bool = False, | ||||
|         host: str | None = None, | ||||
|         websocket: bool = False, | ||||
|     ) -> None: | ||||
|         if not string.startswith("/"): | ||||
|             raise ValueError(f"URL rule '{string}' must start with a slash.") | ||||
| 
 | ||||
|         self.rule = string | ||||
|         self.is_leaf = not string.endswith("/") | ||||
|         self.is_branch = string.endswith("/") | ||||
| 
 | ||||
|         self.map: Map = None  # type: ignore | ||||
|         self.strict_slashes = strict_slashes | ||||
|         self.merge_slashes = merge_slashes | ||||
|         self.subdomain = subdomain | ||||
|         self.host = host | ||||
|         self.defaults = defaults | ||||
|         self.build_only = build_only | ||||
|         self.alias = alias | ||||
|         self.websocket = websocket | ||||
| 
 | ||||
|         if methods is not None: | ||||
|             if isinstance(methods, str): | ||||
|                 raise TypeError("'methods' should be a list of strings.") | ||||
| 
 | ||||
|             methods = {x.upper() for x in methods} | ||||
| 
 | ||||
|             if "HEAD" not in methods and "GET" in methods: | ||||
|                 methods.add("HEAD") | ||||
| 
 | ||||
|             if websocket and methods - {"GET", "HEAD", "OPTIONS"}: | ||||
|                 raise ValueError( | ||||
|                     "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." | ||||
|                 ) | ||||
| 
 | ||||
|         self.methods = methods | ||||
|         self.endpoint: t.Any = endpoint | ||||
|         self.redirect_to = redirect_to | ||||
| 
 | ||||
|         if defaults: | ||||
|             self.arguments = set(map(str, defaults)) | ||||
|         else: | ||||
|             self.arguments = set() | ||||
| 
 | ||||
|         self._converters: dict[str, BaseConverter] = {} | ||||
|         self._trace: list[tuple[bool, str]] = [] | ||||
|         self._parts: list[RulePart] = [] | ||||
| 
 | ||||
|     def empty(self) -> Rule: | ||||
|         """ | ||||
|         Return an unbound copy of this rule. | ||||
| 
 | ||||
|         This can be useful if want to reuse an already bound URL for another | ||||
|         map.  See ``get_empty_kwargs`` to override what keyword arguments are | ||||
|         provided to the new copy. | ||||
|         """ | ||||
|         return type(self)(self.rule, **self.get_empty_kwargs()) | ||||
| 
 | ||||
|     def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: | ||||
|         """ | ||||
|         Provides kwargs for instantiating empty copy with empty() | ||||
| 
 | ||||
|         Use this method to provide custom keyword arguments to the subclass of | ||||
|         ``Rule`` when calling ``some_rule.empty()``.  Helpful when the subclass | ||||
|         has custom keyword arguments that are needed at instantiation. | ||||
| 
 | ||||
|         Must return a ``dict`` that will be provided as kwargs to the new | ||||
|         instance of ``Rule``, following the initial ``self.rule`` value which | ||||
|         is always provided as the first, required positional argument. | ||||
|         """ | ||||
|         defaults = None | ||||
|         if self.defaults: | ||||
|             defaults = dict(self.defaults) | ||||
|         return dict( | ||||
|             defaults=defaults, | ||||
|             subdomain=self.subdomain, | ||||
|             methods=self.methods, | ||||
|             build_only=self.build_only, | ||||
|             endpoint=self.endpoint, | ||||
|             strict_slashes=self.strict_slashes, | ||||
|             redirect_to=self.redirect_to, | ||||
|             alias=self.alias, | ||||
|             host=self.host, | ||||
|         ) | ||||
| 
 | ||||
|     def get_rules(self, map: Map) -> t.Iterator[Rule]: | ||||
|         yield self | ||||
| 
 | ||||
|     def refresh(self) -> None: | ||||
|         """Rebinds and refreshes the URL.  Call this if you modified the | ||||
|         rule in place. | ||||
| 
 | ||||
|         :internal: | ||||
|         """ | ||||
|         self.bind(self.map, rebind=True) | ||||
| 
 | ||||
|     def bind(self, map: Map, rebind: bool = False) -> None: | ||||
|         """Bind the url to a map and create a regular expression based on | ||||
|         the information from the rule itself and the defaults from the map. | ||||
| 
 | ||||
|         :internal: | ||||
|         """ | ||||
|         if self.map is not None and not rebind: | ||||
|             raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") | ||||
|         self.map = map | ||||
|         if self.strict_slashes is None: | ||||
|             self.strict_slashes = map.strict_slashes | ||||
|         if self.merge_slashes is None: | ||||
|             self.merge_slashes = map.merge_slashes | ||||
|         if self.subdomain is None: | ||||
|             self.subdomain = map.default_subdomain | ||||
|         self.compile() | ||||
| 
 | ||||
|     def get_converter( | ||||
|         self, | ||||
|         variable_name: str, | ||||
|         converter_name: str, | ||||
|         args: tuple[t.Any, ...], | ||||
|         kwargs: t.Mapping[str, t.Any], | ||||
|     ) -> BaseConverter: | ||||
|         """Looks up the converter for the given parameter. | ||||
| 
 | ||||
|         .. versionadded:: 0.9 | ||||
|         """ | ||||
|         if converter_name not in self.map.converters: | ||||
|             raise LookupError(f"the converter {converter_name!r} does not exist") | ||||
|         return self.map.converters[converter_name](self.map, *args, **kwargs) | ||||
| 
 | ||||
|     def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: | ||||
|         items: t.Iterable[tuple[str, str]] = iter_multi_items(query_vars) | ||||
| 
 | ||||
|         if self.map.sort_parameters: | ||||
|             items = sorted(items, key=self.map.sort_key) | ||||
| 
 | ||||
|         return _urlencode(items) | ||||
| 
 | ||||
|     def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: | ||||
|         content = "" | ||||
|         static = True | ||||
|         argument_weights = [] | ||||
|         static_weights: list[tuple[int, int]] = [] | ||||
|         final = False | ||||
|         convertor_number = 0 | ||||
| 
 | ||||
|         pos = 0 | ||||
|         while pos < len(rule): | ||||
|             match = _part_re.match(rule, pos) | ||||
|             if match is None: | ||||
|                 raise ValueError(f"malformed url rule: {rule!r}") | ||||
| 
 | ||||
|             data = match.groupdict() | ||||
|             if data["static"] is not None: | ||||
|                 static_weights.append((len(static_weights), -len(data["static"]))) | ||||
|                 self._trace.append((False, data["static"])) | ||||
|                 content += data["static"] if static else re.escape(data["static"]) | ||||
| 
 | ||||
|             if data["variable"] is not None: | ||||
|                 if static: | ||||
|                     # Switching content to represent regex, hence the need to escape | ||||
|                     content = re.escape(content) | ||||
|                 static = False | ||||
|                 c_args, c_kwargs = parse_converter_args(data["arguments"] or "") | ||||
|                 convobj = self.get_converter( | ||||
|                     data["variable"], data["converter"] or "default", c_args, c_kwargs | ||||
|                 ) | ||||
|                 self._converters[data["variable"]] = convobj | ||||
|                 self.arguments.add(data["variable"]) | ||||
|                 if not convobj.part_isolating: | ||||
|                     final = True | ||||
|                 content += f"(?P<__werkzeug_{convertor_number}>{convobj.regex})" | ||||
|                 convertor_number += 1 | ||||
|                 argument_weights.append(convobj.weight) | ||||
|                 self._trace.append((True, data["variable"])) | ||||
| 
 | ||||
|             if data["slash"] is not None: | ||||
|                 self._trace.append((False, "/")) | ||||
|                 if final: | ||||
|                     content += "/" | ||||
|                 else: | ||||
|                     if not static: | ||||
|                         content += r"\Z" | ||||
|                     weight = Weighting( | ||||
|                         -len(static_weights), | ||||
|                         static_weights, | ||||
|                         -len(argument_weights), | ||||
|                         argument_weights, | ||||
|                     ) | ||||
|                     yield RulePart( | ||||
|                         content=content, | ||||
|                         final=final, | ||||
|                         static=static, | ||||
|                         suffixed=False, | ||||
|                         weight=weight, | ||||
|                     ) | ||||
|                     content = "" | ||||
|                     static = True | ||||
|                     argument_weights = [] | ||||
|                     static_weights = [] | ||||
|                     final = False | ||||
|                     convertor_number = 0 | ||||
| 
 | ||||
|             pos = match.end() | ||||
| 
 | ||||
|         suffixed = False | ||||
|         if final and content[-1] == "/": | ||||
|             # If a converter is part_isolating=False (matches slashes) and ends with a | ||||
|             # slash, augment the regex to support slash redirects. | ||||
|             suffixed = True | ||||
|             content = content[:-1] + "(?<!/)(/?)" | ||||
|         if not static: | ||||
|             content += r"\Z" | ||||
|         weight = Weighting( | ||||
|             -len(static_weights), | ||||
|             static_weights, | ||||
|             -len(argument_weights), | ||||
|             argument_weights, | ||||
|         ) | ||||
|         yield RulePart( | ||||
|             content=content, | ||||
|             final=final, | ||||
|             static=static, | ||||
|             suffixed=suffixed, | ||||
|             weight=weight, | ||||
|         ) | ||||
|         if suffixed: | ||||
|             yield RulePart( | ||||
|                 content="", final=False, static=True, suffixed=False, weight=weight | ||||
|             ) | ||||
| 
 | ||||
|     def compile(self) -> None: | ||||
|         """Compiles the regular expression and stores it.""" | ||||
|         assert self.map is not None, "rule not bound" | ||||
| 
 | ||||
|         if self.map.host_matching: | ||||
|             domain_rule = self.host or "" | ||||
|         else: | ||||
|             domain_rule = self.subdomain or "" | ||||
|         self._parts = [] | ||||
|         self._trace = [] | ||||
|         self._converters = {} | ||||
|         if domain_rule == "": | ||||
|             self._parts = [ | ||||
|                 RulePart( | ||||
|                     content="", | ||||
|                     final=False, | ||||
|                     static=True, | ||||
|                     suffixed=False, | ||||
|                     weight=Weighting(0, [], 0, []), | ||||
|                 ) | ||||
|             ] | ||||
|         else: | ||||
|             self._parts.extend(self._parse_rule(domain_rule)) | ||||
|         self._trace.append((False, "|")) | ||||
|         rule = self.rule | ||||
|         if self.merge_slashes: | ||||
|             rule = re.sub("/{2,}?", "/", self.rule) | ||||
|         self._parts.extend(self._parse_rule(rule)) | ||||
| 
 | ||||
|         self._build: t.Callable[..., tuple[str, str]] | ||||
|         self._build = self._compile_builder(False).__get__(self, None) | ||||
|         self._build_unknown: t.Callable[..., tuple[str, str]] | ||||
|         self._build_unknown = self._compile_builder(True).__get__(self, None) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _get_func_code(code: CodeType, name: str) -> t.Callable[..., tuple[str, str]]: | ||||
|         globs: dict[str, t.Any] = {} | ||||
|         locs: dict[str, t.Any] = {} | ||||
|         exec(code, globs, locs) | ||||
|         return locs[name]  # type: ignore | ||||
| 
 | ||||
|     def _compile_builder( | ||||
|         self, append_unknown: bool = True | ||||
|     ) -> t.Callable[..., tuple[str, str]]: | ||||
|         defaults = self.defaults or {} | ||||
|         dom_ops: list[tuple[bool, str]] = [] | ||||
|         url_ops: list[tuple[bool, str]] = [] | ||||
| 
 | ||||
|         opl = dom_ops | ||||
|         for is_dynamic, data in self._trace: | ||||
|             if data == "|" and opl is dom_ops: | ||||
|                 opl = url_ops | ||||
|                 continue | ||||
|             # this seems like a silly case to ever come up but: | ||||
|             # if a default is given for a value that appears in the rule, | ||||
|             # resolve it to a constant ahead of time | ||||
|             if is_dynamic and data in defaults: | ||||
|                 data = self._converters[data].to_url(defaults[data]) | ||||
|                 opl.append((False, data)) | ||||
|             elif not is_dynamic: | ||||
|                 # safe = https://url.spec.whatwg.org/#url-path-segment-string | ||||
|                 opl.append((False, quote(data, safe="!$&'()*+,/:;=@"))) | ||||
|             else: | ||||
|                 opl.append((True, data)) | ||||
| 
 | ||||
|         def _convert(elem: str) -> ast.Call: | ||||
|             ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem), ast.Call) | ||||
|             ret.args = [ast.Name(elem, ast.Load())] | ||||
|             return ret | ||||
| 
 | ||||
|         def _parts(ops: list[tuple[bool, str]]) -> list[ast.expr]: | ||||
|             parts: list[ast.expr] = [ | ||||
|                 _convert(elem) if is_dynamic else ast.Constant(elem) | ||||
|                 for is_dynamic, elem in ops | ||||
|             ] | ||||
|             parts = parts or [ast.Constant("")] | ||||
|             # constant fold | ||||
|             ret = [parts[0]] | ||||
|             for p in parts[1:]: | ||||
|                 if isinstance(p, ast.Constant) and isinstance(ret[-1], ast.Constant): | ||||
|                     ret[-1] = ast.Constant(ret[-1].value + p.value) | ||||
|                 else: | ||||
|                     ret.append(p) | ||||
|             return ret | ||||
| 
 | ||||
|         dom_parts = _parts(dom_ops) | ||||
|         url_parts = _parts(url_ops) | ||||
|         body: list[ast.stmt] | ||||
|         if not append_unknown: | ||||
|             body = [] | ||||
|         else: | ||||
|             body = [_IF_KWARGS_URL_ENCODE_AST] | ||||
|             url_parts.extend(_URL_ENCODE_AST_NAMES) | ||||
| 
 | ||||
|         def _join(parts: list[ast.expr]) -> ast.expr: | ||||
|             if len(parts) == 1:  # shortcut | ||||
|                 return parts[0] | ||||
|             return ast.JoinedStr(parts) | ||||
| 
 | ||||
|         body.append( | ||||
|             ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) | ||||
|         ) | ||||
| 
 | ||||
|         pargs = [ | ||||
|             elem | ||||
|             for is_dynamic, elem in dom_ops + url_ops | ||||
|             if is_dynamic and elem not in defaults | ||||
|         ] | ||||
|         kargs = [str(k) for k in defaults] | ||||
| 
 | ||||
|         func_ast = _prefix_names("def _(): pass", ast.FunctionDef) | ||||
|         func_ast.name = f"<builder:{self.rule!r}>" | ||||
|         func_ast.args.args.append(ast.arg(".self", None)) | ||||
|         for arg in pargs + kargs: | ||||
|             func_ast.args.args.append(ast.arg(arg, None)) | ||||
|         func_ast.args.kwarg = ast.arg(".kwargs", None) | ||||
|         for _ in kargs: | ||||
|             func_ast.args.defaults.append(ast.Constant("")) | ||||
|         func_ast.body = body | ||||
| 
 | ||||
|         # Use `ast.parse` instead of `ast.Module` for better portability, since the | ||||
|         # signature of `ast.Module` can change. | ||||
|         module = ast.parse("") | ||||
|         module.body = [func_ast] | ||||
| 
 | ||||
|         # mark everything as on line 1, offset 0 | ||||
|         # less error-prone than `ast.fix_missing_locations` | ||||
|         # bad line numbers cause an assert to fail in debug builds | ||||
|         for node in ast.walk(module): | ||||
|             if "lineno" in node._attributes: | ||||
|                 node.lineno = 1  # type: ignore[attr-defined] | ||||
|             if "end_lineno" in node._attributes: | ||||
|                 node.end_lineno = node.lineno  # type: ignore[attr-defined] | ||||
|             if "col_offset" in node._attributes: | ||||
|                 node.col_offset = 0  # type: ignore[attr-defined] | ||||
|             if "end_col_offset" in node._attributes: | ||||
|                 node.end_col_offset = node.col_offset  # type: ignore[attr-defined] | ||||
| 
 | ||||
|         code = compile(module, "<werkzeug routing>", "exec") | ||||
|         return self._get_func_code(code, func_ast.name) | ||||
| 
 | ||||
|     def build( | ||||
|         self, values: t.Mapping[str, t.Any], append_unknown: bool = True | ||||
|     ) -> tuple[str, str] | None: | ||||
|         """Assembles the relative url for that rule and the subdomain. | ||||
|         If building doesn't work for some reasons `None` is returned. | ||||
| 
 | ||||
|         :internal: | ||||
|         """ | ||||
|         try: | ||||
|             if append_unknown: | ||||
|                 return self._build_unknown(**values) | ||||
|             else: | ||||
|                 return self._build(**values) | ||||
|         except ValidationError: | ||||
|             return None | ||||
| 
 | ||||
|     def provides_defaults_for(self, rule: Rule) -> bool: | ||||
|         """Check if this rule has defaults for a given rule. | ||||
| 
 | ||||
|         :internal: | ||||
|         """ | ||||
|         return bool( | ||||
|             not self.build_only | ||||
|             and self.defaults | ||||
|             and self.endpoint == rule.endpoint | ||||
|             and self != rule | ||||
|             and self.arguments == rule.arguments | ||||
|         ) | ||||
| 
 | ||||
|     def suitable_for( | ||||
|         self, values: t.Mapping[str, t.Any], method: str | None = None | ||||
|     ) -> bool: | ||||
|         """Check if the dict of values has enough data for url generation. | ||||
| 
 | ||||
|         :internal: | ||||
|         """ | ||||
|         # if a method was given explicitly and that method is not supported | ||||
|         # by this rule, this rule is not suitable. | ||||
|         if ( | ||||
|             method is not None | ||||
|             and self.methods is not None | ||||
|             and method not in self.methods | ||||
|         ): | ||||
|             return False | ||||
| 
 | ||||
|         defaults = self.defaults or () | ||||
| 
 | ||||
|         # all arguments required must be either in the defaults dict or | ||||
|         # the value dictionary otherwise it's not suitable | ||||
|         for key in self.arguments: | ||||
|             if key not in defaults and key not in values: | ||||
|                 return False | ||||
| 
 | ||||
|         # in case defaults are given we ensure that either the value was | ||||
|         # skipped or the value is the same as the default value. | ||||
|         if defaults: | ||||
|             for key, value in defaults.items(): | ||||
|                 if key in values and value != values[key]: | ||||
|                     return False | ||||
| 
 | ||||
|         return True | ||||
| 
 | ||||
|     def build_compare_key(self) -> tuple[int, int, int]: | ||||
|         """The build compare key for sorting. | ||||
| 
 | ||||
|         :internal: | ||||
|         """ | ||||
|         return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) | ||||
| 
 | ||||
|     def __eq__(self, other: object) -> bool: | ||||
|         return isinstance(other, type(self)) and self._trace == other._trace | ||||
| 
 | ||||
|     __hash__ = None  # type: ignore | ||||
| 
 | ||||
|     def __str__(self) -> str: | ||||
|         return self.rule | ||||
| 
 | ||||
|     def __repr__(self) -> str: | ||||
|         if self.map is None: | ||||
|             return f"<{type(self).__name__} (unbound)>" | ||||
|         parts = [] | ||||
|         for is_dynamic, data in self._trace: | ||||
|             if is_dynamic: | ||||
|                 parts.append(f"<{data}>") | ||||
|             else: | ||||
|                 parts.append(data) | ||||
|         parts_str = "".join(parts).lstrip("|") | ||||
|         methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" | ||||
|         return f"<{type(self).__name__} {parts_str!r}{methods} -> {self.endpoint}>" | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tykayn
						Tykayn