Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/routing/converters.py: 52%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

89 statements  

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5import uuid 

6from urllib.parse import quote 

7 

8if t.TYPE_CHECKING: 

9 from .map import Map 

10 

11 

12class ValidationError(ValueError): 

13 """Validation error. If a rule converter raises this exception the rule 

14 does not match the current URL and the next URL is tried. 

15 """ 

16 

17 

18class BaseConverter: 

19 """Base class for all converters. 

20 

21 .. versionchanged:: 2.3 

22 ``part_isolating`` defaults to ``False`` if ``regex`` contains a ``/``. 

23 """ 

24 

25 regex = "[^/]+" 

26 weight = 100 

27 part_isolating = True 

28 

29 def __init_subclass__(cls, **kwargs: t.Any) -> None: 

30 super().__init_subclass__(**kwargs) 

31 

32 # If the converter isn't inheriting its regex, disable part_isolating by default 

33 # if the regex contains a / character. 

34 if "regex" in cls.__dict__ and "part_isolating" not in cls.__dict__: 

35 cls.part_isolating = "/" not in cls.regex 

36 

37 def __init__(self, map: Map, *args: t.Any, **kwargs: t.Any) -> None: 

38 self.map = map 

39 

40 def to_python(self, value: str) -> t.Any: 

41 return value 

42 

43 def to_url(self, value: t.Any) -> str: 

44 # safe = https://url.spec.whatwg.org/#url-path-segment-string 

45 return quote(str(value), safe="!$&'()*+,/:;=@") 

46 

47 

48class UnicodeConverter(BaseConverter): 

49 """This converter is the default converter and accepts any string but 

50 only one path segment. Thus the string can not include a slash. 

51 

52 This is the default validator. 

53 

54 Example:: 

55 

56 Rule('/pages/<page>'), 

57 Rule('/<string(length=2):lang_code>') 

58 

59 :param map: the :class:`Map`. 

60 :param minlength: the minimum length of the string. Must be greater 

61 or equal 1. 

62 :param maxlength: the maximum length of the string. 

63 :param length: the exact length of the string. 

64 """ 

65 

66 def __init__( 

67 self, 

68 map: Map, 

69 minlength: int = 1, 

70 maxlength: int | None = None, 

71 length: int | None = None, 

72 ) -> None: 

73 super().__init__(map) 

74 if length is not None: 

75 length_regex = f"{{{int(length)}}}" 

76 else: 

77 if maxlength is None: 

78 maxlength_value = "" 

79 else: 

80 maxlength_value = str(int(maxlength)) 

81 length_regex = f"{{{int(minlength)},{maxlength_value}}}" 

82 self.regex = f"[^/]{length_regex}" 

83 

84 

85class AnyConverter(BaseConverter): 

86 """Matches one of the items provided. Items can either be Python 

87 identifiers or strings:: 

88 

89 Rule('/<any(about, help, imprint, class, "foo,bar"):page_name>') 

90 

91 :param map: the :class:`Map`. 

92 :param items: this function accepts the possible items as positional 

93 arguments. 

94 

95 .. versionchanged:: 2.2 

96 Value is validated when building a URL. 

97 """ 

98 

99 def __init__(self, map: Map, *items: str) -> None: 

100 super().__init__(map) 

101 self.items = set(items) 

102 self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})" 

103 

104 def to_url(self, value: t.Any) -> str: 

105 if value in self.items: 

106 return str(value) 

107 

108 valid_values = ", ".join(f"'{item}'" for item in sorted(self.items)) 

109 raise ValueError(f"'{value}' is not one of {valid_values}") 

110 

111 

112class PathConverter(BaseConverter): 

113 """Like the default :class:`UnicodeConverter`, but it also matches 

114 slashes. This is useful for wikis and similar applications:: 

115 

116 Rule('/<path:wikipage>') 

117 Rule('/<path:wikipage>/edit') 

118 

119 :param map: the :class:`Map`. 

120 """ 

121 

122 part_isolating = False 

123 regex = "[^/].*?" 

124 weight = 200 

125 

126 

127class NumberConverter(BaseConverter): 

128 """Baseclass for `IntegerConverter` and `FloatConverter`. 

129 

130 :internal: 

131 """ 

