CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-towncrier

Building newsfiles for your project.

Overview
Eval results
Files

fragments.mddocs/

Fragment Processing

Core functionality for discovering, parsing, and rendering news fragments into formatted changelog entries.

Capabilities

Fragment Discovery

Find and load news fragment files from the filesystem.

def find_fragments(
    base_directory: str,
    config: Config,
    strict: bool = False
) -> tuple[dict[str, dict[tuple[str, str, int], str]], list[tuple[str, str]]]:
    """
    Find and load news fragment files.
    
    Args:
        base_directory: Base directory to search from
        config: Configuration object with fragment settings
        strict: If True, fail on invalid fragment filenames
        
    Returns:
        tuple: (fragment_contents, fragment_files)
        - fragment_contents: Dict mapping section -> {(issue, type, counter): content}
        - fragment_files: List of (filename, category) tuples
    """

Fragment Parsing

Parse fragment filenames to extract issue, type, and counter information.

def parse_newfragment_basename(
    basename: str,
    frag_type_names: Iterable[str]
) -> tuple[str, str, int] | tuple[None, None, None]:
    """
    Parse a news fragment basename into components.
    
    Args:
        basename: Fragment filename without path
        frag_type_names: Valid fragment type names
        
    Returns:
        tuple: (issue, category, counter) or (None, None, None) if invalid
        
    Examples:
        "123.feature" -> ("123", "feature", 0)
        "456.bugfix.2" -> ("456", "bugfix", 2)
        "fix-1.2.3.feature" -> ("fix-1.2.3", "feature", 0)
        "+abc123.misc" -> ("+abc123", "misc", 0)
    """

Fragment Organization

Split fragments by type and organize for rendering.

def split_fragments(
    fragment_contents: dict[str, dict[tuple[str, str, int], str]],
    types: Mapping[str, Mapping[str, Any]],
    all_bullets: bool = True
) -> dict[str, dict[str, list[tuple[str, list[str]]]]]:
    """
    Split and organize fragments by section and type.
    
    Args:
        fragment_contents: Raw fragment content by section
        types: Fragment type configuration
        all_bullets: Whether to format all entries as bullet points
        
    Returns:
        dict: Organized fragments as {section: {type: [(issue, [content_lines])]}}
    """

Fragment Rendering

Render organized fragments into final changelog format.

def render_fragments(
    template: str,
    issue_format: str | None,
    fragments: Mapping[str, Mapping[str, Mapping[str, Sequence[str]]]],
    definitions: Mapping[str, Mapping[str, Any]],
    underlines: Sequence[str],
    wrap: bool,
    versiondata: Mapping[str, str],
    top_underline: str = "=",
    all_bullets: bool = False,
    render_title: bool = True,
    md_header_level: int = 1
) -> str:
    """
    Render fragments using Jinja2 template.
    
    Args:
        template: Jinja2 template string for formatting
        issue_format: Optional format string for issue links
        fragments: Organized fragment data by section/type
        definitions: Type definitions and formatting rules
        underlines: Sequence of characters for section underlining
        wrap: Whether to wrap text output
        versiondata: Version information for template variables
        top_underline: Character for top-level section underlines
        all_bullets: Whether to use bullets for all content
        render_title: Whether to render the release title
        md_header_level: Header level for Markdown output
        
    Returns:
        Formatted changelog content as string
    """

News File Writing

Write rendered content to news files with proper insertion and existing content handling.

def append_to_newsfile(
    directory: str,
    filename: str,
    start_string: str,
    top_line: str,
    content: str,
    single_file: bool
) -> None:
    """
    Write content to directory/filename behind start_string.
    
    Args:
        directory: Directory containing the news file
        filename: Name of the news file
        start_string: Marker string for content insertion
        top_line: Release header line to check for duplicates
        content: Rendered changelog content to insert
        single_file: Whether to append to existing file or create new
        
    Raises:
        ValueError: If top_line already exists in the file
    """

def _figure_out_existing_content(
    news_file: Path,
    start_string: str,
    single_file: bool
) -> tuple[str, str]:
    """
    Split existing news file into header and body parts.
    
    Args:
        news_file: Path to the news file
        start_string: Marker string to split on
        single_file: Whether this is a single file or per-release
        
    Returns:
        tuple: (header_content, existing_body_content)
    """

Fragment Path Utilities

FragmentsPath Class

Helper class for managing fragment directory paths.

class FragmentsPath:
    """
    Helper for getting full paths to fragment directories.
    
    Handles both explicit directory configuration and package-based 
    fragment location resolution.
    """
    
    def __init__(self, base_directory: str, config: Config):
        """
        Initialize path helper.
        
        Args:
            base_directory: Base project directory
            config: Configuration object
        """
    
    def __call__(self, section_directory: str = "") -> str:
        """
        Get fragment directory path for a section.
        
        Args:
            section_directory: Section subdirectory name
            
        Returns:
            str: Full path to fragment directory
        """

