Skip to content

Commit 48210c6

Browse files
authored
feat: Add support for identifying and stripping python 3.12 explicit type aliases. (#49)
1 parent 897f329 commit 48210c6

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ jobs:
156156
echo "${{ github.event.number }}" > .pr_number
157157
158158
- name: Upload artifact
159-
uses: actions/upload-artifact@v3
159+
uses: actions/upload-artifact@v4
160160
with:
161161
name: docs-preview
162162
path: |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ classifiers = [
3838
"Intended Audience :: Developers",
3939
]
4040
name = "type-lens"
41-
version = "0.2.4"
41+
version = "0.2.5"
4242
description = "type-lens is a Python template project designed to simplify the setup of a new project."
4343
readme = "README.md"
4444
license = { text = "MIT" }

tests/test_type_view.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,37 @@ def test_fallback_origin() -> None:
426426

427427
if sys.version_info >= (3, 9):
428428
assert TypeView(set[bool]).fallback_origin == set
429+
430+
431+
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12 or higher for TypeAliasType")
432+
def test_is_type_alias() -> None:
433+
from typing import TypeAliasType # type: ignore[attr-defined]
434+
435+
Foo = TypeAliasType("Foo", int) # pyright: ignore
436+
assert TypeView(Foo).is_type_alias is True # pyright: ignore
437+
assert TypeView(int).is_type_alias is False
438+
439+
440+
def test_is_type_alias_extensions() -> None:
441+
from typing_extensions import TypeAliasType
442+
443+
Foo = TypeAliasType("Foo", int) # pyright: ignore
444+
assert TypeView(Foo).is_type_alias is True
445+
assert TypeView(int).is_type_alias is False
446+
447+
448+
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12 or higher for TypeAliasType")
449+
def test_strip_type_alias() -> None:
450+
from typing import TypeAliasType # type: ignore[attr-defined]
451+
452+
Foo = TypeAliasType("Foo", int) # pyright: ignore
453+
assert TypeView(Foo).strip_type_alias() == TypeView(int) # pyright: ignore
454+
assert TypeView(int).strip_type_alias() == TypeView(int)
455+
456+
457+
def test_strip_type_alias_extensions() -> None:
458+
from typing_extensions import TypeAliasType
459+
460+
Foo = TypeAliasType("Foo", int) # pyright: ignore
461+
assert TypeView(Foo).strip_type_alias() == TypeView(int)
462+
assert TypeView(int).strip_type_alias() == TypeView(int)

type_lens/type_view.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import sys
4+
import typing
35
from collections import abc
46
from collections.abc import Collection, Mapping
57
from typing import (
@@ -14,6 +16,7 @@
1416
_SpecialForm, # pyright: ignore[reportPrivateUsage]
1517
)
1618

19+
import typing_extensions
1720
from typing_extensions import Annotated, NotRequired, Required, get_args, get_origin
1821
from typing_extensions import Literal as ExtensionsLiteral
1922

@@ -204,6 +207,19 @@ def is_variadic_tuple(self) -> bool:
204207
"""
205208
return self.is_tuple and len(self.args) == 2 and self.args[1] == ... # pyright: ignore
206209

210+
if sys.version_info < (3, 12):
211+
212+
@property
213+
def is_type_alias(self) -> bool:
214+
"""Whether the annotation is a new-style `type Type = ...` alias or not."""
215+
return _is_typing_extensins_type_alias(self)
216+
else:
217+
218+
@property
219+
def is_type_alias(self) -> bool:
220+
"""Whether the annotation is a new-style `type Type = ...` alias or not."""
221+
return isinstance(self.annotation, typing.TypeAliasType) or _is_typing_extensins_type_alias(self)
222+
207223
@property
208224
def safe_generic_origin(self) -> Any:
209225
"""A type, safe to be used as a generic type across all supported Python versions.
@@ -260,6 +276,7 @@ def is_subclass_of(self, typ: Any | tuple[Any, ...], /) -> bool:
260276
return isinstance(self.fallback_origin, type) and issubclass(self.fallback_origin, typ)
261277

262278
def strip_optional(self) -> TypeView[Any]:
279+
"""Remove the "Optional" component of an `Optional[T]` or `Union[T, None]` type."""
263280
if not self.is_optional:
264281
return self
265282

@@ -269,3 +286,21 @@ def strip_optional(self) -> TypeView[Any]:
269286
args = tuple(a for a in self.args if a is not NoneType)
270287
non_optional = Union[args] # type: ignore[valid-type]
271288
return TypeView(non_optional)
289+
290+
def strip_type_alias(self) -> TypeView[Any]:
291+
"""Remove the type alias from a `type Type = T` type alias.
292+
293+
Examples:
294+
>>> type Foo = int
295+
>>> TypeAlias(Foo).strip_type_alias()
296+
TypeView(int)
297+
"""
298+
if not self.is_type_alias:
299+
return self
300+
return TypeView(self.annotation.__value__)
301+
302+
303+
def _is_typing_extensins_type_alias(type_view: TypeView[Any]) -> bool:
304+
if hasattr(typing_extensions, "TypeAliasType"):
305+
return isinstance(type_view.annotation, typing_extensions.TypeAliasType)
306+
return False

0 commit comments

Comments
 (0)