Skip to content

Commit 8248be3

Browse files
godlygeekAA-Turner
andauthored
autodoc: Reset sys.modules on partial import failure (#11645)
If importing with ``TYPE_CHECKING is True`` fails, reset the state of ``sys.modules`` so that the attempt with ``TYPE_CHECKING is False`` may succeed. Co-authored-by: Adam Turner <[email protected]>
1 parent e494baa commit 8248be3

File tree

7 files changed

+37
-1
lines changed

7 files changed

+37
-1
lines changed

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ Features added
1616
Bugs fixed
1717
----------
1818

19+
* #11645: Fix a regression preventing autodoc from importing modules within
20+
packages that make use of ``if typing.TYPE_CHECKING:`` to guard circular
21+
imports needed by type checkers.
22+
Patch by Matt Wozniski.
23+
1924
Testing
2025
-------
2126

sphinx/ext/autodoc/importer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import importlib
6+
import sys
67
import traceback
78
import typing
89
from typing import TYPE_CHECKING, Any, Callable, NamedTuple
@@ -82,13 +83,18 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
8283
objpath = list(objpath)
8384
while module is None:
8485
try:
86+
orig_modules = frozenset(sys.modules)
8587
try:
8688
# try importing with ``typing.TYPE_CHECKING == True``
8789
typing.TYPE_CHECKING = True
8890
module = import_module(modname, warningiserror=warningiserror)
8991
except ImportError:
9092
# if that fails (e.g. circular import), retry with
91-
# ``typing.TYPE_CHECKING == False``
93+
# ``typing.TYPE_CHECKING == False`` after reverting
94+
# changes made to ``sys.modules`` by the failed try
95+
for m in [m for m in sys.modules if m not in orig_modules]:
96+
sys.modules.pop(m)
97+
9298
typing.TYPE_CHECKING = False
9399
module = import_module(modname, warningiserror=warningiserror)
94100
finally:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from circular_import.c import SomeClass
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
X = 42
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import typing
2+
3+
if typing.TYPE_CHECKING:
4+
from circular_import import SomeClass
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import circular_import.a
2+
import circular_import.b
3+
4+
5+
class SomeClass:
6+
X = circular_import.a.X

tests/test_ext_autodoc.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,6 +2024,19 @@ def test_autodoc_TYPE_CHECKING(app):
20242024
]
20252025

20262026

2027+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
2028+
def test_autodoc_TYPE_CHECKING_circular_import(app):
2029+
options = {"members": None,
2030+
"undoc-members": None}
2031+
actual = do_autodoc(app, 'module', 'circular_import', options)
2032+
assert list(actual) == [
2033+
'',
2034+
'.. py:module:: circular_import',
2035+
'',
2036+
]
2037+
assert sys.modules["circular_import"].a is sys.modules["circular_import.a"]
2038+
2039+
20272040
@pytest.mark.sphinx('html', testroot='ext-autodoc')
20282041
def test_singledispatch(app):
20292042
options = {"members": None}

0 commit comments

Comments
 (0)