Skip to content

Commit

Permalink
[Doc] Use autodoc_mock_imports to mock deps for sphinx builds (ray-pr…
Browse files Browse the repository at this point in the history
…oject#38817)

This PR is the first in a series needed to update Sphinx to the latest version. In this PR, we

- Remove a lot of external dependencies that aren't necessary for building the docs. The one exception to this is tune-sklearn, a project owned by ray-project which actually does have documentation hosted on the Ray docs site.
- External dependencies are now mocked out using Sphinx's autodoc_mock_imports mechanism, which is used by both autodoc and autosummary. The old module mocking mechanism has been removed in favor of this. The one exception to this is packaging.version.Version: Ray currently uses Version to modify some behaviors depending on the version of certain dependencies that the user has installed. 

---------

Signed-off-by: pdmurray <[email protected]>
Co-authored-by: Max Pumperla <[email protected]>
  • Loading branch information
peytondmurray and maxpumperla committed Sep 5, 2023
1 parent 10c461c commit 74e97de
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 170 deletions.
6 changes: 3 additions & 3 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.9"
python: "3.10"

# Build documentation in the docs/ directory with Sphinx
sphinx:
Expand All @@ -19,5 +19,5 @@ sphinx:
# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: doc/requirements-doc.txt
install:
- requirements: doc/requirements-doc.txt
2 changes: 1 addition & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ make linkcheck
To run tests for examples shipping with docstrings in Python files, run the following command:

```shell
RAY_MOCK_MODULES=0 make doctest
make doctest
```

## Adding examples as MyST Markdown Notebooks
Expand Down
48 changes: 3 additions & 45 deletions doc/requirements-doc.txt
Original file line number Diff line number Diff line change
@@ -1,48 +1,7 @@
# Production requirements. This is what readthedocs.com picks up

# Python / ML libraries
accelerate>=0.17.0
click
colorama
colorful
# Newer versions of fairscale do not support Python 3.6 even though they still have wheels for it.
# Have to manually pin it: https://github.com/facebookresearch/fairscale/issues/962
fairscale; python_version >= '3.7'
fairscale<=0.4.0; python_version < '3.7'
filelock
flask
flatbuffers
fastapi==0.99.1
jsonschema
mock
numpy
scikit-image
pandas
pillow
pyarrow
pydantic < 2
# Note: more recent typing-extensions does not work well with pinned pydantic <1.10.0
typing-extensions < 4.6.0
pyyaml
pytorch-lightning==1.6.5; sys_platform != 'darwin' or platform_machine != 'arm64'
pytorch-lightning; sys_platform == 'darwin' and platform_machine == 'arm64'
scikit-optimize
redis
starlette
uvicorn==0.22.0
werkzeug
wandb
tensorflow; sys_platform != 'darwin' or platform_machine != 'arm64'
tensorflow-macos; sys_platform == 'darwin' and platform_machine == 'arm64'
tensorflow-datasets
torch
torchvision
transformers

# Ray libraries
git+https://github.com/ray-project/tune-sklearn@master#tune-sklearn
git+https://github.com/ray-project/xgboost_ray@master#egg=xgboost_ray
git+https://github.com/ray-project/lightgbm_ray@main#lightgbm_ray
watchfiles # Required because sphinx-click doesn't support mocking

# Syntax highlighting
Pygments==2.13.0
Expand All @@ -63,6 +22,8 @@ sphinx-remove-toctrees==0.0.3
autodoc_pydantic==1.6.1
sphinx_design==0.4.1

pydantic<2 # Pydantic is required by autodoc_pydantic, but must be <2 for ray

# MyST
myst-parser==0.15.2
myst-nb==0.13.1
Expand All @@ -72,6 +33,3 @@ jupytext==1.13.6

# Pin urllib to avoid downstream ssl incompatibility issues
urllib3 < 1.27

# For `serve run --reload` CLI.
watchfiles==0.19.0
112 changes: 90 additions & 22 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from pathlib import Path
from importlib import import_module
import os
import sys
from unittest.mock import MagicMock
from jinja2.filters import FILTERS

sys.path.insert(0, os.path.abspath("."))
from custom_directives import (
DownloadAndPreprocessEcosystemDocs,
mock_modules,
update_context,
LinkcheckSummarizer,
build_gallery,
)


# Mocking modules allows Sphinx to work without installing Ray.
mock_modules()

assert (
"ray" not in sys.modules
), "If ray is already imported, we will not render documentation correctly!"
# Compiled ray modules need to be mocked out; readthedocs doesn't have support for
# compiling these. See https://readthedocs-lst.readthedocs.io/en/latest/faq.html
# for more information. Other external dependencies should not be added here.
# Instead add them to autodoc_mock_imports below.
mock_modules = [
"ray._raylet",
"ray.core.generated",
"ray.core.generated.common_pb2",
"ray.core.generated.runtime_env_common_pb2",
"ray.core.generated.gcs_pb2",
"ray.core.generated.logging_pb2",
"ray.core.generated.ray.protocol.Task",
"ray.serve.generated",
"ray.serve.generated.serve_pb2",
"ray.serve.generated.serve_pb2_grpc",
]
sys.modules.update((mod_name, MagicMock()) for mod_name in mock_modules)

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath("../../python/"))

