Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ jobs:
echo "${{ github.event.number }}" > .pr_number

- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: docs-preview
path: |
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ classifiers = [
"Intended Audience :: Developers",
]
name = "type-lens"
version = "0.2.4"
version = "0.2.5"
description = "type-lens is a Python template project designed to simplify the setup of a new project."
readme = "README.md"
license = { text = "MIT" }
Expand Down
34 changes: 34 additions & 0 deletions tests/test_type_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,37 @@ def test_fallback_origin() -> None:

if sys.version_info >= (3, 9):
assert TypeView(set[bool]).fallback_origin == set


@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12 or higher for TypeAliasType")
def test_is_type_alias() -> None:
from typing import TypeAliasType # type: ignore[attr-defined]

Foo = TypeAliasType("Foo", int) # pyright: ignore
assert TypeView(Foo).is_type_alias is True # pyright: ignore
assert TypeView(int).is_type_alias is False


def test_is_type_alias_extensions() -> None:
from typing_extensions import TypeAliasType

Foo = TypeAliasType("Foo", int) # pyright: ignore
assert TypeView(Foo).is_type_alias is True
assert TypeView(int).is_type_alias is False


@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12 or higher for TypeAliasType")
def test_strip_type_alias() -> None:
from typing import TypeAliasType # type: ignore[attr-defined]

Foo = TypeAliasType("Foo", int) # pyright: ignore
assert TypeView(Foo).strip_type_alias() == TypeView(int) # pyright: ignore
assert TypeView(int).strip_type_alias() == TypeView(int)


def test_strip_type_alias_extensions() -> None:
from typing_extensions import TypeAliasType

Foo = TypeAliasType("Foo", int) # pyright: ignore
assert TypeView(Foo).strip_type_alias() == TypeView(int)
assert TypeView(int).strip_type_alias() == TypeView(int)
35 changes: 35 additions & 0 deletions type_lens/type_view.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import sys
import typing
from collections import abc
from collections.abc import Collection, Mapping
from typing import (
Expand All @@ -14,6 +16,7 @@
_SpecialForm, # pyright: ignore[reportPrivateUsage]
)

import typing_extensions
from typing_extensions import Annotated, NotRequired, Required, get_args, get_origin
from typing_extensions import Literal as ExtensionsLiteral

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

if sys.version_info < (3, 12):

@property
def is_type_alias(self) -> bool:
"""Whether the annotation is a new-style `type Type = ...` alias or not."""
return _is_typing_extensins_type_alias(self)
else:

@property
def is_type_alias(self) -> bool:
"""Whether the annotation is a new-style `type Type = ...` alias or not."""
return isinstance(self.annotation, typing.TypeAliasType) or _is_typing_extensins_type_alias(self)

@property
def safe_generic_origin(self) -> Any:
"""A type, safe to be used as a generic type across all supported Python versions.
Expand Down Expand Up @@ -260,6 +276,7 @@ def is_subclass_of(self, typ: Any | tuple[Any, ...], /) -> bool:
return isinstance(self.fallback_origin, type) and issubclass(self.fallback_origin, typ)

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

Expand All @@ -269,3 +286,21 @@ def strip_optional(self) -> TypeView[Any]:
args = tuple(a for a in self.args if a is not NoneType)
non_optional = Union[args] # type: ignore[valid-type]
return TypeView(non_optional)

def strip_type_alias(self) -> TypeView[Any]:
"""Remove the type alias from a `type Type = T` type alias.

Examples:
>>> type Foo = int
>>> TypeAlias(Foo).strip_type_alias()
TypeView(int)
"""
if not self.is_type_alias:
return self
return TypeView(self.annotation.__value__)


def _is_typing_extensins_type_alias(type_view: TypeView[Any]) -> bool:
if hasattr(typing_extensions, "TypeAliasType"):
return isinstance(type_view.annotation, typing_extensions.TypeAliasType)
return False
Loading