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