Skip to content

Commit

Permalink
Append list of wrapped finders in sys.meta_path with list of `non…
Browse files Browse the repository at this point in the history
…-wrapped` finders (#4467)

Readthedocs hasn't been able to build on libraries that import Cirq since fd257e6#r55422405.

This is because Sphinx inserts a `MockFinder` to [sys.meta_path](https://github.com/sphinx-doc/sphinx/blob/f127a2ff5d6d86918a5d3ac975e8ab8a24c407d1/sphinx/ext/autodoc/mock.py#L148) but when Sphinx tries to remove the inserted `MockFinder` from said [path](https://github.com/sphinx-doc/sphinx/blob/f127a2ff5d6d86918a5d3ac975e8ab8a24c407d1/sphinx/ext/autodoc/mock.py#L151), the specific finder that Sphinx is looking for doesn't exist because Cirq wrapped it: https://github.com/quantumlib/Cirq/blob/58cda7a4f9718ef0bebd91950af192df8d47b0d6/cirq-core/cirq/_compat.py#L624


This change fixes the issue, but I don't know what consequences it has for other stuff. I'd be glad to hear better suggestions.

If this looks good, we could do the same thing for  [def wrap_module_executions](https://github.com/quantumlib/Cirq/blob/58cda7a4f9718ef0bebd91950af192df8d47b0d6/cirq-core/cirq/_import.py#L138) as well.
  • Loading branch information
vtomole committed Sep 1, 2021
1 parent 3b6ddfb commit 9349802
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 2 deletions.
7 changes: 7 additions & 0 deletions cirq-core/cirq/_compat.py
Expand Up @@ -621,6 +621,13 @@ def deprecated_submodule(
)

def wrap(finder: Any) -> Any:
# Sphinx looks for non-wrapped MockFinders
# so we have to check for them and not wrap them
if 'sphinx' in sys.modules:
from sphinx.ext.autodoc.mock import MockFinder

if isinstance(finder, MockFinder):
return finder
if not hasattr(finder, 'find_spec'):
return finder
return DeprecatedModuleFinder(
Expand Down
69 changes: 68 additions & 1 deletion cirq-core/cirq/_compat_test.py
Expand Up @@ -20,9 +20,11 @@
import types
import warnings
from types import ModuleType
from typing import Callable, Optional
from typing import Any, Callable, List, Optional, Sequence, Union
from importlib.machinery import ModuleSpec
from unittest import mock
from importlib.abc import MetaPathFinder


import numpy as np
import pandas as pd
Expand Down Expand Up @@ -853,3 +855,68 @@ def _dir_is_still_valid_inner():

for m in ['fake_a', 'info', 'module_a', 'sys']:
assert m in dir(mod)


class MockModule(ModuleType):
def __init__(self, module_name: str):
ModuleType.__init__(self, module_name)
if '.' in module_name:
package, module = module_name.rsplit('.', 1)
setattr(get_mock_module(package), module, self)

def _initialize_(self, module_code: types.FunctionType):
self.__dict__.update(module_code(self.__name__))


def get_mock_module(module_name: str) -> ModuleType:
if module_name not in sys.modules:
sys.modules[module_name] = MockModule(module_name)
return sys.modules[module_name]


def modulize(module_name: str) -> Callable[[types.FunctionType], Any]:
"""Converts a function into a module:
https://stackoverflow.com/a/45421428/5716192
"""
return get_mock_module(
module_name
)._initialize_ # type: ignore # mypy can't detect the _initialize_ method
# from the MockModule in sys.modules[module_name]


def test_deprecated_module_does_not_wrap_mockfinder():
@modulize('sphinx.ext.autodoc.mock')
def module_code( # pylint: disable=unused-variable # https://github.com/PyCQA/pylint/issues/2842
__name__,
): # pylint: disable=redefined-builtin

# put module code here
class MockFinder(MetaPathFinder):
def __init__(self, modnames: List[str]) -> None:
super().__init__()

def find_spec(
self,
fullname: Optional[str] = None,
path: Optional[Sequence[Union[bytes, str]]] = None,
target: Optional[ModuleType] = None,
) -> None:
pass

# the function must return locals()
return locals()

from sphinx.ext.autodoc.mock import MockFinder

fake_mockfinder = MockFinder([])
sys.meta_path.insert(0, fake_mockfinder)
deprecated_submodule(
new_module_name='sphinx_1',
old_parent='sphinx_2',
old_child='old_ch',
deadline='v1.2',
create_attribute=False,
)
assert fake_mockfinder in sys.meta_path
# Cleanup sys.metapath after test
sys.meta_path.remove(fake_mockfinder)
6 changes: 5 additions & 1 deletion dev_tools/conf/mypy.ini
Expand Up @@ -27,4 +27,8 @@ follow_imports_for_stubs = true

# ruamel is a downstream dependency of cirq-rigetti through pyquil.
[mypy-ruamel.*]
ignore_missing_imports = true
ignore_missing_imports = true

# Mocked module in cirq/_compat.py
[mypy-sphinx.*]
ignore_missing_imports = true

0 comments on commit 9349802

Please sign in to comment.