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

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

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

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 provided = set(kwargs) | set(target._fields[: len(args)]) 

191 return set(target._fields) - provided 

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

193 try: 

194 sig = get_signature(target) 

195 except (ValueError, TypeError): 

196 return set() 

197 return { 

198 name 

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

200 if arg_is_required(param) and name not in kwargs 

201 } 

202 

203 

204def convert_keyword_arguments( 

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

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

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

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

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

210 """ 

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

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

213 return bound.args, bound.kwargs 

214 

215 

216def convert_positional_arguments( 

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

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

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

220 been moved to kwargs. 

221 

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

223 """ 

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

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

226 new_args = [] 

227 new_kwargs = dict(bound.arguments) 

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

229 if p.name in new_kwargs: 

230 if p.kind is p.POSITIONAL_ONLY: 

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

232 elif p.kind is p.VAR_POSITIONAL: 

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

234 elif p.kind is p.VAR_KEYWORD: 

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

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

237 return tuple(new_args), new_kwargs 

238 

239 

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

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

242 for node in args.posonlyargs: 

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

244 for node in args.args: 

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

246 if args.vararg is not None: 

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

248 for node in args.kwonlyargs: 

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

250 if args.kwarg is not None: 

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

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

253 

254 

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

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

257 try: 

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

259 except Exception: 

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

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

262 return any( 

263 isinstance(node, ast.Name) 

264 and node.id == name 

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

266 for node in ast.walk(tree) 

267 ) 

268 

269 

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

271 if isinstance(f, partial): 

272 return pretty(f) 

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

274 return repr(f) 

275 name = f.__name__ # type: ignore 

276 if name == "<lambda>": 

277 return lambda_sources.lambda_description(f) 

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

279 self = f.__self__ 

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

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

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

283 if self is global_random_instance: 

284 return f"random.{name}" 

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

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

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

288 return f"dict.{name}" 

289 return name 

290 

291 

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

293 if inspect.isfunction(v): 

294 return get_pretty_function_description(v) 

295 elif isinstance(v, type): 

296 return v.__name__ 

297 else: 

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

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

300 

301 

302def repr_call( 

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

304) -> str: 

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

306 if reorder: 

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

308 

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

310 

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

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

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

314 if kwargs: 

315 for a in sorted(kwargs): 

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

317 

318 rep = nicerepr(f) 

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

320 rep = f"({rep})" 

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

322 if repr_len > 30000: 

323 warnings.warn( 

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

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

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

327 HypothesisWarning, 

328 stacklevel=2, 

329 ) 

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

331 

332 

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

334 if not identifier.isidentifier(): 

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

336 

337 

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

339 

340 

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

342 try: 

343 return eval_cache[source] 

344 except KeyError: 

345 pass 

346 

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

348 result = ModuleType("hypothesis_temporary_module_" + hexdigest) 

349 assert isinstance(source, str) 

350 exec(source, result.__dict__) 

351 eval_cache[source] = result 

352 return result 

353 

354 

355COPY_SIGNATURE_SCRIPT = """ 

356from hypothesis.utils.conventions import not_set 

357 

358def accept({funcname}): 

359 def {name}{signature}: 

360 return {funcname}({invocation}) 

361 return {name} 

362""".lstrip() 

363 

364 

365def get_varargs( 

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

367) -> Parameter | None: 

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

369 if p.kind is kind: 

370 return p 

371 return None 

372 

373 

374def define_function_signature(name, docstring, signature): 

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

376 passed into it.""" 

377 if name == "<lambda>": 

378 name = "_lambda_" 

379 check_valid_identifier(name) 

380 for a in signature.parameters: 

381 check_valid_identifier(a) 

382 

383 used_names = {*signature.parameters, name} 

384 

385 newsig = signature.replace( 

386 parameters=[ 

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

388 for p in ( 

389 p.replace(annotation=signature.empty) 

390 for p in signature.parameters.values() 

391 ) 

392 ], 

393 return_annotation=signature.empty, 

394 ) 

395 

396 pos_args = [ 

397 p 

398 for p in signature.parameters.values() 

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

400 ] 

401 

402 def accept(f): 

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

404 must_pass_as_kwargs = [] 

405 invocation_parts = [] 

406 for p in pos_args: 

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

408 must_pass_as_kwargs.append(p.name) 

409 else: 

410 invocation_parts.append(p.name) 

411 if get_varargs(signature) is not None: 

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

413 for k in must_pass_as_kwargs: 

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

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

416 if p.kind is p.KEYWORD_ONLY: 

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

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

419 if varkw: 

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

421 

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

423 

424 for funcname in candidate_names: # pragma: no branch 

425 if funcname not in used_names: 

426 break 

427 

428 source = COPY_SIGNATURE_SCRIPT.format( 

429 name=name, 

430 funcname=funcname, 

431 signature=str(newsig), 

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

433 ) 

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

435 result.__doc__ = docstring 

436 result.__defaults__ = tuple( 

437 p.default 

438 for p in signature.parameters.values() 

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

440 ) 

441 kwdefaults = { 

442 p.name: p.default 

443 for p in signature.parameters.values() 

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

445 } 

446 if kwdefaults: 

447 result.__kwdefaults__ = kwdefaults 

448 annotations = { 

449 p.name: p.annotation 

450 for p in signature.parameters.values() 

451 if p.annotation is not signature.empty 

452 } 

453 if signature.return_annotation is not signature.empty: 

454 annotations["return"] = signature.return_annotation 

455 if annotations: 

456 result.__annotations__ = annotations 

457 return result 

458 

459 return accept 

460 

461 

462def impersonate(target): 

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

464 introspectors it will appear to be the target function. 

465 

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

467 new one. 

468 """ 

469 

470 def accept(f): 

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

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

473 f.__code__ = f.__code__.replace( 

474 co_filename=target.__code__.co_filename, 

475 co_firstlineno=target.__code__.co_firstlineno, 

476 ) 

477 f.__name__ = target.__name__ 

478 f.__module__ = target.__module__ 

479 f.__doc__ = target.__doc__ 

480 f.__globals__["__hypothesistracebackhide__"] = True 

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

482 # just confused by the lies above 

483 f.__wrapped_target = target 

484 return f 

485 

486 return accept 

487 

488 

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

490 replace_sig = define_function_signature( 

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

492 target.__doc__, 

493 get_signature(target, follow_wrapped=False), 

494 ) 

495 

496 def accept(proxy): 

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

498 

499 return accept 

500 

501 

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

503 try: 

504 code = f.__code__ 

505 except AttributeError: 

506 try: 

507 f = f.__call__ # type: ignore 

508 code = f.__code__ 

509 except AttributeError: 

510 return False 

511 

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

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

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

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

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

517 return False 

518 

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

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

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

522 try: 

523 return code.co_code == template.__code__.co_code 

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

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

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

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

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

529 return False