1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11import codecs
12import copy
13import dataclasses
14import inspect
15import itertools
16import platform
17import sys
18import sysconfig
19import typing
20from functools import partial
21from typing import (
22 TYPE_CHECKING,
23 Any,
24 ForwardRef,
25 Optional,
26 TypedDict as TypedDict,
27 Union,
28 get_args,
29)
30
31try:
32 BaseExceptionGroup = BaseExceptionGroup
33 ExceptionGroup = ExceptionGroup # pragma: no cover
34except NameError:
35 from exceptiongroup import (
36 BaseExceptionGroup as BaseExceptionGroup,
37 ExceptionGroup as ExceptionGroup,
38 )
39if TYPE_CHECKING:
40 from typing_extensions import (
41 Concatenate as Concatenate,
42 NotRequired as NotRequired,
43 ParamSpec as ParamSpec,
44 TypeAlias as TypeAlias,
45 TypedDict as TypedDict,
46 override as override,
47 )
48
49 from hypothesis.internal.conjecture.engine import ConjectureRunner
50else:
51 # In order to use NotRequired, we need the version of TypedDict included in Python 3.11+.
52 if sys.version_info[:2] >= (3, 11):
53 from typing import NotRequired as NotRequired, TypedDict as TypedDict
54 else:
55 try:
56 from typing_extensions import (
57 NotRequired as NotRequired,
58 TypedDict as TypedDict,
59 )
60 except ImportError:
61 # We can use the old TypedDict from Python 3.8+ at runtime.
62 class NotRequired:
63 """A runtime placeholder for the NotRequired type, which is not available in Python <3.11."""
64
65 def __class_getitem__(cls, item):
66 return cls
67
68 try:
69 from typing import (
70 Concatenate as Concatenate,
71 ParamSpec as ParamSpec,
72 TypeAlias as TypeAlias,
73 override as override,
74 )
75 except ImportError:
76 try:
77 from typing_extensions import (
78 Concatenate as Concatenate,
79 ParamSpec as ParamSpec,
80 TypeAlias as TypeAlias,
81 override as override,
82 )
83 except ImportError:
84 Concatenate, ParamSpec = None, None
85 TypeAlias = None
86 override = lambda f: f
87
88if sys.version_info >= (3, 10):
89 from types import EllipsisType as EllipsisType
90elif TYPE_CHECKING:
91 from builtins import ellipsis as EllipsisType
92else: # pragma: no cover
93 EllipsisType = type(Ellipsis)
94
95
96PYPY = platform.python_implementation() == "PyPy"
97GRAALPY = platform.python_implementation() == "GraalVM"
98WINDOWS = platform.system() == "Windows"
99# First defined in CPython 3.13, defaults to False
100FREE_THREADED_CPYTHON = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
101
102
103def add_note(exc, note):
104 try:
105 exc.add_note(note)
106 except AttributeError:
107 if not hasattr(exc, "__notes__"):
108 try:
109 exc.__notes__ = []
110 except AttributeError:
111 return # give up, might be e.g. a frozen dataclass
112 exc.__notes__.append(note)
113
114
115def escape_unicode_characters(s: str) -> str:
116 return codecs.encode(s, "unicode_escape").decode("ascii")
117
118
119def int_from_bytes(data: Union[bytes, bytearray]) -> int:
120 return int.from_bytes(data, "big")
121
122
123def int_to_bytes(i: int, size: int) -> bytes:
124 return i.to_bytes(size, "big")
125
126
127def int_to_byte(i: int) -> bytes:
128 return bytes([i])
129
130
131def is_typed_named_tuple(cls: type) -> bool:
132 """Return True if cls is probably a subtype of `typing.NamedTuple`.
133
134 Unfortunately types created with `class T(NamedTuple):` actually
135 subclass `tuple` directly rather than NamedTuple. This is annoying,
136 and means we just have to hope that nobody defines a different tuple
137 subclass with similar attributes.
138 """
139 return (
140 issubclass(cls, tuple)
141 and hasattr(cls, "_fields")
142 and (hasattr(cls, "_field_types") or hasattr(cls, "__annotations__"))
143 )
144
145
146def _hint_and_args(x):
147 return (x, *get_args(x))
148
149
150def get_type_hints(thing: object) -> dict[str, Any]:
151 """Like the typing version, but tries harder and never errors.
152
153 Tries harder: if the thing to inspect is a class but typing.get_type_hints
154 raises an error or returns no hints, then this function will try calling it
155 on the __init__ method. This second step often helps with user-defined
156 classes on older versions of Python. The third step we take is trying
157 to fetch types from the __signature__ property.
158 They override any other ones we found earlier.
159
160 Never errors: instead of raising TypeError for uninspectable objects, or
161 NameError for unresolvable forward references, just return an empty dict.
162 """
163 if isinstance(thing, partial):
164 from hypothesis.internal.reflection import get_signature
165
166 bound = set(get_signature(thing.func).parameters).difference(
167 get_signature(thing).parameters
168 )
169 return {k: v for k, v in get_type_hints(thing.func).items() if k not in bound}
170
171 try:
172 hints = typing.get_type_hints(thing, include_extras=True)
173 except (AttributeError, TypeError, NameError): # pragma: no cover
174 hints = {}
175
176 if inspect.isclass(thing):
177 try:
178 hints.update(typing.get_type_hints(thing.__init__, include_extras=True))
179 except (TypeError, NameError, AttributeError):
180 pass
181
182 try:
183 if hasattr(thing, "__signature__"):
184 # It is possible for the signature and annotations attributes to
185 # differ on an object due to renamed arguments.
186 from hypothesis.internal.reflection import get_signature
187 from hypothesis.strategies._internal.types import is_a_type
188
189 vkinds = (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
190 for p in get_signature(thing).parameters.values():
191 if (
192 p.kind not in vkinds
193 and is_a_type(p.annotation)
194 and p.annotation is not p.empty
195 ):
196 p_hint = p.annotation
197
198 # Defer to `get_type_hints` if signature annotation is, or
199 # contains, a forward reference that is otherwise resolved.
200 if any(
201 isinstance(sig_hint, ForwardRef)
202 and not isinstance(hint, ForwardRef)
203 for sig_hint, hint in zip(
204 _hint_and_args(p.annotation),
205 _hint_and_args(hints.get(p.name, Any)),
206 )
207 ):
208 p_hint = hints[p.name]
209 if p.default is None:
210 hints[p.name] = typing.Optional[p_hint]
211 else:
212 hints[p.name] = p_hint
213 except (AttributeError, TypeError, NameError): # pragma: no cover
214 pass
215
216 return hints
217
218
219# Under Python 2, math.floor and math.ceil returned floats, which cannot
220# represent large integers - eg `float(2**53) == float(2**53 + 1)`.
221# We therefore implement them entirely in (long) integer operations.
222# We still use the same trick on Python 3, because Numpy values and other
223# custom __floor__ or __ceil__ methods may convert via floats.
224# See issue #1667, Numpy issue 9068.
225def floor(x):
226 y = int(x)
227 if y != x and x < 0:
228 return y - 1
229 return y
230
231
232def ceil(x):
233 y = int(x)
234 if y != x and x > 0:
235 return y + 1
236 return y
237
238
239def extract_bits(x: int, /, width: Optional[int] = None) -> list[int]:
240 assert x >= 0
241 result = []
242 while x:
243 result.append(x & 1)
244 x >>= 1
245 if width is not None:
246 result = (result + [0] * width)[:width]
247 result.reverse()
248 return result
249
250
251# int.bit_count was added in python 3.10
252try:
253 bit_count = int.bit_count
254except AttributeError: # pragma: no cover
255 bit_count = lambda self: sum(extract_bits(abs(self)))
256
257
258def bad_django_TestCase(runner: Optional["ConjectureRunner"]) -> bool:
259 if runner is None or "django.test" not in sys.modules:
260 return False
261 else: # pragma: no cover
262 if not isinstance(runner, sys.modules["django.test"].TransactionTestCase):
263 return False
264
265 from hypothesis.extra.django._impl import HypothesisTestCase
266
267 return not isinstance(runner, HypothesisTestCase)
268
269
270# see issue #3812
271if sys.version_info[:2] < (3, 12):
272
273 def dataclass_asdict(obj, *, dict_factory=dict):
274 """
275 A vendored variant of dataclasses.asdict. Includes the bugfix for
276 defaultdicts (cpython/32056) for all versions. See also issues/3812.
277
278 This should be removed whenever we drop support for 3.11. We can use the
279 standard dataclasses.asdict after that point.
280 """
281 if not dataclasses._is_dataclass_instance(obj): # pragma: no cover
282 raise TypeError("asdict() should be called on dataclass instances")
283 return _asdict_inner(obj, dict_factory)
284
285else: # pragma: no cover
286 dataclass_asdict = dataclasses.asdict
287
288
289def _asdict_inner(obj, dict_factory):
290 if dataclasses._is_dataclass_instance(obj):
291 return dict_factory(
292 (f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
293 for f in dataclasses.fields(obj)
294 )
295 elif isinstance(obj, tuple) and hasattr(obj, "_fields"):
296 return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
297 elif isinstance(obj, (list, tuple)):
298 return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
299 elif isinstance(obj, dict):
300 if hasattr(type(obj), "default_factory"):
301 result = type(obj)(obj.default_factory)
302 for k, v in obj.items():
303 result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
304 return result
305 return type(obj)(
306 (_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
307 for k, v in obj.items()
308 )
309 else:
310 return copy.deepcopy(obj)
311
312
313if sys.version_info[:2] < (3, 13):
314 # batched was added in 3.12, strict flag in 3.13
315 # copied from 3.13 docs reference implementation
316
317 def batched(iterable, n, *, strict=False):
318 if n < 1:
319 raise ValueError("n must be at least one")
320 iterator = iter(iterable)
321 while batch := tuple(itertools.islice(iterator, n)):
322 if strict and len(batch) != n: # pragma: no cover
323 raise ValueError("batched(): incomplete batch")
324 yield batch
325
326else: # pragma: no cover
327 batched = itertools.batched