Skip to content

Commit 8528c31

Browse files
committed
Clean sys.modules if TYPE_CHECKING=True import fails
autodoc now attempts to import with `typing.TYPE_CHECKING = True` first, and then falls back to `typing.TYPE_CHECKING = False` if that fails. Unfortunately, the first import can leave behind some partially-imported modules in `sys.modules` such that the retry fails. Attempt to work around this by detecting what modules were added to `sys.modules` by the first attempt and removing them before retrying. Signed-off-by: Matt Wozniski <[email protected]>
1 parent 2f6ea14 commit 8528c31

File tree

6 files changed

+32
-1
lines changed

6 files changed

+32
-1
lines changed

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 frozenset(sys.modules) - 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)