Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/routing/rules.py: 22%
364 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1import ast
2import re
3import typing as t
4from dataclasses import dataclass
5from string import Template
6from types import CodeType
8from .._internal import _to_bytes
9from ..urls import url_encode
10from ..urls import url_quote
11from .converters import ValidationError
13if t.TYPE_CHECKING:
14 from .converters import BaseConverter
15 from .map import Map
18class Weighting(t.NamedTuple):
19 number_static_weights: int
20 static_weights: t.List[t.Tuple[int, int]]
21 number_argument_weights: int
22 argument_weights: t.List[int]
25@dataclass
26class RulePart:
27 """A part of a rule.
29 Rules can be represented by parts as delimited by `/` with
30 instances of this class representing those parts. The *content* is
31 either the raw content if *static* or a regex string to match
32 against. The *weight* can be used to order parts when matching.
34 """
36 content: str
37 final: bool
38 static: bool
39 weight: Weighting
42_part_re = re.compile(
43 r"""
44 (?:
45 (?P<slash>\/) # a slash
46 |
47 (?P<static>[^<\/]+) # static rule data
48 |
49 (?:
50 <
51 (?:
52 (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name
53 (?:\((?P<arguments>.*?)\))? # converter arguments
54 \: # variable delimiter
55 )?
56 (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # variable name
57 >
58 )
59 )
60 """,
61 re.VERBOSE,
62)
64_simple_rule_re = re.compile(r"<([^>]+)>")
65_converter_args_re = re.compile(
66 r"""
67 ((?P<name>\w+)\s*=\s*)?
68 (?P<value>
69 True|False|
70 \d+.\d+|
71 \d+.|
72 \d+|
73 [\w\d_.]+|
74 [urUR]?(?P<stringval>"[^"]*?"|'[^']*')
75 )\s*,
76 """,
77 re.VERBOSE,
78)
81_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False}
84def _find(value: str, target: str, pos: int) -> int:
85 """Find the *target* in *value* after *pos*.
87 Returns the *value* length if *target* isn't found.
88 """
89 try:
90 return value.index(target, pos)
91 except ValueError:
92 return len(value)
95def _pythonize(value: str) -> t.Union[None, bool, int, float, str]:
96 if value in _PYTHON_CONSTANTS:
97 return _PYTHON_CONSTANTS[value]
98 for convert in int, float:
99 try:
100 return convert(value) # type: ignore
101 except ValueError:
102 pass
103 if value[:1] == value[-1:] and value[0] in "\"'":
104 value = value[1:-1]
105 return str(value)
108def parse_converter_args(argstr: str) -> t.Tuple[t.Tuple, t.Dict[str, t.Any]]:
109 argstr += ","
110 args = []
111 kwargs = {}
113 for item in _converter_args_re.finditer(argstr):
114 value = item.group("stringval")
115 if value is None:
116 value = item.group("value")
117 value = _pythonize(value)
118 if not item.group("name"):
119 args.append(value)
120 else:
121 name = item.group("name")
122 kwargs[name] = value
124 return tuple(args), kwargs
127class RuleFactory:
128 """As soon as you have more complex URL setups it's a good idea to use rule
129 factories to avoid repetitive tasks. Some of them are builtin, others can
130 be added by subclassing `RuleFactory` and overriding `get_rules`.
131 """
133 def get_rules(self, map: "Map") -> t.Iterable["Rule"]:
134 """Subclasses of `RuleFactory` have to override this method and return
135 an iterable of rules."""
136 raise NotImplementedError()
139class Subdomain(RuleFactory):
140 """All URLs provided by this factory have the subdomain set to a
141 specific domain. For example if you want to use the subdomain for
142 the current language this can be a good setup::
144 url_map = Map([
145 Rule('/', endpoint='#select_language'),
146 Subdomain('<string(length=2):lang_code>', [
147 Rule('/', endpoint='index'),
148 Rule('/about', endpoint='about'),
149 Rule('/help', endpoint='help')
150 ])
151 ])
153 All the rules except for the ``'#select_language'`` endpoint will now
154 listen on a two letter long subdomain that holds the language code
155 for the current request.
156 """
158 def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None:
159 self.subdomain = subdomain
160 self.rules = rules
162 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
163 for rulefactory in self.rules:
164 for rule in rulefactory.get_rules(map):
165 rule = rule.empty()
166 rule.subdomain = self.subdomain
167 yield rule
170class Submount(RuleFactory):
171 """Like `Subdomain` but prefixes the URL rule with a given string::
173 url_map = Map([
174 Rule('/', endpoint='index'),
175 Submount('/blog', [
176 Rule('/', endpoint='blog/index'),
177 Rule('/entry/<entry_slug>', endpoint='blog/show')
178 ])
179 ])
181 Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``.
182 """
184 def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None:
185 self.path = path.rstrip("/")
186 self.rules = rules
188 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
189 for rulefactory in self.rules:
190 for rule in rulefactory.get_rules(map):
191 rule = rule.empty()
192 rule.rule = self.path + rule.rule
193 yield rule
196class EndpointPrefix(RuleFactory):
197 """Prefixes all endpoints (which must be strings for this factory) with
198 another string. This can be useful for sub applications::
200 url_map = Map([
201 Rule('/', endpoint='index'),
202 EndpointPrefix('blog/', [Submount('/blog', [
203 Rule('/', endpoint='index'),
204 Rule('/entry/<entry_slug>', endpoint='show')
205 ])])
206 ])
207 """
209 def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None:
210 self.prefix = prefix
211 self.rules = rules
213 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
214 for rulefactory in self.rules:
215 for rule in rulefactory.get_rules(map):
216 rule = rule.empty()
217 rule.endpoint = self.prefix + rule.endpoint
218 yield rule
221class RuleTemplate:
222 """Returns copies of the rules wrapped and expands string templates in
223 the endpoint, rule, defaults or subdomain sections.
225 Here a small example for such a rule template::
227 from werkzeug.routing import Map, Rule, RuleTemplate
229 resource = RuleTemplate([
230 Rule('/$name/', endpoint='$name.list'),
231 Rule('/$name/<int:id>', endpoint='$name.show')
232 ])
234 url_map = Map([resource(name='user'), resource(name='page')])
236 When a rule template is called the keyword arguments are used to
237 replace the placeholders in all the string parameters.
238 """
240 def __init__(self, rules: t.Iterable["Rule"]) -> None:
241 self.rules = list(rules)
243 def __call__(self, *args: t.Any, **kwargs: t.Any) -> "RuleTemplateFactory":
244 return RuleTemplateFactory(self.rules, dict(*args, **kwargs))
247class RuleTemplateFactory(RuleFactory):
248 """A factory that fills in template variables into rules. Used by
249 `RuleTemplate` internally.
251 :internal:
252 """
254 def __init__(
255 self, rules: t.Iterable[RuleFactory], context: t.Dict[str, t.Any]
256 ) -> None:
257 self.rules = rules
258 self.context = context
260 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
261 for rulefactory in self.rules:
262 for rule in rulefactory.get_rules(map):
263 new_defaults = subdomain = None
264 if rule.defaults:
265 new_defaults = {}
266 for key, value in rule.defaults.items():
267 if isinstance(value, str):
268 value = Template(value).substitute(self.context)
269 new_defaults[key] = value
270 if rule.subdomain is not None:
271 subdomain = Template(rule.subdomain).substitute(self.context)
272 new_endpoint = rule.endpoint
273 if isinstance(new_endpoint, str):
274 new_endpoint = Template(new_endpoint).substitute(self.context)
275 yield Rule(
276 Template(rule.rule).substitute(self.context),
277 new_defaults,
278 subdomain,
279 rule.methods,
280 rule.build_only,
281 new_endpoint,
282 rule.strict_slashes,
283 )
286def _prefix_names(src: str) -> ast.stmt:
287 """ast parse and prefix names with `.` to avoid collision with user vars"""
288 tree = ast.parse(src).body[0]
289 if isinstance(tree, ast.Expr):
290 tree = tree.value # type: ignore
291 for node in ast.walk(tree):
292 if isinstance(node, ast.Name):
293 node.id = f".{node.id}"
294 return tree
297_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()"
298_IF_KWARGS_URL_ENCODE_CODE = """\
299if kwargs:
300 params = self._encode_query_vars(kwargs)
301 q = "?" if params else ""
302else:
303 q = params = ""
304"""
305_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE)
306_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params"))
309class Rule(RuleFactory):
310 """A Rule represents one URL pattern. There are some options for `Rule`
311 that change the way it behaves and are passed to the `Rule` constructor.
312 Note that besides the rule-string all arguments *must* be keyword arguments
313 in order to not break the application on Werkzeug upgrades.
315 `string`
316 Rule strings basically are just normal URL paths with placeholders in
317 the format ``<converter(arguments):name>`` where the converter and the
318 arguments are optional. If no converter is defined the `default`
319 converter is used which means `string` in the normal configuration.
321 URL rules that end with a slash are branch URLs, others are leaves.
322 If you have `strict_slashes` enabled (which is the default), all
323 branch URLs that are matched without a trailing slash will trigger a
324 redirect to the same URL with the missing slash appended.
326 The converters are defined on the `Map`.
328 `endpoint`
329 The endpoint for this rule. This can be anything. A reference to a
330 function, a string, a number etc. The preferred way is using a string
331 because the endpoint is used for URL generation.
333 `defaults`
334 An optional dict with defaults for other rules with the same endpoint.
335 This is a bit tricky but useful if you want to have unique URLs::
337 url_map = Map([
338 Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
339 Rule('/all/page/<int:page>', endpoint='all_entries')
340 ])
342 If a user now visits ``http://example.com/all/page/1`` they will be
343 redirected to ``http://example.com/all/``. If `redirect_defaults` is
344 disabled on the `Map` instance this will only affect the URL
345 generation.
347 `subdomain`
348 The subdomain rule string for this rule. If not specified the rule
349 only matches for the `default_subdomain` of the map. If the map is
350 not bound to a subdomain this feature is disabled.
352 Can be useful if you want to have user profiles on different subdomains
353 and all subdomains are forwarded to your application::
355 url_map = Map([
356 Rule('/', subdomain='<username>', endpoint='user/homepage'),
357 Rule('/stats', subdomain='<username>', endpoint='user/stats')
358 ])
360 `methods`
361 A sequence of http methods this rule applies to. If not specified, all
362 methods are allowed. For example this can be useful if you want different
363 endpoints for `POST` and `GET`. If methods are defined and the path
364 matches but the method matched against is not in this list or in the
365 list of another rule for that path the error raised is of the type
366 `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the
367 list of methods and `HEAD` is not, `HEAD` is added automatically.
369 `strict_slashes`
370 Override the `Map` setting for `strict_slashes` only for this rule. If
371 not specified the `Map` setting is used.
373 `merge_slashes`
374 Override :attr:`Map.merge_slashes` for this rule.
376 `build_only`
377 Set this to True and the rule will never match but will create a URL
378 that can be build. This is useful if you have resources on a subdomain
379 or folder that are not handled by the WSGI application (like static data)
381 `redirect_to`
382 If given this must be either a string or callable. In case of a
383 callable it's called with the url adapter that triggered the match and
384 the values of the URL as keyword arguments and has to return the target
385 for the redirect, otherwise it has to be a string with placeholders in
386 rule syntax::
388 def foo_with_slug(adapter, id):
389 # ask the database for the slug for the old id. this of
390 # course has nothing to do with werkzeug.
391 return f'foo/{Foo.get_slug_for_id(id)}'
393 url_map = Map([
394 Rule('/foo/<slug>', endpoint='foo'),
395 Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'),
396 Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug)
397 ])
399 When the rule is matched the routing system will raise a
400 `RequestRedirect` exception with the target for the redirect.
402 Keep in mind that the URL will be joined against the URL root of the
403 script so don't use a leading slash on the target URL unless you
404 really mean root of that domain.
406 `alias`
407 If enabled this rule serves as an alias for another rule with the same
408 endpoint and arguments.
410 `host`
411 If provided and the URL map has host matching enabled this can be
412 used to provide a match rule for the whole host. This also means
413 that the subdomain feature is disabled.
415 `websocket`
416 If ``True``, this rule is only matches for WebSocket (``ws://``,
417 ``wss://``) requests. By default, rules will only match for HTTP
418 requests.
420 .. versionchanged:: 2.1
421 Percent-encoded newlines (``%0a``), which are decoded by WSGI
422 servers, are considered when routing instead of terminating the
423 match early.
425 .. versionadded:: 1.0
426 Added ``websocket``.
428 .. versionadded:: 1.0
429 Added ``merge_slashes``.
431 .. versionadded:: 0.7
432 Added ``alias`` and ``host``.
434 .. versionchanged:: 0.6.1
435 ``HEAD`` is added to ``methods`` if ``GET`` is present.
436 """
438 def __init__(
439 self,
440 string: str,
441 defaults: t.Optional[t.Mapping[str, t.Any]] = None,
442 subdomain: t.Optional[str] = None,
443 methods: t.Optional[t.Iterable[str]] = None,
444 build_only: bool = False,
445 endpoint: t.Optional[str] = None,
446 strict_slashes: t.Optional[bool] = None,
447 merge_slashes: t.Optional[bool] = None,
448 redirect_to: t.Optional[t.Union[str, t.Callable[..., str]]] = None,
449 alias: bool = False,
450 host: t.Optional[str] = None,
451 websocket: bool = False,
452 ) -> None:
453 if not string.startswith("/"):
454 raise ValueError("urls must start with a leading slash")
455 self.rule = string
456 self.is_leaf = not string.endswith("/")
457 self.is_branch = string.endswith("/")
459 self.map: "Map" = None # type: ignore
460 self.strict_slashes = strict_slashes
461 self.merge_slashes = merge_slashes
462 self.subdomain = subdomain
463 self.host = host
464 self.defaults = defaults
465 self.build_only = build_only
466 self.alias = alias
467 self.websocket = websocket
469 if methods is not None:
470 if isinstance(methods, str):
471 raise TypeError("'methods' should be a list of strings.")
473 methods = {x.upper() for x in methods}
475 if "HEAD" not in methods and "GET" in methods:
476 methods.add("HEAD")
478 if websocket and methods - {"GET", "HEAD", "OPTIONS"}:
479 raise ValueError(
480 "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods."
481 )
483 self.methods = methods
484 self.endpoint: str = endpoint # type: ignore
485 self.redirect_to = redirect_to
487 if defaults:
488 self.arguments = set(map(str, defaults))
489 else:
490 self.arguments = set()
492 self._converters: t.Dict[str, "BaseConverter"] = {}
493 self._trace: t.List[t.Tuple[bool, str]] = []
494 self._parts: t.List[RulePart] = []
496 def empty(self) -> "Rule":
497 """
498 Return an unbound copy of this rule.
500 This can be useful if want to reuse an already bound URL for another
501 map. See ``get_empty_kwargs`` to override what keyword arguments are
502 provided to the new copy.
503 """
504 return type(self)(self.rule, **self.get_empty_kwargs())
506 def get_empty_kwargs(self) -> t.Mapping[str, t.Any]:
507 """
508 Provides kwargs for instantiating empty copy with empty()
510 Use this method to provide custom keyword arguments to the subclass of
511 ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass
512 has custom keyword arguments that are needed at instantiation.
514 Must return a ``dict`` that will be provided as kwargs to the new
515 instance of ``Rule``, following the initial ``self.rule`` value which
516 is always provided as the first, required positional argument.
517 """
518 defaults = None
519 if self.defaults:
520 defaults = dict(self.defaults)
521 return dict(
522 defaults=defaults,
523 subdomain=self.subdomain,
524 methods=self.methods,
525 build_only=self.build_only,
526 endpoint=self.endpoint,
527 strict_slashes=self.strict_slashes,
528 redirect_to=self.redirect_to,
529 alias=self.alias,
530 host=self.host,
531 )
533 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
534 yield self
536 def refresh(self) -> None:
537 """Rebinds and refreshes the URL. Call this if you modified the
538 rule in place.
540 :internal:
541 """
542 self.bind(self.map, rebind=True)
544 def bind(self, map: "Map", rebind: bool = False) -> None:
545 """Bind the url to a map and create a regular expression based on
546 the information from the rule itself and the defaults from the map.
548 :internal:
549 """
550 if self.map is not None and not rebind:
551 raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}")
552 self.map = map
553 if self.strict_slashes is None:
554 self.strict_slashes = map.strict_slashes
555 if self.merge_slashes is None:
556 self.merge_slashes = map.merge_slashes
557 if self.subdomain is None:
558 self.subdomain = map.default_subdomain
559 self.compile()
561 def get_converter(
562 self,
563 variable_name: str,
564 converter_name: str,
565 args: t.Tuple,
566 kwargs: t.Mapping[str, t.Any],
567 ) -> "BaseConverter":
568 """Looks up the converter for the given parameter.
570 .. versionadded:: 0.9
571 """
572 if converter_name not in self.map.converters:
573 raise LookupError(f"the converter {converter_name!r} does not exist")
574 return self.map.converters[converter_name](self.map, *args, **kwargs)
576 def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str:
577 return url_encode(
578 query_vars,
579 charset=self.map.charset,
580 sort=self.map.sort_parameters,
581 key=self.map.sort_key,
582 )
584 def _parse_rule(self, rule: str) -> t.Iterable[RulePart]:
585 content = ""
586 static = True
587 argument_weights = []
588 static_weights: t.List[t.Tuple[int, int]] = []
589 final = False
591 pos = 0
592 while pos < len(rule):
593 match = _part_re.match(rule, pos)
594 if match is None:
595 raise ValueError(f"malformed url rule: {rule!r}")
597 data = match.groupdict()
598 if data["static"] is not None:
599 static_weights.append((len(static_weights), -len(data["static"])))
600 self._trace.append((False, data["static"]))
601 content += data["static"] if static else re.escape(data["static"])
603 if data["variable"] is not None:
604 if static:
605 # Switching content to represent regex, hence the need to escape
606 content = re.escape(content)
607 static = False
608 c_args, c_kwargs = parse_converter_args(data["arguments"] or "")
609 convobj = self.get_converter(
610 data["variable"], data["converter"] or "default", c_args, c_kwargs
611 )
612 self._converters[data["variable"]] = convobj
613 self.arguments.add(data["variable"])
614 if not convobj.part_isolating:
615 final = True
616 content += f"({convobj.regex})"
617 argument_weights.append(convobj.weight)
618 self._trace.append((True, data["variable"]))
620 if data["slash"] is not None:
621 self._trace.append((False, "/"))
622 if final:
623 content += "/"
624 else:
625 if not static:
626 content += r"\Z"
627 weight = Weighting(
628 -len(static_weights),
629 static_weights,
630 -len(argument_weights),
631 argument_weights,
632 )
633 yield RulePart(
634 content=content, final=final, static=static, weight=weight
635 )
636 content = ""
637 static = True
638 argument_weights = []
639 static_weights = []
640 final = False
642 pos = match.end()
644 if not static:
645 content += r"\Z"
646 weight = Weighting(
647 -len(static_weights),
648 static_weights,
649 -len(argument_weights),
650 argument_weights,
651 )
652 yield RulePart(content=content, final=final, static=static, weight=weight)
654 def compile(self) -> None:
655 """Compiles the regular expression and stores it."""
656 assert self.map is not None, "rule not bound"
658 if self.map.host_matching:
659 domain_rule = self.host or ""
660 else:
661 domain_rule = self.subdomain or ""
662 self._parts = []
663 self._trace = []
664 self._converters = {}
665 if domain_rule == "":
666 self._parts = [
667 RulePart(
668 content="", final=False, static=True, weight=Weighting(0, [], 0, [])
669 )
670 ]
671 else:
672 self._parts.extend(self._parse_rule(domain_rule))
673 self._trace.append((False, "|"))
674 rule = self.rule
675 if self.merge_slashes:
676 rule = re.sub("/{2,}?", "/", self.rule)
677 self._parts.extend(self._parse_rule(rule))
679 self._build: t.Callable[..., t.Tuple[str, str]]
680 self._build = self._compile_builder(False).__get__(self, None)
681 self._build_unknown: t.Callable[..., t.Tuple[str, str]]
682 self._build_unknown = self._compile_builder(True).__get__(self, None)
684 @staticmethod
685 def _get_func_code(code: CodeType, name: str) -> t.Callable[..., t.Tuple[str, str]]:
686 globs: t.Dict[str, t.Any] = {}
687 locs: t.Dict[str, t.Any] = {}
688 exec(code, globs, locs)
689 return locs[name] # type: ignore
691 def _compile_builder(
692 self, append_unknown: bool = True
693 ) -> t.Callable[..., t.Tuple[str, str]]:
694 defaults = self.defaults or {}
695 dom_ops: t.List[t.Tuple[bool, str]] = []
696 url_ops: t.List[t.Tuple[bool, str]] = []
698 opl = dom_ops
699 for is_dynamic, data in self._trace:
700 if data == "|" and opl is dom_ops:
701 opl = url_ops
702 continue
703 # this seems like a silly case to ever come up but:
704 # if a default is given for a value that appears in the rule,
705 # resolve it to a constant ahead of time
706 if is_dynamic and data in defaults:
707 data = self._converters[data].to_url(defaults[data])
708 opl.append((False, data))
709 elif not is_dynamic:
710 opl.append(
711 (False, url_quote(_to_bytes(data, self.map.charset), safe="/:|+"))
712 )
713 else:
714 opl.append((True, data))
716 def _convert(elem: str) -> ast.stmt:
717 ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem))
718 ret.args = [ast.Name(str(elem), ast.Load())] # type: ignore # str for py2
719 return ret
721 def _parts(ops: t.List[t.Tuple[bool, str]]) -> t.List[ast.AST]:
722 parts = [
723 _convert(elem) if is_dynamic else ast.Str(s=elem)
724 for is_dynamic, elem in ops
725 ]
726 parts = parts or [ast.Str("")]
727 # constant fold
728 ret = [parts[0]]
729 for p in parts[1:]:
730 if isinstance(p, ast.Str) and isinstance(ret[-1], ast.Str):
731 ret[-1] = ast.Str(ret[-1].s + p.s)
732 else:
733 ret.append(p)
734 return ret
736 dom_parts = _parts(dom_ops)
737 url_parts = _parts(url_ops)
738 if not append_unknown:
739 body = []
740 else:
741 body = [_IF_KWARGS_URL_ENCODE_AST]
742 url_parts.extend(_URL_ENCODE_AST_NAMES)
744 def _join(parts: t.List[ast.AST]) -> ast.AST:
745 if len(parts) == 1: # shortcut
746 return parts[0]
747 return ast.JoinedStr(parts)
749 body.append(
750 ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load()))
751 )
753 pargs = [
754 elem
755 for is_dynamic, elem in dom_ops + url_ops
756 if is_dynamic and elem not in defaults
757 ]
758 kargs = [str(k) for k in defaults]
760 func_ast: ast.FunctionDef = _prefix_names("def _(): pass") # type: ignore
761 func_ast.name = f"<builder:{self.rule!r}>"
762 func_ast.args.args.append(ast.arg(".self", None))
763 for arg in pargs + kargs:
764 func_ast.args.args.append(ast.arg(arg, None))
765 func_ast.args.kwarg = ast.arg(".kwargs", None)
766 for _ in kargs:
767 func_ast.args.defaults.append(ast.Str(""))
768 func_ast.body = body
770 # use `ast.parse` instead of `ast.Module` for better portability
771 # Python 3.8 changes the signature of `ast.Module`
772 module = ast.parse("")
773 module.body = [func_ast]
775 # mark everything as on line 1, offset 0
776 # less error-prone than `ast.fix_missing_locations`
777 # bad line numbers cause an assert to fail in debug builds
778 for node in ast.walk(module):
779 if "lineno" in node._attributes:
780 node.lineno = 1
781 if "end_lineno" in node._attributes:
782 node.end_lineno = node.lineno # type: ignore[attr-defined]
783 if "col_offset" in node._attributes:
784 node.col_offset = 0
785 if "end_col_offset" in node._attributes:
786 node.end_col_offset = node.col_offset # type: ignore[attr-defined]
788 code = compile(module, "<werkzeug routing>", "exec")
789 return self._get_func_code(code, func_ast.name)
791 def build(
792 self, values: t.Mapping[str, t.Any], append_unknown: bool = True
793 ) -> t.Optional[t.Tuple[str, str]]:
794 """Assembles the relative url for that rule and the subdomain.
795 If building doesn't work for some reasons `None` is returned.
797 :internal:
798 """
799 try:
800 if append_unknown:
801 return self._build_unknown(**values)
802 else:
803 return self._build(**values)
804 except ValidationError:
805 return None
807 def provides_defaults_for(self, rule: "Rule") -> bool:
808 """Check if this rule has defaults for a given rule.
810 :internal:
811 """
812 return bool(
813 not self.build_only
814 and self.defaults
815 and self.endpoint == rule.endpoint
816 and self != rule
817 and self.arguments == rule.arguments
818 )
820 def suitable_for(
821 self, values: t.Mapping[str, t.Any], method: t.Optional[str] = None
822 ) -> bool:
823 """Check if the dict of values has enough data for url generation.
825 :internal:
826 """
827 # if a method was given explicitly and that method is not supported
828 # by this rule, this rule is not suitable.
829 if (
830 method is not None
831 and self.methods is not None
832 and method not in self.methods
833 ):
834 return False
836 defaults = self.defaults or ()
838 # all arguments required must be either in the defaults dict or
839 # the value dictionary otherwise it's not suitable
840 for key in self.arguments:
841 if key not in defaults and key not in values:
842 return False
844 # in case defaults are given we ensure that either the value was
845 # skipped or the value is the same as the default value.
846 if defaults:
847 for key, value in defaults.items():
848 if key in values and value != values[key]:
849 return False
851 return True
853 def build_compare_key(self) -> t.Tuple[int, int, int]:
854 """The build compare key for sorting.
856 :internal:
857 """
858 return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ()))
860 def __eq__(self, other: object) -> bool:
861 return isinstance(other, type(self)) and self._trace == other._trace
863 __hash__ = None # type: ignore
865 def __str__(self) -> str:
866 return self.rule
868 def __repr__(self) -> str:
869 if self.map is None:
870 return f"<{type(self).__name__} (unbound)>"
871 parts = []
872 for is_dynamic, data in self._trace:
873 if is_dynamic:
874 parts.append(f"<{data}>")
875 else:
876 parts.append(data)
877 parts = "".join(parts).lstrip("|")
878 methods = f" ({', '.join(self.methods)})" if self.methods is not None else ""
879 return f"<{type(self).__name__} {parts!r}{methods} -> {self.endpoint}>"