refactor `mock()` to based on PEP-451 implementation

This commit is contained in:
Takeshi KOMIYA 2018-12-04 22:49:51 +09:00
parent ab3eb1b61d
commit 1a50d34520
3 changed files with 81 additions and 8 deletions

View File

@ -39,12 +39,14 @@ Deprecated
``autodoc.DocstringSignatureMixin.get_doc()``, ``autodoc.DocstringSignatureMixin.get_doc()``,
``autodoc.DocstringSignatureMixin._find_signature()``, and ``autodoc.DocstringSignatureMixin._find_signature()``, and
``autodoc.ClassDocumenter.get_doc()`` are deprecated. ``autodoc.ClassDocumenter.get_doc()`` are deprecated.
* The ``importer`` argument of ``sphinx.ext.autodoc.importer._MockModule``
* The ``nodetype`` argument of ``sphinx.search.WordCollector. * The ``nodetype`` argument of ``sphinx.search.WordCollector.
is_meta_keywords()`` is_meta_keywords()``
* The ``suffix`` argument of ``env.doc2path()`` is deprecated. * The ``suffix`` argument of ``env.doc2path()`` is deprecated.
* The string style ``base`` argument of ``env.doc2path()`` is deprecated. * The string style ``base`` argument of ``env.doc2path()`` is deprecated.
* ``sphinx.application.Sphinx._setting_up_extension`` * ``sphinx.application.Sphinx._setting_up_extension``
* ``sphinx.ext.config.check_unicode()`` * ``sphinx.ext.config.check_unicode()``
* ``sphinx.ext.autodoc.importer._MockImporter``
* ``sphinx.ext.doctest.doctest_encode()`` * ``sphinx.ext.doctest.doctest_encode()``
* ``sphinx.testing.util.remove_unicode_literal()`` * ``sphinx.testing.util.remove_unicode_literal()``
* ``sphinx.util.force_decode()`` * ``sphinx.util.force_decode()``

View File

@ -187,6 +187,16 @@ The following is a list of deprecated interfaces.
- 3.0 - 3.0
- N/A - N/A
* - The ``importer`` argument of ``sphinx.ext.autodoc.importer._MockModule``
- 2.0
- 3.0
- N/A
* - ``sphinx.ext.autodoc.importer._MockImporter``
- 2.0
- 3.0
- N/A
* - ``sphinx.writers.latex.LaTeXTranslator._make_visit_admonition()`` * - ``sphinx.writers.latex.LaTeXTranslator._make_visit_admonition()``
- 2.0 - 2.0
- 3.0 - 3.0

View File

@ -14,14 +14,17 @@ import sys
import traceback import traceback
import warnings import warnings
from collections import namedtuple from collections import namedtuple
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
from types import FunctionType, MethodType, ModuleType from types import FunctionType, MethodType, ModuleType
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.inspect import isenumclass, safe_getattr from sphinx.util.inspect import isenumclass, safe_getattr
if False: if False:
# For type annotation # For type annotation
from typing import Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple # NOQA from typing import Any, Callable, Dict, Generator, Iterator, List, Optional, Sequence, Tuple, Union # NOQA
from sphinx.util.typing import unicode # NOQA from sphinx.util.typing import unicode # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -78,13 +81,16 @@ class _MockModule(ModuleType):
"""Used by autodoc_mock_imports.""" """Used by autodoc_mock_imports."""
__file__ = '/dev/null' __file__ = '/dev/null'
def __init__(self, name, loader): def __init__(self, name, loader=None):
# type: (str, _MockImporter) -> None # type: (str, _MockImporter) -> None
self.__name__ = self.__package__ = name super(_MockModule, self).__init__(name)
self.__loader__ = loader
self.__all__ = [] # type: List[str] self.__all__ = [] # type: List[str]
self.__path__ = [] # type: List[str] self.__path__ = [] # type: List[str]
if loader is not None:
warnings.warn('The loader argument for _MockModule is deprecated.',
RemovedInSphinx30Warning)
def __getattr__(self, name): def __getattr__(self, name):
# type: (str) -> _MockObject # type: (str) -> _MockObject
o = _MockObject() o = _MockObject()
@ -100,6 +106,9 @@ class _MockImporter:
# enable hook by adding itself to meta_path # enable hook by adding itself to meta_path
sys.meta_path.insert(0, self) sys.meta_path.insert(0, self)
warnings.warn('_MockImporter is now deprecated.',
RemovedInSphinx30Warning)
def disable(self): def disable(self):
# type: () -> None # type: () -> None
# remove `self` from `sys.meta_path` to disable import hook # remove `self` from `sys.meta_path` to disable import hook
@ -131,14 +140,66 @@ class _MockImporter:
return module return module
class MockLoader(Loader):
"""A loader for mocking."""
def __init__(self, finder):
# type: (MockFinder) -> None
super(MockLoader, self).__init__()
self.finder = finder
def create_module(self, spec):
# type: (ModuleSpec) -> ModuleType
logger.debug('[autodoc] adding a mock module as %s!', spec.name)
self.finder.mocked_modules.append(spec.name)
return _MockModule(spec.name)
def exec_module(self, module):
# type: (ModuleType) -> None
pass # nothing to do
class MockFinder(MetaPathFinder):
"""A finder for mocking."""
def __init__(self, modnames):
# type: (List[str]) -> None
super(MockFinder, self).__init__()
self.modnames = modnames
self.loader = MockLoader(self)
self.mocked_modules = [] # type: List[str]
def find_spec(self, fullname, path, target=None):
# type: (str, Sequence[Union[bytes, str]], ModuleType) -> ModuleSpec
for modname in self.modnames:
# check if fullname is (or is a descendant of) one of our targets
if modname == fullname or fullname.startswith(modname + '.'):
return ModuleSpec(fullname, self.loader)
return None
def invalidate_caches(self):
# type: () -> None
"""Invalidate mocked modules on sys.modules."""
for modname in self.mocked_modules:
sys.modules.pop(modname, None)
@contextlib.contextmanager @contextlib.contextmanager
def mock(names): def mock(modnames):
# type: (List[str]) -> Generator # type: (List[str]) -> Generator[None, None, None]
"""Insert mock modules during context::
with mock(['target.module.name']):
# mock modules are enabled here
...
"""
try: try:
importer = _MockImporter(names) finder = MockFinder(modnames)
sys.meta_path.insert(0, finder)
yield yield
finally: finally:
importer.disable() sys.meta_path.remove(finder)
finder.invalidate_caches()
def import_module(modname, warningiserror=False): def import_module(modname, warningiserror=False):