Skip to content

Commit a07c5af

Browse files
authored
Merge pull request #4272 from tybug/fromtype-alias
Add support for `TypeAliasType` to `from_type` and `register_type_strategy`
2 parents 54ad985 + f7451a3 commit a07c5af

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

hypothesis-python/RELEASE.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: minor
2+
3+
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`.

hypothesis-python/src/hypothesis/strategies/_internal/core.py

+8
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,14 @@ def from_type_guarded(thing):
12611261
if strategy is not NotImplemented:
12621262
return strategy
12631263
return _from_type(thing.__supertype__)
1264+
if types.is_a_type_alias_type(
1265+
thing
1266+
): # pragma: no cover # covered by 3.12+ tests
1267+
if thing in types._global_type_lookup:
1268+
strategy = as_strategy(types._global_type_lookup[thing], thing)
1269+
if strategy is not NotImplemented:
1270+
return strategy
1271+
return _from_type(thing.__value__)
12641272
# Unions are not instances of `type` - but we still want to resolve them!
12651273
if types.is_a_union(thing):
12661274
args = sorted(thing.__args__, key=types.type_sorting_key)

hypothesis-python/src/hypothesis/strategies/_internal/types.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -272,14 +272,30 @@ def is_a_new_type(thing):
272272
return isinstance(thing, typing.NewType)
273273

274274

275+
def is_a_type_alias_type(thing): # pragma: no cover # covered by 3.12+ tests
276+
# TypeAliasType is new in python 3.12, through the type statement. If we're
277+
# before python 3.12 then this can't possibly by a TypeAliasType.
278+
#
279+
# https://docs.python.org/3/reference/simple_stmts.html#type
280+
# https://docs.python.org/3/library/typing.html#typing.TypeAliasType
281+
if sys.version_info < (3, 12):
282+
return False
283+
return isinstance(thing, typing.TypeAliasType)
284+
285+
275286
def is_a_union(thing: object) -> bool:
276287
"""Return True if thing is a typing.Union or types.UnionType (in py310)."""
277288
return isinstance(thing, UnionType) or get_origin(thing) is typing.Union
278289

279290

280291
def is_a_type(thing: object) -> bool:
281292
"""Return True if thing is a type or a generic type like thing."""
282-
return isinstance(thing, type) or is_generic_type(thing) or is_a_new_type(thing)
293+
return (
294+
isinstance(thing, type)
295+
or is_generic_type(thing)
296+
or is_a_new_type(thing)
297+
or is_a_type_alias_type(thing)
298+
)
283299

284300

285301
def is_typing_literal(thing: object) -> bool:

hypothesis-python/tests/conftest.py

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
collect_ignore_glob = ["django/*"]
3939
if sys.version_info < (3, 10):
4040
collect_ignore_glob.append("cover/*py310*")
41+
if sys.version_info < (3, 12):
42+
collect_ignore_glob.append("cover/*py312*.py")
4143

4244
if sys.version_info >= (3, 11):
4345
collect_ignore_glob.append("cover/test_asyncio.py") # @asyncio.coroutine removed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# This file is part of Hypothesis, which may be found at
2+
# https://github.com/HypothesisWorks/hypothesis/
3+
#
4+
# Copyright the Hypothesis Authors.
5+
# Individual contributors are listed in AUTHORS.rst and the git log.
6+
#
7+
# This Source Code Form is subject to the terms of the Mozilla Public License,
8+
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9+
# obtain one at https://mozilla.org/MPL/2.0/.
10+
11+
import pytest
12+
13+
from hypothesis import strategies as st
14+
15+
from tests.common.debug import assert_simple_property, find_any
16+
from tests.common.utils import temp_registered
17+
18+
19+
def test_resolves_simple_typealias():
20+
type MyInt = int
21+
type AliasedInt = MyInt
22+
type MaybeInt = int | None
23+
24+
assert_simple_property(st.from_type(MyInt), lambda x: isinstance(x, int))
25+
assert_simple_property(st.from_type(AliasedInt), lambda x: isinstance(x, int))
26+
assert_simple_property(
27+
st.from_type(MaybeInt), lambda x: isinstance(x, int) or x is None
28+
)
29+
30+
find_any(st.from_type(MaybeInt), lambda x: isinstance(x, int))
31+
find_any(st.from_type(MaybeInt), lambda x: x is None)
32+
33+
34+
def test_resolves_nested():
35+
type Point1 = int
36+
type Point2 = Point1
37+
type Point3 = Point2
38+
39+
assert_simple_property(st.from_type(Point3), lambda x: isinstance(x, int))
40+
41+
42+
def test_mutually_recursive_fails():
43+
# example from
44+
# https://docs.python.org/3/library/typing.html#typing.TypeAliasType.__value__
45+
type A = B
46+
type B = A
47+
48+
# I guess giving a nicer error here would be good, but detecting this in general
49+
# is...complicated.
50+
with pytest.raises(RecursionError):
51+
find_any(st.from_type(A))
52+
53+
54+
def test_can_register_typealias():
55+
type A = int
56+
st.register_type_strategy(A, st.just("a"))
57+
assert_simple_property(st.from_type(A), lambda x: x == "a")
58+
59+
60+
def test_prefers_manually_registered_typealias():
61+
# manually registering a `type A = ...` should override automatic detection
62+
type A = int
63+
64+
assert_simple_property(st.from_type(A), lambda x: isinstance(x, int))
65+
66+
with temp_registered(A, st.booleans()):
67+
assert_simple_property(st.from_type(A), lambda x: isinstance(x, bool))

0 commit comments

Comments
 (0)