Coverage for pydantic/_hypothesis_plugin.py: 100.00%

140 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-15 13:26 +0000

1""" 

2Register Hypothesis strategies for Pydantic custom types. 

3 

4This enables fully-automatic generation of test data for most Pydantic classes. 

5 

6Note that this module has *no* runtime impact on Pydantic itself; instead it 

7is registered as a setuptools entry point and Hypothesis will import it if 

8Pydantic is installed. See also: 

9 

10https://hypothesis.readthedocs.io/en/latest/strategies.html#registering-strategies-via-setuptools-entry-points 

11https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.register_type_strategy 

12https://hypothesis.readthedocs.io/en/latest/strategies.html#interaction-with-pytest-cov 

13https://docs.pydantic.dev/usage/types/#pydantic-types 

14 

15Note that because our motivation is to *improve user experience*, the strategies 

16are always sound (never generate invalid data) but sacrifice completeness for 

17maintainability (ie may be unable to generate some tricky but valid data). 

18 

19Finally, this module makes liberal use of `# type: ignore[<code>]` pragmas. 

20This is because Hypothesis annotates `register_type_strategy()` with 

21`(T, SearchStrategy[T])`, but in most cases we register e.g. `ConstrainedInt` 

22to generate instances of the builtin `int` type which match the constraints. 

23""" 

24 

25import contextlib 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

26import datetime 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

27import ipaddress 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

28import json 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

29import math 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

30from fractions import Fraction 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

31from typing import Callable, Dict, Type, Union, cast, overload 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

32 

33import hypothesis.strategies as st 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

34 

35import pydantic 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

36import pydantic.color 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

37import pydantic.types 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

38from pydantic.utils import lenient_issubclass 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

39 

40# FilePath and DirectoryPath are explicitly unsupported, as we'd have to create 

41# them on-disk, and that's unsafe in general without being told *where* to do so. 

42# 

43# URLs are unsupported because it's easy for users to define their own strategy for 

44# "normal" URLs, and hard for us to define a general strategy which includes "weird" 

45# URLs but doesn't also have unpredictable performance problems. 

46# 

47# conlist() and conset() are unsupported for now, because the workarounds for 

48# Cython and Hypothesis to handle parametrized generic types are incompatible. 

49# We are rethinking Hypothesis compatibility in Pydantic v2. 

50 

51# Emails 

52try: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

53 import email_validator 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

54except ImportError: # pragma: no cover 1MxyzABNCDEFGOHIJKL

55 pass 1MxyzABNCDEFGOHIJKL

56else: 

57 

58 def is_valid_email(s: str) -> bool: 1uabcdevfghijpqrsPQRSTUtwklmno

59 # Hypothesis' st.emails() occasionally generates emails like 0@A0--0.ac 

60 # that are invalid according to email-validator, so we filter those out. 

61 try: 1uabcdevfghijpqrstwklmno

62 email_validator.validate_email(s, check_deliverability=False) 1uabcdevfghijpqrstwklmno

63 return True 1uabcdevfghijpqrstwklmno

64 except email_validator.EmailNotValidError: # pragma: no cover 

65 return False 

66 

67 # Note that these strategies deliberately stay away from any tricky Unicode 

68 # or other encoding issues; we're just trying to generate *something* valid. 

69 st.register_type_strategy(pydantic.EmailStr, st.emails().filter(is_valid_email)) # type: ignore[arg-type] 1uabcdevfghijpqrsPQRSTUtwklmno

70 st.register_type_strategy( 1uabcdevfghijpqrsPQRSTUtwklmno

71 pydantic.NameEmail, 

72 st.builds( 

73 '{} <{}>'.format, # type: ignore[arg-type] 

74 st.from_regex('[A-Za-z0-9_]+( [A-Za-z0-9_]+){0,5}', fullmatch=True), 

75 st.emails().filter(is_valid_email), 

76 ), 

77 ) 

78 

79# PyObject - dotted names, in this case taken from the math module. 

80st.register_type_strategy( 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

81 pydantic.PyObject, # type: ignore[arg-type] 

82 st.sampled_from( 

83 [cast(pydantic.PyObject, f'math.{name}') for name in sorted(vars(math)) if not name.startswith('_')] 

84 ), 

85) 

86 

87# CSS3 Colors; as name, hex, rgb(a) tuples or strings, or hsl strings 

