Getting Started

This guide will show you how to create a minimal setup for your project. At the same time it will link resources where you can find more information for specific customizations and use cases. This guide assumes that you installed sphinx-polyversion into the development environment of your project and have written some documentation that builds with sphinx-build.

For this tool it doesn’t matter which branch you are working on as it will query the whole git repository for branches and tags to build documentation for. However you will need to fetch all the references that should be visible to the tool. However the tool will always use the configuration currently checked out.

Configuring sphinx-polyversion

With sphinx-polyversion everything revolves around its configuration file which is a python script conventionally named poly.py. This configuration file will be executed when calling sphinx-polyversion. This tool is designed in such a way that poly.py does all the heavy lifting. In fact there is no need to run sphinx-polyversion from the commandline. Instead you can execute poly.py directly to build your documentation. However the sphinx_polyversion python package provides the underlying logic as well as helpful utilities. This design makes sphinx-polyversion highly customizable and extendable allowing it to be used in all kinds of applications - even those that the developers of sphinx-polyversion didn’t even think about.

By convention the poly.py file follows a specific structure that provides some benefits in combination with this tool. This section will walk you through this structure.

Start off with defining the config options to be used for building your versioned documentation. This is done by initializing variables in the global scope. You can find a reasonable example below. Since poly.py is a self contained python script you decide every detail of the build process including all configuration options. There are no specifications you have to conform to when deciding on the config options you define and how you name them.

Warning

There are to naming specifications for config variables: The output directory must always be called OUTPUT_DIR. And you have to pass MOCK to DefaultDriver.run.

Note

Config options will be passed to the build logic later. This requires types like tuple or Path and fortunally any type can be used for config options. However it makes sense to stick to string where possible since the overrides will always be a string entered from the commandline. Currently there is no system to convert these string to other python types. If you have an idea how to design a system that follows the philosophy of this project please open a discussion on github.

Defining the options as variables at the beginning not only makes the configuration file easier to understand but also allows those variables to be overridden from the commandline before being used to build the documentation. This is a major feature of sphinx-polyversion which will be explained further down this guide.

docs/poly.py - imports and config variables
 1from pathlib import Path
 2from datetime import datetime
 3from sphinx_polyversion import *
 4from sphinx_polyversion.git import *
 5from sphinx_polyversion.pyvenv import Poetry
 6from sphinx_polyversion.sphinx import SphinxBuilder
 7
 8#: Regex matching the branches to build docs for
 9BRANCH_REGEX = r".*"
10
11#: Regex matching the tags to build docs for
12TAG_REGEX = r".*"
13
14#: Output dir relative to project root
15#: !!! This name has to be choosen !!!
16OUTPUT_DIR = "docs/build"
17
18#: Source directory
19SOURCE_DIR = "docs/"
20
21#: Arguments to pass to `poetry install`
22POETRY_ARGS = "--only docs --sync"
23
24#: Arguments to pass to `sphinx-build`
25SPHINX_ARGS = "-a -v"
26
27#: Mock data used for building local version
28MOCK_DATA = {
29    "revisions": [
30        GitRef("v1.8.0", "", "", GitRefType.TAG, datetime.fromtimestamp(0)),
31        GitRef("v1.9.3", "", "", GitRefType.TAG, datetime.fromtimestamp(1)),
32        GitRef("v1.10.5", "", "", GitRefType.TAG, datetime.fromtimestamp(2)),
33        GitRef("master", "", "", GitRefType.BRANCH, datetime.fromtimestamp(3)),
34        GitRef("dev", "", "", GitRefType.BRANCH, datetime.fromtimestamp(4)),
35        GitRef("some-feature", "", "", GitRefType.BRANCH, datetime.fromtimestamp(5)),
36    ],
37    "current": GitRef("local", "", "", GitRefType.BRANCH, datetime.fromtimestamp(6)),
38}

Next you add the code handling the overrides read from the commandline. This is straightforward since sphinx-polyversion provides the function apply_overrides that takes care of that. It parses the commandline arguments and overrides the config variables with the given values. For that you need to pass the globals() dictionary to the function.

docs/poly.py - overrides
38# Load overrides read from commandline to global scope
39apply_overrides(globals())

The poly.py file is finished with adding the code that actually builds the different versions of the documentation.

First you determine the root folder of the repository. It makes sense to use the method provided since you might call the script from arbitrary locations. The root will be used for determining the locations of the template, source and static directories.

After that you initialize the DefaultDriver class using the config options you defined earlier. The driver uses the passed vcs object to determine which versions to build. It will proceed with running the builder object in the env environment. In this case sphinx-build is run in a python virtual environment created with poetry for each version. This means that each version is build in an isolated environment with the dependencies defined in its revision.

