Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/routing/converters.py: 53%
89 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:12 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:12 +0000
1import re
2import typing as t
3import uuid
5from ..urls import _fast_url_quote
7if t.TYPE_CHECKING:
8 from .map import Map
11class ValidationError(ValueError):
12 """Validation error. If a rule converter raises this exception the rule
13 does not match the current URL and the next URL is tried.
14 """
17class BaseConverter:
18 """Base class for all converters."""
20 regex = "[^/]+"
21 weight = 100
22 part_isolating = True
24 def __init__(self, map: "Map", *args: t.Any, **kwargs: t.Any) -> None:
25 self.map = map
27 def to_python(self, value: str) -> t.Any:
28 return value
30 def to_url(self, value: t.Any) -> str:
31 if isinstance(value, (bytes, bytearray)):
32 return _fast_url_quote(value)
33 return _fast_url_quote(str(value).encode(self.map.charset))
36class UnicodeConverter(BaseConverter):
37 """This converter is the default converter and accepts any string but
38 only one path segment. Thus the string can not include a slash.
40 This is the default validator.
42 Example::
44 Rule('/pages/<page>'),
45 Rule('/<string(length=2):lang_code>')
47 :param map: the :class:`Map`.
48 :param minlength: the minimum length of the string. Must be greater
49 or equal 1.
50 :param maxlength: the maximum length of the string.
51 :param length: the exact length of the string.
52 """
54 part_isolating = True
56 def __init__(
57 self,
58 map: "Map",
59 minlength: int = 1,
60 maxlength: t.Optional[int] = None,
61 length: t.Optional[int] = None,
62 ) -> None:
63 super().__init__(map)
64 if length is not None:
65 length_regex = f"{{{int(length)}}}"
66 else:
67 if maxlength is None:
68 maxlength_value = ""
69 else:
70 maxlength_value = str(int(maxlength))
71 length_regex = f"{{{int(minlength)},{maxlength_value}}}"
72 self.regex = f"[^/]{length_regex}"
75class AnyConverter(BaseConverter):
76 """Matches one of the items provided. Items can either be Python
77 identifiers or strings::
79 Rule('/<any(about, help, imprint, class, "foo,bar"):page_name>')
81 :param map: the :class:`Map`.
82 :param items: this function accepts the possible items as positional
83 arguments.
85 .. versionchanged:: 2.2
86 Value is validated when building a URL.
87 """
89 part_isolating = True
91 def __init__(self, map: "Map", *items: str) -> None:
92 super().__init__(map)
93 self.items = set(items)
94 self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})"
96 def to_url(self, value: t.Any) -> str:
97 if value in self.items:
98 return str(value)
100 valid_values = ", ".join(f"'{item}'" for item in sorted(self.items))
101 raise ValueError(f"'{value}' is not one of {valid_values}")
104class PathConverter(BaseConverter):
105 """Like the default :class:`UnicodeConverter`, but it also matches
106 slashes. This is useful for wikis and similar applications::
108 Rule('/<path:wikipage>')
109 Rule('/<path:wikipage>/edit')
111 :param map: the :class:`Map`.
112 """
114 regex = "[^/].*?"
115 weight = 200
116 part_isolating = False
119class NumberConverter(BaseConverter):
120 """Baseclass for `IntegerConverter` and `FloatConverter`.
122 :internal:
123 """
125 weight = 50
126 num_convert: t.Callable = int
127 part_isolating = True
129 def __init__(
130 self,
131 map: "Map",
132 fixed_digits: int = 0,
133 min: t.Optional[int] = None,
134 max: t.Optional[int] = None,
135 signed: bool = False,
136 ) -> None:
137 if signed:
138 self.regex = self.signed_regex
139 super().__init__(map)
140 self.fixed_digits = fixed_digits
141 self.min = min
142 self.max = max
143 self.signed = signed
145 def to_python(self, value: str) -> t.Any:
146 if self.fixed_digits and len(value) != self.fixed_digits:
147 raise ValidationError()
148 value = self.num_convert(value)
149 if (self.min is not None and value < self.min) or (
150 self.max is not None and value > self.max
151 ):
152 raise ValidationError()
153 return value
155 def to_url(self, value: t.Any) -> str:
156 value = str(self.num_convert(value))
157 if self.fixed_digits:
158 value = value.zfill(self.fixed_digits)
159 return value
161 @property
162 def signed_regex(self) -> str:
163 return f"-?{self.regex}"
166class IntegerConverter(NumberConverter):
167 """This converter only accepts integer values::
169 Rule("/page/<int:page>")
171 By default it only accepts unsigned, positive values. The ``signed``
172 parameter will enable signed, negative values. ::
174 Rule("/page/<int(signed=True):page>")
176 :param map: The :class:`Map`.
177 :param fixed_digits: The number of fixed digits in the URL. If you
178 set this to ``4`` for example, the rule will only match if the
179 URL looks like ``/0001/``. The default is variable length.
180 :param min: The minimal value.
181 :param max: The maximal value.
182 :param signed: Allow signed (negative) values.
184 .. versionadded:: 0.15
185 The ``signed`` parameter.
186 """
188 regex = r"\d+"
189 part_isolating = True
192class FloatConverter(NumberConverter):
193 """This converter only accepts floating point values::
195 Rule("/probability/<float:probability>")
197 By default it only accepts unsigned, positive values. The ``signed``
198 parameter will enable signed, negative values. ::
200 Rule("/offset/<float(signed=True):offset>")
202 :param map: The :class:`Map`.
203 :param min: The minimal value.
204 :param max: The maximal value.
205 :param signed: Allow signed (negative) values.
207 .. versionadded:: 0.15
208 The ``signed`` parameter.
209 """
211 regex = r"\d+\.\d+"
212 num_convert = float
213 part_isolating = True
215 def __init__(
216 self,
217 map: "Map",
218 min: t.Optional[float] = None,
219 max: t.Optional[float] = None,
220 signed: bool = False,
221 ) -> None:
222 super().__init__(map, min=min, max=max, signed=signed) # type: ignore
225class UUIDConverter(BaseConverter):
226 """This converter only accepts UUID strings::
228 Rule('/object/<uuid:identifier>')
230 .. versionadded:: 0.10
232 :param map: the :class:`Map`.
233 """
235 regex = (
236 r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-"
237 r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"
238 )
239 part_isolating = True
241 def to_python(self, value: str) -> uuid.UUID:
242 return uuid.UUID(value)
244 def to_url(self, value: uuid.UUID) -> str:
245 return str(value)
248#: the default converter mapping for the map.
249DEFAULT_CONVERTERS: t.Mapping[str, t.Type[BaseConverter]] = {
250 "default": UnicodeConverter,
251 "string": UnicodeConverter,
252 "any": AnyConverter,
253 "path": PathConverter,
254 "int": IntegerConverter,
255 "float": FloatConverter,
256 "uuid": UUIDConverter,
257}