132 

133 weight = 50 

134 num_convert: t.Callable[[t.Any], t.Any] = int 

135 

136 def __init__( 

137 self, 

138 map: Map, 

139 fixed_digits: int = 0, 

140 min: int | None = None, 

141 max: int | None = None, 

142 signed: bool = False, 

143 ) -> None: 

144 if signed: 

145 self.regex = self.signed_regex 

146 super().__init__(map) 

147 self.fixed_digits = fixed_digits 

148 self.min = min 

149 self.max = max 

150 self.signed = signed 

151 

152 def to_python(self, value: str) -> t.Any: 

153 if self.fixed_digits and len(value) != self.fixed_digits: 

154 raise ValidationError() 

155 

156 try: 

157 value_num = self.num_convert(value) 

158 except ValueError as e: 

159 raise ValidationError() from e 

160 

161 if (self.min is not None and value_num < self.min) or ( 

162 self.max is not None and value_num > self.max 

163 ): 

164 raise ValidationError() 

165 return value_num 

166 

167 def to_url(self, value: t.Any) -> str: 

168 value_str = str(self.num_convert(value)) 

169 if self.fixed_digits: 

170 value_str = value_str.zfill(self.fixed_digits) 

171 return value_str 

172 

173 @property 

174 def signed_regex(self) -> str: 

175 return f"-?{self.regex}" 

176 

177 

178class IntegerConverter(NumberConverter): 

179 """This converter only accepts integer values:: 

180 

181 Rule("/page/<int:page>") 

182 

183 By default it only accepts unsigned, positive values. The ``signed`` 

184 parameter will enable signed, negative values. :: 

185 

186 Rule("/page/<int(signed=True):page>") 

187 

188 :param map: The :class:`Map`. 

189 :param fixed_digits: The number of fixed digits in the URL. If you 

190 set this to ``4`` for example, the rule will only match if the 

191 URL looks like ``/0001/``. The default is variable length. 

192 :param min: The minimal value. 

193 :param max: The maximal value. 

194 :param signed: Allow signed (negative) values. 

195 

196 .. versionadded:: 0.15 

197 The ``signed`` parameter. 

198 """ 

199 

200 regex = r"\d+" 

201 

202 

203class FloatConverter(NumberConverter): 

204 """This converter only accepts floating point values:: 

205 

206 Rule("/probability/<float:probability>") 

207 

208 By default it only accepts unsigned, positive values. The ``signed`` 

209 parameter will enable signed, negative values. :: 

210 

211 Rule("/offset/<float(signed=True):offset>") 

212 

213 :param map: The :class:`Map`. 

214 :param min: The minimal value. 

215 :param max: The maximal value. 

216 :param signed: Allow signed (negative) values. 

217 

218 .. versionadded:: 0.15 

219 The ``signed`` parameter. 

220 """ 

221 

222 regex = r"\d+\.\d+" 

223 num_convert = float 

224 

225 def __init__( 

226 self, 

227 map: Map, 

228 min: float | None = None, 

229 max: float | None = None, 

230 signed: bool = False, 

231 ) -> None: 

232 super().__init__(map, min=min, max=max, signed=signed) # type: ignore 

233 

234 

235class UUIDConverter(BaseConverter): 

236 """This converter only accepts UUID strings:: 

237 

238 Rule('/object/<uuid:identifier>') 

239 

240 .. versionadded:: 0.10 

241 

242 :param map: the :class:`Map`. 

243 """ 

244 

245 regex = ( 

246 r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-" 

247 r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}" 

248 ) 

249 

250 def to_python(self, value: str) -> uuid.UUID: 

251 return uuid.UUID(value) 

252 

253 def to_url(self, value: uuid.UUID) -> str: 

254 return str(value) 

255 

256 

257#: the default converter mapping for the map. 

258DEFAULT_CONVERTERS: t.Mapping[str, type[BaseConverter]] = { 

259 "default": UnicodeConverter, 

260 "string": UnicodeConverter, 

261 "any": AnyConverter, 

262 "path": PathConverter, 

263 "int": IntegerConverter, 

264 "float": FloatConverter, 

265 "uuid": UUIDConverter, 

266}