Sorting and Organization

Issue Sorting

class IssueParts(NamedTuple):
    """Components of an issue identifier for sorting."""
    prefix: str
    number: int | None
    suffix: str

def issue_key(issue: str) -> IssueParts:
    """
    Generate sort key for issue identifiers.
    
    Args:
        issue: Issue identifier string
        
    Returns:
        IssueParts: Sortable components
        
    Examples:
        "123" -> IssueParts("", 123, "")
        "issue-456" -> IssueParts("issue-", 456, "")
        "+orphan" -> IssueParts("+", None, "orphan")
    """

Entry Sorting

def entry_key(entry: tuple[str, Sequence[str]]) -> tuple[str, list[IssueParts]]:
    """
    Generate sort key for changelog entries.
    
    Args:
        entry: (issue_list, content_lines) tuple
        
    Returns:
        tuple: (concatenated_issues, parsed_issue_parts)
    """

def bullet_key(entry: tuple[str, Sequence[str]]) -> int:
    """
    Generate sort key for bullet point entries.
    
    Args:
        entry: (issue_list, content_lines) tuple
        
    Returns:
        int: Number of lines in entry (for consistent ordering)
    """

Content Processing

Text Formatting

def indent(text: str, prefix: str) -> str:
    """
    Indent text lines with given prefix.
    
    Args:
        text: Text to indent
        prefix: Prefix to add to each line
        
    Returns:
        str: Indented text
    """

def append_newlines_if_trailing_code_block(text: str) -> str:
    """
    Add newlines after trailing code blocks for proper formatting.
    
    Args:
        text: Text content to process
        
    Returns:
        str: Text with proper code block spacing
    """

Issue Reference Rendering

def render_issue(issue_format: str | None, issue: str) -> str:
    """
    Format issue references according to configuration.
    
    Args:
        issue_format: Format string with {issue} placeholder
        issue: Issue identifier
        
    Returns:
        str: Formatted issue reference
        
    Examples:
        render_issue("#{issue}", "123") -> "#123"
        render_issue("`#{issue} <url/{issue}>`_", "456") -> "`#456 <url/456>`_"
    """

Usage Examples

Basic Fragment Processing

from towncrier._builder import find_fragments, split_fragments, render_fragments
from towncrier._settings import load_config_from_options

# Load configuration
base_directory, config = load_config_from_options(None, None)

# Find fragments
fragment_contents, fragment_files = find_fragments(
    base_directory=base_directory,
    config=config,
    strict=True
)

# Organize fragments
fragments = split_fragments(
    fragment_contents=fragment_contents,
    types=config.types,
    all_bullets=config.all_bullets
)

# Render to string
template = "# Version {version}\n\n{% for section in sections %}..."
rendered = render_fragments(
    fragments=fragments,
    config=config,
    template=template,
    project_name="My Project",
    project_version="1.0.0",
    project_date="2024-01-15"
)

Custom Fragment Directory

from towncrier._builder import FragmentsPath

# Create path helper
fragments_path = FragmentsPath(
    base_directory="/path/to/project",
    config=config
)

# Get fragment directories
main_fragments_dir = fragments_path()  # Main section
api_fragments_dir = fragments_path("api")  # API section

Fragment Filename Parsing

from towncrier._builder import parse_newfragment_basename

fragment_types = ["feature", "bugfix", "doc", "removal", "misc"]

# Parse various fragment names
result1 = parse_newfragment_basename("123.feature", fragment_types)
# -> ("123", "feature", 0)

result2 = parse_newfragment_basename("fix-auth.bugfix.2", fragment_types)  
# -> ("fix-auth", "bugfix", 2)

result3 = parse_newfragment_basename("+orphan.misc", fragment_types)
# -> ("+orphan", "misc", 0)

Template Variables

When rendering fragments, these variables are available in Jinja2 templates:

  • name: Project name
  • version: Project version
  • date: Project date
  • sections: Dictionary of section data
  • underlines: RST underline characters
  • fragments: Organized fragment data

Error Handling

Fragment processing handles these error scenarios:

  • Invalid fragment filenames: Optionally strict validation
  • Missing fragment directories: Graceful fallback
  • Template rendering errors: Jinja2 syntax or variable errors
  • File encoding issues: UTF-8 encoding problems
  • Content parsing errors: Malformed fragment content

Install with Tessl CLI

npx tessl i tessl/pypi-towncrier

docs

build.md

check.md

configuration.md

create.md

fragments.md

index.md

project.md

vcs.md

tile.json