Configuring the Sphinx build#

Documenteer provides centralized configuration for technotes. To use these configurations, you must first install Documenteer with the “technote” extra, see installation guide.

Basic configuration#

To use Documenteer’s configuration in a Sphinx technote project, the Sphinx conf.py file must contain the following import:

conf.py#
from documenteer.conf.technote import *

This configuration uses content from the technote.toml file, also in the technote repository, along with defaults in Documenteer to configure the technote build.

Customizing the Sphinx build#

Most technote projects don’t need to customize the Sphinx build beyond the defaults provided by Documenteer. If you do need to customize the build, there are two ways to do so: technote.toml and conf.py.

With technote.toml#

The recommended way to customize the build, where possible, is to through the [technote.sphinx] table in the technote.toml file. Some key configurations provided through technote.toml include:

See also

Configuring the Sphinx build, from the Technote package documentation.

With conf.py#

If technote.toml does not provide the configuration you need, you can customize the Sphinx build by adding additional lines of Python to your conf.py file. Any lines added to the conf.py file can override the configuration provided by Documenteer, or set new Sphinx configurations. The existing configurations provided by Documenteer are shown in Configuration source reference, below.

See also

Directly configuring Sphinx and extensions, from the Technote package documentation.

Intersphinx inventory cache settings#

Technotes prefetch their Intersphinx object inventories (objects.inv) from the Ook inventory cache service: Documenteer’s documenteer.ext.intersphinxcache extension downloads each one and points Sphinx at the local copy, so a technote build doesn’t depend on third-party site availability. A technote build therefore logs the same inventory prefetch summary a user guide does, and gets the same notice when Ook reports that one of its configured inventory URLs has permanently moved.

The Intersphinx projects a technote links to aren’t set here. They live in the [technote.sphinx.intersphinx.projects] table of technote.toml — which is also the table a permanent-redirect notice names when a URL you configure has moved.

The cache settings are a different matter. Guides configure them through the [sphinx.intersphinx.cache] table of a documenteer.toml file, but technotes don’t have that file, and Documenteer adds no keys to technote.toml, whose schema belongs to the Technote package rather than to Documenteer. conf.py is therefore the supported way to override these settings in a technote. Set any of them after the from documenteer.conf.technote import * line:

documenteer_intersphinx_cache_warn_on_permanent_redirect

Whether to report a permanently-moved inventory URL as a Sphinx warning rather than at the INFO log level. Default is False, so a warnings-as-errors (-W) technote build never fails because a third party moved an inventory. Set it to True if you’d rather your build fail than carry a stale inventory URL.

See warn_on_permanent_redirect for what the notice says and what escalating it does — including the documenteer.intersphinx_permanent_redirect warning subtype that silences a move you already know about, while keeping the warning for every other inventory.

documenteer_intersphinx_cache_use_service

Whether to prefetch inventories from the Ook inventory cache service. Default is True. Set it to False as an escape hatch so Intersphinx fetches every inventory directly from its origin site.

documenteer_intersphinx_cache_service_url

Base URL of the Ook API that hosts the inventory cache service. Default is "https://roundtable.lsst.cloud/ook".

documenteer_intersphinx_cache_disk_cache_ttl

How long, in seconds, a prefetched inventory on disk is reused before Documenteer revalidates it with the service. Default is 600 (10 minutes). Set it to 0 to disable that fast path so every build revalidates with the service.

For example, to make a moved inventory URL fail the technote’s build:

conf.py#
from documenteer.conf.technote import *  # noqa: F401, F403

documenteer_intersphinx_cache_warn_on_permanent_redirect = True

Note

Prefetching needs a bearer token for the Ook API, read from the OOK_TOKEN environment variable. Without it the extension is a complete no-op and stock Intersphinx behavior is unchanged, so the technote still builds — there’s simply no summary block in the log.

Configuration source reference#

"""Sphinx configuration for Rubin technotes."""

import warnings
from contextlib import suppress
from pathlib import Path
from typing import NoReturn

from sphinx.deprecation import RemovedInNextVersionWarning
from sphinx.errors import ConfigError

from documenteer.conf import (
    extend_excludes_for_non_index_source,
    extend_static_paths_with_asset_extension,
    get_asset_path,
    get_template_dir,
)