88_color_regexes = ( 1axbyczdAeBfCgDhEiFjGPQRSTUkHlImJnKoL

89 '|'.join( 

90 ( 

91 pydantic.color.r_hex_short, 

92 pydantic.color.r_hex_long, 

93 pydantic.color.r_rgb, 

94 pydantic.color.r_rgba, 

95 pydantic.color.r_hsl, 

96 pydantic.color.r_hsla, 

97 ) 

98 ) 

99 # Use more precise regex patterns to avoid value-out-of-range errors 

100 .replace(pydantic.color._r_sl, r'(?:(\d\d?(?:\.\d+)?|100(?:\.0+)?)%)') 

101 .replace(pydantic.color._r_alpha, r'(?:(0(?:\.\d+)?|1(?:\.0+)?|\.\d+|\d{1,2}%))') 

102 .replace(pydantic.color._r_255, r'(?:((?:\d|\d\d|[01]\d\d|2[0-4]\d|25[0-4])(?:\.\d+)?|255(?:\.0+)?))') 

103) 

104st.register_type_strategy( 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

105 pydantic.color.Color, 

106 st.one_of( 

107 st.sampled_from(sorted(pydantic.color.COLORS_BY_NAME)), 

108 st.tuples( 

109 st.integers(0, 255), 

110 st.integers(0, 255), 

111 st.integers(0, 255), 

112 st.none() | st.floats(0, 1) | st.floats(0, 100).map('{}%'.format), 

113 ), 

114 st.from_regex(_color_regexes, fullmatch=True), 

115 ), 

116) 

117 

118 

119# Card numbers, valid according to the Luhn algorithm 

120 

121 

122def add_luhn_digit(card_number: str) -> str: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

123 # See https://en.wikipedia.org/wiki/Luhn_algorithm 

124 for digit in '0123456789': 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

125 with contextlib.suppress(Exception): 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

126 pydantic.PaymentCardNumber.validate_luhn_check_digit(card_number + digit) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

127 return card_number + digit 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

128 raise AssertionError('Unreachable') # pragma: no cover 

129 

130 

131card_patterns = ( 1axbyczdAeBfCgDhEiFjGPQRSTUkHlImJnKoL

132 # Note that these patterns omit the Luhn check digit; that's added by the function above 

133 '4[0-9]{14}', # Visa 

134 '5[12345][0-9]{13}', # Mastercard 

135 '3[47][0-9]{12}', # American Express 

136 '[0-26-9][0-9]{10,17}', # other (incomplete to avoid overlap) 

137) 

138st.register_type_strategy( 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

139 pydantic.PaymentCardNumber, 

140 st.from_regex('|'.join(card_patterns), fullmatch=True).map(add_luhn_digit), # type: ignore[arg-type] 

141) 

142 

143# UUIDs 

