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