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.
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.
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.
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.
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.
{
# All revisions to be build
"revisions": Tuple[GitRef, ...],
# The revision sphinx is currently building
"current": GitRef,
}
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.
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
.
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.