Skip to content

Add PEP 613 TypeAlias to typing_extensions #732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 16, 2020
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
43 changes: 42 additions & 1 deletion typing_extensions/src_py2/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from typing_extensions import Annotated, NoReturn, ClassVar, IntVar
from typing_extensions import ContextManager, Counter, Deque, DefaultDict
from typing_extensions import NewType, overload
from typing_extensions import NewType, TypeAlias, overload
from typing import Dict, List
import typing
import typing_extensions
Expand Down Expand Up @@ -377,6 +377,47 @@ def test_annotated_in_other_types(self):
self.assertEqual(X[int], List[Annotated[int, 5]])


class TypeAliasTests(BaseTestCase):
def test_canonical_usage(self):
Alias = Employee # type: TypeAlias

def test_cannot_instantiate(self):
with self.assertRaises(TypeError):
TypeAlias()

def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(42, TypeAlias)

def test_no_issubclass(self):
with self.assertRaises(TypeError):
issubclass(Employee, TypeAlias)

with self.assertRaises(TypeError):
issubclass(TypeAlias, Employee)

def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(TypeAlias):
pass

with self.assertRaises(TypeError):
class C(type(TypeAlias)):
pass

def test_repr(self):
if hasattr(typing, 'TypeAlias'):
self.assertEqual(repr(TypeAlias), 'typing.TypeAlias')
self.assertEqual(repr(type(TypeAlias)), 'typing.TypeAlias')
else:
self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias')
self.assertEqual(repr(type(TypeAlias)), 'typing_extensions.TypeAlias')

def test_cannot_subscript(self):
with self.assertRaises(TypeError):
TypeAlias[int]


class AllTests(BaseTestCase):

def test_typing_extensions_includes_standard(self):
Expand Down
38 changes: 38 additions & 0 deletions typing_extensions/src_py2/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,43 @@ class Annotated(object):
__slots__ = ()


class _TypeAliasMeta(typing.TypingMeta):
"""Metaclass for TypeAlias"""

def __new__(cls, name, bases, namespace):
cls.assert_no_subclassing(bases)
self = super(_TypeAliasMeta, cls).__new__(cls, name, bases, namespace)
return self

def __repr__(self):
return 'typing_extensions.TypeAlias'


class _TypeAliasBase(typing._FinalTypingBase):
"""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.

For example::

Predicate = Callable[..., bool] # type: TypeAlias

It's invalid when used anywhere except as in the example above.
"""
__metaclass__ = _TypeAliasMeta
__slots__ = ()

def __instancecheck__(self, obj):
raise TypeError("TypeAlias cannot be used with isinstance().")

def __subclasscheck__(self, cls):
raise TypeError("TypeAlias cannot be used with issubclass().")

def __repr__(self):
return 'typing_extensions.TypeAlias'


TypeAlias = _TypeAliasBase(_root=True)

# This alias exists for backwards compatibility.
runtime = runtime_checkable
47 changes: 47 additions & 0 deletions typing_extensions/src_py3/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Generic
from typing import no_type_check
from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict
from typing_extensions import TypeAlias
try:
from typing_extensions import Protocol, runtime, runtime_checkable
except ImportError:
Expand Down Expand Up @@ -1822,6 +1823,52 @@ def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]":
)


class TypeAliasTests(BaseTestCase):
@skipUnless(PY36, 'Python 3.6 required')
def test_canonical_usage_with_variable_annotation(self):
ns = {}
exec('Alias: TypeAlias = Employee', globals(), ns)

def test_canonical_usage_with_type_comment(self):
Alias = Employee # type: TypeAlias

def test_cannot_instantiate(self):
with self.assertRaises(TypeError):
TypeAlias()

def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(42, TypeAlias)

def test_no_issubclass(self):
with self.assertRaises(TypeError):
issubclass(Employee, TypeAlias)

if SUBCLASS_CHECK_FORBIDDEN:
with self.assertRaises(TypeError):
issubclass(TypeAlias, Employee)

def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(TypeAlias):
pass

if SUBCLASS_CHECK_FORBIDDEN:
with self.assertRaises(TypeError):
class C(type(TypeAlias)):
pass

def test_repr(self):
if hasattr(typing, 'TypeAlias'):
self.assertEqual(repr(TypeAlias), 'typing.TypeAlias')
else:
self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias')

def test_cannot_subscript(self):
with self.assertRaises(TypeError):
TypeAlias[int]


class AllTests(BaseTestCase):

def test_typing_extensions_includes_standard(self):
Expand Down
95 changes: 95 additions & 0 deletions typing_extensions/src_py3/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2071,3 +2071,98 @@ def get_args(tp):
res = (list(res[:-1]), res[-1])
return res
return ()


if hasattr(typing, 'TypeAlias'):
TypeAlias = typing.TypeAlias
elif sys.version_info[:2] >= (3, 9):
class _TypeAliasForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name

@_TypeAliasForm
def TypeAlias(self, parameters):
"""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.

For example::

Predicate: TypeAlias = Callable[..., bool]

It's invalid when used anywhere except as in the example above.
"""
raise TypeError("{} is not subscriptable".format(self))

elif sys.version_info[:2] >= (3, 7):
class _TypeAliasForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name

TypeAlias = _TypeAliasForm('TypeAlias',
doc="""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.

For example::

Predicate: TypeAlias = Callable[..., bool]

It's invalid when used anywhere except as in the example
above.""")

elif hasattr(typing, '_FinalTypingBase'):
class _TypeAliasMeta(typing.TypingMeta):
"""Metaclass for TypeAlias"""

def __repr__(self):
return 'typing_extensions.TypeAlias'

class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True):
"""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.

For example::

Predicate: TypeAlias = Callable[..., bool]

It's invalid when used anywhere except as in the example above.
"""
__slots__ = ()

def __instancecheck__(self, obj):
raise TypeError("TypeAlias cannot be used with isinstance().")

def __subclasscheck__(self, cls):
raise TypeError("TypeAlias cannot be used with issubclass().")

def __repr__(self):
return 'typing_extensions.TypeAlias'

TypeAlias = _TypeAliasBase(_root=True)
else:
class _TypeAliasMeta(typing.TypingMeta):
"""Metaclass for TypeAlias"""

def __instancecheck__(self, obj):
raise TypeError("TypeAlias cannot be used with isinstance().")

def __subclasscheck__(self, cls):
raise TypeError("TypeAlias cannot be used with issubclass().")

def __call__(self, *args, **kwargs):
raise TypeError("Cannot instantiate TypeAlias")

class TypeAlias(metaclass=_TypeAliasMeta, _root=True):
"""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.

For example::

Predicate: TypeAlias = Callable[..., bool]

It's invalid when used anywhere except as in the example above.
"""
__slots__ = ()