Reputation: 241
When building html documentation, how do you force sphinx to report, or create an error, on links that don't exist?
Specifically, I have properties and methods within my Python project that have been removed or renamed, and it is hard to find all the dead links with the sphinx generated html output.
I feel like I'm staring at the answer here: http://sphinx-doc.org/glossary.html, as descriped in the opening paragraph.
I'm obviously not understanding something.
Upvotes: 17
Views: 4204
Reputation: 1
I wanted to write a test that there are no broken links, but faced several difficulties:
sphinx build
in a subprocess doesn't work if you don't want to make actual http requests in tests -> use python API with pytest.mark.vcr
capfd
or similar -> use the warning
keyword to Sphinx
constructor:any:
seems to throw warnings, but I prefer default_role = "py:obj"
-> use nitpicky
mode. Luckily, its easy to override the conf.py
if you don't want to set nitpicky there.mypy
and the type-hints don't seem to resolve well for sphinx
, leading to a ton of warnings -> set autodoc_typehints = "none"
app.build
generates a ton of stdout and logging (possibly by extensions) -> set status=StringIO()
and use caplog
See below:
import logging
import shutil
import subprocess
from io import StringIO
from pathlib import Path
import pytest
from sphinx.application import Sphinx
#: Log messages from sphinx that should fail the test
MESSAGES_TO_AVOID = {
"ERROR:",
"more than one target found",
"start-string without end-string",
"undefined label",
"reference target not found",
}
#: Whitelisted (intersphinx) objects (network access is blocked)
WHITELISTED_REFERENCE_TARGETS = {
"matplotlib",
"numpy",
"pandas",
"python",
}
#: Path to docs directory
DOCS_DIR = Path(__file__).parent.parent / "docs"
#: Path to build directory
OUTPUT_DIR = DOCS_DIR / "_test_build"
def bad_msg(line: str) -> bool:
if "<unknown>" in line or any(
f"reference target not found: {obj}" in line
for obj in WHITELISTED_REFERENCE_TARGETS
):
return False
return any(message in line for message in MESSAGES_TO_AVOID)
@pytest.fixture
def clean(capfd):
shutil.rmtree(OUTPUT_DIR, ignore_errors=True)
subprocess.run(["make", "-C", str(DOCS_DIR), "clean-api"])
capfd.readouterr() # hide stdout output
@pytest.fixture
def warning_io():
return StringIO()
@pytest.fixture
def app(clean, warning_io):
app = Sphinx(
srcdir=str(DOCS_DIR),
confdir=str(DOCS_DIR),
outdir=str(OUTPUT_DIR),
doctreedir=str(OUTPUT_DIR / "doctrees"),
buildername="html",
status=StringIO(), # hide stdout output
warning=warning_io,
)
app.config.nitpicky = True
# type hints and inherited classes depend on external resources and aren't
# necessarily fixable, so set them off so that we don't complain about them
app.config.autodoc_typehints = "none"
# its interesting that the following option must be `pop`ped, setting it to
# `False` is not enough. Also ensure the generated apidocs don't have
# show-inheritance, which is on by default
app.config.autodoc_default_options.pop("show-inheritance")
app.config.autodoc_inherit_docstrings = False
return app
@pytest.mark.block_network
def test_docs(app, warning_io, caplog, capfd):
subprocess.run(["make", "-C", str(DOCS_DIR), "apidocs"])
with caplog.at_level(logging.CRITICAL):
app.build()
capfd.readouterr() # hide stdout output
logs = warning_io.getvalue()
bad_messages = [line for line in logs.split("\n") if bad_msg(line)]
if bad_messages:
bad_logs = "\n".join(bad_messages)
result = f"{len(bad_messages)} failure cases: {bad_logs}"
raise AssertionError(result)
Upvotes: 0
Reputation: 50947
Set the nitpicky configuration variable to True
(you can also use the -n option when running sphinx-build).
In nitpicky mode, a cross-reference to a function (such as :func:`myfunc`
), class, or other object that cannot be found will generate a warning message.
Upvotes: 18
Reputation: 473863
I think CheckExternalLinksBuilder
is what you're looking for.
It's basically used by calling 'sphinx-build' with -b linkcheck
option. Please see sphinx-build
for more info. Also, take a look at the list of sphinx-extensions here and here.
Upvotes: 9