144st.register_type_strategy(pydantic.UUID1, st.uuids(version=1)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

145st.register_type_strategy(pydantic.UUID3, st.uuids(version=3)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

146st.register_type_strategy(pydantic.UUID4, st.uuids(version=4)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

147st.register_type_strategy(pydantic.UUID5, st.uuids(version=5)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

148 

149# Secrets 

150st.register_type_strategy(pydantic.SecretBytes, st.binary().map(pydantic.SecretBytes)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

151st.register_type_strategy(pydantic.SecretStr, st.text().map(pydantic.SecretStr)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

152 

153# IP addresses, networks, and interfaces 

154st.register_type_strategy(pydantic.IPvAnyAddress, st.ip_addresses()) # type: ignore[arg-type] 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

155st.register_type_strategy( 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

156 pydantic.IPvAnyInterface, 

157 st.from_type(ipaddress.IPv4Interface) | st.from_type(ipaddress.IPv6Interface), # type: ignore[arg-type] 

158) 

159st.register_type_strategy( 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

160 pydantic.IPvAnyNetwork, 

161 st.from_type(ipaddress.IPv4Network) | st.from_type(ipaddress.IPv6Network), # type: ignore[arg-type] 

162) 

163 

164# We hook into the con***() functions and the ConstrainedNumberMeta metaclass, 

165# so here we only have to register subclasses for other constrained types which 

166# don't go via those mechanisms. Then there are the registration hooks below. 

167st.register_type_strategy(pydantic.StrictBool, st.booleans()) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

168st.register_type_strategy(pydantic.StrictStr, st.text()) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

169 

170 

171# FutureDate, PastDate 

172st.register_type_strategy(pydantic.FutureDate, st.dates(min_value=datetime.date.today() + datetime.timedelta(days=1))) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

173st.register_type_strategy(pydantic.PastDate, st.dates(max_value=datetime.date.today() - datetime.timedelta(days=1))) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

174 

175 

176# Constrained-type resolver functions 

177# 

178# For these ones, we actually want to inspect the type in order to work out a 

179# satisfying strategy. First up, the machinery for tracking resolver functions: 

180 

181RESOLVERS: Dict[type, Callable[[type], st.SearchStrategy]] = {} # type: ignore[type-arg] 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

182 

183 

184@overload 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

185def _registered(typ: Type[pydantic.types.T]) -> Type[pydantic.types.T]: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

186 pass 

187 

188 

189@overload 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

190def _registered(typ: pydantic.types.ConstrainedNumberMeta) -> pydantic.types.ConstrainedNumberMeta: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

191 pass 

192 

193 

194def _registered( 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

195 typ: Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta] 

196) -> Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta]: 

197 # This function replaces the version in `pydantic.types`, in order to 

198 # effect the registration of new constrained types so that Hypothesis 

199 # can generate valid examples. 

200 pydantic.types._DEFINED_TYPES.add(typ) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

201 for supertype, resolver in RESOLVERS.items(): 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

202 if issubclass(typ, supertype): 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

203 st.register_type_strategy(typ, resolver(typ)) # type: ignore 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

204 return typ 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

205 raise NotImplementedError(f'Unknown type {typ!r} has no resolver to register') # pragma: no cover 

206 

207 

208def resolves( 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

209 typ: Union[type, pydantic.types.ConstrainedNumberMeta] 

210) -> Callable[[Callable[..., st.SearchStrategy]], Callable[..., st.SearchStrategy]]: # type: ignore[type-arg] 

211 def inner(f): # type: ignore 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

212 assert f not in RESOLVERS 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

213 RESOLVERS[typ] = f 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

214 return f 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

215 

216 return inner 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

217 

218 

219# Type-to-strategy resolver functions 

220 

221 

222@resolves(pydantic.JsonWrapper) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

223def resolve_json(cls): # type: ignore[no-untyped-def] 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

224 try: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

225 inner = st.none() if cls.inner_type is None else st.from_type(cls.inner_type) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

226 except Exception: # pragma: no cover 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

227 finite = st.floats(allow_infinity=False, allow_nan=False) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

228 inner = st.recursive( 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

229 base=st.one_of(st.none(), st.booleans(), st.integers(), finite, st.text()), 

230 extend=lambda x: st.lists(x) | st.dictionaries(st.text(), x), # type: ignore 

231 ) 

232 inner_type = getattr(cls, 'inner_type', None) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

233 return st.builds( 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

234 cls.inner_type.json if lenient_issubclass(inner_type, pydantic.BaseModel) else json.dumps, 

235 inner, 

236 ensure_ascii=st.booleans(), 

237 indent=st.none() | st.integers(0, 16), 

238 sort_keys=st.booleans(), 

239 ) 

240 

241 

242@resolves(pydantic.ConstrainedBytes) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

243def resolve_conbytes(cls): # type: ignore[no-untyped-def] # pragma: no cover 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

244 min_size = cls.min_length or 0 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

245 max_size = cls.max_length 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

246 if not cls.strip_whitespace: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

247 return st.binary(min_size=min_size, max_size=max_size) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

248 # Fun with regex to ensure we neither start nor end with whitespace 

249 repeats = '{{{},{}}}'.format( 

250 min_size - 2 if min_size > 2 else 0, 

251 max_size - 2 if (max_size or 0) > 2 else '', 

252 ) 

253 if min_size >= 2: 

254 pattern = rf'\W.{repeats}\W' 

255 elif min_size == 1: 

256 pattern = rf'\W(.{repeats}\W)?' 

257 else: 

258 assert min_size == 0 

259 pattern = rf'(\W(.{repeats}\W)?)?' 

260 return st.from_regex(pattern.encode(), fullmatch=True) 

261 

262 

263@resolves(pydantic.ConstrainedDecimal) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

264def resolve_condecimal(cls): # type: ignore[no-untyped-def] 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

265 min_value = cls.ge 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

266 max_value = cls.le 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

267 if cls.gt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

268 assert min_value is None, 'Set `gt` or `ge`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

269 min_value = cls.gt 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

270 if cls.lt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

271 assert max_value is None, 'Set `lt` or `le`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

272 max_value = cls.lt 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

273 s = st.decimals(min_value, max_value, allow_nan=False, places=cls.decimal_places) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

274 if cls.lt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

275 s = s.filter(lambda d: d < cls.lt) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

276 if cls.gt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

277 s = s.filter(lambda d: cls.gt < d) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

278 return s 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

279 

280 

281@resolves(pydantic.ConstrainedFloat) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

282def resolve_confloat(cls): # type: ignore[no-untyped-def] 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

283 min_value = cls.ge 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

284 max_value = cls.le 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

285 exclude_min = False 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

286 exclude_max = False 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

287 

288 if cls.gt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

289 assert min_value is None, 'Set `gt` or `ge`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

290 min_value = cls.gt 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

291 exclude_min = True 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

292 if cls.lt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

293 assert max_value is None, 'Set `lt` or `le`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

294 max_value = cls.lt 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

295 exclude_max = True 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

296 

297 if cls.multiple_of is None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

298 return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

299 

300 if min_value is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

301 min_value = math.ceil(min_value / cls.multiple_of) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

302 if exclude_min: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

303 min_value = min_value + 1 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

304 if max_value is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

305 assert max_value >= cls.multiple_of, 'Cannot build model with max value smaller than multiple of' 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

306 max_value = math.floor(max_value / cls.multiple_of) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

307 if exclude_max: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

308 max_value = max_value - 1 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

309 

310 return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

311 

312 

313@resolves(pydantic.ConstrainedInt) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

314def resolve_conint(cls): # type: ignore[no-untyped-def] 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

315 min_value = cls.ge 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

316 max_value = cls.le 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

317 if cls.gt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

318 assert min_value is None, 'Set `gt` or `ge`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

319 min_value = cls.gt + 1 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

320 if cls.lt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

321 assert max_value is None, 'Set `lt` or `le`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

322 max_value = cls.lt - 1 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

323 

324 if cls.multiple_of is None or cls.multiple_of == 1: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

325 return st.integers(min_value, max_value) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

326 

327 # These adjustments and the .map handle integer-valued multiples, while the 

328 # .filter handles trickier cases as for confloat. 

329 if min_value is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

330 min_value = math.ceil(Fraction(min_value) / Fraction(cls.multiple_of)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

331 if max_value is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

332 max_value = math.floor(Fraction(max_value) / Fraction(cls.multiple_of)) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

333 return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

334 

335 

336@resolves(pydantic.ConstrainedDate) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

337def resolve_condate(cls): # type: ignore[no-untyped-def] 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

338 if cls.ge is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

339 assert cls.gt is None, 'Set `gt` or `ge`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

340 min_value = cls.ge 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

341 elif cls.gt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

342 min_value = cls.gt + datetime.timedelta(days=1) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

343 else: 

344 min_value = datetime.date.min 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

345 if cls.le is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

346 assert cls.lt is None, 'Set `lt` or `le`, but not both' 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

347 max_value = cls.le 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

348 elif cls.lt is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

349 max_value = cls.lt - datetime.timedelta(days=1) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

350 else: 

351 max_value = datetime.date.max 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

352 return st.dates(min_value, max_value) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

353 

354 

355@resolves(pydantic.ConstrainedStr) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

356def resolve_constr(cls): # type: ignore[no-untyped-def] # pragma: no cover 1axbyczdAeBfCgDhEiFjGpqrsPQRSTUtkHlImJnKoL

357 min_size = cls.min_length or 0 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

358 max_size = cls.max_length 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

359 

360 if cls.regex is None and not cls.strip_whitespace: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

361 return st.text(min_size=min_size, max_size=max_size) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

362 

363 if cls.regex is not None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

364 strategy = st.from_regex(cls.regex) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

365 if cls.strip_whitespace: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

366 strategy = strategy.filter(lambda s: s == s.strip()) 

367 elif cls.strip_whitespace: 1uabcdevfghijpqrstwklmno

368 repeats = '{{{},{}}}'.format( 1uabcdevfghijpqrstwklmno

369 min_size - 2 if min_size > 2 else 0, 1uabcdevfghijpqrstwklmno

370 max_size - 2 if (max_size or 0) > 2 else '', 1uabcdevfghijpqrstwklmno

371 ) 

372 if min_size >= 2: 1uabcdevfghijpqrstwklmno

373 strategy = st.from_regex(rf'\W.{repeats}\W') 

374 elif min_size == 1: 1uabcdevfghijpqrstwklmno

375 strategy = st.from_regex(rf'\W(.{repeats}\W)?') 

376 else: 

377 assert min_size == 0 1uabcdevfghijpqrstwklmno

378 strategy = st.from_regex(rf'(\W(.{repeats}\W)?)?') 1uabcdevfghijpqrstwklmno

379 

380 if min_size == 0 and max_size is None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

381 return strategy 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

382 elif max_size is None: 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

383 return strategy.filter(lambda s: min_size <= len(s)) 

384 return strategy.filter(lambda s: min_size <= len(s) <= max_size) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL

385 

386 

387# Finally, register all previously-defined types, and patch in our new function 

388for typ in list(pydantic.types._DEFINED_TYPES): 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

389 _registered(typ) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

390pydantic.types._registered = _registered 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL

391st.register_type_strategy(pydantic.Json, resolve_json) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL