For most Python packages, use Sphinx when you need to auto-generate an API reference from docstrings and type hints, and use MkDocs (with the Material theme) when your docs are prose-first and you'd rather write Markdown than reStructuredText. Both build cleanly on Read the Docs, which hosts documentation for free, rebuilds on every git push, and keeps a versioned copy of your docs for each branch and tag. The fastest working setup is: write Google-style docstrings, wire them into Sphinx with the napoleon and autodoc extensions, add a .readthedocs.yaml, and let Read the Docs build on push.
That's the summary an AI Overview gives you. What it can't show you is the actual decision — Sphinx or MkDocs — mapped to your project's shape, or the exact sequence of files and commands that turns a docstring into a hosted, versioned page. Below: a side-by-side comparison table with a clear "pick this when" row, an animated diagram of the docstring-to-hosted-docs pipeline, copy-paste configs for both tools, and a pre-publish checklist. Start with the tool choice.
Sphinx vs MkDocs: Which Should You Use?
| Factor | Sphinx | MkDocs (Material) |
|---|---|---|
| Markup language | reStructuredText (Markdown via MyST) | Markdown |
| API from docstrings | autodoc + napoleon (mature, deep) | mkdocstrings (good, newer) |
| Setup complexity | Higher (conf.py, .rst toctrees) | Lower (mkdocs.yml, sensible defaults) |
| Cross-references / intersphinx | First-class, links to CPython & other projects | Limited |
| PDF / EPUB output | Yes (LaTeX / rinoh) | No (HTML only) |
| Live reload while writing | sphinx-autobuild (add-on) | mkdocs serve (built-in) |
| Look out of the box | sphinx-rtd-theme / Furo | Material (polished, modern) |
| Pick this when | You have a real API surface, type hints, and want reference docs that stay in sync with code | Your docs are guides/tutorials/prose and you want the fastest path to a beautiful site |
Both render identically well on Read the Docs, so this choice is about the writing experience and how much of your documentation is generated from code versus written by hand.
From Docstring to Hosted Docs: The Pipeline
The key insight the diagram encodes: once .readthedocs.yaml is committed, steps 2–4 happen automatically on every push. Your only ongoing job is keeping docstrings accurate (step 1).
Documentation Types
Professional documentation includes multiple types:
┌─────────────────────────────────────────────────────────────┐
│ Documentation Quadrant │
├─────────────────────────────────────────────────────────────┤
│ │
│ LEARNING INFORMATION │
│ ────────────────────────────────────────────── │
│ │
│ Tutorials Reference │
│ • Get started quickly • API documentation │
│ • Learning-oriented • Function signatures │
│ • Step-by-step • Complete & accurate │
│ │
│ ────────────────────────────────────────────── │
│ │
│ How-to Guides Explanation │
│ • Solve specific problems • Background & context │
│ • Task-oriented • Why things work │
│ • Practical steps • Architecture decisions │
│ │
│ DOING UNDERSTANDING │
│ │
└─────────────────────────────────────────────────────────────┘
Docstring Formats
Google Style (Recommended)
Clean and readable, works well with type hints:
def process_data(input_path: str, output_path: str, *, validate: bool = True) -> dict:
"""Process data from input file and write to output.
Reads the input file, applies transformations, and writes
the result to the output path.
Args:
input_path: Path to the input file. Must exist.
output_path: Path for the output file. Created if doesn't exist.
validate: Whether to validate data before processing.
Defaults to True.
Returns:
A dictionary containing processing statistics:
- processed: Number of records processed
- errors: Number of errors encountered
- duration: Processing time in seconds
Raises:
FileNotFoundError: If input_path doesn't exist.
ValidationError: If validate=True and data is invalid.
Example:
>>> stats = process_data("input.csv", "output.json")
>>> print(stats["processed"])
100
"""
NumPy Style
Common in scientific Python:
def calculate_statistics(data, axis=None):
"""
Calculate basic statistics for the input data.
Parameters
----------
data : array_like
Input data for statistical calculation.
axis : int or None, optional
Axis along which to compute statistics.
If None, compute over flattened array.
Returns
-------
dict
Dictionary containing:
- mean : float
Arithmetic mean of the data.
- std : float
Standard deviation.
- min : float
Minimum value.
- max : float
Maximum value.
See Also
--------
numpy.mean : Compute arithmetic mean.
numpy.std : Compute standard deviation.
Examples
--------
>>> data = [1, 2, 3, 4, 5]
>>> stats = calculate_statistics(data)
>>> stats['mean']
3.0
"""
Sphinx (reStructuredText) Style
Native Sphinx format:
def fetch_user(user_id, include_details=False):
"""
Fetch a user by ID.
:param user_id: The unique user identifier.
:type user_id: int
:param include_details: Include additional user details.
:type include_details: bool, optional
:returns: User object or None if not found.
:rtype: User or None
:raises ValueError: If user_id is negative.
.. note::
This function makes a network request.
.. warning::
Rate limited to 100 requests per minute.
"""
Sphinx: Full-Featured Documentation
Sphinx is the standard for Python documentation.
Setup
pip install sphinx sphinx-rtd-theme sphinx-autodoc-typehints
# Initialize in your project
cd my-package
sphinx-quickstart docs
# Answer prompts:
# > Separate source and build directories? [n]: n
# > Project name: My Package
# > Author name(s): Your Name
# > Project release: 1.0.0
Project Structure
my-package/
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── core.py
├── docs/
│ ├── conf.py # Sphinx configuration
│ ├── index.rst # Main page
│ ├── installation.rst
│ ├── quickstart.rst
│ ├── api.rst # API reference
│ └── _static/ # Custom CSS, images
├── pyproject.toml
└── README.md
Configuration (conf.py)
# docs/conf.py
import os
import sys
# Add source to path for autodoc
sys.path.insert(0, os.path.abspath("../src"))
# Project information
project = "My Package"
copyright = "2026, Your Name"
author = "Your Name"
release = "1.0.0"
# Extensions
extensions = [
"sphinx.ext.autodoc", # Auto-generate from docstrings
"sphinx.ext.napoleon", # Google/NumPy docstring support
"sphinx.ext.viewcode", # Add links to source code
"sphinx.ext.intersphinx", # Link to other projects
"sphinx_autodoc_typehints", # Type hints in docs
]
# Napoleon settings (for Google/NumPy docstrings)
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = True
# Autodoc settings
autodoc_default_options = {
"members": True,
"undoc-members": True,
"show-inheritance": True,
}
autodoc_typehints = "description"
# Intersphinx mapping
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"requests": ("https://requests.readthedocs.io/en/latest/", None),
}
# Theme
html_theme = "sphinx_rtd_theme"
html_theme_options = {
"navigation_depth": 4,
}
Main Page (index.rst)
My Package
==========
My Package is a Python library for doing awesome things.
.. toctree::
:maxdepth: 2
:caption: Contents:
installation
quickstart
api
changelog
Features
--------
* Feature one
* Feature two
* Feature three
Quick Example
-------------
.. code-block:: python
from my_package import process
result = process("input.txt")
print(result)
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
API Reference (api.rst)
API Reference
=============
Core Module
-----------
.. automodule:: my_package.core
:members:
:undoc-members:
:show-inheritance:
Utilities
---------
.. automodule:: my_package.utils
:members:
Specific Function
-----------------
.. autofunction:: my_package.process_data
Building Documentation
# Build HTML
cd docs
make html
# View locally
open _build/html/index.html
# Clean build
make clean html
MkDocs: Simple Markdown Docs
MkDocs uses Markdown and is simpler to configure.
Setup
pip install mkdocs mkdocs-material mkdocstrings[python]
# Initialize
mkdocs new .
Project Structure
my-package/
├── src/
│ └── my_package/
├── docs/
│ ├── index.md
│ ├── installation.md
│ ├── quickstart.md
│ ├── api/
│ │ └── core.md
│ └── assets/
├── mkdocs.yml
└── pyproject.toml
Configuration (mkdocs.yml)
# mkdocs.yml
site_name: My Package
site_url: https://mypackage.readthedocs.io/
repo_url: https://github.com/user/my-package
repo_name: user/my-package
theme:
name: material
features:
- navigation.tabs
- navigation.sections
- content.code.copy
palette:
- scheme: default
primary: indigo
accent: indigo
plugins:
- search
- mkdocstrings:
default_handler: python
handlers:
python:
options:
show_source: true
show_root_heading: true
nav:
- Home: index.md
- Getting Started:
- Installation: installation.md
- Quick Start: quickstart.md
- API Reference:
- Core: api/core.md
- Changelog: changelog.md
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- admonition
- toc:
permalink: true
API Documentation (api/core.md)
# Core Module
Main functionality of the package.
## process_data
::: my_package.core.process_data
## DataProcessor
::: my_package.core.DataProcessor
options:
members: true
show_source: false
Building
# Serve locally with hot reload
mkdocs serve
# Build static site
mkdocs build
# Deploy to GitHub Pages
mkdocs gh-deploy
Read the Docs
Host your documentation for free with automatic builds.
Configuration (.readthedocs.yaml)
# .readthedocs.yaml
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Sphinx
sphinx:
configuration: docs/conf.py
# Or MkDocs
# mkdocs:
# configuration: mkdocs.yml
python:
install:
- method: pip
path: .
extra_requirements:
- docs
pyproject.toml Docs Dependencies
[project.optional-dependencies]
docs = [
"sphinx>=7.0",
"sphinx-rtd-theme>=1.0",
"sphinx-autodoc-typehints>=1.0",
# Or for MkDocs:
# "mkdocs>=1.5",
# "mkdocs-material>=9.0",
# "mkdocstrings[python]>=0.24",
]
Setting Up Read the Docs
- Push your code to GitHub/GitLab/Bitbucket
- Sign up at readthedocs.org
- Import your project
- Configure build settings
- Docs build automatically on each push
Versioned Documentation
Read the Docs builds docs for:
- Main branch (latest)
- Tags (stable versions)
- Feature branches (preview)
# .readthedocs.yaml
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build PDF/EPUB
formats:
- pdf
- epub
Documentation Best Practices
1. Write README First
# My Package
[](https://pypi.org/project/my-package/)
[](https://my-package.readthedocs.io/)
Short description of what your package does.
## Installation
```bash
pip install my-package
Quick Start
from my_package import process
result = process("input.txt")
Documentation
Full documentation: https://my-package.readthedocs.io/
License
MIT
### 2. Include Working Examples
```python
def calculate_sum(numbers: list[int]) -> int:
"""Calculate the sum of numbers.
Example:
>>> calculate_sum([1, 2, 3])
6
>>> calculate_sum([])
0
>>> calculate_sum([-1, 1])
0
Note:
Examples are tested with doctest.
"""
return sum(numbers)
3. Document the Why, Not Just What
def connect(host: str, port: int, timeout: float = 30.0) -> Connection:
"""Connect to the server.
The timeout defaults to 30 seconds, which works well for most
network conditions. For slow connections or when connecting to
remote servers, you may need to increase this value.
If the server uses a non-standard port, ensure your firewall
allows outbound connections on that port.
Args:
host: Server hostname or IP address.
port: Server port number.
timeout: Connection timeout in seconds. Increase for slow
networks or distant servers.
Returns:
Active connection to the server.
"""
4. Use Admonitions
!!! note
This feature requires Python 3.10+
!!! warning
This operation cannot be undone.
!!! tip
Use `--verbose` for detailed output.
!!! danger
Never commit API keys to version control.
.. note::
This feature requires Python 3.10+
.. warning::
This operation cannot be undone.
5. Keep Docs in Sync
# pyproject.toml
[tool.pytest.ini_options]
addopts = "--doctest-modules"
# Run doctests to verify examples
# pytest src/ --doctest-modules
Project Documentation Structure
docs/
├── index.rst/md # Introduction, quick links
├── installation.rst/md # How to install
├── quickstart.rst/md # 5-minute tutorial
├── tutorials/ # In-depth tutorials
│ ├── tutorial1.rst/md
│ └── tutorial2.rst/md
├── how-to/ # Task-oriented guides
│ ├── configure.rst/md
│ └── deploy.rst/md
├── api/ # API reference
│ ├── core.rst/md
│ └── utils.rst/md
├── changelog.rst/md # Version history
└── contributing.rst/md # How to contribute
Adding Badges
# Badges for README
[](https://pypi.org/project/my-package/)
[](https://pypi.org/project/my-package/)
[](https://my-package.readthedocs.io/)
[](https://github.com/user/my-package/actions)
[](https://codecov.io/gh/user/my-package)
[](https://github.com/user/my-package/blob/main/LICENSE)
Command Reference
Sphinx
sphinx-quickstart docs # Initialize Sphinx
make html # Build HTML
make clean # Clean build
sphinx-build -b html . _build # Build command
MkDocs
mkdocs new . # Initialize MkDocs
mkdocs serve # Local development server
mkdocs build # Build static site
mkdocs gh-deploy # Deploy to GitHub Pages
Pre-Publish Documentation Checklist
Run through this before you tag a release. If any item fails, the docs will ship broken or drift out of sync.
If your API reference renders blank on Read the Docs, it is almost always an import failure: autodoc and mkdocstrings both import your code to read its docstrings, so the package must be installed in the build environment. That is what path: . under python.install in .readthedocs.yaml does — it pip installs your project so autodoc can import it.
Next Steps
- Learn to Publish to PyPI with documented packages
- See our CLI Tools Guide for CLI documentation
- Explore the Python Packaging Complete Guide for the full ecosystem
For more Python development guides, explore our complete Python packaging series.