Mopidy is an extensible music server written in Python
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Mopidy's configuration system provides schema-based configuration management with validation, type safety, and support for multiple configuration sources. It enables both core functionality and extensions to define, validate, and access configuration settings in a consistent manner.
Core functions for loading, parsing, and managing configuration from multiple sources.
def load(files, ext_schemas, ext_defaults, overrides):
"""
Load configuration from multiple sources.
Parameters:
- files (list[str]): Configuration file paths
- ext_schemas (list[ConfigSchema]): Extension schemas
- ext_defaults (list[str]): Extension default configs
- overrides (list[tuple]): Command-line overrides
Returns:
- tuple[dict, dict]: (config, errors) - Parsed config and validation errors
"""
...
def format(config, ext_schemas, comments=None, display=True):
"""
Format configuration for display or output.
Parameters:
- config (dict): Configuration to format
- ext_schemas (list[ConfigSchema]): Extension schemas
- comments (dict, optional): Comments to include
- display (bool): Whether to format for display
Returns:
- str: Formatted configuration string
"""
...
def format_initial(extensions_data):
"""
Format initial configuration template with defaults.
Parameters:
- extensions_data (list[ExtensionData]): Extension data
Returns:
- str: Initial configuration template
"""
...
def read(config_file):
"""
Read configuration file content.
Parameters:
- config_file (str): Path to configuration file
Returns:
- str: Configuration file content
"""
...Usage example:
from mopidy import config
# Load configuration
config_data, errors = config.load(
files=["/etc/mopidy/mopidy.conf", "~/.config/mopidy/mopidy.conf"],
ext_schemas=[spotify_schema, local_schema],
ext_defaults=[spotify_defaults, local_defaults],
overrides=[("core", "cache_dir", "/tmp/mopidy")]
)
if errors:
print("Configuration errors:", errors)Schema definition system for validating and organizing configuration sections.
class ConfigSchema:
"""
Configuration schema for a section.
Parameters:
- name (str): Schema section name
"""
def __init__(self, name): ...
name: str # Section name
def __setitem__(self, key, value):
"""
Add configuration field to schema.
Parameters:
- key (str): Configuration key
- value (ConfigValue): Field validator
"""
...
def __getitem__(self, key):
"""Get configuration field validator."""
...
def deserialize(self, values):
"""
Deserialize configuration values.
Parameters:
- values (dict): Raw configuration values
Returns:
- tuple[dict, dict]: (parsed_values, errors)
"""
...
def serialize(self, values, display=False):
"""
Serialize configuration values.
Parameters:
- values (dict): Configuration values to serialize
- display (bool): Whether formatting for display
Returns:
- dict: Serialized values
"""
...
class MapConfigSchema(ConfigSchema):
"""
Schema for map-like configuration sections.
Parameters:
- name (str): Schema section name
- value_type (ConfigValue): Validator for all values
"""
def __init__(self, name, value_type): ...Usage example:
from mopidy.config.schemas import ConfigSchema
from mopidy.config.types import String, Integer, Boolean, Secret
# Define schema for an extension
schema = ConfigSchema("spotify")
schema["username"] = String()
schema["password"] = Secret() # Hidden in output
schema["enabled"] = Boolean()
schema["timeout"] = Integer(minimum=1, maximum=300)
schema["playlists"] = List()Strongly-typed configuration value validators with built-in validation and conversion.
class ConfigValue:
"""Base class for configuration value types."""
def __init__(self, optional=False):
"""
Initialize config value.
Parameters:
- optional (bool): Whether value is optional
"""
...
def deserialize(self, value):
"""
Convert string value to Python type.
Parameters:
- value (str): Raw configuration value
Returns:
- tuple[Any, str]: (converted_value, error_message)
"""
...
def serialize(self, value, display=False):
"""
Convert Python value to string.
Parameters:
- value: Python value to serialize
- display (bool): Whether formatting for display
Returns:
- str: Serialized value
"""
...
class String(ConfigValue):
"""String configuration value."""
def __init__(self, optional=False, choices=None): ...
class Integer(ConfigValue):
"""
Integer configuration value.
Parameters:
- optional (bool): Whether value is optional
- minimum (int, optional): Minimum allowed value
- maximum (int, optional): Maximum allowed value
"""
def __init__(self, optional=False, minimum=None, maximum=None): ...
class Float(ConfigValue):
"""
Float configuration value.
Parameters:
- optional (bool): Whether value is optional
- minimum (float, optional): Minimum allowed value
- maximum (float, optional): Maximum allowed value
"""
def __init__(self, optional=False, minimum=None, maximum=None): ...
class Boolean(ConfigValue):
"""Boolean configuration value (true/false, yes/no, 1/0)."""
def __init__(self, optional=False): ...
class List(ConfigValue):
"""
List configuration value (comma-separated).
Parameters:
- optional (bool): Whether value is optional
- separator (str): List item separator (default: comma)
"""
def __init__(self, optional=False, separator=","): ...
class Pair(ConfigValue):
"""
Key-value pair configuration value.
Parameters:
- optional (bool): Whether value is optional
- separator (str): Key-value separator (default: pipe)
"""
def __init__(self, optional=False, separator="|"): ...
class Secret(ConfigValue):
"""Secret configuration value (hidden in output)."""
def __init__(self, optional=False): ...
class Path(ConfigValue):
"""File system path configuration value."""
def __init__(self, optional=False): ...
class Hostname(ConfigValue):
"""Network hostname configuration value."""
def __init__(self, optional=False): ...
class Port(ConfigValue):
"""
Network port configuration value.
Parameters:
- optional (bool): Whether value is optional
- choices (list[int], optional): Allowed port numbers
"""
def __init__(self, optional=False, choices=None): ...
class LogLevel(ConfigValue):
"""Logging level configuration value."""
def __init__(self, optional=False): ...
class LogColor(ConfigValue):
"""Log color configuration value."""
def __init__(self, optional=False): ...Usage example:
# Configure different value types
schema["server_host"] = Hostname()
schema["server_port"] = Port(choices=[8080, 8081, 8082])
schema["log_level"] = LogLevel()
schema["api_timeout"] = Integer(minimum=1, maximum=300)
schema["cache_dir"] = Path()
schema["enabled_formats"] = List()
schema["database_url"] = Secret() # Won't be shown in config outputSupport for handling deprecated configuration options with warnings and migration.
class Deprecated(ConfigValue):
"""Marks a configuration option as deprecated."""
def __init__(self, message=None): ...
class DeprecatedValue:
"""Wrapper for deprecated configuration values."""
def __init__(self, value, message): ...Usage example:
# Mark old configuration options as deprecated
schema["old_option"] = Deprecated("Use 'new_option' instead")
schema["legacy_setting"] = Deprecated()Proxy object providing convenient access to nested configuration values.
class Proxy:
"""
Configuration proxy for convenient access to nested values.
Parameters:
- data (dict): Configuration data to wrap
"""
def __init__(self, data): ...
def __getitem__(self, key): ...
def __iter__(self): ...
def __len__(self): ...
def __repr__(self): ...Usage example:
config_proxy = Proxy(config_data)
# Access nested configuration values
cache_dir = config_proxy["core"]["cache_dir"]
spotify_username = config_proxy["spotify"]["username"]
# Proxy maintains dict-like interface
for section_name in config_proxy:
section = config_proxy[section_name]
print(f"Section {section_name} has {len(section)} options")Built-in configuration schemas for Mopidy's core functionality.
# Core system configuration
_core_schema = ConfigSchema("core")
_core_schema["cache_dir"] = Path()
_core_schema["config_dir"] = Path()
_core_schema["data_dir"] = Path()
_core_schema["max_tracklist_length"] = Integer(minimum=1)
_core_schema["restore_state"] = Boolean(optional=True)
# Logging configuration
_logging_schema = ConfigSchema("logging")
_logging_schema["verbosity"] = Integer(minimum=-1, maximum=4)
_logging_schema["format"] = String()
_logging_schema["color"] = Boolean()
_logging_schema["config_file"] = Path(optional=True)
# Audio system configuration
_audio_schema = ConfigSchema("audio")
_audio_schema["mixer"] = String()
_audio_schema["mixer_volume"] = Integer(optional=True, minimum=0, maximum=100)
_audio_schema["output"] = String()
_audio_schema["buffer_time"] = Integer(optional=True, minimum=1)
# HTTP proxy configuration
_proxy_schema = ConfigSchema("proxy")
_proxy_schema["scheme"] = String(optional=True, choices=["http", "https", "socks4", "socks5"])
_proxy_schema["hostname"] = Hostname(optional=True)
_proxy_schema["port"] = Port(optional=True)
_proxy_schema["username"] = String(optional=True)
_proxy_schema["password"] = Secret(optional=True)
# Per-module log levels
_loglevels_schema = MapConfigSchema("loglevels", LogLevel())
# Per-module log colors
_logcolors_schema = MapConfigSchema("logcolors", LogColor())Standard configuration file search paths and loading order:
# Standard configuration locations (in search order):
# 1. /etc/mopidy/mopidy.conf
# 2. /etc/mopidy/conf.d/*.conf
# 3. $XDG_CONFIG_HOME/mopidy/mopidy.conf (defaults to ~/.config/mopidy/mopidy.conf)
# 4. $XDG_CONFIG_HOME/mopidy/conf.d/*.conf
# 5. Command-line overrides
# Configuration can also be loaded from directories:
config_data, errors = config.load(
files=[
"/etc/mopidy/mopidy.conf",
"/etc/mopidy/conf.d/", # Will load all .conf files
"~/.config/mopidy/mopidy.conf"
],
ext_schemas=schemas,
ext_defaults=defaults,
overrides=[]
)Comprehensive validation with detailed error reporting:
# Configuration loading returns both config and errors
config_data, errors = config.load(files, schemas, defaults, overrides)
# Errors are organized by section
if errors:
for section_name, section_errors in errors.items():
print(f"Errors in [{section_name}]:")
for field, error_msg in section_errors.items():
print(f" {field}: {error_msg}")
# Example error output:
# Errors in [spotify]:
# username: Required field is missing
# timeout: Value must be between 1 and 300Integration with environment variables and system paths:
import os
from pathlib import Path
# Environment variable support in configuration
config_template = """
[core]
cache_dir = ${XDG_CACHE_HOME}/mopidy
data_dir = ${XDG_DATA_HOME}/mopidy
[myext]
api_key = ${MYEXT_API_KEY}
"""
# Path expansion and validation
path_config = Path()
expanded_path = path_config.deserialize("~/music") # Expands to full pathfrom mopidy.config.schemas import ConfigSchema
from mopidy.config.types import String, Integer, Boolean, Secret, List
class MyExtension(Extension):
def get_config_schema(self):
schema = super().get_config_schema()
# Required settings
schema["api_key"] = Secret() # Never shown in output
schema["endpoint"] = String()
# Optional settings with defaults
schema["timeout"] = Integer(minimum=1, maximum=300)
schema["max_results"] = Integer(minimum=1, maximum=1000)
schema["enabled"] = Boolean()
# List and choice settings
schema["supported_formats"] = List()
schema["quality"] = String(choices=["low", "medium", "high"])
return schema
def get_default_config(self):
return """\
[myext]
enabled = true
api_key =
endpoint = https://api.example.com
timeout = 30
max_results = 100
supported_formats = mp3, flac, ogg
quality = medium
"""class MyBackend(Backend):
def __init__(self, config, audio):
super().__init__(config, audio)
# Access extension configuration
ext_config = config["myext"]
self.api_key = ext_config["api_key"]
self.endpoint = ext_config["endpoint"]
self.timeout = ext_config["timeout"]
# Validate required settings
if not self.api_key:
raise ExtensionError("MyExt requires api_key configuration")# Configuration can be reloaded at runtime
def reload_config():
new_config, errors = config.load(
files=config_files,
ext_schemas=schemas,
ext_defaults=defaults,
overrides=[]
)
if not errors:
# Update components with new configuration
update_backends(new_config)
update_frontends(new_config)