Skip to content

Commit

Permalink
Switch uses of __import__ to importlib.get_module()
Browse files Browse the repository at this point in the history
The Python docs for __import__ recommend using importlib.get_module().

https://docs.python.org/3/library/functions.html#__import__

> Note: This is an advanced function that is not needed in everyday
> Python programming, unlike importlib.import_module().

As importlib.get_module() uses the Python module cache and returns the
module, this also allows simplifying many module cache checks of use of
sys.modules.

importlib.get_module() has been available since Python 3.3.
  • Loading branch information
jdufresne committed Aug 17, 2019
1 parent bf573ae commit ecb1e76
Show file tree
Hide file tree
Showing 9 changed files with 36 additions and 36 deletions.
5 changes: 1 addition & 4 deletions sphinx/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ class RemovedInSphinx40Warning(PendingDeprecationWarning):

def deprecated_alias(modname, objects, warning):
# type: (str, Dict, Type[Warning]) -> None
module = sys.modules.get(modname)
if module is None:
module = import_module(modname)

module = import_module(modname)
sys.modules[modname] = _ModuleWrapper(module, modname, objects, warning) # type: ignore


Expand Down
7 changes: 3 additions & 4 deletions sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""

import sys
import importlib
import traceback
import warnings
from collections import namedtuple
Expand All @@ -23,14 +23,13 @@

def import_module(modname: str, warningiserror: bool = False) -> Any:
"""
Call __import__(modname), convert exceptions to ImportError
Call importlib.import_module(modname), convert exceptions to ImportError
"""
try:
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=ImportWarning)
with logging.skip_warningiserror(not warningiserror):
__import__(modname)
return sys.modules[modname]
return importlib.import_module(modname)
except BaseException as exc:
# Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors.
Expand Down
3 changes: 2 additions & 1 deletion sphinx/ext/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import inspect
import pickle
import re
from importlib import import_module
from os import path
from typing import Any, Dict, IO, List, Pattern, Set, Tuple

Expand Down Expand Up @@ -144,7 +145,7 @@ def build_py_coverage(self) -> None:
continue

try:
mod = __import__(mod_name, fromlist=['foo'])
mod = import_module(mod_name)
except ImportError as err:
logger.warning(__('module %s could not be imported: %s'), mod_name, err)
self.py_undoc[mod_name] = {'error': err}
Expand Down
12 changes: 7 additions & 5 deletions sphinx/ext/inheritance_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class E(B): pass
import builtins
import inspect
import re
import sys
from hashlib import md5
from importlib import import_module
from typing import Any, Dict, Iterable, List, Tuple
from typing import cast

