Building newsfiles for your project.
Load and manage towncrier configuration from TOML files with validation, defaults, and flexible discovery.
Load configuration from directories or specific files with automatic discovery.
def load_config_from_options(
directory: str | None,
config_path: str | None
) -> tuple[str, Config]:
"""
Load configuration from directory or file.
Args:
directory: Directory to search for config (None for current)
config_path: Explicit config file path (None for auto-discovery)
Returns:
tuple: (base_directory, config_object)
Raises:
ConfigError: If configuration is invalid or not found
"""
def load_config(directory: str) -> Config | None:
"""
Load configuration from a directory.
Args:
directory: Directory to search for towncrier configuration
Returns:
Config | None: Configuration object or None if not found
"""
def traverse_for_config(path: str | None) -> tuple[str, Config]:
"""
Search for configuration in current and parent directories.
Args:
path: Starting directory (None for current directory)
Returns:
tuple: (base_directory, config_object)
Raises:
ConfigError: If no configuration found in directory tree
"""Load and parse TOML configuration files.
def load_config_from_file(directory: str, config_file: str) -> Config:
"""
Load configuration from a specific file.
Args:
directory: Base directory for relative paths
config_file: Path to configuration file
Returns:
Config: Parsed configuration object
Raises:
ConfigError: If file cannot be loaded or parsed
"""
def load_toml_from_file(config_file: str) -> Mapping[str, Any]:
"""
Load raw TOML data from file.
Args:
config_file: Path to TOML file
Returns:
Mapping[str, Any]: Raw TOML data
Raises:
ConfigError: If file cannot be read or parsed
"""
def parse_toml(base_path: str, config: Mapping[str, Any]) -> Config:
"""
Parse TOML data into Config object.
Args:
base_path: Base directory for resolving relative paths
config: Raw TOML configuration data
Returns:
Config: Validated configuration object
Raises:
ConfigError: If configuration is invalid
"""The main configuration dataclass containing all towncrier settings.
@dataclasses.dataclass
class Config:
"""Complete towncrier configuration."""
# Required fields
sections: Mapping[str, str]
types: Mapping[str, Mapping[str, Any]]
template: str | tuple[str, str]
start_string: str
# Optional fields with defaults
package: str = ""
package_dir: str = "."
single_file: bool = True
filename: str = "NEWS.rst"
directory: str | None = None
version: str | None = None
name: str = ""
title_format: str | Literal[False] = ""
issue_format: str | None = None
underlines: Sequence[str] = ("=", "-", "~")
wrap: bool = False
all_bullets: bool = True
orphan_prefix: str = "+"
create_eof_newline: bool = True
create_add_extension: bool = True
ignore: list[str] | None = None
issue_pattern: str = ""Exception raised for configuration validation errors.
class ConfigError(ClickException):
"""Configuration validation or loading error."""
def __init__(self, *args: str, **kwargs: str):
"""
Initialize configuration error.
Args:
*args: Error message components
**kwargs: Additional error context
failing_option: The configuration option that failed
"""
self.failing_option = kwargs.get("failing_option")
super().__init__(*args)Towncrier searches for configuration files in this order:
--config parametertowncrier.toml in current/parent directoriespyproject.toml with [tool.towncrier] sectionConfiguration sections are searched in:
[tool.towncrier] in pyproject.tomltowncrier.toml[tool.towncrier]
package = "mypackage" # Package name for version detection
package_dir = "src" # Directory containing package
filename = "CHANGELOG.md" # Output changelog filename
directory = "news" # Fragment directory (alternative to package-based)template = "path/to/template.j2" # Local template file
template = "mypackage:template.j2" # Package resource template
start_string = "<!-- towncrier release notes start -->" # Insertion marker[[tool.towncrier.type]]
directory = "feature"
name = "Features"
showcontent = true
[[tool.towncrier.type]]
directory = "bugfix"
name = "Bugfixes"
showcontent = true
[[tool.towncrier.type]]
directory = "doc"
name = "Improved Documentation"
showcontent = true
[[tool.towncrier.type]]
directory = "removal"
name = "Deprecations and Removals"
showcontent = true
[[tool.towncrier.type]]
directory = "misc"
name = "Misc"
showcontent = false[[tool.towncrier.section]]
name = "" # Main section
path = ""
[[tool.towncrier.section]]
name = "Web"
path = "web"
[[tool.towncrier.section]]
name = "Core"
path = "core"title_format = "{name} {version} ({date})"
issue_format = "`#{issue} <https://github.com/user/repo/issues/{issue}>`_"
underlines = ["=", "-", "~"]
wrap = false
all_bullets = truecreate_eof_newline = true # Add newline at end of fragments
create_add_extension = true # Auto-add file extensions
orphan_prefix = "+" # Prefix for orphan fragments
ignore = ["README.rst"] # Files to ignore in fragment directoryfrom towncrier._settings import load_config_from_options
# Load from current directory
base_dir, config = load_config_from_options(
directory=None, # Current directory
config_path=None # Auto-discover config file
)
print(f"Base directory: {base_dir}")
print(f"Package: {config.package}")
print(f"Fragment types: {list(config.types.keys())}")# Load specific config file
base_dir, config = load_config_from_options(
directory="/path/to/project",
config_path="/path/to/custom-towncrier.toml"
)from towncrier._settings.load import traverse_for_config
# Search up directory tree
base_dir, config = traverse_for_config("/deep/nested/directory")# Template can be file path or package resource
if isinstance(config.template, tuple):
# Package resource: ("package.name", "template.j2")
package_name, template_name = config.template
print(f"Using template {template_name} from package {package_name}")
else:
# File path: "path/to/template.j2"
print(f"Using template file: {config.template}")"package:resource"directory and name fieldsshowcontent is optional (defaults to true)Configuration loading handles these error scenarios:
The configuration system integrates with CLI commands through the config_option_help constant:
config_option_help = (
"Pass a custom config file at FILE_PATH. "
"Default: towncrier.toml or pyproject.toml file, "
"if both files exist, the first will take precedence."
)This help text is used across all CLI commands that accept a --config option.
Install with Tessl CLI
npx tessl i tessl/pypi-towncrier