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

280 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 textwrap 

19import types 

20import warnings 

21from collections.abc import Callable, Sequence 

22from functools import partial, wraps 

23from inspect import Parameter, Signature 

24from io import StringIO 

25from keyword import iskeyword 

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 

30from unittest.mock import _patch as PatchType 

31 

32from hypothesis.errors import HypothesisWarning 

33from hypothesis.internal import lambda_sources 

34from hypothesis.internal.compat import is_typed_named_tuple 

35from hypothesis.utils.conventions import not_set 

36from hypothesis.vendor.pretty import pretty 

37 

38if TYPE_CHECKING: 

39 from hypothesis.strategies._internal.strategies import SearchStrategy 

40 

41T = TypeVar("T") 

42 

43 

44def is_mock(obj: object) -> bool: 

45 """Determine if the given argument is a mock type.""" 

46 

47 # We want to be able to detect these when dealing with various test 

48 # args. As they are sneaky and can look like almost anything else, 

49 # we'll check this by looking for an attribute with a name that it's really 

50 # unlikely to implement accidentally, and that anyone who implements it 

51 # deliberately should know what they're doing. This is more robust than 

52 # looking for types. 

53 return hasattr(obj, "hypothesis_internal_is_this_a_mock_check") 

54 

55 

56def _clean_source(src: str) -> bytes: 

57 """Return the source code as bytes, without decorators or comments. 

58 

59 Because this is part of our database key, we reduce the cache invalidation 

60 rate by ignoring decorators, comments, trailing whitespace, and empty lines. 

61 We can't just use the (dumped) AST directly because it changes between Python 

62 versions (e.g. ast.Constant) 

63 """ 

64 # Get the (one-indexed) line number of the function definition, and drop preceding 

65 # lines - i.e. any decorators, so that adding `@example()`s keeps the same key. 

66 try: 

67 funcdef = ast.parse(src).body[0] 

68 src = "".join(src.splitlines(keepends=True)[funcdef.lineno - 1 :]) 

69 except Exception: 

70 pass 

71 # Remove blank lines and use the tokenize module to strip out comments, 

72 # so that those can be changed without changing the database key. 

73 try: 

74 src = untokenize( 

75 t for t in generate_tokens(StringIO(src).readline) if t.type != COMMENT 

76 ) 

77 except Exception: 

78 pass 

79 # Finally, remove any trailing whitespace and empty lines as a last cleanup. 

80 return "\n".join(x.rstrip() for x in src.splitlines() if x.rstrip()).encode() 

81 

82 

83def function_digest(function: Any) -> bytes: 

84 """Returns a string that is stable across multiple invocations across 

85 multiple processes and is prone to changing significantly in response to 

86 minor changes to the function. 

87 

88 No guarantee of uniqueness though it usually will be. Digest collisions 

89 lead to unfortunate but not fatal problems during database replay. 

90 """ 

91 hasher = hashlib.sha384() 

92 try: 

93 src = inspect.getsource(function) 

94 except (OSError, TypeError): 

95 # If we can't actually get the source code, try for the name as a fallback. 

96 # NOTE: We might want to change this to always adding function.__qualname__, 

97 # to differentiate f.x. two classes having the same function implementation 

98 # with class-dependent behaviour. 

99 try: 

100 hasher.update(function.__name__.encode()) 

101 except AttributeError: 

102 pass 

103 else: 

104 hasher.update(_clean_source(src)) 

105 try: 

106 # This is additional to the source code because it can include the effects 

107 # of decorators, or of post-hoc assignment to the .__signature__ attribute. 

108 hasher.update(repr(get_signature(function)).encode()) 

109 except Exception: 

110 pass 

111 try: 

112 # We set this in order to distinguish e.g. @pytest.mark.parametrize cases. 

113 hasher.update(function._hypothesis_internal_add_digest) 

114 except AttributeError: 

115 pass 

116 return hasher.digest() 

117 

118 

119def check_signature(sig: Signature) -> None: # pragma: no cover # 3.10 only 

120 # Backport from Python 3.11; see https://github.com/python/cpython/pull/92065 

121 for p in sig.parameters.values(): 

122 if iskeyword(p.name) and p.kind is not p.POSITIONAL_ONLY: 

123 raise ValueError( 

124 f"Signature {sig!r} contains a parameter named {p.name!r}, " 

125 f"but this is a SyntaxError because `{p.name}` is a keyword. " 

126 "You, or a library you use, must have manually created an " 

127 "invalid signature - this will be an error in Python 3.11+" 

128 ) 

129 

130 

131def get_signature( 

132 target: Any, *, follow_wrapped: bool = True, eval_str: bool = False 

133) -> Signature: 

