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
11"""This file can approximately be considered the collection of hypothesis going
12to really unreasonable lengths to produce pretty output."""
13
14import ast
15import hashlib
16import inspect
17import re
18import sys
19import textwrap
20import types
21import warnings
22from collections.abc import Callable, Sequence
23from functools import partial, wraps
24from inspect import Parameter, Signature
25from io import StringIO
26from random import _inst as global_random_instance
27from tokenize import COMMENT, generate_tokens, untokenize
28from types import EllipsisType, ModuleType
29from typing import TYPE_CHECKING, Any, TypeVar, Union
30
31from hypothesis.errors import HypothesisWarning
32from hypothesis.internal import lambda_sources
33from hypothesis.internal.compat import is_typed_named_tuple
34from hypothesis.utils.conventions import not_set
35from hypothesis.vendor.pretty import pretty
36
37if TYPE_CHECKING:
38 from hypothesis.strategies._internal.strategies import SearchStrategy
39
40T = TypeVar("T")
41
42
43def is_mock(obj: object) -> bool:
44 """Determine if the given argument is a mock type."""
45
46 # We want to be able to detect these when dealing with various test
47 # args. As they are sneaky and can look like almost anything else,
48 # we'll check this by looking for an attribute with a name that it's really
49 # unlikely to implement accidentally, and that anyone who implements it
50 # deliberately should know what they're doing. This is more robust than
51 # looking for types.
52 return hasattr(obj, "hypothesis_internal_is_this_a_mock_check")
53
54
55def _clean_source(src: str) -> bytes:
56 """Return the source code as bytes, without decorators or comments.
57
58 Because this is part of our database key, we reduce the cache invalidation
59 rate by ignoring decorators, comments, trailing whitespace, and empty lines.
60 We can't just use the (dumped) AST directly because it changes between Python
61 versions (e.g. ast.Constant)
62 """
63 # Get the (one-indexed) line number of the function definition, and drop preceding
64 # lines - i.e. any decorators, so that adding `@example()`s keeps the same key.
65 try:
66 funcdef = ast.parse(src).body[0]
67 src = "".join(src.splitlines(keepends=True)[funcdef.lineno - 1 :])
68 except Exception:
69 pass
70 # Remove blank lines and use the tokenize module to strip out comments,
71 # so that those can be changed without changing the database key.
72 try:
73 src = untokenize(
74 t for t in generate_tokens(StringIO(src).readline) if t.type != COMMENT
75 )
76 except Exception:
77 pass
78 # Finally, remove any trailing whitespace and empty lines as a last cleanup.
79 return "\n".join(x.rstrip() for x in src.splitlines() if x.rstrip()).encode()
80
81
82def function_digest(function: Any) -> bytes:
83 """Returns a string that is stable across multiple invocations across
84 multiple processes and is prone to changing significantly in response to
85 minor changes to the function.
86
87 No guarantee of uniqueness though it usually will be. Digest collisions
88 lead to unfortunate but not fatal problems during database replay.
89 """
90 hasher = hashlib.sha384()
91 try:
92 src = inspect.getsource(function)
93 except (OSError, TypeError):
94 # If we can't actually get the source code, try for the name as a fallback.
95 # NOTE: We might want to change this to always adding function.__qualname__,
96 # to differentiate f.x. two classes having the same function implementation
97 # with class-dependent behaviour.
98 try:
99 hasher.update(function.__name__.encode())
100 except AttributeError:
101 pass
102 else:
103 hasher.update(_clean_source(src))
104 try:
105 # This is additional to the source code because it can include the effects
106 # of decorators, or of post-hoc assignment to the .__signature__ attribute.
107 hasher.update(repr(get_signature(function)).encode())
108 except Exception:
109 pass
110 try:
111 # We set this in order to distinguish e.g. @pytest.mark.parametrize cases.
112 hasher.update(function._hypothesis_internal_add_digest)
113 except AttributeError:
114 pass
115 return hasher.digest()
116
117
118def get_signature(
119 target: Any, *, follow_wrapped: bool = True, eval_str: bool = False
120) -> Signature:
121 # Special case for use of `@unittest.mock.patch` decorator, mimicking the
122 # behaviour of getfullargspec instead of reporting unusable arguments.
123 # (unittest.mock is slow to import, and can't have been used if not imported)
124 patches = getattr(target, "patchings", None)
125 if (
126 isinstance(patches, list)
127 and (mock := sys.modules.get("unittest.mock")) is not None
128 and all(isinstance(p, mock._patch) for p in patches)
129 ):
130 return Signature(
131 [
132 Parameter("args", Parameter.VAR_POSITIONAL),
133 Parameter("keywargs", Parameter.VAR_KEYWORD),
134 ]
135 )
136
137 if isinstance(getattr(target, "__signature__", None), Signature):
138 # This special case covers unusual codegen like Pydantic models
139 sig = target.__signature__
140 # And *this* much more complicated block ignores the `self` argument
141 # if that's been (incorrectly) included in the custom signature.
142 if sig.parameters and (inspect.isclass(target) or inspect.ismethod(target)):
143 selfy = next(iter(sig.parameters.values()))
144 if (
145 selfy.name == "self"
146 and selfy.default is Parameter.empty
147 and selfy.kind.name.startswith("POSITIONAL_")
148 ):
149 return sig.replace(
150 parameters=[v for k, v in sig.parameters.items() if k != "self"]
151 )
152 return sig
153 if sys.version_info >= (3, 14) and not eval_str:
154 from annotationlib import Format
155
156 # Preserve resolved types without requiring TYPE_CHECKING-only names
157 # to be available when Python evaluates lazy annotations.
158 return inspect.signature(
159 target, follow_wrapped=follow_wrapped, annotation_format=Format.FORWARDREF
160 )
161 return inspect.signature(target, follow_wrapped=follow_wrapped, eval_str=eval_str)
162
163
164def arg_is_required(param: Parameter) -> bool:
165 return param.default is Parameter.empty and param.kind in (
166 Parameter.POSITIONAL_OR_KEYWORD,
167 Parameter.KEYWORD_ONLY,
168 )
169
170
171def required_args(
172 target: Callable[..., Any],
173 args: tuple["SearchStrategy[Any]", ...] = (),
174 kwargs: dict[str, Union["SearchStrategy[Any]", EllipsisType]] | None = None,
175) -> set[str]:
176 """Return a set of names of required args to target that were not supplied
177 in args or kwargs.
178
179 This is used in builds() to determine which arguments to attempt to
180 fill from type hints. target may be any callable (including classes
181 and bound methods). args and kwargs should be as they are passed to
182 builds() - that is, a tuple of values and a dict of names: values.
183 """
184 kwargs = {} if kwargs is None else kwargs
185 # We start with a workaround for NamedTuples, which don't have nice inits
186 if inspect.isclass(target) and is_typed_named_tuple(target):
187 fields = target._fields # type: ignore
188 provided = set(kwargs) | set(fields[: len(args)])
189 return set(fields) - provided
190 # Then we try to do the right thing with inspect.signature
191 try:
192 sig = get_signature(target)
193 except (ValueError, TypeError):
194 return set()
195 return {
196 name
197 for name, param in list(sig.parameters.items())[len(args) :]
198 if arg_is_required(param) and name not in kwargs
199 }
200
201
202def convert_keyword_arguments(
203 function: Any, args: Sequence[object], kwargs: dict[str, object]
204) -> tuple[tuple[object, ...], dict[str, object]]:
205 """Returns a pair of a tuple and a dictionary which would be equivalent
206 passed as positional and keyword args to the function. Unless function has
207 kwonlyargs or **kwargs the dictionary will always be empty.
208 """
209 sig = get_signature(function, follow_wrapped=False)
210 bound = sig.bind(*args, **kwargs)
211 return bound.args, bound.kwargs
212
213
214def convert_positional_arguments(
215 function: Any, args: Sequence[object], kwargs: dict[str, object]
216) -> tuple[tuple[object, ...], dict[str, object]]:
217 """Return a tuple (new_args, new_kwargs) where all possible arguments have
218 been moved to kwargs.
219
220 new_args will only be non-empty if function has pos-only args or *args.
221 """
222 sig = get_signature(function, follow_wrapped=False)
223 bound = sig.bind(*args, **kwargs)
224 new_args = []
225 new_kwargs = dict(bound.arguments)
226 for p in sig.parameters.values():
227 if p.name in new_kwargs:
228 if p.kind is p.POSITIONAL_ONLY:
229 new_args.append(new_kwargs.pop(p.name))
230 elif p.kind is p.VAR_POSITIONAL:
231 new_args.extend(new_kwargs.pop(p.name))
232 elif p.kind is p.VAR_KEYWORD:
233 assert set(new_kwargs[p.name]).isdisjoint(set(new_kwargs) - {p.name})
234 new_kwargs.update(new_kwargs.pop(p.name))
235 return tuple(new_args), new_kwargs
236
237
238def ast_arguments_matches_signature(args: ast.arguments, sig: Signature) -> bool:
239 expected: list[tuple[str, int]] = []
240 for node in args.posonlyargs:
241 expected.append((node.arg, Parameter.POSITIONAL_ONLY))
242 for node in args.args:
243 expected.append((node.arg, Parameter.POSITIONAL_OR_KEYWORD))
244 if args.vararg is not None:
245 expected.append((args.vararg.arg, Parameter.VAR_POSITIONAL))
246 for node in args.kwonlyargs:
247 expected.append((node.arg, Parameter.KEYWORD_ONLY))
248 if args.kwarg is not None:
249 expected.append((args.kwarg.arg, Parameter.VAR_KEYWORD))
250 return expected == [(p.name, p.kind) for p in sig.parameters.values()]
251
252
253def is_first_param_referenced_in_function(f: Any) -> bool:
254 """Is the given name referenced within f?"""
255 try:
256 tree = ast.parse(textwrap.dedent(inspect.getsource(f)))
257 except Exception:
258 return True # Assume it's OK unless we know otherwise
259 name = next(iter(get_signature(f).parameters))
260 return any(
261 isinstance(node, ast.Name)
262 and node.id == name
263 and isinstance(node.ctx, ast.Load)
264 for node in ast.walk(tree)
265 )
266
267
268def function_location(f: object) -> str | None:
269 """Return a ``filename:lineno`` string for where ``f`` was defined, if known."""
270 try:
271 code = f.__code__ # type: ignore[attr-defined]
272 return f"{code.co_filename}:{code.co_firstlineno}"
273 except AttributeError:
274 return None
275
276
277def get_pretty_function_description(f: object) -> str:
278 # Anything which knows how to pretty-print itself knows better than we do -
279 # e.g. the constant functions from st.functions(), which would otherwise
280 # borrow the name of the function they imitate. We skip classes, whose
281 # instance method would be called with the printer as `self`, and require
282 # a callable so that pretty() can't bounce straight back to us.
283 if not isinstance(f, type) and callable(getattr(f, "_repr_pretty_", None)):
284 return pretty(f)
285 if isinstance(f, partial):
286 return pretty(f)
287 if not hasattr(f, "__name__"):
288 return repr(f)
289 name = f.__name__
290 if name == "<lambda>":
291 return lambda_sources.lambda_description(f)
292 elif isinstance(f, (types.MethodType, types.BuiltinMethodType)):
293 self = f.__self__
294 # Some objects, like `builtins.abs` are of BuiltinMethodType but have
295 # their module as __self__. This might include c-extensions generally?
296 if not (self is None or inspect.isclass(self) or inspect.ismodule(self)):
297 if self is global_random_instance:
298 return f"random.{name}"
299 return f"{self!r}.{name}"
300 elif isinstance(name, str) and getattr(dict, name, object()) is f:
301 # special case for keys/values views in from_type() / ghostwriter output
302 return f"dict.{name}"
303 return name
304
305
306def nicerepr(v: Any) -> str:
307 if inspect.isfunction(v):
308 return get_pretty_function_description(v)
309 elif isinstance(v, type):
310 return v.__name__
311 else:
312 # With TypeVar T, show List[T] instead of TypeError on List[~T]
313 return re.sub(r"(\[)~([A-Z][a-z]*\])", r"\g<1>\g<2>", pretty(v))
314
315
316def repr_call(
317 f: Any, args: Sequence[object], kwargs: dict[str, object], *, reorder: bool = True
318) -> str:
319 # Note: for multi-line pretty-printing, see RepresentationPrinter.repr_call()
320 if reorder:
321 args, kwargs = convert_positional_arguments(f, args, kwargs)
322
323 bits = [nicerepr(x) for x in args]
324
325 for p in get_signature(f).parameters.values():
326 if p.name in kwargs and not p.kind.name.startswith("VAR_"):
327 bits.append(f"{p.name}={nicerepr(kwargs.pop(p.name))}")
328 if kwargs:
329 for a in sorted(kwargs):
330 bits.append(f"{a}={nicerepr(kwargs[a])}")
331
332 rep = nicerepr(f)
333 if rep.startswith("lambda") and ":" in rep:
334 rep = f"({rep})"
335 repr_len = len(rep) + sum(len(b) for b in bits) # approx
336 if repr_len > 30000:
337 warnings.warn(
338 "Generating overly large repr. This is an expensive operation, and with "
339 f"a length of {repr_len//1000} kB is unlikely to be useful. Use -Wignore "
340 "to ignore the warning, or -Werror to get a traceback.",
341 HypothesisWarning,
342 stacklevel=2,
343 )
344 return rep + "(" + ", ".join(bits) + ")"
345
346
347def check_valid_identifier(identifier: str) -> None:
348 if not identifier.isidentifier():
349 raise ValueError(f"{identifier!r} is not a valid python identifier")
350
351
352eval_cache: dict[str, ModuleType] = {}
353
354
355def source_exec_as_module(source: str) -> ModuleType:
356 try:
357 return eval_cache[source]
358 except KeyError:
359 pass
360
361 hexdigest = hashlib.sha384(source.encode()).hexdigest()
362 result = ModuleType("hypothesis_temporary_module_" + hexdigest)
363 # ModuleType() sets __spec__ = None. Later, we call @impersonate on functions defined
364 # in the module, which gives it a real co_filename. Python traceback formatting then
365 # calls internal linecache code which warns on the combination of "real on-disk file"
366 # + "null __spec__".
367 #
368 # Sidestep this by deleting __spec__; we don't need it.
369 del result.__spec__
370 assert isinstance(source, str)
371 exec(source, result.__dict__)
372 eval_cache[source] = result
373 return result
374
375
376COPY_SIGNATURE_SCRIPT = """
377from contextlib import aclosing, closing
378
379from hypothesis.utils.conventions import not_set
380
381def accept({funcname}):
382 {def_prefix}def {name}{signature}:
383 {body}
384 return {name}
385""".lstrip()
386
387
388def get_varargs(
389 sig: Signature, kind: int = Parameter.VAR_POSITIONAL
390) -> Parameter | None:
391 for p in sig.parameters.values():
392 if p.kind is kind:
393 return p
394 return None
395
396
397def define_function_signature(name, docstring, signature):
398 """A decorator which sets the name, signature and docstring of the function
399 passed into it."""
400 if name == "<lambda>":
401 name = "_lambda_"
402 check_valid_identifier(name)
403 for a in signature.parameters:
404 check_valid_identifier(a)
405
406 used_names = {*signature.parameters, name}
407
408 newsig = signature.replace(
409 parameters=[
410 p if p.default is signature.empty else p.replace(default=not_set)
411 for p in (
412 p.replace(annotation=signature.empty)
413 for p in signature.parameters.values()
414 )
415 ],
416 return_annotation=signature.empty,
417 )
418
419 pos_args = [
420 p
421 for p in signature.parameters.values()
422 if p.kind.name.startswith("POSITIONAL_")
423 ]
424
425 def accept(f):
426 fsig = inspect.signature(f, follow_wrapped=False)
427 must_pass_as_kwargs = []
428 invocation_parts = []
429 for p in pos_args:
430 if p.name not in fsig.parameters and get_varargs(fsig) is None:
431 must_pass_as_kwargs.append(p.name)
432 else:
433 invocation_parts.append(p.name)
434 if get_varargs(signature) is not None:
435 invocation_parts.append("*" + get_varargs(signature).name)
436 for k in must_pass_as_kwargs:
437 invocation_parts.append(f"{k}={k}")
438 for p in signature.parameters.values():
439 if p.kind is p.KEYWORD_ONLY:
440 invocation_parts.append(f"{p.name}={p.name}")
441 varkw = get_varargs(signature, kind=Parameter.VAR_KEYWORD)
442 if varkw:
443 invocation_parts.append("**" + varkw.name)
444
445 candidate_names = ["f"] + [f"f_{i}" for i in range(1, len(used_names) + 4)]
446 free_names = [n for n in candidate_names if n not in used_names]
447 funcname, gen, val = free_names[:3]
448
449 invocation = f"{funcname}({', '.join(invocation_parts)})"
450 # Preserve the kind of the wrapped function, so that e.g. proxies for
451 # async or generator functions are themselves async or generators -
452 # closing the proxy also closes the underlying (async) generator.
453 if inspect.iscoroutinefunction(f):
454 def_prefix, body = "async ", f"return await {invocation}"
455 elif inspect.isasyncgenfunction(f):
456 def_prefix = "async "
457 body = (
458 f"async with aclosing({invocation}) as {gen}:\n"
459 f" async for {val} in {gen}:\n"
460 f" yield {val}"
461 )
462 elif inspect.isgeneratorfunction(f):
463 def_prefix = ""
464 body = (
465 f"with closing({invocation}) as {gen}:\n"
466 f" return (yield from {gen})"
467 )
468 else:
469 def_prefix, body = "", f"return {invocation}"
470
471 source = COPY_SIGNATURE_SCRIPT.format(
472 name=name,
473 funcname=funcname,
474 signature=str(newsig),
475 def_prefix=def_prefix,
476 body=body,
477 )
478 result = source_exec_as_module(source).accept(f)
479 result.__doc__ = docstring
480 result.__defaults__ = tuple(
481 p.default
482 for p in signature.parameters.values()
483 if p.default is not signature.empty and "POSITIONAL" in p.kind.name
484 )
485 kwdefaults = {
486 p.name: p.default
487 for p in signature.parameters.values()
488 if p.default is not signature.empty and p.kind is p.KEYWORD_ONLY
489 }
490 if kwdefaults:
491 result.__kwdefaults__ = kwdefaults
492 annotations = {
493 p.name: p.annotation
494 for p in signature.parameters.values()
495 if p.annotation is not signature.empty
496 }
497 if signature.return_annotation is not signature.empty:
498 annotations["return"] = signature.return_annotation
499 if annotations:
500 result.__annotations__ = annotations
501 return result
502
503 return accept
504
505
506def impersonate(target):
507 """Decorator to update the attributes of a function so that to external
508 introspectors it will appear to be the target function.
509
510 Note that this updates the function in place, it doesn't return a
511 new one.
512 """
513
514 def accept(f):
515 # Lie shamelessly about where this code comes from, to hide the hypothesis
516 # internals from pytest, ipython, and other runtime introspection.
517 f.__code__ = f.__code__.replace(
518 co_filename=target.__code__.co_filename,
519 co_firstlineno=target.__code__.co_firstlineno,
520 )
521 f.__name__ = target.__name__
522 f.__module__ = target.__module__
523 f.__doc__ = target.__doc__
524 f.__globals__["__hypothesistracebackhide__"] = True
525 # But leave an breadcrumb for _describe_lambda to follow, it's
526 # just confused by the lies above
527 f.__wrapped_target = target
528 return f
529
530 return accept
531
532
533def proxies(target: T) -> Callable[[Callable], T]:
534 replace_sig = define_function_signature(
535 target.__name__.replace("<lambda>", "_lambda_"), # type: ignore
536 target.__doc__,
537 get_signature(target, follow_wrapped=False),
538 )
539
540 def accept(proxy):
541 return impersonate(target)(wraps(target)(replace_sig(proxy)))
542
543 return accept
544
545
546def is_identity_function(f: Callable) -> bool:
547 try:
548 code = f.__code__
549 except AttributeError:
550 try:
551 f = f.__call__ # type: ignore
552 code = f.__code__
553 except AttributeError:
554 return False
555
556 # We only accept a single unbound argument. While it would be possible to
557 # accept extra defaulted arguments, it would be pointless as they couldn't
558 # be referenced at all in the code object (or the co_code check would fail).
559 bound_args = int(inspect.ismethod(f))
560 if code.co_argcount != bound_args + 1 or code.co_kwonlyargcount > 0:
561 return False
562
563 # We know that f accepts a single positional argument, now check that its
564 # code object is simply "return first unbound argument".
565 template = (lambda self, x: x) if bound_args else (lambda x: x)
566 try:
567 return code.co_code == template.__code__.co_code
568 except AttributeError: # pragma: no cover # pypy only
569 # In PyPy, some builtin functions have a code object ('builtin-code')
570 # lacking co_code, perhaps because they are native-compiled and don't have
571 # a corresponding bytecode. Regardless, since Python doesn't have any
572 # builtin identity function it seems safe to say that this one isn't
573 return False