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

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

362 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 functools import partial, wraps 

25from io import StringIO 

26from keyword import iskeyword 

27from random import _inst as global_random_instance 

28from tokenize import COMMENT, detect_encoding, generate_tokens, untokenize 

29from types import ModuleType 

30from typing import TYPE_CHECKING, Any, Callable, MutableMapping 

31from unittest.mock import _patch as PatchType 

32from weakref import WeakKeyDictionary 

33 

34from hypothesis.errors import HypothesisWarning 

35from hypothesis.internal.compat import PYPY, 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 T 

41 

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

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

44 

45 

46def is_mock(obj): 

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

48 

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

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

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

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

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

54 # looking for types. 

55 return hasattr(obj, "hypothesis_internal_is_this_a_mock_check") 

56 

57 

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

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

60 

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

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

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

64 versions (e.g. ast.Constant) 

65 """ 

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

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

68 try: 

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

70 if sys.version_info[:2] == (3, 8) and PYPY: 

71 # We can't get a line number of the (async) def here, so as a best-effort 

72 # approximation we'll use str.split instead and hope for the best. 

73 tag = "async def " if isinstance(funcdef, ast.AsyncFunctionDef) else "def " 

74 if tag in src: 

75 src = tag + src.split(tag, maxsplit=1)[1] 

76 else: 

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

78 except Exception: 

79 pass 

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

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

82 try: 

83 src = untokenize( 

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

85 ) 

86 except Exception: 

87 pass 

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

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

90 

91 

92def function_digest(function): 

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

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

95 minor changes to the function. 

96 

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

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

99 """ 

100 hasher = hashlib.sha384() 

101 try: 

102 src = inspect.getsource(function) 

103 except (OSError, TypeError): 

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

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

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

107 # with class-dependent behaviour. 

108 try: 

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

110 except AttributeError: 

111 pass 

112 else: 

113 hasher.update(_clean_source(src)) 

114 try: 

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

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

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

118 except Exception: 

119 pass 

120 try: 

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

122 hasher.update(function._hypothesis_internal_add_digest) 

123 except AttributeError: 

124 pass 

125 return hasher.digest() 

126 

127 

128def check_signature(sig: inspect.Signature) -> None: 

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

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

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

132 raise ValueError( 

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

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

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

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

137 ) 

138 

139 

140def get_signature( 

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

142) -> inspect.Signature: 

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

144 # behaviour of getfullargspec instead of reporting unusable arguments. 

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

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

147 P = inspect.Parameter 

148 return inspect.Signature( 

149 [P("args", P.VAR_POSITIONAL), P("keywargs", P.VAR_KEYWORD)] 

150 ) 

151 

152 if isinstance(getattr(target, "__signature__", None), inspect.Signature): 

153 # This special case covers unusual codegen like Pydantic models 

154 sig = target.__signature__ 

155 check_signature(sig) 

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

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

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

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

160 if ( 

161 selfy.name == "self" 

162 and selfy.default is inspect.Parameter.empty 

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

164 ): 

165 return sig.replace( 

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

167 ) 

168 return sig 

169 if sys.version_info[:2] <= (3, 8) and inspect.isclass(target): 

170 # Workaround for subclasses of typing.Generic on Python <= 3.8 

171 from hypothesis.strategies._internal.types import is_generic_type 

172 

173 if is_generic_type(target): 

174 sig = inspect.signature(target.__init__) 

175 check_signature(sig) 

176 return sig.replace( 

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

178 ) 

179 # eval_str is only supported by Python 3.10 and newer 

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

181 sig = inspect.signature( 

182 target, follow_wrapped=follow_wrapped, eval_str=eval_str 

183 ) 

184 else: 

185 sig = inspect.signature( 

186 target, follow_wrapped=follow_wrapped 

187 ) # pragma: no cover 

188 check_signature(sig) 

189 return sig 

190 

191 

192def arg_is_required(param): 

193 return param.default is inspect.Parameter.empty and param.kind in ( 

194 inspect.Parameter.POSITIONAL_OR_KEYWORD, 

195 inspect.Parameter.KEYWORD_ONLY, 

196 ) 

