diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 560215066cc31c..ad7f385e40794f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4491,6 +4491,67 @@ def test_no_isinstance(self): issubclass(int, TypeGuard) +class SpecialAttrsTests(BaseTestCase): + def test_special_attrs(self): + cls_to_check = ( + # ABC classes + typing.AbstractSet, + typing.AsyncContextManager, + typing.AsyncGenerator, + typing.AsyncIterable, + typing.AsyncIterator, + typing.Awaitable, + typing.ByteString, + typing.Callable, + typing.ChainMap, + typing.Collection, + typing.Container, + typing.ContextManager, + typing.Coroutine, + typing.Counter, + typing.DefaultDict, + typing.Deque, + typing.Dict, + typing.FrozenSet, + typing.Generator, + typing.Hashable, + typing.ItemsView, + typing.Iterable, + typing.Iterator, + typing.KeysView, + typing.List, + typing.Mapping, + typing.MappingView, + typing.MutableMapping, + typing.MutableSequence, + typing.MutableSet, + typing.OrderedDict, + typing.Reversible, + typing.Sequence, + typing.Set, + typing.Sized, + typing.Tuple, + typing.Type, + typing.ValuesView, + # Special Forms + typing.Any, + typing.NoReturn, + typing.ClassVar, + typing.Final, + typing.Union, + typing.Optional, + typing.Literal, + typing.TypeAlias, + typing.Concatenate, + typing.TypeGuard, + ) + + for cls in cls_to_check: + with self.subTest(cls=cls): + self.assertEqual(cls.__name__, cls._name) + self.assertEqual(cls.__qualname__, cls._name) + self.assertEqual(cls.__module__, 'typing') + class AllTests(BaseTestCase): """Tests for __all__.""" diff --git a/Lib/typing.py b/Lib/typing.py index 660ad3526f4bd4..cb70394bec36f2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -357,6 +357,12 @@ def __init__(self, getitem): self._name = getitem.__name__ self.__doc__ = getitem.__doc__ + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + def __mro_entries__(self, bases): raise TypeError(f"Cannot subclass {self!r}") @@ -934,6 +940,9 @@ def __mro_entries__(self, bases): return tuple(res) def __getattr__(self, attr): + if attr in {'__name__', '__qualname__'}: + return self._name + # We are careful for copy and pickle. # Also for simplicity we just don't relay all dunder names if '__origin__' in self.__dict__ and not _is_dunder(attr): diff --git a/Misc/NEWS.d/next/Library/2021-07-19-14-04-42.bpo-44524.Nbf2JC.rst b/Misc/NEWS.d/next/Library/2021-07-19-14-04-42.bpo-44524.Nbf2JC.rst new file mode 100644 index 00000000000000..0acdc7dff029f0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-07-19-14-04-42.bpo-44524.Nbf2JC.rst @@ -0,0 +1,2 @@ +Add missing ``__name__`` and ``__qualname__`` attributes to ``typing`` module +classes. Patch provided by Yurii Karabas.