Skip to content

Commit

Permalink
autodoc: Reset sys.modules on partial import failure (#11645)
Browse files Browse the repository at this point in the history
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 <9087854+aa-turner@users.noreply.github.com>
  • Loading branch information
godlygeek and AA-Turner committed Aug 29, 2023
1 parent e494baa commit 8248be3
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGES
Expand Up @@ -16,6 +16,11 @@ Features added
Bugs fixed
----------

* #11645: Fix a regression preventing autodoc from importing modules within
packages that make use of ``if typing.TYPE_CHECKING:`` to guard circular
imports needed by type checkers.
Patch by Matt Wozniski.

Testing
-------

Expand Down
8 changes: 7 additions & 1 deletion sphinx/ext/autodoc/importer.py
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import importlib
import sys
import traceback
import typing
from typing import TYPE_CHECKING, Any, Callable, NamedTuple
Expand Down Expand Up @@ -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 [m for m in sys.modules if m not in orig_modules]:
sys.modules.pop(m)

typing.TYPE_CHECKING = False
module = import_module(modname, warningiserror=warningiserror)
finally:
Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-ext-autodoc/circular_import/__init__.py
@@ -0,0 +1 @@
from circular_import.c import SomeClass
1 change: 1 addition & 0 deletions tests/roots/test-ext-autodoc/circular_import/a.py
@@ -0,0 +1 @@
X = 42
4 changes: 4 additions & 0 deletions tests/roots/test-ext-autodoc/circular_import/b.py
@@ -0,0 +1,4 @@
import typing

if typing.TYPE_CHECKING:
from circular_import import SomeClass
6 changes: 6 additions & 0 deletions 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
13 changes: 13 additions & 0 deletions tests/test_ext_autodoc.py
Expand Up @@ -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}
Expand Down

0 comments on commit 8248be3

Please sign in to comment.