******************************* Advanced changelog layout setup ******************************* This page attempts do demonstrate one way of setting up a docs site for use with |project| 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 :doc:`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. .. code-block:: * !.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 |project|. ``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 :ref:`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. .. code-block:: rst ********* Changelog ********* Versions follow `Semantic Versioning`_ (``..``). 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_``. .. code-block:: python """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. .. code-block:: rst 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 :doc:`Sphinx static docs site generator `. It may optionally contain the minimum necessary versions of those. .. code-block:: text 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. .. _pip-tools: https://github.com/jazzband/pip-tools ``.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. .. code-block:: yaml --- 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 :ref:`settings supported by it ` are derived from the web UI. .. tip:: Having :doc:`Read the Docs ` plugged into your project it is also possible to :doc:`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. .. code-block:: rst .. 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. .. code-block:: toml [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``. .. code-block:: 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 .. code-block:: ini [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.