0
# Extension System
1
2
Mopidy's extension system provides a plugin architecture that allows third-party developers to add new music sources, frontends, and other functionality. Extensions integrate seamlessly with Mopidy's configuration system, dependency management, and component registry.
3
4
## Capabilities
5
6
### Extension Base Class
7
8
The foundational class for creating Mopidy extensions with configuration, lifecycle management, and component registration.
9
10
```python { .api }
11
class Extension:
12
"""
13
Base class for Mopidy extensions providing plugin functionality.
14
15
Attributes:
16
- dist_name (str): Distribution name as registered on PyPI
17
- ext_name (str): Extension short name for configuration
18
- version (str): Extension version string
19
"""
20
dist_name: str # e.g., "Mopidy-Spotify"
21
ext_name: str # e.g., "spotify"
22
version: str # e.g., "4.1.1"
23
24
def get_default_config(self):
25
"""
26
Get default configuration for this extension.
27
28
Returns:
29
- str: Default configuration as INI format string
30
"""
31
...
32
33
def get_config_schema(self):
34
"""
35
Get configuration schema for validation.
36
37
Returns:
38
- ConfigSchema: Schema for extension configuration
39
"""
40
...
41
42
def validate_environment(self):
43
"""
44
Validate that extension can run in current environment.
45
Raises ExtensionError if environment is invalid.
46
"""
47
...
48
49
def setup(self, registry):
50
"""
51
Register extension components with Mopidy.
52
53
Parameters:
54
- registry: Component registry for registration
55
"""
56
...
57
58
def get_cache_dir(self, config):
59
"""
60
Get cache directory for this extension.
61
62
Parameters:
63
- config: Mopidy configuration
64
65
Returns:
66
- pathlib.Path: Cache directory path
67
"""
68
...
69
70
def get_config_dir(self, config):
71
"""
72
Get configuration directory for this extension.
73
74
Parameters:
75
- config: Mopidy configuration
76
77
Returns:
78
- pathlib.Path: Configuration directory path
79
"""
80
...
81
82
def get_data_dir(self, config):
83
"""
84
Get data directory for this extension.
85
86
Parameters:
87
- config: Mopidy configuration
88
89
Returns:
90
- pathlib.Path: Data directory path
91
"""
92
...
93
```
94
95
Usage example:
96
```python
97
from mopidy import config
98
from mopidy.ext import Extension
99
100
class SpotifyExtension(Extension):
101
dist_name = "Mopidy-Spotify"
102
ext_name = "spotify"
103
version = "4.1.1"
104
105
def get_default_config(self):
106
return """\
107
[spotify]
108
enabled = true
109
username =
110
password =
111
client_id =
112
client_secret =
113
"""
114
115
def get_config_schema(self):
116
schema = super().get_config_schema()
117
schema["username"] = config.String()
118
schema["password"] = config.Secret()
119
schema["client_id"] = config.String()
120
schema["client_secret"] = config.Secret()
121
return schema
122
123
def validate_environment(self):
124
try:
125
import spotipy
126
except ImportError as e:
127
raise ExtensionError("Spotify extension requires spotipy library") from e
128
129
def setup(self, registry):
130
from .backend import SpotifyBackend
131
from .frontend import SpotifyWebApp
132
133
registry.add("backend", SpotifyBackend)
134
registry.add("frontend", SpotifyWebApp)
135
```
136
137
### Extension Metadata Container
138
139
Data structure containing extension information and configuration for the system.
140
141
```python { .api }
142
class ExtensionData(NamedTuple):
143
"""
144
Container for extension metadata and configuration.
145
146
Attributes:
147
- extension (Extension): Extension instance
148
- entry_point: setuptools entry point object
149
- config_schema (ConfigSchema): Extension configuration schema
150
- config_defaults: Default configuration values
151
- command (Command, optional): Extension CLI command
152
"""
153
extension: Extension
154
entry_point: Any
155
config_schema: ConfigSchema
156
config_defaults: Any
157
command: Optional[Command]
158
```
159
160
### Extension Discovery and Loading
161
162
Functions for discovering, loading, and validating extensions at runtime.
163
164
```python { .api }
165
def load_extensions():
166
"""
167
Load all available Mopidy extensions.
168
169
Returns:
170
- list[ExtensionData]: Loaded extension data
171
"""
172
...
173
174
def get_extensions_by_name():
175
"""
176
Get extensions mapped by name.
177
178
Returns:
179
- dict[str, ExtensionData]: Extensions by name
180
"""
181
...
182
183
def validate_extension(extension_data):
184
"""
185
Validate that extension can be used.
186
187
Parameters:
188
- extension_data (ExtensionData): Extension to validate
189
190
Returns:
191
- bool: True if extension is valid
192
"""
193
...
194
```
195
196
### Component Registry
197
198
Registry system for managing extension-provided components like backends, frontends, and mixers.
199
200
```python { .api }
201
class Registry:
202
"""Registry for extension-provided components."""
203
204
def add(self, component_type, component_class):
205
"""
206
Register a component class.
207
208
Parameters:
209
- component_type (str): Type of component ('backend', 'frontend', 'mixer')
210
- component_class: Component class to register
211
"""
212
...
213
214
def get(self, component_type):
215
"""
216
Get all registered components of a type.
217
218
Parameters:
219
- component_type (str): Component type to retrieve
220
221
Returns:
222
- list: Registered component classes
223
"""
224
...
225
226
def get_by_name(self, component_type, name):
227
"""
228
Get specific component by name.
229
230
Parameters:
231
- component_type (str): Component type
232
- name (str): Component name
233
234
Returns:
235
- Component class or None
236
"""
237
...
238
```
239
240
Usage example:
241
```python
242
def setup(self, registry):
243
from .backend import MyBackend
244
from .frontend import MyFrontend
245
from .mixer import MyMixer
246
247
registry.add("backend", MyBackend)
248
registry.add("frontend", MyFrontend)
249
registry.add("mixer", MyMixer)
250
```
251
252
### Extension Configuration Integration
253
254
Extensions integrate with Mopidy's configuration system through schemas and defaults.
255
256
```python { .api }
257
def get_config_schemas(extensions_data):
258
"""
259
Get configuration schemas from extensions.
260
261
Parameters:
262
- extensions_data (list[ExtensionData]): Extension data
263
264
Returns:
265
- list[ConfigSchema]: Configuration schemas
266
"""
267
...
268
269
def get_config_defaults(extensions_data):
270
"""
271
Get default configuration from extensions.
272
273
Parameters:
274
- extensions_data (list[ExtensionData]): Extension data
275
276
Returns:
277
- list[str]: Default configuration strings
278
"""
279
...
280
```
281
282
### Extension CLI Commands
283
284
Extensions can provide command-line interface commands that integrate with Mopidy's CLI system.
285
286
```python { .api }
287
class Command:
288
"""Base class for extension CLI commands."""
289
290
help: str # Help text for the command
291
292
def __init__(self):
293
...
294
295
def add_to_parser(self, parser):
296
"""
297
Add command arguments to argument parser.
298
299
Parameters:
300
- parser: Argument parser instance
301
"""
302
...
303
304
def run(self, args, config, extensions_data):
305
"""
306
Execute the command.
307
308
Parameters:
309
- args: Parsed command arguments
310
- config: Mopidy configuration
311
- extensions_data (list[ExtensionData]): Available extensions
312
313
Returns:
314
- int: Exit code
315
"""
316
...
317
```
318
319
Usage example:
320
```python
321
from mopidy.commands import Command
322
323
class ScanCommand(Command):
324
help = "Scan local music library"
325
326
def add_to_parser(self, parser):
327
parser.add_argument(
328
"--force",
329
action="store_true",
330
help="Force full rescan"
331
)
332
333
def run(self, args, config, extensions_data):
334
print("Scanning music library...")
335
# Implement scan logic
336
return 0
337
338
# In extension setup
339
def get_command(self):
340
return ScanCommand()
341
```
342
343
### Extension Installation and Setup
344
345
Guidelines for packaging and distributing extensions.
346
347
```python { .api }
348
# setup.py example for extensions
349
from setuptools import setup
350
351
setup(
352
name="Mopidy-MyExtension",
353
version="1.0.0",
354
packages=["mopidy_myextension"],
355
entry_points={
356
"mopidy.ext": [
357
"myextension = mopidy_myextension:Extension"
358
]
359
},
360
install_requires=[
361
"Mopidy >= 3.0.0",
362
"Pykka >= 2.0.1",
363
# Extension-specific dependencies
364
]
365
)
366
```
367
368
### Environment Validation
369
370
Best practices for validating extension environments and dependencies.
371
372
```python { .api }
373
def validate_environment(self):
374
"""Validate extension environment and dependencies."""
375
376
# Check required dependencies
377
try:
378
import required_library
379
except ImportError as e:
380
raise ExtensionError(
381
f"Extension requires 'required_library' package"
382
) from e
383
384
# Check version requirements
385
if required_library.__version__ < "2.0.0":
386
raise ExtensionError(
387
f"Extension requires required_library >= 2.0.0, "
388
f"found {required_library.__version__}"
389
)
390
391
# Check system requirements
392
if not os.path.exists("/dev/audio"):
393
raise ExtensionError("Extension requires audio device")
394
395
# Check configuration
396
config = self.get_config()
397
if not config["api_key"]:
398
raise ExtensionError("Extension requires API key configuration")
399
```
400
401
### Extension Lifecycle Events
402
403
Extensions can respond to system lifecycle events for initialization and cleanup.
404
405
```python { .api }
406
class Extension:
407
def on_start(self):
408
"""Called when Mopidy starts up."""
409
...
410
411
def on_stop(self):
412
"""Called when Mopidy shuts down."""
413
...
414
```
415
416
## Built-in Extensions
417
418
Mopidy includes several built-in extensions that demonstrate the extension system:
419
420
### HTTP Extension
421
422
```python { .api }
423
class HttpExtension(Extension):
424
"""HTTP frontend providing web interface and API."""
425
dist_name = "Mopidy"
426
ext_name = "http"
427
```
428
429
### File Extension
430
431
```python { .api }
432
class FileExtension(Extension):
433
"""Local file backend for playing music files."""
434
dist_name = "Mopidy"
435
ext_name = "file"
436
```
437
438
### M3U Extension
439
440
```python { .api }
441
class M3uExtension(Extension):
442
"""M3U playlist backend for .m3u playlist files."""
443
dist_name = "Mopidy"
444
ext_name = "m3u"
445
```
446
447
### Stream Extension
448
449
```python { .api }
450
class StreamExtension(Extension):
451
"""Stream backend for internet radio and streaming URLs."""
452
dist_name = "Mopidy"
453
ext_name = "stream"
454
```
455
456
### Software Mixer Extension
457
458
```python { .api }
459
class SoftwareMixerExtension(Extension):
460
"""Software-based audio mixer implementation."""
461
dist_name = "Mopidy"
462
ext_name = "softwaremixer"
463
```
464
465
## Extension Development Best Practices
466
467
### Configuration Schema Design
468
469
```python
470
def get_config_schema(self):
471
schema = super().get_config_schema()
472
473
# Use appropriate config types
474
schema["api_endpoint"] = config.String()
475
schema["api_key"] = config.Secret() # For sensitive data
476
schema["timeout"] = config.Integer(minimum=1, maximum=300)
477
schema["enabled_features"] = config.List()
478
schema["cache_size"] = config.Integer(minimum=0)
479
480
return schema
481
```
482
483
### Error Handling
484
485
```python
486
from mopidy.exceptions import ExtensionError
487
488
class MyExtension(Extension):
489
def validate_environment(self):
490
try:
491
self._check_dependencies()
492
self._check_credentials()
493
except Exception as e:
494
raise ExtensionError(f"MyExtension setup failed: {e}") from e
495
```
496
497
### Logging
498
499
```python
500
import logging
501
502
logger = logging.getLogger(__name__)
503
504
class MyExtension(Extension):
505
def setup(self, registry):
506
logger.info("Setting up MyExtension")
507
try:
508
# Setup logic
509
registry.add("backend", MyBackend)
510
logger.info("MyExtension setup complete")
511
except Exception as e:
512
logger.error(f"MyExtension setup failed: {e}")
513
raise
514
```