Skip to content

Commit 08d0e52

Browse files
feature: Support typing.NewType in typeapi.ClassTypeHint (#38)
* feature: Support `typing.NewType` in `typeapi.ClassTypeHint` * fix for older Python versions * update comment * insert pr into changelog
1 parent 8dcaf65 commit 08d0e52

File tree

4 files changed

+41
-5
lines changed

4 files changed

+41
-5
lines changed

.changelog/_unreleased.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[[entries]]
2+
id = "a10b5733-3f9c-472f-a922-9f1fa33a3179"
3+
type = "feature"
4+
description = "Support `typing.NewType` in `typeapi.ClassTypeHint`"
5+
author = "@NiklasRosenstein"
6+
pr = "https://github.com/NiklasRosenstein/python-typeapi/pull/38"

src/typeapi/typehint.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
get_type_hint_origin_or_none,
3030
get_type_hint_original_bases,
3131
get_type_hint_parameters,
32+
is_new_type,
3233
type_repr,
3334
)
3435

@@ -285,10 +286,11 @@ class ClassTypeHint(TypeHint):
285286

286287
def __init__(self, hint: object, source: "Any | None" = None) -> None:
287288
super().__init__(hint, source)
288-
assert isinstance(self.hint, type) or isinstance(self.origin, type), (
289-
"ClassTypeHint must be initialized from a real type or a generic that points to a real type. "
290-
f'Got "{self.hint!r}" with origin "{self.origin}"'
291-
)
289+
if not is_new_type(hint):
290+
assert isinstance(self.hint, type) or isinstance(self.origin, type), (
291+
"ClassTypeHint must be initialized from a real type or a generic that points to a real type. "
292+
f'Got "{self.hint!r}" with origin "{self.origin}"'
293+
)
292294

293295
def parameterize(self, parameter_map: Mapping[object, Any]) -> "TypeHint":
294296
if self.type is Generic: # type: ignore[comparison-overlap]
@@ -303,6 +305,8 @@ def type(self) -> type:
303305
return self.origin
304306
if isinstance(self.hint, type):
305307
return self.hint
308+
if is_new_type(self.hint):
309+
return self.hint.__supertype__
306310
assert False, "ClassTypeHint not initialized from a real type or a generic that points to a real type."
307311

308312
@property

src/typeapi/typehint_test.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, ClassVar, Dict, Generic, List, Optional, Sequence, Tuple, TypeVar, Union
1+
from typing import Any, ClassVar, Dict, Generic, List, NewType, Optional, Sequence, Tuple, TypeVar, Union
22

33
from pytest import mark
44
from typing_extensions import Annotated, Literal, TypeAlias
@@ -263,6 +263,17 @@ def test__TypeHint__from_future_syntax_ForwardRef_union() -> None:
263263
assert hint.parameters == ()
264264

265265

266+
def test__TypeHint__from_newtype() -> None:
267+
MyInt = NewType("MyInt", int)
268+
hint = TypeHint(MyInt)
269+
assert isinstance(hint, ClassTypeHint)
270+
assert hint.args == ()
271+
assert hint.bases == (object,)
272+
assert hint.origin is None
273+
assert hint.type is int
274+
assert hint.hint is MyInt
275+
276+
266277
def test__ClassTypeHint__parametrize() -> None:
267278
"""This method tests the infusion of type parameters into other types.
268279

src/typeapi/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,18 @@ def is_typed_dict(hint: Any) -> TypeGuard[TypedDictProtocol]:
384384

385385
class HasGetitem(Protocol, Generic[T_contra, U_co]):
386386
def __getitem__(self, __key: T_contra) -> U_co: ...
387+
388+
389+
class NewTypeP(Protocol):
390+
"""
391+
Protocol for objects returned by `typing.NewType`.
392+
"""
393+
394+
__name__: str
395+
__supertype__: type
396+
397+
398+
def is_new_type(hint: Any) -> TypeGuard[NewTypeP]:
399+
# NOTE: Starting with Python 3.10, `typing.NewType` is actually a class instead of a function, but it is
400+
# still typed as a function in Mypy until 3.12.
401+
return hasattr(hint, "__name__") and hasattr(hint, "__supertype__")

0 commit comments

Comments
 (0)