Skip to content

Commit f35c8f2

Browse files
authored
Merge pull request #4550 from Liam-DeVoe/mypyc
Add a `st.characters` validation, and mypyc typing changes
2 parents 4238f1b + 95c70a1 commit f35c8f2

File tree

21 files changed

+121
-86
lines changed

21 files changed

+121
-86
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# mypyc
2+
3+
*.so
4+
15
# misc (editors, file systems, etc)
26

37
*.swo

hypothesis-python/RELEASE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: minor
2+
3+
|st.characters| now validates that the elements of the ``exclude_characters`` and ``include_characters`` arguments are single characters, which was always assumed internally. For example, ``exclude_characters=["a", "b"]`` is valid while ``exclude_characters=["ab"]`` will now raise an error up-front.

hypothesis-python/src/hypothesis/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ def __init__(
942942
self._timing_features = {}
943943

944944
@property
945-
def test_identifier(self):
945+
def test_identifier(self) -> str:
946946
return getattr(
947947
current_pytest_item.value, "nodeid", None
948948
) or get_pretty_function_description(self.wrapped_test)

hypothesis-python/src/hypothesis/internal/charmap.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import sys
1616
import tempfile
1717
import unicodedata
18-
from collections.abc import Iterable
18+
from collections.abc import Collection, Iterable
1919
from functools import cache
2020
from pathlib import Path
2121
from typing import TYPE_CHECKING, Literal, Optional
@@ -78,7 +78,7 @@ def charmap_file(fname: str = "charmap") -> Path:
7878
)
7979

8080

81-
_charmap = None
81+
_charmap: Optional[dict[CategoryName, IntervalsT]] = None
8282

8383