134 # Special case for use of `@unittest.mock.patch` decorator, mimicking the 

135 # behaviour of getfullargspec instead of reporting unusable arguments. 

136 patches = getattr(target, "patchings", None) 

137 if isinstance(patches, list) and all(isinstance(p, PatchType) for p in patches): 

138 return Signature( 

139 [ 

140 Parameter("args", Parameter.VAR_POSITIONAL), 

141 Parameter("keywargs", Parameter.VAR_KEYWORD), 

142 ] 

143 ) 

144 

145 if isinstance(getattr(target, "__signature__", None), Signature): 

146 # This special case covers unusual codegen like Pydantic models 

147 sig = target.__signature__ 

148 check_signature(sig) 

149 # And *this* much more complicated block ignores the `self` argument 

150 # if that's been (incorrectly) included in the custom signature. 

151 if sig.parameters and (inspect.isclass(target) or inspect.ismethod(target)): 

152 selfy = next(iter(sig.parameters.values())) 

153 if ( 

154 selfy.name == "self" 

155 and selfy.default is Parameter.empty 

156 and selfy.kind.name.startswith("POSITIONAL_") 

157 ): 

158 return sig.replace( 

159 parameters=[v for k, v in sig.parameters.items() if k != "self"] 

160 ) 

161 return sig 

162 sig = inspect.signature(target, follow_wrapped=follow_wrapped, eval_str=eval_str) 

163 check_signature(sig) 

164 return sig 

165 

166 

167def arg_is_required(param: Parameter) -> bool: 

168 return param.default is Parameter.empty and param.kind in ( 

169 Parameter.POSITIONAL_OR_KEYWORD, 

170 Parameter.KEYWORD_ONLY, 

171 ) 

172 

173 

174def required_args( 

175 target: Callable[..., Any], 

176 args: tuple["SearchStrategy[Any]", ...] = (), 

177 kwargs: dict[str, Union["SearchStrategy[Any]", EllipsisType]] | None = None, 

178) -> set[str]: 

179 """Return a set of names of required args to target that were not supplied 

180 in args or kwargs. 

181 

182 This is used in builds() to determine which arguments to attempt to 

183 fill from type hints. target may be any callable (including classes 

184 and bound methods). args and kwargs should be as they are passed to 

185 builds() - that is, a tuple of values and a dict of names: values. 

186 """ 

187 kwargs = {} if kwargs is None else kwargs 

188 # We start with a workaround for NamedTuples, which don't have nice inits 

189 if inspect.isclass(target) and is_typed_named_tuple(target): 

190 fields = target._fields # type: ignore 

191 provided = set(kwargs) | set(fields[: len(args)]) 

192 return set(fields) - provided 

193 # Then we try to do the right thing with inspect.signature 

194 try: 

195 sig = get_signature(target) 

196 except (ValueError, TypeError): 

197 return set() 

198 return { 

199 name 

200 for name, param in list(sig.parameters.items())[len(args) :] 

201 if arg_is_required(param) and name not in kwargs 

202 } 

203 

204 

205def convert_keyword_arguments( 

206 function: Any, args: Sequence[object], kwargs: dict[str, object] 

207) -> tuple[tuple[object, ...], dict[str, object]]: 

208 """Returns a pair of a tuple and a dictionary which would be equivalent 

209 passed as positional and keyword args to the function. Unless function has 

210 kwonlyargs or **kwargs the dictionary will always be empty. 

211 """ 

212 sig = inspect.signature(function, follow_wrapped=False) 

213 bound = sig.bind(*args, **kwargs) 

214 return bound.args, bound.kwargs 

215 

216 

217def convert_positional_arguments( 

218 function: Any, args: Sequence[object], kwargs: dict[str, object] 

219) -> tuple[tuple[object, ...], dict[str, object]]: 

220 """Return a tuple (new_args, new_kwargs) where all possible arguments have 

221 been moved to kwargs. 

222 

223 new_args will only be non-empty if function has pos-only args or *args. 

224 """ 

225 sig = inspect.signature(function, follow_wrapped=False) 

226 bound = sig.bind(*args, **kwargs) 

227 new_args = [] 

228 new_kwargs = dict(bound.arguments) 

229 for p in sig.parameters.values(): 

230 if p.name in new_kwargs: 

231 if p.kind is p.POSITIONAL_ONLY: 

232 new_args.append(new_kwargs.pop(p.name)) 

233 elif p.kind is p.VAR_POSITIONAL: 

234 new_args.extend(new_kwargs.pop(p.name)) 

235 elif p.kind is p.VAR_KEYWORD: 

