Skip to content

Commit

Permalink
Get annotated types
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Sep 6, 2021
1 parent 591031c commit 80b49d6
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 12 deletions.
15 changes: 13 additions & 2 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
RELEASE_TYPE: minor

Adds ability to specify hypothesis strategies in :obj:`python:typing.Annotated`.
Example: ``PositiveInt = Annotated[int, st.integer(min_value=1)]``
This release teaches :func:`~hypothesis.strategies.from_type` a neat trick:
when resolving an :obj:`python:typing.Annotated` type, if one of the annotations
is a strategy object we use that as the inferred strategy. For example:

.. code-block:: python
PositiveInt = Annotated[int, st.integers(min_value=1)]
If there are multiple strategies, we use the last outer-most annotation.
See :issue:`2978` and :pull:`3082` for discussion.

*Requires Python 3.9 or later for*
:func:`get_type_hints(..., include_extras=False) <typing.get_type_hints>`.
13 changes: 4 additions & 9 deletions hypothesis-python/src/hypothesis/internal/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,25 @@ def get_type_hints(thing):
Never errors: instead of raising TypeError for uninspectable objects, or
NameError for unresolvable forward references, just return an empty dict.
"""
kwargs = {} if sys.version_info[:2] < (3, 9) else {"include_extras": True}

try:
hints = typing.get_type_hints(thing)
hints = typing.get_type_hints(thing, **kwargs)
except (AttributeError, TypeError, NameError):
hints = {}

if not inspect.isclass(thing):
return hints

try:
hints.update(typing.get_type_hints(thing.__init__))
hints.update(typing.get_type_hints(thing.__init__, **kwargs))
except (TypeError, NameError, AttributeError):
pass

try:
if hasattr(thing, "__signature__"):
# It is possible for the signature and annotations attributes to
# differ on an object due to renamed arguments.
# To prevent missing arguments we use the signature to provide any type
# hints it has and then override any common names with the more
# comprehensive type information from get_type_hints
# See https://github.com/HypothesisWorks/hypothesis/pull/2580
# for more details.
from hypothesis.strategies._internal.types import is_a_type

vkinds = (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
Expand All @@ -112,8 +109,6 @@ def get_type_hints(thing):
hints[p.name] = typing.Optional[p.annotation]
else:
hints[p.name] = p.annotation
else: # pragma: no cover
pass
except (AttributeError, TypeError, NameError): # pragma: no cover
pass

Expand Down
13 changes: 12 additions & 1 deletion hypothesis-python/tests/cover/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
# END HEADER

import math
from inspect import Parameter, Signature

import pytest

from hypothesis.internal.compat import ceil, floor
from hypothesis.internal.compat import ceil, floor, get_type_hints

floor_ceil_values = [
-10.7,
Expand All @@ -39,3 +40,13 @@ def test_our_floor_agrees_with_math_floor(value):
@pytest.mark.parametrize("value", floor_ceil_values)
def test_our_ceil_agrees_with_math_ceil(value):
assert ceil(value) == math.ceil(value)


class WeirdSig:
__signature__ = Signature(
parameters=[Parameter(name="args", kind=Parameter.VAR_POSITIONAL)]
)


def test_no_type_hints():
assert get_type_hints(WeirdSig) == {}

0 comments on commit 80b49d6

Please sign in to comment.