Skip to content

Commit

Permalink
Merge pull request #3396 from Zac-HD/paramspec-for-composite
Browse files Browse the repository at this point in the history
Use `ParamSpec` to precisely annotate `@st.composite` and `st.functions()`
  • Loading branch information
Zac-HD authored Jul 4, 2022
2 parents 6e44301 + 860bb3a commit 45b35c5
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 76 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ their individual contributions.
* `Felix Sheldon <https://www.github.com/darkpaw>`_
* `Florian Bruhin <https://www.github.com/The-Compiler>`_
* `follower <https://www.github.com/follower>`_
* `Gabe Joseph <https://github.com/gjoseph92>`_
* `Gary Donovan <https://www.github.com/garyd203>`_
* `George Macon <https://www.github.com/gmacon>`_
* `Glenn Lehman <https://www.github.com/glnnlhmn>`_
Expand Down
7 changes: 7 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
RELEASE_TYPE: minor

This release uses :pep:`612` :obj:`python:typing.ParamSpec` (or the
:pypi:`typing_extensions` backport) to express the first-argument-removing
behaviour of :func:`@st.composite <hypothesis.strategies.composite>`
and signature-preservation of :func:`~hypothesis.strategies.functions`
to IDEs, editor plugins, and static type checkers such as :pypi:`mypy`.
6 changes: 3 additions & 3 deletions hypothesis-python/src/hypothesis/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,19 +148,19 @@ def __init__(
# The intended use is "like **kwargs, but more tractable for tooling".
max_examples: int = not_set, # type: ignore
derandomize: bool = not_set, # type: ignore
database: Union[None, "ExampleDatabase"] = not_set, # type: ignore
database: Optional["ExampleDatabase"] = not_set, # type: ignore
verbosity: "Verbosity" = not_set, # type: ignore
phases: Collection["Phase"] = not_set, # type: ignore
stateful_step_count: int = not_set, # type: ignore
report_multiple_bugs: bool = not_set, # type: ignore
suppress_health_check: Collection["HealthCheck"] = not_set, # type: ignore
deadline: Union[None, int, float, datetime.timedelta] = not_set, # type: ignore
deadline: Union[int, float, datetime.timedelta, None] = not_set, # type: ignore
print_blob: bool = not_set, # type: ignore
) -> None:
if parent is not None:
check_type(settings, parent, "parent")
if derandomize not in (not_set, False):
if database not in (not_set, None):
if database not in (not_set, None): # type: ignore
raise InvalidArgument(
"derandomize=True implies database=None, so passing "
f"database={database!r} too is invalid."
Expand Down
6 changes: 3 additions & 3 deletions hypothesis-python/src/hypothesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,7 +1001,7 @@ def as_param(name, kind, defaults):
def given(
*_given_arguments: Union[SearchStrategy[Any], InferType],
) -> Callable[
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
[Callable[..., Optional[Coroutine[Any, Any, None]]]], Callable[..., None]
]: # pragma: no cover
...

Expand All @@ -1010,7 +1010,7 @@ def given(
def given(
**_given_kwargs: Union[SearchStrategy[Any], InferType],
) -> Callable[
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
[Callable[..., Optional[Coroutine[Any, Any, None]]]], Callable[..., None]
]: # pragma: no cover
...

Expand All @@ -1019,7 +1019,7 @@ def given(
*_given_arguments: Union[SearchStrategy[Any], InferType],
**_given_kwargs: Union[SearchStrategy[Any], InferType],
) -> Callable[
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
[Callable[..., Optional[Coroutine[Any, Any, None]]]], Callable[..., None]
]:
"""A decorator for turning a test function that accepts arguments into a
randomized test.
Expand Down
8 changes: 4 additions & 4 deletions hypothesis-python/src/hypothesis/internal/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@
WINDOWS = platform.system() == "Windows"


def escape_unicode_characters(s):
def escape_unicode_characters(s: str) -> str:
return codecs.encode(s, "unicode_escape").decode("ascii")


def int_from_bytes(data):
def int_from_bytes(data: typing.Union[bytes, bytearray]) -> int:
return int.from_bytes(data, "big")


def int_to_bytes(i, size):
def int_to_bytes(i: int, size: int) -> bytes:
return i.to_bytes(size, "big")


def int_to_byte(i):
def int_to_byte(i: int) -> bytes:
return bytes([i])


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def __init__(self):
self.n: "Optional[int]" = None

@property
def exhausted(self):
def exhausted(self) -> bool:
return self.live_child_count == 0


Expand Down
5 changes: 3 additions & 2 deletions hypothesis-python/src/hypothesis/internal/conjecture/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from typing_extensions import dataclass_transform

from hypothesis.strategies import SearchStrategy
from hypothesis.strategies._internal.strategies import Ex
else:

def dataclass_transform():
Expand Down Expand Up @@ -816,7 +817,7 @@ def __init__(
self.max_length = max_length
self.is_find = False
self.overdraw = 0
self.__prefix = prefix
self.__prefix = bytes(prefix)
self.__random = random

assert random is not None or max_length <= len(prefix)
Expand Down Expand Up @@ -907,7 +908,7 @@ def note(self, value: Any) -> None:
value = repr(value)
self.output += value

def draw(self, strategy: "SearchStrategy[Any]", label: Optional[int] = None) -> Any:
def draw(self, strategy: "SearchStrategy[Ex]", label: Optional[int] = None) -> "Ex":
if self.is_find and not strategy.supports_find:
raise InvalidArgument(
f"Cannot use strategy {strategy!r} within a call to find "
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/src/hypothesis/internal/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ConstructivePredicate(NamedTuple):
predicate: Optional[Predicate]

@classmethod
def unchanged(cls, predicate):
def unchanged(cls, predicate: Predicate) -> "ConstructivePredicate":
return cls({}, predicate)


Expand Down
10 changes: 3 additions & 7 deletions hypothesis-python/src/hypothesis/internal/floats.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import math
import struct
from sys import float_info
from typing import Callable, Optional
from typing import Callable, Optional, SupportsFloat

# Format codes for (int, float) sized types, used for byte-wise casts.
# See https://docs.python.org/3/library/struct.html#format-characters
Expand All @@ -36,19 +36,15 @@ def float_of(x, width):
return reinterpret_bits(float(x), "!e", "!e")


def sign(x):
def is_negative(x: SupportsFloat) -> bool:
try:
return math.copysign(1.0, x)
return math.copysign(1.0, x) < 0
except TypeError:
raise TypeError(
f"Expected float but got {x!r} of type {type(x).__name__}"
) from None


def is_negative(x):
return sign(x) < 0


def count_between_floats(x, y, width=64):
assert x <= y
if is_negative(x):
Expand Down
Loading

0 comments on commit 45b35c5

Please sign in to comment.