Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/routing.py: 43%
819 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
1"""When it comes to combining multiple controller or view functions
2(however you want to call them) you need a dispatcher. A simple way
3would be applying regular expression tests on the ``PATH_INFO`` and
4calling registered callback functions that return the value then.
6This module implements a much more powerful system than simple regular
7expression matching because it can also convert values in the URLs and
8build URLs.
10Here a simple example that creates a URL map for an application with
11two subdomains (www and kb) and some URL rules:
13.. code-block:: python
15 m = Map([
16 # Static URLs
17 Rule('/', endpoint='static/index'),
18 Rule('/about', endpoint='static/about'),
19 Rule('/help', endpoint='static/help'),
20 # Knowledge Base
21 Subdomain('kb', [
22 Rule('/', endpoint='kb/index'),
23 Rule('/browse/', endpoint='kb/browse'),
24 Rule('/browse/<int:id>/', endpoint='kb/browse'),
25 Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
26 ])
27 ], default_subdomain='www')
29If the application doesn't use subdomains it's perfectly fine to not set
30the default subdomain and not use the `Subdomain` rule factory. The
31endpoint in the rules can be anything, for example import paths or
32unique identifiers. The WSGI application can use those endpoints to get the
33handler for that URL. It doesn't have to be a string at all but it's
34recommended.
36Now it's possible to create a URL adapter for one of the subdomains and
37build URLs:
39.. code-block:: python
41 c = m.bind('example.com')
43 c.build("kb/browse", dict(id=42))
44 'http://kb.example.com/browse/42/'
46 c.build("kb/browse", dict())
47 'http://kb.example.com/browse/'
49 c.build("kb/browse", dict(id=42, page=3))
50 'http://kb.example.com/browse/42/3'
52 c.build("static/about")
53 '/about'
55 c.build("static/index", force_external=True)
56 'http://www.example.com/'
58 c = m.bind('example.com', subdomain='kb')
60 c.build("static/about")
61 'http://www.example.com/about'
63The first argument to bind is the server name *without* the subdomain.
64Per default it will assume that the script is mounted on the root, but
65often that's not the case so you can provide the real mount point as
66second argument:
68.. code-block:: python
70 c = m.bind('example.com', '/applications/example')
72The third argument can be the subdomain, if not given the default
73subdomain is used. For more details about binding have a look at the
74documentation of the `MapAdapter`.
76And here is how you can match URLs:
78.. code-block:: python
80 c = m.bind('example.com')
82 c.match("/")
83 ('static/index', {})
85 c.match("/about")
86 ('static/about', {})
88 c = m.bind('example.com', '/', 'kb')
90 c.match("/")
91 ('kb/index', {})
93 c.match("/browse/42/23")
94 ('kb/browse', {'id': 42, 'page': 23})
96If matching fails you get a ``NotFound`` exception, if the rule thinks
97it's a good idea to redirect (for example because the URL was defined
98to have a slash at the end but the request was missing that slash) it
99will raise a ``RequestRedirect`` exception. Both are subclasses of
100``HTTPException`` so you can use those errors as responses in the
101application.
103If matching succeeded but the URL rule was incompatible to the given
104method (for example there were only rules for ``GET`` and ``HEAD`` but
105routing tried to match a ``POST`` request) a ``MethodNotAllowed``
106exception is raised.
107"""
108import ast
109import difflib
110import posixpath
111import re
112import typing
113import typing as t
114import uuid
115import warnings
116from pprint import pformat
117from string import Template
118from threading import Lock
119from types import CodeType
121from ._internal import _encode_idna
122from ._internal import _get_environ
123from ._internal import _to_bytes
124from ._internal import _to_str
125from ._internal import _wsgi_decoding_dance
126from .datastructures import ImmutableDict
127from .datastructures import MultiDict
128from .exceptions import BadHost
129from .exceptions import BadRequest
130from .exceptions import HTTPException
131from .exceptions import MethodNotAllowed
132from .exceptions import NotFound
133from .urls import _fast_url_quote
134from .urls import url_encode
135from .urls import url_join
136from .urls import url_quote
137from .urls import url_unquote
138from .utils import cached_property
139from .utils import redirect
140from .wsgi import get_host
142if t.TYPE_CHECKING:
143 import typing_extensions as te
144 from _typeshed.wsgi import WSGIApplication
145 from _typeshed.wsgi import WSGIEnvironment
146 from .wrappers.request import Request
147 from .wrappers.response import Response
149_rule_re = re.compile(
150 r"""
151 (?P<static>[^<]*) # static rule data
152 <
153 (?:
154 (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name
155 (?:\((?P<args>.*?)\))? # converter arguments
156 \: # variable delimiter
157 )?
158 (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # variable name
159 >
160 """,
161 re.VERBOSE,
162)
163_simple_rule_re = re.compile(r"<([^>]+)>")
164_converter_args_re = re.compile(
165 r"""
166 ((?P<name>\w+)\s*=\s*)?
167 (?P<value>
168 True|False|
169 \d+.\d+|
170 \d+.|
171 \d+|
172 [\w\d_.]+|
173 [urUR]?(?P<stringval>"[^"]*?"|'[^']*')
174 )\s*,
175 """,
176 re.VERBOSE,
177)
180_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False}
183def _pythonize(value: str) -> t.Union[None, bool, int, float, str]:
184 if value in _PYTHON_CONSTANTS:
185 return _PYTHON_CONSTANTS[value]
186 for convert in int, float:
187 try:
188 return convert(value) # type: ignore
189 except ValueError:
190 pass
191 if value[:1] == value[-1:] and value[0] in "\"'":
192 value = value[1:-1]
193 return str(value)
196def parse_converter_args(argstr: str) -> t.Tuple[t.Tuple, t.Dict[str, t.Any]]:
197 argstr += ","
198 args = []
199 kwargs = {}
201 for item in _converter_args_re.finditer(argstr):
202 value = item.group("stringval")
203 if value is None:
204 value = item.group("value")
205 value = _pythonize(value)
206 if not item.group("name"):
207 args.append(value)
208 else:
209 name = item.group("name")
210 kwargs[name] = value
212 return tuple(args), kwargs
215def parse_rule(rule: str) -> t.Iterator[t.Tuple[t.Optional[str], t.Optional[str], str]]:
216 """Parse a rule and return it as generator. Each iteration yields tuples
217 in the form ``(converter, arguments, variable)``. If the converter is
218 `None` it's a static url part, otherwise it's a dynamic one.
220 :internal:
221 """
222 pos = 0
223 end = len(rule)
224 do_match = _rule_re.match
225 used_names = set()
226 while pos < end:
227 m = do_match(rule, pos)
228 if m is None:
229 break
230 data = m.groupdict()
231 if data["static"]:
232 yield None, None, data["static"]
233 variable = data["variable"]
234 converter = data["converter"] or "default"
235 if variable in used_names:
236 raise ValueError(f"variable name {variable!r} used twice.")
237 used_names.add(variable)
238 yield converter, data["args"] or None, variable
239 pos = m.end()
240 if pos < end:
241 remaining = rule[pos:]
242 if ">" in remaining or "<" in remaining:
243 raise ValueError(f"malformed url rule: {rule!r}")
244 yield None, None, remaining
247class RoutingException(Exception):
248 """Special exceptions that require the application to redirect, notifying
249 about missing urls, etc.
251 :internal:
252 """
255class RequestRedirect(HTTPException, RoutingException):
256 """Raise if the map requests a redirect. This is for example the case if
257 `strict_slashes` are activated and an url that requires a trailing slash.
259 The attribute `new_url` contains the absolute destination url.
260 """
262 code = 308
264 def __init__(self, new_url: str) -> None:
265 super().__init__(new_url)
266 self.new_url = new_url
268 def get_response(
269 self,
270 environ: t.Optional[t.Union["WSGIEnvironment", "Request"]] = None,
271 scope: t.Optional[dict] = None,
272 ) -> "Response":
273 return redirect(self.new_url, self.code)
276class RequestPath(RoutingException):
277 """Internal exception."""
279 __slots__ = ("path_info",)
281 def __init__(self, path_info: str) -> None:
282 super().__init__()
283 self.path_info = path_info
286class RequestAliasRedirect(RoutingException): # noqa: B903
287 """This rule is an alias and wants to redirect to the canonical URL."""
289 def __init__(self, matched_values: t.Mapping[str, t.Any]) -> None:
290 super().__init__()
291 self.matched_values = matched_values
294class BuildError(RoutingException, LookupError):
295 """Raised if the build system cannot find a URL for an endpoint with the
296 values provided.
297 """
299 def __init__(
300 self,
301 endpoint: str,
302 values: t.Mapping[str, t.Any],
303 method: t.Optional[str],
304 adapter: t.Optional["MapAdapter"] = None,
305 ) -> None:
306 super().__init__(endpoint, values, method)
307 self.endpoint = endpoint
308 self.values = values
309 self.method = method
310 self.adapter = adapter
312 @cached_property
313 def suggested(self) -> t.Optional["Rule"]:
314 return self.closest_rule(self.adapter)
316 def closest_rule(self, adapter: t.Optional["MapAdapter"]) -> t.Optional["Rule"]:
317 def _score_rule(rule: "Rule") -> float:
318 return sum(
319 [
320 0.98
321 * difflib.SequenceMatcher(
322 None, rule.endpoint, self.endpoint
323 ).ratio(),
324 0.01 * bool(set(self.values or ()).issubset(rule.arguments)),
325 0.01 * bool(rule.methods and self.method in rule.methods),
326 ]
327 )
329 if adapter and adapter.map._rules:
330 return max(adapter.map._rules, key=_score_rule)
332 return None
334 def __str__(self) -> str:
335 message = [f"Could not build url for endpoint {self.endpoint!r}"]
336 if self.method:
337 message.append(f" ({self.method!r})")
338 if self.values:
339 message.append(f" with values {sorted(self.values)!r}")
340 message.append(".")
341 if self.suggested:
342 if self.endpoint == self.suggested.endpoint:
343 if (
344 self.method
345 and self.suggested.methods is not None
346 and self.method not in self.suggested.methods
347 ):
348 message.append(
349 " Did you mean to use methods"
350 f" {sorted(self.suggested.methods)!r}?"
351 )
352 missing_values = self.suggested.arguments.union(
353 set(self.suggested.defaults or ())
354 ) - set(self.values.keys())
355 if missing_values:
356 message.append(
357 f" Did you forget to specify values {sorted(missing_values)!r}?"
358 )
359 else:
360 message.append(f" Did you mean {self.suggested.endpoint!r} instead?")
361 return "".join(message)
364class WebsocketMismatch(BadRequest):
365 """The only matched rule is either a WebSocket and the request is
366 HTTP, or the rule is HTTP and the request is a WebSocket.
367 """
370class ValidationError(ValueError):
371 """Validation error. If a rule converter raises this exception the rule
372 does not match the current URL and the next URL is tried.
373 """
376class RuleFactory:
377 """As soon as you have more complex URL setups it's a good idea to use rule
378 factories to avoid repetitive tasks. Some of them are builtin, others can
379 be added by subclassing `RuleFactory` and overriding `get_rules`.
380 """
382 def get_rules(self, map: "Map") -> t.Iterable["Rule"]:
383 """Subclasses of `RuleFactory` have to override this method and return
384 an iterable of rules."""
385 raise NotImplementedError()
388class Subdomain(RuleFactory):
389 """All URLs provided by this factory have the subdomain set to a
390 specific domain. For example if you want to use the subdomain for
391 the current language this can be a good setup::
393 url_map = Map([
394 Rule('/', endpoint='#select_language'),
395 Subdomain('<string(length=2):lang_code>', [
396 Rule('/', endpoint='index'),
397 Rule('/about', endpoint='about'),
398 Rule('/help', endpoint='help')
399 ])
400 ])
402 All the rules except for the ``'#select_language'`` endpoint will now
403 listen on a two letter long subdomain that holds the language code
404 for the current request.
405 """
407 def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None:
408 self.subdomain = subdomain
409 self.rules = rules
411 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
412 for rulefactory in self.rules:
413 for rule in rulefactory.get_rules(map):
414 rule = rule.empty()
415 rule.subdomain = self.subdomain
416 yield rule
419class Submount(RuleFactory):
420 """Like `Subdomain` but prefixes the URL rule with a given string::
422 url_map = Map([
423 Rule('/', endpoint='index'),
424 Submount('/blog', [
425 Rule('/', endpoint='blog/index'),
426 Rule('/entry/<entry_slug>', endpoint='blog/show')
427 ])
428 ])
430 Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``.
431 """
433 def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None:
434 self.path = path.rstrip("/")
435 self.rules = rules
437 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
438 for rulefactory in self.rules:
439 for rule in rulefactory.get_rules(map):
440 rule = rule.empty()
441 rule.rule = self.path + rule.rule
442 yield rule
445class EndpointPrefix(RuleFactory):
446 """Prefixes all endpoints (which must be strings for this factory) with
447 another string. This can be useful for sub applications::
449 url_map = Map([
450 Rule('/', endpoint='index'),
451 EndpointPrefix('blog/', [Submount('/blog', [
452 Rule('/', endpoint='index'),
453 Rule('/entry/<entry_slug>', endpoint='show')
454 ])])
455 ])
456 """
458 def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None:
459 self.prefix = prefix
460 self.rules = rules
462 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
463 for rulefactory in self.rules:
464 for rule in rulefactory.get_rules(map):
465 rule = rule.empty()
466 rule.endpoint = self.prefix + rule.endpoint
467 yield rule
470class RuleTemplate:
471 """Returns copies of the rules wrapped and expands string templates in
472 the endpoint, rule, defaults or subdomain sections.
474 Here a small example for such a rule template::
476 from werkzeug.routing import Map, Rule, RuleTemplate
478 resource = RuleTemplate([
479 Rule('/$name/', endpoint='$name.list'),
480 Rule('/$name/<int:id>', endpoint='$name.show')
481 ])
483 url_map = Map([resource(name='user'), resource(name='page')])
485 When a rule template is called the keyword arguments are used to
486 replace the placeholders in all the string parameters.
487 """
489 def __init__(self, rules: t.Iterable["Rule"]) -> None:
490 self.rules = list(rules)
492 def __call__(self, *args: t.Any, **kwargs: t.Any) -> "RuleTemplateFactory":
493 return RuleTemplateFactory(self.rules, dict(*args, **kwargs))
496class RuleTemplateFactory(RuleFactory):
497 """A factory that fills in template variables into rules. Used by
498 `RuleTemplate` internally.
500 :internal:
501 """
503 def __init__(
504 self, rules: t.Iterable[RuleFactory], context: t.Dict[str, t.Any]
505 ) -> None:
506 self.rules = rules
507 self.context = context
509 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
510 for rulefactory in self.rules:
511 for rule in rulefactory.get_rules(map):
512 new_defaults = subdomain = None
513 if rule.defaults:
514 new_defaults = {}
515 for key, value in rule.defaults.items():
516 if isinstance(value, str):
517 value = Template(value).substitute(self.context)
518 new_defaults[key] = value
519 if rule.subdomain is not None:
520 subdomain = Template(rule.subdomain).substitute(self.context)
521 new_endpoint = rule.endpoint
522 if isinstance(new_endpoint, str):
523 new_endpoint = Template(new_endpoint).substitute(self.context)
524 yield Rule(
525 Template(rule.rule).substitute(self.context),
526 new_defaults,
527 subdomain,
528 rule.methods,
529 rule.build_only,
530 new_endpoint,
531 rule.strict_slashes,
532 )
535def _prefix_names(src: str) -> ast.stmt:
536 """ast parse and prefix names with `.` to avoid collision with user vars"""
537 tree = ast.parse(src).body[0]
538 if isinstance(tree, ast.Expr):
539 tree = tree.value # type: ignore
540 for node in ast.walk(tree):
541 if isinstance(node, ast.Name):
542 node.id = f".{node.id}"
543 return tree
546_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()"
547_IF_KWARGS_URL_ENCODE_CODE = """\
548if kwargs:
549 params = self._encode_query_vars(kwargs)
550 q = "?" if params else ""
551else:
552 q = params = ""
553"""
554_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE)
555_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params"))
558class Rule(RuleFactory):
559 """A Rule represents one URL pattern. There are some options for `Rule`
560 that change the way it behaves and are passed to the `Rule` constructor.
561 Note that besides the rule-string all arguments *must* be keyword arguments
562 in order to not break the application on Werkzeug upgrades.
564 `string`
565 Rule strings basically are just normal URL paths with placeholders in
566 the format ``<converter(arguments):name>`` where the converter and the
567 arguments are optional. If no converter is defined the `default`
568 converter is used which means `string` in the normal configuration.
570 URL rules that end with a slash are branch URLs, others are leaves.
571 If you have `strict_slashes` enabled (which is the default), all
572 branch URLs that are matched without a trailing slash will trigger a
573 redirect to the same URL with the missing slash appended.
575 The converters are defined on the `Map`.
577 `endpoint`
578 The endpoint for this rule. This can be anything. A reference to a
579 function, a string, a number etc. The preferred way is using a string
580 because the endpoint is used for URL generation.
582 `defaults`
583 An optional dict with defaults for other rules with the same endpoint.
584 This is a bit tricky but useful if you want to have unique URLs::
586 url_map = Map([
587 Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
588 Rule('/all/page/<int:page>', endpoint='all_entries')
589 ])
591 If a user now visits ``http://example.com/all/page/1`` they will be
592 redirected to ``http://example.com/all/``. If `redirect_defaults` is
593 disabled on the `Map` instance this will only affect the URL
594 generation.
596 `subdomain`
597 The subdomain rule string for this rule. If not specified the rule
598 only matches for the `default_subdomain` of the map. If the map is
599 not bound to a subdomain this feature is disabled.
601 Can be useful if you want to have user profiles on different subdomains
602 and all subdomains are forwarded to your application::
604 url_map = Map([
605 Rule('/', subdomain='<username>', endpoint='user/homepage'),
606 Rule('/stats', subdomain='<username>', endpoint='user/stats')
607 ])
609 `methods`
610 A sequence of http methods this rule applies to. If not specified, all
611 methods are allowed. For example this can be useful if you want different
612 endpoints for `POST` and `GET`. If methods are defined and the path
613 matches but the method matched against is not in this list or in the
614 list of another rule for that path the error raised is of the type
615 `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the
616 list of methods and `HEAD` is not, `HEAD` is added automatically.
618 `strict_slashes`
619 Override the `Map` setting for `strict_slashes` only for this rule. If
620 not specified the `Map` setting is used.
622 `merge_slashes`
623 Override :attr:`Map.merge_slashes` for this rule.
625 `build_only`
626 Set this to True and the rule will never match but will create a URL
627 that can be build. This is useful if you have resources on a subdomain
628 or folder that are not handled by the WSGI application (like static data)
630 `redirect_to`
631 If given this must be either a string or callable. In case of a
632 callable it's called with the url adapter that triggered the match and
633 the values of the URL as keyword arguments and has to return the target
634 for the redirect, otherwise it has to be a string with placeholders in
635 rule syntax::
637 def foo_with_slug(adapter, id):
638 # ask the database for the slug for the old id. this of
639 # course has nothing to do with werkzeug.
640 return f'foo/{Foo.get_slug_for_id(id)}'
642 url_map = Map([
643 Rule('/foo/<slug>', endpoint='foo'),
644 Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'),
645 Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug)
646 ])
648 When the rule is matched the routing system will raise a
649 `RequestRedirect` exception with the target for the redirect.
651 Keep in mind that the URL will be joined against the URL root of the
652 script so don't use a leading slash on the target URL unless you
653 really mean root of that domain.
655 `alias`
656 If enabled this rule serves as an alias for another rule with the same
657 endpoint and arguments.
659 `host`
660 If provided and the URL map has host matching enabled this can be
661 used to provide a match rule for the whole host. This also means
662 that the subdomain feature is disabled.
664 `websocket`
665 If ``True``, this rule is only matches for WebSocket (``ws://``,
666 ``wss://``) requests. By default, rules will only match for HTTP
667 requests.
669 .. versionchanged:: 2.1
670 Percent-encoded newlines (``%0a``), which are decoded by WSGI
671 servers, are considered when routing instead of terminating the
672 match early.
674 .. versionadded:: 1.0
675 Added ``websocket``.
677 .. versionadded:: 1.0
678 Added ``merge_slashes``.
680 .. versionadded:: 0.7
681 Added ``alias`` and ``host``.
683 .. versionchanged:: 0.6.1
684 ``HEAD`` is added to ``methods`` if ``GET`` is present.
685 """
687 def __init__(
688 self,
689 string: str,
690 defaults: t.Optional[t.Mapping[str, t.Any]] = None,
691 subdomain: t.Optional[str] = None,
692 methods: t.Optional[t.Iterable[str]] = None,
693 build_only: bool = False,
694 endpoint: t.Optional[str] = None,
695 strict_slashes: t.Optional[bool] = None,
696 merge_slashes: t.Optional[bool] = None,
697 redirect_to: t.Optional[t.Union[str, t.Callable[..., str]]] = None,
698 alias: bool = False,
699 host: t.Optional[str] = None,
700 websocket: bool = False,
701 ) -> None:
702 if not string.startswith("/"):
703 raise ValueError("urls must start with a leading slash")
704 self.rule = string
705 self.is_leaf = not string.endswith("/")
707 self.map: "Map" = None # type: ignore
708 self.strict_slashes = strict_slashes
709 self.merge_slashes = merge_slashes
710 self.subdomain = subdomain
711 self.host = host
712 self.defaults = defaults
713 self.build_only = build_only
714 self.alias = alias
715 self.websocket = websocket
717 if methods is not None:
718 if isinstance(methods, str):
719 raise TypeError("'methods' should be a list of strings.")
721 methods = {x.upper() for x in methods}
723 if "HEAD" not in methods and "GET" in methods:
724 methods.add("HEAD")
726 if websocket and methods - {"GET", "HEAD", "OPTIONS"}:
727 raise ValueError(
728 "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods."
729 )
731 self.methods = methods
732 self.endpoint: str = endpoint # type: ignore
733 self.redirect_to = redirect_to
735 if defaults:
736 self.arguments = set(map(str, defaults))
737 else:
738 self.arguments = set()
740 self._trace: t.List[t.Tuple[bool, str]] = []
742 def empty(self) -> "Rule":
743 """
744 Return an unbound copy of this rule.
746 This can be useful if want to reuse an already bound URL for another
747 map. See ``get_empty_kwargs`` to override what keyword arguments are
748 provided to the new copy.
749 """
750 return type(self)(self.rule, **self.get_empty_kwargs())
752 def get_empty_kwargs(self) -> t.Mapping[str, t.Any]:
753 """
754 Provides kwargs for instantiating empty copy with empty()
756 Use this method to provide custom keyword arguments to the subclass of
757 ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass
758 has custom keyword arguments that are needed at instantiation.
760 Must return a ``dict`` that will be provided as kwargs to the new
761 instance of ``Rule``, following the initial ``self.rule`` value which
762 is always provided as the first, required positional argument.
763 """
764 defaults = None
765 if self.defaults:
766 defaults = dict(self.defaults)
767 return dict(
768 defaults=defaults,
769 subdomain=self.subdomain,
770 methods=self.methods,
771 build_only=self.build_only,
772 endpoint=self.endpoint,
773 strict_slashes=self.strict_slashes,
774 redirect_to=self.redirect_to,
775 alias=self.alias,
776 host=self.host,
777 )
779 def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
780 yield self
782 def refresh(self) -> None:
783 """Rebinds and refreshes the URL. Call this if you modified the
784 rule in place.
786 :internal:
787 """
788 self.bind(self.map, rebind=True)
790 def bind(self, map: "Map", rebind: bool = False) -> None:
791 """Bind the url to a map and create a regular expression based on
792 the information from the rule itself and the defaults from the map.
794 :internal:
795 """
796 if self.map is not None and not rebind:
797 raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}")
798 self.map = map
799 if self.strict_slashes is None:
800 self.strict_slashes = map.strict_slashes
801 if self.merge_slashes is None:
802 self.merge_slashes = map.merge_slashes
803 if self.subdomain is None:
804 self.subdomain = map.default_subdomain
805 self.compile()
807 def get_converter(
808 self,
809 variable_name: str,
810 converter_name: str,
811 args: t.Tuple,
812 kwargs: t.Mapping[str, t.Any],
813 ) -> "BaseConverter":
814 """Looks up the converter for the given parameter.
816 .. versionadded:: 0.9
817 """
818 if converter_name not in self.map.converters:
819 raise LookupError(f"the converter {converter_name!r} does not exist")
820 return self.map.converters[converter_name](self.map, *args, **kwargs)
822 def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str:
823 return url_encode(
824 query_vars,
825 charset=self.map.charset,
826 sort=self.map.sort_parameters,
827 key=self.map.sort_key,
828 )
830 def compile(self) -> None:
831 """Compiles the regular expression and stores it."""
832 assert self.map is not None, "rule not bound"
834 if self.map.host_matching:
835 domain_rule = self.host or ""
836 else:
837 domain_rule = self.subdomain or ""
839 self._trace = []
840 self._converters: t.Dict[str, "BaseConverter"] = {}
841 self._static_weights: t.List[t.Tuple[int, int]] = []
842 self._argument_weights: t.List[int] = []
843 regex_parts = []
845 def _build_regex(rule: str) -> None:
846 index = 0
847 for converter, arguments, variable in parse_rule(rule):
848 if converter is None:
849 for match in re.finditer(r"/+|[^/]+", variable):
850 part = match.group(0)
851 if part.startswith("/"):
852 if self.merge_slashes:
853 regex_parts.append(r"/+?")
854 self._trace.append((False, "/"))
855 else:
856 regex_parts.append(part)
857 self._trace.append((False, part))
858 continue
859 self._trace.append((False, part))
860 regex_parts.append(re.escape(part))
861 if part:
862 self._static_weights.append((index, -len(part)))
863 else:
864 if arguments:
865 c_args, c_kwargs = parse_converter_args(arguments)
866 else:
867 c_args = ()
868 c_kwargs = {}
869 convobj = self.get_converter(variable, converter, c_args, c_kwargs)
870 regex_parts.append(f"(?P<{variable}>{convobj.regex})")
871 self._converters[variable] = convobj
872 self._trace.append((True, variable))
873 self._argument_weights.append(convobj.weight)
874 self.arguments.add(str(variable))
875 index = index + 1
877 _build_regex(domain_rule)
878 regex_parts.append("\\|")
879 self._trace.append((False, "|"))
880 _build_regex(self.rule if self.is_leaf else self.rule.rstrip("/"))
881 if not self.is_leaf:
882 self._trace.append((False, "/"))
884 self._build: t.Callable[..., t.Tuple[str, str]]
885 self._build = self._compile_builder(False).__get__(self, None)
886 self._build_unknown: t.Callable[..., t.Tuple[str, str]]
887 self._build_unknown = self._compile_builder(True).__get__(self, None)
889 if self.build_only:
890 return
892 if not (self.is_leaf and self.strict_slashes):
893 reps = "*" if self.merge_slashes else "?"
894 tail = f"(?<!/)(?P<__suffix__>/{reps})"
895 else:
896 tail = ""
898 # Use \Z instead of $ to avoid matching before a %0a decoded to
899 # a \n by WSGI.
900 regex = rf"^{''.join(regex_parts)}{tail}$\Z"
901 self._regex = re.compile(regex)
903 def match(
904 self, path: str, method: t.Optional[str] = None
905 ) -> t.Optional[t.MutableMapping[str, t.Any]]:
906 """Check if the rule matches a given path. Path is a string in the
907 form ``"subdomain|/path"`` and is assembled by the map. If
908 the map is doing host matching the subdomain part will be the host
909 instead.
911 If the rule matches a dict with the converted values is returned,
912 otherwise the return value is `None`.
914 :internal:
915 """
916 if not self.build_only:
917 require_redirect = False
919 m = self._regex.search(path)
920 if m is not None:
921 groups = m.groupdict()
922 # we have a folder like part of the url without a trailing
923 # slash and strict slashes enabled. raise an exception that
924 # tells the map to redirect to the same url but with a
925 # trailing slash
926 if (
927 self.strict_slashes
928 and not self.is_leaf
929 and not groups.pop("__suffix__")
930 and (
931 method is None or self.methods is None or method in self.methods
932 )
933 ):
934 path += "/"
935 require_redirect = True
936 # if we are not in strict slashes mode we have to remove
937 # a __suffix__
938 elif not self.strict_slashes:
939 del groups["__suffix__"]
941 result = {}
942 for name, value in groups.items():
943 try:
944 value = self._converters[name].to_python(value)
945 except ValidationError:
946 return None
947 result[str(name)] = value
948 if self.defaults:
949 result.update(self.defaults)
951 if self.merge_slashes:
952 new_path = "|".join(self.build(result, False)) # type: ignore
953 if path.endswith("/") and not new_path.endswith("/"):
954 new_path += "/"
955 if new_path.count("/") < path.count("/"):
956 # The URL will be encoded when MapAdapter.match
957 # handles the RequestPath raised below. Decode
958 # the URL here to avoid a double encoding.
959 path = url_unquote(new_path)
960 require_redirect = True
962 if require_redirect:
963 path = path.split("|", 1)[1]
964 raise RequestPath(path)
966 if self.alias and self.map.redirect_defaults:
967 raise RequestAliasRedirect(result)
969 return result
971 return None
973 @staticmethod
974 def _get_func_code(code: CodeType, name: str) -> t.Callable[..., t.Tuple[str, str]]:
975 globs: t.Dict[str, t.Any] = {}
976 locs: t.Dict[str, t.Any] = {}
977 exec(code, globs, locs)
978 return locs[name] # type: ignore
980 def _compile_builder(
981 self, append_unknown: bool = True
982 ) -> t.Callable[..., t.Tuple[str, str]]:
983 defaults = self.defaults or {}
984 dom_ops: t.List[t.Tuple[bool, str]] = []
985 url_ops: t.List[t.Tuple[bool, str]] = []
987 opl = dom_ops
988 for is_dynamic, data in self._trace:
989 if data == "|" and opl is dom_ops:
990 opl = url_ops
991 continue
992 # this seems like a silly case to ever come up but:
993 # if a default is given for a value that appears in the rule,
994 # resolve it to a constant ahead of time
995 if is_dynamic and data in defaults:
996 data = self._converters[data].to_url(defaults[data])
997 opl.append((False, data))
998 elif not is_dynamic:
999 opl.append(
1000 (False, url_quote(_to_bytes(data, self.map.charset), safe="/:|+"))
1001 )
1002 else:
1003 opl.append((True, data))
1005 def _convert(elem: str) -> ast.stmt:
1006 ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem))
1007 ret.args = [ast.Name(str(elem), ast.Load())] # type: ignore # str for py2
1008 return ret
1010 def _parts(ops: t.List[t.Tuple[bool, str]]) -> t.List[ast.AST]:
1011 parts = [
1012 _convert(elem) if is_dynamic else ast.Str(s=elem)
1013 for is_dynamic, elem in ops
1014 ]
1015 parts = parts or [ast.Str("")]
1016 # constant fold
1017 ret = [parts[0]]
1018 for p in parts[1:]:
1019 if isinstance(p, ast.Str) and isinstance(ret[-1], ast.Str):
1020 ret[-1] = ast.Str(ret[-1].s + p.s)
1021 else:
1022 ret.append(p)
1023 return ret
1025 dom_parts = _parts(dom_ops)
1026 url_parts = _parts(url_ops)
1027 if not append_unknown:
1028 body = []
1029 else:
1030 body = [_IF_KWARGS_URL_ENCODE_AST]
1031 url_parts.extend(_URL_ENCODE_AST_NAMES)
1033 def _join(parts: t.List[ast.AST]) -> ast.AST:
1034 if len(parts) == 1: # shortcut
1035 return parts[0]
1036 return ast.JoinedStr(parts)
1038 body.append(
1039 ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load()))
1040 )
1042 pargs = [
1043 elem
1044 for is_dynamic, elem in dom_ops + url_ops
1045 if is_dynamic and elem not in defaults
1046 ]
1047 kargs = [str(k) for k in defaults]
1049 func_ast: ast.FunctionDef = _prefix_names("def _(): pass") # type: ignore
1050 func_ast.name = f"<builder:{self.rule!r}>"
1051 func_ast.args.args.append(ast.arg(".self", None))
1052 for arg in pargs + kargs:
1053 func_ast.args.args.append(ast.arg(arg, None))
1054 func_ast.args.kwarg = ast.arg(".kwargs", None)
1055 for _ in kargs:
1056 func_ast.args.defaults.append(ast.Str(""))
1057 func_ast.body = body
1059 # use `ast.parse` instead of `ast.Module` for better portability
1060 # Python 3.8 changes the signature of `ast.Module`
1061 module = ast.parse("")
1062 module.body = [func_ast]
1064 # mark everything as on line 1, offset 0
1065 # less error-prone than `ast.fix_missing_locations`
1066 # bad line numbers cause an assert to fail in debug builds
1067 for node in ast.walk(module):
1068 if "lineno" in node._attributes:
1069 node.lineno = 1
1070 if "col_offset" in node._attributes:
1071 node.col_offset = 0
1073 code = compile(module, "<werkzeug routing>", "exec")
1074 return self._get_func_code(code, func_ast.name)
1076 def build(
1077 self, values: t.Mapping[str, t.Any], append_unknown: bool = True
1078 ) -> t.Optional[t.Tuple[str, str]]:
1079 """Assembles the relative url for that rule and the subdomain.
1080 If building doesn't work for some reasons `None` is returned.
1082 :internal:
1083 """
1084 try:
1085 if append_unknown:
1086 return self._build_unknown(**values)
1087 else:
1088 return self._build(**values)
1089 except ValidationError:
1090 return None
1092 def provides_defaults_for(self, rule: "Rule") -> bool:
1093 """Check if this rule has defaults for a given rule.
1095 :internal:
1096 """
1097 return bool(
1098 not self.build_only
1099 and self.defaults
1100 and self.endpoint == rule.endpoint
1101 and self != rule
1102 and self.arguments == rule.arguments
1103 )
1105 def suitable_for(
1106 self, values: t.Mapping[str, t.Any], method: t.Optional[str] = None
1107 ) -> bool:
1108 """Check if the dict of values has enough data for url generation.
1110 :internal:
1111 """
1112 # if a method was given explicitly and that method is not supported
1113 # by this rule, this rule is not suitable.
1114 if (
1115 method is not None
1116 and self.methods is not None
1117 and method not in self.methods
1118 ):
1119 return False
1121 defaults = self.defaults or ()
1123 # all arguments required must be either in the defaults dict or
1124 # the value dictionary otherwise it's not suitable
1125 for key in self.arguments:
1126 if key not in defaults and key not in values:
1127 return False
1129 # in case defaults are given we ensure that either the value was
1130 # skipped or the value is the same as the default value.
1131 if defaults:
1132 for key, value in defaults.items():
1133 if key in values and value != values[key]:
1134 return False
1136 return True
1138 def match_compare_key(
1139 self,
1140 ) -> t.Tuple[bool, int, t.Iterable[t.Tuple[int, int]], int, t.Iterable[int]]:
1141 """The match compare key for sorting.
1143 Current implementation:
1145 1. rules without any arguments come first for performance
1146 reasons only as we expect them to match faster and some
1147 common ones usually don't have any arguments (index pages etc.)
1148 2. rules with more static parts come first so the second argument
1149 is the negative length of the number of the static weights.
1150 3. we order by static weights, which is a combination of index
1151 and length
1152 4. The more complex rules come first so the next argument is the
1153 negative length of the number of argument weights.
1154 5. lastly we order by the actual argument weights.
1156 :internal:
1157 """
1158 return (
1159 bool(self.arguments),
1160 -len(self._static_weights),
1161 self._static_weights,
1162 -len(self._argument_weights),
1163 self._argument_weights,
1164 )
1166 def build_compare_key(self) -> t.Tuple[int, int, int]:
1167 """The build compare key for sorting.
1169 :internal:
1170 """
1171 return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ()))
1173 def __eq__(self, other: object) -> bool:
1174 return isinstance(other, type(self)) and self._trace == other._trace
1176 __hash__ = None # type: ignore
1178 def __str__(self) -> str:
1179 return self.rule
1181 def __repr__(self) -> str:
1182 if self.map is None:
1183 return f"<{type(self).__name__} (unbound)>"
1184 parts = []
1185 for is_dynamic, data in self._trace:
1186 if is_dynamic:
1187 parts.append(f"<{data}>")
1188 else:
1189 parts.append(data)
1190 parts = "".join(parts).lstrip("|")
1191 methods = f" ({', '.join(self.methods)})" if self.methods is not None else ""
1192 return f"<{type(self).__name__} {parts!r}{methods} -> {self.endpoint}>"
1195class BaseConverter:
1196 """Base class for all converters."""
1198 regex = "[^/]+"
1199 weight = 100
1201 def __init__(self, map: "Map", *args: t.Any, **kwargs: t.Any) -> None:
1202 self.map = map
1204 def to_python(self, value: str) -> t.Any:
1205 return value
1207 def to_url(self, value: t.Any) -> str:
1208 if isinstance(value, (bytes, bytearray)):
1209 return _fast_url_quote(value)
1210 return _fast_url_quote(str(value).encode(self.map.charset))
1213class UnicodeConverter(BaseConverter):
1214 """This converter is the default converter and accepts any string but
1215 only one path segment. Thus the string can not include a slash.
1217 This is the default validator.
1219 Example::
1221 Rule('/pages/<page>'),
1222 Rule('/<string(length=2):lang_code>')
1224 :param map: the :class:`Map`.
1225 :param minlength: the minimum length of the string. Must be greater
1226 or equal 1.
1227 :param maxlength: the maximum length of the string.
1228 :param length: the exact length of the string.
1229 """
1231 def __init__(
1232 self,
1233 map: "Map",
1234 minlength: int = 1,
1235 maxlength: t.Optional[int] = None,
1236 length: t.Optional[int] = None,
1237 ) -> None:
1238 super().__init__(map)
1239 if length is not None:
1240 length_regex = f"{{{int(length)}}}"
1241 else:
1242 if maxlength is None:
1243 maxlength_value = ""
1244 else:
1245 maxlength_value = str(int(maxlength))
1246 length_regex = f"{{{int(minlength)},{maxlength_value}}}"
1247 self.regex = f"[^/]{length_regex}"
1250class AnyConverter(BaseConverter):
1251 """Matches one of the items provided. Items can either be Python
1252 identifiers or strings::
1254 Rule('/<any(about, help, imprint, class, "foo,bar"):page_name>')
1256 :param map: the :class:`Map`.
1257 :param items: this function accepts the possible items as positional
1258 arguments.
1259 """
1261 def __init__(self, map: "Map", *items: str) -> None:
1262 super().__init__(map)
1263 self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})"
1266class PathConverter(BaseConverter):
1267 """Like the default :class:`UnicodeConverter`, but it also matches
1268 slashes. This is useful for wikis and similar applications::
1270 Rule('/<path:wikipage>')
1271 Rule('/<path:wikipage>/edit')
1273 :param map: the :class:`Map`.
1274 """
1276 regex = "[^/].*?"
1277 weight = 200
1280class NumberConverter(BaseConverter):
1281 """Baseclass for `IntegerConverter` and `FloatConverter`.
1283 :internal:
1284 """
1286 weight = 50
1287 num_convert: t.Callable = int
1289 def __init__(
1290 self,
1291 map: "Map",
1292 fixed_digits: int = 0,
1293 min: t.Optional[int] = None,
1294 max: t.Optional[int] = None,
1295 signed: bool = False,
1296 ) -> None:
1297 if signed:
1298 self.regex = self.signed_regex
1299 super().__init__(map)
1300 self.fixed_digits = fixed_digits
1301 self.min = min
1302 self.max = max
1303 self.signed = signed
1305 def to_python(self, value: str) -> t.Any:
1306 if self.fixed_digits and len(value) != self.fixed_digits:
1307 raise ValidationError()
1308 value = self.num_convert(value)
1309 if (self.min is not None and value < self.min) or (
1310 self.max is not None and value > self.max
1311 ):
1312 raise ValidationError()
1313 return value
1315 def to_url(self, value: t.Any) -> str:
1316 value = str(self.num_convert(value))
1317 if self.fixed_digits:
1318 value = value.zfill(self.fixed_digits)
1319 return value
1321 @property
1322 def signed_regex(self) -> str:
1323 return f"-?{self.regex}"
1326class IntegerConverter(NumberConverter):
1327 """This converter only accepts integer values::
1329 Rule("/page/<int:page>")
1331 By default it only accepts unsigned, positive values. The ``signed``
1332 parameter will enable signed, negative values. ::
1334 Rule("/page/<int(signed=True):page>")
1336 :param map: The :class:`Map`.
1337 :param fixed_digits: The number of fixed digits in the URL. If you
1338 set this to ``4`` for example, the rule will only match if the
1339 URL looks like ``/0001/``. The default is variable length.
1340 :param min: The minimal value.
1341 :param max: The maximal value.
1342 :param signed: Allow signed (negative) values.
1344 .. versionadded:: 0.15
1345 The ``signed`` parameter.
1346 """
1348 regex = r"\d+"
1351class FloatConverter(NumberConverter):
1352 """This converter only accepts floating point values::
1354 Rule("/probability/<float:probability>")
1356 By default it only accepts unsigned, positive values. The ``signed``
1357 parameter will enable signed, negative values. ::
1359 Rule("/offset/<float(signed=True):offset>")
1361 :param map: The :class:`Map`.
1362 :param min: The minimal value.
1363 :param max: The maximal value.
1364 :param signed: Allow signed (negative) values.
1366 .. versionadded:: 0.15
1367 The ``signed`` parameter.
1368 """
1370 regex = r"\d+\.\d+"
1371 num_convert = float
1373 def __init__(
1374 self,
1375 map: "Map",
1376 min: t.Optional[float] = None,
1377 max: t.Optional[float] = None,
1378 signed: bool = False,
1379 ) -> None:
1380 super().__init__(map, min=min, max=max, signed=signed) # type: ignore
1383class UUIDConverter(BaseConverter):
1384 """This converter only accepts UUID strings::
1386 Rule('/object/<uuid:identifier>')
1388 .. versionadded:: 0.10
1390 :param map: the :class:`Map`.
1391 """
1393 regex = (
1394 r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-"
1395 r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"
1396 )
1398 def to_python(self, value: str) -> uuid.UUID:
1399 return uuid.UUID(value)
1401 def to_url(self, value: uuid.UUID) -> str:
1402 return str(value)
1405#: the default converter mapping for the map.
1406DEFAULT_CONVERTERS: t.Mapping[str, t.Type[BaseConverter]] = {
1407 "default": UnicodeConverter,
1408 "string": UnicodeConverter,
1409 "any": AnyConverter,
1410 "path": PathConverter,
1411 "int": IntegerConverter,
1412 "float": FloatConverter,
1413 "uuid": UUIDConverter,
1414}
1417class Map:
1418 """The map class stores all the URL rules and some configuration
1419 parameters. Some of the configuration values are only stored on the
1420 `Map` instance since those affect all rules, others are just defaults
1421 and can be overridden for each rule. Note that you have to specify all
1422 arguments besides the `rules` as keyword arguments!
1424 :param rules: sequence of url rules for this map.
1425 :param default_subdomain: The default subdomain for rules without a
1426 subdomain defined.
1427 :param charset: charset of the url. defaults to ``"utf-8"``
1428 :param strict_slashes: If a rule ends with a slash but the matched
1429 URL does not, redirect to the URL with a trailing slash.
1430 :param merge_slashes: Merge consecutive slashes when matching or
1431 building URLs. Matches will redirect to the normalized URL.
1432 Slashes in variable parts are not merged.
1433 :param redirect_defaults: This will redirect to the default rule if it
1434 wasn't visited that way. This helps creating
1435 unique URLs.
1436 :param converters: A dict of converters that adds additional converters
1437 to the list of converters. If you redefine one
1438 converter this will override the original one.
1439 :param sort_parameters: If set to `True` the url parameters are sorted.
1440 See `url_encode` for more details.
1441 :param sort_key: The sort key function for `url_encode`.
1442 :param encoding_errors: the error method to use for decoding
1443 :param host_matching: if set to `True` it enables the host matching
1444 feature and disables the subdomain one. If
1445 enabled the `host` parameter to rules is used
1446 instead of the `subdomain` one.
1448 .. versionchanged:: 1.0
1449 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules
1450 will match.
1452 .. versionchanged:: 1.0
1453 Added ``merge_slashes``.
1455 .. versionchanged:: 0.7
1456 Added ``encoding_errors`` and ``host_matching``.
1458 .. versionchanged:: 0.5
1459 Added ``sort_parameters`` and ``sort_key``.
1460 """
1462 #: A dict of default converters to be used.
1463 default_converters = ImmutableDict(DEFAULT_CONVERTERS)
1465 #: The type of lock to use when updating.
1466 #:
1467 #: .. versionadded:: 1.0
1468 lock_class = Lock
1470 def __init__(
1471 self,
1472 rules: t.Optional[t.Iterable[RuleFactory]] = None,
1473 default_subdomain: str = "",
1474 charset: str = "utf-8",
1475 strict_slashes: bool = True,
1476 merge_slashes: bool = True,
1477 redirect_defaults: bool = True,
1478 converters: t.Optional[t.Mapping[str, t.Type[BaseConverter]]] = None,
1479 sort_parameters: bool = False,
1480 sort_key: t.Optional[t.Callable[[t.Any], t.Any]] = None,
1481 encoding_errors: str = "replace",
1482 host_matching: bool = False,
1483 ) -> None:
1484 self._rules: t.List[Rule] = []
1485 self._rules_by_endpoint: t.Dict[str, t.List[Rule]] = {}
1486 self._remap = True
1487 self._remap_lock = self.lock_class()
1489 self.default_subdomain = default_subdomain
1490 self.charset = charset
1491 self.encoding_errors = encoding_errors
1492 self.strict_slashes = strict_slashes
1493 self.merge_slashes = merge_slashes
1494 self.redirect_defaults = redirect_defaults
1495 self.host_matching = host_matching
1497 self.converters = self.default_converters.copy()
1498 if converters:
1499 self.converters.update(converters)
1501 self.sort_parameters = sort_parameters
1502 self.sort_key = sort_key
1504 for rulefactory in rules or ():
1505 self.add(rulefactory)
1507 def is_endpoint_expecting(self, endpoint: str, *arguments: str) -> bool:
1508 """Iterate over all rules and check if the endpoint expects
1509 the arguments provided. This is for example useful if you have
1510 some URLs that expect a language code and others that do not and
1511 you want to wrap the builder a bit so that the current language
1512 code is automatically added if not provided but endpoints expect
1513 it.
1515 :param endpoint: the endpoint to check.
1516 :param arguments: this function accepts one or more arguments
1517 as positional arguments. Each one of them is
1518 checked.
1519 """
1520 self.update()
1521 arguments = set(arguments)
1522 for rule in self._rules_by_endpoint[endpoint]:
1523 if arguments.issubset(rule.arguments):
1524 return True
1525 return False
1527 def iter_rules(self, endpoint: t.Optional[str] = None) -> t.Iterator[Rule]:
1528 """Iterate over all rules or the rules of an endpoint.
1530 :param endpoint: if provided only the rules for that endpoint
1531 are returned.
1532 :return: an iterator
1533 """
1534 self.update()
1535 if endpoint is not None:
1536 return iter(self._rules_by_endpoint[endpoint])
1537 return iter(self._rules)
1539 def add(self, rulefactory: RuleFactory) -> None:
1540 """Add a new rule or factory to the map and bind it. Requires that the
1541 rule is not bound to another map.
1543 :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
1544 """
1545 for rule in rulefactory.get_rules(self):
1546 rule.bind(self)
1547 self._rules.append(rule)
1548 self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
1549 self._remap = True
1551 def bind(
1552 self,
1553 server_name: str,
1554 script_name: t.Optional[str] = None,
1555 subdomain: t.Optional[str] = None,
1556 url_scheme: str = "http",
1557 default_method: str = "GET",
1558 path_info: t.Optional[str] = None,
1559 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
1560 ) -> "MapAdapter":
1561 """Return a new :class:`MapAdapter` with the details specified to the
1562 call. Note that `script_name` will default to ``'/'`` if not further
1563 specified or `None`. The `server_name` at least is a requirement
1564 because the HTTP RFC requires absolute URLs for redirects and so all
1565 redirect exceptions raised by Werkzeug will contain the full canonical
1566 URL.
1568 If no path_info is passed to :meth:`match` it will use the default path
1569 info passed to bind. While this doesn't really make sense for
1570 manual bind calls, it's useful if you bind a map to a WSGI
1571 environment which already contains the path info.
1573 `subdomain` will default to the `default_subdomain` for this map if
1574 no defined. If there is no `default_subdomain` you cannot use the
1575 subdomain feature.
1577 .. versionchanged:: 1.0
1578 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules
1579 will match.
1581 .. versionchanged:: 0.15
1582 ``path_info`` defaults to ``'/'`` if ``None``.
1584 .. versionchanged:: 0.8
1585 ``query_args`` can be a string.
1587 .. versionchanged:: 0.7
1588 Added ``query_args``.
1589 """
1590 server_name = server_name.lower()
1591 if self.host_matching:
1592 if subdomain is not None:
1593 raise RuntimeError("host matching enabled and a subdomain was provided")
1594 elif subdomain is None:
1595 subdomain = self.default_subdomain
1596 if script_name is None:
1597 script_name = "/"
1598 if path_info is None:
1599 path_info = "/"
1601 try:
1602 server_name = _encode_idna(server_name) # type: ignore
1603 except UnicodeError as e:
1604 raise BadHost() from e
1606 return MapAdapter(
1607 self,
1608 server_name,
1609 script_name,
1610 subdomain,
1611 url_scheme,
1612 path_info,
1613 default_method,
1614 query_args,
1615 )
1617 def bind_to_environ(
1618 self,
1619 environ: t.Union["WSGIEnvironment", "Request"],
1620 server_name: t.Optional[str] = None,
1621 subdomain: t.Optional[str] = None,
1622 ) -> "MapAdapter":
1623 """Like :meth:`bind` but you can pass it an WSGI environment and it
1624 will fetch the information from that dictionary. Note that because of
1625 limitations in the protocol there is no way to get the current
1626 subdomain and real `server_name` from the environment. If you don't
1627 provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or
1628 `HTTP_HOST` if provided) as used `server_name` with disabled subdomain
1629 feature.
1631 If `subdomain` is `None` but an environment and a server name is
1632 provided it will calculate the current subdomain automatically.
1633 Example: `server_name` is ``'example.com'`` and the `SERVER_NAME`
1634 in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated
1635 subdomain will be ``'staging.dev'``.
1637 If the object passed as environ has an environ attribute, the value of
1638 this attribute is used instead. This allows you to pass request
1639 objects. Additionally `PATH_INFO` added as a default of the
1640 :class:`MapAdapter` so that you don't have to pass the path info to
1641 the match method.
1643 .. versionchanged:: 1.0.0
1644 If the passed server name specifies port 443, it will match
1645 if the incoming scheme is ``https`` without a port.
1647 .. versionchanged:: 1.0.0
1648 A warning is shown when the passed server name does not
1649 match the incoming WSGI server name.
1651 .. versionchanged:: 0.8
1652 This will no longer raise a ValueError when an unexpected server
1653 name was passed.
1655 .. versionchanged:: 0.5
1656 previously this method accepted a bogus `calculate_subdomain`
1657 parameter that did not have any effect. It was removed because
1658 of that.
1660 :param environ: a WSGI environment.
1661 :param server_name: an optional server name hint (see above).
1662 :param subdomain: optionally the current subdomain (see above).
1663 """
1664 env = _get_environ(environ)
1665 wsgi_server_name = get_host(env).lower()
1666 scheme = env["wsgi.url_scheme"]
1667 upgrade = any(
1668 v.strip() == "upgrade"
1669 for v in env.get("HTTP_CONNECTION", "").lower().split(",")
1670 )
1672 if upgrade and env.get("HTTP_UPGRADE", "").lower() == "websocket":
1673 scheme = "wss" if scheme == "https" else "ws"
1675 if server_name is None:
1676 server_name = wsgi_server_name
1677 else:
1678 server_name = server_name.lower()
1680 # strip standard port to match get_host()
1681 if scheme in {"http", "ws"} and server_name.endswith(":80"):
1682 server_name = server_name[:-3]
1683 elif scheme in {"https", "wss"} and server_name.endswith(":443"):
1684 server_name = server_name[:-4]
1686 if subdomain is None and not self.host_matching:
1687 cur_server_name = wsgi_server_name.split(".")
1688 real_server_name = server_name.split(".")
1689 offset = -len(real_server_name)
1691 if cur_server_name[offset:] != real_server_name:
1692 # This can happen even with valid configs if the server was
1693 # accessed directly by IP address under some situations.
1694 # Instead of raising an exception like in Werkzeug 0.7 or
1695 # earlier we go by an invalid subdomain which will result
1696 # in a 404 error on matching.
1697 warnings.warn(
1698 f"Current server name {wsgi_server_name!r} doesn't match configured"
1699 f" server name {server_name!r}",
1700 stacklevel=2,
1701 )
1702 subdomain = "<invalid>"
1703 else:
1704 subdomain = ".".join(filter(None, cur_server_name[:offset]))
1706 def _get_wsgi_string(name: str) -> t.Optional[str]:
1707 val = env.get(name)
1708 if val is not None:
1709 return _wsgi_decoding_dance(val, self.charset)
1710 return None
1712 script_name = _get_wsgi_string("SCRIPT_NAME")
1713 path_info = _get_wsgi_string("PATH_INFO")
1714 query_args = _get_wsgi_string("QUERY_STRING")
1715 return Map.bind(
1716 self,
1717 server_name,
1718 script_name,
1719 subdomain,
1720 scheme,
1721 env["REQUEST_METHOD"],
1722 path_info,
1723 query_args=query_args,
1724 )
1726 def update(self) -> None:
1727 """Called before matching and building to keep the compiled rules
1728 in the correct order after things changed.
1729 """
1730 if not self._remap:
1731 return
1733 with self._remap_lock:
1734 if not self._remap:
1735 return
1737 self._rules.sort(key=lambda x: x.match_compare_key())
1738 for rules in self._rules_by_endpoint.values():
1739 rules.sort(key=lambda x: x.build_compare_key())
1740 self._remap = False
1742 def __repr__(self) -> str:
1743 rules = self.iter_rules()
1744 return f"{type(self).__name__}({pformat(list(rules))})"
1747class MapAdapter:
1749 """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does
1750 the URL matching and building based on runtime information.
1751 """
1753 def __init__(
1754 self,
1755 map: Map,
1756 server_name: str,
1757 script_name: str,
1758 subdomain: t.Optional[str],
1759 url_scheme: str,
1760 path_info: str,
1761 default_method: str,
1762 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
1763 ):
1764 self.map = map
1765 self.server_name = _to_str(server_name)
1766 script_name = _to_str(script_name)
1767 if not script_name.endswith("/"):
1768 script_name += "/"
1769 self.script_name = script_name
1770 self.subdomain = _to_str(subdomain)
1771 self.url_scheme = _to_str(url_scheme)
1772 self.path_info = _to_str(path_info)
1773 self.default_method = _to_str(default_method)
1774 self.query_args = query_args
1775 self.websocket = self.url_scheme in {"ws", "wss"}
1777 def dispatch(
1778 self,
1779 view_func: t.Callable[[str, t.Mapping[str, t.Any]], "WSGIApplication"],
1780 path_info: t.Optional[str] = None,
1781 method: t.Optional[str] = None,
1782 catch_http_exceptions: bool = False,
1783 ) -> "WSGIApplication":
1784 """Does the complete dispatching process. `view_func` is called with
1785 the endpoint and a dict with the values for the view. It should
1786 look up the view function, call it, and return a response object
1787 or WSGI application. http exceptions are not caught by default
1788 so that applications can display nicer error messages by just
1789 catching them by hand. If you want to stick with the default
1790 error messages you can pass it ``catch_http_exceptions=True`` and
1791 it will catch the http exceptions.
1793 Here a small example for the dispatch usage::
1795 from werkzeug.wrappers import Request, Response
1796 from werkzeug.wsgi import responder
1797 from werkzeug.routing import Map, Rule
1799 def on_index(request):
1800 return Response('Hello from the index')
1802 url_map = Map([Rule('/', endpoint='index')])
1803 views = {'index': on_index}
1805 @responder
1806 def application(environ, start_response):
1807 request = Request(environ)
1808 urls = url_map.bind_to_environ(environ)
1809 return urls.dispatch(lambda e, v: views[e](request, **v),
1810 catch_http_exceptions=True)
1812 Keep in mind that this method might return exception objects, too, so
1813 use :class:`Response.force_type` to get a response object.
1815 :param view_func: a function that is called with the endpoint as
1816 first argument and the value dict as second. Has
1817 to dispatch to the actual view function with this
1818 information. (see above)
1819 :param path_info: the path info to use for matching. Overrides the
1820 path info specified on binding.
1821 :param method: the HTTP method used for matching. Overrides the
1822 method specified on binding.
1823 :param catch_http_exceptions: set to `True` to catch any of the
1824 werkzeug :class:`HTTPException`\\s.
1825 """
1826 try:
1827 try:
1828 endpoint, args = self.match(path_info, method)
1829 except RequestRedirect as e:
1830 return e
1831 return view_func(endpoint, args)
1832 except HTTPException as e:
1833 if catch_http_exceptions:
1834 return e
1835 raise
1837 @typing.overload
1838 def match( # type: ignore
1839 self,
1840 path_info: t.Optional[str] = None,
1841 method: t.Optional[str] = None,
1842 return_rule: "te.Literal[False]" = False,
1843 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
1844 websocket: t.Optional[bool] = None,
1845 ) -> t.Tuple[str, t.Mapping[str, t.Any]]:
1846 ...
1848 @typing.overload
1849 def match(
1850 self,
1851 path_info: t.Optional[str] = None,
1852 method: t.Optional[str] = None,
1853 return_rule: "te.Literal[True]" = True,
1854 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
1855 websocket: t.Optional[bool] = None,
1856 ) -> t.Tuple[Rule, t.Mapping[str, t.Any]]:
1857 ...
1859 def match(
1860 self,
1861 path_info: t.Optional[str] = None,
1862 method: t.Optional[str] = None,
1863 return_rule: bool = False,
1864 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
1865 websocket: t.Optional[bool] = None,
1866 ) -> t.Tuple[t.Union[str, Rule], t.Mapping[str, t.Any]]:
1867 """The usage is simple: you just pass the match method the current
1868 path info as well as the method (which defaults to `GET`). The
1869 following things can then happen:
1871 - you receive a `NotFound` exception that indicates that no URL is
1872 matching. A `NotFound` exception is also a WSGI application you
1873 can call to get a default page not found page (happens to be the
1874 same object as `werkzeug.exceptions.NotFound`)
1876 - you receive a `MethodNotAllowed` exception that indicates that there
1877 is a match for this URL but not for the current request method.
1878 This is useful for RESTful applications.
1880 - you receive a `RequestRedirect` exception with a `new_url`
1881 attribute. This exception is used to notify you about a request
1882 Werkzeug requests from your WSGI application. This is for example the
1883 case if you request ``/foo`` although the correct URL is ``/foo/``
1884 You can use the `RequestRedirect` instance as response-like object
1885 similar to all other subclasses of `HTTPException`.
1887 - you receive a ``WebsocketMismatch`` exception if the only
1888 match is a WebSocket rule but the bind is an HTTP request, or
1889 if the match is an HTTP rule but the bind is a WebSocket
1890 request.
1892 - you get a tuple in the form ``(endpoint, arguments)`` if there is
1893 a match (unless `return_rule` is True, in which case you get a tuple
1894 in the form ``(rule, arguments)``)
1896 If the path info is not passed to the match method the default path
1897 info of the map is used (defaults to the root URL if not defined
1898 explicitly).
1900 All of the exceptions raised are subclasses of `HTTPException` so they
1901 can be used as WSGI responses. They will all render generic error or
1902 redirect pages.
1904 Here is a small example for matching:
1906 >>> m = Map([
1907 ... Rule('/', endpoint='index'),
1908 ... Rule('/downloads/', endpoint='downloads/index'),
1909 ... Rule('/downloads/<int:id>', endpoint='downloads/show')
1910 ... ])
1911 >>> urls = m.bind("example.com", "/")
1912 >>> urls.match("/", "GET")
1913 ('index', {})
1914 >>> urls.match("/downloads/42")
1915 ('downloads/show', {'id': 42})
1917 And here is what happens on redirect and missing URLs:
1919 >>> urls.match("/downloads")
1920 Traceback (most recent call last):
1921 ...
1922 RequestRedirect: http://example.com/downloads/
1923 >>> urls.match("/missing")
1924 Traceback (most recent call last):
1925 ...
1926 NotFound: 404 Not Found
1928 :param path_info: the path info to use for matching. Overrides the
1929 path info specified on binding.
1930 :param method: the HTTP method used for matching. Overrides the
1931 method specified on binding.
1932 :param return_rule: return the rule that matched instead of just the
1933 endpoint (defaults to `False`).
1934 :param query_args: optional query arguments that are used for
1935 automatic redirects as string or dictionary. It's
1936 currently not possible to use the query arguments
1937 for URL matching.
1938 :param websocket: Match WebSocket instead of HTTP requests. A
1939 websocket request has a ``ws`` or ``wss``
1940 :attr:`url_scheme`. This overrides that detection.
1942 .. versionadded:: 1.0
1943 Added ``websocket``.
1945 .. versionchanged:: 0.8
1946 ``query_args`` can be a string.
1948 .. versionadded:: 0.7
1949 Added ``query_args``.
1951 .. versionadded:: 0.6
1952 Added ``return_rule``.
1953 """
1954 self.map.update()
1955 if path_info is None:
1956 path_info = self.path_info
1957 else:
1958 path_info = _to_str(path_info, self.map.charset)
1959 if query_args is None:
1960 query_args = self.query_args or {}
1961 method = (method or self.default_method).upper()
1963 if websocket is None:
1964 websocket = self.websocket
1966 require_redirect = False
1968 domain_part = self.server_name if self.map.host_matching else self.subdomain
1969 path_part = f"/{path_info.lstrip('/')}" if path_info else ""
1970 path = f"{domain_part}|{path_part}"
1972 have_match_for = set()
1973 websocket_mismatch = False
1975 for rule in self.map._rules:
1976 try:
1977 rv = rule.match(path, method)
1978 except RequestPath as e:
1979 raise RequestRedirect(
1980 self.make_redirect_url(
1981 url_quote(e.path_info, self.map.charset, safe="/:|+"),
1982 query_args,
1983 )
1984 ) from None
1985 except RequestAliasRedirect as e:
1986 raise RequestRedirect(
1987 self.make_alias_redirect_url(
1988 path, rule.endpoint, e.matched_values, method, query_args
1989 )
1990 ) from None
1991 if rv is None:
1992 continue
1993 if rule.methods is not None and method not in rule.methods:
1994 have_match_for.update(rule.methods)
1995 continue
1997 if rule.websocket != websocket:
1998 websocket_mismatch = True
1999 continue
2001 if self.map.redirect_defaults:
2002 redirect_url = self.get_default_redirect(rule, method, rv, query_args)
2003 if redirect_url is not None:
2004 raise RequestRedirect(redirect_url)
2006 if rule.redirect_to is not None:
2007 if isinstance(rule.redirect_to, str):
2009 def _handle_match(match: t.Match[str]) -> str:
2010 value = rv[match.group(1)] # type: ignore
2011 return rule._converters[match.group(1)].to_url(value)
2013 redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to)
2014 else:
2015 redirect_url = rule.redirect_to(self, **rv)
2017 if self.subdomain:
2018 netloc = f"{self.subdomain}.{self.server_name}"
2019 else:
2020 netloc = self.server_name
2022 raise RequestRedirect(
2023 url_join(
2024 f"{self.url_scheme or 'http'}://{netloc}{self.script_name}",
2025 redirect_url,
2026 )
2027 )
2029 if require_redirect:
2030 raise RequestRedirect(
2031 self.make_redirect_url(
2032 url_quote(path_info, self.map.charset, safe="/:|+"), query_args
2033 )
2034 )
2036 if return_rule:
2037 return rule, rv
2038 else:
2039 return rule.endpoint, rv
2041 if have_match_for:
2042 raise MethodNotAllowed(valid_methods=list(have_match_for))
2044 if websocket_mismatch:
2045 raise WebsocketMismatch()
2047 raise NotFound()
2049 def test(
2050 self, path_info: t.Optional[str] = None, method: t.Optional[str] = None
2051 ) -> bool:
2052 """Test if a rule would match. Works like `match` but returns `True`
2053 if the URL matches, or `False` if it does not exist.
2055 :param path_info: the path info to use for matching. Overrides the
2056 path info specified on binding.
2057 :param method: the HTTP method used for matching. Overrides the
2058 method specified on binding.
2059 """
2060 try:
2061 self.match(path_info, method)
2062 except RequestRedirect:
2063 pass
2064 except HTTPException:
2065 return False
2066 return True
2068 def allowed_methods(self, path_info: t.Optional[str] = None) -> t.Iterable[str]:
2069 """Returns the valid methods that match for a given path.
2071 .. versionadded:: 0.7
2072 """
2073 try:
2074 self.match(path_info, method="--")
2075 except MethodNotAllowed as e:
2076 return e.valid_methods # type: ignore
2077 except HTTPException:
2078 pass
2079 return []
2081 def get_host(self, domain_part: t.Optional[str]) -> str:
2082 """Figures out the full host name for the given domain part. The
2083 domain part is a subdomain in case host matching is disabled or
2084 a full host name.
2085 """
2086 if self.map.host_matching:
2087 if domain_part is None:
2088 return self.server_name
2089 return _to_str(domain_part, "ascii")
2090 subdomain = domain_part
2091 if subdomain is None:
2092 subdomain = self.subdomain
2093 else:
2094 subdomain = _to_str(subdomain, "ascii")
2096 if subdomain:
2097 return f"{subdomain}.{self.server_name}"
2098 else:
2099 return self.server_name
2101 def get_default_redirect(
2102 self,
2103 rule: Rule,
2104 method: str,
2105 values: t.MutableMapping[str, t.Any],
2106 query_args: t.Union[t.Mapping[str, t.Any], str],
2107 ) -> t.Optional[str]:
2108 """A helper that returns the URL to redirect to if it finds one.
2109 This is used for default redirecting only.
2111 :internal:
2112 """
2113 assert self.map.redirect_defaults
2114 for r in self.map._rules_by_endpoint[rule.endpoint]:
2115 # every rule that comes after this one, including ourself
2116 # has a lower priority for the defaults. We order the ones
2117 # with the highest priority up for building.
2118 if r is rule:
2119 break
2120 if r.provides_defaults_for(rule) and r.suitable_for(values, method):
2121 values.update(r.defaults) # type: ignore
2122 domain_part, path = r.build(values) # type: ignore
2123 return self.make_redirect_url(path, query_args, domain_part=domain_part)
2124 return None
2126 def encode_query_args(self, query_args: t.Union[t.Mapping[str, t.Any], str]) -> str:
2127 if not isinstance(query_args, str):
2128 return url_encode(query_args, self.map.charset)
2129 return query_args
2131 def make_redirect_url(
2132 self,
2133 path_info: str,
2134 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
2135 domain_part: t.Optional[str] = None,
2136 ) -> str:
2137 """Creates a redirect URL.
2139 :internal:
2140 """
2141 if query_args:
2142 suffix = f"?{self.encode_query_args(query_args)}"
2143 else:
2144 suffix = ""
2146 scheme = self.url_scheme or "http"
2147 host = self.get_host(domain_part)
2148 path = posixpath.join(self.script_name.strip("/"), path_info.lstrip("/"))
2149 return f"{scheme}://{host}/{path}{suffix}"
2151 def make_alias_redirect_url(
2152 self,
2153 path: str,
2154 endpoint: str,
2155 values: t.Mapping[str, t.Any],
2156 method: str,
2157 query_args: t.Union[t.Mapping[str, t.Any], str],
2158 ) -> str:
2159 """Internally called to make an alias redirect URL."""
2160 url = self.build(
2161 endpoint, values, method, append_unknown=False, force_external=True
2162 )
2163 if query_args:
2164 url += f"?{self.encode_query_args(query_args)}"
2165 assert url != path, "detected invalid alias setting. No canonical URL found"
2166 return url
2168 def _partial_build(
2169 self,
2170 endpoint: str,
2171 values: t.Mapping[str, t.Any],
2172 method: t.Optional[str],
2173 append_unknown: bool,
2174 ) -> t.Optional[t.Tuple[str, str, bool]]:
2175 """Helper for :meth:`build`. Returns subdomain and path for the
2176 rule that accepts this endpoint, values and method.
2178 :internal:
2179 """
2180 # in case the method is none, try with the default method first
2181 if method is None:
2182 rv = self._partial_build(
2183 endpoint, values, self.default_method, append_unknown
2184 )
2185 if rv is not None:
2186 return rv
2188 # Default method did not match or a specific method is passed.
2189 # Check all for first match with matching host. If no matching
2190 # host is found, go with first result.
2191 first_match = None
2193 for rule in self.map._rules_by_endpoint.get(endpoint, ()):
2194 if rule.suitable_for(values, method):
2195 build_rv = rule.build(values, append_unknown)
2197 if build_rv is not None:
2198 rv = (build_rv[0], build_rv[1], rule.websocket)
2199 if self.map.host_matching:
2200 if rv[0] == self.server_name:
2201 return rv
2202 elif first_match is None:
2203 first_match = rv
2204 else:
2205 return rv
2207 return first_match
2209 def build(
2210 self,
2211 endpoint: str,
2212 values: t.Optional[t.Mapping[str, t.Any]] = None,
2213 method: t.Optional[str] = None,
2214 force_external: bool = False,
2215 append_unknown: bool = True,
2216 url_scheme: t.Optional[str] = None,
2217 ) -> str:
2218 """Building URLs works pretty much the other way round. Instead of
2219 `match` you call `build` and pass it the endpoint and a dict of
2220 arguments for the placeholders.
2222 The `build` function also accepts an argument called `force_external`
2223 which, if you set it to `True` will force external URLs. Per default
2224 external URLs (include the server name) will only be used if the
2225 target URL is on a different subdomain.
2227 >>> m = Map([
2228 ... Rule('/', endpoint='index'),
2229 ... Rule('/downloads/', endpoint='downloads/index'),
2230 ... Rule('/downloads/<int:id>', endpoint='downloads/show')
2231 ... ])
2232 >>> urls = m.bind("example.com", "/")
2233 >>> urls.build("index", {})
2234 '/'
2235 >>> urls.build("downloads/show", {'id': 42})
2236 '/downloads/42'
2237 >>> urls.build("downloads/show", {'id': 42}, force_external=True)
2238 'http://example.com/downloads/42'
2240 Because URLs cannot contain non ASCII data you will always get
2241 bytes back. Non ASCII characters are urlencoded with the
2242 charset defined on the map instance.
2244 Additional values are converted to strings and appended to the URL as
2245 URL querystring parameters:
2247 >>> urls.build("index", {'q': 'My Searchstring'})
2248 '/?q=My+Searchstring'
2250 When processing those additional values, lists are furthermore
2251 interpreted as multiple values (as per
2252 :py:class:`werkzeug.datastructures.MultiDict`):
2254 >>> urls.build("index", {'q': ['a', 'b', 'c']})
2255 '/?q=a&q=b&q=c'
2257 Passing a ``MultiDict`` will also add multiple values:
2259 >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b'))))
2260 '/?p=z&q=a&q=b'
2262 If a rule does not exist when building a `BuildError` exception is
2263 raised.
2265 The build method accepts an argument called `method` which allows you
2266 to specify the method you want to have an URL built for if you have
2267 different methods for the same endpoint specified.
2269 :param endpoint: the endpoint of the URL to build.
2270 :param values: the values for the URL to build. Unhandled values are
2271 appended to the URL as query parameters.
2272 :param method: the HTTP method for the rule if there are different
2273 URLs for different methods on the same endpoint.
2274 :param force_external: enforce full canonical external URLs. If the URL
2275 scheme is not provided, this will generate
2276 a protocol-relative URL.
2277 :param append_unknown: unknown parameters are appended to the generated
2278 URL as query string argument. Disable this
2279 if you want the builder to ignore those.
2280 :param url_scheme: Scheme to use in place of the bound
2281 :attr:`url_scheme`.
2283 .. versionchanged:: 2.0
2284 Added the ``url_scheme`` parameter.
2286 .. versionadded:: 0.6
2287 Added the ``append_unknown`` parameter.
2288 """
2289 self.map.update()
2291 if values:
2292 if isinstance(values, MultiDict):
2293 values = {
2294 k: (v[0] if len(v) == 1 else v)
2295 for k, v in dict.items(values)
2296 if len(v) != 0
2297 }
2298 else: # plain dict
2299 values = {k: v for k, v in values.items() if v is not None}
2300 else:
2301 values = {}
2303 rv = self._partial_build(endpoint, values, method, append_unknown)
2304 if rv is None:
2305 raise BuildError(endpoint, values, method, self)
2307 domain_part, path, websocket = rv
2308 host = self.get_host(domain_part)
2310 if url_scheme is None:
2311 url_scheme = self.url_scheme
2313 # Always build WebSocket routes with the scheme (browsers
2314 # require full URLs). If bound to a WebSocket, ensure that HTTP
2315 # routes are built with an HTTP scheme.
2316 secure = url_scheme in {"https", "wss"}
2318 if websocket:
2319 force_external = True
2320 url_scheme = "wss" if secure else "ws"
2321 elif url_scheme:
2322 url_scheme = "https" if secure else "http"
2324 # shortcut this.
2325 if not force_external and (
2326 (self.map.host_matching and host == self.server_name)
2327 or (not self.map.host_matching and domain_part == self.subdomain)
2328 ):
2329 return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}"
2331 scheme = f"{url_scheme}:" if url_scheme else ""
2332 return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}"