Skip to content
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

Add a backport of generic NamedTuples #44

Merged
merged 21 commits into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Expand Up @@ -123,8 +123,7 @@ Certain objects were changed after they were added to `typing`, and
`typing_extensions.get_overloads()`, you must use
`@typing_extensions.overload`.
- `NamedTuple` was changed in Python 3.11 to allow for multiple inheritance
with `typing.Generic`. The implementation of `NamedTuple` was also changed in
3.9 so that `NamedTuple` became a function rather than a class.
with `typing.Generic`.

There are a few types whose interface was modified between different
versions of typing. For example, `typing.Sequence` was modified to
Expand Down
19 changes: 10 additions & 9 deletions src/test_typing_extensions.py
Expand Up @@ -3114,12 +3114,7 @@ def test_namedtuple_errors(self):
def test_copy_and_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('cool', int)])
pickleable_classes = [Emp, CoolEmployee]
# support for pickling nested classes
# appears to have been added in 3.7.6
if sys.version_info >= (3, 7, 6):
pickleable_classes.append(self.NestedEmployee)
for cls in pickleable_classes:
for cls in Emp, CoolEmployee, self.NestedEmployee:
with self.subTest(cls=cls):
jane = cls('jane', 37)
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
Expand All @@ -3139,13 +3134,21 @@ def test_copy_and_pickle(self):
def test_docstring(self):
self.assertEqual(NamedTuple.__doc__, typing.NamedTuple.__doc__)

@skipIf(sys.version_info < (3, 8), "NamedTuple had a bad signature on <=3.7")
def test_signature_is_same_as_typing_NamedTuple(self):
self.assertEqual(inspect.signature(NamedTuple), inspect.signature(typing.NamedTuple))

@skipIf(sys.version_info >= (3, 8), "tests are only relveant to <=3.7")
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
def test_signature_on_37(self):
self.assertIsInstance(inspect.signature(NamedTuple), inspect.Signature)
self.assertFalse(hasattr(NamedTuple, "__text_signature__"))

@skipIf(sys.version_info < (3, 9), "NamedTuple was a class on 3.8 and lower")
def test_same_as_typing_NamedTuple_39_plus(self):
self.assertEqual(
set(dir(NamedTuple)),
set(dir(typing.NamedTuple)) | {"__text_signature__"}
)
self.assertEqual(inspect.signature(NamedTuple), inspect.signature(typing.NamedTuple))
self.assertIs(type(NamedTuple), type(typing.NamedTuple))

@skipIf(sys.version_info >= (3, 9), "tests are only relevant to <=3.8")
Expand All @@ -3154,8 +3157,6 @@ def test_same_as_typing_NamedTuple_38_minus(self):
self.NestedEmployee.__annotations__,
self.NestedEmployee._field_types
)
self.assertIsInstance(inspect.signature(NamedTuple), inspect.Signature)
self.assertFalse(hasattr(NamedTuple, "__text_signature__"))


if __name__ == '__main__':
Expand Down
10 changes: 8 additions & 2 deletions src/typing_extensions.py
Expand Up @@ -1961,6 +1961,9 @@ def decorator(cls_or_fn):
typing._check_generic = _check_generic


# Backport typing.NamedTuple as it exists in Python 3.11.
# In 3.11, the ability to define generic `NamedTuple`s was supported.
# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
if sys.version_info >= (3, 11):
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
NamedTuple = typing.NamedTuple
else:
Expand All @@ -1984,7 +1987,7 @@ def _make_nmtuple(name, types, module, defaults=()):
return nm_tpl

_prohibited_namedtuple_fields = typing._prohibited
_special_namedtuple_fields = typing._special
_special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})

class _NamedTupleMeta(type):
def __new__(cls, typename, bases, ns):
Expand Down Expand Up @@ -2034,7 +2037,10 @@ def NamedTuple(__typename, __fields=None, **kwargs):
NamedTuple.__doc__ = typing.NamedTuple.__doc__
_NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})

if sys.version_info >= (3, 9):
# On 3.8+, alter the signature so that it matches typing.NamedTuple.
# The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
# so just leave the signature as it is on 3.7.
if sys.version_info >= (3, 8):
NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)'

def _namedtuple_mro_entries(bases):
Expand Down