236 assert set(new_kwargs[p.name]).isdisjoint(set(new_kwargs) - {p.name}) 

237 new_kwargs.update(new_kwargs.pop(p.name)) 

238 return tuple(new_args), new_kwargs 

239 

240 

241def ast_arguments_matches_signature(args: ast.arguments, sig: Signature) -> bool: 

242 expected: list[tuple[str, int]] = [] 

243 for node in args.posonlyargs: 

244 expected.append((node.arg, Parameter.POSITIONAL_ONLY)) 

245 for node in args.args: 

246 expected.append((node.arg, Parameter.POSITIONAL_OR_KEYWORD)) 

247 if args.vararg is not None: 

248 expected.append((args.vararg.arg, Parameter.VAR_POSITIONAL)) 

249 for node in args.kwonlyargs: 

250 expected.append((node.arg, Parameter.KEYWORD_ONLY)) 

251 if args.kwarg is not None: 

252 expected.append((args.kwarg.arg, Parameter.VAR_KEYWORD)) 

253 return expected == [(p.name, p.kind) for p in sig.parameters.values()] 

254 

255 

256def is_first_param_referenced_in_function(f: Any) -> bool: 

257 """Is the given name referenced within f?""" 

258 try: 

259 tree = ast.parse(textwrap.dedent(inspect.getsource(f))) 

260 except Exception: 

261 return True # Assume it's OK unless we know otherwise 

262 name = next(iter(get_signature(f).parameters)) 

263 return any( 

264 isinstance(node, ast.Name) 

265 and node.id == name 

266 and isinstance(node.ctx, ast.Load) 

267 for node in ast.walk(tree) 

268 ) 

269 

270 

271def function_location(f: object) -> str | None: 

272 """Return a ``filename:lineno`` string for where ``f`` was defined, if known.""" 

273 try: 

274 code = f.__code__ # type: ignore[attr-defined] 

275 return f"{code.co_filename}:{code.co_firstlineno}" 

276 except AttributeError: 

277 return None 

278 

279 

280def get_pretty_function_description(f: object) -> str: 

281 # Anything which knows how to pretty-print itself knows better than we do - 

282 # e.g. the constant functions from st.functions(), which would otherwise 

283 # borrow the name of the function they imitate. We skip classes, whose 

284 # instance method would be called with the printer as `self`, and require 

285 # a callable so that pretty() can't bounce straight back to us. 

286 if not isinstance(f, type) and callable(getattr(f, "_repr_pretty_", None)): 

287 return pretty(f) 

288 if isinstance(f, partial): 

289 return pretty(f) 

290 if not hasattr(f, "__name__"): 

291 return repr(f) 

292 name = f.__name__ 

293 if name == "<lambda>": 

294 return lambda_sources.lambda_description(f) 

295 elif isinstance(f, (types.MethodType, types.BuiltinMethodType)): 

296 self = f.__self__ 

297 # Some objects, like `builtins.abs` are of BuiltinMethodType but have 

298 # their module as __self__. This might include c-extensions generally? 

299 if not (self is None or inspect.isclass(self) or inspect.ismodule(self)): 

300 if self is global_random_instance: 

301 return f"random.{name}" 

302 return f"{self!r}.{name}" 

303 elif isinstance(name, str) and getattr(dict, name, object()) is f: 

304 # special case for keys/values views in from_type() / ghostwriter output 

305 return f"dict.{name}" 

306 return name 

307 

308 

309def nicerepr(v: Any) -> str: 

310 if inspect.isfunction(v): 

311 return get_pretty_function_description(v) 

312 elif isinstance(v, type): 

313 return v.__name__ 

314 else: 

315 # With TypeVar T, show List[T] instead of TypeError on List[~T] 

316 return re.sub(r"(\[)~([A-Z][a-z]*\])", r"\g<1>\g<2>", pretty(v)) 

317 

318 

319def repr_call( 

320 f: Any, args: Sequence[object], kwargs: dict[str, object], *, reorder: bool = True 

321) -> str: 

322 # Note: for multi-line pretty-printing, see RepresentationPrinter.repr_call() 

323 if reorder: 

324 args, kwargs = convert_positional_arguments(f, args, kwargs) 

325 

326 bits = [nicerepr(x) for x in args] 

327 

328 for p in get_signature(f).parameters.values(): 

329 if p.name in kwargs and not p.kind.name.startswith("VAR_"): 

330 bits.append(f"{p.name}={nicerepr(kwargs.pop(p.name))}") 

331 if kwargs: 

332 for a in sorted(kwargs): 

333 bits.append(f"{a}={nicerepr(kwargs[a])}") 

