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

1import re 

2import typing as t 

3import uuid 

4 

5from ..urls import _fast_url_quote 

6 

7if t.TYPE_CHECKING: 

8 from .map import Map 

9 

10 

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 """ 

15 

16 

17class BaseConverter: 

18 """Base class for all converters.""" 

19 

20 regex = "[^/]+" 

21 weight = 100 

22 part_isolating = True 

23 

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

25 self.map = map 

26 

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

28 return value 

29 

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)) 

34 

35 

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. 

39 

40 This is the default validator. 

41 

42 Example:: 

43 

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

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

46 

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 """ 

53 

54 part_isolating = True 

55 

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}" 

73 

74 

75class AnyConverter(BaseConverter): 

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

77 identifiers or strings:: 

78 

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

80 

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

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

83 arguments. 

84 

85 .. versionchanged:: 2.2 

86 Value is validated when building a URL. 

87 """ 

88 

89 part_isolating = True 

90 

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])})" 

95 

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

97 if value in self.items: 

98 return str(value) 

99 

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

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

102 

103 

104class PathConverter(BaseConverter): 

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

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

107 

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

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

110 

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

112 """ 

113 

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

115 weight = 200 

116 part_isolating = False 

117 

118 

119class NumberConverter(BaseConverter): 

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

121 

122 :internal: 

123 """ 

124 

125 weight = 50 

126 num_convert: t.Callable = int 

127 part_isolating = True 

128 

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 

144 

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 

154 

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 

160 

161 @property 

162 def signed_regex(self) -> str: 

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

164 

165 

166class IntegerConverter(NumberConverter): 

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

168 

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

170 

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

172 parameter will enable signed, negative values. :: 

173 

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

175 

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. 

183 

184 .. versionadded:: 0.15 

185 The ``signed`` parameter. 

186 """ 

187 

188 regex = r"\d+" 

189 part_isolating = True 

190 

191 

192class FloatConverter(NumberConverter): 

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

194 

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

196 

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

198 parameter will enable signed, negative values. :: 

199 

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

201 

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. 

206 

207 .. versionadded:: 0.15 

208 The ``signed`` parameter. 

209 """ 

210 

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

212 num_convert = float 

213 part_isolating = True 

214 

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 

223 

224 

225class UUIDConverter(BaseConverter): 

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

227 

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

229 

230 .. versionadded:: 0.10 

231 

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

233 """ 

234 

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 

240 

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

242 return uuid.UUID(value) 

243 

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

245 return str(value) 

246 

247 

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}