or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

audio-system.mdbackend-system.mdconfiguration.mdcore-controllers.mdextension-system.mdindex.mdmodels.md

extension-system.mddocs/

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

```