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

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

267 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 Sequence 

23from functools import partial, wraps 

24from inspect import Parameter, Signature 

25from io import StringIO 

26from keyword import iskeyword 

27from random import _inst as global_random_instance 

28from tokenize import COMMENT, generate_tokens, untokenize 

29from types import ModuleType 

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

31from unittest.mock import _patch as PatchType 

32 

33from hypothesis.errors import HypothesisWarning 

34from hypothesis.internal import lambda_sources 

35from hypothesis.internal.compat import EllipsisType, is_typed_named_tuple 

36from hypothesis.utils.conventions import not_set 

37from hypothesis.vendor.pretty import pretty 

38 

39if TYPE_CHECKING: 

40 from hypothesis.strategies._internal.strategies import SearchStrategy 

41 

42T = TypeVar("T") 

43 

44 

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

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

47 

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

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

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

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

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

53 # looking for types. 

54 return hasattr(obj, "hypothesis_internal_is_this_a_mock_check") 

55 

56 

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

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

59 

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

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

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

63 versions (e.g. ast.Constant) 

64 """ 

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

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

67 try: 

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

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

70 except Exception: 

71 pass 

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

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

74 try: 

75 src = untokenize( 

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

77 ) 

78 except Exception: 

79 pass 

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

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

82 

83 

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

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

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

87 minor changes to the function. 

88 

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

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

91 """ 

92 hasher = hashlib.sha384() 

93 try: 

94 src = inspect.getsource(function) 

95 except (OSError, TypeError): 

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

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

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

99 # with class-dependent behaviour. 

100 try: 

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

102 except AttributeError: 

103 pass 

104 else: 

105 hasher.update(_clean_source(src)) 

106 try: 

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

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

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

110 except Exception: 

111 pass 

112 try: 

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

114 hasher.update(function._hypothesis_internal_add_digest) 

115 except AttributeError: 

116 pass 

117 return hasher.digest() 

118 

119 

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

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

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

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

124 raise ValueError( 

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

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

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

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

129 ) 

130 

131 

132def get_signature( 

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

134) -> Signature: 

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

136 # behaviour of getfullargspec instead of reporting unusable arguments. 

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

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

139 return Signature( 

140 [ 

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

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

143 ] 

144 ) 

145 

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

147 # This special case covers unusual codegen like Pydantic models 

148 sig = target.__signature__ 

149 check_signature(sig) 

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

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

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

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

154 if ( 

155 selfy.name == "self" 

156 and selfy.default is Parameter.empty 

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

158 ): 

159 return sig.replace( 

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

161 ) 

162 return sig 

163 # eval_str is only supported by Python 3.10 and newer 

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

165 sig = inspect.signature( 

166 target, follow_wrapped=follow_wrapped, eval_str=eval_str 

167 ) 

168 else: 

169 sig = inspect.signature( 

170 target, follow_wrapped=follow_wrapped 

171 ) # pragma: no cover 

172 check_signature(sig) 

173 return sig 

174 

175 

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

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

178 Parameter.POSITIONAL_OR_KEYWORD, 

179 Parameter.KEYWORD_ONLY, 

180 ) 

181 

182 

183def required_args( 

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

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

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

187) -> set[str]: 

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

189 in args or kwargs. 

190 

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

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

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

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

195 """ 

196 kwargs = {} if kwargs is None else kwargs 

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

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

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

200 return set(target._fields) - provided 

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

202 try: 

203 sig = get_signature(target) 

204 except (ValueError, TypeError): 

205 return set() 

206 return { 

207 name 

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

209 if arg_is_required(param) and name not in kwargs 

210 } 

211 

212 

213def convert_keyword_arguments( 

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

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

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

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

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

219 """ 

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

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

222 return bound.args, bound.kwargs 

223 

224 

