From 7569c0fe91dfcf562dee8c29798ecda74d738aa8 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 26 Jun 2021 23:31:32 +0000 Subject: [PATCH] bpo-44468: Never skip base classes in `typing.get_type_hints()`, even with invalid `.__module__`. (GH-26862) --- Lib/test/test_typing.py | 25 +++++++++++++------ Lib/typing.py | 5 +--- .../2021-06-23-19-02-00.bpo-44468.-klV5-.rst | 2 ++ 3 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-06-23-19-02-00.bpo-44468.-klV5-.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index cb198d6b75f..e693883094d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2277,13 +2277,6 @@ class ClassVarTests(BaseTestCase): with self.assertRaises(TypeError): issubclass(int, ClassVar) - def test_bad_module(self): - # bpo-41515 - class BadModule: - pass - BadModule.__module__ = 'bad' # Something not in sys.modules - self.assertEqual(get_type_hints(BadModule), {}) - class FinalTests(BaseTestCase): def test_basics(self): @@ -3033,6 +3026,24 @@ class GetTypeHintTests(BaseTestCase): # This previously raised an error under PEP 563. self.assertEqual(get_type_hints(Foo), {'x': str}) + def test_get_type_hints_bad_module(self): + # bpo-41515 + class BadModule: + pass + BadModule.__module__ = 'bad' # Something not in sys.modules + self.assertNotIn('bad', sys.modules) + self.assertEqual(get_type_hints(BadModule), {}) + + def test_get_type_hints_annotated_bad_module(self): + # See https://bugs.python.org/issue44468 + class BadBase: + foo: tuple + class BadType(BadBase): + bar: list + BadType.__module__ = BadBase.__module__ = 'bad' + self.assertNotIn('bad', sys.modules) + self.assertEqual(get_type_hints(BadType), {'foo': tuple, 'bar': list}) + class GetUtilitiesTestCase(TestCase): def test_get_origin(self): diff --git a/Lib/typing.py b/Lib/typing.py index 00a0df591cb..2287f0521a3 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1701,10 +1701,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): hints = {} for base in reversed(obj.__mro__): if globalns is None: - try: - base_globals = sys.modules[base.__module__].__dict__ - except KeyError: - continue + base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) else: base_globals = globalns ann = base.__dict__.get('__annotations__', {}) diff --git a/Misc/NEWS.d/next/Library/2021-06-23-19-02-00.bpo-44468.-klV5-.rst b/Misc/NEWS.d/next/Library/2021-06-23-19-02-00.bpo-44468.-klV5-.rst new file mode 100644 index 00000000000..78251c7d550 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-06-23-19-02-00.bpo-44468.-klV5-.rst @@ -0,0 +1,2 @@ +:func:`typing.get_type_hints` now finds annotations in classes and base classes +with unexpected ``__module__``. Previously, it skipped those MRO elements.