197 

198 

199def required_args(target, args=(), kwargs=()): 

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

201 in args or kwargs. 

202 

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

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

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

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

207 """ 

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

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

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

211 return set(target._fields) - provided 

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

213 try: 

214 sig = get_signature(target) 

215 except (ValueError, TypeError): 

216 return set() 

217 return { 

218 name 

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

220 if arg_is_required(param) and name not in kwargs 

221 } 

222 

223 

224def convert_keyword_arguments(function, args, kwargs): 

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

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

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

228 """ 

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

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

231 return bound.args, bound.kwargs 

232 

233 

234def convert_positional_arguments(function, args, kwargs): 

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

236 been moved to kwargs. 

237 

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

239 """ 

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

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

242 new_args = [] 

243 new_kwargs = dict(bound.arguments) 

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

245 if p.name in new_kwargs: 

246 if p.kind is p.POSITIONAL_ONLY: 

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

248 elif p.kind is p.VAR_POSITIONAL: 

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

250 elif p.kind is p.VAR_KEYWORD: 

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

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

253 return tuple(new_args), new_kwargs 

254 

255 

256def ast_arguments_matches_signature(args, sig): 

257 assert isinstance(args, ast.arguments) 

258 assert isinstance(sig, inspect.Signature) 

259 expected = [] 

260 for node in getattr(args, "posonlyargs", ()): # New in Python 3.8 

261 expected.append((node.arg, inspect.Parameter.POSITIONAL_ONLY)) 

262 for node in args.args: 

263 expected.append((node.arg, inspect.Parameter.POSITIONAL_OR_KEYWORD)) 

264 if args.vararg is not None: 

265 expected.append((args.vararg.arg, inspect.Parameter.VAR_POSITIONAL)) 

266 for node in args.kwonlyargs: 

267 expected.append((node.arg, inspect.Parameter.KEYWORD_ONLY)) 

268 if args.kwarg is not None: 

269 expected.append((args.kwarg.arg, inspect.Parameter.VAR_KEYWORD)) 

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

271 

272 

273def is_first_param_referenced_in_function(f): 

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

275 try: 

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

277 except Exception: 

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

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

280 return any( 

281 isinstance(node, ast.Name) 

282 and node.id == name 

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

284 for node in ast.walk(tree) 

285 ) 

286 

287 

288def extract_all_lambdas(tree, matching_signature): 

289 lambdas = [] 

290 

291 class Visitor(ast.NodeVisitor): 

292 def visit_Lambda(self, node): 

293 if ast_arguments_matches_signature(node.args, matching_signature): 

294 lambdas.append(node) 

295 

296 Visitor().visit(tree) 

297 

298 return lambdas 

299 

300 

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

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

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

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

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

306 

307 

308def _extract_lambda_source(f): 

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

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

311 

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

313 sins, oh lord 

314 """ 

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

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

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

318 sig = inspect.signature(f) 

319 assert sig.return_annotation in (inspect.Parameter.empty, None), sig 

320 

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

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

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

324 

325 if sig.parameters: 

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

327 else: 

328 if_confused = "lambda: <unknown>" 

329 try: 

330 source = inspect.getsource(f) 

331 except OSError: 

332 return if_confused 

333 

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

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

336 source = source.strip() 

337 if "lambda" not in source and sys.platform == "emscripten": # pragma: no cover 

338 return if_confused # work around Pyodide bug in inspect.getsource() 

339 assert "lambda" in source, source 

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): 

456 if isinstance(f, partial): 

457 return pretty(f) 

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

459 return repr(f) 

460 name = f.__name__ 

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): 

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(f, args, kwargs, *, reorder=True): 

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

489 if reorder: 

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

491 

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

493 

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

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

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

497 if kwargs: 

498 for a in sorted(kwargs): 

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

500 

501 rep = nicerepr(f) 

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

503 rep = f"({rep})" 

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

505 if repr_len > 30000: 

506 warnings.warn( 

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

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

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

510 HypothesisWarning, 

511 stacklevel=2, 

512 ) 

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

514 

515 

516def check_valid_identifier(identifier): 

