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

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

86 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 value_num = self.num_convert(value) 

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

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

158 ): 

159 raise ValidationError() 

160 return value_num 

161 

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

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

164 if self.fixed_digits: 

165 value_str = value_str.zfill(self.fixed_digits) 

166 return value_str 

167 

168 @property 

169 def signed_regex(self) -> str: 

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

171 

172 

173class IntegerConverter(NumberConverter): 

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

175 

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

177 

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

179 parameter will enable signed, negative values. :: 

180 

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

182 

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

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

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

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

187 :param min: The minimal value. 

188 :param max: The maximal value. 

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

190 

191 .. versionadded:: 0.15 

192 The ``signed`` parameter. 

193 """ 

194 

195 regex = r"\d+" 

196 

197 

198class FloatConverter(NumberConverter): 

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

200 

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

202 

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

204 parameter will enable signed, negative values. :: 

205 

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

207 

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

209 :param min: The minimal value. 

210 :param max: The maximal value. 

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

212 

213 .. versionadded:: 0.15 

214 The ``signed`` parameter. 

215 """ 

216 

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

218 num_convert = float 

219 

220 def __init__( 

221 self, 

222 map: Map, 

223 min: float | None = None, 

224 max: float | None = None, 

225 signed: bool = False, 

226 ) -> None: 

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

228 

229 

230class UUIDConverter(BaseConverter): 

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

232 

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

234 

235 .. versionadded:: 0.10 

236 

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

238 """ 

239 

240 regex = ( 

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

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

243 ) 

244 

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

246 return uuid.UUID(value) 

247 

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

249 return str(value) 

250 

251 

252#: the default converter mapping for the map. 

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

254 "default": UnicodeConverter, 

255 "string": UnicodeConverter, 

256 "any": AnyConverter, 

257 "path": PathConverter, 

258 "int": IntegerConverter, 

259 "float": FloatConverter, 

260 "uuid": UUIDConverter, 

261}