A comprehensive music library management system and command-line application for organizing and maintaining digital music collections
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Global configuration management with YAML-based settings, environment variable support, and plugin configuration integration. The configuration system provides flexible, hierarchical settings management for all aspects of beets functionality.
The central configuration instance that manages all beets settings.
config: IncludeLazyConfig # Global configuration object available as beets.config
class IncludeLazyConfig:
"""Configuration class with lazy loading and include support."""
def read(self, user: bool = True, defaults: bool = True) -> None:
"""
Read configuration from files.
Parameters:
- user: Whether to read user configuration file
- defaults: Whether to read default configuration
"""
def set_file(self, filename: str) -> None:
"""
Load additional configuration from file.
Parameters:
- filename: Path to YAML configuration file
"""
def set_args(self, args: argparse.Namespace) -> None:
"""
Set configuration from command-line arguments.
Parameters:
- args: Parsed command-line arguments
"""
def __getitem__(self, key: str) -> ConfigView:
"""
Get configuration section or value.
Parameters:
- key: Configuration key or section name
Returns:
ConfigView object for further access
"""
def user_config_path(self) -> str:
"""
Get path to user configuration file.
Returns:
Path to user's config.yaml file
"""
def config_dir(self) -> str:
"""
Get beets configuration directory path.
Returns:
Path to beets configuration directory
"""Objects representing configuration sections and values with type conversion.
class ConfigView:
"""View into configuration hierarchy with type conversion."""
def get(self, typ: Type = None) -> Any:
"""
Get configuration value with optional type conversion.
Parameters:
- typ: Type to convert value to (str, int, bool, list, dict)
Returns:
Configuration value converted to specified type
"""
def as_str(self) -> str:
"""
Get configuration value as string.
Returns:
String representation of configuration value
"""
def as_filename(self) -> str:
"""
Get configuration value as filename with path expansion.
Returns:
Expanded filename path
"""
def as_str_seq(self, split: bool = True) -> List[str]:
"""
Get configuration value as list of strings.
Parameters:
- split: Whether to split string values on whitespace/commas
Returns:
List of string values
"""
def __getitem__(self, key: str) -> 'ConfigView':
"""
Get nested configuration section.
Parameters:
- key: Nested key name
Returns:
ConfigView for nested section
"""
def items(self) -> Iterator[Tuple[str, 'ConfigView']]:
"""
Iterate over configuration key-value pairs.
Returns:
Iterator of (key, ConfigView) tuples
"""
def keys(self) -> Iterator[str]:
"""
Get all configuration keys in this section.
Returns:
Iterator over key names
"""Settings for database and music directory management.
# Library database file path
library: ~/.config/beets/musiclibrary.db
# Root directory for music files
directory: ~/Music
# Path format templates
paths:
default: $albumartist/$album/$track $title
singleton: Non-Album/$artist - $title
comp: Compilations/$album/$track $title
albumtype:soundtrack: Soundtracks/$album/$track $title
albumtype:live: Live/$albumartist/$album/$track $title# Access library configuration
from beets import config
library_path = config['library'].as_filename()
music_dir = config['directory'].as_filename()
path_formats = config['paths'].get(dict)Settings controlling the import process and metadata handling.
import:
# Write tags to files after import
write: yes
# Copy files vs move files
copy: no
# Resume interrupted imports
resume: ask
# Skip unchanged directories
incremental: no
# Quiet fallback for timeouts
quiet_fallback: skip
# Default action for ambiguous matches
timid: no
# Enabled metadata sources
sources: [filesystem, musicbrainz, discogs]
# Search by specific IDs
search_ids: []# Access import configuration
from beets import config
write_tags = config['import']['write'].get(bool)
copy_files = config['import']['copy'].get(bool)
resume_mode = config['import']['resume'].get(str)
sources = config['import']['sources'].as_str_seq()User interface settings including colors and terminal behavior.
ui:
# Enable terminal colors
color: yes
# Terminal width override
terminal_width: 80
# Color scheme
colors:
text_success: [green]
text_warning: [yellow]
text_error: [red]
text_highlight: [bold, blue]
action_default: [bold, turquoise]
action: [turquoise]# Access UI configuration
from beets import config
color_enabled = config['ui']['color'].get(bool)
terminal_width = config['ui']['terminal_width'].get(int)
colors = config['ui']['colors'].get(dict)Plugin loading and individual plugin settings.
plugins:
- fetchart
- lyrics
- discogs
- replaygain
- web
# Plugin-specific configuration
fetchart:
auto: yes
sources: coverart lastfm amazon
minwidth: 300
lyrics:
auto: yes
sources: genius lyricwiki musixmatch
web:
host: 127.0.0.1
port: 8337# Access plugin configuration
from beets import config
enabled_plugins = config['plugins'].as_str_seq()
fetchart_auto = config['fetchart']['auto'].get(bool)
fetchart_sources = config['fetchart']['sources'].as_str_seq()
web_host = config['web']['host'].get(str)
web_port = config['web']['port'].get(int)# Default configuration file locations
# Linux/macOS: ~/.config/beets/config.yaml
# Windows: %APPDATA%\beets\config.yaml
from beets import config
# Get configuration file path
config_path = config.user_config_path()
# Get configuration directory
config_dir = config.config_dir()
# Get default config values
default_config = config.default_config_path()from beets import config
# Load default configuration
config.read()
# Load additional config file
config.set_file('/path/to/additional/config.yaml')
# Set values from command line
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
config.set_args(args)The configuration system supports including other YAML files.
# Main config.yaml
include:
- paths.yaml
- plugins.yaml
- colors.yaml
library: ~/.config/beets/musiclibrary.db
directory: ~/Music# paths.yaml
paths:
default: $albumartist/$album/$track $title
classical: Classical/$composer/$album/$track $title
# plugins.yaml
plugins:
- fetchart
- lyrics
- discogs
fetchart:
auto: yes
sources: coverart lastfmfrom beets import config
# String values
library_path = config['library'].as_filename()
music_dir = config['directory'].as_filename()
# Boolean values
write_tags = config['import']['write'].get(bool)
color_enabled = config['ui']['color'].get(bool)
# Integer values
terminal_width = config['ui']['terminal_width'].get(int)
web_port = config['web']['port'].get(int)
# List values
plugins = config['plugins'].as_str_seq()
sources = config['import']['sources'].as_str_seq()
# Dictionary values
paths = config['paths'].get(dict)
colors = config['ui']['colors'].get(dict)from beets import config
# Provide default values
timeout = config['import']['timeout'].get(int, 5)
max_width = config['ui']['max_width'].get(int, 80)
# Check if value exists
if 'api_key' in config['discogs']:
api_key = config['discogs']['api_key'].get(str)
else:
print("No Discogs API key configured")
# Conditional configuration
if config['import']['write'].get(bool):
# Enable tag writing
passfrom beets import config
from beets.plugins import BeetsPlugin
class MyPlugin(BeetsPlugin):
def __init__(self, name):
super().__init__(name)
# Set plugin defaults
config[name].add({
'enabled': True,
'timeout': 10,
'sources': ['default'],
})
def get_config_value(self, key, default=None):
"""Get plugin-specific configuration value."""
return config[self.name][key].get(default)
def is_enabled(self):
"""Check if plugin is enabled."""
return self.get_config_value('enabled', True)Configuration values can reference environment variables and support path expansion.
# Environment variable expansion
library: $BEETS_LIBRARY_PATH
directory: $MUSIC_DIR
# Path expansion
library: ~/beets/library.db
directory: ~/Music
# Mixed expansion
directory: $HOME/Music/Libraryimport os
from beets import config
# Set environment variables
os.environ['BEETS_LIBRARY_PATH'] = '/custom/path/library.db'
os.environ['MUSIC_DIR'] = '/mnt/music'
# Configuration will automatically expand these
library_path = config['library'].as_filename()
# Result: '/custom/path/library.db'from beets import config
# Modify configuration at runtime
config['import']['write'] = True
config['ui']['color'] = False
# Add new sections
config['mycustom'] = {
'setting1': 'value1',
'setting2': 42
}
# Plugin configuration
config['myplugin']['enabled'] = Truefrom beets import config
import contextlib
@contextlib.contextmanager
def temp_config(**overrides):
"""Temporarily override configuration values."""
old_values = {}
for key, value in overrides.items():
old_values[key] = config[key].get()
config[key] = value
try:
yield
finally:
for key, old_value in old_values.items():
config[key] = old_value
# Usage
with temp_config(import_write=False, ui_color=True):
# Configuration temporarily modified
perform_operation()
# Configuration restoredfrom beets import config
import confuse
def validate_config():
"""Validate configuration values."""
try:
# Validate required settings
library_path = config['library'].as_filename()
if not library_path:
raise ValueError("Library path is required")
# Validate numeric ranges
port = config['web']['port'].get(int)
if not 1024 <= port <= 65535:
raise ValueError(f"Invalid port number: {port}")
# Validate choices
log_level = config['log_level'].get(str, 'INFO')
if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR']:
raise ValueError(f"Invalid log level: {log_level}")
except confuse.ConfigError as e:
print(f"Configuration error: {e}")
raisefrom beets import config
from beets.plugins import BeetsPlugin
class ValidatedPlugin(BeetsPlugin):
def __init__(self, name):
super().__init__(name)
# Set defaults with validation
config[name].add({
'timeout': 10,
'retries': 3,
'sources': ['default']
})
self.validate_config()
def validate_config(self):
"""Validate plugin configuration."""
timeout = config[self.name]['timeout'].get(int)
if timeout <= 0:
raise ValueError(f"{self.name}: timeout must be positive")
retries = config[self.name]['retries'].get(int)
if retries < 0:
raise ValueError(f"{self.name}: retries cannot be negative")
sources = config[self.name]['sources'].as_str_seq()
if not sources:
raise ValueError(f"{self.name}: at least one source required")class ConfigError(Exception):
"""Base exception for configuration errors."""
class ConfigTypeError(ConfigError):
"""Raised when configuration value has wrong type."""
class ConfigReadError(ConfigError):
"""Raised when configuration file cannot be read."""from beets import config
import confuse
def safe_config_access():
"""Safely access configuration with error handling."""
try:
# Access configuration values
library_path = config['library'].as_filename()
plugins = config['plugins'].as_str_seq()
except confuse.NotFoundError:
print("Required configuration section not found")
except confuse.ConfigTypeError as e:
print(f"Configuration type error: {e}")
except confuse.ConfigReadError as e:
print(f"Cannot read configuration file: {e}")This comprehensive configuration system provides flexible, type-safe access to all beets settings with support for environment variables, includes, validation, and runtime modification.
Install with Tessl CLI
npx tessl i tessl/pypi-beets