Skip to content

Commit 3872f74

Browse files
Aydin-abNeelansh-Khare
authored andcommitted
[doc] add ipython3 lexer hook for notebooks with shell/magic cells (ray-project#63515)
## Description Sphinx falls back to the `python3` lexer on `.ipynb` files that don't declare `language_info.pygments_lexer`, but `python3` can't tokenise `!shell` or `%magic` cells. The pygments warning is fatal under Readthedocs `-W` and breaks the docs build. This source-read hook injects `pygments_lexer = "ipython3"` into notebooks matching configurable glob patterns (`ipython3_lexer_patterns` / `_exclude_patterns`). Globs cover the in-tree example layouts plus `_collections/**/*.ipynb` for notebooks fetched at build time by `sphinx_collections`. Review feedback from the previous round on ray-project#59984 (early-return pattern, no broad `except`) is preserved. ## Related issues Related to ray-project#59984 (closed-stale 2026-02-07). ## Additional information Verified locally with `make html` against `upstream/master` plus this commit and a fixture notebook at `_collections/lexer-fixture/test.ipynb` containing a `!pip install` cell and no `language_info`: build succeeded, no `WARNING.*lexer` matches in the log, fixture confirmed source-read. Today most `_collections/` notebooks are excluded from the build via `exclude_patterns` in favour of their `.md` counterparts, so the new `_collections/**/*.ipynb` glob is mostly defensive. The exclude design is upstream of this PR. --------- Signed-off-by: Aydin Abiar <aydin@anyscale.com> Signed-off-by: Neelansh Khare <kharen@uci.edu>
1 parent b215adc commit 3872f74

1 file changed

Lines changed: 49 additions & 0 deletions

File tree

doc/source/conf.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from jinja2.filters import FILTERS
1818
from sphinx.ext.autosummary import generate
1919
from sphinx.util.inspect import safe_getattr
20+
from sphinx.util.matching import compile_matchers
2021

2122
DEFAULT_API_GROUP = "Others"
2223

@@ -783,6 +784,11 @@ def fix_collections_code_blocks(app, docname, source):
783784

784785
app.connect('source-read', fix_collections_code_blocks)
785786

787+
app.add_config_value("ipython3_lexer_patterns", [], "env")
788+
app.add_config_value("ipython3_lexer_exclude_patterns", [], "env")
789+
app.connect("config-inited", _compile_pattern_matchers)
790+
app.connect("source-read", apply_ipython3_lexer)
791+
786792

787793
redoc = [
788794
{
@@ -921,3 +927,46 @@ def fix_collections_code_blocks(app, docname, source):
921927
), "If ray is already imported, we will not render documentation correctly!"
922928

923929
os.environ["RAY_DOC_BUILD"] = "1"
930+
931+
ipython3_lexer_patterns = [
932+
# External templates fetched by sphinx_collections (see #62179) land here at
933+
# build time; their notebook JSON has no language_info, so Sphinx defaults
934+
# to the python3 lexer and chokes on !pip / %magic cells.
935+
"_collections/**/*.ipynb",
936+
"ray-overview/examples/**/content/**.ipynb",
937+
"serve/tutorials/**/content/**.ipynb",
938+
"data/examples/**/content/**.ipynb",
939+
"tune/examples/**/content/**.ipynb",
940+
]
941+
ipython3_lexer_exclude_patterns = []
942+
943+
944+
def _compile_pattern_matchers(app, config):
945+
app.ipython3_lexer_patterns = compile_matchers(
946+
config.ipython3_lexer_patterns or []
947+
)
948+
app.ipython3_lexer_exclude_patterns = compile_matchers(
949+
config.ipython3_lexer_exclude_patterns or []
950+
)
951+
952+
953+
def apply_ipython3_lexer(app, docname, source):
954+
"""Force the ipython3 pygments lexer on notebooks matching
955+
``ipython3_lexer_patterns`` (minus ``ipython3_lexer_exclude_patterns``).
956+
957+
Sphinx + myst-nb otherwise default to the python3 lexer, which fails on
958+
``!shell`` and ``%magic`` cells and is fatal under Readthedocs ``-W``.
959+
"""
960+
doc_source = app.env.doc2path(docname, base=False)
961+
if not doc_source.endswith(".ipynb"):
962+
return
963+
if any(m(doc_source) for m in app.ipython3_lexer_exclude_patterns):
964+
return
965+
if not any(m(doc_source) for m in app.ipython3_lexer_patterns):
966+
return
967+
968+
notebook = json.loads(source[0])
969+
lang_info = notebook.setdefault("metadata", {}).setdefault("language_info", {})
970+
if lang_info.get("pygments_lexer") != "ipython3":
971+
lang_info["pygments_lexer"] = "ipython3"
972+
source[0] = json.dumps(notebook, ensure_ascii=False)

0 commit comments

Comments
 (0)