diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index 2082a714036..6a90c3da3fe 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -3,6 +3,7 @@ from __future__ import annotations import importlib +import sys import traceback import typing from typing import TYPE_CHECKING, Any, Callable, NamedTuple @@ -82,13 +83,18 @@ def import_object(modname: str, objpath: list[str], objtype: str = '', objpath = list(objpath) while module is None: try: + orig_modules = frozenset(sys.modules) try: # try importing with ``typing.TYPE_CHECKING == True`` typing.TYPE_CHECKING = True module = import_module(modname, warningiserror=warningiserror) except ImportError: # if that fails (e.g. circular import), retry with - # ``typing.TYPE_CHECKING == False`` + # ``typing.TYPE_CHECKING == False`` after reverting + # changes made to ``sys.modules`` by the failed try + for m in frozenset(sys.modules) - orig_modules: + sys.modules.pop(m) + typing.TYPE_CHECKING = False module = import_module(modname, warningiserror=warningiserror) finally: diff --git a/tests/roots/test-ext-autodoc/circular_import/__init__.py b/tests/roots/test-ext-autodoc/circular_import/__init__.py new file mode 100644 index 00000000000..402678d6706 --- /dev/null +++ b/tests/roots/test-ext-autodoc/circular_import/__init__.py @@ -0,0 +1 @@ +from circular_import.c import SomeClass diff --git a/tests/roots/test-ext-autodoc/circular_import/a.py b/tests/roots/test-ext-autodoc/circular_import/a.py new file mode 100644 index 00000000000..97ad9d8ab1b --- /dev/null +++ b/tests/roots/test-ext-autodoc/circular_import/a.py @@ -0,0 +1 @@ +X = 42 diff --git a/tests/roots/test-ext-autodoc/circular_import/b.py b/tests/roots/test-ext-autodoc/circular_import/b.py new file mode 100644 index 00000000000..c9b8ad54249 --- /dev/null +++ b/tests/roots/test-ext-autodoc/circular_import/b.py @@ -0,0 +1,4 @@ +import typing + +if typing.TYPE_CHECKING: + from circular_import import SomeClass diff --git a/tests/roots/test-ext-autodoc/circular_import/c.py b/tests/roots/test-ext-autodoc/circular_import/c.py new file mode 100644 index 00000000000..0a8829e378c --- /dev/null +++ b/tests/roots/test-ext-autodoc/circular_import/c.py @@ -0,0 +1,6 @@ +import circular_import.a +import circular_import.b + + +class SomeClass: + X = circular_import.a.X diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index c2f0d0bfc44..02869b8bee2 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -2024,6 +2024,19 @@ def test_autodoc_TYPE_CHECKING(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_TYPE_CHECKING_circular_import(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'circular_import', options) + assert list(actual) == [ + '', + '.. py:module:: circular_import', + '', + ] + assert sys.modules["circular_import"].a is sys.modules["circular_import.a"] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_singledispatch(app): options = {"members": None}