docs/poly.py - building the docs
41# Determine repository root directory
42root = Git.root(Path(__file__).parent)
43
44# Setup driver and run it
45src = Path(SOURCE_DIR)  # convert from string
46DefaultDriver(
47    root,
48    OUTPUT_DIR,
49    vcs=Git(
50        branch_regex=BRANCH_REGEX,
51        tag_regex=TAG_REGEX,
52        buffer_size=1 * 10**9,  # 1 GB
53        predicate=file_predicate([src]),  # exclude refs without source dir
54    ),
55    builder=SphinxBuilder(src / "sphinx", args=SPHINX_ARGS.split()),
56    env=Poetry.factory(args=POETRY_ARGS.split()),
57    template_dir=root / src / "templates",
58    static_dir=root / src / "static",
59    mock=MOCK_DATA,
60).run(MOCK)

Using versioning data in conf.py

When using sphinx the versioning data (current revision, list of all revisions, …) can be accessed inside the conf.py file and inside the jinja templates used to render the docs. For that the version data is serialized to json and exposed through an environment variable to sphinx. The data can the be read in conf.py and written to html_context. This sphinx configuration variable holds a dictionary with fields available in jinja templates.

Luckily you don’t have to worry about that, the load function takes care of everything for you. After calling this function the following data is merged into html_context. You can customize what data is passed to sphinx though.

default data exposed to sphinx docs
{
    # All revisions to be build
    "revisions": Tuple[GitRef, ...],
    # The revision sphinx is currently building
    "current": GitRef,
}
docs/conf.py - loading versioning data
 6# -- Load versioning data ----------------------------------------------------
 7
 8from sphinx_polyversion import load
 9from sphinx_polyversion.git import GitRef
10
11data = load(globals())  # adds variables `current` and `revisions`
12current: GitRef = data['current']

Populating the root of the merged docs

The docs for each revision will be build into a subfolder of the docs/build:

docs/build
├───dev
├───master
├───v2.3
├───v2.4
└───v3.7

You can add global pages to the root of the documentation. That is docs/build/. Those can either be static files that are copied or templates that are rendered using jinja2. In this example static files will be located in docs/static and templates in docs/templates. This results in the following layout:

docs
├───build
├───sphinx
│   ├───_static
│   ├───_templates
│   └───conf.py
├───static
├───templates
│   └───index.html
└───poly.py

The index.html file is optional but makes sense since it will be the page shown when entering the url to your documentation. In most cases you will want the it to redirect to the latest revision of the sphinx docs. The following jinja template generates the corresponding html.

docs/templates/index.html
 1<!doctype html>
 2
 3<html>
 4    <head>
 5        <title>Redirecting to master branch</title>
 6        <meta charset="utf-8" />
 7        <meta
 8            http-equiv="refresh"
 9            content="0; url=./{{ latest.name }}/index.html"
10        />
11        <link rel="canonical" href="./{{ latest.name }}/index.html" />
12    </head>
13</html>

You will have to add some lines to poly.py since the template requires a latest field that isn’t provided by default since sphinx-polyversion can’t know which tag represents the latest revision. First you have to implement root_data (see below) and then pass root_data_factory=root_data to DefaultDriver.

docs/poly.py - calculate and expose latest revision
40from sphinx_polyversion.git import refs_by_type
41
42def root_data(driver: DefaultDriver):
43    revisions = driver.builds
44    tags, branches  = refs_by_type(revisions)
45    latest = max(tags or branches)
46    return {"revisions": revisions, "latest": latest}

Building with sphinx-polyversion

Now that everything is setup you can actually run sphinx-polyversion and build your versioned documentation. All versions configured in poly.py will be build. However if you want to test local changes you can use the -l flag to build a documentation from the files in the local filesystem. When passing this flag all other versions are not build.

usage: sphinx_polyversion [-h] [-o [OVERRIDE ...]] [-v] [-l] conf [out]

Positional Arguments

conf

Polyversion config file to load. This must be a python file that can be evaluated.

out

Output directory to build the merged docs to.

Default: False

Named Arguments

-o, --override

Override config options. Pass them as key=value pairs.

Default: {}

-v, --verbosity

Increase output verbosity (decreases minimum log level). The default log level is ERROR.

Default: 0

-l, --local, --mock

Build the local version of your docs.

Default: False

Overriding config options

You can override the defaults set in poly.py by specifying values on the commandline. Specifying an output location will override OUTPUT_DIR while specifying --local will set MOCK to True. All other variables can be overidden through the -o flag. You can override the arguments passed to sphinx-build by entering the following:

sphinx-polyversion docs/poly.py -o SPHINX_BUILD='-T -v'

Adding a version picker to the docs

There are plenty of ways how to add a widget to your rendered documentation that allows the user to select the version to view. Some themes might come with a version picker build-in while for the others you have to add one yourself. Usually you can leverage sphinx template system for that. For a reference you can have a look how this documentation implemented the version picker.