Skip to content

Commit e3175f4

Browse files
authored
Reorganise and rename sphinx.ext.autodoc._importer (#14036)
1 parent f7b98a0 commit e3175f4

File tree

9 files changed

+156
-179
lines changed

9 files changed

+156
-179
lines changed

sphinx/ext/autodoc/_docstrings.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from typing import TYPE_CHECKING, TypeVar
44

55
from sphinx.errors import PycodeError
6+
from sphinx.ext.autodoc._importer import (
7+
_get_attribute_comment,
8+
_is_runtime_instance_attribute_not_commented,
9+
)
610
from sphinx.ext.autodoc._property_types import _ClassDefProperties
711
from sphinx.ext.autodoc._sentinels import (
812
RUNTIME_INSTANCE_ATTRIBUTE,
@@ -21,8 +25,8 @@
2125

2226
from sphinx.events import EventManager
2327
from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
28+
from sphinx.ext.autodoc._importer import _AttrGetter
2429
from sphinx.ext.autodoc._property_types import _ItemProperties
25-
from sphinx.ext.autodoc.importer import _AttrGetter
2630

2731
logger = logging.getLogger('sphinx.ext.autodoc')
2832

@@ -241,11 +245,6 @@ def _get_docstring_lines(
241245
return [prepare_docstring(docstring, tab_width)]
242246

243247
if props.obj_type == 'attribute':
244-
from sphinx.ext.autodoc.importer import (
245-
_get_attribute_comment,
246-
_is_runtime_instance_attribute_not_commented,
247-
)
248-
249248
# Check the attribute has a docstring-comment
250249
comment = _get_attribute_comment(
251250
parent=parent, obj_path=props.parts, attrname=props.parts[-1]

sphinx/ext/autodoc/_generate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
from sphinx.environment import _CurrentDocument
2424
from sphinx.events import EventManager
2525
from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
26+
from sphinx.ext.autodoc._importer import _AttrGetter
2627
from sphinx.ext.autodoc._property_types import _ItemProperties
27-
from sphinx.ext.autodoc.importer import _AttrGetter
2828
from sphinx.util.typing import _RestifyMode
2929

3030
logger = logging.getLogger('sphinx.ext.autodoc')

sphinx/ext/autodoc/importer.py renamed to sphinx/ext/autodoc/_importer.py

Lines changed: 139 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
from sphinx.ext.autodoc.mock import ismock, mock, undecorate
2020
from sphinx.pycode import ModuleAnalyzer
2121
from sphinx.util import inspect, logging
22-
from sphinx.util.inspect import (
23-
isclass,
24-
safe_getattr,
25-
)
22+
from sphinx.util.inspect import isclass, safe_getattr
2623
from sphinx.util.typing import get_type_hints
2724

2825
if TYPE_CHECKING:
@@ -71,119 +68,47 @@ def __repr__(self) -> str:
7168
return f'<{self.__class__.__name__} {self.__dict__}>'
7269

7370

74-
def mangle(subject: Any, name: str) -> str:
75-
"""Mangle the given name."""
76-
try:
77-
if isclass(subject) and name.startswith('__') and not name.endswith('__'):
78-
return f'_{subject.__name__}{name}'
79-
except AttributeError:
80-
pass
81-
82-
return name
83-
84-
85-
def import_module(modname: str, try_reload: bool = False) -> Any:
86-
if modname in sys.modules:
87-
return sys.modules[modname]
88-
89-
skip_pyi = bool(os.getenv('SPHINX_AUTODOC_IGNORE_NATIVE_MODULE_TYPE_STUBS', ''))
90-
original_module_names = frozenset(sys.modules)
91-
try:
92-
spec = find_spec(modname)
93-
if spec is None:
94-
msg = f'No module named {modname!r}'
95-
raise ModuleNotFoundError(msg, name=modname) # NoQA: TRY301
96-
spec, pyi_path = _find_type_stub_spec(spec, modname)
97-
if skip_pyi or pyi_path is None:
98-
module = importlib.import_module(modname)
99-
else:
100-
if spec.loader is None:
101-
msg = 'missing loader'
102-
raise ImportError(msg, name=spec.name) # NoQA: TRY301
103-
sys.modules[modname] = module = module_from_spec(spec)
104-
spec.loader.exec_module(module)
105-
except ImportError:
106-
raise
107-
except BaseException as exc:
108-
# Importing modules may cause any side effects, including
109-
# SystemExit, so we need to catch all errors.
110-
raise ImportError(exc, traceback.format_exc()) from exc
111-
if try_reload and os.environ.get('SPHINX_AUTODOC_RELOAD_MODULES'):
112-
new_modules = [m for m in sys.modules if m not in original_module_names]
113-
# Try reloading modules with ``typing.TYPE_CHECKING == True``.
114-
try:
115-
typing.TYPE_CHECKING = True # type: ignore[misc]
116-
# Ignore failures; we've already successfully loaded these modules
117-
with contextlib.suppress(ImportError, KeyError):
118-
for m in new_modules:
119-
mod_path = getattr(sys.modules[m], '__file__', '')
120-
if mod_path and mod_path.endswith('.pyi'):
121-
continue
122-
_reload_module(sys.modules[m])
123-
finally:
124-
typing.TYPE_CHECKING = False # type: ignore[misc]
125-
module = sys.modules[modname]
126-
return module
127-
128-
129-
def _find_type_stub_spec(
130-
spec: ModuleSpec, modname: str
131-
) -> tuple[ModuleSpec, Path | None]:
132-
"""Try finding a spec for a PEP 561 '.pyi' stub file for native modules."""
133-
if spec.origin is None:
134-
return spec, None
135-
136-
for suffix in _NATIVE_SUFFIXES:
137-
if not spec.origin.endswith(suffix):
138-
continue
139-
pyi_path = Path(spec.origin.removesuffix(suffix) + '.pyi')
140-
if not pyi_path.is_file():
141-
continue
142-
pyi_loader = _StubFileLoader(modname, path=str(pyi_path))
143-
pyi_spec = spec_from_loader(modname, loader=pyi_loader)
144-
if pyi_spec is not None:
145-
return pyi_spec, pyi_path
146-
return spec, None
147-
148-
149-
class _StubFileLoader(FileLoader):
150-
"""Load modules from ``.pyi`` stub files."""
151-
152-
def get_source(self, fullname: str) -> str:
153-
path = self.get_filename(fullname)
154-
for suffix in _NATIVE_SUFFIXES:
155-
if not path.endswith(suffix):
156-
continue
157-
path = path.removesuffix(suffix) + '.pyi'
158-
try:
159-
source_bytes = self.get_data(path)
160-
except OSError as exc:
161-
raise ImportError from exc
162-
return decode_source(source_bytes)
163-
164-
165-
def _reload_module(module: ModuleType) -> Any:
166-
"""Call importlib.reload(module), convert exceptions to ImportError"""
71+
def _import_object(
72+
*,
73+
get_attr: _AttrGetter = safe_getattr,
74+
mock_imports: list[str],
75+
module_name: str,
76+
obj_path: Sequence[str],
77+
obj_type: _AutodocObjType,
78+
type_aliases: dict[str, Any] | None,
79+
) -> _ImportedObject | None:
80+
"""Import the module and get the object to document."""
16781
try:
168-
return importlib.reload(module)
169-
except BaseException as exc:
170-
# Importing modules may cause any side effects, including
171-
# SystemExit, so we need to catch all errors.
172-
raise ImportError(exc, traceback.format_exc()) from exc
173-
82+
with mock(mock_imports):
83+
im = _import_from_module_and_path(
84+
module_name=module_name, obj_path=obj_path, get_attr=get_attr
85+
)
86+
except ImportError as exc:
87+
if obj_type == 'data':
88+
im_ = _import_data_declaration(
89+
module_name=module_name,
90+
obj_path=obj_path,
91+
mock_imports=mock_imports,
92+
type_aliases=type_aliases,
93+
)
94+
if im_ is not None:
95+
return im_
96+
elif obj_type == 'attribute':
97+
im_ = _import_attribute_declaration(
98+
module_name=module_name,
99+
obj_path=obj_path,
100+
mock_imports=mock_imports,
101+
type_aliases=type_aliases,
102+
get_attr=get_attr,
103+
)
104+
if im_ is not None:
105+
return im_
106+
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
107+
return None
174108

175-
def import_object(
176-
modname: str,
177-
objpath: list[str],
178-
objtype: str = '',
179-
attrgetter: _AttrGetter = safe_getattr,
180-
) -> Any:
181-
ret = _import_from_module_and_path(
182-
module_name=modname, obj_path=objpath, get_attr=attrgetter
183-
)
184-
if isinstance(ret, _ImportedObject):
185-
return [ret.module, ret.parent, ret.object_name, ret.obj]
186-
return None
109+
if ismock(im.obj):
110+
im.obj = undecorate(im.obj)
111+
return im
187112

188113

189114
def _import_from_module_and_path(
@@ -203,7 +128,7 @@ def _import_from_module_and_path(
203128
try:
204129
while module is None:
205130
try:
206-
module = import_module(module_name, try_reload=True)
131+
module = _import_module(module_name, try_reload=True)
207132
logger.debug('[autodoc] import %s => %r', module_name, module)
208133
except ImportError as exc:
209134
logger.debug('[autodoc] import %s => failed', module_name)
@@ -221,7 +146,7 @@ def _import_from_module_and_path(
221146
for attr_name in obj_path:
222147
parent = obj
223148
logger.debug('[autodoc] getattr(_, %r)', attr_name)
224-
mangled_name = mangle(obj, attr_name)
149+
mangled_name = _mangle_name(obj, attr_name)
225150
obj = get_attr(obj, mangled_name)
226151

227152
try:
@@ -253,7 +178,7 @@ def _import_from_module_and_path(
253178
err_parts = [f'autodoc: failed to import {module_name!r}']
254179

255180
if isinstance(exc, ImportError):
256-
# import_module() raises ImportError having real exception obj and
181+
# _import_module() raises ImportError having real exception obj and
257182
# traceback
258183
real_exc = exc.args[0]
259184
traceback_msg = traceback.format_exception(exc)
@@ -280,52 +205,105 @@ def _import_from_module_and_path(
280205
raise ImportError(errmsg) from exc
281206

282207

283-
def _import_object(
284-
*,
285-
module_name: str,
286-
obj_path: Sequence[str],
287-
mock_imports: list[str],
288-
get_attr: _AttrGetter = safe_getattr,
289-
obj_type: _AutodocObjType,
290-
type_aliases: dict[str, Any] | None,
291-
) -> _ImportedObject | None:
292-
"""Import the object given by *module_name* and *obj_path* and set
293-
it as *object*.
208+
def _import_module(modname: str, try_reload: bool = False) -> Any:
209+
if modname in sys.modules:
210+
return sys.modules[modname]
294211

295-
Returns True if successful, False if an error occurred.
296-
"""
212+
skip_pyi = bool(os.getenv('SPHINX_AUTODOC_IGNORE_NATIVE_MODULE_TYPE_STUBS', ''))
213+
original_module_names = frozenset(sys.modules)
297214
try:
298-
with mock(mock_imports):
299-
im = _import_from_module_and_path(
300-
module_name=module_name, obj_path=obj_path, get_attr=get_attr
301-
)
302-
except ImportError as exc:
303-
if obj_type == 'data':
304-
im_ = _import_data_declaration(
305-
module_name=module_name,
306-
obj_path=obj_path,
307-
mock_imports=mock_imports,
308-
type_aliases=type_aliases,
309-
)
310-
if im_ is not None:
311-
return im_
312-
elif obj_type == 'attribute':
313-
im_ = _import_attribute_declaration(
314-
module_name=module_name,
315-
obj_path=obj_path,
316-
mock_imports=mock_imports,
317-
type_aliases=type_aliases,
318-
get_attr=get_attr,
319-
)
320-
if im_ is not None:
321-
return im_
215+
spec = find_spec(modname)
216+
if spec is None:
217+
msg = f'No module named {modname!r}'
218+
raise ModuleNotFoundError(msg, name=modname) # NoQA: TRY301
219+
spec, pyi_path = _find_type_stub_spec(spec, modname)
220+
if skip_pyi or pyi_path is None:
221+
module = importlib.import_module(modname)
222+
else:
223+
if spec.loader is None:
224+
msg = 'missing loader'
225+
raise ImportError(msg, name=spec.name) # NoQA: TRY301
226+
sys.modules[modname] = module = module_from_spec(spec)
227+
spec.loader.exec_module(module)
228+
except ImportError:
229+
raise
230+
except BaseException as exc:
231+
# Importing modules may cause any side effects, including
232+
# SystemExit, so we need to catch all errors.
233+
raise ImportError(exc, traceback.format_exc()) from exc
234+
if try_reload and os.environ.get('SPHINX_AUTODOC_RELOAD_MODULES'):
235+
new_modules = [m for m in sys.modules if m not in original_module_names]
236+
# Try reloading modules with ``typing.TYPE_CHECKING == True``.
237+
try:
238+
typing.TYPE_CHECKING = True # type: ignore[misc]
239+
# Ignore failures; we've already successfully loaded these modules
240+
with contextlib.suppress(ImportError, KeyError):
241+
for m in new_modules:
242+
mod_path = getattr(sys.modules[m], '__file__', '')
243+
if mod_path and mod_path.endswith('.pyi'):
244+
continue
245+
_reload_module(sys.modules[m])
246+
finally:
247+
typing.TYPE_CHECKING = False # type: ignore[misc]
248+
module = sys.modules[modname]
249+
return module
322250

323-
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
324-
return None
325251

326-
if ismock(im.obj):
327-
im.obj = undecorate(im.obj)
328-
return im
252+
def _find_type_stub_spec(
253+
spec: ModuleSpec, modname: str
254+
) -> tuple[ModuleSpec, Path | None]:
255+
"""Try finding a spec for a PEP 561 '.pyi' stub file for native modules."""
256+
if spec.origin is None:
257+
return spec, None
258+
259+
for suffix in _NATIVE_SUFFIXES:
260+
if not spec.origin.endswith(suffix):
261+
continue
262+
pyi_path = Path(spec.origin.removesuffix(suffix) + '.pyi')
263+
if not pyi_path.is_file():
264+
continue
265+
pyi_loader = _StubFileLoader(modname, path=str(pyi_path))
266+
pyi_spec = spec_from_loader(modname, loader=pyi_loader)
267+
if pyi_spec is not None:
268+
return pyi_spec, pyi_path
269+
return spec, None
270+
271+
272+
class _StubFileLoader(FileLoader):
273+
"""Load modules from ``.pyi`` stub files."""
274+
275+
def get_source(self, fullname: str) -> str:
276+
path = self.get_filename(fullname)
277+
for suffix in _NATIVE_SUFFIXES:
278+
if not path.endswith(suffix):
279+
continue
280+
path = path.removesuffix(suffix) + '.pyi'
281+
try:
282+
source_bytes = self.get_data(path)
283+
except OSError as exc:
284+
raise ImportError from exc
285+
return decode_source(source_bytes)
286+
287+
288+
def _reload_module(module: ModuleType) -> Any:
289+
"""Call importlib.reload(module), convert exceptions to ImportError"""
290+
try:
291+
return importlib.reload(module)
292+
except BaseException as exc:
293+
# Importing modules may cause any side effects, including
294+
# SystemExit, so we need to catch all errors.
295+
raise ImportError(exc, traceback.format_exc()) from exc
296+
297+
298+
def _mangle_name(subject: Any, name: str) -> str:
299+
"""Mangle the given name."""
300+
try:
301+
if isclass(subject) and name.startswith('__') and not name.endswith('__'):
302+
return f'_{subject.__name__}{name}'
303+
except AttributeError:
304+
pass
305+
306+
return name
329307

330308

331309
def _import_data_declaration(
@@ -338,7 +316,7 @@ def _import_data_declaration(
338316
# annotation only instance variable (PEP-526)
339317
try:
340318
with mock(mock_imports):
341-
parent = import_module(module_name)
319+
parent = _import_module(module_name)
342320
annotations = get_type_hints(parent, None, type_aliases, include_extras=True)
343321
if obj_path[-1] in annotations:
344322
im = _ImportedObject(

0 commit comments

Comments
 (0)