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
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
1"""
2Register Hypothesis strategies for Pydantic custom types.
4This enables fully-automatic generation of test data for most Pydantic classes.
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:
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
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).
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"""
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
33import hypothesis.strategies as st 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
35import pydantic 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
36import pydantic.color 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
37import pydantic.types 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
38from pydantic.utils import lenient_issubclass 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
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.
51# Emails
52try: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
53 import email_validator 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
54except ImportError: # pragma: no cover 1MxyzABNCDEFGOHIJKL
55 pass 1MxyzABNCDEFGOHIJKL
56else:
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
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 )
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)
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)
119# Card numbers, valid according to the Luhn algorithm
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
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)
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
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
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)
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
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
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:
181RESOLVERS: Dict[type, Callable[[type], st.SearchStrategy]] = {} # type: ignore[type-arg] 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
184@overload 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
185def _registered(typ: Type[pydantic.types.T]) -> Type[pydantic.types.T]: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
186 pass
189@overload 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
190def _registered(typ: pydantic.types.ConstrainedNumberMeta) -> pydantic.types.ConstrainedNumberMeta: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
191 pass
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
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
216 return inner 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
219# Type-to-strategy resolver functions
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 )
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)
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
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
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
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
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
310 return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of) 1uMaxbyczdAeBvNfCgDhEiFjGpqrstwOkHlImJnKoL
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
324 if cls.multiple_of is None or cls.multiple_of == 1: 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
325 return st.integers(min_value, max_value) 1uMaxbyczdAeBvNfCgDhEiFjGpqrsPQRSTUtwOkHlImJnKoL
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
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
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
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
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
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
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