CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-sopel

Simple and extensible IRC bot framework written in Python with plugin architecture and database support

Pending
Overview
Eval results
Files

configuration.mddocs/

Bot Configuration

Sopel's configuration system provides hierarchical, section-based configuration with type validation, environment variable support, and runtime management. The system is built around the Config class and StaticSection subclasses that define configuration schemas.

Capabilities

Configuration Management

Core configuration functionality for loading, saving, and managing bot settings.

class Config:
    """Main configuration management class."""
    
    def __init__(self, filename: str, validate: bool = True):
        """
        Initialize configuration from file.
        
        Args:
            filename (str): Path to configuration file
            validate (bool): Whether to validate configuration on load
        """
    
    @property
    def filename(self) -> str:
        """Path to the configuration file."""
    
    @property  
    def basename(self) -> str:
        """Configuration file basename without extension."""
    
    @property
    def homedir(self) -> str:
        """Configuration home directory path."""
    
    @property
    def parser(self) -> 'configparser.RawConfigParser':
        """Underlying configuration parser object."""
    
    def save(self) -> None:
        """
        Save all configuration changes to file.
        
        Note: This removes any comments from the configuration file.
        """
    
    def add_section(self, name: str) -> None | bool:
        """
        Add a new empty section to configuration.
        
        Args:
            name (str): Section name (should use snake_case)
            
        Returns:
            None if successful, False if section already exists
        """
    
    def define_section(self, name: str, cls_, validate: bool = True) -> None:
        """
        Define a configuration section with a StaticSection class.
        
        Args:
            name (str): Section name (should use snake_case) 
            cls_: StaticSection subclass defining the section schema
            validate (bool): Whether to validate section values
            
        Raises:
            ValueError: If section already defined with different class
        """
    
    def get_defined_sections(self) -> list:
        """
        Get all defined StaticSection instances.
        
        Returns:
            List of (name, section) tuples for all defined sections
        """
    
    def option(self, question: str, default: bool = False) -> bool:
        """
        Ask user a yes/no question interactively.
        
        Args:
            question (str): Question to ask user
            default (bool): Default answer (True for 'y', False for 'n')
            
        Returns:
            User's boolean response
        """
    
    def get(self, section: str, option: str) -> str:
        """Shortcut to parser.get() method."""

Section Definition

Base classes and system for defining structured configuration sections.

class StaticSection:
    """
    Base class for defining configuration sections with typed attributes.
    
    Subclass this to create configuration sections with validation and
    type conversion.
    """
    
    def __init__(self, config: Config, section_name: str, validate: bool = True):
        """
        Initialize section from configuration.
        
        Args:
            config (Config): Parent configuration object
            section_name (str): Name of this section
            validate (bool): Whether to validate attributes
        """

Configuration Attributes

Typed attribute classes for configuration values with validation and conversion.

class ValidatedAttribute:
    """
    Base attribute class with validation and type conversion.
    
    Use for configuration values that need type checking or validation.
    """
    
    def __init__(self, name: str, cls_=None, default=None, parse=None):
        """
        Create a validated configuration attribute.
        
        Args:
            name (str): Configuration key name
            cls_: Type class for validation (int, str, bool, etc.)
            default: Default value if not set
            parse (callable): Custom parsing function
        """

class ListAttribute(ValidatedAttribute):
    """
    Attribute for configuration values that are lists.
    
    Automatically splits comma-separated values into lists.
    """
    
    def __init__(self, name: str, strip: bool = True, default=None):
        """
        Create a list configuration attribute.
        
        Args:
            name (str): Configuration key name
            strip (bool): Whether to strip whitespace from list items
            default: Default list value
        """

class ChoiceAttribute(ValidatedAttribute):
    """
    Attribute that must be one of a predefined set of choices.
    """
    
    def __init__(self, name: str, choices: list, default=None):
        """
        Create a choice configuration attribute.
        
        Args:
            name (str): Configuration key name
            choices (list): List of valid choices
            default: Default choice value
        """

class FilenameAttribute(ValidatedAttribute):
    """
    Attribute for file path configuration values.
    
    Handles path resolution and validation.
    """
    
    def __init__(self, name: str, relative: bool = True, directory: str = None, default=None):
        """
        Create a filename configuration attribute.
        
        Args:
            name (str): Configuration key name
            relative (bool): Whether paths are relative to config directory
            directory (str): Base directory for relative paths
            default: Default filename
        """

Core Configuration Section

The built-in core configuration section that defines essential bot settings.

class CoreSection(StaticSection):
    """Core bot configuration section with essential settings."""
    
    # Connection settings
    nick: str  # Bot's nickname
    host: str  # IRC server hostname
    port: int  # IRC server port (default: 6667)
    use_ssl: bool  # Whether to use SSL/TLS (default: False)
    verify_ssl: bool  # Whether to verify SSL certificates (default: True)
    
    # Authentication
    password: str  # Server password (optional)
    auth_method: str  # Authentication method
    auth_username: str  # Authentication username
    auth_password: str  # Authentication password
    
    # Bot identity
    user: str  # IRC username/ident
    name: str  # Real name field
    channels: list  # Auto-join channels
    
    # Bot operators
    owner: str  # Bot owner nickname
    admins: list  # Bot admin nicknames
    
    # Database
    db_type: str  # Database type (sqlite, mysql, postgresql, etc.)
    db_filename: str  # Database filename (SQLite only)
    db_host: str  # Database host
    db_port: int  # Database port
    db_user: str  # Database username
    db_pass: str  # Database password
    db_name: str  # Database name
    
    # Logging
    logdir: str  # Log directory
    logging_level: str  # Logging level (DEBUG, INFO, WARNING, ERROR)
    logging_format: str  # Log message format
    
    # Plugin settings
    extra: list  # Additional directories to search for plugins
    exclude: list  # Plugins to exclude from loading
    
    # Runtime settings
    homedir: str  # Bot home directory
    pid_dir: str  # Directory for PID files
    help_prefix: str  # Prefix for help commands
    reply_errors: bool  # Whether to reply with error messages