import ray

# -- General configuration ------------------------------------------------

# The name of a reST role (builtin or Sphinx extension) to use as the default role, that
Expand Down Expand Up @@ -109,10 +116,6 @@
"replacements",
]

intersphinx_mapping = {
"sklearn": ("https://scikit-learn.org/stable/", None),
}

# Cache notebook outputs in _build/.jupyter_cache
# To prevent notebook execution, set this to "off". To force re-execution, set this to "force".
# To cache previous runs, set this to "cache".
Expand All @@ -137,9 +140,23 @@
# functionality with the `sphinx_tabs_disable_tab_closing` option.
sphinx_tabs_disable_tab_closing = True

# There's a flaky autodoc import for "TensorFlowVariables" that fails depending on the doc structure / order
# of imports.
# autodoc_mock_imports = ["ray.experimental.tf_utils"]
# Special mocking of packaging.version.Version is required when using sphinx;
# we can't just add this to autodoc_mock_imports, as packaging is imported by
# sphinx even before it can be mocked. Instead, we patch it here.
import packaging

Version = packaging.version.Version


class MockVersion(Version):
def __init__(self, version: str):
if isinstance(version, (str, bytes)):
super().__init__(version)
else:
super().__init__("0")


packaging.version.Version = MockVersion

# This is used to suppress warnings about explicit "toctree" directives.
suppress_warnings = ["etoc.toctree"]
Expand Down Expand Up @@ -180,11 +197,12 @@

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
from ray import __version__ as version
# built documents. Retrieve the version using `find_version` rather than importing
# directly (from ray import __version__) because initializing ray will prevent
# mocking of certain external dependencies.
from setup import find_version

# The full version, including alpha/beta/rc tags.
release = version
release = find_version("ray", "__init__.py")

language = None

Expand Down Expand Up @@ -436,3 +454,53 @@ def setup(app):
"ray.serve.deployment": "ray.serve.deployment_decorator",
"ray.serve.Deployment": "ray.serve.Deployment",
}

# Mock out external dependencies here.
autodoc_mock_imports = [
"transformers",
"horovod",
"datasets",
"tensorflow",
"torch",
"torchvision",
"lightgbm",
"lightgbm_ray",
"pytorch_lightning",
"xgboost",
"xgboost_ray",
"wandb",
"huggingface",
"joblib",
"watchfiles",
"setproctitle",
"gymnasium",
"fastapi",
"tree",
"uvicorn",
"starlette",
"fsspec",
"skimage",
"aiohttp",
]


for mock_target in autodoc_mock_imports:
assert mock_target not in sys.modules, (
f"Problematic mock target ({mock_target}) found; "
"autodoc_mock_imports cannot mock modules that have already"
"been loaded into sys.modules when the sphinx build starts."
)

