Towncrier
Towncrier is a utility for generating useful, summarized news files (often called changelogs) for Python projects. It operates by collecting small, individual "news fragments" that developers create for each change, compiling them into a single, formatted release notes document. This approach helps avoid merge conflicts common with single changelog files and provides a clean separation between developer logs and end-user-facing release notes. Currently at version 25.8.0, Towncrier maintains an active development pace with several releases throughout the year, adding features and supporting new Python versions.
Warnings
- breaking Python 3.8 support has been removed in Towncrier 25.8.0. Users on Python 3.8 or older will need to upgrade their Python version or use an earlier Towncrier release.
- gotcha Towncrier uses a specific configuration file precedence: `towncrier.toml` takes precedence over `pyproject.toml`. If both exist, settings in `towncrier.toml` will override those in `pyproject.toml`.
- gotcha By default, `towncrier build` will remove news fragments after successfully generating the news file. If you wish to keep the fragments (e.g., for review before manual deletion), you must use the `--keep` flag.
- gotcha When defining custom news fragment types in your configuration, using a TOML mapping (e.g., `[tool.towncrier.fragment.mytype]`) will result in fragment types being sorted alphabetically in the output. To control the order, use a TOML array of tables (`[[tool.towncrier.type]]`).
- deprecated Support for the `towncrier.ini` configuration file was removed in favor of `pyproject.toml` (and `towncrier.toml`).
Install
-
pip install towncrier
Imports
- towncrier
import subprocess subprocess.run(['towncrier', 'build'])
Quickstart
import subprocess
import os
from pathlib import Path
import shutil
def run_towncrier_quickstart():
project_root = Path("./my_project_with_news")
news_dir = project_root / "newsfragments"
news_file = project_root / "NEWS.rst"
# Clean up previous run if any
if project_root.exists():
shutil.rmtree(project_root)
project_root.mkdir()
news_dir.mkdir()
# 1. Create pyproject.toml configuration
pyproject_toml_content = '''
[project]
name = "my-project"
version = "1.0.0"
[tool.towncrier]
directory = "newsfragments"
filename = "NEWS.rst"
issue_format = "#{issue}"
'''
(project_root / "pyproject.toml").write_text(pyproject_toml_content)
# 2. Create news fragments
(news_dir / "123.feature").write_text("Added an awesome new feature.")
(news_dir / "456.bugfix").write_text("Fixed a critical bug in component X.")
(news_dir / "789.doc").write_text("Improved documentation for API Y.")
print(f"\n--- Running 'towncrier build --draft' in {project_root.name} ---")
subprocess.run(['towncrier', 'build', '--draft'], cwd=project_root, check=True)
print(f"\n--- Running 'towncrier build' in {project_root.name} ---")
# 'towncrier build' automatically removes fragments for tracked files by default (since 24.7.0)
# If you were in a real git repo, you'd then 'git add NEWS.rst' and 'git commit'
subprocess.run(['towncrier', 'build', '--yes'], cwd=project_root, check=True)
print("\n--- Generated NEWS.rst content ---")
print(news_file.read_text())
print("\n--- News fragments after build ---")
if list(news_dir.iterdir()):
print(f"Fragments still exist in {news_dir}. This might happen if they were not git-tracked or --keep was used.")
else:
print(f"No fragments found in {news_dir} (expected).")
# Clean up
shutil.rmtree(project_root)
run_towncrier_quickstart()