Usage Examples

Basic Configuration Setup

from sopel.config import Config

# Load configuration from file
config = Config('/path/to/bot.cfg')

# Access core settings
print(f"Bot nick: {config.core.nick}")
print(f"IRC server: {config.core.host}")
print(f"Channels: {config.core.channels}")

# Save configuration changes
config.core.nick = "NewBotName"
config.save()

Creating Custom Configuration Sections

from sopel import config

class WeatherSection(config.types.StaticSection):
    """Configuration for weather plugin."""
    
    api_key = config.types.ValidatedAttribute('api_key')
    default_location = config.types.ValidatedAttribute('default_location', default='London')
    units = config.types.ChoiceAttribute('units', choices=['metric', 'imperial'], default='metric')
    max_locations = config.types.ValidatedAttribute('max_locations', int, default=5)
    timeout = config.types.ValidatedAttribute('timeout', int, default=30)

# In plugin setup function
def setup(bot):
    """Setup weather plugin configuration."""
    bot.settings.define_section('weather', WeatherSection)
    
    # Validate required settings
    if not bot.settings.weather.api_key:
        raise Exception("Weather plugin requires api_key in [weather] section")

# In plugin functions  
@plugin.command('weather')
def weather_command(bot, trigger):
    """Get weather using configured settings."""
    api_key = bot.settings.weather.api_key
    units = bot.settings.weather.units
    timeout = bot.settings.weather.timeout
    
    # Use configuration values...

Advanced Configuration with Validation

from sopel import config
import os

class DatabaseSection(config.types.StaticSection):
    """Advanced database configuration with validation."""
    
    type = config.types.ChoiceAttribute(
        'type', 
        choices=['sqlite', 'mysql', 'postgresql'], 
        default='sqlite'
    )
    
    filename = config.types.FilenameAttribute(
        'filename',
        relative=True,
        default='bot.db'
    )
    
    host = config.types.ValidatedAttribute('host', default='localhost')
    port = config.types.ValidatedAttribute('port', int, default=5432)
    username = config.types.ValidatedAttribute('username')
    password = config.types.ValidatedAttribute('password')
    name = config.types.ValidatedAttribute('name', default='sopel')
    
    # Custom validation
    def __init__(self, config, section_name, validate=True):
        super().__init__(config, section_name, validate)
        
        if validate and self.type != 'sqlite':
            if not self.username or not self.password:
                raise ValueError("Database username and password required for non-SQLite databases")

# Using the configuration
def setup(bot):
    bot.settings.define_section('database', DatabaseSection)
    
    # Access configuration
    db_config = bot.settings.database
    if db_config.type == 'sqlite':
        db_path = os.path.join(bot.settings.core.homedir, db_config.filename)
        print(f"Using SQLite database: {db_path}")
    else:
        print(f"Using {db_config.type} database on {db_config.host}:{db_config.port}")

Environment Variable Overrides

# Configuration values can be overridden with environment variables
# Format: SOPEL_<SECTION>_<OPTION>

# Override core.nick
# export SOPEL_CORE_NICK="MyBot"

# Override custom section values
# export SOPEL_WEATHER_API_KEY="your_api_key_here"

from sopel.config import Config

# Load config with environment overrides
config = Config('/path/to/bot.cfg')

# Values are automatically overridden from environment
print(config.core.nick)  # Uses SOPEL_CORE_NICK if set

Interactive Configuration

from sopel.config import Config

def configure_weather_plugin(config):
    """Interactively configure weather plugin."""
    
    # Ask yes/no questions
    enable_weather = config.option("Enable weather plugin?", default=True)
    if not enable_weather:
        return
    
    # Get user input for settings
    api_key = input("Enter weather API key: ")
    default_location = input("Enter default location [London]: ") or "London"
    
    # Save configuration
    if 'weather' not in config:
        config.add_section('weather')
    
    config.weather.api_key = api_key
    config.weather.default_location = default_location
    config.save()
    
    print("Weather plugin configured successfully!")

Types

Configuration Constants

# Default configuration directory
DEFAULT_HOMEDIR: str = "~/.sopel"

Configuration Exceptions

class ConfigurationError(Exception):
    """Base exception for configuration errors."""
    
    def __init__(self, value: str):
        """
        Initialize configuration error.
        
        Args:
            value (str): Error description
        """

class ConfigurationNotFound(ConfigurationError):
    """Exception raised when configuration file cannot be found."""
    
    def __init__(self, filename: str):
        """
        Initialize configuration not found error.
        
        Args:
            filename (str): Path to missing configuration file
        """
    
    @property
    def filename(self) -> str:
        """Path to the configuration file that could not be found."""

Install with Tessl CLI

npx tessl i tessl/pypi-sopel

docs

configuration.md

database.md

index.md

irc-protocol.md

plugin-development.md

utilities.md

tile.json