From d5bdf5e57cc4e59e5272b1462b7b204eeb3e978c Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Fri, 25 Aug 2023 19:16:05 -0400 Subject: [PATCH] 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 --- sphinx/ext/autodoc/importer.py | 8 +++++++- tests/roots/test-ext-autodoc/circular_import/__init__.py | 1 + tests/roots/test-ext-autodoc/circular_import/a.py | 1 + tests/roots/test-ext-autodoc/circular_import/b.py | 4 ++++ tests/roots/test-ext-autodoc/circular_import/c.py | 7 +++++++ tests/roots/test-ext-autodoc/index.rst | 2 ++ 6 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/circular_import/__init__.py create mode 100644 tests/roots/test-ext-autodoc/circular_import/a.py create mode 100644 tests/roots/test-ext-autodoc/circular_import/b.py create mode 100644 tests/roots/test-ext-autodoc/circular_import/c.py 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..01aa134a110 --- /dev/null +++ b/tests/roots/test-ext-autodoc/circular_import/c.py @@ -0,0 +1,7 @@ +import circular_import.a +import circular_import.b + + +class SomeClass: + assert hasattr(circular_import, "a") + X = circular_import.a.X diff --git a/tests/roots/test-ext-autodoc/index.rst b/tests/roots/test-ext-autodoc/index.rst index eb10829dcff..4f7378516da 100644 --- a/tests/roots/test-ext-autodoc/index.rst +++ b/tests/roots/test-ext-autodoc/index.rst @@ -13,3 +13,5 @@ .. autofunction:: target.overload.sum .. autofunction:: target.typehints.tuple_args + +.. automodule:: circular_import