This guide walks through setting up automatic Sphinx documentation for a Python package using a src layout. At the end, running make docs generates complete API reference documentation from docstrings without any manual maintenance.
- Python 3.10+
- Poetry installed
- A repository with this minimum structure:
my_package/
├── src/
│ └── my_package/
│ └── __init__.py
├── pyproject.toml
└── README.md
poetry add --group dev "sphinx>=7.0,<8.0"Sphinx 9+ requires Python 3.12. Pin to 7.x for Python 3.10 compatibility.
From the project root:
poetry run sphinx-quickstart docsWhen prompted:
- Separate source and build directories:
n - Project name: your package name
- Author name: your name
- Project version: your version
- Language:
en
This creates:
docs/
├── conf.py
├── index.rst
├── Makefile
└── make.bat
Replace the contents of docs/conf.py with:
import os
import sys
sys.path.insert(0, os.path.abspath("../src"))
project = "my_package"
copyright = "2026, Your Name"
author = "Your Name"
release = "0.1.0"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
]
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
html_theme = "alabaster"
html_static_path = []Key points:
sys.path.insert(0, os.path.abspath("../src"))tells Sphinx where to find the package in asrclayout.autodocextracts docstrings from source files.napoleonenables Google-style docstring support.html_static_path = []avoids a warning when no custom static files exist.
Replace the contents of docs/index.rst with:
my_package documentation
=========================
.. toctree::
:maxdepth: 2
:caption: Contents:
installation
usage
reference/modules.. toctree:: is a Sphinx directive that builds the navigation tree for the documentation site. Everything indented under it is a page that will appear in the sidebar and table of contents. :maxdepth: 2 controls how many heading levels deep the table of contents goes — at 2 it shows the page title and its immediate sub-sections. :caption: Contents: is the label that appears above the navigation list in the sidebar.
installation, usage, and reference/modules are paths to .rst files relative to docs/. Sphinx resolves them as docs/installation.rst, docs/usage.rst, and docs/reference/modules.rst. The .rst extension is omitted by convention.
reference/modules is the only generated entry. sphinx-apidoc always writes this file as its top-level table of contents for the package, and it in turn links to the individual per-module pages it generates. The other two are hand-written narrative pages that you maintain.
Create docs/installation.rst:
Installation
============
Requirements
------------
- Python 3.10 or higher
- Poetry (for development installs)
Install from source
-------------------
.. code-block:: bash
git clone <repo-url>
cd my_package
poetry installCreate docs/usage.rst:
Usage
=====
CLI
---
.. code-block:: bash
my_package --help
API
---
Send a POST request to ``/predict`` with the required input. See the package reference for full documentation.Add the following to .gitignore:
# Sphinx docs
docs/_build/
docs/reference/
docs/_build/ is build output. docs/reference/ contains files generated by sphinx-apidoc at build time and should never be committed.
Create Makefile in the project root:
.PHONY: docs docs-open tests lint
docs:
poetry run sphinx-apidoc -f -o docs/reference src/my_package
poetry run sphinx-build -b html -W docs/ docs/_build/html
docs-open:
poetry run sphinx-apidoc -f -o docs/reference src/my_package
poetry run sphinx-build -b html -W docs/ docs/_build/html
open docs/_build/html/index.html
docs-start:
poetry run sphinx-apidoc -f -o docs/reference src/my_package
poetry run sphinx-build -b html -W docs/ docs/_build/html
start docs/_build/html/index.html
tests:
poetry run pytest tests/ -v
lint:
poetry run pre-commit run --all-filesReplace my_package with your actual package name.
Each target is a shortcut for a common task:
make docsrunssphinx-apidocto generate reference.rstfiles from the source tree, then runssphinx-buildto produce the HTML output indocs/_build/html. This is the command you and your team run to build the documentation locally, and the command the pre-commit hook and GitHub Actions workflow use in CI.make docs-opendoes the same asmake docsand then opens the result in the browser. Use this during development to preview changes to docstrings or narrative pages.make testsruns the full test suite. Use this before opening a pull request.make lintruns all pre-commit hooks against every file. Use this to catch formatting and linting issues without making a commit. The-fflag onsphinx-apidocforces it to overwrite any existing generated files on every run, ensuring the reference is always up to date. Without it,sphinx-apidocskips files that already exist, which can leave stale pages behind after a module is renamed or moved.
docs-open is macOS only — Windows users should replace open with start.
Create .pre-commit-config.yaml in the project root. First install pre-commit:
poetry add --group dev pre-commit
poetry run pre-commit installMinimal .pre-commit-config.yaml:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
- repo: local
hooks:
- id: sphinx-build
name: Build Sphinx documentation
entry: bash -c 'poetry run sphinx-apidoc -f -q -o docs/reference src/my_package && poetry run sphinx-build -b html -W docs/ docs/_build/html'
language: system
pass_filenames: false
files: \.(py|rst)$The pre-commit-hooks entries enforce basic hygiene on every commit. black auto-formats Python files. The sphinx-build hook runs sphinx-apidoc followed by sphinx-build so it is fully self-contained — it never depends on previously generated files.
The -q flag silences sphinx-apidoc output so only errors are shown. The -W flag on sphinx-build promotes warnings to errors, so a broken docstring or missing reference blocks the commit rather than silently producing bad documentation.
If you are running pre-commit in a CI environment without Poetry, add SKIP=sphinx-build to the CI step that runs pre-commit and handle the docs build separately in your docs deployment workflow.
make docs
# MacOS
open docs/_build/html/index.html
# Windows
start docs/_build/html/index.htmlThe package reference should appear with all modules and their docstrings.
When you add a new module, no documentation files need to be updated. On the next make docs run, sphinx-apidoc finds the new module and generates its reference page automatically. When you remove a module, the reference page disappears on the next build. The only files that ever need manual updates are the narrative pages: installation.rst and usage.rst.
sphinx-apidoc recurses into subdirectories automatically, as long as each directory contains an __init__.py. For example, adding src/my_package/inference/predict.py requires two files:
src/my_package/inference/__init__.py
src/my_package/inference/predict.py
The __init__.py can be minimal:
"""Inference subpackage for my_package."""On the next make docs run, sphinx-apidoc generates docs/reference/my_package.inference.rst and docs/reference/my_package.inference.predict.rst automatically. The module appears in the reference under my_package.inference.predict with its docstrings. No changes to docs/index.rst or any other file are required.
This step deploys the built documentation to GitHub Pages on every push to main.
In your repository go to Settings > Pages and set the source to GitHub Actions.
Create .github/workflows/docs.yaml:
name: Deploy Sphinx documentation to GitHub Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install dependencies
run: |
pip install poetry
poetry install --with dev
- name: Generate API reference
run: poetry run sphinx-apidoc -f -q -o docs/reference src/my_package
- name: Build Sphinx documentation
run: poetry run sphinx-build -b html -W docs/ docs/_build/html
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/_build/html
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5Replace my_package with your actual package name.
The Generate API reference step is critical — it must run before Build Sphinx documentation. Without it, docs/reference/ is empty in the CI environment because generated .rst files are in .gitignore and are never committed. Omitting this step produces a site with no package reference content even though the build succeeds.
The workflow triggers on every push to main and can also be triggered manually from the Actions tab via workflow_dispatch. It installs dependencies via Poetry, runs sphinx-apidoc to generate reference .rst files from the source tree, runs sphinx-build to produce the HTML output, and deploys to GitHub Pages. The workflow does not run on pull requests — documentation is only published when changes land on main.
After pushing to main, go to the Actions tab in your GitHub repository. You will see the workflow named Deploy Sphinx documentation to GitHub Pages running. Click on it to expand each step. The Generate API reference and Build Sphinx documentation steps show any warnings or errors from the build. The final Deploy to GitHub Pages step shows the deployed URL when it completes.
To review past deployments go to Actions > Deployments > All Deployments. Each entry shows which PR triggered the deployment, who triggered it, and whether it succeeded. The currently active deployment is marked Active.
The deployed URL follows the pattern https://<org>.github.io/<repo>/ and also appears under Settings > Pages in the repository.
The lint workflow runs pre-commit, which includes the sphinx-build hook. That hook requires Poetry and the full package environment, which is not available in the lint runner. Skip it explicitly in your lint workflow:
- name: Run pre-commit hooks
run: SKIP=sphinx-build pre-commit run --all-filesThe docs.yaml workflow is the authoritative build and deploy pipeline. The pre-commit hook is for catching broken docs locally before committing.
This setup uses Google-style docstrings via the napoleon extension:
def predict(image_path: str, model_path: str) -> dict:
"""Run inference on a single image.
Args:
image_path: Path to the input image file.
model_path: Path to the trained model weights.
Returns:
A dict containing segmentation masks and confidence score.
Raises:
FileNotFoundError: If image_path or model_path does not exist.
Example:
>>> result = predict("image.png", "model.h5")
>>> print(result["confidence_score"])
"""Sphinx renders Args, Returns, Raises, and Example sections automatically from this format.
Avoid indented sub-lists inside named sections — Sphinx treats them as unexpected indentation and will fail the build. Use a Note: section with unindented bullet points instead:
"""Summary line.
Note:
Some important information:
- First point
- Second point
"""| Error | Cause | Fix |
|---|---|---|
toctree contains reference to nonexisting document 'reference/modules' |
sphinx-apidoc has not been run yet |
Run make docs instead of sphinx-build directly |
autodoc: failed to import module |
Package dependency not installed | Run poetry install |
duplicate object description |
Module documented in two places | Check for leftover .rst files in docs/ from a previous setup and delete them |
html_static_path entry does not exist |
_static directory missing |
Set html_static_path = [] in conf.py |
Unexpected indentation in docstring |
Indented sub-list inside a named section | Use a Note: section with unindented bullet points |