225def convert_positional_arguments( 

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

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

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

229 been moved to kwargs. 

230 

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

232 """ 

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

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

235 new_args = [] 

236 new_kwargs = dict(bound.arguments) 

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

238 if p.name in new_kwargs: 

239 if p.kind is p.POSITIONAL_ONLY: 

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

241 elif p.kind is p.VAR_POSITIONAL: 

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

243 elif p.kind is p.VAR_KEYWORD: 

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

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

246 return tuple(new_args), new_kwargs 

247 

248 

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

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

251 for node in args.posonlyargs: 

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

253 for node in args.args: 

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

255 if args.vararg is not None: 

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

257 for node in args.kwonlyargs: 

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

259 if args.kwarg is not None: 

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

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

262 

263 

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

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

266 try: 

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

268 except Exception: 

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

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

271 return any( 

272 isinstance(node, ast.Name) 

273 and node.id == name 

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

275 for node in ast.walk(tree) 

276 ) 

277 

278 

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

280 if isinstance(f, partial): 

281 return pretty(f) 

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

283 return repr(f) 

284 name = f.__name__ # type: ignore 

285 if name == "<lambda>": 

286 return lambda_sources.lambda_description(f) 

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

288 self = f.__self__ 

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

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

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

292 if self is global_random_instance: 

293 return f"random.{name}" 

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

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

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

297 return f"dict.{name}" 

298 return name 

299 

300 

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

302 if inspect.isfunction(v): 

303 return get_pretty_function_description(v) 

304 elif isinstance(v, type): 

305 return v.__name__ 

306 else: 

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

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

309 

310 

311def repr_call( 

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

313) -> str: 

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

315 if reorder: 

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

317 

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

319 

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

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

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

323 if kwargs: 

324 for a in sorted(kwargs): 

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

326 

327 rep = nicerepr(f) 

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

329 rep = f"({rep})" 

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

331 if repr_len > 30000: 

332 warnings.warn( 

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

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

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

336 HypothesisWarning, 

337 stacklevel=2, 

338 ) 

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

340 

341 

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

343 if not identifier.isidentifier(): 

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

345 

346 

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

348 

349 

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

351 try: 

352 return eval_cache[source] 

353 except KeyError: 

354 pass 

355 

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

357 result = ModuleType("hypothesis_temporary_module_" + hexdigest) 

358 assert isinstance(source, str) 

359 exec(source, result.__dict__) 

360 eval_cache[source] = result 

361 return result 

362 

363 

364COPY_SIGNATURE_SCRIPT = """ 

365from hypothesis.utils.conventions import not_set 

366 

367def accept({funcname}): 

368 def {name}{signature}: 

369 return {funcname}({invocation}) 

370 return {name} 

371""".lstrip() 

372 

373 

374def get_varargs( 

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

376) -> Optional[Parameter]: 

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

378 if p.kind is kind: 

379 return p 

380 return None 

381 

382 

383def define_function_signature(name, docstring, signature): 

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

385 passed into it.""" 

386 if name == "<lambda>": 

387 name = "_lambda_" 

388 check_valid_identifier(name) 

389 for a in signature.parameters: 

390 check_valid_identifier(a) 

391 

392 used_names = {*signature.parameters, name} 

393 

394 newsig = signature.replace( 

395 parameters=[ 

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

397 for p in ( 

398 p.replace(annotation=signature.empty) 

399 for p in signature.parameters.values() 

400 ) 

401 ], 

402 return_annotation=signature.empty, 

403 ) 

404 

405 pos_args = [ 

406 p 

407 for p in signature.parameters.values() 

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

409 ] 

410 

411 def accept(f): 

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

413 must_pass_as_kwargs = [] 

414 invocation_parts = [] 

415 for p in pos_args: 

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

417 must_pass_as_kwargs.append(p.name) 

418 else: 

419 invocation_parts.append(p.name) 

420 if get_varargs(signature) is not None: 

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

422 for k in must_pass_as_kwargs: 

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

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

425 if p.kind is p.KEYWORD_ONLY: 

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

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

428 if varkw: 

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

430 

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

432 

433 for funcname in candidate_names: # pragma: no branch 

434 if funcname not in used_names: 

435 break 

436 

437 source = COPY_SIGNATURE_SCRIPT.format( 

438 name=name, 

439 funcname=funcname, 

440 signature=str(newsig), 

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

442 ) 

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

444 result.__doc__ = docstring 

445 result.__defaults__ = tuple( 

446 p.default 

447 for p in signature.parameters.values() 

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

449 ) 

450 kwdefaults = { 

451 p.name: p.default 

452 for p in signature.parameters.values() 

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

454 } 

455 if kwdefaults: 

456 result.__kwdefaults__ = kwdefaults 

457 annotations = { 

458 p.name: p.annotation 

459 for p in signature.parameters.values() 

460 if p.annotation is not signature.empty 

461 } 

462 if signature.return_annotation is not signature.empty: 

463 annotations["return"] = signature.return_annotation 

464 if annotations: 

465 result.__annotations__ = annotations 

466 return result 

467 

468 return accept 

469 

470 

471def impersonate(target): 

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

473 introspectors it will appear to be the target function. 

474 

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

476 new one. 

477 """ 

478 

479 def accept(f): 

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

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

482 f.__code__ = f.__code__.replace( 

483 co_filename=target.__code__.co_filename, 

484 co_firstlineno=target.__code__.co_firstlineno, 

485 ) 

486 f.__name__ = target.__name__ 

487 f.__module__ = target.__module__ 

488 f.__doc__ = target.__doc__ 

489 f.__globals__["__hypothesistracebackhide__"] = True 

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

491 # just confused by the lies above 

492 f.__wrapped_target = target 

493 return f 

494 

495 return accept 

496 

497 

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

499 replace_sig = define_function_signature( 

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

501 target.__doc__, 

502 get_signature(target, follow_wrapped=False), 

503 ) 

504 

505 def accept(proxy): 

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

507 

508 return accept 

509 

510 

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

512 try: 

513 code = f.__code__ 

514 except AttributeError: 

515 try: 

516 f = f.__call__ # type: ignore 

517 code = f.__code__ 

518 except AttributeError: 

519 return False 

520 

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

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

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

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

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

526 return False 

527 

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

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

530 template = (lambda self, x: x) if bound_args else (lambda x: x) # type: ignore 

531 try: 

532 return code.co_code == template.__code__.co_code 

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

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

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

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

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

538 return False