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

89 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-09 06:08 +0000

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5import uuid 

6import warnings 

7from urllib.parse import quote 

8 

9if t.TYPE_CHECKING: 

10 from .map import Map 

11 

12 

13class ValidationError(ValueError): 

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

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

16 """ 

17 

18 

19class BaseConverter: 

20 """Base class for all converters. 

21 

22 .. versionchanged:: 2.3 

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

24 """ 

25 

26 regex = "[^/]+" 

27 weight = 100 

28 part_isolating = True 

29 

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

31 super().__init_subclass__(**kwargs) 

32 

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

34 # if the regex contains a / character. 

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

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

37 

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

39 self.map = map 

40 

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

42 return value 

43 

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

45 if isinstance(value, (bytes, bytearray)): 

46 warnings.warn( 

47 "Passing bytes as a URL value is deprecated and will not be supported" 

48 " in Werkzeug 3.0.", 

49 DeprecationWarning, 

50 stacklevel=7, 

51 ) 

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

53 

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

55 return quote(str(value), encoding=self.map.charset, safe="!$&'()*+,/:;=@") 

56 

57 

58class UnicodeConverter(BaseConverter): 

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

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

61 

62 This is the default validator. 

63 

64 Example:: 

65 

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

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

68 

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

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

71 or equal 1. 

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

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

74 """ 

75 

76 def __init__( 

77 self, 

78 map: Map, 

79 minlength: int = 1, 

80 maxlength: int | None = None, 

81 length: int | None = None, 

82 ) -> None: 

83 super().__init__(map) 

84 if length is not None: 

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

86 else: 

87 if maxlength is None: 

88 maxlength_value = "" 

89 else: 

90 maxlength_value = str(int(maxlength)) 

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

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

93 

94 

95class AnyConverter(BaseConverter): 

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

97 identifiers or strings:: 

98 

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

100 

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

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

103 arguments. 

104 

105 .. versionchanged:: 2.2 

106 Value is validated when building a URL. 

107 """ 

108 

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

110 super().__init__(map) 

111 self.items = set(items) 

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

113 

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

115 if value in self.items: 

116 return str(value) 

117 

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

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

120 

121 

122class PathConverter(BaseConverter): 

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

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

125 

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

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

128 

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

130 """ 

131 

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

133 weight = 200 

134 

135 

136class NumberConverter(BaseConverter): 

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

138 

139 :internal: 

140 """ 

141 

142 weight = 50 

143 num_convert: t.Callable = int 

144 

145 def __init__( 

146 self, 

147 map: Map, 

148 fixed_digits: int = 0, 

149 min: int | None = None, 

150 max: int | None = None, 

151 signed: bool = False, 

152 ) -> None: 

153 if signed: 

154 self.regex = self.signed_regex 

155 super().__init__(map) 

156 self.fixed_digits = fixed_digits 

157 self.min = min 

158 self.max = max 

159 self.signed = signed 

160 

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

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

163 raise ValidationError() 

164 value = self.num_convert(value) 

165 if (self.min is not None and value < self.min) or ( 

166 self.max is not None and value > self.max 

167 ): 

168 raise ValidationError() 

169 return value 

170 

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

172 value = str(self.num_convert(value)) 

173 if self.fixed_digits: 

174 value = value.zfill(self.fixed_digits) 

175 return value 

176 

177 @property 

178 def signed_regex(self) -> str: 

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

180 

181 

182class IntegerConverter(NumberConverter): 

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

184 

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

186 

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

188 parameter will enable signed, negative values. :: 

189 

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

191 

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

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

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

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

196 :param min: The minimal value. 

197 :param max: The maximal value. 

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

199 

200 .. versionadded:: 0.15 

201 The ``signed`` parameter. 

202 """ 

203 

204 regex = r"\d+" 

205 

206 

207class FloatConverter(NumberConverter): 

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

209 

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

211 

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

213 parameter will enable signed, negative values. :: 

214 

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

216 

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

218 :param min: The minimal value. 

219 :param max: The maximal value. 

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

221 

222 .. versionadded:: 0.15 

223 The ``signed`` parameter. 

224 """ 

225 

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

227 num_convert = float 

228 

229 def __init__( 

230 self, 

231 map: Map, 

232 min: float | None = None, 

233 max: float | None = None, 

234 signed: bool = False, 

235 ) -> None: 

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

237 

238 

239class UUIDConverter(BaseConverter): 

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

241 

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

243 

244 .. versionadded:: 0.10 

245 

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

247 """ 

248 

249 regex = ( 

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

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

252 ) 

253 

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

255 return uuid.UUID(value) 

256 

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

258 return str(value) 

259 

260 

261#: the default converter mapping for the map. 

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

263 "default": UnicodeConverter, 

264 "string": UnicodeConverter, 

265 "any": AnyConverter, 

266 "path": PathConverter, 

267 "int": IntegerConverter, 

268 "float": FloatConverter, 

269 "uuid": UUIDConverter, 

270}