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

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

353 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 linecache 

18import os 

19import re 

20import sys 

21import textwrap 

22import types 

23import warnings 

24from collections.abc import MutableMapping, Sequence 

25from functools import partial, wraps 

26from inspect import Parameter, Signature 

27from io import StringIO 

28from keyword import iskeyword 

29from random import _inst as global_random_instance 

30from tokenize import COMMENT, detect_encoding, generate_tokens, untokenize 

31from types import ModuleType 

32from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union 

33from unittest.mock import _patch as PatchType 

34from weakref import WeakKeyDictionary 

35 

36from hypothesis.errors import HypothesisWarning 

37from hypothesis.internal.compat import EllipsisType, is_typed_named_tuple 

38from hypothesis.utils.conventions import not_set 

39from hypothesis.vendor.pretty import pretty 

40 

41if TYPE_CHECKING: 

42 from hypothesis.strategies._internal.strategies import SearchStrategy 

43 

44T = TypeVar("T") 

45 

46READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True" 

47LAMBDA_SOURCE_CACHE: MutableMapping[Callable, str] = WeakKeyDictionary() 

48 

49 

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

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

52 

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

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

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

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

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

58 # looking for types. 

59 return hasattr(obj, "hypothesis_internal_is_this_a_mock_check") 

60 

61 

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

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

64 

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

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

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

68 versions (e.g. ast.Constant) 

69 """ 

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

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

72 try: 

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

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

75 except Exception: 

76 pass 

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

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

79 try: 

80 src = untokenize( 

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

82 ) 

83 except Exception: 

84 pass 

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

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

87 

88 

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

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

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

92 minor changes to the function. 

93 

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

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

96 """ 

97 hasher = hashlib.sha384() 

98 try: 

99 src = inspect.getsource(function) 

100 except (OSError, TypeError): 

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

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

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

104 # with class-dependent behaviour. 

105 try: 

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

107 except AttributeError: 

108 pass 

109 else: 

110 hasher.update(_clean_source(src)) 

111 try: 

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

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

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

115 except Exception: 

116 pass 

117 try: 

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

119 hasher.update(function._hypothesis_internal_add_digest) 

120 except AttributeError: 

121 pass 

122 return hasher.digest() 

123 

124 

125def check_signature(sig: Signature) -> None: 

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

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

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

129 raise ValueError( 

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

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

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

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

134 ) 

135 

136 

137def get_signature( 

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

139) -> Signature: 

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

141 # behaviour of getfullargspec instead of reporting unusable arguments. 

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

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

144 return Signature( 

145 [ 

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

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

148 ] 

149 ) 

150 

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

152 # This special case covers unusual codegen like Pydantic models 

153 sig = target.__signature__ 

154 check_signature(sig) 

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

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

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

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

159 if ( 

160 selfy.name == "self" 

161 and selfy.default is Parameter.empty 

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

163 ): 

164 return sig.replace( 

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

166 ) 

167 return sig 

168 # eval_str is only supported by Python 3.10 and newer 

169 if sys.version_info[:2] >= (3, 10): 

170 sig = inspect.signature( 

171 target, follow_wrapped=follow_wrapped, eval_str=eval_str 

172 ) 

173 else: 

174 sig = inspect.signature( 

175 target, follow_wrapped=follow_wrapped 

176 ) # pragma: no cover 

177 check_signature(sig) 

178 return sig 

179 

180 

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

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

183 Parameter.POSITIONAL_OR_KEYWORD, 

184 Parameter.KEYWORD_ONLY, 

185 ) 

186 

187 

188def required_args( 

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

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

191 kwargs: Optional[dict[str, Union["SearchStrategy[Any]", EllipsisType]]] = None, 

192) -> set[str]: 

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

194 in args or kwargs. 

195 

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

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

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

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

200 """ 

201 kwargs = {} if kwargs is None else kwargs 

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

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

204 provided = set(kwargs) | set(target._fields[: len(args)]) 

205 return set(target._fields) - provided 

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

207 try: 

208 sig = get_signature(target) 

209 except (ValueError, TypeError): 

210 return set() 

211 return { 

212 name 

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

214 if arg_is_required(param) and name not in kwargs 

215 } 

216 

217 

218def convert_keyword_arguments( 

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

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

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

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

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

224 """ 

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

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

227 return bound.args, bound.kwargs 