334 

335 rep = nicerepr(f) 

336 if rep.startswith("lambda") and ":" in rep: 

337 rep = f"({rep})" 

338 repr_len = len(rep) + sum(len(b) for b in bits) # approx 

339 if repr_len > 30000: 

340 warnings.warn( 

341 "Generating overly large repr. This is an expensive operation, and with " 

342 f"a length of {repr_len//1000} kB is unlikely to be useful. Use -Wignore " 

343 "to ignore the warning, or -Werror to get a traceback.", 

344 HypothesisWarning, 

345 stacklevel=2, 

346 ) 

347 return rep + "(" + ", ".join(bits) + ")" 

348 

349 

350def check_valid_identifier(identifier: str) -> None: 

351 if not identifier.isidentifier(): 

352 raise ValueError(f"{identifier!r} is not a valid python identifier") 

353 

354 

355eval_cache: dict[str, ModuleType] = {} 

356 

357 

358def source_exec_as_module(source: str) -> ModuleType: 

359 try: 

360 return eval_cache[source] 

361 except KeyError: 

362 pass 

363 

364 hexdigest = hashlib.sha384(source.encode()).hexdigest() 

365 result = ModuleType("hypothesis_temporary_module_" + hexdigest) 

366 # ModuleType() sets __spec__ = None. Later, we call @impersonate on functions defined 

367 # in the module, which gives it a real co_filename. Python traceback formatting then 

368 # calls internal linecache code which warns on the combination of "real on-disk file" 

369 # + "null __spec__". 

370 # 

371 # Sidestep this by deleting __spec__; we don't need it. 

372 del result.__spec__ 

373 assert isinstance(source, str) 

374 exec(source, result.__dict__) 

375 eval_cache[source] = result 

376 return result 

377 

378 

379COPY_SIGNATURE_SCRIPT = """ 

380from contextlib import aclosing, closing 

381 

382from hypothesis.utils.conventions import not_set 

383 

384def accept({funcname}): 

385 {def_prefix}def {name}{signature}: 

386 {body} 

387 return {name} 

388""".lstrip() 

389 

390 

391def get_varargs( 

392 sig: Signature, kind: int = Parameter.VAR_POSITIONAL 

393) -> Parameter | None: 

394 for p in sig.parameters.values(): 

395 if p.kind is kind: 

396 return p 

397 return None 

398 

399 

400def define_function_signature(name, docstring, signature): 

401 """A decorator which sets the name, signature and docstring of the function 

402 passed into it.""" 

403 if name == "<lambda>": 

404 name = "_lambda_" 

405 check_valid_identifier(name) 

406 for a in signature.parameters: 

407 check_valid_identifier(a) 

408 

409 used_names = {*signature.parameters, name} 

410 

411 newsig = signature.replace( 

412 parameters=[ 

413 p if p.default is signature.empty else p.replace(default=not_set) 

414 for p in ( 

415 p.replace(annotation=signature.empty) 

416 for p in signature.parameters.values() 

417 ) 

418 ], 

419 return_annotation=signature.empty, 

420 ) 

421 

422 pos_args = [ 

423 p 

424 for p in signature.parameters.values() 

425 if p.kind.name.startswith("POSITIONAL_") 

426 ] 

427 

428 def accept(f): 

429 fsig = inspect.signature(f, follow_wrapped=False) 

430 must_pass_as_kwargs = [] 

431 invocation_parts = [] 

432 for p in pos_args: 

433 if p.name not in fsig.parameters and get_varargs(fsig) is None: 

434 must_pass_as_kwargs.append(p.name) 

435 else: 

436 invocation_parts.append(p.name) 

437 if get_varargs(signature) is not None: 

438 invocation_parts.append("*" + get_varargs(signature).name) 

439 for k in must_pass_as_kwargs: 

440 invocation_parts.append(f"{k}={k}") 

441 for p in signature.parameters.values(): 

442 if p.kind is p.KEYWORD_ONLY: 

443 invocation_parts.append(f"{p.name}={p.name}") 

444 varkw = get_varargs(signature, kind=Parameter.VAR_KEYWORD) 

445 if varkw: 

446 invocation_parts.append("**" + varkw.name) 

447 

448 candidate_names = ["f"] + [f"f_{i}" for i in range(1, len(used_names) + 4)] 

449 free_names = [n for n in candidate_names if n not in used_names] 

450 funcname, gen, val = free_names[:3] 

451 

452 invocation = f"{funcname}({', '.join(invocation_parts)})" 

453 # Preserve the kind of the wrapped function, so that e.g. proxies for 

