Advanced changelog layout setup¶
This page attempts do demonstrate one way of setting up a docs site for use with sphinxcontrib-towncrier and includes a few opinionated integration solutions. But of course, this layout may not fit everybody’s needs. When configuring your project, try to figure out what works for you — you don’t have to follow everything laid out here blindly.
Project structure¶
The author likes the following project directory layout:
{{ project_root }}/
│
├─ docs/
│ ├─ changelog.d/
│ │ │─ .gitignore
│ │ │─ .towncrier-template.rst.j2
│ │ │─ {{ issue_number }}.{{ changelog_fragment_type }}.rst
│ │ │─ ...
│ │ └─ README.rst
│ ├─ changelog.rst
│ ├─ conf.py
│ ├─ index.rst
│ ├─ requirements.in
│ └─ requirements.txt
├─ src/
│ └─ {{ python_importable_name }}/
│ └─ ...
├─ .readthedocs.yml
├─ CHANGELOG.rst
├─ pyproject.toml
├─ README.rst
├─ tox.ini
└─ ...
This is an src
-layout project with a Python package located at
src/{{ python_importable_name }}/
but we won’t touch this topic.
There are several automation, configuration, documentation and metadata
files in the project root that will be described later on this page.
Finally, a Sphinx-based site is located under
the docs/
.
The rest of this page will describe what to have in each of those files.
docs/changelog.d/
¶
Let’s start with the docs/changelog.d/
. This is a folder where the
end-users are supposed to add their changelog fragments for Towncrier to
consume.
docs/changelog.d/.gitignore
¶
First, let’s make sure Git only tracks files that we want there by adding
a .gitignore
file in this folder. First thing, it adds everything to
“ignore” but then allows .gitignore
, .gitignore
, README.rst
and any RST documents matching Towncrier change note fragment naming
convention.
*
!.gitignore
!.towncrier-template.rst.j2
!*.*.rst
!README.rst
docs/changelog.d/.towncrier-template.rst.j2
¶
Then, there’s .towncrier-template.rst.j2
. It’s a changelog template,
for Towncrier to use. It can be copied from
https://github.com/twisted/towncrier/tree/master/src/towncrier/templates.
This name is set in pyproject.toml
in the project root.
docs/changelog.d/{{ issue_number }}.{{ changelog_fragment_type }}.rst
¶
These are changelog fragments in RST format. They are absorbed by Towncrier during the release and before that, these files will be used in the preview generated by sphinxcontrib-towncrier.
docs/changelog.d/README.rst
¶
This README.rst
file would normally contain — a guide for the
contributors on how to write change notes. For example, setuptools has a
useful write-up on authoring changelog fragments. It is useful to have it
in this place so that it shows up on GitHub when the users navigate to
the folder with the fragments via the web UI.
docs/
¶
docs/changelog.rst
¶
This is a Sphinx page that contains both the future version changelog
preview via .. towncrier-draft-entries::
directive and the changelog
for all already released versions that is managed by Towncrier in a
separate RST document CHANGELOG.rst
in the project root.
*********
Changelog
*********
Versions follow `Semantic Versioning`_ (``<major>.<minor>.<patch>``).
Backward incompatible (breaking) changes will only be introduced in major
versions with advance notice in the **Deprecations** section of releases.
.. _Semantic Versioning: https://semver.org/
.. towncrier-draft-entries:: |release| [UNRELEASED DRAFT] as on |today|
.. include:: ../CHANGELOG.rst
docs/conf.py
¶
The Sphinx configuration demonstrates how to keep the version
information known to Sphinx in sync with the Git tag based metadata.
Note the exclusion of docs/changelog.d/
and the settings prefixed
with towncrier_draft_
.
"""Configuration for the Sphinx documentation generator."""
from functools import partial
from pathlib import Path
from setuptools_scm import get_version
# -- Path setup --------------------------------------------------------------
PROJECT_ROOT_DIR = Path(__file__).parents[1].resolve()
get_scm_version = partial(get_version, root=PROJECT_ROOT_DIR)
# -- Project information -----------------------------------------------------
github_url = 'https://github.com'
github_repo_org = 'your-org'
github_repo_name = 'your-project'
github_repo_slug = f'{github_repo_org}/{github_repo_name}'
github_repo_url = f'{github_url}/{github_repo_slug}'
github_sponsors_url = f'{github_url}/sponsors'
project = github_repo_name
author = f'{project} Contributors'
copyright = f'2021, {author}'
# The short X.Y version
version = '.'.join(
get_scm_version(
local_scheme='no-local-version',
).split('.')[:3],
)
# The full version, including alpha/beta/rc tags
release = get_scm_version()
rst_epilog = f"""
.. |project| replace:: {project}
"""
# -- General configuration ---------------------------------------------------
extensions = [
# Built-in extensions:
'sphinx.ext.extlinks',
'sphinx.ext.intersphinx',
# Third-party extensions:
'sphinxcontrib.towncrier', # provides `towncrier-draft-entries` directive
]
exclude_patterns = [
'_build', 'Thumbs.db', '.DS_Store', # <- Defaults
'changelog.d/**', # Towncrier-managed change notes
]
# -- Options for HTML output -------------------------------------------------
html_theme = 'furo'
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'rtd': ('https://docs.rtfd.io/en/stable', None),
'sphinx': ('https://www.sphinx-doc.org/en/master', None),
}
# -- Options for extlinks extension ------------------------------------------
extlinks = {
'issue': (f'{github_repo_url}/issues/%s', '#'),
'pr': (f'{github_repo_url}/pull/%s', 'PR #'),
'commit': (f'{github_repo_url}/commit/%s', ''),
'gh': (f'{github_url}/%s', 'GitHub: '),
'user': (f'{github_sponsors_url}/%s', '@'),
}
# -- Options for towncrier_draft extension -----------------------------------
towncrier_draft_autoversion_mode = 'draft' # or: 'sphinx-version', 'sphinx-release'
towncrier_draft_include_empty = True
towncrier_draft_working_directory = PROJECT_ROOT_DIR
# Not yet supported: towncrier_draft_config_path = 'pyproject.toml' # relative to cwd
# -- Strict mode -------------------------------------------------------------
default_role = 'any'
nitpicky = True
docs/index.rst
¶
The root document includes most of the README excluding one badge and its title. It allows to flexibly control what information goes to the PyPI and GitHub repo pages and what appears in the docs.
This document must contain a .. toctree::
directive that has a
pointer to the changelog
document in the list.
Welcome to |project|'s documentation!
=====================================
.. include:: ../README.rst
:end-before: DO-NOT-REMOVE-docs-badges-END
.. include:: ../README.rst
:start-after: DO-NOT-REMOVE-docs-intro-START
.. toctree::
:maxdepth: 2
:caption: Contents:
changelog
docs/requirements.in
¶
requirements.in
is a standard requirements.txt
-type file that
only lists dependencies that are directly used by the Sphinx
static docs site generator. It may optionally contain
the minimum necessary versions of those.
furo
setuptools-scm
Sphinx
sphinxcontrib-towncrier
docs/requirements.txt
¶
But stating just the direct dependencies without strict version
restrictions is not enough for reproducible builds. Since it is
important to keep the docs build predictable over time, we use
pip-tools to generate a constraints.txt
-type pip-compatible
lockfile with pinned version constraints for the whole transitive
dependency tree. This file is requirements.txt
and using it will
ensure that the virtualenv for building the docs always has the same
software with the same versions in it.
Tip
As a bonus, having a .in
+ .txt
pair of files is natively
supported by GitHub Dependabot.
.readthedocs.yml
¶
To set up Read the Docs, add a .readthedocs.yml
file in the project
root. The following configuration makes sure that the lockfile is used
to provision the build env. It also configures how Sphinx should behave
like failing the build on any warnings and having nice URLs.
---
version: 2
formats: all
sphinx:
builder: dirhtml
configuration: docs/conf.py
fail_on_warning: true
build:
image: latest
python:
version: 3.8
install:
- requirements: docs/requirements.txt
...
Note
When you have a Read the Docs YAML config in your repository, none of the settings supported by it are derived from the web UI.
Tip
Having Read the Docs plugged into your project it is also possible to enable pull-request builds.
CHANGELOG.rst
¶
This file in the project root contains the compiled changelog with the notes from the released project versions. It is managed by Towncrier and should not be edited by you manually.
.. towncrier release notes start
pyproject.toml
¶
pyproject.toml
in the root contains the setup for Towncrier itself
under the [tool.towncrier]
section. It binds it all together
pointing at the directory for the change notes, the target changelog
document and the template to use when generating it. It also lists the
categories for the change fragments.
[tool.towncrier]
directory = "docs/changelog.d/"
filename = "CHANGELOG.rst"
issue_format = ":issue:`{issue}`"
package_dir = "src"
template = "docs/changelog.d/.towncrier-template.rst.j2"
title_format = "v{version} ({project_date})"
underlines = ["=", "^", "-", "~"]
[[tool.towncrier.section]]
path = ""
[[tool.towncrier.type]]
directory = "bugfix"
name = "Bugfixes"
showcontent = true
[[tool.towncrier.type]]
directory = "feature"
name = "Features"
showcontent = true
[[tool.towncrier.type]]
directory = "deprecation"
name = "Deprecations (removal in next major release)"
showcontent = true
[[tool.towncrier.type]]
directory = "breaking"
name = "Backward incompatible changes"
showcontent = true
[[tool.towncrier.type]]
directory = "doc"
name = "Documentation"
showcontent = true
[[tool.towncrier.type]]
directory = "misc"
name = "Miscellaneous"
showcontent = true
README.rst
¶
The README document is an important bit of your project. It shows up on GitHub and is normally shown on PyPI. Besides that, it’s possible to include its fragments into the docs front page.
The example below shows how to use comment markers to include a part of
the badges into a Sphinx document also embedding some prose from the
README. Scroll up and see how it’s being embedded into
docs/index.rst
.
.. image:: https://img.shields.io/pypi/v/your-project.svg?logo=Python&logoColor=white
:target: https://pypi.org/project/your-project
:alt: your-project @ PyPI
.. image:: https://github.com/your-org/your-project/actions/workflows/tox-tests.yaml/badge.svg?event=push
:target: https://github.com/your-org/your-project/actions/workflows/tox-tests.yaml
:alt: GitHub Actions CI/CD build status
.. DO-NOT-REMOVE-docs-badges-END
.. image:: https://img.shields.io/readthedocs/your-project/latest.svg?logo=Read%20The%20Docs&logoColor=white
:target: https://your-project.rtfd.io/en/latest/?badge=latest
:alt: Documentation Status @ RTD
your-project
============
.. DO-NOT-REMOVE-docs-intro-START
A project with Sphinx-managed documentation and description sourced
from this README.
tox.ini
¶
This is an example of setting up a tox-based Sphinx invocation
[tox]
envlist = python
isolated_build = true
minversion = 3.21.0
[testenv:docs]
basepython = python3
deps =
-r{toxinidir}{/}docs{/}requirements.txt
description = Build The Docs
commands =
# Retrieve possibly missing commits:
-git fetch --unshallow
-git fetch --tags
# Build the html docs with Sphinx:
{envpython} -m sphinx \
-j auto \
-b html \
{tty:--color} \
-a \
-n \
-W --keep-going \
-d "{temp_dir}{/}.doctrees" \
{posargs:} \
. \
"{envdir}{/}docs_out"
# Print out the output docs dir and a way to serve html:
-{envpython} -c\
'import pathlib;\
docs_dir = pathlib.Path(r"{envdir}") / "docs_out";\
index_file = docs_dir / "index.html";\
print("\n" + "=" * 120 +\
f"\n\nDocumentation available under:\n\n\
\tfile://\{index_file\}\n\nTo serve docs, use\n\n\
\t$ python3 -m http.server --directory \
\N\{QUOTATION MARK\}\{docs_dir\}\N\{QUOTATION MARK\} 0\n\n" +\
"=" * 120)'
changedir = {toxinidir}{/}docs
isolated_build = true
passenv =
SSH_AUTH_SOCK
skip_install = true
whitelist_externals =
git
With this setup, run tox -e docs
to build the site locally. Integrate
the same command in your CI.