Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/hypothesis/internal/reflection.py: 70%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

279 statements  

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