Skip to content

Commit 09c1ed4

Browse files
JelleZijlstrasrittauAlexWaygood
authored
Add TypeAliasType (#160)
Co-authored-by: Sebastian Rittau <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent 40dbc09 commit 09c1ed4

File tree

4 files changed

+181
-3
lines changed

4 files changed

+181
-3
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
using the new release, and vice versa. Most users are unlikely to be affected
4545
by this change. Patch by Alex Waygood.
4646
- Backport the ability to define `__init__` methods on Protocol classes, a
47-
change made in Python 3.11 (originally implemented in
47+
change made in Python 3.11 (originally implemented in
4848
https://github.com/python/cpython/pull/31628 by Adrian Garcia Badaracco).
4949
Patch by Alex Waygood.
5050
- Speedup `isinstance(3, typing_extensions.SupportsIndex)` by >10x on Python
@@ -73,6 +73,8 @@
7373
- Backport the implementation of `NewType` from 3.10 (where it is implemented
7474
as a class rather than a function). This allows user-defined `NewType`s to be
7575
pickled. Patch by Alex Waygood.
76+
- Add `typing_extensions.TypeAliasType`, a backport of `typing.TypeAliasType`
77+
from PEP 695. Patch by Jelle Zijlstra.
7678
- Backport changes to the repr of `typing.Unpack` that were made in order to
7779
implement [PEP 692](https://peps.python.org/pep-0692/) (backport of
7880
https://github.com/python/cpython/pull/104048). Patch by Alex Waygood.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ This module currently contains the following:
4242
- In the standard library since Python 3.12
4343

4444
- `override` (equivalent to `typing.override`; see [PEP 698](https://peps.python.org/pep-0698/))
45+
- `TypeAliasType` (equivalent to `typing.TypeAliasType`; see [PEP 695](https://peps.python.org/pep-0695/))
4546
- `Buffer` (equivalent to `collections.abc.Buffer`; see [PEP 688](https://peps.python.org/pep-0688/))
4647
- `get_original_bases` (equivalent to
4748
[`types.get_original_bases`](https://docs.python.org/3.12/library/types.html#types.get_original_bases)

src/test_typing_extensions.py

+79-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import typing
2222
from typing import TypeVar, Optional, Union, AnyStr
2323
from typing import T, KT, VT # Not in __all__.
24-
from typing import Tuple, List, Dict, Iterable, Iterator, Callable
24+
from typing import Tuple, List, Set, Dict, Iterable, Iterator, Callable
2525
from typing import Generic
2626
from typing import no_type_check
2727
import warnings
@@ -35,7 +35,7 @@
3535
from typing_extensions import assert_type, get_type_hints, get_origin, get_args, get_original_bases
3636
from typing_extensions import clear_overloads, get_overloads, overload
3737
from typing_extensions import NamedTuple
38-
from typing_extensions import override, deprecated, Buffer
38+
from typing_extensions import override, deprecated, Buffer, TypeAliasType
3939
from _typed_dict_test_helper import Foo, FooGeneric
4040

4141
# Flags used to mark tests that only apply after a specific
@@ -4553,5 +4553,82 @@ class GenericTypedDict(TypedDict, Generic[T]):
45534553
)
45544554

45554555

4556+
class TypeAliasTypeTests(BaseTestCase):
4557+
def test_attributes(self):
4558+
Simple = TypeAliasType("Simple", int)
4559+
self.assertEqual(Simple.__name__, "Simple")
4560+
self.assertIs(Simple.__value__, int)
4561+
self.assertEqual(Simple.__type_params__, ())
4562+
self.assertEqual(Simple.__parameters__, ())
4563+
4564+
T = TypeVar("T")
4565+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
4566+
self.assertEqual(ListOrSetT.__name__, "ListOrSetT")
4567+
self.assertEqual(ListOrSetT.__value__, Union[List[T], Set[T]])
4568+
self.assertEqual(ListOrSetT.__type_params__, (T,))
4569+
self.assertEqual(ListOrSetT.__parameters__, (T,))
4570+
4571+
Ts = TypeVarTuple("Ts")
4572+
Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,))
4573+
self.assertEqual(Variadic.__name__, "Variadic")
4574+
self.assertEqual(Variadic.__value__, Tuple[int, Unpack[Ts]])
4575+
self.assertEqual(Variadic.__type_params__, (Ts,))
4576+
self.assertEqual(Variadic.__parameters__, tuple(iter(Ts)))
4577+
4578+
def test_immutable(self):
4579+
Simple = TypeAliasType("Simple", int)
4580+
with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
4581+
Simple.__name__ = "NewName"
4582+
with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
4583+
Simple.__value__ = str
4584+
with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
4585+
Simple.__type_params__ = (T,)
4586+
with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
4587+
Simple.__parameters__ = (T,)
4588+
with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
4589+
Simple.some_attribute = "not allowed"
4590+
with self.assertRaisesRegex(AttributeError, "Can't delete attribute"):
4591+
del Simple.__name__
4592+
with self.assertRaisesRegex(AttributeError, "Can't delete attribute"):
4593+
del Simple.nonexistent_attribute
4594+
4595+
def test_or(self):
4596+
Alias = TypeAliasType("Alias", int)
4597+
if sys.version_info >= (3, 10):
4598+
self.assertEqual(Alias | "Ref", Union[Alias, typing.ForwardRef("Ref")])
4599+
else:
4600+
with self.assertRaises(TypeError):
4601+
Alias | "Ref"
4602+
4603+
def test_getitem(self):
4604+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
4605+
subscripted = ListOrSetT[int]
4606+
self.assertEqual(get_args(subscripted), (int,))
4607+
self.assertIs(get_origin(subscripted), ListOrSetT)
4608+
with self.assertRaises(TypeError):
4609+
subscripted[str]
4610+
4611+
still_generic = ListOrSetT[Iterable[T]]
4612+
self.assertEqual(get_args(still_generic), (Iterable[T],))
4613+
self.assertIs(get_origin(still_generic), ListOrSetT)
4614+
fully_subscripted = still_generic[float]
4615+
self.assertEqual(get_args(fully_subscripted), (Iterable[float],))
4616+
self.assertIs(get_origin(fully_subscripted), ListOrSetT)
4617+
4618+
def test_pickle(self):
4619+
global Alias
4620+
Alias = TypeAliasType("Alias", int)
4621+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4622+
with self.subTest(proto=proto):
4623+
pickled = pickle.dumps(Alias, proto)
4624+
unpickled = pickle.loads(pickled)
4625+
self.assertIs(unpickled, Alias)
4626+
4627+
def test_no_instance_subclassing(self):
4628+
with self.assertRaises(TypeError):
4629+
class MyAlias(TypeAliasType):
4630+
pass
4631+
4632+
45564633
if __name__ == '__main__':
45574634
main()

src/typing_extensions.py

+98
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
'runtime_checkable',
8080
'Text',
8181
'TypeAlias',
82+
'TypeAliasType',
8283
'TypeGuard',
8384
'TYPE_CHECKING',
8485
'Never',
@@ -2646,3 +2647,100 @@ def __or__(self, other):
26462647

26472648
def __ror__(self, other):
26482649
return typing.Union[other, self]
2650+
2651+
2652+
if hasattr(typing, "TypeAliasType"):
2653+
TypeAliasType = typing.TypeAliasType
2654+
else:
2655+
class TypeAliasType:
2656+
"""Create named, parameterized type aliases.
2657+
2658+
This provides a backport of the new `type` statement in Python 3.12:
2659+
2660+
type ListOrSet[T] = list[T] | set[T]
2661+
2662+
is equivalent to:
2663+
2664+
T = TypeVar("T")
2665+
ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))
2666+
2667+
The name ListOrSet can then be used as an alias for the type it refers to.
2668+
2669+
The type_params argument should contain all the type parameters used
2670+
in the value of the type alias. If the alias is not generic, this
2671+
argument is omitted.
2672+
2673+
Static type checkers should only support type aliases declared using
2674+
TypeAliasType that follow these rules:
2675+
2676+
- The first argument (the name) must be a string literal.
2677+
- The TypeAliasType instance must be immediately assigned to a variable
2678+
of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
2679+
as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').
2680+
2681+
"""
2682+
2683+
def __init__(self, name: str, value, *, type_params=()):
2684+
if not isinstance(name, str):
2685+
raise TypeError("TypeAliasType name must be a string")
2686+
self.__value__ = value
2687+
self.__type_params__ = type_params
2688+
2689+
parameters = []
2690+
for type_param in type_params:
2691+
if isinstance(type_param, TypeVarTuple):
2692+
parameters.extend(type_param)
2693+
else:
2694+
parameters.append(type_param)
2695+
self.__parameters__ = tuple(parameters)
2696+
def_mod = _caller()
2697+
if def_mod != 'typing_extensions':
2698+
self.__module__ = def_mod
2699+
# Setting this attribute closes the TypeAliasType from further modification
2700+
self.__name__ = name
2701+
2702+
def __setattr__(self, __name: str, __value: object) -> None:
2703+
if hasattr(self, "__name__"):
2704+
raise AttributeError(
2705+
f"Can't set attribute {__name!r} on an instance of TypeAliasType"
2706+
)
2707+
super().__setattr__(__name, __value)
2708+
2709+
def __delattr__(self, __name: str) -> None:
2710+
raise AttributeError(
2711+
f"Can't delete attribute {__name!r} on an instance of TypeAliasType"
2712+
)
2713+
2714+
def __repr__(self) -> str:
2715+
return self.__name__
2716+
2717+
def __getitem__(self, parameters):
2718+
if not isinstance(parameters, tuple):
2719+
parameters = (parameters,)
2720+
parameters = [
2721+
typing._type_check(
2722+
item, f'Subscripting {self.__name__} requires a type.'
2723+
)
2724+
for item in parameters
2725+
]
2726+
return typing._GenericAlias(self, tuple(parameters))
2727+
2728+
def __reduce__(self):
2729+
return self.__name__
2730+
2731+
def __init_subclass__(cls, *args, **kwargs):
2732+
raise TypeError(
2733+
"type 'typing_extensions.TypeAliasType' is not an acceptable base type"
2734+
)
2735+
2736+
# The presence of this method convinces typing._type_check
2737+
# that TypeAliasTypes are types.
2738+
def __call__(self):
2739+
raise TypeError("Type alias is not callable")
2740+
2741+
if sys.version_info >= (3, 10):
2742+
def __or__(self, right):
2743+
return typing.Union[self, right]
2744+
2745+
def __ror__(self, left):
2746+
return typing.Union[left, self]

0 commit comments

Comments
 (0)