1 from __future__
import annotations
5 from pprint
import pformat
6 from threading
import Lock
7 from urllib
.parse
import quote
8 from urllib
.parse
import urljoin
9 from urllib
.parse
import urlunsplit
11 from .._internal
import _get_environ
12 from .._internal
import _wsgi_decoding_dance
13 from ..datastructures
import ImmutableDict
14 from ..datastructures
import MultiDict
15 from ..exceptions
import BadHost
16 from ..exceptions
import HTTPException
17 from ..exceptions
import MethodNotAllowed
18 from ..exceptions
import NotFound
19 from ..urls
import _urlencode
20 from ..wsgi
import get_host
21 from .converters
import DEFAULT_CONVERTERS
22 from .exceptions
import BuildError
23 from .exceptions
import NoMatch
24 from .exceptions
import RequestAliasRedirect
25 from .exceptions
import RequestPath
26 from .exceptions
import RequestRedirect
27 from .exceptions
import WebsocketMismatch
28 from .matcher
import StateMachineMatcher
29 from .rules
import _simple_rule_re
30 from .rules
import Rule
33 from _typeshed
.wsgi
import WSGIApplication
34 from _typeshed
.wsgi
import WSGIEnvironment
35 from .converters
import BaseConverter
36 from .rules
import RuleFactory
37 from ..wrappers
.request
import Request
41 """The map class stores all the URL rules and some configuration
42 parameters. Some of the configuration values are only stored on the
43 `Map` instance since those affect all rules, others are just defaults
44 and can be overridden for each rule. Note that you have to specify all
45 arguments besides the `rules` as keyword arguments!
47 :param rules: sequence of url rules for this map.
48 :param default_subdomain: The default subdomain for rules without a
50 :param strict_slashes: If a rule ends with a slash but the matched
51 URL does not, redirect to the URL with a trailing slash.
52 :param merge_slashes: Merge consecutive slashes when matching or
53 building URLs. Matches will redirect to the normalized URL.
54 Slashes in variable parts are not merged.
55 :param redirect_defaults: This will redirect to the default rule if it
56 wasn't visited that way. This helps creating
58 :param converters: A dict of converters that adds additional converters
59 to the list of converters. If you redefine one
60 converter this will override the original one.
61 :param sort_parameters: If set to `True` the url parameters are sorted.
62 See `url_encode` for more details.
63 :param sort_key: The sort key function for `url_encode`.
64 :param host_matching: if set to `True` it enables the host matching
65 feature and disables the subdomain one. If
66 enabled the `host` parameter to rules is used
67 instead of the `subdomain` one.
69 .. versionchanged:: 3.0
70 The ``charset`` and ``encoding_errors`` parameters were removed.
72 .. versionchanged:: 1.0
73 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules will match.
75 .. versionchanged:: 1.0
76 The ``merge_slashes`` parameter was added.
78 .. versionchanged:: 0.7
79 The ``encoding_errors`` and ``host_matching`` parameters were added.
81 .. versionchanged:: 0.5
82 The ``sort_parameters`` and ``sort_key`` paramters were added.
85 #: A dict of default converters to be used.
86 default_converters
= ImmutableDict(DEFAULT_CONVERTERS
)
88 #: The type of lock to use when updating.
90 #: .. versionadded:: 1.0
95 rules
: t
.Iterable
[RuleFactory
] |
None = None,
96 default_subdomain
: str = "",
97 strict_slashes
: bool = True,
98 merge_slashes
: bool = True,
99 redirect_defaults
: bool = True,
100 converters
: t
.Mapping
[str, type[BaseConverter
]] |
None = None,
101 sort_parameters
: bool = False,
102 sort_key
: t
.Callable
[[t
.Any
], t
.Any
] |
None = None,
103 host_matching
: bool = False,
105 self
._matcher
= StateMachineMatcher(merge_slashes
)
106 self
._rules
_by
_endpoint
: dict[str, list[Rule
]] = {}
108 self
._remap
_lock
= self
.lock_class()
110 self
.default_subdomain
= default_subdomain
111 self
.strict_slashes
= strict_slashes
112 self
.merge_slashes
= merge_slashes
113 self
.redirect_defaults
= redirect_defaults
114 self
.host_matching
= host_matching
116 self
.converters
= self
.default_converters
.copy()
118 self
.converters
.update(converters
)
120 self
.sort_parameters
= sort_parameters
121 self
.sort_key
= sort_key
123 for rulefactory
in rules
or ():
124 self
.add(rulefactory
)
126 def is_endpoint_expecting(self
, endpoint
: str, *arguments
: str) -> bool:
127 """Iterate over all rules and check if the endpoint expects
128 the arguments provided. This is for example useful if you have
129 some URLs that expect a language code and others that do not and
130 you want to wrap the builder a bit so that the current language
131 code is automatically added if not provided but endpoints expect
134 :param endpoint: the endpoint to check.
135 :param arguments: this function accepts one or more arguments
136 as positional arguments. Each one of them is
140 arguments
= set(arguments
)
141 for rule
in self
._rules
_by
_endpoint
[endpoint
]:
142 if arguments
.issubset(rule
.arguments
):
147 def _rules(self
) -> list[Rule
]:
148 return [rule
for rules
in self
._rules
_by
_endpoint
.values() for rule
in rules
]
150 def iter_rules(self
, endpoint
: str |
None = None) -> t
.Iterator
[Rule
]:
151 """Iterate over all rules or the rules of an endpoint.
153 :param endpoint: if provided only the rules for that endpoint
158 if endpoint
is not None:
159 return iter(self
._rules
_by
_endpoint
[endpoint
])
160 return iter(self
._rules
)
162 def add(self
, rulefactory
: RuleFactory
) -> None:
163 """Add a new rule or factory to the map and bind it. Requires that the
164 rule is not bound to another map.
166 :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
168 for rule
in rulefactory
.get_rules(self
):
170 if not rule
.build_only
:
171 self
._matcher
.add(rule
)
172 self
._rules
_by
_endpoint
.setdefault(rule
.endpoint
, []).append(rule
)
178 script_name
: str |
None = None,
179 subdomain
: str |
None = None,
180 url_scheme
: str = "http",
181 default_method
: str = "GET",
182 path_info
: str |
None = None,
183 query_args
: t
.Mapping
[str, t
.Any
] |
str |
None = None,
185 """Return a new :class:`MapAdapter` with the details specified to the
186 call. Note that `script_name` will default to ``'/'`` if not further
187 specified or `None`. The `server_name` at least is a requirement
188 because the HTTP RFC requires absolute URLs for redirects and so all
189 redirect exceptions raised by Werkzeug will contain the full canonical
192 If no path_info is passed to :meth:`match` it will use the default path
193 info passed to bind. While this doesn't really make sense for
194 manual bind calls, it's useful if you bind a map to a WSGI
195 environment which already contains the path info.
197 `subdomain` will default to the `default_subdomain` for this map if
198 no defined. If there is no `default_subdomain` you cannot use the
201 .. versionchanged:: 1.0
202 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules
205 .. versionchanged:: 0.15
206 ``path_info`` defaults to ``'/'`` if ``None``.
208 .. versionchanged:: 0.8
209 ``query_args`` can be a string.
211 .. versionchanged:: 0.7
212 Added ``query_args``.
214 server_name
= server_name
.lower()
215 if self
.host_matching
:
216 if subdomain
is not None:
217 raise RuntimeError("host matching enabled and a subdomain was provided")
218 elif subdomain
is None:
219 subdomain
= self
.default_subdomain
220 if script_name
is None:
222 if path_info
is None:
225 # Port isn't part of IDNA, and might push a name over the 63 octet limit.
226 server_name
, port_sep
, port
= server_name
.partition(":")
229 server_name
= server_name
.encode("idna").decode("ascii")
230 except UnicodeError as e
:
231 raise BadHost() from e
235 f
"{server_name}{port_sep}{port}",
246 environ
: WSGIEnvironment | Request
,
247 server_name
: str |
None = None,
248 subdomain
: str |
None = None,
250 """Like :meth:`bind` but you can pass it an WSGI environment and it
251 will fetch the information from that dictionary. Note that because of
252 limitations in the protocol there is no way to get the current
253 subdomain and real `server_name` from the environment. If you don't
254 provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or
255 `HTTP_HOST` if provided) as used `server_name` with disabled subdomain
258 If `subdomain` is `None` but an environment and a server name is
259 provided it will calculate the current subdomain automatically.
260 Example: `server_name` is ``'example.com'`` and the `SERVER_NAME`
261 in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated
262 subdomain will be ``'staging.dev'``.
264 If the object passed as environ has an environ attribute, the value of
265 this attribute is used instead. This allows you to pass request
266 objects. Additionally `PATH_INFO` added as a default of the
267 :class:`MapAdapter` so that you don't have to pass the path info to
270 .. versionchanged:: 1.0.0
271 If the passed server name specifies port 443, it will match
272 if the incoming scheme is ``https`` without a port.
274 .. versionchanged:: 1.0.0
275 A warning is shown when the passed server name does not
276 match the incoming WSGI server name.
278 .. versionchanged:: 0.8
279 This will no longer raise a ValueError when an unexpected server
282 .. versionchanged:: 0.5
283 previously this method accepted a bogus `calculate_subdomain`
284 parameter that did not have any effect. It was removed because
287 :param environ: a WSGI environment.
288 :param server_name: an optional server name hint (see above).
289 :param subdomain: optionally the current subdomain (see above).
291 env
= _get_environ(environ
)
292 wsgi_server_name
= get_host(env
).lower()
293 scheme
= env
["wsgi.url_scheme"]
295 v
.strip() == "upgrade"
296 for v
in env
.get("HTTP_CONNECTION", "").lower().split(",")
299 if upgrade
and env
.get("HTTP_UPGRADE", "").lower() == "websocket":
300 scheme
= "wss" if scheme
== "https" else "ws"
302 if server_name
is None:
303 server_name
= wsgi_server_name
305 server_name
= server_name
.lower()
307 # strip standard port to match get_host()
308 if scheme
in {"http", "ws"}
and server_name
.endswith(":80"):
309 server_name
= server_name
[:-3]
310 elif scheme
in {"https", "wss"}
and server_name
.endswith(":443"):
311 server_name
= server_name
[:-4]
313 if subdomain
is None and not self
.host_matching
:
314 cur_server_name
= wsgi_server_name
.split(".")
315 real_server_name
= server_name
.split(".")
316 offset
= -len(real_server_name
)
318 if cur_server_name
[offset
:] != real_server_name
:
319 # This can happen even with valid configs if the server was
320 # accessed directly by IP address under some situations.
321 # Instead of raising an exception like in Werkzeug 0.7 or
322 # earlier we go by an invalid subdomain which will result
323 # in a 404 error on matching.
325 f
"Current server name {wsgi_server_name!r} doesn't match configured"
326 f
" server name {server_name!r}",
329 subdomain
= "<invalid>"
331 subdomain
= ".".join(filter(None, cur_server_name
[:offset
]))
333 def _get_wsgi_string(name
: str) -> str |
None:
336 return _wsgi_decoding_dance(val
)
339 script_name
= _get_wsgi_string("SCRIPT_NAME")
340 path_info
= _get_wsgi_string("PATH_INFO")
341 query_args
= _get_wsgi_string("QUERY_STRING")
348 env
["REQUEST_METHOD"],
350 query_args
=query_args
,
353 def update(self
) -> None:
354 """Called before matching and building to keep the compiled rules
355 in the correct order after things changed.
360 with self
._remap
_lock
:
364 self
._matcher
.update()
365 for rules
in self
._rules
_by
_endpoint
.values():
366 rules
.sort(key
=lambda x
: x
.build_compare_key())
369 def __repr__(self
) -> str:
370 rules
= self
.iter_rules()
371 return f
"{type(self).__name__}({pformat(list(rules))})"
376 """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does
377 the URL matching and building based on runtime information.
385 subdomain
: str |
None,
389 query_args
: t
.Mapping
[str, t
.Any
] |
str |
None = None,
392 self
.server_name
= server_name
394 if not script_name
.endswith("/"):
397 self
.script_name
= script_name
398 self
.subdomain
= subdomain
399 self
.url_scheme
= url_scheme
400 self
.path_info
= path_info
401 self
.default_method
= default_method
402 self
.query_args
= query_args
403 self
.websocket
= self
.url_scheme
in {"ws", "wss"}
407 view_func
: t
.Callable
[[str, t
.Mapping
[str, t
.Any
]], WSGIApplication
],
408 path_info
: str |
None = None,
409 method
: str |
None = None,
410 catch_http_exceptions
: bool = False,
411 ) -> WSGIApplication
:
412 """Does the complete dispatching process. `view_func` is called with
413 the endpoint and a dict with the values for the view. It should
414 look up the view function, call it, and return a response object
415 or WSGI application. http exceptions are not caught by default
416 so that applications can display nicer error messages by just
417 catching them by hand. If you want to stick with the default
418 error messages you can pass it ``catch_http_exceptions=True`` and
419 it will catch the http exceptions.
421 Here a small example for the dispatch usage::
423 from werkzeug.wrappers import Request, Response
424 from werkzeug.wsgi import responder
425 from werkzeug.routing import Map, Rule
427 def on_index(request):
428 return Response('Hello from the index')
430 url_map = Map([Rule('/', endpoint='index')])
431 views = {'index': on_index}
434 def application(environ, start_response):
435 request = Request(environ)
436 urls = url_map.bind_to_environ(environ)
437 return urls.dispatch(lambda e, v: views[e](request, **v),
438 catch_http_exceptions=True)
440 Keep in mind that this method might return exception objects, too, so
441 use :class:`Response.force_type` to get a response object.
443 :param view_func: a function that is called with the endpoint as
444 first argument and the value dict as second. Has
445 to dispatch to the actual view function with this
446 information. (see above)
447 :param path_info: the path info to use for matching. Overrides the
448 path info specified on binding.
449 :param method: the HTTP method used for matching. Overrides the
450 method specified on binding.
451 :param catch_http_exceptions: set to `True` to catch any of the
452 werkzeug :class:`HTTPException`\\s.
456 endpoint
, args
= self
.match(path_info
, method
)
457 except RequestRedirect
as e
:
459 return view_func(endpoint
, args
)
460 except HTTPException
as e
:
461 if catch_http_exceptions
:
466 def match( # type: ignore
468 path_info
: str |
None = None,
469 method
: str |
None = None,
470 return_rule
: t
.Literal
[False] = False,
471 query_args
: t
.Mapping
[str, t
.Any
] |
str |
None = None,
472 websocket
: bool |
None = None,
473 ) -> tuple[str, t
.Mapping
[str, t
.Any
]]:
479 path_info
: str |
None = None,
480 method
: str |
None = None,
481 return_rule
: t
.Literal
[True] = True,
482 query_args
: t
.Mapping
[str, t
.Any
] |
str |
None = None,
483 websocket
: bool |
None = None,
484 ) -> tuple[Rule
, t
.Mapping
[str, t
.Any
]]:
489 path_info
: str |
None = None,
490 method
: str |
None = None,
491 return_rule
: bool = False,
492 query_args
: t
.Mapping
[str, t
.Any
] |
str |
None = None,
493 websocket
: bool |
None = None,
494 ) -> tuple[str | Rule
, t
.Mapping
[str, t
.Any
]]:
495 """The usage is simple: you just pass the match method the current
496 path info as well as the method (which defaults to `GET`). The
497 following things can then happen:
499 - you receive a `NotFound` exception that indicates that no URL is
500 matching. A `NotFound` exception is also a WSGI application you
501 can call to get a default page not found page (happens to be the
502 same object as `werkzeug.exceptions.NotFound`)
504 - you receive a `MethodNotAllowed` exception that indicates that there
505 is a match for this URL but not for the current request method.
506 This is useful for RESTful applications.
508 - you receive a `RequestRedirect` exception with a `new_url`
509 attribute. This exception is used to notify you about a request
510 Werkzeug requests from your WSGI application. This is for example the
511 case if you request ``/foo`` although the correct URL is ``/foo/``
512 You can use the `RequestRedirect` instance as response-like object
513 similar to all other subclasses of `HTTPException`.
515 - you receive a ``WebsocketMismatch`` exception if the only
516 match is a WebSocket rule but the bind is an HTTP request, or
517 if the match is an HTTP rule but the bind is a WebSocket
520 - you get a tuple in the form ``(endpoint, arguments)`` if there is
521 a match (unless `return_rule` is True, in which case you get a tuple
522 in the form ``(rule, arguments)``)
524 If the path info is not passed to the match method the default path
525 info of the map is used (defaults to the root URL if not defined
528 All of the exceptions raised are subclasses of `HTTPException` so they
529 can be used as WSGI responses. They will all render generic error or
532 Here is a small example for matching:
535 ... Rule('/', endpoint='index'),
536 ... Rule('/downloads/', endpoint='downloads/index'),
537 ... Rule('/downloads/<int:id>', endpoint='downloads/show')
539 >>> urls = m.bind("example.com", "/")
540 >>> urls.match("/", "GET")
542 >>> urls.match("/downloads/42")
543 ('downloads/show', {'id': 42})
545 And here is what happens on redirect and missing URLs:
547 >>> urls.match("/downloads")
548 Traceback (most recent call last):
550 RequestRedirect: http://example.com/downloads/
551 >>> urls.match("/missing")
552 Traceback (most recent call last):
554 NotFound: 404 Not Found
556 :param path_info: the path info to use for matching. Overrides the
557 path info specified on binding.
558 :param method: the HTTP method used for matching. Overrides the
559 method specified on binding.
560 :param return_rule: return the rule that matched instead of just the
561 endpoint (defaults to `False`).
562 :param query_args: optional query arguments that are used for
563 automatic redirects as string or dictionary. It's
564 currently not possible to use the query arguments
566 :param websocket: Match WebSocket instead of HTTP requests. A
567 websocket request has a ``ws`` or ``wss``
568 :attr:`url_scheme`. This overrides that detection.
570 .. versionadded:: 1.0
573 .. versionchanged:: 0.8
574 ``query_args`` can be a string.
576 .. versionadded:: 0.7
577 Added ``query_args``.
579 .. versionadded:: 0.6
580 Added ``return_rule``.
583 if path_info
is None:
584 path_info
= self
.path_info
585 if query_args
is None:
586 query_args
= self
.query_args
or {}
587 method
= (method
or self
.default_method
).upper()
589 if websocket
is None:
590 websocket
= self
.websocket
592 domain_part
= self
.server_name
594 if not self
.map.host_matching
and self
.subdomain
is not None:
595 domain_part
= self
.subdomain
597 path_part
= f
"/{path_info.lstrip('/')}" if path_info
else ""
600 result
= self
.map._matcher
.match(domain_part
, path_part
, method
, websocket
)
601 except RequestPath
as e
:
602 # safe = https://url.spec.whatwg.org/#url-path-segment-string
603 new_path
= quote(e
.path_info
, safe
="!$&'()*+,/:;=@")
604 raise RequestRedirect(
605 self
.make_redirect_url(new_path
, query_args
)
607 except RequestAliasRedirect
as e
:
608 raise RequestRedirect(
609 self
.make_alias_redirect_url(
610 f
"{domain_part}|{path_part}",
619 raise MethodNotAllowed(valid_methods
=list(e
.have_match_for
)) from None
621 if e
.websocket_mismatch
:
622 raise WebsocketMismatch() from None
624 raise NotFound() from None
628 if self
.map.redirect_defaults
:
629 redirect_url
= self
.get_default_redirect(rule
, method
, rv
, query_args
)
630 if redirect_url
is not None:
631 raise RequestRedirect(redirect_url
)
633 if rule
.redirect_to
is not None:
634 if isinstance(rule
.redirect_to
, str):
636 def _handle_match(match
: t
.Match
[str]) -> str:
637 value
= rv
[match
.group(1)]
638 return rule
._converters
[match
.group(1)].to_url(value
)
640 redirect_url
= _simple_rule_re
.sub(_handle_match
, rule
.redirect_to
)
642 redirect_url
= rule
.redirect_to(self
, **rv
)
645 netloc
= f
"{self.subdomain}.{self.server_name}"
647 netloc
= self
.server_name
649 raise RequestRedirect(
651 f
"{self.url_scheme or 'http'}://{netloc}{self.script_name}",
659 return rule
.endpoint
, rv
661 def test(self
, path_info
: str |
None = None, method
: str |
None = None) -> bool:
662 """Test if a rule would match. Works like `match` but returns `True`
663 if the URL matches, or `False` if it does not exist.
665 :param path_info: the path info to use for matching. Overrides the
666 path info specified on binding.
667 :param method: the HTTP method used for matching. Overrides the
668 method specified on binding.
671 self
.match(path_info
, method
)
672 except RequestRedirect
:
674 except HTTPException
:
678 def allowed_methods(self
, path_info
: str |
None = None) -> t
.Iterable
[str]:
679 """Returns the valid methods that match for a given path.
681 .. versionadded:: 0.7
684 self
.match(path_info
, method
="--")
685 except MethodNotAllowed
as e
:
686 return e
.valid_methods
# type: ignore
687 except HTTPException
:
691 def get_host(self
, domain_part
: str |
None) -> str:
692 """Figures out the full host name for the given domain part. The
693 domain part is a subdomain in case host matching is disabled or
696 if self
.map.host_matching
:
697 if domain_part
is None:
698 return self
.server_name
702 if domain_part
is None:
703 subdomain
= self
.subdomain
705 subdomain
= domain_part
708 return f
"{subdomain}.{self.server_name}"
710 return self
.server_name
712 def get_default_redirect(
716 values
: t
.MutableMapping
[str, t
.Any
],
717 query_args
: t
.Mapping
[str, t
.Any
] |
str,
719 """A helper that returns the URL to redirect to if it finds one.
720 This is used for default redirecting only.
724 assert self
.map.redirect_defaults
725 for r
in self
.map._rules
_by
_endpoint
[rule
.endpoint
]:
726 # every rule that comes after this one, including ourself
727 # has a lower priority for the defaults. We order the ones
728 # with the highest priority up for building.
731 if r
.provides_defaults_for(rule
) and r
.suitable_for(values
, method
):
732 values
.update(r
.defaults
) # type: ignore
733 domain_part
, path
= r
.build(values
) # type: ignore
734 return self
.make_redirect_url(path
, query_args
, domain_part
=domain_part
)
737 def encode_query_args(self
, query_args
: t
.Mapping
[str, t
.Any
] |
str) -> str:
738 if not isinstance(query_args
, str):
739 return _urlencode(query_args
)
742 def make_redirect_url(
745 query_args
: t
.Mapping
[str, t
.Any
] |
str |
None = None,
746 domain_part
: str |
None = None,
748 """Creates a redirect URL.
752 if query_args
is None:
753 query_args
= self
.query_args
756 query_str
= self
.encode_query_args(query_args
)
760 scheme
= self
.url_scheme
or "http"
761 host
= self
.get_host(domain_part
)
762 path
= "/".join((self
.script_name
.strip("/"), path_info
.lstrip("/")))
763 return urlunsplit((scheme
, host
, path
, query_str
, None))
765 def make_alias_redirect_url(
769 values
: t
.Mapping
[str, t
.Any
],
771 query_args
: t
.Mapping
[str, t
.Any
] |
str,
773 """Internally called to make an alias redirect URL."""
775 endpoint
, values
, method
, append_unknown
=False, force_external
=True
778 url
+= f
"?{self.encode_query_args(query_args)}"
779 assert url
!= path
, "detected invalid alias setting. No canonical URL found"
785 values
: t
.Mapping
[str, t
.Any
],
787 append_unknown
: bool,
788 ) -> tuple[str, str, bool] |
None:
789 """Helper for :meth:`build`. Returns subdomain and path for the
790 rule that accepts this endpoint, values and method.
794 # in case the method is none, try with the default method first
796 rv
= self
._partial
_build
(
797 endpoint
, values
, self
.default_method
, append_unknown
802 # Default method did not match or a specific method is passed.
803 # Check all for first match with matching host. If no matching
804 # host is found, go with first result.
807 for rule
in self
.map._rules
_by
_endpoint
.get(endpoint
, ()):
808 if rule
.suitable_for(values
, method
):
809 build_rv
= rule
.build(values
, append_unknown
)
811 if build_rv
is not None:
812 rv
= (build_rv
[0], build_rv
[1], rule
.websocket
)
813 if self
.map.host_matching
:
814 if rv
[0] == self
.server_name
:
816 elif first_match
is None:
826 values
: t
.Mapping
[str, t
.Any
] |
None = None,
827 method
: str |
None = None,
828 force_external
: bool = False,
829 append_unknown
: bool = True,
830 url_scheme
: str |
None = None,
832 """Building URLs works pretty much the other way round. Instead of
833 `match` you call `build` and pass it the endpoint and a dict of
834 arguments for the placeholders.
836 The `build` function also accepts an argument called `force_external`
837 which, if you set it to `True` will force external URLs. Per default
838 external URLs (include the server name) will only be used if the
839 target URL is on a different subdomain.
842 ... Rule('/', endpoint='index'),
843 ... Rule('/downloads/', endpoint='downloads/index'),
844 ... Rule('/downloads/<int:id>', endpoint='downloads/show')
846 >>> urls = m.bind("example.com", "/")
847 >>> urls.build("index", {})
849 >>> urls.build("downloads/show", {'id': 42})
851 >>> urls.build("downloads/show", {'id': 42}, force_external=True)
852 'http://example.com/downloads/42'
854 Because URLs cannot contain non ASCII data you will always get
855 bytes back. Non ASCII characters are urlencoded with the
856 charset defined on the map instance.
858 Additional values are converted to strings and appended to the URL as
859 URL querystring parameters:
861 >>> urls.build("index", {'q': 'My Searchstring'})
862 '/?q=My+Searchstring'
864 When processing those additional values, lists are furthermore
865 interpreted as multiple values (as per
866 :py:class:`werkzeug.datastructures.MultiDict`):
868 >>> urls.build("index", {'q': ['a', 'b', 'c']})
871 Passing a ``MultiDict`` will also add multiple values:
873 >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b'))))
876 If a rule does not exist when building a `BuildError` exception is
879 The build method accepts an argument called `method` which allows you
880 to specify the method you want to have an URL built for if you have
881 different methods for the same endpoint specified.
883 :param endpoint: the endpoint of the URL to build.
884 :param values: the values for the URL to build. Unhandled values are
885 appended to the URL as query parameters.
886 :param method: the HTTP method for the rule if there are different
887 URLs for different methods on the same endpoint.
888 :param force_external: enforce full canonical external URLs. If the URL
889 scheme is not provided, this will generate
890 a protocol-relative URL.
891 :param append_unknown: unknown parameters are appended to the generated
892 URL as query string argument. Disable this
893 if you want the builder to ignore those.
894 :param url_scheme: Scheme to use in place of the bound
897 .. versionchanged:: 2.0
898 Added the ``url_scheme`` parameter.
900 .. versionadded:: 0.6
901 Added the ``append_unknown`` parameter.
906 if isinstance(values
, MultiDict
):
908 k
: (v
[0] if len(v
) == 1 else v
)
909 for k
, v
in dict.items(values
)
913 values
= {k: v for k, v in values.items() if v is not None}
917 rv
= self
._partial
_build
(endpoint
, values
, method
, append_unknown
)
919 raise BuildError(endpoint
, values
, method
, self
)
921 domain_part
, path
, websocket
= rv
922 host
= self
.get_host(domain_part
)
924 if url_scheme
is None:
925 url_scheme
= self
.url_scheme
927 # Always build WebSocket routes with the scheme (browsers
928 # require full URLs). If bound to a WebSocket, ensure that HTTP
929 # routes are built with an HTTP scheme.
930 secure
= url_scheme
in {"https", "wss"}
933 force_external
= True
934 url_scheme
= "wss" if secure
else "ws"
936 url_scheme
= "https" if secure
else "http"
939 if not force_external
and (
940 (self
.map.host_matching
and host
== self
.server_name
)
941 or (not self
.map.host_matching
and domain_part
== self
.subdomain
)
943 return f
"{self.script_name.rstrip('/')}/{path.lstrip('/')}"
945 scheme
= f
"{url_scheme}:" if url_scheme
else ""
946 return f
"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}"