from ._errors import fail_config
from ._technotecitation import TechnoteCitation
from ._utils import (
    get_common_nitpick_ignore,
    get_common_nitpick_ignore_regex,
    get_technote_origin_base_url,
)


def _fail_technote_config(error: ConfigError) -> NoReturn:
    """Report a technote.toml the technote package rejected, in the words
    technote composed for it, and end the process.

    From technote 0.12 the message is technote's own work.
    ``technote.sources.tomlerrors.format_validation_error`` — the same design
    as `documenteer.conf._tomlerrors`, ported — addresses each validation
    failure by the table, the entry counted from one, and the field an author
    wrote, and a file TOML itself cannot parse arrives already wrapped in a
    `~sphinx.errors.ConfigError` naming the line and column the parser stopped
    at. Nothing is left to translate; what is left is to keep the message out
    of Sphinx's crash frame.

    So it is relayed unchanged rather than re-composed from the
    `~pydantic.ValidationError` technote keeps as the error's cause. Technote
    writes in the same vocabulary a ``documenteer.toml`` is reported in, and
    only technote knows what one item of its own arrays is called: it says
    ``author #1, affiliation #1`` where Documenteer's formatter, which knows
    only the arrays of its own file, would say ``author #1, item #1``.
    """
    fail_config(str(error), source="technote.toml")


# Importing technote's preset is what reads and validates technote.toml, so
# it is also where a bad file stops the build. It is reported and exits here
# rather than being left to propagate: an exception raised while conf.py runs
# is rendered by Sphinx as a crash of Sphinx, which buries the message that
# says what to fix. See documenteer.conf._errors for why the exit has to be
# the one it is.
try:
    from technote.sphinxconf import *  # noqa: F403
except ConfigError as _technote_config_error:
    _fail_technote_config(_technote_config_error)

# Suppress warnings about deprecated features in future Sphinx versions.
# This is noise for users because Documenteer itself constrains the Sphinx
# version.
warnings.filterwarnings(
    "ignore",
    category=RemovedInNextVersionWarning,
)

with suppress(ValueError):
    # Remove the sphinxcontrib-bibtex extension so that we can add it back
    # in the proper order relative to documenteer.ext.githubbibcache.
    extensions.remove("sphinxcontrib.bibtex")  # noqa: F405

with suppress(ValueError):
    # Remove myst-parser if added by technote.sphinxconf so we can
    # add myst-nb.
    extensions.remove("myst_parser")  # noqa: F405


# Add the GitHub bibfile cache extension before sphinxcontrib-bibtex so
# that it can add bibfiles to the sphinxcontrib-bibtex configuration.
extensions.extend(  # noqa: F405
    [
        "myst_nb",  # enables MyST markdown and Jupyter Notebook parsing
        "documenteer.ext.jira",
        "documenteer.ext.lsstdocushare",
        "documenteer.ext.mockcoderefs",
        "documenteer.ext.remotecodeblock",
        "documenteer.ext.bibtex",
        "documenteer.ext.githubbibcache",
        "sphinxcontrib.bibtex",
        "documenteer.ext.diagrams",
        "sphinxcontrib.mermaid",
        "sphinx_prompt",
        "sphinx_design",
        "sphinxcontrib.youtube",
        "sphinx_sitemap",
        "documenteer.ext.linkcheckservice",
        "documenteer.ext.intersphinxcache",
    ]
)

# The source file suffixes for .md and .ipynb are automatically managed by
# myst-nb.
source_suffix = {
    ".rst": "restructuredtext",
}

html_static_path: list[str] = [
    get_asset_path("rubin-favicon-transparent-32px.png"),
    get_asset_path("rubin-favicon.svg"),
    get_asset_path("rubin-technote.css"),
    get_asset_path("rubin-technote.css.map"),
    get_asset_path("rsd-assets/rubin-imagotype-color-on-white-crop.svg"),
    get_asset_path("rsd-assets/rubin-imagotype-color-on-black-crop.svg"),
]
extend_static_paths_with_asset_extension(html_static_path, "woff2")

html_css_files = ["rubin-technote.css"]

# A list of paths that contain extra templates (or templates that overwrite
# builtin/theme-specific templates).
templates_path = [get_template_dir("technote")]


