Skip to content

Commit

Permalink
add support for TypeAliasType to from_type and register_type_strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
tybug committed Feb 22, 2025
1 parent 54ad985 commit be00394
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 1 deletion.
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: minor

This releases adds support for type aliases created with the :py:keyword:`type` statement (new in python 3.12) to :func:`~hypothesis.strategies.from_type` and :func:`~hypothesis.strategies.register_type_strategy`.
6 changes: 6 additions & 0 deletions hypothesis-python/src/hypothesis/strategies/_internal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,12 @@ def from_type_guarded(thing):
if strategy is not NotImplemented:
return strategy
return _from_type(thing.__supertype__)
if types.is_a_type_alias_type(thing):
if thing in types._global_type_lookup:
strategy = as_strategy(types._global_type_lookup[thing], thing)
if strategy is not NotImplemented:
return strategy
return _from_type(thing.__value__)
# Unions are not instances of `type` - but we still want to resolve them!
if types.is_a_union(thing):
args = sorted(thing.__args__, key=types.type_sorting_key)
Expand Down
18 changes: 17 additions & 1 deletion hypothesis-python/src/hypothesis/strategies/_internal/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,30 @@ def is_a_new_type(thing):
return isinstance(thing, typing.NewType)


def is_a_type_alias_type(thing):
# TypeAliasType is new in python 3.12, through the type statement. If we're
# before python 3.12 then this can't possibly by a TypeAliasType.
#
# https://docs.python.org/3/reference/simple_stmts.html#type
# https://docs.python.org/3/library/typing.html#typing.TypeAliasType
if sys.version_info < (3, 12):
return False
return isinstance(thing, typing.TypeAliasType)


def is_a_union(thing: object) -> bool:
"""Return True if thing is a typing.Union or types.UnionType (in py310)."""
return isinstance(thing, UnionType) or get_origin(thing) is typing.Union


def is_a_type(thing: object) -> bool:
"""Return True if thing is a type or a generic type like thing."""
return isinstance(thing, type) or is_generic_type(thing) or is_a_new_type(thing)
return (
isinstance(thing, type)
or is_generic_type(thing)
or is_a_new_type(thing)
or is_a_type_alias_type(thing)
)


def is_typing_literal(thing: object) -> bool:
Expand Down
2 changes: 2 additions & 0 deletions hypothesis-python/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
collect_ignore_glob = ["django/*"]
if sys.version_info < (3, 10):
collect_ignore_glob.append("cover/*py310*")
if sys.version_info < (3, 12):
collect_ignore_glob.append("cover/*py312*.py")

if sys.version_info >= (3, 11):
collect_ignore_glob.append("cover/test_asyncio.py") # @asyncio.coroutine removed
Expand Down
67 changes: 67 additions & 0 deletions hypothesis-python/tests/cover/test_typealias_py312.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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/.

import pytest

from hypothesis import strategies as st

from tests.common.debug import assert_simple_property, find_any
from tests.common.utils import temp_registered


def test_resolves_simple_typealias():
type MyInt = int
type AliasedInt = MyInt
type MaybeInt = int | None

assert_simple_property(st.from_type(MyInt), lambda x: isinstance(x, int))
assert_simple_property(st.from_type(AliasedInt), lambda x: isinstance(x, int))
assert_simple_property(
st.from_type(MaybeInt), lambda x: isinstance(x, int) or x is None
)

find_any(st.from_type(MaybeInt), lambda x: isinstance(x, int))
find_any(st.from_type(MaybeInt), lambda x: x is None)


def test_resolves_nested():
type Point1 = int
type Point2 = Point1
type Point3 = Point2

assert_simple_property(st.from_type(Point3), lambda x: isinstance(x, int))


def test_mutually_recursive_fails():
# example from
# https://docs.python.org/3/library/typing.html#typing.TypeAliasType.__value__
type A = B
type B = A

# I guess giving a nicer error here would be good, but detecting this in general
# is...complicated.
with pytest.raises(RecursionError):
find_any(st.from_type(A))


def test_can_register_typealias():
type A = int
st.register_type_strategy(A, st.just("a"))
assert_simple_property(st.from_type(A), lambda x: x == "a")


def test_prefers_manually_registered_typealias():
# manually registering a `type A = ...` should override automatic detection
type A = int

assert_simple_property(st.from_type(A), lambda x: isinstance(x, int))

with temp_registered(A, st.booleans()):
assert_simple_property(st.from_type(A), lambda x: isinstance(x, bool))

0 comments on commit be00394

Please sign in to comment.