扫码登录,获取cookies
This commit is contained in:
648
backend/venv/Lib/site-packages/hypothesis/internal/reflection.py
Normal file
648
backend/venv/Lib/site-packages/hypothesis/internal/reflection.py
Normal file
@@ -0,0 +1,648 @@
|
||||
# This file is part of Hypothesis, which may be found at
|
||||
# https://github.com/HypothesisWorks/hypothesis/
|
||||
#
|
||||
# Copyright the Hypothesis Authors.
|
||||
# Individual contributors are listed in AUTHORS.rst and the git log.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
# obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
"""This file can approximately be considered the collection of hypothesis going
|
||||
to really unreasonable lengths to produce pretty output."""
|
||||
|
||||
import ast
|
||||
import hashlib
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import types
|
||||
from functools import wraps
|
||||
from io import StringIO
|
||||
from keyword import iskeyword
|
||||
from tokenize import COMMENT, detect_encoding, generate_tokens, untokenize
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
from unittest.mock import _patch as PatchType
|
||||
|
||||
from hypothesis.internal.compat import PYPY, is_typed_named_tuple
|
||||
from hypothesis.utils.conventions import not_set
|
||||
from hypothesis.vendor.pretty import pretty
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from hypothesis.strategies._internal.strategies import T
|
||||
|
||||
READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
|
||||
|
||||
|
||||
def is_mock(obj):
|
||||
"""Determine if the given argument is a mock type."""
|
||||
|
||||
# We want to be able to detect these when dealing with various test
|
||||
# args. As they are sneaky and can look like almost anything else,
|
||||
# we'll check this by looking for an attribute with a name that it's really
|
||||
# unlikely to implement accidentally, and that anyone who implements it
|
||||
# deliberately should know what they're doing. This is more robust than
|
||||
# looking for types.
|
||||
return hasattr(obj, "hypothesis_internal_is_this_a_mock_check")
|
||||
|
||||
|
||||
def _clean_source(src: str) -> bytes:
|
||||
"""Return the source code as bytes, without decorators or comments.
|
||||
|
||||
Because this is part of our database key, we reduce the cache invalidation
|
||||
rate by ignoring decorators, comments, trailing whitespace, and empty lines.
|
||||
We can't just use the (dumped) AST directly because it changes between Python
|
||||
versions (e.g. ast.Constant)
|
||||
"""
|
||||
# Get the (one-indexed) line number of the function definition, and drop preceding
|
||||
# lines - i.e. any decorators, so that adding `@example()`s keeps the same key.
|
||||
try:
|
||||
funcdef = ast.parse(src).body[0]
|
||||
if sys.version_info[:2] == (3, 8) and PYPY:
|
||||
# We can't get a line number of the (async) def here, so as a best-effort
|
||||
# approximation we'll use str.split instead and hope for the best.
|
||||
tag = "async def " if isinstance(funcdef, ast.AsyncFunctionDef) else "def "
|
||||
if tag in src:
|
||||
src = tag + src.split(tag, maxsplit=1)[1]
|
||||
else:
|
||||
src = "".join(src.splitlines(keepends=True)[funcdef.lineno - 1 :])
|
||||
except Exception:
|
||||
pass
|
||||
# Remove blank lines and use the tokenize module to strip out comments,
|
||||
# so that those can be changed without changing the database key.
|
||||
try:
|
||||
src = untokenize(
|
||||
t for t in generate_tokens(StringIO(src).readline) if t.type != COMMENT
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
# Finally, remove any trailing whitespace and empty lines as a last cleanup.
|
||||
return "\n".join(x.rstrip() for x in src.splitlines() if x.rstrip()).encode()
|
||||
|
||||
|
||||
def function_digest(function):
|
||||
"""Returns a string that is stable across multiple invocations across
|
||||
multiple processes and is prone to changing significantly in response to
|
||||
minor changes to the function.
|
||||
|
||||
No guarantee of uniqueness though it usually will be. Digest collisions
|
||||
lead to unfortunate but not fatal problems during database replay.
|
||||
"""
|
||||
hasher = hashlib.sha384()
|
||||
try:
|
||||
src = inspect.getsource(function)
|
||||
except (OSError, TypeError):
|
||||
# If we can't actually get the source code, try for the name as a fallback.
|
||||
# NOTE: We might want to change this to always adding function.__qualname__,
|
||||
# to differentiate f.x. two classes having the same function implementation
|
||||
# with class-dependent behaviour.
|
||||
try:
|
||||
hasher.update(function.__name__.encode())
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
hasher.update(_clean_source(src))
|
||||
try:
|
||||
# This is additional to the source code because it can include the effects
|
||||
# of decorators, or of post-hoc assignment to the .__signature__ attribute.
|
||||
hasher.update(repr(get_signature(function)).encode())
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
# We set this in order to distinguish e.g. @pytest.mark.parametrize cases.
|
||||
hasher.update(function._hypothesis_internal_add_digest)
|
||||
except AttributeError:
|
||||
pass
|
||||
return hasher.digest()
|
||||
|
||||
|
||||
def check_signature(sig: inspect.Signature) -> None:
|
||||
# Backport from Python 3.11; see https://github.com/python/cpython/pull/92065
|
||||
for p in sig.parameters.values():
|
||||
if iskeyword(p.name) and p.kind is not p.POSITIONAL_ONLY:
|
||||
raise ValueError(
|
||||
f"Signature {sig!r} contains a parameter named {p.name!r}, "
|
||||
f"but this is a SyntaxError because `{p.name}` is a keyword. "
|
||||
"You, or a library you use, must have manually created an "
|
||||
"invalid signature - this will be an error in Python 3.11+"
|
||||
)
|
||||
|
||||
|
||||
def get_signature(
|
||||
target: Any, *, follow_wrapped: bool = True, eval_str: bool = False
|
||||
) -> inspect.Signature:
|
||||
# Special case for use of `@unittest.mock.patch` decorator, mimicking the
|
||||
# behaviour of getfullargspec instead of reporting unusable arguments.
|
||||
patches = getattr(target, "patchings", None)
|
||||
if isinstance(patches, list) and all(isinstance(p, PatchType) for p in patches):
|
||||
P = inspect.Parameter
|
||||
return inspect.Signature(
|
||||
[P("args", P.VAR_POSITIONAL), P("keywargs", P.VAR_KEYWORD)]
|
||||
)
|
||||
|
||||
if isinstance(getattr(target, "__signature__", None), inspect.Signature):
|
||||
# This special case covers unusual codegen like Pydantic models
|
||||
sig = target.__signature__
|
||||
check_signature(sig)
|
||||
# And *this* much more complicated block ignores the `self` argument
|
||||
# if that's been (incorrectly) included in the custom signature.
|
||||
if sig.parameters and (inspect.isclass(target) or inspect.ismethod(target)):
|
||||
selfy = next(iter(sig.parameters.values()))
|
||||
if (
|
||||
selfy.name == "self"
|
||||
and selfy.default is inspect.Parameter.empty
|
||||
and selfy.kind.name.startswith("POSITIONAL_")
|
||||
):
|
||||
return sig.replace(
|
||||
parameters=[v for k, v in sig.parameters.items() if k != "self"]
|
||||
)
|
||||
return sig
|
||||
if sys.version_info[:2] <= (3, 8) and inspect.isclass(target):
|
||||
# Workaround for subclasses of typing.Generic on Python <= 3.8
|
||||
from hypothesis.strategies._internal.types import is_generic_type
|
||||
|
||||
if is_generic_type(target):
|
||||
sig = inspect.signature(target.__init__)
|
||||
check_signature(sig)
|
||||
return sig.replace(
|
||||
parameters=[v for k, v in sig.parameters.items() if k != "self"]
|
||||
)
|
||||
# eval_str is only supported by Python 3.10 and newer
|
||||
if sys.version_info[:2] >= (3, 10):
|
||||
sig = inspect.signature(
|
||||
target, follow_wrapped=follow_wrapped, eval_str=eval_str
|
||||
)
|
||||
else:
|
||||
sig = inspect.signature(
|
||||
target, follow_wrapped=follow_wrapped
|
||||
) # pragma: no cover
|
||||
check_signature(sig)
|
||||
return sig
|
||||
|
||||
|
||||
def arg_is_required(param):
|
||||
return param.default is inspect.Parameter.empty and param.kind in (
|
||||
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
inspect.Parameter.KEYWORD_ONLY,
|
||||
)
|
||||
|
||||
|
||||
def required_args(target, args=(), kwargs=()):
|
||||
"""Return a set of names of required args to target that were not supplied
|
||||
in args or kwargs.
|
||||
|
||||
This is used in builds() to determine which arguments to attempt to
|
||||
fill from type hints. target may be any callable (including classes
|
||||
and bound methods). args and kwargs should be as they are passed to
|
||||
builds() - that is, a tuple of values and a dict of names: values.
|
||||
"""
|
||||
# We start with a workaround for NamedTuples, which don't have nice inits
|
||||
if inspect.isclass(target) and is_typed_named_tuple(target):
|
||||
provided = set(kwargs) | set(target._fields[: len(args)])
|
||||
return set(target._fields) - provided
|
||||
# Then we try to do the right thing with inspect.signature
|
||||
try:
|
||||
sig = get_signature(target)
|
||||
except (ValueError, TypeError):
|
||||
return set()
|
||||
return {
|
||||
name
|
||||
for name, param in list(sig.parameters.items())[len(args) :]
|
||||
if arg_is_required(param) and name not in kwargs
|
||||
}
|
||||
|
||||
|
||||
def convert_keyword_arguments(function, args, kwargs):
|
||||
"""Returns a pair of a tuple and a dictionary which would be equivalent
|
||||
passed as positional and keyword args to the function. Unless function has
|
||||
kwonlyargs or **kwargs the dictionary will always be empty.
|
||||
"""
|
||||
sig = inspect.signature(function, follow_wrapped=False)
|
||||
bound = sig.bind(*args, **kwargs)
|
||||
return bound.args, bound.kwargs
|
||||
|
||||
|
||||
def convert_positional_arguments(function, args, kwargs):
|
||||
"""Return a tuple (new_args, new_kwargs) where all possible arguments have
|
||||
been moved to kwargs.
|
||||
|
||||
new_args will only be non-empty if function has pos-only args or *args.
|
||||
"""
|
||||
sig = inspect.signature(function, follow_wrapped=False)
|
||||
bound = sig.bind(*args, **kwargs)
|
||||
new_args = []
|
||||
new_kwargs = dict(bound.arguments)
|
||||
for p in sig.parameters.values():
|
||||
if p.name in new_kwargs:
|
||||
if p.kind is p.POSITIONAL_ONLY:
|
||||
new_args.append(new_kwargs.pop(p.name))
|
||||
elif p.kind is p.VAR_POSITIONAL:
|
||||
new_args.extend(new_kwargs.pop(p.name))
|
||||
elif p.kind is p.VAR_KEYWORD:
|
||||
assert set(new_kwargs[p.name]).isdisjoint(set(new_kwargs) - {p.name})
|
||||
new_kwargs.update(new_kwargs.pop(p.name))
|
||||
return tuple(new_args), new_kwargs
|
||||
|
||||
|
||||
def ast_arguments_matches_signature(args, sig):
|
||||
assert isinstance(args, ast.arguments)
|
||||
assert isinstance(sig, inspect.Signature)
|
||||
expected = []
|
||||
for node in getattr(args, "posonlyargs", ()): # New in Python 3.8
|
||||
expected.append((node.arg, inspect.Parameter.POSITIONAL_ONLY))
|
||||
for node in args.args:
|
||||
expected.append((node.arg, inspect.Parameter.POSITIONAL_OR_KEYWORD))
|
||||
if args.vararg is not None:
|
||||
expected.append((args.vararg.arg, inspect.Parameter.VAR_POSITIONAL))
|
||||
for node in args.kwonlyargs:
|
||||
expected.append((node.arg, inspect.Parameter.KEYWORD_ONLY))
|
||||
if args.kwarg is not None:
|
||||
expected.append((args.kwarg.arg, inspect.Parameter.VAR_KEYWORD))
|
||||
return expected == [(p.name, p.kind) for p in sig.parameters.values()]
|
||||
|
||||
|
||||
def is_first_param_referenced_in_function(f):
|
||||
"""Is the given name referenced within f?"""
|
||||
try:
|
||||
tree = ast.parse(textwrap.dedent(inspect.getsource(f)))
|
||||
except Exception:
|
||||
return True # Assume it's OK unless we know otherwise
|
||||
name = next(iter(get_signature(f).parameters))
|
||||
return any(
|
||||
isinstance(node, ast.Name)
|
||||
and node.id == name
|
||||
and isinstance(node.ctx, ast.Load)
|
||||
for node in ast.walk(tree)
|
||||
)
|
||||
|
||||
|
||||
def extract_all_lambdas(tree, matching_signature):
|
||||
lambdas = []
|
||||
|
||||
class Visitor(ast.NodeVisitor):
|
||||
def visit_Lambda(self, node):
|
||||
if ast_arguments_matches_signature(node.args, matching_signature):
|
||||
lambdas.append(node)
|
||||
|
||||
Visitor().visit(tree)
|
||||
|
||||
return lambdas
|
||||
|
||||
|
||||
LINE_CONTINUATION = re.compile(r"\\\n")
|
||||
WHITESPACE = re.compile(r"\s+")
|
||||
PROBABLY_A_COMMENT = re.compile("""#[^'"]*$""")
|
||||
SPACE_FOLLOWS_OPEN_BRACKET = re.compile(r"\( ")
|
||||
SPACE_PRECEDES_CLOSE_BRACKET = re.compile(r" \)")
|
||||
|
||||
|
||||
def extract_lambda_source(f):
|
||||
"""Extracts a single lambda expression from the string source. Returns a
|
||||
string indicating an unknown body if it gets confused in any way.
|
||||
|
||||
This is not a good function and I am sorry for it. Forgive me my
|
||||
sins, oh lord
|
||||
"""
|
||||
# You might be wondering how a lambda can have a return-type annotation?
|
||||
# The answer is that we add this at runtime, in new_given_signature(),
|
||||
# and we do support strange choices as applying @given() to a lambda.
|
||||
sig = inspect.signature(f)
|
||||
assert sig.return_annotation in (inspect.Parameter.empty, None), sig
|
||||
|
||||
if sig.parameters:
|
||||
if_confused = f"lambda {str(sig)[1:-1]}: <unknown>"
|
||||
else:
|
||||
if_confused = "lambda: <unknown>"
|
||||
try:
|
||||
source = inspect.getsource(f)
|
||||
except OSError:
|
||||
return if_confused
|
||||
|
||||
source = LINE_CONTINUATION.sub(" ", source)
|
||||
source = WHITESPACE.sub(" ", source)
|
||||
source = source.strip()
|
||||
if "lambda" not in source and sys.platform == "emscripten": # pragma: no cover
|
||||
return if_confused # work around Pyodide bug in inspect.getsource()
|
||||
assert "lambda" in source
|
||||
|
||||
tree = None
|
||||
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
except SyntaxError:
|
||||
for i in range(len(source) - 1, len("lambda"), -1):
|
||||
prefix = source[:i]
|
||||
if "lambda" not in prefix:
|
||||
break
|
||||
try:
|
||||
tree = ast.parse(prefix)
|
||||
source = prefix
|
||||
break
|
||||
except SyntaxError:
|
||||
continue
|
||||
if tree is None and source.startswith(("@", ".")):
|
||||
# This will always eventually find a valid expression because the
|
||||
# decorator or chained operator must be a valid Python function call,
|
||||
# so will eventually be syntactically valid and break out of the loop.
|
||||
# Thus, this loop can never terminate normally.
|
||||
for i in range(len(source) + 1):
|
||||
p = source[1:i]
|
||||
if "lambda" in p:
|
||||
try:
|
||||
tree = ast.parse(p)
|
||||
source = p
|
||||
break
|
||||
except SyntaxError:
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError("expected to be unreachable")
|
||||
|
||||
if tree is None:
|
||||
return if_confused
|
||||
|
||||
aligned_lambdas = extract_all_lambdas(tree, matching_signature=sig)
|
||||
if len(aligned_lambdas) != 1:
|
||||
return if_confused
|
||||
lambda_ast = aligned_lambdas[0]
|
||||
assert lambda_ast.lineno == 1
|
||||
|
||||
# If the source code contains Unicode characters, the bytes of the original
|
||||
# file don't line up with the string indexes, and `col_offset` doesn't match
|
||||
# the string we're using. We need to convert the source code into bytes
|
||||
# before slicing.
|
||||
#
|
||||
# Under the hood, the inspect module is using `tokenize.detect_encoding` to
|
||||
# detect the encoding of the original source file. We'll use the same
|
||||
# approach to get the source code as bytes.
|
||||
#
|
||||
# See https://github.com/HypothesisWorks/hypothesis/issues/1700 for an
|
||||
# example of what happens if you don't correct for this.
|
||||
#
|
||||
# Note: if the code doesn't come from a file (but, for example, a doctest),
|
||||
# `getsourcefile` will return `None` and the `open()` call will fail with
|
||||
# an OSError. Or if `f` is a built-in function, in which case we get a
|
||||
# TypeError. In both cases, fall back to splitting the Unicode string.
|
||||
# It's not perfect, but it's the best we can do.
|
||||
try:
|
||||
with open(inspect.getsourcefile(f), "rb") as src_f:
|
||||
encoding, _ = detect_encoding(src_f.readline)
|
||||
|
||||
source_bytes = source.encode(encoding)
|
||||
source_bytes = source_bytes[lambda_ast.col_offset :].strip()
|
||||
source = source_bytes.decode(encoding)
|
||||
except (OSError, TypeError):
|
||||
source = source[lambda_ast.col_offset :].strip()
|
||||
|
||||
# This ValueError can be thrown in Python 3 if:
|
||||
#
|
||||
# - There's a Unicode character in the line before the Lambda, and
|
||||
# - For some reason we can't detect the source encoding of the file
|
||||
#
|
||||
# because slicing on `lambda_ast.col_offset` will account for bytes, but
|
||||
# the slice will be on Unicode characters.
|
||||
#
|
||||
# In practice this seems relatively rare, so we just give up rather than
|
||||
# trying to recover.
|
||||
try:
|
||||
source = source[source.index("lambda") :]
|
||||
except ValueError:
|
||||
return if_confused
|
||||
|
||||
for i in range(len(source), len("lambda"), -1): # pragma: no branch
|
||||
try:
|
||||
parsed = ast.parse(source[:i])
|
||||
assert len(parsed.body) == 1
|
||||
assert parsed.body
|
||||
if isinstance(parsed.body[0].value, ast.Lambda):
|
||||
source = source[:i]
|
||||
break
|
||||
except SyntaxError:
|
||||
pass
|
||||
lines = source.split("\n")
|
||||
lines = [PROBABLY_A_COMMENT.sub("", l) for l in lines]
|
||||
source = "\n".join(lines)
|
||||
|
||||
source = WHITESPACE.sub(" ", source)
|
||||
source = SPACE_FOLLOWS_OPEN_BRACKET.sub("(", source)
|
||||
source = SPACE_PRECEDES_CLOSE_BRACKET.sub(")", source)
|
||||
return source.strip()
|
||||
|
||||
|
||||
def get_pretty_function_description(f):
|
||||
if not hasattr(f, "__name__"):
|
||||
return repr(f)
|
||||
name = f.__name__
|
||||
if name == "<lambda>":
|
||||
return extract_lambda_source(f)
|
||||
elif isinstance(f, (types.MethodType, types.BuiltinMethodType)):
|
||||
self = f.__self__
|
||||
# Some objects, like `builtins.abs` are of BuiltinMethodType but have
|
||||
# their module as __self__. This might include c-extensions generally?
|
||||
if not (self is None or inspect.isclass(self) or inspect.ismodule(self)):
|
||||
return f"{self!r}.{name}"
|
||||
elif isinstance(name, str) and getattr(dict, name, object()) is f:
|
||||
# special case for keys/values views in from_type() / ghostwriter output
|
||||
return f"dict.{name}"
|
||||
return name
|
||||
|
||||
|
||||
def nicerepr(v):
|
||||
if inspect.isfunction(v):
|
||||
return get_pretty_function_description(v)
|
||||
elif isinstance(v, type):
|
||||
return v.__name__
|
||||
else:
|
||||
# With TypeVar T, show List[T] instead of TypeError on List[~T]
|
||||
return re.sub(r"(\[)~([A-Z][a-z]*\])", r"\g<1>\g<2>", pretty(v))
|
||||
|
||||
|
||||
def repr_call(f, args, kwargs, *, reorder=True):
|
||||
# Note: for multi-line pretty-printing, see RepresentationPrinter.repr_call()
|
||||
if reorder:
|
||||
args, kwargs = convert_positional_arguments(f, args, kwargs)
|
||||
|
||||
bits = [nicerepr(x) for x in args]
|
||||
|
||||
for p in get_signature(f).parameters.values():
|
||||
if p.name in kwargs and not p.kind.name.startswith("VAR_"):
|
||||
bits.append(f"{p.name}={nicerepr(kwargs.pop(p.name))}")
|
||||
if kwargs:
|
||||
for a in sorted(kwargs):
|
||||
bits.append(f"{a}={nicerepr(kwargs[a])}")
|
||||
|
||||
rep = nicerepr(f)
|
||||
if rep.startswith("lambda") and ":" in rep:
|
||||
rep = f"({rep})"
|
||||
return rep + "(" + ", ".join(bits) + ")"
|
||||
|
||||
|
||||
def check_valid_identifier(identifier):
|
||||
if not identifier.isidentifier():
|
||||
raise ValueError(f"{identifier!r} is not a valid python identifier")
|
||||
|
||||
|
||||
eval_cache: dict = {}
|
||||
|
||||
|
||||
def source_exec_as_module(source):
|
||||
try:
|
||||
return eval_cache[source]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
hexdigest = hashlib.sha384(source.encode()).hexdigest()
|
||||
result = ModuleType("hypothesis_temporary_module_" + hexdigest)
|
||||
assert isinstance(source, str)
|
||||
exec(source, result.__dict__)
|
||||
eval_cache[source] = result
|
||||
return result
|
||||
|
||||
|
||||
COPY_SIGNATURE_SCRIPT = """
|
||||
from hypothesis.utils.conventions import not_set
|
||||
|
||||
def accept({funcname}):
|
||||
def {name}{signature}:
|
||||
return {funcname}({invocation})
|
||||
return {name}
|
||||
""".lstrip()
|
||||
|
||||
|
||||
def get_varargs(sig, kind=inspect.Parameter.VAR_POSITIONAL):
|
||||
for p in sig.parameters.values():
|
||||
if p.kind is kind:
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def define_function_signature(name, docstring, signature):
|
||||
"""A decorator which sets the name, signature and docstring of the function
|
||||
passed into it."""
|
||||
if name == "<lambda>":
|
||||
name = "_lambda_"
|
||||
check_valid_identifier(name)
|
||||
for a in signature.parameters:
|
||||
check_valid_identifier(a)
|
||||
|
||||
used_names = {*signature.parameters, name}
|
||||
|
||||
newsig = signature.replace(
|
||||
parameters=[
|
||||
p if p.default is signature.empty else p.replace(default=not_set)
|
||||
for p in (
|
||||
p.replace(annotation=signature.empty)
|
||||
for p in signature.parameters.values()
|
||||
)
|
||||
],
|
||||
return_annotation=signature.empty,
|
||||
)
|
||||
|
||||
pos_args = [
|
||||
p
|
||||
for p in signature.parameters.values()
|
||||
if p.kind.name.startswith("POSITIONAL_")
|
||||
]
|
||||
|
||||
def accept(f):
|
||||
fsig = inspect.signature(f, follow_wrapped=False)
|
||||
must_pass_as_kwargs = []
|
||||
invocation_parts = []
|
||||
for p in pos_args:
|
||||
if p.name not in fsig.parameters and get_varargs(fsig) is None:
|
||||
must_pass_as_kwargs.append(p.name)
|
||||
else:
|
||||
invocation_parts.append(p.name)
|
||||
if get_varargs(signature) is not None:
|
||||
invocation_parts.append("*" + get_varargs(signature).name)
|
||||
for k in must_pass_as_kwargs:
|
||||
invocation_parts.append(f"{k}={k}")
|
||||
for p in signature.parameters.values():
|
||||
if p.kind is p.KEYWORD_ONLY:
|
||||
invocation_parts.append(f"{p.name}={p.name}")
|
||||
varkw = get_varargs(signature, kind=inspect.Parameter.VAR_KEYWORD)
|
||||
if varkw:
|
||||
invocation_parts.append("**" + varkw.name)
|
||||
|
||||
candidate_names = ["f"] + [f"f_{i}" for i in range(1, len(used_names) + 2)]
|
||||
|
||||
for funcname in candidate_names: # pragma: no branch
|
||||
if funcname not in used_names:
|
||||
break
|
||||
|
||||
source = COPY_SIGNATURE_SCRIPT.format(
|
||||
name=name,
|
||||
funcname=funcname,
|
||||
signature=str(newsig),
|
||||
invocation=", ".join(invocation_parts),
|
||||
)
|
||||
result = source_exec_as_module(source).accept(f)
|
||||
result.__doc__ = docstring
|
||||
result.__defaults__ = tuple(
|
||||
p.default
|
||||
for p in signature.parameters.values()
|
||||
if p.default is not signature.empty and "POSITIONAL" in p.kind.name
|
||||
)
|
||||
kwdefaults = {
|
||||
p.name: p.default
|
||||
for p in signature.parameters.values()
|
||||
if p.default is not signature.empty and p.kind is p.KEYWORD_ONLY
|
||||
}
|
||||
if kwdefaults:
|
||||
result.__kwdefaults__ = kwdefaults
|
||||
annotations = {
|
||||
p.name: p.annotation
|
||||
for p in signature.parameters.values()
|
||||
if p.annotation is not signature.empty
|
||||
}
|
||||
if signature.return_annotation is not signature.empty:
|
||||
annotations["return"] = signature.return_annotation
|
||||
if annotations:
|
||||
result.__annotations__ = annotations
|
||||
return result
|
||||
|
||||
return accept
|
||||
|
||||
|
||||
def impersonate(target):
|
||||
"""Decorator to update the attributes of a function so that to external
|
||||
introspectors it will appear to be the target function.
|
||||
|
||||
Note that this updates the function in place, it doesn't return a
|
||||
new one.
|
||||
"""
|
||||
|
||||
def accept(f):
|
||||
# Lie shamelessly about where this code comes from, to hide the hypothesis
|
||||
# internals from pytest, ipython, and other runtime introspection.
|
||||
f.__code__ = f.__code__.replace(
|
||||
co_filename=target.__code__.co_filename,
|
||||
co_firstlineno=target.__code__.co_firstlineno,
|
||||
)
|
||||
f.__name__ = target.__name__
|
||||
f.__module__ = target.__module__
|
||||
f.__doc__ = target.__doc__
|
||||
f.__globals__["__hypothesistracebackhide__"] = True
|
||||
return f
|
||||
|
||||
return accept
|
||||
|
||||
|
||||
def proxies(target: "T") -> Callable[[Callable], "T"]:
|
||||
replace_sig = define_function_signature(
|
||||
target.__name__.replace("<lambda>", "_lambda_"), # type: ignore
|
||||
target.__doc__,
|
||||
get_signature(target, follow_wrapped=False),
|
||||
)
|
||||
|
||||
def accept(proxy):
|
||||
return impersonate(target)(wraps(target)(replace_sig(proxy)))
|
||||
|
||||
return accept
|
||||
|
||||
|
||||
def is_identity_function(f):
|
||||
# TODO: pattern-match the AST to handle `def ...` identity functions too
|
||||
return bool(re.fullmatch(r"lambda (\w+): \1", get_pretty_function_description(f)))
|
||||
Reference in New Issue
Block a user