Skip to content

Commit 02e0a74

Browse files
Harden logic for when typing_extensions doesn't alias TypeAliasType (#274)
* Harden logic for when `typing_extensions` doesn't alias `TypeAliasType`. * Update/add tests. * Update snapshots. * Remove unused import. * Create a directory for tests that can only run in 3.12+ so that unused snapshots aren't detected in 3.11. * Update snapshots.
1 parent ac93708 commit 02e0a74

File tree

11 files changed

+111
-8
lines changed

11 files changed

+111
-8
lines changed

python/src/typechat/_internal/ts_conversion/python_type_to_ts_nodes.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from collections import OrderedDict
44
import inspect
5+
import sys
56
import typing
67
import typing_extensions
78
from dataclasses import MISSING, Field, dataclass
@@ -79,6 +80,15 @@ class Dataclassish(Protocol):
7980
class TypeOfTypedDict(Protocol):
8081
__total__: bool
8182

83+
if sys.version_info >= (3, 12) and typing.TypeAliasType is not typing_extensions.TypeAliasType:
84+
# Sometimes typing_extensions aliases TypeAliasType,
85+
# sometimes it's its own declaration.
86+
def is_type_alias_type(py_type: object) -> TypeGuard[TypeAliasType]:
87+
return isinstance(py_type, typing.TypeAliasType | typing_extensions.TypeAliasType)
88+
else:
89+
def is_type_alias_type(py_type: object) -> TypeGuard[TypeAliasType]:
90+
return isinstance(py_type, typing_extensions.TypeAliasType)
91+
8292

8393
def is_generic(py_type: object) -> TypeGuard[GenericAliasish]:
8494
return hasattr(py_type, "__origin__") and hasattr(py_type, "__args__")
@@ -88,9 +98,8 @@ def is_dataclass(py_type: object) -> TypeGuard[Dataclassish]:
8898

8999
TypeReferenceTarget: TypeAlias = type | TypeAliasType | TypeVar | GenericAliasish
90100

91-
92101
def is_python_type_or_alias(origin: object) -> TypeGuard[type | TypeAliasType]:
93-
return isinstance(origin, TypeAliasType | type)
102+
return isinstance(origin, type) or is_type_alias_type(origin)
94103

95104

96105
_KNOWN_GENERIC_SPECIAL_FORMS: frozenset[Any] = frozenset(
@@ -393,7 +402,7 @@ def declare_type(py_type: object):
393402
reserve_name(py_type)
394403

395404
return InterfaceDeclarationNode(py_type.__name__, None, "", None, [])
396-
if isinstance(py_type, TypeAliasType):
405+
if is_type_alias_type(py_type):
397406
type_params = [TypeParameterDeclarationNode(type_param.__name__) for type_param in py_type.__type_params__]
398407

399408
reserve_name(py_type)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Entry point is: 'FirstOrSecond'
2+
3+
type FirstOrSecond<T> = First<T> | Second<T>
4+
5+
interface Second<T> {
6+
kind: "second";
7+
second_attr: T;
8+
}
9+
10+
interface First<T> {
11+
kind: "first";
12+
first_attr: T;
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Entry point is: 'StrOrInt'
2+
3+
type StrOrInt = string | number
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Entry point is: 'Nested'
2+
3+
interface Nested {
4+
item: FirstOrSecond<string>;
5+
}
6+
7+
type FirstOrSecond<T> = First<T> | Second<T>
8+
9+
interface Second<T> {
10+
kind: "second";
11+
second_attr: T;
12+
}
13+
14+
interface First<T> {
15+
kind: "first";
16+
first_attr: T;
17+
}

python/tests/test_generic_alias_1.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
T = TypeVar("T", covariant=True)
77

8-
98
class First(Generic[T], TypedDict):
109
kind: Literal["first"]
1110
first_attr: T

python/tests/test_generic_alias_2.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,5 @@ class Nested(TypedDict):
2222
item: FirstOrSecond[str]
2323

2424

25-
26-
def test_generic_alias1(snapshot: Any):
25+
def test_generic_alias2(snapshot: Any):
2726
assert(python_type_to_typescript_schema(Nested) == snapshot(extension_class=TypeScriptSchemaSnapshotExtension))
28-

python/tests/test_generic_alias_3.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Any
2+
from .utilities import check_snapshot_for_module_string_if_3_12_plus
3+
4+
module_str = """
5+
from typing import Literal, TypedDict
6+
class First[T](TypedDict):
7+
kind: Literal["first"]
8+
first_attr: T
9+
10+
11+
class Second[T](TypedDict):
12+
kind: Literal["second"]
13+
second_attr: T
14+
15+
16+
type FirstOrSecond[T] = First[T] | Second[T]
17+
"""
18+
19+
def test_generic_alias3(snapshot: Any):
20+
check_snapshot_for_module_string_if_3_12_plus(snapshot, input_type_str="FirstOrSecond", module_str=module_str)

python/tests/test_generic_alias_4.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Any
2+
from .utilities import check_snapshot_for_module_string_if_3_12_plus
3+
4+
module_str = """
5+
from typing import Literal, TypedDict
6+
class First[T](TypedDict):
7+
kind: Literal["first"]
8+
first_attr: T
9+
10+
11+
class Second[T](TypedDict):
12+
kind: Literal["second"]
13+
second_attr: T
14+
15+
16+
type FirstOrSecond[T] = First[T] | Second[T]
17+
18+
class Nested(TypedDict):
19+
item: FirstOrSecond[str]
20+
"""
21+
22+
def test_generic_alias4(snapshot: Any):
23+
check_snapshot_for_module_string_if_3_12_plus(snapshot, input_type_str="Nested", module_str=module_str)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from typing import Any
2+
from .utilities import check_snapshot_for_module_string_if_3_12_plus
3+
4+
module_str = "type StrOrInt = str | int"
5+
6+
def test_type_alias_union1(snapshot: Any):
7+
check_snapshot_for_module_string_if_3_12_plus(snapshot, "StrOrInt", module_str)

0 commit comments

Comments
 (0)