summaryrefslogtreecommitdiffstats
path: root/contrib/python/hypothesis/py3
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2024-07-27 12:19:49 +0300
committerrobot-piglet <[email protected]>2024-07-27 12:28:17 +0300
commit25820897807c4bbe6fc46855ef8b7afc18f475df (patch)
tree74cf3d50ef7246caf81bdc9b21449c75026187b5 /contrib/python/hypothesis/py3
parentdd3740bc9af7fa735444c348d70607a1bd135661 (diff)
Intermediate changes
Diffstat (limited to 'contrib/python/hypothesis/py3')
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/_settings.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/control.py8
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/core.py81
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/codemods.py12
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py153
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/numpy.py28
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/intervalsets.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py14
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py30
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strings.py52
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py27
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make2
14 files changed, 246 insertions, 169 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index 8c732599ab2..c9d68c09490 100644
--- a/contrib/python/hypothesis/py3/.dist-info/METADATA
+++ b/contrib/python/hypothesis/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: hypothesis
-Version: 6.105.1
+Version: 6.107.0
Summary: A library for property-based testing
Home-page: https://hypothesis.works
Author: David R. MacIver and Zac Hatfield-Dodds
diff --git a/contrib/python/hypothesis/py3/hypothesis/_settings.py b/contrib/python/hypothesis/py3/hypothesis/_settings.py
index 061292e9bd0..d337be4f579 100644
--- a/contrib/python/hypothesis/py3/hypothesis/_settings.py
+++ b/contrib/python/hypothesis/py3/hypothesis/_settings.py
@@ -482,7 +482,7 @@ class HealthCheck(Enum, metaclass=HealthCheckMeta):
def all(cls) -> List["HealthCheck"]:
# Skipping of deprecated attributes is handled in HealthCheckMeta.__iter__
note_deprecation(
- "`Healthcheck.all()` is deprecated; use `list(HealthCheck)` instead.",
+ "`HealthCheck.all()` is deprecated; use `list(HealthCheck)` instead.",
since="2023-04-16",
has_codemod=True,
stacklevel=1,
diff --git a/contrib/python/hypothesis/py3/hypothesis/control.py b/contrib/python/hypothesis/py3/hypothesis/control.py
index ea085b0b551..c9bcacc391a 100644
--- a/contrib/python/hypothesis/py3/hypothesis/control.py
+++ b/contrib/python/hypothesis/py3/hypothesis/control.py
@@ -138,13 +138,11 @@ class BuildContext:
# The printer will discard duplicates which return different representations.
self.known_object_printers = defaultdict(list)
- def record_call(self, obj, func, args, kwargs, arg_slices=None):
+ def record_call(self, obj, func, args, kwargs):
name = get_pretty_function_description(func)
self.known_object_printers[IDKey(obj)].append(
- lambda obj, p, cycle: (
- p.text("<...>")
- if cycle
- else p.repr_call(name, args, kwargs, arg_slices=arg_slices)
+ lambda obj, p, cycle: p.maybe_repr_known_object_as_call(
+ obj, cycle, name, args, kwargs
)
)
diff --git a/contrib/python/hypothesis/py3/hypothesis/core.py b/contrib/python/hypothesis/py3/hypothesis/core.py
index 69593c463b1..2e21b98f641 100644
--- a/contrib/python/hypothesis/py3/hypothesis/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/core.py
@@ -60,7 +60,6 @@ from hypothesis.errors import (
FailedHealthCheck,
Flaky,
Found,
- HypothesisDeprecationWarning,
HypothesisException,
HypothesisWarning,
InvalidArgument,
@@ -799,6 +798,15 @@ class StateForActualGivenExecution:
current_pytest_item.value, "nodeid", None
) or get_pretty_function_description(self.wrapped_test)
+ def _should_trace(self):
+ _trace_obs = TESTCASE_CALLBACKS and OBSERVABILITY_COLLECT_COVERAGE
+ _trace_failure = (
+ self.failed_normally
+ and not self.failed_due_to_deadline
+ and {Phase.shrink, Phase.explain}.issubset(self.settings.phases)
+ )
+ return _trace_obs or _trace_failure
+
def execute_once(
self,
data,
@@ -1007,35 +1015,7 @@ class StateForActualGivenExecution:
"""
trace: Trace = set()
try:
- # this is actually covered by our tests, but only on >= 3.12.
- if (
- sys.version_info[:2] >= (3, 12)
- and sys.monitoring.get_tool(MONITORING_TOOL_ID) is not None
- ): # pragma: no cover
- warnings.warn(
- "avoiding tracing test function because tool id "
- f"{MONITORING_TOOL_ID} is already taken by tool "
- f"{sys.monitoring.get_tool(MONITORING_TOOL_ID)}.",
- HypothesisWarning,
- # I'm not sure computing a correct stacklevel is reasonable
- # given the number of entry points here.
- stacklevel=1,
- )
-
- _can_trace = (
- (sys.version_info[:2] < (3, 12) and sys.gettrace() is None)
- or (
- sys.version_info[:2] >= (3, 12)
- and sys.monitoring.get_tool(MONITORING_TOOL_ID) is None
- )
- ) and not PYPY
- _trace_obs = TESTCASE_CALLBACKS and OBSERVABILITY_COLLECT_COVERAGE
- _trace_failure = (
- self.failed_normally
- and not self.failed_due_to_deadline
- and {Phase.shrink, Phase.explain}.issubset(self.settings.phases)
- )
- if _can_trace and (_trace_obs or _trace_failure): # pragma: no cover
+ if self._should_trace() and Tracer.can_trace(): # pragma: no cover
# This is in fact covered by our *non-coverage* tests, but due to the
# settrace() contention *not* by our coverage tests. Ah well.
with Tracer() as tracer:
@@ -1063,7 +1043,6 @@ class StateForActualGivenExecution:
# OK to re-raise it.
raise
except (
- HypothesisDeprecationWarning,
FailedHealthCheck,
*skip_exceptions_to_reraise(),
):
@@ -1071,12 +1050,11 @@ class StateForActualGivenExecution:
# engine, so we re-raise them.
raise
except failure_exceptions_to_catch() as e:
- # If the error was raised by Hypothesis-internal code, re-raise it
- # as a fatal error instead of treating it as a test failure.
+ # If an unhandled (i.e., non-Hypothesis) error was raised by
+ # Hypothesis-internal code, re-raise it as a fatal error instead
+ # of treating it as a test failure.
filepath = traceback.extract_tb(e.__traceback__)[-1][0]
- if is_hypothesis_file(filepath) and not isinstance(
- e, (HypothesisException, StopTest, UnsatisfiedAssumption)
- ):
+ if is_hypothesis_file(filepath) and not isinstance(e, HypothesisException):
raise
if data.frozen:
@@ -1184,6 +1162,23 @@ class StateForActualGivenExecution:
rep = get_pretty_function_description(self.test)
raise Unsatisfiable(f"Unable to satisfy assumptions of {rep}")
+ # If we have not traced executions, warn about that now (but only when
+ # we'd expect to do so reliably, i.e. on CPython>=3.12)
+ if (
+ sys.version_info[:2] >= (3, 12)
+ and not PYPY
+ and self._should_trace()
+ and not Tracer.can_trace()
+ ): # pragma: no cover
+ # actually covered by our tests, but only on >= 3.12
+ warnings.warn(
+ "avoiding tracing test function because tool id "
+ f"{MONITORING_TOOL_ID} is already taken by tool "
+ f"{sys.monitoring.get_tool(MONITORING_TOOL_ID)}.",
+ HypothesisWarning,
+ stacklevel=3,
+ )
+
if not self.falsifying_examples:
return
elif not (self.settings.report_multiple_bugs and pytest_shows_exceptiongroups):
@@ -1222,10 +1217,20 @@ class StateForActualGivenExecution:
info._expected_traceback,
),
)
- except (UnsatisfiedAssumption, StopTest) as e:
+ except StopTest:
+ err = Flaky(
+ "Inconsistent results: An example which failed on the "
+ "first run now succeeds (or fails with another error)."
+ )
+ # Link the expected exception from the first run. Not sure
+ # how to access the current exception, if it failed
+ # differently on this run.
+ err.__cause__ = err.__context__ = info._expected_exception
+ errors_to_report.append((fragments, err))
+ except UnsatisfiedAssumption as e: # pragma: no cover # ironically flaky
err = Flaky(
"Unreliable assumption: An example which satisfied "
- "assumptions on the first run now fails it.",
+ "assumptions on the first run now fails it."
)
err.__cause__ = err.__context__ = e
errors_to_report.append((fragments, err))
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/codemods.py b/contrib/python/hypothesis/py3/hypothesis/extra/codemods.py
index 3de0580ada3..85d1d44fd66 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/codemods.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/codemods.py
@@ -68,7 +68,7 @@ def refactor(code: str) -> str:
transforms: List[VisitorBasedCodemodCommand] = [
HypothesisFixPositionalKeywonlyArgs(context),
HypothesisFixComplexMinMagnitude(context),
- HypothesisFixHealthcheckAll(context),
+ HypothesisFixHealthCheckAll(context),
HypothesisFixCharactersArguments(context),
]
for transform in transforms:
@@ -228,16 +228,16 @@ class HypothesisFixPositionalKeywonlyArgs(VisitorBasedCodemodCommand):
return updated_node.with_changes(args=newargs)
-class HypothesisFixHealthcheckAll(VisitorBasedCodemodCommand):
- """Replace Healthcheck.all() with list(Healthcheck)"""
+class HypothesisFixHealthCheckAll(VisitorBasedCodemodCommand):
+ """Replace HealthCheck.all() with list(HealthCheck)"""
- DESCRIPTION = "Replace Healthcheck.all() with list(Healthcheck)"
+ DESCRIPTION = "Replace HealthCheck.all() with list(HealthCheck)"
- @m.leave(m.Call(func=m.Attribute(m.Name("Healthcheck"), m.Name("all")), args=[]))
+ @m.leave(m.Call(func=m.Attribute(m.Name("HealthCheck"), m.Name("all")), args=[]))
def replace_healthcheck(self, original_node, updated_node):
return updated_node.with_changes(
func=cst.Name("list"),
- args=[cst.Arg(value=cst.Name("Healthcheck"))],
+ args=[cst.Arg(value=cst.Name("HealthCheck"))],
)
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py b/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py
index 0c4a5d85234..9179e2d28fe 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py
@@ -603,10 +603,14 @@ def _assert_eq(style: str, a: str, b: str) -> str:
def _imports_for_object(obj):
"""Return the imports for `obj`, which may be empty for e.g. lambdas"""
+ if type(obj) is getattr(types, "UnionType", object()):
+ return {mod for mod, _ in set().union(*map(_imports_for_object, obj.__args__))}
if isinstance(obj, (re.Pattern, re.Match)):
return {"re"}
if isinstance(obj, st.SearchStrategy):
return _imports_for_strategy(obj)
+ if isinstance(obj, getattr(sys.modules.get("numpy"), "dtype", ())):
+ return {("numpy", "dtype")}
try:
if is_generic_type(obj):
if isinstance(obj, TypeVar):
@@ -772,7 +776,7 @@ def _get_module(obj):
raise RuntimeError(f"Could not find module for ufunc {obj.__name__} ({obj!r}")
-def _get_qualname(obj, *, include_module=False):
+def _get_qualname(obj: Any, *, include_module: bool = False) -> str:
# Replacing angle-brackets for objects defined in `.<locals>.`
qname = getattr(obj, "__qualname__", obj.__name__)
qname = qname.replace("<", "_").replace(">", "_").replace(" ", "")
@@ -1133,6 +1137,60 @@ ROUNDTRIP_PAIRS = (
)
+def _get_testable_functions(thing: object) -> Dict[str, Callable]:
+ by_name = {}
+ if callable(thing):
+ funcs: List[Optional[Any]] = [thing]
+ elif isinstance(thing, types.ModuleType):
+ if hasattr(thing, "__all__"):
+ funcs = [getattr(thing, name, None) for name in thing.__all__]
+ elif hasattr(thing, "__package__"):
+ pkg = thing.__package__
+ funcs = [
+ v
+ for k, v in vars(thing).items()
+ if callable(v)
+ and not is_mock(v)
+ and ((not pkg) or getattr(v, "__module__", pkg).startswith(pkg))
+ and not k.startswith("_")
+ ]
+ if pkg and any(getattr(f, "__module__", pkg) == pkg for f in funcs):
+ funcs = [f for f in funcs if getattr(f, "__module__", pkg) == pkg]
+ else:
+ raise InvalidArgument(f"Can't test non-module non-callable {thing!r}")
+
+ for f in list(funcs):
+ if inspect.isclass(f):
+ funcs += [
+ v.__get__(f)
+ for k, v in vars(f).items()
+ if hasattr(v, "__func__") and not is_mock(v) and not k.startswith("_")
+ ]
+ for f in funcs:
+ try:
+ if (
+ (not is_mock(f))
+ and callable(f)
+ and _get_params(f)
+ and not isinstance(f, enum.EnumMeta)
+ ):
+ if getattr(thing, "__name__", None):
+ if inspect.isclass(thing):
+ KNOWN_FUNCTION_LOCATIONS[f] = _get_module_helper(thing)
+ elif isinstance(thing, types.ModuleType):
+ KNOWN_FUNCTION_LOCATIONS[f] = thing.__name__
+ try:
+ _get_params(f)
+ by_name[_get_qualname(f, include_module=True)] = f
+ except Exception:
+ # usually inspect.signature on C code such as socket.inet_aton,
+ # or Pandas 'CallableDynamicDoc' object has no attr. '__name__'
+ pass
+ except (TypeError, ValueError):
+ pass
+ return by_name
+
+
def magic(
*modules_or_functions: Union[Callable, types.ModuleType],
except_: Except = (),
@@ -1157,85 +1215,42 @@ def magic(
_check_style(style)
if not modules_or_functions:
raise InvalidArgument("Must pass at least one function or module to test.")
- functions = set()
+
+ parts = []
+ by_name = {}
+ imports = set()
+
for thing in modules_or_functions:
- if callable(thing):
- functions.add(thing)
- # class need to be added for exploration
- if inspect.isclass(thing):
- funcs: List[Optional[Any]] = [thing]
- else:
- funcs = []
- elif isinstance(thing, types.ModuleType):
- if hasattr(thing, "__all__"):
- funcs = [getattr(thing, name, None) for name in thing.__all__]
- elif hasattr(thing, "__package__"):
- pkg = thing.__package__
- funcs = [
- v
- for k, v in vars(thing).items()
- if callable(v)
- and not is_mock(v)
- and ((not pkg) or getattr(v, "__module__", pkg).startswith(pkg))
- and not k.startswith("_")
- ]
- if pkg and any(getattr(f, "__module__", pkg) == pkg for f in funcs):
- funcs = [f for f in funcs if getattr(f, "__module__", pkg) == pkg]
- else:
- raise InvalidArgument(f"Can't test non-module non-callable {thing!r}")
-
- for f in list(funcs):
- if inspect.isclass(f):
- funcs += [
- v.__get__(f)
- for k, v in vars(f).items()
- if hasattr(v, "__func__")
- and not is_mock(v)
- and not k.startswith("_")
- ]
- for f in funcs:
- try:
+ by_name.update(found := _get_testable_functions(thing))
+ if (not found) and isinstance(thing, types.ModuleType):
+ msg = f"# Found no testable functions in {thing.__name__} (from {thing.__file__!r})"
+ mods: list = []
+ for k in sorted(sys.modules, key=len):
if (
- (not is_mock(f))
- and callable(f)
- and _get_params(f)
- and not isinstance(f, enum.EnumMeta)
+ k.startswith(f"{thing.__name__}.")
+ and "._" not in k[len(thing.__name__) :]
+ and not k.startswith(tuple(f"{m}." for m in mods))
+ and _get_testable_functions(sys.modules[k])
):
- functions.add(f)
- if getattr(thing, "__name__", None):
- if inspect.isclass(thing):
- KNOWN_FUNCTION_LOCATIONS[f] = _get_module_helper(thing)
- else:
- KNOWN_FUNCTION_LOCATIONS[f] = thing.__name__
- except (TypeError, ValueError):
- pass
+ mods.append(k)
+ if mods:
+ msg += (
+ f"\n# Try writing tests for submodules, e.g. by using:\n"
+ f"# hypothesis write {' '.join(sorted(mods))}"
+ )
+ parts.append(msg)
- if annotate is None:
- annotate = _are_annotations_used(*functions)
+ if not by_name:
+ return "\n\n".join(parts)
- imports = set()
- parts = []
+ if annotate is None:
+ annotate = _are_annotations_used(*by_name.values())
def make_(how, *args, **kwargs):
imp, body = how(*args, **kwargs, except_=except_, style=style)
imports.update(imp)
parts.append(body)
- by_name = {}
- for f in functions:
- try:
- _get_params(f)
- by_name[_get_qualname(f, include_module=True)] = f
- except Exception:
- # usually inspect.signature on C code such as socket.inet_aton, sometimes
- # e.g. Pandas 'CallableDynamicDoc' object has no attribute '__name__'
- pass
- if not by_name:
- return (
- f"# Found no testable functions in\n"
- f"# {functions!r} from {modules_or_functions}\n"
- )
-
# Look for pairs of functions that roundtrip, based on known naming patterns.
for writename, readname in ROUNDTRIP_PAIRS:
for name in sorted(by_name):
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py b/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
index 3754192d707..de0940c97d0 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
@@ -10,6 +10,7 @@
import importlib
import math
+import types
from typing import (
TYPE_CHECKING,
Any,
@@ -523,6 +524,11 @@ def arrays(
hundreds or more elements, having a fill value is essential if you want
your tests to run in reasonable time.
"""
+ # Our dtype argument might be a union, e.g. `np.float64 | np.complex64`; we handle
+ # that by turning it into a strategy up-front.
+ if type(dtype) in (getattr(types, "UnionType", object()), Union):
+ dtype = st.one_of(*(from_dtype(np.dtype(d)) for d in dtype.__args__)) # type: ignore
+
# We support passing strategies as arguments for convenience, or at least
# for legacy reasons, but don't want to pay the perf cost of a composite
# strategy (i.e. repeated argument handling and validation) when it's not
@@ -1279,7 +1285,7 @@ def _unpack_generic(thing):
def _unpack_dtype(dtype):
dtype_args = getattr(dtype, "__args__", ())
- if dtype_args:
+ if dtype_args and type(dtype) not in (getattr(types, "UnionType", object()), Union):
assert len(dtype_args) == 1
if isinstance(dtype_args[0], TypeVar):
# numpy.dtype[+ScalarType]
@@ -1291,22 +1297,22 @@ def _unpack_dtype(dtype):
return dtype
-def _dtype_and_shape_from_args(args):
+def _dtype_from_args(args):
if len(args) <= 1:
# Zero args: ndarray, _SupportsArray
# One arg: ndarray[type], _SupportsArray[type]
- shape = Any
dtype = _unpack_dtype(args[0]) if args else Any
else:
# Two args: ndarray[shape, type], NDArray[*]
assert len(args) == 2
- shape = args[0]
- assert shape is Any
+ assert args[0] is Any
dtype = _unpack_dtype(args[1])
- return (
- scalar_dtypes() if dtype is Any else np.dtype(dtype),
- array_shapes(max_dims=2) if shape is Any else shape,
- )
+
+ if dtype is Any:
+ return scalar_dtypes()
+ elif type(dtype) in (getattr(types, "UnionType", object()), Union):
+ return dtype
+ return np.dtype(dtype)
def _from_type(thing: Type[Ex]) -> Optional[st.SearchStrategy[Ex]]:
@@ -1394,8 +1400,8 @@ def _from_type(thing: Type[Ex]) -> Optional[st.SearchStrategy[Ex]]:
)
if real_thing in [np.ndarray, _SupportsArray]:
- dtype, shape = _dtype_and_shape_from_args(args)
- return arrays(dtype, shape) # type: ignore[return-value]
+ dtype = _dtype_from_args(args)
+ return arrays(dtype, array_shapes(max_dims=2)) # type: ignore[return-value]
# We didn't find a type to resolve, continue
return None
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/intervalsets.py b/contrib/python/hypothesis/py3/hypothesis/internal/intervalsets.py
index e48802ee77c..47abae66ea9 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/intervalsets.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/intervalsets.py
@@ -22,7 +22,7 @@ class IntervalSet:
x = cls((ord(c), ord(c)) for c in sorted(s))
return x.union(x)
- def __init__(self, intervals):
+ def __init__(self, intervals=()):
self.intervals = tuple(intervals)
self.offsets = [0]
for u, v in self.intervals:
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py b/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py
index d99e767c1d6..0cd760cf17e 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py
@@ -21,6 +21,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from hypothesis._settings import Phase, Verbosity
+from hypothesis.internal.compat import PYPY
from hypothesis.internal.escalation import is_hypothesis_file
if TYPE_CHECKING:
@@ -58,6 +59,16 @@ class Tracer:
self.branches: Trace = set()
self._previous_location = None
+ @staticmethod
+ def can_trace():
+ return (
+ (sys.version_info[:2] < (3, 12) and sys.gettrace() is None)
+ or (
+ sys.version_info[:2] >= (3, 12)
+ and sys.monitoring.get_tool(MONITORING_TOOL_ID) is None
+ )
+ ) and not PYPY
+
def trace(self, frame, event, arg):
try:
if event == "call":
@@ -80,8 +91,9 @@ class Tracer:
self._previous_location = current_location
def __enter__(self):
+ assert self.can_trace() # caller checks in core.py
+
if sys.version_info[:2] < (3, 12):
- assert sys.gettrace() is None # caller checks in core.py
sys.settrace(self.trace)
return self
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
index 570645509d6..572679d4033 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
@@ -21,7 +21,7 @@ import warnings
from contextvars import ContextVar
from decimal import Context, Decimal, localcontext
from fractions import Fraction
-from functools import lru_cache, reduce
+from functools import reduce
from inspect import Parameter, Signature, isabstract, isclass
from types import FunctionType
from typing import (
@@ -137,6 +137,7 @@ from hypothesis.strategies._internal.strings import (
BytesStrategy,
OneCharStringStrategy,
TextStrategy,
+ _check_is_single_character,
)
from hypothesis.strategies._internal.utils import (
cacheable,
@@ -789,19 +790,6 @@ characters.__signature__ = (__sig := get_signature(characters)).replace( # type
)
-# Cache size is limited by sys.maxunicode, but passing None makes it slightly faster.
-@lru_cache(maxsize=None)
-def _check_is_single_character(c):
- # In order to mitigate the performance cost of this check, we use a shared cache,
- # even at the cost of showing the culprit strategy in the error message.
- if not isinstance(c, str):
- type_ = get_pretty_function_description(type(c))
- raise InvalidArgument(f"Got non-string {c!r} (type {type_})")
- if len(c) != 1:
- raise InvalidArgument(f"Got {c!r} (length {len(c)} != 1)")
- return c
-
-
@cacheable
@defines_strategy(force_reusable_values=True)
def text(
@@ -924,19 +912,7 @@ def from_regex(
check_type((str, SearchStrategy), alphabet, "alphabet")
if not isinstance(pattern, str):
raise InvalidArgument("alphabet= is not supported for bytestrings")
-
- if isinstance(alphabet, str):
- alphabet = characters(categories=(), include_characters=alphabet)
- char_strategy = unwrap_strategies(alphabet)
- if isinstance(char_strategy, SampledFromStrategy):
- alphabet = characters(
- categories=(),
- include_characters=alphabet.elements, # type: ignore
- )
- elif not isinstance(char_strategy, OneCharStringStrategy):
- raise InvalidArgument(
- f"{alphabet=} must be a sampled_from() or characters() strategy"
- )
+ alphabet = OneCharStringStrategy.from_alphabet(alphabet)
elif isinstance(pattern, str):
alphabet = characters(codec="utf-8")
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strings.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strings.py
index 8df955e632a..b6e6dd8deb3 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strings.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strings.py
@@ -17,13 +17,31 @@ from hypothesis.errors import HypothesisWarning, InvalidArgument
from hypothesis.internal import charmap
from hypothesis.internal.filtering import max_len, min_len
from hypothesis.internal.intervalsets import IntervalSet
+from hypothesis.internal.reflection import get_pretty_function_description
from hypothesis.strategies._internal.collections import ListStrategy
from hypothesis.strategies._internal.lazy import unwrap_strategies
from hypothesis.strategies._internal.numbers import IntegersStrategy
-from hypothesis.strategies._internal.strategies import SearchStrategy
+from hypothesis.strategies._internal.strategies import (
+ OneOfStrategy,
+ SampledFromStrategy,
+ SearchStrategy,
+)
from hypothesis.vendor.pretty import pretty
+# Cache size is limited by sys.maxunicode, but passing None makes it slightly faster.
+@lru_cache(maxsize=None)
+def _check_is_single_character(c):
+ # In order to mitigate the performance cost of this check, we use a shared cache,
+ # even at the cost of showing the culprit strategy in the error message.
+ if not isinstance(c, str):
+ type_ = get_pretty_function_description(type(c))
+ raise InvalidArgument(f"Got non-string {c!r} (type {type_})")
+ if len(c) != 1:
+ raise InvalidArgument(f"Got {c!r} (length {len(c)} != 1)")
+ return c
+
+
class OneCharStringStrategy(SearchStrategy):
"""A strategy which generates single character strings of text type."""
@@ -72,6 +90,31 @@ class OneCharStringStrategy(SearchStrategy):
)
return cls(intervals, force_repr=f"characters({_arg_repr})")
+ @classmethod
+ def from_alphabet(cls, alphabet):
+ if isinstance(alphabet, str):
+ return cls.from_characters_args(categories=(), include_characters=alphabet)
+
+ assert isinstance(alphabet, SearchStrategy)
+ char_strategy = unwrap_strategies(alphabet)
+ if isinstance(char_strategy, cls):
+ return char_strategy
+ elif isinstance(char_strategy, SampledFromStrategy):
+ for c in char_strategy.elements:
+ _check_is_single_character(c)
+ return cls.from_characters_args(
+ categories=(),
+ include_characters=char_strategy.elements,
+ )
+ elif isinstance(char_strategy, OneOfStrategy):
+ intervals = IntervalSet()
+ for s in char_strategy.element_strategies:
+ intervals = intervals.union(cls.from_alphabet(s).intervals)
+ return cls(intervals, force_repr=repr(alphabet))
+ raise InvalidArgument(
+ f"{alphabet=} must be a sampled_from() or characters() strategy"
+ )
+
def __repr__(self):
return self._force_repr or f"OneCharStringStrategy({self.intervals!r})"
@@ -189,13 +232,10 @@ def _string_filter_rewrite(self, kind, condition):
):
from hypothesis.strategies._internal.regex import regex_strategy
- print(f"{condition=}")
- print(f"{condition.__name__=}")
-
if condition.__name__ == "match":
# Replace with an easier-to-handle equivalent condition
- caret = "^" if kind is str else b"^"
- pattern = re.compile(caret + pattern.pattern, flags=pattern.flags)
+ caret, close = ("^(?:", ")") if kind is str else (b"^(?:", b")")
+ pattern = re.compile(caret + pattern.pattern + close, flags=pattern.flags)
condition = pattern.search
if condition.__name__ in ("search", "findall", "fullmatch"):
diff --git a/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py b/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py
index ceffe3a6aa2..6b4718ff79c 100644
--- a/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py
+++ b/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py
@@ -62,6 +62,7 @@ Inheritance diagram:
:license: BSD License.
"""
+import ast
import datetime
import re
import struct
@@ -69,7 +70,7 @@ import sys
import types
import warnings
from collections import defaultdict, deque
-from contextlib import contextmanager
+from contextlib import contextmanager, suppress
from enum import Flag
from io import StringIO
from math import copysign, isnan
@@ -373,6 +374,29 @@ class RepresentationPrinter:
self.flush()
return self.output.getvalue()
+ def maybe_repr_known_object_as_call(self, obj, cycle, name, args, kwargs):
+ # pprint this object as a call, _unless_ the call would be invalid syntax
+ # and the repr would be valid and there are not comments on arguments.
+ if cycle:
+ return self.text("<...>")
+ # Since we don't yet track comments for sub-argument parts, we omit the
+ # "if no comments" condition here for now. Add it when we revive
+ # https://github.com/HypothesisWorks/hypothesis/pull/3624/
+ with suppress(Exception):
+ # Check whether the repr is valid syntax:
+ ast.parse(repr(obj))
+ # Given that the repr is valid syntax, check the call:
+ p = RepresentationPrinter()
+ p.stack = self.stack.copy()
+ p.known_object_printers = self.known_object_printers
+ p.repr_call(name, args, kwargs)
+ # If the call is not valid syntax, use the repr
+ try:
+ ast.parse(p.getvalue())
+ except Exception:
+ return _repr_pprint(obj, self, cycle)
+ return self.repr_call(name, args, kwargs)
+
def repr_call(
self,
func_name,
@@ -423,6 +447,7 @@ class RepresentationPrinter:
self.text(leading_comment)
self.break_()
else:
+ assert leading_comment is None # only passed by top-level report
self.breakable(" " if i else "")
if k:
self.text(f"{k}=")
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index 1883646c339..d81e1c4cef8 100644
--- a/contrib/python/hypothesis/py3/hypothesis/version.py
+++ b/contrib/python/hypothesis/py3/hypothesis/version.py
@@ -8,5 +8,5 @@
# 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/.
-__version_info__ = (6, 105, 1)
+__version_info__ = (6, 107, 0)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index 13e59544308..adf8c798663 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.105.1)
+VERSION(6.107.0)
LICENSE(MPL-2.0)