CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-beets

A comprehensive music library management system and command-line application for organizing and maintaining digital music collections

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

user-interface.mddocs/

User Interface

Command-line interface utilities, user interaction functions, output formatting, and command infrastructure for building interactive music management tools. The UI system provides the foundation for beets' CLI and enables plugins to create rich interactive experiences.

Capabilities

Input Functions

Functions for getting user input with proper Unicode handling and validation.

def input_(prompt: str = None) -> str:
    """
    Get user input with Unicode handling and error checking.
    
    Parameters:
    - prompt: Optional prompt string to display
    
    Returns:
    User input as Unicode string
    
    Raises:
    UserError: If stdin is not available (e.g., in non-interactive mode)
    """

def input_options(options: List[str], require: bool = False, prompt: str = None, 
                 fallback_prompt: str = None, numrange: Tuple[int, int] = None, 
                 default: str = None, max_width: int = 72) -> str:
    """
    Prompt user to choose from a list of options.
    
    Parameters:
    - options: List of option strings (capitalize letters for shortcuts)
    - require: If True, user must make a choice (no default)
    - prompt: Custom prompt string
    - fallback_prompt: Prompt shown for invalid input
    - numrange: Optional (low, high) tuple for numeric input
    - default: Default option if user presses enter
    - max_width: Maximum prompt width for wrapping
    
    Returns:
    Single character representing chosen option
    """

def input_yn(prompt: str, require: bool = False) -> bool:
    """
    Prompt user for yes/no response.
    
    Parameters:
    - prompt: Question to ask user
    - require: If True, user must explicitly choose (no default)
    
    Returns:
    True for yes, False for no
    """

def input_select_objects(prompt: str, objs: List[Any], rep: Callable, 
                        prompt_all: str = None) -> List[Any]:
    """
    Let user select from a list of objects.
    
    Parameters:
    - prompt: Action prompt (e.g., "Delete item")
    - objs: List of objects to choose from
    - rep: Function to display each object
    - prompt_all: Optional prompt for the initial all/none/select choice
    
    Returns:
    List of selected objects
    """

Output Functions

Functions for safe, formatted output with encoding and colorization support.

def print_(*strings: List[str], **kwargs) -> None:
    """
    Print strings with safe Unicode encoding handling.
    
    Parameters:
    - *strings: String arguments to print (must be Unicode)
    - end: String to append (defaults to newline)
    
    Notes:
    Handles encoding errors gracefully and respects terminal capabilities
    """

def colorize(color_name: str, text: str) -> str:
    """
    Apply color formatting to text for terminal display.
    
    Parameters:
    - color_name: Color name from configuration (e.g., 'text_error', 'text_success')
    - text: Text to colorize
    
    Returns:
    Text with ANSI color codes (if color is enabled)
    """

def uncolorize(colored_text: str) -> str:
    """
    Remove ANSI color codes from text.
    
    Parameters:
    - colored_text: Text potentially containing ANSI codes
    
    Returns:
    Plain text with color codes stripped
    """

def colordiff(a: Any, b: Any) -> Tuple[str, str]:
    """
    Highlight differences between two values.
    
    Parameters:
    - a: First value for comparison
    - b: Second value for comparison
    
    Returns:
    Tuple of (colored_a, colored_b) with differences highlighted
    """

Formatting Functions

Functions for human-readable formatting of various data types.

def human_bytes(size: int) -> str:
    """
    Format byte count in human-readable format.
    
    Parameters:
    - size: Size in bytes
    
    Returns:
    Formatted string (e.g., "1.5 MB", "832 KB")
    """

def human_seconds(interval: float) -> str:
    """
    Format time interval in human-readable format.
    
    Parameters:
    - interval: Time interval in seconds
    
    Returns:
    Formatted string (e.g., "3.2 minutes", "1.5 hours")
    """

def human_seconds_short(interval: float) -> str:
    """
    Format time interval in short M:SS format.
    
    Parameters:
    - interval: Time interval in seconds
    
    Returns:
    Formatted string (e.g., "3:45", "12:03")
    """

Display Functions

Functions for displaying model changes and file operations.

def show_model_changes(new: Model, old: Model = None, fields: List[str] = None, 
                      always: bool = False) -> bool:
    """
    Display changes between model objects.
    
    Parameters:
    - new: Updated model object
    - old: Original model object (uses pristine version if None)
    - fields: Specific fields to show (all fields if None)
    - always: Show object even if no changes found
    
    Returns:
    True if changes were found and displayed
    """

def show_path_changes(path_changes: List[Tuple[str, str]]) -> None:
    """
    Display file path changes in formatted layout.
    
    Parameters:
    - path_changes: List of (source_path, dest_path) tuples
    
    Notes:
    Automatically chooses single-line or multi-line layout based on path lengths
    """

Command Infrastructure

Classes for building CLI commands and option parsing.

