📖 sphinx#
Sphinx documentation generation utilities.
This subpackage provides a comprehensive set of utilities for automatically generating and managing Sphinx documentation for Python projects. It handles the creation of configuration files, index pages, version management, and HTML generation.
The work is split by concern: forges knows where source files live on the web,
theming decides how code is coloured, conf_file writes the generated conf.py,
index_page builds the landing page, module_pages the API reference, and builder runs the whole thing.
Example of usage:
import stouputils as stp
from stouputils.applications import automatic_docs
if __name__ == "__main__":
automatic_docs.sphinx_docs(
root_path=stp.get_root_path(__file__, go_up=1),
project="stouputils",
author="Stoupy",
copyright="2025, Stoupy",
html_logo="https://avatars.githubusercontent.com/u/35665974",
html_favicon="https://avatars.githubusercontent.com/u/35665974",
html_theme="breeze", # Available themes: breeze, furo, pydata_sphinx_theme, sphinx_rtd_theme, or other you installed
github_user="Stoupy51",
github_repo="stouputils",
version="1.2.0",
skip_undocumented=True,
)
builder#
Orchestration of a documentation build: lay out the folders, write the generated files, then run Sphinx.
sphinx_docs is the only entry point most projects ever call.
Every step it performs is a parameter, so a project needing a different landing page or a different build command
replaces that one callable instead of forking the whole routine.
- generate_documentation(
- source_dir: str,
- modules_dir: str,
- project_dir: str,
- build_dir: str,
Generate documentation using Sphinx.
- Parameters:
source_dir – Source directory
modules_dir – Modules directory
project_dir – Project directory
build_dir – Build directory
- write_version_switcher(
- path: str,
- site_url: str,
- versions: list[str],
Write the
switcher.jsonbreeze fetches to fill its version dropdown.- Parameters:
site_url – Absolute URL of the folder holding one subfolder per version, ending with a slash
versions – Versions without their “v” prefix, “latest” included
- sphinx_docs(root_path: str, project: str, project_dir: str = '', author: str = 'Author', copyright: str = '2025, Author', html_logo: str = '', html_favicon: str = '', html_theme: str = 'breeze', github_user: str = '', github_repo: str = '', repo_url: str = '', repo_provider: str = 'github', repo_branch: str = 'main', edit_link_path: str = '', pygments_light_style: str = 'vscode-light-plus', pygments_dark_style: str = 'vscode-dark-plus', default_mode: str = 'dark', autodoc_mock_imports: list[str] | None = None, version: str | None = None, skip_undocumented: bool = True, recent_minor_versions: int = 2, external_links: list[str] | None = None, get_versions_function: ~collections.abc.Callable[[str, str, int], list[str]] = <functools._lru_cache_wrapper object>, generate_index_function: ~collections.abc.Callable[[...], None] = <function generate_index_md>, generate_docs_function: ~collections.abc.Callable[[...], None] = <function generate_documentation>, generate_redirect_function: ~collections.abc.Callable[[str], None] = <function generate_redirect_html>, get_conf_content_function: ~collections.abc.Callable[[...], str] = <function get_sphinx_conf_content>) None[source]#
Update the Sphinx documentation.
- Parameters:
root_path – Root path of the project
project – Name of the project
project_dir – Path to the project directory (to be used with generate_docs_function)
author – Author of the project
copyright – Copyright information
html_logo – URL to the logo
html_favicon – URL to the favicon
html_theme – Theme to use for the documentation. Defaults to “breeze”
github_user – GitHub username
github_repo – GitHub repository name
repo_url – Repository URL used for source links, defaulting to the GitHub one built from the two above
repo_provider – Which key of
FORGESdescribes the repository URL. Defaults to “github”repo_branch – Branch the source links point at. Defaults to “main”
edit_link_path – Where the Sphinx sources are tracked, enabling the “edit this page” link, ex: “docs/source”
pygments_light_style – Pygments style used in light mode
pygments_dark_style – Pygments style used in dark mode
default_mode – Colour mode a first-time visitor gets: “auto”, “light” or “dark”
autodoc_mock_imports – Packages autodoc stubs out instead of importing, defaulting to none
version – Version to build documentation for (e.g. “1.0.0”, defaults to “latest”)
skip_undocumented – Whether to skip undocumented members. Defaults to True
recent_minor_versions – Number of recent minor versions to show all patches for. Defaults to 2
external_links – URLs shown as icons in the header before the repository one, ex: a Discord invite or a PyPI page
get_versions_function – Function to get versions from GitHub
generate_index_function – Function to generate index.md
generate_docs_function – Function to generate documentation
generate_redirect_function – Function to create redirect file
get_conf_content_function – Function to get Sphinx conf.py content
conf_file#
Generation of the docs/source/conf.py file Sphinx reads.
The file is produced as text rather than imported from a template, because a fair part of it is decided by the caller’s arguments: which forge hosts the sources, which theme renders them, and which pygments styles colour them.
- python_literal(value: dict[str, Any]) str[source]#
Render a mapping as a Python literal fit for the generated
conf.py.- Parameters:
value – Mapping to render
- Returns:
The literal, with JSON booleans translated back to Python ones
>>> python_literal({"a": True, "b": False}) '{\n\t"a": True,\n\t"b": False\n}\n'
- get_sphinx_conf_content(
- project: str,
- project_dir: str,
- author: str,
- current_version: str,
- copyright: str,
- html_logo: str,
- html_favicon: str,
- html_theme: str = 'breeze',
- github_user: str = '',
- github_repo: str = '',
- version_switcher_url: str = '',
- skip_undocumented: bool = True,
- repo_url: str = '',
- repo_provider: str = 'github',
- repo_branch: str = 'main',
- source_prefix: str = '',
- edit_link_path: str = '',
- pygments_light_style: str = 'vscode-light-plus',
- pygments_dark_style: str = 'vscode-dark-plus',
- default_mode: str = 'dark',
- autodoc_mock_imports: list[str] | None = None,
- external_links: list[str] | None = None,
Get the content of the Sphinx configuration file.
- Parameters:
project – Name of the project
project_dir – Path to the project directory
author – Author of the project
current_version – Current version
copyright – Copyright information
html_logo – URL to the logo
html_favicon – URL to the favicon
html_theme – Theme rendering the documentation. Defaults to “breeze”
github_user – GitHub username
github_repo – GitHub repository name
version_switcher_url – Absolute URL of the
switcher.jsonlisting every published version, empty for no switcherskip_undocumented – Whether to skip undocumented members. Defaults to True
repo_url – Repository URL used for source links, ex: “https://gitlab.example.com/group/project”
repo_provider – Which key of
FORGESdescribes the repository URL. Defaults to “github”repo_branch – Branch the source links point at. Defaults to “main”
source_prefix – Path from the repository root to the importable package’s parent, ex: “src/”
edit_link_path – Where the Sphinx sources are tracked, enabling the “edit this page” link Leave it empty when those sources are generated, since editing them would be pointless.
pygments_light_style – Pygments style used in light mode
pygments_dark_style – Pygments style used in dark mode
default_mode – Colour mode a first-time visitor gets: “auto”, “light” or “dark”
autodoc_mock_imports – Packages autodoc replaces with a stub instead of importing Only name packages the documented code never calls at import time, since a mock answers every attribute with another mock, which turns an ordinary decorator or metaclass into a failed import.
external_links – URLs shown as icons in the header before the repository one, ex: a Discord invite or a PyPI page
- Returns:
Content of the Sphinx configuration file
forges#
Code forge URL conventions, used to link a documented object back to its source.
Every forge agrees that a URL needs a repository, a branch and a path, and no two of them agree on the order. This module holds that knowledge in one table so the rest of the generator never has to care.
- class ForgeUrls(edit: str, blob: str)[source]#
Bases:
objectURL patterns of a code forge, built from a repository URL.
- FORGES: dict[str, ForgeUrls] = {'bitbucket': ForgeUrls(edit='{repo}/src/{branch}/{path}?mode=edit', blob='{repo}/src/{branch}/{path}'), 'codeberg': ForgeUrls(edit='{repo}/_edit/{branch}/{path}', blob='{repo}/src/branch/{branch}/{path}'), 'github': ForgeUrls(edit='{repo}/edit/{branch}/{path}', blob='{repo}/blob/{branch}/{path}'), 'gitlab': ForgeUrls(edit='{repo}/-/edit/{branch}/{path}', blob='{repo}/-/blob/{branch}/{path}')}[source]#
Path conventions of each supported forge, since no two of them agree on where to put the branch.
- get_source_url(
- repo_url: str,
- repo_provider: str,
- repo_branch: str,
Build the
linkcodetemplate pointing at a module’s source file.- Parameters:
repo_url – Repository URL, ex: “https://github.com/Stoupy51/stouputils”
repo_provider – Which key of
FORGESdescribes the repository URLrepo_branch – Branch the source links point at
- Returns:
URL with a remaining
{filename}placeholder, empty when no repository is known
>>> get_source_url("https://github.com/Stoupy51/stouputils", "github", "main") 'https://github.com/Stoupy51/stouputils/blob/main/{filename}.py' >>> get_source_url("", "github", "main") ''
- get_edit_url(
- repo_url: str,
- repo_provider: str,
- repo_branch: str,
- edit_link_path: str,
Build the “edit this page” template the theme fills with the page path.
- Parameters:
repo_url – Repository URL, ex: “https://github.com/Stoupy51/stouputils”
repo_provider – Which key of
FORGESdescribes the repository URLrepo_branch – Branch the edit links point at
edit_link_path – Where the Sphinx sources are tracked, ex: “docs/source”
- Returns:
URL ending in the theme’s
%splaceholder, empty when either argument is missing
>>> get_edit_url("https://gitlab.com/g/p", "gitlab", "main", "docs/source") 'https://gitlab.com/g/p/-/edit/main/docs/source/%s' >>> get_edit_url("https://gitlab.com/g/p", "gitlab", "main", "") ''
index_page#
Generation of the documentation landing page.
The default page is the README, with a hidden toctree making the API reference a tab of the header.
Projects wanting something else pass their own callable as generate_index_function.
- generate_index_md(
- readme_path: str,
- index_path: str,
- project: str,
Generate
index.md(MyST) from README.md content, kept as Markdown.- Parameters:
readme_path – Path to the README.md file
index_path – Path where index.md should be created
project – Name of the project, lowercased into the name of its root package
module_pages#
Generation of the API reference, one page per package.
A page opens with the package docstring, lists its subpackages, then gives each module of the package a section.
When a subpackage’s __init__.py imports names from its modules, those names are its API and the only ones documented,
which leaves out the modules it imports nothing from. The root package’s modules are imported by their own path, so they are all kept.
An emoji opening the docstring of a package or module also opens its title, and breeze shows it in the navigation.
- ROOT_TITLE: str = 'API reference'[source]#
Title of the root package’s page, which the header shows as a tab.
- write_module_pages(
- project_dir: str,
- modules_dir: str,
Write one reStructuredText page per package of
project_dirintomodules_dir, named after its dotted path.A folder holding Python files without an
__init__.pycounts as a namespace package. Packages and modules whose name starts with an underscore are left out.
- is_public(relative_path: Path) bool[source]#
Whether no part of a path under the project starts with an underscore or a dot.
>>> is_public(Path("typing/runtime.py")), is_public(Path("typing/__init__.py")), is_public(Path("__pycache__")) (True, False, False)
- titled(source: Path) str[source]#
Name of a module or package, after the emoji opening its docstring if any.
- Parameters:
source – The module’s file, or the
__init__.pyof a package, which may not exist for a namespace package
- exported_names(init: Path) dict[str, list[str]][source]#
Names an
__init__.pyre-exports from each module beside it, keyed by module name, empty when the file does not exist.A re-export is an
import x as xor a name listed in__all__, the convention type checkers follow. A plain import is the package using a helper of its own, which stays out of its API.
- dunder_all(tree: Module) set[str][source]#
Names a module lists in
__all__.>>> sorted(dunder_all(ast.parse("__all__ = ['b', 'a']"))) ['a', 'b']
- heading(title: str, underline: str) list[str][source]#
Lines of a reStructuredText heading, underlined twice its length since an emoji takes two columns.
- package_page(
- package: Path,
- dotted: str,
- title: str,
- subpackages: list[str],
- exported: dict[str, list[str]],
reStructuredText of a package’s page.
- Parameters:
dotted – Import path of the package, ex: “stouputils.typing”
subpackages – Import paths of the packages directly below it, which get pages of their own
exported – Names the package imports from each of its modules, documenting every module in full when none matches
- drop_overloads() None[source]#
Make autodoc show the signature of a function’s implementation instead of one line per
@overloadabove it.Autodoc reads the overloads from the module analyzer it caches, and only skips them along with every annotation of the site.
- exact_builtin_references(
- app: Any,
- doctree: Any,
Handler for
doctree-readkeeping the builtin names of annotations, liketype, from linking to project objects.Sphinx looks up an annotation by suffix whenever its node carries
refspecific, which it always does, even set to False. A builtin then resolves to every attribute of the project sharing its name, and links to the first one.
theming#
Theme, syntax highlighting and stylesheet concerns of the generated documentation.
Pygments’ Python lexer is coarse: it only tags an identifier as Name.Function or Name.Class when a literal
def or class introduces it, and emits a bare Name for every call, attribute, argument and variable.
A palette designed for a finer grammar therefore paints most of the code in whichever colour it gave Name.
That is why the styles below are picked so that Name keeps the foreground colour, leaving hues for the tokens
the lexer genuinely recognises.
- DEFAULT_LIGHT_STYLE: str = 'vscode-light-plus'[source]#
Pygments style for light mode, transcribed from VS Code so a snippet matches the reader’s editor.
- DEFAULT_DARK_STYLE: str = 'vscode-dark-plus'[source]#
Pygments style for dark mode.
The theme’s own default,
github-dark-high-contrast, paintsNamein#DBB7FF. Since a Python page emits about a hundred bareNametokens for eachdef, that turns whole snippets violet.
- CUSTOM_CSS: str = '\n/* Custom CSS for Sphinx documentation */\n/* Reduce heading sizes */\nh1 { font-size: 2.0em !important; }\nh2 { font-size: 1.6em !important; }\nh3 { font-size: 1.4em !important; }\nh4 { font-size: 1.2em !important; }\nh5 { font-size: 1.0em !important; }\nh6 { font-size: 0.9em !important; }\n\n/* Gradient animation keyframes */\n@keyframes shine-slide {\n\t0% { background-position: -200% center; }\n\t100% { background-position: 200% center; }\n}\n\n/* Adjustments to abmonition */\n.admonition {\n\ttext-decoration: none;\n\tpadding: 1rem;\n\tdisplay: block;\n}\n\n/* On hover animation for various elements */\na, h1, h2, h3, h4, h5, h6, .admonition {\n\ttransition: transform 0.3s;\n}\n\na:hover, h1:hover, h2:hover, h3:hover, h4:hover, h5:hover, h6:hover, .admonition:hover {\n\ttransform: scale(1.05);\n}\na:hover, a:hover span {\n\tbackground: linear-gradient(\n\t\t110deg,\n\t\tcurrentColor 0%,\n\t\tcurrentColor 40%,\n\t\twhite 50%,\n\t\tcurrentColor 60%,\n\t\tcurrentColor 100%\n\t);\n\tbackground-size: 200% 100%;\n\tbackground-clip: text;\n\t-webkit-background-clip: text;\n\t-webkit-text-fill-color: transparent;\n\tanimation: shine-slide 3.5s linear infinite;\n}\n\n/* Code is indented with tabs, which browsers render eight columns wide by default */\npre, code, .highlight pre { tab-size: 4; }\n\n/* The page title reserves room under the page actions, so it must not cover them while its hover transform runs */\n.bz-page-actions { z-index: 2; }\n\n/* A doctest prompt is a marker, not code, so it should never end up in what a reader copies by hand */\n.highlight .gp { user-select: none; }\n'[source]#
Stylesheet written to
_static/custom.cssand loaded on top of the theme.
- SIDEBAR_NAV: str = '{%- set content = toctree(level=1, maxdepth=-1)\n\tor toctree(level=0, maxdepth=-1) | replace("<details>", "<details open>", 1) -%}\n{%- if content -%}\n<nav class="bz-sidebar-nav" aria-label="{{ _(\'Section Navigation\') }}">\n {%- if theme_emojis_sidebar_nav | tobool -%}{{ wrap_emoji(content) }}{%- else -%}{{ replace_emoji(content) }}{%- endif -%}\n</nav>\n{%- endif -%}\n'[source]#
Breeze’s
sidebar-nav.html, falling back to the whole tree where breeze lists only the current header tab.The landing page belongs to no tab, so breeze alone leaves its sidebar empty. Breeze only unfolds the branch holding the current page, so the fallback unfolds the first one itself. Both list every depth, where breeze stops at the
:maxdepth:of the subpackage lists the pages show.
- check_dependencies(html_theme: str) None[source]#
Check that the requested theme, and every base requirement, is installed.
- Parameters:
html_theme – HTML theme used by the documentation, ex: “breeze”, “pydata_sphinx_theme”, “furo”
- Raises:
ImportError – If the theme or any base requirement is missing
- get_theme_options(
- html_theme: str,
- default_mode: str,
- external_links: list[str],
Build the
html_theme_optionsmapping, holding only keys the chosen theme understands.default_modereaches breeze throughhtml_theme_options, and pydata throughhtml_context. Passing it to a theme that knows neither only earns an “unsupported theme option” warning, so it is filtered here.- Parameters:
html_theme – HTML theme used by the documentation
default_mode – Colour mode a first-time visitor gets, one of “auto”, “light” or “dark”
external_links – URLs breeze shows as icons in the header and footer, named after their domain
- Returns:
Options to write into the generated
conf.py
>>> get_theme_options("breeze", "dark", ["https://github.com/Stoupy51/stouputils"])["external_links"] ['https://github.com/Stoupy51/stouputils'] >>> get_theme_options("furo", "dark", ["https://github.com/Stoupy51/stouputils"]) {'navigation_with_keys': True}
- write_custom_css(static_dir: str) None[source]#
Write
CUSTOM_CSSinto the static folder the generatedconf.pypoints at.- Parameters:
static_dir – The
docs/source/_staticfolder
Write
SIDEBAR_NAVinto the templates folder, where it takes precedence over breeze’s own.- Parameters:
templates_dir – The
docs/source/_templatesfolder