454 # async or generator functions are themselves async or generators - 

455 # closing the proxy also closes the underlying (async) generator. 

456 if inspect.iscoroutinefunction(f): 

457 def_prefix, body = "async ", f"return await {invocation}" 

458 elif inspect.isasyncgenfunction(f): 

459 def_prefix = "async " 

460 body = ( 

461 f"async with aclosing({invocation}) as {gen}:\n" 

462 f" async for {val} in {gen}:\n" 

463 f" yield {val}" 

464 ) 

465 elif inspect.isgeneratorfunction(f): 

466 def_prefix = "" 

467 body = ( 

468 f"with closing({invocation}) as {gen}:\n" 

469 f" return (yield from {gen})" 

470 ) 

471 else: 

472 def_prefix, body = "", f"return {invocation}" 

473 

474 source = COPY_SIGNATURE_SCRIPT.format( 

475 name=name, 

476 funcname=funcname, 

477 signature=str(newsig), 

478 def_prefix=def_prefix, 

479 body=body, 

480 ) 

481 result = source_exec_as_module(source).accept(f) 

482 result.__doc__ = docstring 

483 result.__defaults__ = tuple( 

484 p.default 

485 for p in signature.parameters.values() 

486 if p.default is not signature.empty and "POSITIONAL" in p.kind.name 

487 ) 

488 kwdefaults = { 

489 p.name: p.default 

490 for p in signature.parameters.values() 

491 if p.default is not signature.empty and p.kind is p.KEYWORD_ONLY 

492 } 

493 if kwdefaults: 

494 result.__kwdefaults__ = kwdefaults 

495 annotations = { 

496 p.name: p.annotation 

497 for p in signature.parameters.values() 

498 if p.annotation is not signature.empty 

499 } 

500 if signature.return_annotation is not signature.empty: 

501 annotations["return"] = signature.return_annotation 

502 if annotations: 

503 result.__annotations__ = annotations 

504 return result 

505 

506 return accept 

507 

508 

509def impersonate(target): 

510 """Decorator to update the attributes of a function so that to external 

511 introspectors it will appear to be the target function. 

512 

513 Note that this updates the function in place, it doesn't return a 

514 new one. 

515 """ 

516 

517 def accept(f): 

518 # Lie shamelessly about where this code comes from, to hide the hypothesis 

519 # internals from pytest, ipython, and other runtime introspection. 

520 f.__code__ = f.__code__.replace( 

521 co_filename=target.__code__.co_filename, 

522 co_firstlineno=target.__code__.co_firstlineno, 

523 ) 

524 f.__name__ = target.__name__ 

525 f.__module__ = target.__module__ 

526 f.__doc__ = target.__doc__ 

527 f.__globals__["__hypothesistracebackhide__"] = True 

528 # But leave an breadcrumb for _describe_lambda to follow, it's 

529 # just confused by the lies above 

530 f.__wrapped_target = target 

531 return f 

532 

533 return accept 

534 

535 

536def proxies(target: T) -> Callable[[Callable], T]: 

537 replace_sig = define_function_signature( 

538 target.__name__.replace("<lambda>", "_lambda_"), # type: ignore 

539 target.__doc__, 

540 get_signature(target, follow_wrapped=False), 

541 ) 

542 

543 def accept(proxy): 

544 return impersonate(target)(wraps(target)(replace_sig(proxy))) 

545 

546 return accept 

547 

548 

549def is_identity_function(f: Callable) -> bool: 

550 try: 

551 code = f.__code__ 

552 except AttributeError: 

553 try: 

554 f = f.__call__ # type: ignore 

555 code = f.__code__ 

556 except AttributeError: 

557 return False 

558 

559 # We only accept a single unbound argument. While it would be possible to 

560 # accept extra defaulted arguments, it would be pointless as they couldn't 

561 # be referenced at all in the code object (or the co_code check would fail). 

562 bound_args = int(inspect.ismethod(f)) 

563 if code.co_argcount != bound_args + 1 or code.co_kwonlyargcount > 0: 

564 return False 

565 

566 # We know that f accepts a single positional argument, now check that its 

567 # code object is simply "return first unbound argument". 

568 template = (lambda self, x: x) if bound_args else (lambda x: x) 

569 try: 

570 return code.co_code == template.__code__.co_code 

571 except AttributeError: # pragma: no cover # pypy only 

572 # In PyPy, some builtin functions have a code object ('builtin-code') 

573 # lacking co_code, perhaps because they are native-compiled and don't have 

574 # a corresponding bytecode. Regardless, since Python doesn't have any 

575 # builtin identity function it seems safe to say that this one isn't 

576 return False