class Subcommand:
    """Represents a CLI subcommand that can be invoked by beets."""
    
    def __init__(self, name: str, parser: OptionParser = None, help: str = "", 
                 aliases: List[str] = (), hide: bool = False):
        """
        Create a new subcommand.
        
        Parameters:
        - name: Primary command name
        - parser: OptionParser for command options (creates default if None)
        - help: Help text description
        - aliases: Alternative names for the command
        - hide: Whether to hide from help output
        """
    
    func: Callable[[Library, optparse.Values, List[str]], Any]
    """Function to execute when command is invoked."""
    
    def parse_args(self, args: List[str]) -> Tuple[optparse.Values, List[str]]:
        """
        Parse command-line arguments for this command.
        
        Parameters:
        - args: List of argument strings
        
        Returns:
        Tuple of (options, remaining_args)
        """

class CommonOptionsParser(optparse.OptionParser):
    """Enhanced OptionParser with common beets options."""
    
    def add_album_option(self, flags: Tuple[str, str] = ("-a", "--album")) -> None:
        """Add -a/--album option to match albums instead of tracks."""
    
    def add_path_option(self, flags: Tuple[str, str] = ("-p", "--path")) -> None:
        """Add -p/--path option to display paths instead of formatted output."""
    
    def add_format_option(self, flags: Tuple[str, str] = ("-f", "--format"), 
                         target: str = None) -> None:
        """Add -f/--format option for custom output formatting."""
    
    def add_all_common_options(self) -> None:
        """Add album, path, and format options together."""

class SubcommandsOptionParser(CommonOptionsParser):
    """OptionParser that handles multiple subcommands."""
    
    def add_subcommand(self, *cmds: Subcommand) -> None:
        """Add one or more subcommands to the parser."""
    
    def parse_subcommand(self, args: List[str]) -> Tuple[Subcommand, optparse.Values, List[str]]:
        """
        Parse arguments and return the invoked subcommand.
        
        Parameters:
        - args: Command-line arguments
        
        Returns:
        Tuple of (subcommand, options, subcommand_args)
        """

Configuration Helpers

Functions for handling UI-related configuration and decisions.

def should_write(write_opt: bool = None) -> bool:
    """
    Determine if metadata should be written to files.
    
    Parameters:
    - write_opt: Explicit write option (uses config if None)
    
    Returns:
    True if files should be written
    """

def should_move(move_opt: bool = None) -> bool:
    """
    Determine if files should be moved after metadata updates.
    
    Parameters:
    - move_opt: Explicit move option (uses config if None)
    
    Returns:
    True if files should be moved
    """

def get_path_formats(subview: dict = None) -> List[Tuple[str, Template]]:
    """
    Get path format templates from configuration.
    
    Parameters:
    - subview: Optional config subview (uses paths config if None)
    
    Returns:
    List of (query, template) tuples
    """

def get_replacements() -> List[Tuple[re.Pattern, str]]:
    """
    Get character replacement rules from configuration.
    
    Returns:
    List of (regex_pattern, replacement) tuples
    """

Terminal Utilities

Functions for terminal interaction and layout.

def term_width() -> int:
    """
    Get terminal width in columns.
    
    Returns:
    Terminal width or configured fallback value
    """

def indent(count: int) -> str:
    """
    Create indentation string.
    
    Parameters:
    - count: Number of spaces for indentation
    
    Returns:
    String with specified number of spaces
    """

Command Development Examples

Basic Command Plugin

from beets.ui import Subcommand, print_
from beets.plugins import BeetsPlugin

class StatsCommand(Subcommand):
    """Command to show library statistics."""
    
    def __init__(self):
        super().__init__('stats', help='show library statistics')
        self.func = self.run
    
    def run(self, lib, opts, args):
        """Execute the stats command."""
        total_items = len(lib.items())
        total_albums = len(lib.albums())
        
        print_(f"Library contains:")
        print_(f"  {total_items} tracks")
        print_(f"  {total_albums} albums")

class StatsPlugin(BeetsPlugin):
    def commands(self):
        return [StatsCommand()]

Interactive Command with Options

from beets.ui import Subcommand, input_yn, input_options, show_model_changes
from beets.ui import CommonOptionsParser

class CleanupCommand(Subcommand):
    """Command with interactive cleanup options."""
    
    def __init__(self):
        parser = CommonOptionsParser()
        parser.add_option('-n', '--dry-run', action='store_true',
                         help='show what would be done without making changes')
        parser.add_album_option()
        
        super().__init__('cleanup', parser=parser, 
                        help='interactive library cleanup')
        self.func = self.run
    
    def run(self, lib, opts, args):
        """Execute cleanup with user interaction."""
        # Find items with potential issues
        problematic = lib.items('genre:')  # Items with no genre
        
        if not problematic:
            print_("No cleanup needed!")
            return
        
        print_(f"Found {len(problematic)} items without genre")
        
        if opts.dry_run:
            for item in problematic:
                print_(f"Would process: {item}")
            return
        
        # Interactive processing
        action = input_options(['fix', 'skip', 'quit'], 
                             prompt="Fix genres automatically?")
        
        if action == 'f':  # fix
            for item in problematic:
                item.genre = 'Unknown'
                if show_model_changes(item):
                    if input_yn(f"Save changes to {item}?"):
                        item.store()
        elif action == 'q':  # quit
            return

Command with Custom Formatting

from beets.ui import Subcommand, print_, colorize
from beets.ui import CommonOptionsParser

