Skip to content

Commit 0876fcf

Browse files
authored
Add support for actions flow in Player config entries (#2572)
1 parent f7e821c commit 0876fcf

File tree

20 files changed

+193
-67
lines changed

20 files changed

+193
-67
lines changed

music_assistant/controllers/config.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from music_assistant.helpers.api import api_command
6969
from music_assistant.helpers.json import JSON_DECODE_EXCEPTIONS, async_json_dumps, async_json_loads
7070
from music_assistant.helpers.util import load_provider_module
71+
from music_assistant.models import ProviderModuleType
7172

7273
if TYPE_CHECKING:
7374
import asyncio
@@ -244,7 +245,7 @@ async def get_provider_config_value(self, instance_id: str, key: str) -> ConfigV
244245
return val
245246

246247
@api_command("config/providers/get_entries")
247-
async def get_provider_config_entries(
248+
async def get_provider_config_entries( # noqa: PLR0915
248249
self,
249250
provider_domain: str,
250251
instance_id: str | None = None,
@@ -260,13 +261,21 @@ async def get_provider_config_entries(
260261
values: the (intermediate) raw values for config entries sent with the action.
261262
"""
262263
# lookup provider manifest and module
264+
prov_mod: ProviderModuleType | None
263265
for manifest in self.mass.get_provider_manifests():
264266
if manifest.domain == provider_domain:
265-
prov_mod = await load_provider_module(provider_domain, manifest.requirements)
267+
try:
268+
prov_mod = await load_provider_module(provider_domain, manifest.requirements)
269+
except Exception as e:
270+
msg = f"Failed to load provider module for {provider_domain}: {e}"
271+
LOGGER.exception(msg)
272+
return []
266273
break
267274
else:
268275
msg = f"Unknown provider domain: {provider_domain}"
269-
raise KeyError(msg)
276+
LOGGER.exception(msg)
277+
return []
278+
270279
if values is None:
271280
values = self.get(f"{CONF_PROVIDERS}/{instance_id}/values", {}) if instance_id else {}
272281

@@ -416,14 +425,22 @@ async def get_player_configs(
416425
]
417426

418427
@api_command("config/players/get")
419-
async def get_player_config(self, player_id: str) -> PlayerConfig:
428+
async def get_player_config(
429+
self,
430+
player_id: str,
431+
action: str | None = None,
432+
values: dict[str, ConfigValueType] | None = None,
433+
) -> PlayerConfig:
420434
"""Return (full) configuration for a single player."""
421435
raw_conf: dict[str, Any]
422436
if raw_conf := self.get(f"{CONF_PLAYERS}/{player_id}"):
423437
if player := self.mass.players.get(player_id, False):
424438
raw_conf["default_name"] = player.display_name
425439
raw_conf["provider"] = player.provider.lookup_key
426-
conf_entries = await player.get_config_entries()
440+
# pass action and values to get_config_entries
441+
if values is None:
442+
values = raw_conf.get("values", {})
443+
conf_entries = await player.get_config_entries(action=action, values=values)
427444
else:
428445
# handle unavailable player and/or provider
429446
conf_entries = []
@@ -434,6 +451,29 @@ async def get_player_config(self, player_id: str) -> PlayerConfig:
434451
msg = f"No config found for player id {player_id}"
435452
raise KeyError(msg)
436453

454+
@api_command("config/players/get_entries")
455+
async def get_player_config_entries(
456+
self,
457+
player_id: str,
458+
action: str | None = None,
459+
values: dict[str, ConfigValueType] | None = None,
460+
) -> list[ConfigEntry]:
461+
"""
462+
Return Config entries to configure a player.
463+
464+
player_id: id of an existing player instance.
465+
action: [optional] action key called from config entries UI.
466+
values: the (intermediate) raw values for config entries sent with the action.
467+
"""
468+
if not (player := self.mass.players.get(player_id, False)):
469+
msg = f"Player {player_id} not found"
470+
raise KeyError(msg)
471+
472+
if values is None:
473+
values = self.get(f"{CONF_PLAYERS}/{player_id}/values", {})
474+
475+
return await player.get_config_entries(action=action, values=values)
476+
437477
@api_command("config/players/get_value")
438478
async def get_player_config_value(
439479
self,

music_assistant/controllers/players/sync_groups.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from typing import TYPE_CHECKING, cast
1414

1515
import shortuuid
16-
from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption
16+
from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption, ConfigValueType
1717
from music_assistant_models.constants import PLAYER_CONTROL_NONE
1818
from music_assistant_models.enums import (
1919
ConfigEntryType,
@@ -184,11 +184,15 @@ def can_group_with(self) -> set[str]:
184184
else:
185185
return set()
186186

187-
async def get_config_entries(self) -> list[ConfigEntry]:
187+
async def get_config_entries(
188+
self,
189+
action: str | None = None,
190+
values: dict[str, ConfigValueType] | None = None,
191+
) -> list[ConfigEntry]:
188192
"""Return all (provider/player specific) Config Entries for the given player (if any)."""
189193
entries: list[ConfigEntry] = [
190194
# default entries for player groups
191-
*await super().get_config_entries(),
195+
*await super().get_config_entries(action=action, values=values),
192196
# add syncgroup specific entries
193197
ConfigEntry(
194198
key=CONF_GROUP_MEMBERS,

music_assistant/models/player.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
from copy import deepcopy
1616
from typing import TYPE_CHECKING, Any, cast, final
1717

18-
from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption, PlayerConfig
18+
from music_assistant_models.config_entries import (
19+
ConfigEntry,
20+
ConfigValueOption,
21+
ConfigValueType,
22+
PlayerConfig,
23+
)
1924
from music_assistant_models.constants import (
2025
PLAYER_CONTROL_FAKE,
2126
PLAYER_CONTROL_NATIVE,
@@ -581,8 +586,14 @@ async def poll(self) -> None:
581586

582587
async def get_config_entries(
583588
self,
589+
action: str | None = None,
590+
values: dict[str, ConfigValueType] | None = None,
584591
) -> list[ConfigEntry]:
585-
"""Return all (provider/player specific) Config Entries for the player."""
592+
"""Return all (provider/player specific) Config Entries for the player.
593+
594+
action: [optional] action key called from config entries UI.
595+
values: the (intermediate) raw values for config entries sent with the action.
596+
"""
586597
# Return all base config entries for a player.
587598
# Feel free to override but ensure to include the base entries by calling super() first.
588599
# To override the default config entries, simply define an entry with the same key
@@ -1426,8 +1437,16 @@ def synced_to(self) -> str | None:
14261437
# default implementation: groups can't be synced
14271438
return None
14281439

1429-
async def get_config_entries(self) -> list[ConfigEntry]:
1430-
"""Return all (provider/player specific) Config Entries for the player."""
1440+
async def get_config_entries(
1441+
self,
1442+
action: str | None = None,
1443+
values: dict[str, ConfigValueType] | None = None,
1444+
) -> list[ConfigEntry]:
1445+
"""Return all (provider/player specific) Config Entries for the player.
1446+
1447+
action: [optional] action key called from config entries UI.
1448+
values: the (intermediate) raw values for config entries sent with the action.
1449+
"""
14311450
# Return all base config entries for a group player.
14321451
# Feel free to override but ensure to include the base entries by calling super() first.
14331452
# To override the default config entries, simply define an entry with the same key

music_assistant/providers/_demo_player_provider/player.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import TYPE_CHECKING
66

7-
from music_assistant_models.config_entries import ConfigEntry
7+
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
88
from music_assistant_models.enums import ConfigEntryType, PlaybackState, PlayerFeature, PlayerType
99
from music_assistant_models.player import PlayerSource
1010

@@ -84,14 +84,18 @@ def _source_list(self) -> list[PlayerSource]:
8484
),
8585
]
8686

87-
async def get_config_entries(self) -> list[ConfigEntry]:
87+
async def get_config_entries(
88+
self,
89+
action: str | None = None,
90+
values: dict[str, ConfigValueType] | None = None,
91+
) -> list[ConfigEntry]:
8892
"""Return all (provider/player specific) Config Entries for the player."""
8993
# OPTIONAL
9094
# this method is optional and should be implemented if you need player specific
9195
# configuration entries. If you do not need player specific configuration entries,
9296
# you can leave this method out completely to accept the default implementation.
9397
# Please note that you need to call the super() method to get the default entries.
94-
default_entries = await super().get_config_entries()
98+
default_entries = await super().get_config_entries(action=action, values=values)
9599
return [
96100
*default_entries,
97101
# example of a player specific config entry

music_assistant/providers/airplay/player.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import time
77
from typing import TYPE_CHECKING, cast
88

9-
from music_assistant_models.config_entries import ConfigEntry
9+
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
1010
from music_assistant_models.enums import (
1111
ConfigEntryType,
1212
ContentType,
@@ -104,10 +104,14 @@ def __init__(
104104
self._attr_can_group_with = {provider.lookup_key}
105105
self._attr_enabled_by_default = not is_broken_raop_model(manufacturer, model)
106106

107-
async def get_config_entries(self) -> list[ConfigEntry]:
107+
async def get_config_entries(
108+
self,
109+
action: str | None = None,
110+
values: dict[str, ConfigValueType] | None = None,
111+
) -> list[ConfigEntry]:
108112
"""Return all (provider/player specific) Config Entries for the given player (if any)."""
109-
base_entries = [
110-
*await super().get_config_entries(),
113+
base_entries = await super().get_config_entries(action=action, values=values)
114+
base_entries += [
111115
CONF_ENTRY_FLOW_MODE_ENFORCED,
112116
CONF_ENTRY_DEPRECATED_EQ_BASS,
113117
CONF_ENTRY_DEPRECATED_EQ_MID,

music_assistant/providers/alexa/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,13 @@ async def play_media(self, media: PlayerMedia) -> None:
368368
self._attr_playback_state = PlaybackState.PLAYING
369369
self.update_state()
370370

371-
async def get_config_entries(self) -> list[ConfigEntry]:
371+
async def get_config_entries(
372+
self,
373+
action: str | None = None,
374+
values: dict[str, ConfigValueType] | None = None,
375+
) -> list[ConfigEntry]:
372376
"""Return all (provider/player specific) Config Entries for the given player (if any)."""
373-
base_entries = await super().get_config_entries()
377+
base_entries = await super().get_config_entries(action=action, values=values)
374378
return [
375379
*base_entries,
376380
CONF_ENTRY_FLOW_MODE_ENFORCED,

music_assistant/providers/bluesound/player.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import time
77
from typing import TYPE_CHECKING
88

9-
from music_assistant_models.config_entries import ConfigEntry
9+
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
1010
from music_assistant_models.enums import PlaybackState, PlayerFeature, PlayerType
1111
from music_assistant_models.errors import PlayerCommandFailed
1212
from pyblu import Player as BluosPlayer
@@ -84,10 +84,14 @@ async def setup(self) -> None:
8484
self._attr_supported_features.add(PlayerFeature.VOLUME_SET)
8585
await self.mass.players.register_or_update(self)
8686

87-
async def get_config_entries(self) -> list[ConfigEntry]:
87+
async def get_config_entries(
88+
self,
89+
action: str | None = None,
90+
values: dict[str, ConfigValueType] | None = None,
91+
) -> list[ConfigEntry]:
8892
"""Return all (provider/player specific) Config Entries for the player."""
8993
return [
90-
*await super().get_config_entries(),
94+
*await super().get_config_entries(action=action, values=values),
9195
CONF_ENTRY_HTTP_PROFILE_DEFAULT_3,
9296
create_sample_rates_config_entry(
9397
max_sample_rate=192000,

music_assistant/providers/builtin_player/player.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from aiohttp import web
99
from music_assistant_models.builtin_player import BuiltinPlayerEvent, BuiltinPlayerState
10-
from music_assistant_models.config_entries import ConfigEntry
10+
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
1111
from music_assistant_models.constants import PLAYER_CONTROL_NATIVE
1212
from music_assistant_models.enums import (
1313
BuiltinPlayerEventType,
@@ -95,9 +95,13 @@ def register(self, player_name: str, update_state: bool = True) -> None:
9595
if update_state:
9696
self.update_state()
9797

98-
async def get_config_entries(self) -> list[ConfigEntry]:
98+
async def get_config_entries(
99+
self,
100+
action: str | None = None,
101+
values: dict[str, ConfigValueType] | None = None,
102+
) -> list[ConfigEntry]:
99103
"""Return all (provider/player specific) Config Entries for the player."""
100-
base_entries = await super().get_config_entries()
104+
base_entries = await super().get_config_entries(action=action, values=values)
101105
return [
102106
*base_entries,
103107
CONF_ENTRY_FLOW_MODE_ENFORCED,

music_assistant/providers/chromecast/player.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from typing import TYPE_CHECKING, Any, cast
88
from uuid import UUID
99

10+
from music_assistant_models.config_entries import ConfigEntry
11+
12+
if TYPE_CHECKING:
13+
from music_assistant_models.config_entries import ConfigValueType
1014
from music_assistant_models.enums import MediaType, PlaybackState, PlayerFeature, PlayerType
1115
from music_assistant_models.errors import PlayerUnavailableError
1216
from music_assistant_models.player import PlayerSource
@@ -104,9 +108,13 @@ def __init__(
104108
self.mz_controller = mz_controller
105109
self.cc.start()
106110

107-
async def get_config_entries(self) -> list[ConfigEntry]:
111+
async def get_config_entries(
112+
self,
113+
action: str | None = None,
114+
values: dict[str, ConfigValueType] | None = None,
115+
) -> list[ConfigEntry]:
108116
"""Return all (provider/player specific) Config Entries for the given player (if any)."""
109-
base_entries = await super().get_config_entries()
117+
base_entries = await super().get_config_entries(action=action, values=values)
110118
if self.type == PlayerType.GROUP:
111119
return [
112120
*base_entries,

music_assistant/providers/dlna/player.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from async_upnp_client.client import UpnpService, UpnpStateVariable
1111
from async_upnp_client.exceptions import UpnpError, UpnpResponseError
1212
from async_upnp_client.profiles.dlna import DmrDevice, TransportState
13-
from music_assistant_models.config_entries import ConfigEntry
13+
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
1414
from music_assistant_models.enums import PlaybackState, PlayerFeature
1515
from music_assistant_models.errors import PlayerUnavailableError
1616
from music_assistant_models.player import DeviceInfo, PlayerMedia
@@ -260,9 +260,11 @@ def _get_playback_state(self) -> PlaybackState | None:
260260

261261
async def get_config_entries(
262262
self,
263+
action: str | None = None,
264+
values: dict[str, ConfigValueType] | None = None,
263265
) -> list[ConfigEntry]:
264266
"""Return all (provider/player specific) Config Entries for the given player (if any)."""
265-
base_entries = await super().get_config_entries()
267+
base_entries = await super().get_config_entries(action=action, values=values)
266268
return base_entries + PLAYER_CONFIG_ENTRIES
267269

268270
# async def on_player_config_change(

0 commit comments

Comments
 (0)