sphinx
Sphinx is the documentation generator that Python's own docs are built with. You keep a source directory of reStructuredText files plus a conf.py, run sphinx-build, and get HTML, PDF via LaTeX, EPUB, man pages, or plain text out the other side. What separates it from a static site generator is the semantic layer: it knows what a Python class, a C function, or a glossary term is, builds indexes and cross-references from that knowledge, and with intersphinx will resolve a reference to a symbol in another project's docs into a working link. The bundled autodoc extension imports your package and pulls docstrings straight out of the live objects, napoleon lets those docstrings be written in Google or NumPy style, and doctest runs the examples in them as tests. Everything else is extensions, of which there are hundreds, including MyST for writing in Markdown instead.
For a Python library that needs a real API reference, versioned cross-project links, and more than one output format, Sphinx is still the only complete answer and it is actively maintained. For a product docs site written by humans in Markdown, MkDocs with Material will get you further in an afternoon than Sphinx will in a week.
Use it if
- You are documenting a Python library and want the API reference generated from real docstrings, with autodoc importing the package and napoleon parsing Google or NumPy style sections
- You need cross-project linking: intersphinx turns a bare reference to a symbol in the standard library, NumPy, or Django into a real hyperlink, and no Markdown-first tool does this properly
- Your output is not just a website: the same source has to produce a PDF for a release, man pages for a CLI, or an EPUB, and Sphinx has builders for all of them
- You want documentation checked in CI: sphinx-build -W turns warnings into failures, nitpicky mode flags every unresolved reference, the doctest builder runs your examples, and the linkcheck builder finds dead URLs
- Your team writes Markdown and will not learn reStructuredText. The native markup is rST, and Markdown support means adding myst-parser and then learning its directive syntax anyway. MkDocs with the Material theme is a far shorter path to a good-looking docs site
- You only want an API reference for a small package: pdoc reads your module and emits HTML in one command with no conf.py, no toctree, and no build directory
- You cannot tolerate an import at build time: autodoc imports your package to read docstrings, so anything with heavy dependencies, C extensions, or import-time side effects fails on the docs builder until you list every offender in autodoc_mock_imports
- You are on Python 3.11 or older: Sphinx 9.1 dropped 3.11 and requires 3.12 or newer, and Docutils is pinned to a narrow range (0.21 up to but excluding 0.23), which regularly collides with other pinned packages in the same environment
- You depend on autodoc internals from your own extension: autodoc was substantially rewritten in Sphinx 9.0 and the release notes admit edge-case breakage, with autodoc_use_legacy_class_based provided as a temporary way back to the old implementation
- You want fast feedback: a full build of a large project takes minutes, incremental builds get confused often enough that make clean is a reflex, and live reload needs the third-party sphinx-autobuild package
Setup reality
pip install sphinx pulls around a dozen dependencies including Jinja2, Pygments, Docutils, Babel, requests, and six separate sphinxcontrib-* helper packages, and needs Python 3.12 or newer as of 9.1. sphinx-quickstart asks a handful of questions and writes conf.py, index.rst, and a Makefile, after which every feature you actually want is an extension you add to the extensions list by hand. The first real fight is autodoc: it imports your package, so the package must be installed in the environment doing the build (pip install -e . or the docs will not find it), and sys.path juggling in conf.py is the classic workaround people copy without understanding. The second is the toctree: a file that is not listed in some toctree is built but unreachable and Sphinx warns about it, while a file listed twice warns about that too. The default alabaster theme looks like 2011, so most projects install furo, sphinx-rtd-theme, or pydata-sphinx-theme immediately. PDF output needs a full LaTeX toolchain installed on the machine, which on CI means apt-get installing several hundred megabytes of TeX Live. Warnings are informative but numerous, so adopt -W early or you will never get to zero.
Patterns
Scaffold and build a docs treestart-a-docs-project
pip install sphinx
sphinx-quickstart docs
# build HTML into docs/_build/html
sphinx-build -M html docs docs/_buildAnswer yes to the separate source and build directories question unless you enjoy gitignoring build output inside your source folder. The -M form takes the build root and appends the builder name; the older -b form takes the exact output directory.
A conf.py that does the useful thingsconfigure-extensions
# docs/conf.py
project = 'acme'
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
html_theme = 'furo'
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
}Extension order in the list rarely matters, but forgetting to also pip install the non-bundled ones (furo here) gives a confusing theme-not-found error. Everything under sphinx.ext.* ships with Sphinx itself.
Generate API pages from docstringsautodoc-a-module
.. automodule:: acme.client
:members:
:undoc-members:
:show-inheritance:
.. autoclass:: acme.client.Client
:members: connect, closeautodoc imports the module, so the package must be installed in the build environment. Without :members: you get only the module docstring and nothing else, which is the most common autodoc bug report.
Build docs without installing every dependencymock-heavy-imports
# docs/conf.py
autodoc_mock_imports = ['torch', 'cv2', 'pyodbc']Each mocked name stands in for the real import so autodoc can still read your docstrings. Type annotations that reference mocked classes render as the mock path, which looks wrong in the output; mock as little as you can get away with.
Write docstrings that are readable as sourcegoogle-style-docstrings
# conf.py
extensions = ['sphinx.ext.napoleon']
napoleon_google_docstring = True
napoleon_numpy_docstring = False
# in your code
def fetch(url: str, timeout: float = 5.0) -> bytes:
"""Download a URL.
Args:
url: Absolute URL to fetch.
timeout: Seconds before giving up.
Returns:
The raw response body.
Raises:
TimeoutError: If the server did not answer in time.
"""Without napoleon you would be writing :param url: style fields, which nobody enjoys reading in a terminal. napoleon rewrites these sections into rST before the parser sees them, so error messages sometimes point at line numbers that do not match your file.
Generate one page per module automaticallyautosummary-stub-pages
# conf.py
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary']
autosummary_generate = True
# index.rst
.. autosummary::
:toctree: api
:recursive:
acmeThis is how you avoid hand-writing an automodule file per module. The generated stubs land in the toctree directory you name and are regenerated on each build, so add that directory to .gitignore.
Wire pages into the navigation treetoctree-navigation
.. toctree::
:maxdepth: 2
:caption: Guides
install
quickstart
api/indexEntries are document paths without the .rst extension, relative to the file containing the toctree. Any document not reachable from a toctree gets a "document isn't included in any toctree" warning and no navigation links.
Write pages in Markdown instead of rSTmarkdown-with-myst
pip install myst-parser
# conf.py
extensions = ['myst_parser']
myst_enable_extensions = ['colon_fence', 'deflist']MyST accepts .md files alongside .rst, but Sphinx directives still have to be written in MyST's fenced syntax, so this removes the rST prose syntax and not the directive learning curve. Sphinx 9.1 shipped a specific compatibility fix for MyST, so keep both current.
Link to symbols in other projectsintersphinx-cross-links
# conf.py
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'numpy': ('https://numpy.org/doc/stable/', None),
}
# in a document
Use :class:`pathlib.Path` and :func:`numpy.linspace`.The None means fetch objects.inv from that URL at build time, so builds need network access; point it at a local inventory file for offline or air-gapped builds. Sphinx 9.0 added a file-based cache for remote inventories, which cuts repeat build time.
Make documentation problems break CIfail-build-on-warnings
sphinx-build -W --nitpicky -b html docs docs/_build/html
# conf.py, for the unavoidable exceptions
nitpick_ignore = [('py:class', 'collections.abc.Buffer')]
suppress_warnings = ['epub.unknown_project_files']-W turns warnings into errors and --nitpicky adds a warning for every cross-reference that failed to resolve, which on a mature project means a long first cleanup. nitpick_ignore is for third-party types that have no inventory entry.
Test the examples in your docsrun-doctests
.. doctest::
>>> from acme import add
>>> add(2, 2)
4
# then
sphinx-build -b doctest docs docs/_build/doctestThe doctest builder is bundled as sphinx.ext.doctest and runs in its own build pass, so wire it into CI separately from the HTML build. Sphinx 9.0 added doctest_fail_fast if you want it to stop at the first failure.
Find broken external linkscheck-dead-links
sphinx-build -b linkcheck docs docs/_build/linkcheck
# conf.py
linkcheck_ignore = [r'http://localhost:\d+/']
linkcheck_allowed_redirects = {r'https://github\.com/.*': r'https://github\.com/.*'}Run this on a schedule rather than on every commit; it makes a real HTTP request per link and rate-limited hosts will produce failures that have nothing to do with your change.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| mkdocs-material | PyPI | Your docs are prose in Markdown and you want a polished searchable site today rather than a cross-reference engine |
| pdoc | PyPI | You only need an API reference from docstrings and want zero configuration files |
| myst-parser | PyPI | You are staying on Sphinx for its cross-references but want the source files written in Markdown |
| mkdocstrings | PyPI | You picked MkDocs and still need API pages generated from Python docstrings |