class ListCommand(Subcommand):
    """Enhanced list command with custom formatting."""
    
    def __init__(self):
        parser = CommonOptionsParser()
        parser.add_all_common_options()
        parser.add_option('--count', action='store_true',
                         help='show count instead of items')
        
        super().__init__('mylist', parser=parser,
                        help='list items with custom formatting')
        self.func = self.run
    
    def run(self, lib, opts, args):
        """Execute custom list command."""
        query = ' '.join(args) if args else None
        
        if hasattr(opts, 'album') and opts.album:
            objs = lib.albums(query)
            obj_type = "albums"
        else:
            objs = lib.items(query)
            obj_type = "items"
        
        if opts.count:
            print_(f"{len(objs)} {obj_type}")
            return
        
        # Custom formatting
        for obj in objs:
            if hasattr(obj, 'album'):  # Item
                artist = colorize('text_highlight', obj.artist)
                title = colorize('text_success', obj.title)
                print_(f"{artist} - {title}")
            else:  # Album
                artist = colorize('text_highlight', obj.albumartist)
                album = colorize('text_success', obj.album)
                year = f" ({obj.year})" if obj.year else ""
                print_(f"{artist} - {album}{year}")

Form-based Input

from beets.ui import input_, input_options, input_select_objects

def interactive_import_session():
    """Example of complex user interaction."""
    
    # Get import directory
    directory = input_("Enter directory to import: ")
    
    # Get import options
    write_tags = input_yn("Write tags to files?", require=False)
    copy_files = input_yn("Copy files (vs. move)?", require=False)
    
    # Choose import strategy
    strategy = input_options(
        ['automatic', 'manual', 'skip-existing'],
        prompt="Choose import strategy",
        default='automatic'
    )
    
    print_(f"Importing from: {directory}")
    print_(f"Write tags: {write_tags}")
    print_(f"Copy files: {copy_files}")
    print_(f"Strategy: {strategy}")

def select_items_for_action(lib):
    """Example of object selection UI."""
    
    # Get all untagged items
    untagged = list(lib.items('artist:'))
    
    if not untagged:
        print_("No untagged items found")
        return
    
    # Let user select items to process
    def show_item(item):
        print_(f"  {item.path}")
    
    selected = input_select_objects(
        "Tag item", 
        untagged,
        show_item,
        "Tag all untagged items"
    )
    
    print_(f"Selected {len(selected)} items for tagging")
    return selected

Color Configuration

Color Names

# Standard color names used by beets
COLOR_NAMES = [
    'text_success',           # Success messages
    'text_warning',           # Warning messages  
    'text_error',             # Error messages
    'text_highlight',         # Important text
    'text_highlight_minor',   # Secondary highlights
    'action_default',         # Default action in prompts
    'action',                 # Action text in prompts
    'text',                   # Normal text
    'text_faint',            # Subdued text
    'import_path',           # Import path displays
    'import_path_items',     # Import item paths
    'action_description',    # Action descriptions
    'added',                 # Added content
    'removed',               # Removed content
    'changed',               # Changed content
    'added_highlight',       # Highlighted additions
    'removed_highlight',     # Highlighted removals
    'changed_highlight',     # Highlighted changes
    'text_diff_added',       # Diff additions
    'text_diff_removed',     # Diff removals
    'text_diff_changed',     # Diff changes
]

Color Usage Examples

from beets.ui import colorize, print_

# Status messages
print_(colorize('text_success', "Import completed successfully"))
print_(colorize('text_warning', "Some files could not be processed"))
print_(colorize('text_error', "Database connection failed"))

# Highlighted content
artist = colorize('text_highlight', item.artist)
title = colorize('text_highlight_minor', item.title)
print_(f"{artist} - {title}")

# Action prompts  
action = colorize('action_default', "[A]pply")
description = colorize('action_description', "apply changes")
print_(f"{action} {description}")

Exception Handling

class UserError(Exception):
    """Exception for user-facing error messages."""
    
    def __init__(self, message: str):
        """
        Initialize user error.
        
        Parameters:
        - message: Human-readable error message
        """
        super().__init__(message)

Error Handling Examples

from beets.ui import UserError, print_, colorize

def safe_command_execution(lib, opts, args):
    """Example of proper error handling in commands."""
    
    try:
        # Command logic here
        result = perform_operation(lib, args)
        print_(colorize('text_success', f"Operation completed: {result}"))
        
    except UserError as e:
        print_(colorize('text_error', f"Error: {e}"))
        return 1
        
    except KeyboardInterrupt:
        print_(colorize('text_warning', "\nOperation cancelled by user"))
        return 1
        
    except Exception as e:
        print_(colorize('text_error', f"Unexpected error: {e}"))
        return 1
    
    return 0

This comprehensive UI system enables the creation of rich, interactive command-line tools that integrate seamlessly with beets' existing interface patterns and user experience conventions.

Install with Tessl CLI

npx tessl i tessl/pypi-beets

docs

configuration.md

import-autotag.md

index.md

library-management.md

plugin-system.md

query-system.md

user-interface.md

utilities-templates.md

tile.json