0
# Bot Configuration
1
2
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.
3
4
## Capabilities
5
6
### Configuration Management
7
8
Core configuration functionality for loading, saving, and managing bot settings.
9
10
```python { .api }
11
class Config:
12
"""Main configuration management class."""
13
14
def __init__(self, filename: str, validate: bool = True):
15
"""
16
Initialize configuration from file.
17
18
Args:
19
filename (str): Path to configuration file
20
validate (bool): Whether to validate configuration on load
21
"""
22
23
@property
24
def filename(self) -> str:
25
"""Path to the configuration file."""
26
27
@property
28
def basename(self) -> str:
29
"""Configuration file basename without extension."""
30
31
@property
32
def homedir(self) -> str:
33
"""Configuration home directory path."""
34
35
@property
36
def parser(self) -> 'configparser.RawConfigParser':
37
"""Underlying configuration parser object."""
38
39
def save(self) -> None:
40
"""
41
Save all configuration changes to file.
42
43
Note: This removes any comments from the configuration file.
44
"""
45
46
def add_section(self, name: str) -> None | bool:
47
"""
48
Add a new empty section to configuration.
49
50
Args:
51
name (str): Section name (should use snake_case)
52
53
Returns:
54
None if successful, False if section already exists
55
"""
56
57
def define_section(self, name: str, cls_, validate: bool = True) -> None:
58
"""
59
Define a configuration section with a StaticSection class.
60
61
Args:
62
name (str): Section name (should use snake_case)
63
cls_: StaticSection subclass defining the section schema
64
validate (bool): Whether to validate section values
65
66
Raises:
67
ValueError: If section already defined with different class
68
"""
69
70
def get_defined_sections(self) -> list:
71
"""
72
Get all defined StaticSection instances.
73
74
Returns:
75
List of (name, section) tuples for all defined sections
76
"""
77
78
def option(self, question: str, default: bool = False) -> bool:
79
"""
80
Ask user a yes/no question interactively.
81
82
Args:
83
question (str): Question to ask user
84
default (bool): Default answer (True for 'y', False for 'n')
85
86
Returns:
87
User's boolean response
88
"""
89
90
def get(self, section: str, option: str) -> str:
91
"""Shortcut to parser.get() method."""
92
```
93
94
### Section Definition
95
96
Base classes and system for defining structured configuration sections.
97
98
```python { .api }
99
class StaticSection:
100
"""
101
Base class for defining configuration sections with typed attributes.
102
103
Subclass this to create configuration sections with validation and
104
type conversion.
105
"""
106
107
def __init__(self, config: Config, section_name: str, validate: bool = True):
108
"""
109
Initialize section from configuration.
110
111
Args:
112
config (Config): Parent configuration object
113
section_name (str): Name of this section
114
validate (bool): Whether to validate attributes
115
"""
116
```
117
118
### Configuration Attributes
119
120
Typed attribute classes for configuration values with validation and conversion.
121
122
```python { .api }
123
class ValidatedAttribute:
124
"""
125
Base attribute class with validation and type conversion.
126
127
Use for configuration values that need type checking or validation.
128
"""
129
130
def __init__(self, name: str, cls_=None, default=None, parse=None):
131
"""
132
Create a validated configuration attribute.
133
134
Args:
135
name (str): Configuration key name
136
cls_: Type class for validation (int, str, bool, etc.)
137
default: Default value if not set
138
parse (callable): Custom parsing function
139
"""
140
141
class ListAttribute(ValidatedAttribute):
142
"""
143
Attribute for configuration values that are lists.
144
145
Automatically splits comma-separated values into lists.
146
"""
147
148
def __init__(self, name: str, strip: bool = True, default=None):
149
"""
150
Create a list configuration attribute.
151
152
Args:
153
name (str): Configuration key name
154
strip (bool): Whether to strip whitespace from list items
155
default: Default list value
156
"""
157
158
class ChoiceAttribute(ValidatedAttribute):
159
"""
160
Attribute that must be one of a predefined set of choices.
161
"""
162
163
def __init__(self, name: str, choices: list, default=None):
164
"""
165
Create a choice configuration attribute.
166
167
Args:
168
name (str): Configuration key name
169
choices (list): List of valid choices
170
default: Default choice value
171
"""
172
173
class FilenameAttribute(ValidatedAttribute):
174
"""
175
Attribute for file path configuration values.
176
177
Handles path resolution and validation.
178
"""
179
180
def __init__(self, name: str, relative: bool = True, directory: str = None, default=None):
181
"""
182
Create a filename configuration attribute.
183
184
Args:
185
name (str): Configuration key name
186
relative (bool): Whether paths are relative to config directory
187
directory (str): Base directory for relative paths
188
default: Default filename
189
"""
190
```
191
192
### Core Configuration Section
193
194
The built-in core configuration section that defines essential bot settings.
195
196
```python { .api }
197
class CoreSection(StaticSection):
198
"""Core bot configuration section with essential settings."""
199
200
# Connection settings
201
nick: str # Bot's nickname
202
host: str # IRC server hostname
203
port: int # IRC server port (default: 6667)
204
use_ssl: bool # Whether to use SSL/TLS (default: False)
205
verify_ssl: bool # Whether to verify SSL certificates (default: True)
206
207
# Authentication
208
password: str # Server password (optional)
209
auth_method: str # Authentication method
210
auth_username: str # Authentication username
211
auth_password: str # Authentication password
212
213
# Bot identity
214
user: str # IRC username/ident
215
name: str # Real name field
216
channels: list # Auto-join channels
217
218
# Bot operators
219
owner: str # Bot owner nickname
220
admins: list # Bot admin nicknames
221
222
# Database
223
db_type: str # Database type (sqlite, mysql, postgresql, etc.)
224
db_filename: str # Database filename (SQLite only)
225
db_host: str # Database host
226
db_port: int # Database port
227
db_user: str # Database username
228
db_pass: str # Database password
229
db_name: str # Database name
230
231
# Logging
232
logdir: str # Log directory
233
logging_level: str # Logging level (DEBUG, INFO, WARNING, ERROR)
234
logging_format: str # Log message format
235
236
# Plugin settings
237
extra: list # Additional directories to search for plugins
238
exclude: list # Plugins to exclude from loading
239
240
# Runtime settings
241
homedir: str # Bot home directory
242
pid_dir: str # Directory for PID files
243
help_prefix: str # Prefix for help commands
244
reply_errors: bool # Whether to reply with error messages
245
```
246
247
## Usage Examples
248
249
### Basic Configuration Setup
250
251
```python
252
from sopel.config import Config
253
254
# Load configuration from file
255
config = Config('/path/to/bot.cfg')
256
257
# Access core settings
258
print(f"Bot nick: {config.core.nick}")
259
print(f"IRC server: {config.core.host}")
260
print(f"Channels: {config.core.channels}")
261
262
# Save configuration changes
263
config.core.nick = "NewBotName"
264
config.save()
265
```
266
267
### Creating Custom Configuration Sections
268
269
```python
270
from sopel import config
271
272
class WeatherSection(config.types.StaticSection):
273
"""Configuration for weather plugin."""
274
275
api_key = config.types.ValidatedAttribute('api_key')
276
default_location = config.types.ValidatedAttribute('default_location', default='London')
277
units = config.types.ChoiceAttribute('units', choices=['metric', 'imperial'], default='metric')
278
max_locations = config.types.ValidatedAttribute('max_locations', int, default=5)
279
timeout = config.types.ValidatedAttribute('timeout', int, default=30)
280
281
# In plugin setup function
282
def setup(bot):
283
"""Setup weather plugin configuration."""
284
bot.settings.define_section('weather', WeatherSection)
285
286
# Validate required settings
287
if not bot.settings.weather.api_key:
288
raise Exception("Weather plugin requires api_key in [weather] section")
289
290
# In plugin functions
291
@plugin.command('weather')
292
def weather_command(bot, trigger):
293
"""Get weather using configured settings."""
294
api_key = bot.settings.weather.api_key
295
units = bot.settings.weather.units
296
timeout = bot.settings.weather.timeout
297
298
# Use configuration values...
299
```
300
301
### Advanced Configuration with Validation
302
303
```python
304
from sopel import config
305
import os
306
307
class DatabaseSection(config.types.StaticSection):
308
"""Advanced database configuration with validation."""
309
310
type = config.types.ChoiceAttribute(
311
'type',
312
choices=['sqlite', 'mysql', 'postgresql'],
313
default='sqlite'
314
)
315
316
filename = config.types.FilenameAttribute(
317
'filename',
318
relative=True,
319
default='bot.db'
320
)
321
322
host = config.types.ValidatedAttribute('host', default='localhost')
323
port = config.types.ValidatedAttribute('port', int, default=5432)
324
username = config.types.ValidatedAttribute('username')
325
password = config.types.ValidatedAttribute('password')
326
name = config.types.ValidatedAttribute('name', default='sopel')
327
328
# Custom validation
329
def __init__(self, config, section_name, validate=True):
330
super().__init__(config, section_name, validate)
331
332
if validate and self.type != 'sqlite':
333
if not self.username or not self.password:
334
raise ValueError("Database username and password required for non-SQLite databases")
335
336
# Using the configuration
337
def setup(bot):
338
bot.settings.define_section('database', DatabaseSection)
339
340
# Access configuration
341
db_config = bot.settings.database
342
if db_config.type == 'sqlite':
343
db_path = os.path.join(bot.settings.core.homedir, db_config.filename)
344
print(f"Using SQLite database: {db_path}")
345
else:
346
print(f"Using {db_config.type} database on {db_config.host}:{db_config.port}")
347
```
348
349
### Environment Variable Overrides
350
351
```python
352
# Configuration values can be overridden with environment variables
353
# Format: SOPEL_<SECTION>_<OPTION>
354
355
# Override core.nick
356
# export SOPEL_CORE_NICK="MyBot"
357
358
# Override custom section values
359
# export SOPEL_WEATHER_API_KEY="your_api_key_here"
360
361
from sopel.config import Config
362
363
# Load config with environment overrides
364
config = Config('/path/to/bot.cfg')
365
366
# Values are automatically overridden from environment
367
print(config.core.nick) # Uses SOPEL_CORE_NICK if set
368
```
369
370
### Interactive Configuration
371
372
```python
373
from sopel.config import Config
374
375
def configure_weather_plugin(config):
376
"""Interactively configure weather plugin."""
377
378
# Ask yes/no questions
379
enable_weather = config.option("Enable weather plugin?", default=True)
380
if not enable_weather:
381
return
382
383
# Get user input for settings
384
api_key = input("Enter weather API key: ")
385
default_location = input("Enter default location [London]: ") or "London"
386
387
# Save configuration
388
if 'weather' not in config:
389
config.add_section('weather')
390
391
config.weather.api_key = api_key
392
config.weather.default_location = default_location
393
config.save()
394
395
print("Weather plugin configured successfully!")
396
```
397
398
## Types
399
400
### Configuration Constants
401
402
```python { .api }
403
# Default configuration directory
404
DEFAULT_HOMEDIR: str = "~/.sopel"
405
```
406
407
### Configuration Exceptions
408
409
```python { .api }
410
class ConfigurationError(Exception):
411
"""Base exception for configuration errors."""
412
413
def __init__(self, value: str):
414
"""
415
Initialize configuration error.
416
417
Args:
418
value (str): Error description
419
"""
420
421
class ConfigurationNotFound(ConfigurationError):
422
"""Exception raised when configuration file cannot be found."""
423
424
def __init__(self, filename: str):
425
"""
426
Initialize configuration not found error.
427
428
Args:
429
filename (str): Path to missing configuration file
430
"""
431
432
@property
433
def filename(self) -> str:
434
"""Path to the configuration file that could not be found."""
435
```