Publishing docs¶
This is a short guide explaining all the necessary steps to publish documentation for a Python package using Spinx, Jupyter Book, and GitHub, including GitHub-Actions and Github-Pages.
Software structure¶
When I write a Python package, I usually only rely on setuptools and a setup.py
file that contains all the necessary information for packaging. Instructions can be found here. Note that in the future, setup.py
will be replaced by setting all information in a setup.cfg
file (see here for example).
Writing docs¶
There are multiple ways of writing documentation for software. My personal preference is the Numpy style documentation. It is important to follow the correct syntax so that automatic documentation tools like Sphinx can then parse the information. Here’s just a quick example for a function:
def my_function(a):
"""
This is my function.
Parameters
----------
a: 2d array
This is my array.
Returns
-------
b: 2d array
"""
b = 3 * a
return b
Creating the docs¶
The following guide is a shortened version of this excellent Medium post.
The main tool used to create the docs is Sphinx. It has to be installed using e.g.:
pip install sphinx
When pointed to a package, Sphinx can discover all modules and parse the content of their docs. For a given project, we first need to initialize some Sphinx files. In our project we create a docs
folder and from there we use:
sphinx-quickstart
Answer the three questions (this information can also be updated later). Make sure you say y
when asked about separating source and build. Now you should have a few files in the docs/source
folder. Most importantly, you should have a conf.py
file. This file needs to be completed:
Add these lines so that the package (situated two levels higher) is discoverable:
import os import sys sys.path.insert(0, os.path.abspath('../..'))
So that Sphinx can automatically parse code documentation, it needs additional plugins. Add this information to the file:
extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon' ]
Now we can let Sphinx parse the information from our package. In the terminal move to the level of the package folder and use:
sphinx-apidoc -o docs/source pythonaction/
(where of course you replace the package name). This generates two .rst
files that describe the contents of the documentation.
Generating the HTML¶
From this point, we could simply use Sphinx to generate the HTML from the above collected information. Instead of that, in order to include richer information, we include the docs in a Jupyter Book format. Under the hood, Jupyter Nook uses Sphinx to generate the HTML but it does it transparently for us.
What we need to do now is to setup the Jupyter Book project. First we need to install the package:
pip install jupyter-book
We only need to add two files to to base of our project: a _config.yml
file that contains settings for our project and _toc.yml
file that defines the contents of the book. You can find detailed information here. In this project, _config.yml
file is:
# Book settings
title: Pythonaction documentation
author: Guillaume Witz
sphinx:
extra_extensions: [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon'
]
(the spinx information might be redundant with that entered in the Sphinx configuration file).
The table of contents file _toc.yml
is:
- file: docs/introd
- file: docs/notebook
- file: docs/source/modules
i.e. it includes the docs/introd.md
Markdown file, the docs/notebook.ipynb
Jupyter notebook and the docs/source/modules.rst
restructured file. Note that the latter file is the one we previously created using Sphinx and which will include our entire automated docs in the final document. Finally we can generate our HTML using:
jupyter-book build .
from within our project base. Now you can open your webpage by opening _build/html/index.html
.
Publishing on GitHub¶
To automatically update and publish the documentation, we need to use GitHub-Actions and GitHub-Pages. The information that we have to include in our repository is:
the
_config.yml
and_toc.yml
filesthe contents of the
docs/source
folder (conf.py
,make.bat
,Makefile
)Any document that we added in the
docs
folder with content we want to publish (e.g. a Jupyter notebook).
GitHub actions with conda¶
Now that we have all the required files in our repository, we want to automatically update the documentation when we change the package or change the contents when we modify the _toc.yml
file. This should happen every time we do a push to the repository. This can be achieved by using GitHub-Actions, i.e. scripts that are triggered to run on the repository every time some event (such as a push) happens.
The action is mostly copied from Jupyter Book (see here) with a few modifications and it looks like this:
name: deploy-book
# Only run this when the master branch changes
on:
push:
branches:
- master
# This job installs dependencies, build the book, and pushes it to `gh-pages`
jobs:
deploy-book:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Install dependencies
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
# Install conda dependencies
- name: Install dependencies
run: |
# $CONDA is an environment variable pointing to the root of the miniconda directory
$CONDA/bin/conda env update --file environment.yml --name base
# Install pip dependencies
- name: Install dependencies
run: |
$CONDA/bin/pip install -r requirements.txt
# Show packages
- name: Show packages
run: |
$CONDA/bin/conda list
# Compile the docs
- name: Compile sphinx
run: |
$CONDA/bin/sphinx-apidoc -o docs/source pythonaction/
# Build the book
- name: Build the book
run: |
$CONDA/bin/jupyter-book build .
# Push the book's HTML to github-pages
- name: GitHub Pages action
uses: peaceiris/actions-gh-pages@v3.5.9
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./_build/html
As indicated, the action runs on a Ubuntu machine (runs-on: ubuntu-latest
) and executes the list of steps
described. In the regular GitHub-Action, necessary packages, in particular jupyter-book
, are installed via a requirements.yml
file. If we want to be able to execute notebooks that use our package as demo, we need to include all dependencies for our package (pythonaction
) in the file as well. As I usually prefer to use conda to install dependencies via an environment.yml
file, I modified the action in that perspective. First I “Install conda dependencies”. This has to be done in the (base) environment as it is hard (impossible?) to activate an other one in Gtihub-Actions. Once this is done, I also install the jupyter-book
dependencies using the requirements.yml
file.
Note
Note that we use $CONDA/bin/pip install -r requirements.txt
instead of the simple $pip install -r requirements.txt
. The reason for that is that we want to use the (base) conda environment and not the default Python installation. Otherwise, jupyter-book
won’t have access to our package that we installed via conda.
What also differs from the standard action, is that we then create the docs via Sphinx in the next block. This ensures that our docs are always up-to-date. Finally we compile the book (still via conda) and push the result to the gh-pages branch.
GitHub Pages¶
GitHub offers an automated mechanism to host a static web site. Whenever folder structure typical of a web-site is pushed to a branch of the repository specifically called gh-pages
, it will be accessible at an address of the type https://guiwitz.gtihub.io/Pythonaction (with your user name and project name). The last action in our GitHub-Action script does exactly that: push our docs built with Jupyter-book to a gh-pages
branch.