1 from __future__
import annotations
6 from ..exceptions
import BadRequest
7 from ..exceptions
import HTTPException
8 from ..utils
import cached_property
9 from ..utils
import redirect
12 from _typeshed
.wsgi
import WSGIEnvironment
13 from .map import MapAdapter
14 from .rules
import Rule
15 from ..wrappers
.request
import Request
16 from ..wrappers
.response
import Response
19 class RoutingException(Exception):
20 """Special exceptions that require the application to redirect, notifying
21 about missing urls, etc.
27 class RequestRedirect(HTTPException
, RoutingException
):
28 """Raise if the map requests a redirect. This is for example the case if
29 `strict_slashes` are activated and an url that requires a trailing slash.
31 The attribute `new_url` contains the absolute destination url.
36 def __init__(self
, new_url
: str) -> None:
37 super().__init
__(new_url
)
38 self
.new_url
= new_url
42 environ
: WSGIEnvironment | Request |
None = None,
43 scope
: dict |
None = None,
45 return redirect(self
.new_url
, self
.code
)
48 class RequestPath(RoutingException
):
49 """Internal exception."""
51 __slots__
= ("path_info",)
53 def __init__(self
, path_info
: str) -> None:
55 self
.path_info
= path_info
58 class RequestAliasRedirect(RoutingException
): # noqa: B903
59 """This rule is an alias and wants to redirect to the canonical URL."""
61 def __init__(self
, matched_values
: t
.Mapping
[str, t
.Any
], endpoint
: str) -> None:
63 self
.matched_values
= matched_values
64 self
.endpoint
= endpoint
67 class BuildError(RoutingException
, LookupError):
68 """Raised if the build system cannot find a URL for an endpoint with the
75 values
: t
.Mapping
[str, t
.Any
],
77 adapter
: MapAdapter |
None = None,
79 super().__init
__(endpoint
, values
, method
)
80 self
.endpoint
= endpoint
83 self
.adapter
= adapter
86 def suggested(self
) -> Rule |
None:
87 return self
.closest_rule(self
.adapter
)
89 def closest_rule(self
, adapter
: MapAdapter |
None) -> Rule |
None:
90 def _score_rule(rule
: Rule
) -> float:
94 * difflib
.SequenceMatcher(
95 None, rule
.endpoint
, self
.endpoint
97 0.01 * bool(set(self
.values
or ()).issubset(rule
.arguments
)),
98 0.01 * bool(rule
.methods
and self
.method
in rule
.methods
),
102 if adapter
and adapter
.map._rules
:
103 return max(adapter
.map._rules
, key
=_score_rule
)
107 def __str__(self
) -> str:
108 message
= [f
"Could not build url for endpoint {self.endpoint!r}"]
110 message
.append(f
" ({self.method!r})")
112 message
.append(f
" with values {sorted(self.values)!r}")
115 if self
.endpoint
== self
.suggested
.endpoint
:
118 and self
.suggested
.methods
is not None
119 and self
.method
not in self
.suggested
.methods
122 " Did you mean to use methods"
123 f
" {sorted(self.suggested.methods)!r}?"
125 missing_values
= self
.suggested
.arguments
.union(
126 set(self
.suggested
.defaults
or ())
127 ) - set(self
.values
.keys())
130 f
" Did you forget to specify values {sorted(missing_values)!r}?"
133 message
.append(f
" Did you mean {self.suggested.endpoint!r} instead?")
134 return "".join(message
)
137 class WebsocketMismatch(BadRequest
):
138 """The only matched rule is either a WebSocket and the request is
139 HTTP, or the rule is HTTP and the request is a WebSocket.
143 class NoMatch(Exception):
144 __slots__
= ("have_match_for", "websocket_mismatch")
146 def __init__(self
, have_match_for
: set[str], websocket_mismatch
: bool) -> None:
147 self
.have_match_for
= have_match_for
148 self
.websocket_mismatch
= websocket_mismatch