Expand Down Expand Up @@ -74,8 +74,10 @@ def try_import(objname: str) -> Any:
Returns imported object or module. If failed, returns None value.
"""
try:
__import__(objname)
return sys.modules.get(objname)
return import_module(objname)
except TypeError:
# Relative import
return None
except ImportError:
matched = module_sig_re.match(objname)

Expand All @@ -87,8 +89,8 @@ def try_import(objname: str) -> Any:
if modname is None:
return None
try:
__import__(modname)
return getattr(sys.modules.get(modname), attrname, None)
module = import_module(modname)
return getattr(module, attrname, None)
except ImportError:
return None

Expand Down
3 changes: 2 additions & 1 deletion sphinx/highlighting.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import html
import warnings
from functools import partial
from importlib import import_module

from pygments import highlight
from pygments.filters import ErrorToken
Expand Down Expand Up @@ -92,7 +93,7 @@ def get_style(self, stylename):
return NoneStyle
elif '.' in stylename:
module, stylename = stylename.rsplit('.', 1)
return getattr(__import__(module, None, None, ['__name__']), stylename)
return getattr(import_module(module), stylename)
else:
return get_style_by_name(stylename)

Expand Down
8 changes: 5 additions & 3 deletions sphinx/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import traceback
import warnings
from importlib import import_module
from inspect import isclass
from types import MethodType

Expand Down Expand Up @@ -471,18 +472,19 @@ def load_extension(self, app, extname):
prefix = __('while setting up extension %s:') % extname
with prefixed_warnings(prefix):
try:
mod = __import__(extname, None, None, ['setup'])
mod = import_module(extname)
except ImportError as err:
logger.verbose(__('Original exception:\n') + traceback.format_exc())
raise ExtensionError(__('Could not import extension %s') % extname, err)

if not hasattr(mod, 'setup'):
setup = getattr(mod, 'setup', None)
if setup is None:
logger.warning(__('extension %r has no setup() function; is it really '
'a Sphinx extension module?'), extname)
metadata = {} # type: Dict[str, Any]
else:
try:
metadata = mod.setup(app)
metadata = setup(app)
except VersionRequirementError as err:
# add the extension name to the version required
raise VersionRequirementError(
Expand Down
4 changes: 2 additions & 2 deletions sphinx/search/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pickle
import re
import warnings
from importlib import import_module
from os import path

from docutils import nodes
Expand Down Expand Up @@ -278,8 +279,7 @@ def __init__(self, env, lang, options, scoring):
self.lang = SearchEnglish(options) # type: SearchLanguage
elif isinstance(lang_class, str):
module, classname = lang_class.rsplit('.', 1)
lang_class = getattr(__import__(module, None, None, [classname]),
classname)
lang_class = getattr(import_module(module), classname)
self.lang = lang_class(options)
else:
# it's directly a class (e.g. added by app.add_search_language)
Expand Down
19 changes: 8 additions & 11 deletions sphinx/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from collections import deque
from datetime import datetime
from hashlib import md5
from importlib import import_module
from os import path
from time import mktime, strptime
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple
Expand Down Expand Up @@ -270,12 +271,10 @@ def get_module_source(modname: str) -> Tuple[str, str]:
Can return ('file', 'filename') in which case the source is in the given
file, or ('string', 'source') which which case the source is the string.
"""
if modname not in sys.modules:
try:
__import__(modname)
except Exception as err:
raise PycodeError('error importing %r' % modname, err)
mod = sys.modules[modname]
try:
mod = import_module(modname)
except Exception as err:
raise PycodeError('error importing %r' % modname, err)
filename = getattr(mod, '__file__', None)
loader = getattr(mod, '__loader__', None)
if loader and getattr(loader, 'get_filename', None):
Expand Down Expand Up @@ -316,8 +315,7 @@ def get_full_modname(modname: str, attribute: str) -> str:
# Prevents a TypeError: if the last getattr() call will return None
# then it's better to return it directly
return None
__import__(modname)
module = sys.modules[modname]
module = import_module(modname)

# Allow an attribute to have multiple parts and incidentially allow
# repeated .s in the attribute.
Expand Down Expand Up @@ -572,14 +570,13 @@ def import_object(objname: str, source: str = None) -> Any:
try:
objpath = objname.split('.')
modname = objpath.pop(0)
obj = __import__(modname)
obj = import_module(modname)
for name in objpath:
modname += '.' + name
try:
obj = getattr(obj, name)
except AttributeError:
__import__(modname)
obj = getattr(obj, name)
obj = import_module(modname)

return obj
except (AttributeError, ImportError) as exc:
Expand Down
11 changes: 6 additions & 5 deletions tests/test_ext_autodoc_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import abc
import sys
from importlib import import_module

import pytest

Expand Down Expand Up @@ -56,27 +57,27 @@ def test_mock():
submodule = modname + '.submodule'
assert modname not in sys.modules
with pytest.raises(ImportError):
__import__(modname)
import_module(modname)

with mock([modname]):
__import__(modname)
import_module(modname)
assert modname in sys.modules
assert isinstance(sys.modules[modname], _MockModule)

# submodules are also mocked
__import__(submodule)
import_module(submodule)
assert submodule in sys.modules
assert isinstance(sys.modules[submodule], _MockModule)

assert modname not in sys.modules
with pytest.raises(ImportError):
__import__(modname)
import_module(modname)


def test_mock_does_not_follow_upper_modules():
with mock(['sphinx.unknown.module']):
with pytest.raises(ImportError):
__import__('sphinx.unknown')
import_module('sphinx.unknown')


@pytest.mark.skipif(sys.version_info < (3, 7), reason='Only for py37 or above')
Expand Down

0 comments on commit ecb1e76

Please sign in to comment.