From ec1527da10d5ac5235b5f4086c16b8ebd7093a55 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 2 Jul 2026 11:22:55 +0000 Subject: [PATCH 1/3] Normalize emitted asset paths for nested Sphinx pages Rewrite file-like paths in py-repl attributes to page-relative URLs using Sphinx get_target_uri and relative_uri. This fixes REPL asset loading on nested doc pages while preserving flat-layout behavior and compatibility with RTD-style path prefixes. Adds asset_href helpers, unit tests, a nested-page build fixture, and README documentation. Closes #19. Co-authored-by: chrizzftd --- README.md | 2 + sphinx_pyrepl_web/__init__.py | 60 ++++++++++++++++--- tests/test_asset_href.py | 97 +++++++++++++++++++++++++++++++ tests/test_nested_static_paths.py | 77 ++++++++++++++++++++++++ 4 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 tests/test_asset_href.py create mode 100644 tests/test_nested_static_paths.py diff --git a/README.md b/README.md index c9353e7..4c6d69c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ All options drive [pyrepl-web](https://github.com/chrizzFTD/pyrepl-web)'s attrib Python code within the `.. py-repl::` directive is written to `_static/pyrepl/` at build time and emitted as `replay-src`. +File paths in `:packages:`, `:src:`, `replay-src`, and `pyrepl_autodoc_packages` are rewritten to page-relative URLs at build time so REPLs work on nested pages (for example `docs/api/...`). PyPI package names, absolute URLs, and paths you write as root-absolute (`/_static/...`) are left unchanged. + Optional Sphinx config: ```python diff --git a/sphinx_pyrepl_web/__init__.py b/sphinx_pyrepl_web/__init__.py index 881a4d7..6b188ed 100644 --- a/sphinx_pyrepl_web/__init__.py +++ b/sphinx_pyrepl_web/__init__.py @@ -13,6 +13,7 @@ from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.fileutil import copy_asset_file +from sphinx.util.osutil import relative_uri PYREPL_DIR = Path(__file__).parent / "pyrepl" STARTUP_FILES_KEY = "pyrepl-startup-files" @@ -20,6 +21,33 @@ BOOTSTRAP_FILES_KEY = "pyrepl-bootstrap-files" _DOCTEST_PARSER = DocTestParser() logger = logging.getLogger(__name__) +_ABSOLUTE_PATH_PREFIXES = ("/", "http://", "https://", "emfs:") + + +def _is_file_like_path(path: str) -> bool: + """Return True if *path* should be rewritten as a page-relative asset URL.""" + if path.startswith(_ABSOLUTE_PATH_PREFIXES): + return False + if " @ " in path: + return False + return "/" in path or path.endswith((".whl", ".py")) + + +def asset_href(app: Sphinx, docname: str, path: str) -> str: + """Rewrite a file path for the HTML page that will emit it.""" + if not _is_file_like_path(path): + return path + builder = app.builder + if builder.format != "html": + return path + return relative_uri(builder.get_target_uri(docname), path) + + +def asset_href_packages(app: Sphinx, docname: str, packages: str) -> str: + """Rewrite comma-separated package entries that refer to local files.""" + return ", ".join( + asset_href(app, docname, part.strip()) for part in packages.split(",") + ) def setup(app: Sphinx): @@ -105,16 +133,24 @@ def autodoc_bootstrap_source( def make_pyrepl_raw( + app: Sphinx, + docname: str, replay_src: str, packages: str | None = None, src: str | None = None, ) -> nodes.raw: """Build a raw HTML node for an autodoc doctest replay widget.""" - attrs = ["no-header", "no-banner", f'replay-src="{replay_src}"'] + attrs = [ + "no-header", + "no-banner", + f'replay-src="{asset_href(app, docname, replay_src)}"', + ] if packages: - attrs.insert(0, f'packages="{packages}"') + attrs.insert( + 0, f'packages="{asset_href_packages(app, docname, packages)}"' + ) if src: - attrs.insert(0, f'src="{src}"') + attrs.insert(0, f'src="{asset_href(app, docname, src)}"') attr_str = " ".join(attrs) return nodes.raw("", f"\n", format="html") @@ -173,7 +209,9 @@ def transform_doctest_blocks(app: Sphinx, doctree: nodes.document): env, docname, bootstrap_text, replay_name ) node.replace_self( - make_pyrepl_raw(replay_src, src=bootstrap_src, packages=packages) + make_pyrepl_raw( + app, docname, replay_src, src=bootstrap_src, packages=packages + ) ) replaced = True @@ -201,6 +239,8 @@ class PyRepl(SphinxDirective): def run(self): env = self.env + app = env.app + docname = env.docname attrs: list[str] = [] for option, attr in ( @@ -210,6 +250,8 @@ def run(self): ): if option in self.options: value = self.options[option] + if option == "packages": + value = asset_href_packages(app, docname, value) attrs.append(f'{attr}="{value}"') for flag in ("no-header", "no-buttons", "readonly", "no-banner"): @@ -228,7 +270,11 @@ def run(self): except OSError as exc: raise self.error(f"Could not read file: {exc}") from exc self.env.note_dependency(path) - rel_src = path.relative_to(Path(self.env.srcdir)).as_posix() + rel_src = asset_href( + app, + docname, + path.relative_to(Path(self.env.srcdir)).as_posix(), + ) startup_files = json.loads( self.env.metadata[self.env.docname].setdefault( STARTUP_FILES_KEY, "[]" @@ -247,8 +293,8 @@ def run(self): if has_body: body_text = doctest_to_replay_source(list(self.content)) - replay_src, _ = register_autodoc_repl(env, env.docname, body_text) - attrs.append(f'replay-src="{replay_src}"') + replay_src, _ = register_autodoc_repl(env, docname, body_text) + attrs.append(f'replay-src="{asset_href(app, docname, replay_src)}"') self.env.metadata[self.env.docname]["pyrepl"] = True attr_str = (" " + " ".join(attrs)) if attrs else "" diff --git a/tests/test_asset_href.py b/tests/test_asset_href.py new file mode 100644 index 0000000..cd6940f --- /dev/null +++ b/tests/test_asset_href.py @@ -0,0 +1,97 @@ +import shutil +import sys +from pathlib import Path + +import pytest +from sphinx.application import Sphinx +from unittest.mock import MagicMock + +from sphinx_pyrepl_web import asset_href, asset_href_packages + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) + + +def _build_sphinx(srcdir: Path, outdir: Path, doctreedir: Path) -> Sphinx: + outdir.mkdir(parents=True, exist_ok=True) + doctreedir.mkdir(parents=True, exist_ok=True) + with open(outdir / "warnings.txt", "w", encoding="utf-8") as warning_file: + app = Sphinx( + srcdir=str(srcdir), + confdir=str(srcdir), + outdir=str(outdir), + doctreedir=str(doctreedir), + buildername="html", + warning=warning_file, + freshenv=True, + ) + app.build() + return app + + +@pytest.fixture +def html_app(tmp_path): + srcdir = tmp_path / "docs" + srcdir.mkdir() + (srcdir / "conf.py").write_text("", encoding="utf-8") + (srcdir / "index.rst").write_text("Test\n====\n", encoding="utf-8") + (srcdir / "api").mkdir() + (srcdir / "api" / "module.rst").write_text("API\n===\n", encoding="utf-8") + outdir = tmp_path / "_build" + doctreedir = tmp_path / "_doctree" + return _build_sphinx(srcdir, outdir, doctreedir) + + +def test_asset_href_rewrites_static_path_on_nested_page(html_app): + assert ( + asset_href(html_app, "api/module", "_static/wheels/foo.whl") + == "../_static/wheels/foo.whl" + ) + + +def test_asset_href_leaves_root_page_static_path_unchanged(html_app): + assert ( + asset_href(html_app, "index", "_static/wheels/foo.whl") + == "_static/wheels/foo.whl" + ) + + +def test_asset_href_leaves_root_absolute_path_unchanged(html_app): + assert ( + asset_href(html_app, "api/module", "/_static/wheels/foo.whl") + == "/_static/wheels/foo.whl" + ) + + +def test_asset_href_leaves_https_url_unchanged(html_app): + url = "https://cdn.example/w.whl" + assert asset_href(html_app, "api/module", url) == url + + +def test_asset_href_leaves_pypi_name_unchanged(html_app): + assert asset_href(html_app, "api/module", "numpy") == "numpy" + + +def test_asset_href_rewrites_src_relative_to_source_root(html_app): + assert asset_href(html_app, "api/module", "demo.py") == "../demo.py" + + +def test_asset_href_leaves_micropip_spec_unchanged(html_app): + spec = "mypkg @ https://example.com/wheels/mypkg.whl" + assert asset_href(html_app, "api/module", spec) == spec + + +def test_asset_href_packages_rewrites_only_file_like_entries(html_app): + packages = "numpy, _static/wheels/foo.whl" + assert asset_href_packages(html_app, "api/module", packages) == ( + "numpy, ../_static/wheels/foo.whl" + ) + + +def test_asset_href_skips_normalization_for_non_html_builder(): + app = MagicMock() + app.builder.format = "" + assert ( + asset_href(app, "api/module", "_static/wheels/foo.whl") + == "_static/wheels/foo.whl" + ) diff --git a/tests/test_nested_static_paths.py b/tests/test_nested_static_paths.py new file mode 100644 index 0000000..2acc8cc --- /dev/null +++ b/tests/test_nested_static_paths.py @@ -0,0 +1,77 @@ +import shutil +import sys +from pathlib import Path + +from sphinx.application import Sphinx + +ROOT = Path(__file__).resolve().parents[1] +FIXTURES = Path(__file__).resolve().parent / "fixtures" +WHEEL_NAME = "pyrepl_test_pkg-1.0.0-py3-none-any.whl" +WHEEL_PATH = f"_static/wheels/{WHEEL_NAME}" + +sys.path.insert(0, str(ROOT)) + + +def _build_sphinx(srcdir: Path, outdir: Path, doctreedir: Path) -> Sphinx: + outdir.mkdir(parents=True, exist_ok=True) + doctreedir.mkdir(parents=True, exist_ok=True) + with open(outdir / "warnings.txt", "w", encoding="utf-8") as warning_file: + app = Sphinx( + srcdir=str(srcdir), + confdir=str(srcdir), + outdir=str(outdir), + doctreedir=str(doctreedir), + buildername="html", + warning=warning_file, + freshenv=True, + ) + app.build() + return app + + +def test_nested_page_emits_page_relative_static_paths(tmp_path): + srcdir = tmp_path / "docs" + wheels_dir = srcdir / "_static" / "wheels" + wheels_dir.mkdir(parents=True) + shutil.copy2(FIXTURES / "wheels" / WHEEL_NAME, wheels_dir / WHEEL_NAME) + (srcdir / "api").mkdir() + outdir = tmp_path / "_build" + doctreedir = tmp_path / "_doctree" + + (srcdir / "conf.py").write_text( + """ +extensions = ["sphinx_pyrepl_web"] +master_doc = "index" +pyrepl_js = "pyrepl.js" +html_static_path = ["_static"] +""", + encoding="utf-8", + ) + (srcdir / "index.rst").write_text("Home\n====\n", encoding="utf-8") + (srcdir / "api" / "index.rst").write_text( + f""" +API +=== + +.. py-repl:: + :packages: {WHEEL_PATH} + :no-header: + + >>> 1 + 1 +""", + encoding="utf-8", + ) + + app = _build_sphinx(srcdir, outdir, doctreedir) + + html = (outdir / "api" / "index.html").read_text(encoding="utf-8") + assert 'packages="../_static/wheels/' in html + assert 'packages="api/_static/' not in html + assert 'packages="/_static/' not in html + assert 'replay-src="../_static/pyrepl/api-index-1.py"' in html + + root_html = (outdir / "index.html").read_text(encoding="utf-8") + assert "py-repl" not in root_html + + replay_files = app.env.metadata["api/index"].get("pyrepl-replay-files") + assert replay_files is not None From 5f8d1f094019347e02e43eb6a57581b6ee7fc040 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 2 Jul 2026 11:30:35 +0000 Subject: [PATCH 2/3] Fix CI: avoid deprecated env.app access under PYTHONWARNINGS=error Use app.builder in transforms and env._app.builder in PyRepl.run(), matching Sphinx internals. asset_href now takes a Builder directly. Co-authored-by: chrizzftd --- sphinx_pyrepl_web/__init__.py | 26 ++++++++++---------- tests/test_asset_href.py | 45 ++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/sphinx_pyrepl_web/__init__.py b/sphinx_pyrepl_web/__init__.py index 6b188ed..2f50f60 100644 --- a/sphinx_pyrepl_web/__init__.py +++ b/sphinx_pyrepl_web/__init__.py @@ -10,6 +10,7 @@ from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.application import Sphinx +from sphinx.builders import Builder from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.fileutil import copy_asset_file @@ -33,20 +34,19 @@ def _is_file_like_path(path: str) -> bool: return "/" in path or path.endswith((".whl", ".py")) -def asset_href(app: Sphinx, docname: str, path: str) -> str: +def asset_href(builder: Builder, docname: str, path: str) -> str: """Rewrite a file path for the HTML page that will emit it.""" if not _is_file_like_path(path): return path - builder = app.builder if builder.format != "html": return path return relative_uri(builder.get_target_uri(docname), path) -def asset_href_packages(app: Sphinx, docname: str, packages: str) -> str: +def asset_href_packages(builder: Builder, docname: str, packages: str) -> str: """Rewrite comma-separated package entries that refer to local files.""" return ", ".join( - asset_href(app, docname, part.strip()) for part in packages.split(",") + asset_href(builder, docname, part.strip()) for part in packages.split(",") ) @@ -133,7 +133,7 @@ def autodoc_bootstrap_source( def make_pyrepl_raw( - app: Sphinx, + builder: Builder, docname: str, replay_src: str, packages: str | None = None, @@ -143,14 +143,14 @@ def make_pyrepl_raw( attrs = [ "no-header", "no-banner", - f'replay-src="{asset_href(app, docname, replay_src)}"', + f'replay-src="{asset_href(builder, docname, replay_src)}"', ] if packages: attrs.insert( - 0, f'packages="{asset_href_packages(app, docname, packages)}"' + 0, f'packages="{asset_href_packages(builder, docname, packages)}"' ) if src: - attrs.insert(0, f'src="{asset_href(app, docname, src)}"') + attrs.insert(0, f'src="{asset_href(builder, docname, src)}"') attr_str = " ".join(attrs) return nodes.raw("", f"\n", format="html") @@ -210,7 +210,7 @@ def transform_doctest_blocks(app: Sphinx, doctree: nodes.document): ) node.replace_self( make_pyrepl_raw( - app, docname, replay_src, src=bootstrap_src, packages=packages + app.builder, docname, replay_src, src=bootstrap_src, packages=packages ) ) replaced = True @@ -239,7 +239,7 @@ class PyRepl(SphinxDirective): def run(self): env = self.env - app = env.app + builder = env._app.builder docname = env.docname attrs: list[str] = [] @@ -251,7 +251,7 @@ def run(self): if option in self.options: value = self.options[option] if option == "packages": - value = asset_href_packages(app, docname, value) + value = asset_href_packages(builder, docname, value) attrs.append(f'{attr}="{value}"') for flag in ("no-header", "no-buttons", "readonly", "no-banner"): @@ -271,7 +271,7 @@ def run(self): raise self.error(f"Could not read file: {exc}") from exc self.env.note_dependency(path) rel_src = asset_href( - app, + builder, docname, path.relative_to(Path(self.env.srcdir)).as_posix(), ) @@ -294,7 +294,7 @@ def run(self): if has_body: body_text = doctest_to_replay_source(list(self.content)) replay_src, _ = register_autodoc_repl(env, docname, body_text) - attrs.append(f'replay-src="{asset_href(app, docname, replay_src)}"') + attrs.append(f'replay-src="{asset_href(builder, docname, replay_src)}"') self.env.metadata[self.env.docname]["pyrepl"] = True attr_str = (" " + " ".join(attrs)) if attrs else "" diff --git a/tests/test_asset_href.py b/tests/test_asset_href.py index cd6940f..58a213a 100644 --- a/tests/test_asset_href.py +++ b/tests/test_asset_href.py @@ -1,10 +1,10 @@ import shutil import sys from pathlib import Path +from unittest.mock import MagicMock import pytest from sphinx.application import Sphinx -from unittest.mock import MagicMock from sphinx_pyrepl_web import asset_href, asset_href_packages @@ -30,7 +30,7 @@ def _build_sphinx(srcdir: Path, outdir: Path, doctreedir: Path) -> Sphinx: @pytest.fixture -def html_app(tmp_path): +def html_builder(tmp_path): srcdir = tmp_path / "docs" srcdir.mkdir() (srcdir / "conf.py").write_text("", encoding="utf-8") @@ -39,59 +39,60 @@ def html_app(tmp_path): (srcdir / "api" / "module.rst").write_text("API\n===\n", encoding="utf-8") outdir = tmp_path / "_build" doctreedir = tmp_path / "_doctree" - return _build_sphinx(srcdir, outdir, doctreedir) + app = _build_sphinx(srcdir, outdir, doctreedir) + return app.builder -def test_asset_href_rewrites_static_path_on_nested_page(html_app): +def test_asset_href_rewrites_static_path_on_nested_page(html_builder): assert ( - asset_href(html_app, "api/module", "_static/wheels/foo.whl") + asset_href(html_builder, "api/module", "_static/wheels/foo.whl") == "../_static/wheels/foo.whl" ) -def test_asset_href_leaves_root_page_static_path_unchanged(html_app): +def test_asset_href_leaves_root_page_static_path_unchanged(html_builder): assert ( - asset_href(html_app, "index", "_static/wheels/foo.whl") + asset_href(html_builder, "index", "_static/wheels/foo.whl") == "_static/wheels/foo.whl" ) -def test_asset_href_leaves_root_absolute_path_unchanged(html_app): +def test_asset_href_leaves_root_absolute_path_unchanged(html_builder): assert ( - asset_href(html_app, "api/module", "/_static/wheels/foo.whl") + asset_href(html_builder, "api/module", "/_static/wheels/foo.whl") == "/_static/wheels/foo.whl" ) -def test_asset_href_leaves_https_url_unchanged(html_app): +def test_asset_href_leaves_https_url_unchanged(html_builder): url = "https://cdn.example/w.whl" - assert asset_href(html_app, "api/module", url) == url + assert asset_href(html_builder, "api/module", url) == url -def test_asset_href_leaves_pypi_name_unchanged(html_app): - assert asset_href(html_app, "api/module", "numpy") == "numpy" +def test_asset_href_leaves_pypi_name_unchanged(html_builder): + assert asset_href(html_builder, "api/module", "numpy") == "numpy" -def test_asset_href_rewrites_src_relative_to_source_root(html_app): - assert asset_href(html_app, "api/module", "demo.py") == "../demo.py" +def test_asset_href_rewrites_src_relative_to_source_root(html_builder): + assert asset_href(html_builder, "api/module", "demo.py") == "../demo.py" -def test_asset_href_leaves_micropip_spec_unchanged(html_app): +def test_asset_href_leaves_micropip_spec_unchanged(html_builder): spec = "mypkg @ https://example.com/wheels/mypkg.whl" - assert asset_href(html_app, "api/module", spec) == spec + assert asset_href(html_builder, "api/module", spec) == spec -def test_asset_href_packages_rewrites_only_file_like_entries(html_app): +def test_asset_href_packages_rewrites_only_file_like_entries(html_builder): packages = "numpy, _static/wheels/foo.whl" - assert asset_href_packages(html_app, "api/module", packages) == ( + assert asset_href_packages(html_builder, "api/module", packages) == ( "numpy, ../_static/wheels/foo.whl" ) def test_asset_href_skips_normalization_for_non_html_builder(): - app = MagicMock() - app.builder.format = "" + builder = MagicMock() + builder.format = "" assert ( - asset_href(app, "api/module", "_static/wheels/foo.whl") + asset_href(builder, "api/module", "_static/wheels/foo.whl") == "_static/wheels/foo.whl" ) From fa4eb007103d91aa165a787ab8ee1ec04fde8420 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 2 Jul 2026 11:50:24 +0000 Subject: [PATCH 3/3] Make asset href helpers private module API Rename asset_href and asset_href_packages to _asset_href and _asset_href_packages so they are not mistaken for public extension API. Co-authored-by: chrizzftd --- sphinx_pyrepl_web/__init__.py | 18 +++++++++--------- tests/test_asset_href.py | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/sphinx_pyrepl_web/__init__.py b/sphinx_pyrepl_web/__init__.py index 2f50f60..098f020 100644 --- a/sphinx_pyrepl_web/__init__.py +++ b/sphinx_pyrepl_web/__init__.py @@ -34,7 +34,7 @@ def _is_file_like_path(path: str) -> bool: return "/" in path or path.endswith((".whl", ".py")) -def asset_href(builder: Builder, docname: str, path: str) -> str: +def _asset_href(builder: Builder, docname: str, path: str) -> str: """Rewrite a file path for the HTML page that will emit it.""" if not _is_file_like_path(path): return path @@ -43,10 +43,10 @@ def asset_href(builder: Builder, docname: str, path: str) -> str: return relative_uri(builder.get_target_uri(docname), path) -def asset_href_packages(builder: Builder, docname: str, packages: str) -> str: +def _asset_href_packages(builder: Builder, docname: str, packages: str) -> str: """Rewrite comma-separated package entries that refer to local files.""" return ", ".join( - asset_href(builder, docname, part.strip()) for part in packages.split(",") + _asset_href(builder, docname, part.strip()) for part in packages.split(",") ) @@ -143,14 +143,14 @@ def make_pyrepl_raw( attrs = [ "no-header", "no-banner", - f'replay-src="{asset_href(builder, docname, replay_src)}"', + f'replay-src="{_asset_href(builder, docname, replay_src)}"', ] if packages: attrs.insert( - 0, f'packages="{asset_href_packages(builder, docname, packages)}"' + 0, f'packages="{_asset_href_packages(builder, docname, packages)}"' ) if src: - attrs.insert(0, f'src="{asset_href(builder, docname, src)}"') + attrs.insert(0, f'src="{_asset_href(builder, docname, src)}"') attr_str = " ".join(attrs) return nodes.raw("", f"\n", format="html") @@ -251,7 +251,7 @@ def run(self): if option in self.options: value = self.options[option] if option == "packages": - value = asset_href_packages(builder, docname, value) + value = _asset_href_packages(builder, docname, value) attrs.append(f'{attr}="{value}"') for flag in ("no-header", "no-buttons", "readonly", "no-banner"): @@ -270,7 +270,7 @@ def run(self): except OSError as exc: raise self.error(f"Could not read file: {exc}") from exc self.env.note_dependency(path) - rel_src = asset_href( + rel_src = _asset_href( builder, docname, path.relative_to(Path(self.env.srcdir)).as_posix(), @@ -294,7 +294,7 @@ def run(self): if has_body: body_text = doctest_to_replay_source(list(self.content)) replay_src, _ = register_autodoc_repl(env, docname, body_text) - attrs.append(f'replay-src="{asset_href(builder, docname, replay_src)}"') + attrs.append(f'replay-src="{_asset_href(builder, docname, replay_src)}"') self.env.metadata[self.env.docname]["pyrepl"] = True attr_str = (" " + " ".join(attrs)) if attrs else "" diff --git a/tests/test_asset_href.py b/tests/test_asset_href.py index 58a213a..fca2fb0 100644 --- a/tests/test_asset_href.py +++ b/tests/test_asset_href.py @@ -6,7 +6,7 @@ import pytest from sphinx.application import Sphinx -from sphinx_pyrepl_web import asset_href, asset_href_packages +from sphinx_pyrepl_web import _asset_href, _asset_href_packages ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) @@ -45,46 +45,46 @@ def html_builder(tmp_path): def test_asset_href_rewrites_static_path_on_nested_page(html_builder): assert ( - asset_href(html_builder, "api/module", "_static/wheels/foo.whl") + _asset_href(html_builder, "api/module", "_static/wheels/foo.whl") == "../_static/wheels/foo.whl" ) def test_asset_href_leaves_root_page_static_path_unchanged(html_builder): assert ( - asset_href(html_builder, "index", "_static/wheels/foo.whl") + _asset_href(html_builder, "index", "_static/wheels/foo.whl") == "_static/wheels/foo.whl" ) def test_asset_href_leaves_root_absolute_path_unchanged(html_builder): assert ( - asset_href(html_builder, "api/module", "/_static/wheels/foo.whl") + _asset_href(html_builder, "api/module", "/_static/wheels/foo.whl") == "/_static/wheels/foo.whl" ) def test_asset_href_leaves_https_url_unchanged(html_builder): url = "https://cdn.example/w.whl" - assert asset_href(html_builder, "api/module", url) == url + assert _asset_href(html_builder, "api/module", url) == url def test_asset_href_leaves_pypi_name_unchanged(html_builder): - assert asset_href(html_builder, "api/module", "numpy") == "numpy" + assert _asset_href(html_builder, "api/module", "numpy") == "numpy" def test_asset_href_rewrites_src_relative_to_source_root(html_builder): - assert asset_href(html_builder, "api/module", "demo.py") == "../demo.py" + assert _asset_href(html_builder, "api/module", "demo.py") == "../demo.py" def test_asset_href_leaves_micropip_spec_unchanged(html_builder): spec = "mypkg @ https://example.com/wheels/mypkg.whl" - assert asset_href(html_builder, "api/module", spec) == spec + assert _asset_href(html_builder, "api/module", spec) == spec def test_asset_href_packages_rewrites_only_file_like_entries(html_builder): packages = "numpy, _static/wheels/foo.whl" - assert asset_href_packages(html_builder, "api/module", packages) == ( + assert _asset_href_packages(html_builder, "api/module", packages) == ( "numpy, ../_static/wheels/foo.whl" ) @@ -93,6 +93,6 @@ def test_asset_href_skips_normalization_for_non_html_builder(): builder = MagicMock() builder.format = "" assert ( - asset_href(builder, "api/module", "_static/wheels/foo.whl") + _asset_href(builder, "api/module", "_static/wheels/foo.whl") == "_static/wheels/foo.whl" )