8484
def charmap() -> dict[CategoryName, IntervalsT]:
@@ -293,8 +293,8 @@ def query(
293293
categories: Optional[Categories] = None,
294294
min_codepoint: Optional[int] = None,
295295
max_codepoint: Optional[int] = None,
296-
include_characters: str = "",
297-
exclude_characters: str = "",
296+
include_characters: Collection[str] = "",
297+
exclude_characters: Collection[str] = "",
298298
) -> IntervalSet:
299299
"""Return a tuple of intervals covering the codepoints for all characters
300300
that meet the criteria.
@@ -314,8 +314,8 @@ def query(
314314
if max_codepoint is None:
315315
max_codepoint = sys.maxunicode
316316
catkey = _category_key(categories)
317-
character_intervals = IntervalSet.from_string(include_characters or "")
318-
exclude_intervals = IntervalSet.from_string(exclude_characters or "")
317+
character_intervals = IntervalSet.from_string("".join(include_characters))
318+
exclude_intervals = IntervalSet.from_string("".join(exclude_characters))
319319
qkey = (
320320
catkey,
321321
min_codepoint,

hypothesis-python/src/hypothesis/internal/conjecture/data.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,6 @@ def __getattr__(name: str) -> Any:
115115
threadlocal = ThreadLocal(global_test_counter=int)
116116

117117

118-
class ExtraInformation:
119-
"""A class for holding shared state on a ``ConjectureData`` that should
120-
be added to the final ``ConjectureResult``."""
121-
122-
def __repr__(self) -> str:
123-
return "ExtraInformation({})".format(
124-
", ".join(f"{k}={v!r}" for k, v in self.__dict__.items()),
125-
)
126-
127-
def has_information(self) -> bool:
128-
return bool(self.__dict__)
129-
130-
131118
class Status(IntEnum):
132119
OVERRUN = 0
133120
INVALID = 1
@@ -597,7 +584,6 @@ class ConjectureResult:
597584
nodes: tuple[ChoiceNode, ...] = attr.ib(eq=False, repr=False)
598585
length: int = attr.ib()
599586
output: str = attr.ib()
600-
extra_information: Optional[ExtraInformation] = attr.ib()
601587
expected_exception: Optional[BaseException] = attr.ib()
602588
expected_traceback: Optional[str] = attr.ib()
603589
has_discards: bool = attr.ib()
@@ -725,11 +711,10 @@ def __init__(
725711
self._stateful_repr_parts: Optional[list[Any]] = None
726712
self.states_for_ids: Optional[dict[int, RandomState]] = None
727713
self.seeds_to_states: Optional[dict[Any, RandomState]] = None
728-
self.hypothesis_runner = not_set
714+
self.hypothesis_runner: Any = not_set
729715

730716
self.expected_exception: Optional[BaseException] = None
731717
self.expected_traceback: Optional[str] = None
732-
self.extra_information = ExtraInformation()
733718

734719
self.prefix = prefix
735720
self.nodes: tuple[ChoiceNode, ...] = ()
@@ -1165,11 +1150,6 @@ def as_result(self) -> Union[ConjectureResult, _Overrun]:
11651150
output=self.output,
11661151
expected_traceback=self.expected_traceback,
11671152
expected_exception=self.expected_exception,
1168-
extra_information=(
1169-
self.extra_information
1170-
if self.extra_information.has_information()
1171-
else None
1172-
),
11731153
has_discards=self.has_discards,
11741154
target_observations=self.target_observations,
11751155
tags=frozenset(self.tags),

hypothesis-python/src/hypothesis/internal/conjecture/datatree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def compute_max_children(
220220
# or downwards with our full 128 bit generation, but only half of these
221221
# (plus one for the case of generating zero) result in a probe in the
222222
# direction we want. ((2**128 - 1) // 2) + 1 == 2 ** 127
223-
assert (min_value is None) ^ (max_value is None)
223+
assert (min_value is None) != (max_value is None)
224224
return 2**127
225225
elif choice_type == "boolean":
226226
constraints = cast(BooleanConstraints, constraints)

hypothesis-python/src/hypothesis/internal/conjecture/engine.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from datetime import timedelta
2121
from enum import Enum
2222
from random import Random, getrandbits
23-
from typing import Callable, Final, Literal, NoReturn, Optional, Union, cast
23+
from typing import Callable, Literal, NoReturn, Optional, Union, cast
2424

2525
from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings
2626
from hypothesis._settings import local_settings, note_deprecation
@@ -71,11 +71,15 @@
7171
from hypothesis.internal.observability import Observation, with_observability_callback
7272
from hypothesis.reporting import base_report, report
7373

74+
# In most cases, the following constants are all Final. However, we do allow users
75+
# to monkeypatch all of these variables, which means we cannot annotate them as
76+
# Final or mypyc will inline them and render monkeypatching useless.
77+
7478
#: The maximum number of times the shrinker will reduce the complexity of a failing
7579
#: input before giving up. This avoids falling down a trap of exponential (or worse)
7680
#: complexity, where the shrinker appears to be making progress but will take a
7781
#: substantially long time to finish completely.
78-
MAX_SHRINKS: Final[int] = 500
82+
MAX_SHRINKS: int = 500
7983

8084
# If the shrinking phase takes more than five minutes, abort it early and print
8185
# a warning. Many CI systems will kill a build after around ten minutes with
@@ -87,17 +91,17 @@
8791
#: for before giving up. This is across all shrinks for the same failure, so even
8892
#: if the shrinker successfully reduces the complexity of a single failure several
8993
#: times, it will stop when it hits |MAX_SHRINKING_SECONDS| of total time taken.
90-
MAX_SHRINKING_SECONDS: Final[int] = 300
94+
MAX_SHRINKING_SECONDS: int = 300
9195

9296
#: The maximum amount of entropy a single test case can use before giving up
9397
#: while making random choices during input generation.
9498
#:
9599
#: The "unit" of one |BUFFER_SIZE| does not have any defined semantics, and you
96100
#: should not rely on it, except that a linear increase |BUFFER_SIZE| will linearly
97101
#: increase the amount of entropy a test case can use during generation.
98-
BUFFER_SIZE: Final[int] = 8 * 1024
99-
CACHE_SIZE: Final[int] = 10000
100-
MIN_TEST_CALLS: Final[int] = 10
102+
BUFFER_SIZE: int = 8 * 1024
103+
CACHE_SIZE: int = 10000
104+
MIN_TEST_CALLS: int = 10
101105

102106

103107
def shortlex(s):
@@ -166,8 +170,8 @@ class RunIsComplete(Exception):
166170

167171

168172
def _get_provider(backend: str) -> Union[type, PrimitiveProvider]:
169-
mname, cname = AVAILABLE_PROVIDERS[backend].rsplit(".", 1)
170-
provider_cls = getattr(importlib.import_module(mname), cname)
173+
module_name, class_name = AVAILABLE_PROVIDERS[backend].rsplit(".", 1)
174+
provider_cls = getattr(importlib.import_module(module_name), class_name)
171175
if provider_cls.lifetime == "test_function":
172176
return provider_cls(None)
173177
elif provider_cls.lifetime == "test_case":

hypothesis-python/src/hypothesis/internal/conjecture/shrinking/integer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def try_mask(k):
6161
return self.consider(mask & base)
6262

6363
@property
64-
def size(self):
64+
def size(self) -> int:
6565
return self.current.bit_length()
6666

6767
def shrink_by_multiples(self, k):

hypothesis-python/src/hypothesis/internal/escalation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
import sys
1414
import textwrap
1515
import traceback
16+
from dataclasses import dataclass
1617
from functools import partial
1718
from inspect import getframeinfo
1819
from pathlib import Path
1920
from types import ModuleType, TracebackType
20-
from typing import Callable, NamedTuple, Optional
21+
from typing import Callable, Optional
2122

2223
import hypothesis
2324
from hypothesis.errors import _Trimmable
@@ -90,7 +91,8 @@ def get_trimmed_traceback(
9091
return tb
9192

9293

93-
class InterestingOrigin(NamedTuple):
94+
@dataclass(frozen=True)
95+
class InterestingOrigin:
9496
# The `interesting_origin` is how Hypothesis distinguishes between multiple
9597
# failures, for reporting and also to replay from the example database (even
9698
# if report_multiple_bugs=False). We traditionally use the exception type and

hypothesis-python/src/hypothesis/internal/floats.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838

3939

40-
def reinterpret_bits(x: float, from_: str, to: str) -> float:
40+
def reinterpret_bits(x: Union[float, int], from_: str, to: str) -> Union[float, int]:
4141
x = struct.unpack(to, struct.pack(from_, x))[0]
4242
assert isinstance(x, (float, int))
4343
return x
@@ -185,15 +185,17 @@ def float_clamper(f: float) -> float:
185185
return float_clamper
186186

187187

188-
def sign_aware_lte(x: float, y: float) -> bool:
188+
def sign_aware_lte(x: Union[float, int], y: Union[float, int]) -> bool:
189189
"""Less-than-or-equals, but strictly orders -0.0 and 0.0"""
190190
if x == 0.0 == y:
191191
return math.copysign(1.0, x) <= math.copysign(1.0, y)
192192
else:
193193
return x <= y
194194

195195

196-
def clamp(lower: float, value: float, upper: float) -> float:
196+
def clamp(
197+
lower: Union[float, int], value: Union[float, int], upper: Union[float, int]
198+
) -> Union[float, int]:
197199
"""Given a value and lower/upper bounds, 'clamp' the value so that
198200
it satisfies lower <= value <= upper. NaN is mapped to lower."""
199201
# this seems pointless (and is for integers), but handles the -0.0/0.0 case.

0 commit comments

Comments
 (0)