# Other sphinx docs can be linked to if the appropriate URL to the docs
# is specified in the `intersphinx_mapping` - for example, types in function signatures
# that are defined in dependencies can link to their respective documentation.
intersphinx_mapping = {
"sklearn": ("https://scikit-learn.org/stable/", None),
}

# Ray must not be imported in conf.py because third party modules initialized by
# `import ray` will no be mocked out correctly. Perform a check here to ensure
# ray is not imported.
assert (
"ray" not in sys.modules
), "If ray is already imported, we will not render documentation correctly!"
99 changes: 0 additions & 99 deletions doc/source/custom_directives.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import logging
import logging.handlers
import os
import sys
import urllib
import urllib.request
from pathlib import Path
from queue import Queue
from urllib.parse import urlparse
import yaml


import requests
import scipy.linalg # noqa: F401

# Note: the scipy import has to stay here, it's used implicitly down the line
import scipy.stats # noqa: F401
from preprocess_github_markdown import preprocess_github_markdown_file
from sphinx.util import logging as sphinx_logging
from sphinx.util.console import red # type: ignore

import mock

__all__ = [
"DownloadAndPreprocessEcosystemDocs",
"mock_modules",
"update_context",
"LinkcheckSummarizer",
"build_gallery",
Expand Down Expand Up @@ -58,94 +47,6 @@ def update_context(app, pagename, templatename, context, doctree):
context["feedback_form_url"] = feedback_form_url(app.config.project, pagename)


MOCK_MODULES = [
"ax",
"ax.service.ax_client",
"ConfigSpace",
"dask.distributed",
"datasets",
"datasets.iterable_dataset",
"datasets.load",
"gym",
"gym.spaces",
"gymnasium",
"gymnasium.spaces",
"gymnasium.envs",
"horovod",
"horovod.runner",
"horovod.runner.common",
"horovod.runner.common.util",
"horovod.ray",
"horovod.ray.runner",
"horovod.ray.utils",
"horovod.torch",
"hyperopt",
"hyperopt.hp",
"kubernetes",
"mlflow",
"modin",
"optuna",
"optuna.distributions",
"optuna.samplers",
"optuna.trial",
"psutil",
"ray._raylet",
"ray.core.generated",
"ray.core.generated.common_pb2",
"ray.core.generated.runtime_env_common_pb2",
"ray.core.generated.gcs_pb2",
"ray.core.generated.logging_pb2",
"ray.core.generated.ray.protocol.Task",
"ray.serve.generated",
"ray.serve.generated.serve_pb2",
"ray.serve.generated.serve_pb2_grpc",
"scipy.signal",
"scipy.stats",
"setproctitle",
"tensorflow_probability",
"tensorflow.contrib",
"tensorflow.contrib.all_reduce",
"tensorflow.contrib.all_reduce.python",
"tensorflow.contrib.layers",
"tensorflow.contrib.rnn",
"tensorflow.contrib.slim",
"tree",
"wandb",
"wandb.data_types",
"wandb.util",
"zoopt",
"composer",
"composer.trainer",
"composer.loggers",
"composer.loggers.logger_destination",
"composer.core",
"composer.core.state",
]


def make_typing_mock(module, name):
class Object:
pass

Object.__module__ = module
Object.__qualname__ = name
Object.__name__ = name

return Object


def mock_modules():
if os.environ.get("RAY_MOCK_MODULES", "1") == "0":
return

for mod_name in MOCK_MODULES:
mock_module = mock.MagicMock()
mock_module.__spec__ = mock.MagicMock()
sys.modules[mod_name] = mock_module

sys.modules["ray._raylet"].ObjectRef = make_typing_mock("ray", "ObjectRef")


# Add doc files from external repositories to be downloaded during build here
# (repo, ref, path to get, path to save on disk)
EXTERNAL_MARKDOWN_FILES = []
Expand Down

0 comments on commit 74e97de

Please sign in to comment.