517 if not identifier.isidentifier(): 

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

519 

520 

521eval_cache: dict = {} 

522 

523 

524def source_exec_as_module(source): 

525 try: 

526 return eval_cache[source] 

527 except KeyError: 

528 pass 

529 

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

531 result = ModuleType("hypothesis_temporary_module_" + hexdigest) 

532 assert isinstance(source, str) 

533 exec(source, result.__dict__) 

534 eval_cache[source] = result 

535 return result 

536 

537 

538COPY_SIGNATURE_SCRIPT = """ 

539from hypothesis.utils.conventions import not_set 

540 

541def accept({funcname}): 

542 def {name}{signature}: 

543 return {funcname}({invocation}) 

544 return {name} 

545""".lstrip() 

546 

547 

548def get_varargs(sig, kind=inspect.Parameter.VAR_POSITIONAL): 

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

550 if p.kind is kind: 

551 return p 

552 return None 

553 

554 

555def define_function_signature(name, docstring, signature): 

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

557 passed into it.""" 

558 if name == "<lambda>": 

559 name = "_lambda_" 

560 check_valid_identifier(name) 

561 for a in signature.parameters: 

562 check_valid_identifier(a) 

563 

564 used_names = {*signature.parameters, name} 

565 

566 newsig = signature.replace( 

567 parameters=[ 

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

569 for p in ( 

570 p.replace(annotation=signature.empty) 

571 for p in signature.parameters.values() 

572 ) 

573 ], 

574 return_annotation=signature.empty, 

575 ) 

576 

577 pos_args = [ 

578 p 

579 for p in signature.parameters.values() 

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

581 ] 

582 

583 def accept(f): 

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

585 must_pass_as_kwargs = [] 

586 invocation_parts = [] 

587 for p in pos_args: 

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

589 must_pass_as_kwargs.append(p.name) 

590 else: 

591 invocation_parts.append(p.name) 

592 if get_varargs(signature) is not None: 

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

594 for k in must_pass_as_kwargs: 

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

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

597 if p.kind is p.KEYWORD_ONLY: 

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

599 varkw = get_varargs(signature, kind=inspect.Parameter.VAR_KEYWORD) 

600 if varkw: 

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

602 

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

604 

605 for funcname in candidate_names: # pragma: no branch 

606 if funcname not in used_names: 

607 break 

608 

609 source = COPY_SIGNATURE_SCRIPT.format( 

610 name=name, 

611 funcname=funcname, 

612 signature=str(newsig), 

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

614 ) 

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

616 result.__doc__ = docstring 

617 result.__defaults__ = tuple( 

618 p.default 

619 for p in signature.parameters.values() 

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

621 ) 

622 kwdefaults = { 

623 p.name: p.default 

624 for p in signature.parameters.values() 

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

626 } 

627 if kwdefaults: 

628 result.__kwdefaults__ = kwdefaults 

629 annotations = { 

630 p.name: p.annotation 

631 for p in signature.parameters.values() 

632 if p.annotation is not signature.empty 

633 } 

634 if signature.return_annotation is not signature.empty: 

635 annotations["return"] = signature.return_annotation 

636 if annotations: 

637 result.__annotations__ = annotations 

638 return result 

639 

640 return accept 

641 

642 

643def impersonate(target): 

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

645 introspectors it will appear to be the target function. 

646 

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

648 new one. 

649 """ 

650 

651 def accept(f): 

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

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

654 f.__code__ = f.__code__.replace( 

655 co_filename=target.__code__.co_filename, 

656 co_firstlineno=target.__code__.co_firstlineno, 

657 ) 

658 f.__name__ = target.__name__ 

659 f.__module__ = target.__module__ 

660 f.__doc__ = target.__doc__ 

661 f.__globals__["__hypothesistracebackhide__"] = True 

662 return f 

663 

664 return accept 

665 

666 

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

668 replace_sig = define_function_signature( 

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

670 target.__doc__, 

671 get_signature(target, follow_wrapped=False), 

672 ) 

673 

674 def accept(proxy): 

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

676 

677 return accept 

678 

679 

680def is_identity_function(f): 

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

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