Add `sphinx.util._importer` (#12762)

This commit is contained in:
Adam Turner
2024-08-11 17:24:26 +01:00
committed by GitHub
parent 655d1c7213
commit d39ba32604
8 changed files with 85 additions and 56 deletions

View File

@@ -13,6 +13,10 @@ Incompatible changes
Deprecated
----------
* #12762: Deprecate ``sphinx.util.import_object``.
Use :py:func:`importlib.import_module` instead.
Patch by Adam Turner.
Features added
--------------

View File

@@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- Removed
- Alternatives
* - ``sphinx.util.import_object``
- 8.1
- 10.0
- ``importlib.import_module``
* - ``sphinx.ext.intersphinx.normalize_intersphinx_mapping``
- 8.0
- 10.0

View File

@@ -19,10 +19,10 @@ from sphinx.locale import __
from sphinx.util import (
UnicodeDecodeErrorHandler,
get_filetype,
import_object,
logging,
rst,
)
from sphinx.util._importer import import_object
from sphinx.util.build_phase import BuildPhase
from sphinx.util.console import bold
from sphinx.util.display import progress_message, status_iterator
@@ -139,8 +139,11 @@ class Builder:
def create_template_bridge(self) -> None:
"""Return the template bridge configured."""
if self.config.template_bridge:
self.templates = import_object(self.config.template_bridge,
'template_bridge setting')()
template_bridge_cls = import_object(
self.config.template_bridge,
source='template_bridge setting'
)
self.templates = template_bridge_cls()
else:
from sphinx.jinja2glue import BuiltinTemplateLoader
self.templates = BuiltinTemplateLoader()

View File

@@ -29,7 +29,7 @@ except ImportError:
from sphinx.errors import ExtensionError, SphinxError
from sphinx.search import SearchLanguage
from sphinx.util import import_object
from sphinx.util._importer import import_object
class BaseSplitter:
@@ -505,12 +505,18 @@ class SearchJapanese(SearchLanguage):
language_name = 'Japanese'
def init(self, options: dict[str, str]) -> None:
dotted_path = options.get('type', 'sphinx.search.ja.DefaultSplitter')
try:
self.splitter = import_object(dotted_path)(options)
except ExtensionError as exc:
raise ExtensionError("Splitter module %r can't be imported" %
dotted_path) from exc
dotted_path = options.get('type')
if dotted_path is None:
self.splitter = DefaultSplitter(options)
else:
try:
splitter_cls = import_object(
dotted_path, "html_search_options['type'] setting"
)
self.splitter = splitter_cls(options)
except ExtensionError as exc:
msg = f"Splitter module {dotted_path!r} can't be imported"
raise ExtensionError(msg) from exc
def split(self, input: str) -> list[str]:
return self.splitter.split(input)

View File

@@ -6,15 +6,14 @@ import hashlib
import os
import posixpath
import re
from importlib import import_module
from os import path
from typing import IO, Any
from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
from sphinx.errors import ExtensionError, FiletypeNotFoundError
from sphinx.errors import FiletypeNotFoundError
from sphinx.locale import __
from sphinx.util import _importer, logging
from sphinx.util import index_entries as _index_entries
from sphinx.util import logging
from sphinx.util.console import strip_colors # NoQA: F401
from sphinx.util.matching import patfilter # NoQA: F401
from sphinx.util.nodes import ( # NoQA: F401
@@ -217,27 +216,6 @@ def parselinenos(spec: str, total: int) -> list[int]:
return items
def import_object(objname: str, source: str | None = None) -> Any:
"""Import python object by qualname."""
try:
objpath = objname.split('.')
modname = objpath.pop(0)
obj = import_module(modname)
for name in objpath:
modname += '.' + name
try:
obj = getattr(obj, name)
except AttributeError:
obj = import_module(modname)
return obj
except (AttributeError, ImportError) as exc:
if source:
raise ExtensionError('Could not import %s (needed for %s)' %
(objname, source), exc) from exc
raise ExtensionError('Could not import %s' % objname, exc) from exc
def encode_uri(uri: str) -> str:
split = list(urlsplit(uri))
split[1] = split[1].encode('idna').decode('ascii')
@@ -262,6 +240,7 @@ _DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
(9, 0)),
'md5': (_md5, '', (9, 0)),
'sha1': (_sha1, '', (9, 0)),
'import_object': (_importer.import_object, '', (10, 0)),
}

27
sphinx/util/_importer.py Normal file
View File

@@ -0,0 +1,27 @@
from __future__ import annotations
from importlib import import_module
from typing import Any
from sphinx.errors import ExtensionError
def import_object(object_name: str, /, source: str = '') -> Any:
"""Import python object by qualname."""
obj_path = object_name.split('.')
module_name = obj_path.pop(0)
try:
obj = import_module(module_name)
for name in obj_path:
module_name += '.' + name
try:
obj = getattr(obj, name)
except AttributeError:
obj = import_module(module_name)
except (AttributeError, ImportError) as exc:
if source:
msg = f'Could not import {object_name} (needed for {source})'
raise ExtensionError(msg, exc) from exc
msg = f'Could not import {object_name}'
raise ExtensionError(msg, exc) from exc
return obj

View File

@@ -5,8 +5,7 @@ import tempfile
import pytest
from sphinx.errors import ExtensionError
from sphinx.util import encode_uri, import_object, parselinenos
from sphinx.util import encode_uri, parselinenos
from sphinx.util.osutil import ensuredir
@@ -40,26 +39,6 @@ def test_ensuredir():
assert os.path.isdir(path)
def test_import_object():
module = import_object('sphinx')
assert module.__name__ == 'sphinx'
module = import_object('sphinx.application')
assert module.__name__ == 'sphinx.application'
obj = import_object('sphinx.application.Sphinx')
assert obj.__name__ == 'Sphinx'
with pytest.raises(ExtensionError) as exc:
import_object('sphinx.unknown_module')
assert exc.value.args[0] == 'Could not import sphinx.unknown_module'
with pytest.raises(ExtensionError) as exc:
import_object('sphinx.unknown_module', 'my extension')
expected = 'Could not import sphinx.unknown_module (needed for my extension)'
assert exc.value.args[0] == expected
def test_parselinenos():
assert parselinenos('1,2,3', 10) == [0, 1, 2]
assert parselinenos('4, 5, 6', 10) == [3, 4, 5]

View File

@@ -0,0 +1,26 @@
"""Test sphinx.util._importer."""
import pytest
from sphinx.errors import ExtensionError
from sphinx.util._importer import import_object
def test_import_object():
module = import_object('sphinx')
assert module.__name__ == 'sphinx'
module = import_object('sphinx.application')
assert module.__name__ == 'sphinx.application'
obj = import_object('sphinx.application.Sphinx')
assert obj.__name__ == 'Sphinx'
with pytest.raises(ExtensionError) as exc:
import_object('sphinx.unknown_module')
assert exc.value.args[0] == 'Could not import sphinx.unknown_module'
with pytest.raises(ExtensionError) as exc:
import_object('sphinx.unknown_module', 'my extension')
expected = 'Could not import sphinx.unknown_module (needed for my extension)'
assert exc.value.args[0] == expected