Skip to content
59 changes: 59 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6695,6 +6695,22 @@ def test_copy_and_pickle(self):
self.assertEqual(jane2, jane)
self.assertIsInstance(jane2, cls)

def test_orig_bases(self):
T = TypeVar('T')

class SimpleNamedTuple(NamedTuple):
pass

class GenericNamedTuple(NamedTuple, Generic[T]):
pass

self.assertEqual(SimpleNamedTuple.__orig_bases__, (NamedTuple,))
self.assertEqual(GenericNamedTuple.__orig_bases__, (NamedTuple, Generic[T]))

CallNamedTuple = NamedTuple('CallNamedTuple', [])

self.assertEqual(CallNamedTuple.__orig_bases__, (NamedTuple,))


class TypedDictTests(BaseTestCase):
def test_basics_functional_syntax(self):
Expand Down Expand Up @@ -7126,6 +7142,49 @@ class TD(TypedDict):
self.assertIs(type(a), dict)
self.assertEqual(a, {'a': 1})

def test_orig_bases(self):
T = TypeVar('T')

class Parent(TypedDict):
pass

class Child(Parent):
pass

class OtherChild(Parent):
pass

class MixedChild(Child, OtherChild, Parent):
pass

class GenericParent(TypedDict, Generic[T]):
pass

class GenericChild(GenericParent[int]):
pass

class OtherGenericChild(GenericParent[str]):
pass

class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]):
pass

class MultipleGenericBases(GenericParent[int], GenericParent[float]):
pass

CallTypedDict = TypedDict('CallTypedDict', {})

self.assertEqual(Parent.__orig_bases__, (TypedDict,))
self.assertEqual(Child.__orig_bases__, (Parent,))
self.assertEqual(OtherChild.__orig_bases__, (Parent,))
self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,))
self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T]))
self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],))
self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],))
self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float]))
self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float]))
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))


class RequiredTests(BaseTestCase):

Expand Down
11 changes: 9 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2962,7 +2962,9 @@ class Employee(NamedTuple):
elif kwargs:
raise TypeError("Either list of fields or keywords"
" can be provided to NamedTuple, not both")
return _make_nmtuple(typename, fields, module=_caller())
nt = _make_nmtuple(typename, fields, module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt

_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})

Expand Down Expand Up @@ -2994,6 +2996,9 @@ def __new__(cls, name, bases, ns, total=True):

tp_dict = type.__new__(_TypedDictMeta, name, (*generic_base, dict), ns)

if not hasattr(tp_dict, '__orig_bases__'):
tp_dict.__orig_bases__ = bases

annotations = {}
own_annotations = ns.get('__annotations__', {})
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
Expand Down Expand Up @@ -3104,7 +3109,9 @@ class body be required.
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module

return _TypedDictMeta(typename, (), ns, total=total)
td = _TypedDictMeta(typename, (), ns, total=total)
td.__orig_bases__ = (TypedDict,)
return td

_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
TypedDict.__mro_entries__ = lambda bases: (_TypedDict,)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add ``__orig_bases__`` to non-generic TypedDicts, call-based TypedDicts, and
call-based NamedTuples. Other TypedDicts and NamedTuples already had the attribute.