228 

229 

230def convert_positional_arguments( 

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

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

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

234 been moved to kwargs. 

235 

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

237 """ 

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

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

240 new_args = [] 

241 new_kwargs = dict(bound.arguments) 

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

243 if p.name in new_kwargs: 

244 if p.kind is p.POSITIONAL_ONLY: 

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

246 elif p.kind is p.VAR_POSITIONAL: 

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

248 elif p.kind is p.VAR_KEYWORD: 

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

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

251 return tuple(new_args), new_kwargs 

252 

253 

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

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

256 for node in args.posonlyargs: 

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

258 for node in args.args: 

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

260 if args.vararg is not None: 

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

262 for node in args.kwonlyargs: 

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

264 if args.kwarg is not None: 

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

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

267 

268 

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

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

271 try: 

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

273 except Exception: 

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

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

276 return any( 

277 isinstance(node, ast.Name) 

278 and node.id == name 

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

280 for node in ast.walk(tree) 

281 ) 

282 

283 

284def extract_all_lambdas(tree, matching_signature): 

285 lambdas = [] 

286 

287 class Visitor(ast.NodeVisitor): 

288 def visit_Lambda(self, node): 

289 if ast_arguments_matches_signature(node.args, matching_signature): 

290 lambdas.append(node) 

291 

292 Visitor().visit(tree) 

293 

294 return lambdas 

295 

296 

297LINE_CONTINUATION = re.compile(r"\\\n") 

298WHITESPACE = re.compile(r"\s+") 

299PROBABLY_A_COMMENT = re.compile("""#[^'"]*$""") 

300SPACE_FOLLOWS_OPEN_BRACKET = re.compile(r"\( ") 

301SPACE_PRECEDES_CLOSE_BRACKET = re.compile(r" \)") 

302 

303 

304def _extract_lambda_source(f): 

305 """Extracts a single lambda expression from the string source. Returns a 

306 string indicating an unknown body if it gets confused in any way. 

307 

308 This is not a good function and I am sorry for it. Forgive me my 

309 sins, oh lord 

310 """ 

311 # You might be wondering how a lambda can have a return-type annotation? 

312 # The answer is that we add this at runtime, in new_given_signature(), 

313 # and we do support strange choices as applying @given() to a lambda. 

314 sig = inspect.signature(f) 

315 assert sig.return_annotation in (Parameter.empty, None), sig 

316 

317 # Using pytest-xdist on Python 3.13, there's an entry in the linecache for 

318 # file "<string>", which then returns nonsense to getsource. Discard it. 

319 linecache.cache.pop("<string>", None) 

320 

321 if sig.parameters: 

322 if_confused = f"lambda {str(sig)[1:-1]}: <unknown>" 

323 else: 

324 if_confused = "lambda: <unknown>" 

325 try: 

326 source = inspect.getsource(f) 

327 except OSError: 

328 return if_confused 

329 

330 source = LINE_CONTINUATION.sub(" ", source) 

331 source = WHITESPACE.sub(" ", source) 

332 source = source.strip() 

333 if "lambda" not in source: # pragma: no cover 

334 # If a user starts a hypothesis process, then edits their code, the lines 

335 # in the parsed source code might not match the live __code__ objects. 

336 # 

337 # (and on sys.platform == "emscripten", this can happen regardless 

338 # due to a pyodide bug in inspect.getsource()). 

339 return if_confused 

340 

341 tree = None 

342 

343 try: 

344 tree = ast.parse(source) 

345 except SyntaxError: 

346 for i in range(len(source) - 1, len("lambda"), -1): 

347 prefix = source[:i] 

348 if "lambda" not in prefix: 

349 break 

350 try: 

351 tree = ast.parse(prefix) 

352 source = prefix 

353 break 

354 except SyntaxError: 

355 continue 

356 if tree is None and source.startswith(("@", ".")): 

357 # This will always eventually find a valid expression because the 

358 # decorator or chained operator must be a valid Python function call, 

359 # so will eventually be syntactically valid and break out of the loop. 

360 # Thus, this loop can never terminate normally. 

361 for i in range(len(source) + 1): 

362 p = source[1:i] 

363 if "lambda" in p: 

364 try: 

365 tree = ast.parse(p) 

366 source = p 

367 break 

368 except SyntaxError: 

369 pass 

370 else: 

371 raise NotImplementedError("expected to be unreachable") 

372 

373 if tree is None: 

374 return if_confused 

375 

376 aligned_lambdas = extract_all_lambdas(tree, matching_signature=sig) 

377 if len(aligned_lambdas) != 1: 

378 return if_confused 

379 lambda_ast = aligned_lambdas[0] 

380 assert lambda_ast.lineno == 1 

381 

382 # If the source code contains Unicode characters, the bytes of the original 

383 # file don't line up with the string indexes, and `col_offset` doesn't match 

384 # the string we're using. We need to convert the source code into bytes 

385 # before slicing. 

386 # 

387 # Under the hood, the inspect module is using `tokenize.detect_encoding` to 

388 # detect the encoding of the original source file. We'll use the same 

389 # approach to get the source code as bytes. 

390 # 

391 # See https://github.com/HypothesisWorks/hypothesis/issues/1700 for an 

392 # example of what happens if you don't correct for this. 

393 # 

394 # Note: if the code doesn't come from a file (but, for example, a doctest), 

395 # `getsourcefile` will return `None` and the `open()` call will fail with 

396 # an OSError. Or if `f` is a built-in function, in which case we get a 

397 # TypeError. In both cases, fall back to splitting the Unicode string. 

398 # It's not perfect, but it's the best we can do. 

399 try: 

400 with open(inspect.getsourcefile(f), "rb") as src_f: 

401 encoding, _ = detect_encoding(src_f.readline) 

402 

403 source_bytes = source.encode(encoding) 

404 source_bytes = source_bytes[lambda_ast.col_offset :].strip() 

405 source = source_bytes.decode(encoding) 

406 except (OSError, TypeError): 

407 source = source[lambda_ast.col_offset :].strip() 

408 

409 # This ValueError can be thrown in Python 3 if: 

410 # 

411 # - There's a Unicode character in the line before the Lambda, and 

412 # - For some reason we can't detect the source encoding of the file 

413 # 

414 # because slicing on `lambda_ast.col_offset` will account for bytes, but 

415 # the slice will be on Unicode characters. 

416 # 

417 # In practice this seems relatively rare, so we just give up rather than 

418 # trying to recover. 

419 try: 

420 source = source[source.index("lambda") :] 

421 except ValueError: 

422 return if_confused 

423 

424 for i in range(len(source), len("lambda"), -1): # pragma: no branch 

425 try: 

426 parsed = ast.parse(source[:i]) 

427 assert len(parsed.body) == 1 

428 assert parsed.body 

429 if isinstance(parsed.body[0].value, ast.Lambda): 

430 source = source[:i] 

431 break 

432 except SyntaxError: 

433 pass 

434 lines = source.split("\n") 

435 lines = [PROBABLY_A_COMMENT.sub("", l) for l in lines] 

436 source = "\n".join(lines) 

437 

438 source = WHITESPACE.sub(" ", source) 

439 source = SPACE_FOLLOWS_OPEN_BRACKET.sub("(", source) 

440 source = SPACE_PRECEDES_CLOSE_BRACKET.sub(")", source) 

441 return source.strip() 

442 

443 

444def extract_lambda_source(f): 

445 try: 

446 return LAMBDA_SOURCE_CACHE[f] 

447 except KeyError: 

448 pass 

449 

450 source = _extract_lambda_source(f) 

451 LAMBDA_SOURCE_CACHE[f] = source 

452 return source 

453 

454 

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

456 if isinstance(f, partial): 

457 return pretty(f) 

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

459 return repr(f) 

460 name = f.__name__ # type: ignore 

461 if name == "<lambda>": 

462 return extract_lambda_source(f) 

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

464 self = f.__self__ 

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

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

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

468 if self is global_random_instance: 

469 return f"random.{name}" 

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

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

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

473 return f"dict.{name}" 

474 return name 

475 

476 

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

478 if inspect.isfunction(v): 

479 return get_pretty_function_description(v) 

480 elif isinstance(v, type): 

481 return v.__name__ 

482 else: 

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

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

485 

486 

487def repr_call( 

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

489) -> str: 

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

491 if reorder: 

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

493 

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

495 

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

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

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

499 if kwargs: 

500 for a in sorted(kwargs): 

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

502 

503 rep = nicerepr(f) 

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

505 rep = f"({rep})" 

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

507 if repr_len > 30000: 

508 warnings.warn( 

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

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

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

512 HypothesisWarning, 

513 stacklevel=2, 

514 ) 

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

516 

517 

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

519 if not identifier.isidentifier(): 

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

521 

522 

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

524 

525 

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

527 try: 

528 return eval_cache[source] 

529 except KeyError: 

530 pass 

531 

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

533 result = ModuleType("hypothesis_temporary_module_" + hexdigest) 

534 assert isinstance(source, str) 

535 exec(source, result.__dict__) 

536 eval_cache[source] = result 

537 return result 

538 

539 

540COPY_SIGNATURE_SCRIPT = """ 

541from hypothesis.utils.conventions import not_set 

542 

543def accept({funcname}): 

544 def {name}{signature}: 

545 return {funcname}({invocation}) 

546 return {name} 

547""".lstrip() 

548 

549 

550def get_varargs( 

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

552) -> Optional[Parameter]: 

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

554 if p.kind is kind: 

555 return p 

556 return None 

557 

558 

559def define_function_signature(name, docstring, signature): 

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

561 passed into it.""" 

562 if name == "<lambda>": 

563 name = "_lambda_" 

564 check_valid_identifier(name) 

565 for a in signature.parameters: 

566 check_valid_identifier(a) 

567 

568 used_names = {*signature.parameters, name} 

569 

570 newsig = signature.replace( 

571 parameters=[ 

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

573 for p in ( 

574 p.replace(annotation=signature.empty) 

575 for p in signature.parameters.values() 

576 ) 

577 ], 

578 return_annotation=signature.empty, 

579 ) 

580 

581 pos_args = [ 

582 p 

583 for p in signature.parameters.values() 

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

585 ] 

586 

587 def accept(f): 

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

589 must_pass_as_kwargs = [] 

590 invocation_parts = [] 

591 for p in pos_args: 

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

593 must_pass_as_kwargs.append(p.name) 

594 else: 

595 invocation_parts.append(p.name) 

596 if get_varargs(signature) is not None: 

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

598 for k in must_pass_as_kwargs: 

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

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

601 if p.kind is p.KEYWORD_ONLY: 

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

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

604 if varkw: 

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

606 

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

608 

609 for funcname in candidate_names: # pragma: no branch 

610 if funcname not in used_names: 

611 break 

612 

613 source = COPY_SIGNATURE_SCRIPT.format( 

614 name=name, 

615 funcname=funcname, 

616 signature=str(newsig), 

617 invocation=", ".join(invocation_parts), 

618 ) 

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

620 result.__doc__ = docstring 

621 result.__defaults__ = tuple( 

622 p.default 

623 for p in signature.parameters.values() 

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

625 ) 

626 kwdefaults = { 

627 p.name: p.default 

628 for p in signature.parameters.values() 

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

630 } 

631 if kwdefaults: 

632 result.__kwdefaults__ = kwdefaults 

633 annotations = { 

634 p.name: p.annotation 

635 for p in signature.parameters.values() 

636 if p.annotation is not signature.empty 

637 } 

638 if signature.return_annotation is not signature.empty: 

639 annotations["return"] = signature.return_annotation 

640 if annotations: 

641 result.__annotations__ = annotations 

642 return result 

643 

644 return accept 

645 

646 

647def impersonate(target): 

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

649 introspectors it will appear to be the target function. 

650 

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

652 new one. 

653 """ 

654 

655 def accept(f): 

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

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

658 f.__code__ = f.__code__.replace( 

659 co_filename=target.__code__.co_filename, 

660 co_firstlineno=target.__code__.co_firstlineno, 

661 ) 

662 f.__name__ = target.__name__ 

663 f.__module__ = target.__module__ 

664 f.__doc__ = target.__doc__ 

665 f.__globals__["__hypothesistracebackhide__"] = True 

666 return f 

667 

668 return accept 

669 

670 

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

672 replace_sig = define_function_signature( 

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

674 target.__doc__, 

675 get_signature(target, follow_wrapped=False), 

676 ) 

677 

678 def accept(proxy): 

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

680 

681 return accept 

682 

683 

684def is_identity_function(f: object) -> bool: 

685 # TODO: pattern-match the AST to handle `def ...` identity functions too 

686 return bool(re.fullmatch(r"lambda (\w+): \1", get_pretty_function_description(f)))