# Configurations for the technote theme.
html_theme_options = {
    "light_logo": "rubin-imagotype-color-on-white-crop.svg",
    "dark_logo": "rubin-imagotype-color-on-black-crop.svg",
    "logo_link_url": "https://www.lsst.io",
    "logo_alt_text": "Rubin Observatory logo",
}

# Enable mermaid code fences as the Mermaid directive.
myst_fence_as_directive = ["mermaid"]

# Exclude non-index.ipynb Jupyter Notebooks
extend_excludes_for_non_index_source(exclude_patterns, "ipynb")  # noqa: F405
extend_excludes_for_non_index_source(exclude_patterns, "md")  # noqa: F405
extend_excludes_for_non_index_source(exclude_patterns, "rst")  # noqa: F405

# Configure bibliography with the bib cache
documenteer_bibfile_cache_dir = ".technote/bibfiles"
documenteer_bibfile_github_repos = [
    {
        "repo": "lsst/lsst-texmf",
        "ref": "main",
        "bibfiles": [
            "texmf/bibtex/bib/lsst.bib",
            "texmf/bibtex/bib/lsst-dm.bib",
            "texmf/bibtex/bib/refs_ads.bib",
            "texmf/bibtex/bib/refs.bib",
            "texmf/bibtex/bib/books.bib",
        ],
    }
]
# Set up bibtex_bibfiles
# Automatically load local bibfiles in the root directory.
bibtex_bibfiles = [str(p) for p in Path.cwd().glob("*.bib")]

bibtex_default_style = "lsst_aa"
bibtex_reference_style = "author_year"

_id = T.metadata.id  # noqa: F405
if _id is not None:
    html_context["editions_url"] = (  # noqa: F405
        f"https://{_id.lower()}.lsst.io/v/"
    )

# Every technote is citable -- a registered one by its DOI, and every other by
# its canonical URL, which is as stable -- and every technote says so in its
# sidebar: the BibTeX entry with a button that copies it, and, for a technote
# registered with a DOI, that DOI as a resolvable link
# (components/sidebar-citation.html). Every value comes from this one object,
# which composes them from the technote's own metadata through
# documenteer.citations; the template composes nothing.
#
# The object, rather than the composed strings, is what goes into the context
# because a technote's title is not known yet: technote.ext.metadata copies
# the document's H1 into the metadata at html-page-context time, so the entry
# has to be composed when the template reads it.
_citation = TechnoteCitation(T.metadata)  # noqa: F405
html_context["documenteer_technote_citation"] = _citation  # noqa: F405
# The same script the guide's citation surfaces use; it reads the entry from
# the <pre> the component renders and removes the button where the clipboard
# API is unavailable. Every technote renders a copy button, so every technote
# ships the script -- the rule that a page carrying no button loads no script
# is kept by there being no such technote page.
html_static_path.append(get_asset_path("rubin-citation-copy.js"))
html_js_files: list[str] = ["rubin-citation-copy.js"]

# Ook link-check service settings for documenteer.ext.linkcheckservice.
# Only the technote-derived settings are set here; the others keep the
# defaults registered by the extension. All of them are overridable in
# the technote's conf.py after the ``from documenteer.conf.technote
# import *`` line (e.g. ``documenteer_linkcheck_use_service = False``
# restores Sphinx's built-in linkcheck builder).
_canonical_url = T.toml.technote.canonical_url  # noqa: F405
documenteer_linkcheck_origin_base_url = get_technote_origin_base_url(
    canonical_url=str(_canonical_url) if _canonical_url else None,
    technote_id=_id,
)
documenteer_linkcheck_default_branch_name = (
    T.toml.technote.github_default_branch  # noqa: F405
)

nitpick_ignore_regex.extend(get_common_nitpick_ignore_regex())  # noqa: F405
nitpick_ignore.extend(get_common_nitpick_ignore())  # noqa: F405

# Configure sitemap.xml
sitemap_url_scheme = "{link}"
sitemap_show_lastmod = True
sitemap_excludes = [
    # These auto-generated pages aren't relevant in technotes, which are
    # meant to be single-page documents.
    "search.html",
    "genindex.html",
    "py-modindex.html",
]