mrkeyoor.com_
Thu 06 Aug 07:41 UTC
PyPIUtilsupdated 06 Aug 2026

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.

Verdict

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.

API stability4/5conf.py options and rST directives stay valid across majors, and deprecations are announced two releases ahead. Extension authors have a rougher time: 9.0 rewrote autodoc, deprecated the .app attributes, and scheduled sphinx.io for removal in 10.
Docs5/5sphinx-doc.org is written with the tool itself and covers a tutorial, every configuration value, each bundled extension, the extension API, and the deprecation timetable. The changelog is unusually detailed about incompatible changes.
Maintenance5/59.1.0 released 31 December 2025, repository pushed 2 August 2026, and 9.1.1 already collecting fixes. The 1259 open issues (1459 counting PRs) reflect a 15-year-old project with a huge surface, not neglect.
Ecosystem5/5Around 20.7M weekly downloads, the default choice on Read the Docs, and hundreds of extensions covering Markdown, notebooks, OpenAPI, diagrams, and API docs for other languages. Most Python projects you already read the docs of use it.

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
Skip it if

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/_build

Answer 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, close

autodoc 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:

   acme

This 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/index

Entries 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/doctest

The 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

PackageRegistryPick it when
mkdocs-materialPyPIYour docs are prose in Markdown and you want a polished searchable site today rather than a cross-reference engine
pdocPyPIYou only need an API reference from docstrings and want zero configuration files
myst-parserPyPIYou are staying on Sphinx for its cross-references but want the source files written in Markdown
mkdocstringsPyPIYou picked MkDocs and still need API pages generated from Python docstrings