/
/
/
1"""Configuration factory for creating typed config descriptors."""
2
3from __future__ import annotations
4
5from collections.abc import Callable
6from typing import overload
7
8from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
9from music_assistant_models.enums import ConfigEntryType
10
11from music_assistant.constants import CONF_ENTRY_UNOFFICIAL_PROVIDER
12
13from .descriptor import ConfigDescriptor
14
15# Global registry for all config entries
16_registry: list[ConfigEntry] = []
17
18
19class ConfigFactory:
20 """Factory class for creating config options with automatic category assignment."""
21
22 def __init__(self, category: str) -> None:
23 """Initialize factory with a specific category name."""
24 self.category = category
25
26 def bool_config(
27 self,
28 key: str,
29 label: str,
30 default: bool = False,
31 description: str = "",
32 ) -> ConfigDescriptor[bool]:
33 """Create boolean config options."""
34 return ConfigDescriptor(
35 cast=ConfigFactory.as_bool(default),
36 config_entry=self._create_entry(
37 key=key,
38 entry_type=ConfigEntryType.BOOLEAN,
39 label=label,
40 default_value=default,
41 description=description,
42 ),
43 )
44
45 def int_config(
46 self,
47 key: str,
48 label: str,
49 default: int = 25,
50 min_val: int = 1,
51 max_val: int = 100,
52 description: str = "",
53 ) -> ConfigDescriptor[int]:
54 """Create integer config options."""
55 return ConfigDescriptor(
56 cast=ConfigFactory.as_int(default, min_val, max_val),
57 config_entry=self._create_entry(
58 key=key,
59 entry_type=ConfigEntryType.INTEGER,
60 label=label,
61 default_value=default,
62 description=description,
63 value_range=(min_val, max_val),
64 ),
65 )
66
67 def str_list_config(
68 self, key: str, label: str, description: str = ""
69 ) -> ConfigDescriptor[list[str]]:
70 """Create string list config options (comma-separated tags)."""
71 return ConfigDescriptor(
72 cast=ConfigFactory.as_str_list(),
73 config_entry=self._create_entry(
74 key=key,
75 entry_type=ConfigEntryType.STRING,
76 label=label,
77 default_value="",
78 description=description,
79 ),
80 )
81
82 @overload
83 def str_config(
84 self, key: str, label: str, default: str, description: str = ""
85 ) -> ConfigDescriptor[str]: ...
86
87 @overload
88 def str_config(
89 self, key: str, label: str, default: None = None, description: str = ""
90 ) -> ConfigDescriptor[str | None]: ...
91
92 def str_config(
93 self, key: str, label: str, default: str | None = None, description: str = ""
94 ) -> ConfigDescriptor[str] | ConfigDescriptor[str | None]:
95 """Create string config options that can be None."""
96 return ConfigDescriptor(
97 cast=ConfigFactory.as_str(default),
98 config_entry=self._create_entry(
99 key=key,
100 entry_type=ConfigEntryType.STRING,
101 label=label,
102 default_value=default,
103 description=description,
104 ),
105 )
106
107 def secure_str_or_none_config(
108 self, key: str, label: str, description: str = ""
109 ) -> ConfigDescriptor[str | None]:
110 """Create secure string config options that can be None."""
111 return ConfigDescriptor(
112 cast=ConfigFactory.as_str(None),
113 config_entry=self._create_entry(
114 key=key,
115 entry_type=ConfigEntryType.SECURE_STRING,
116 label=label,
117 default_value="",
118 description=description,
119 ),
120 )
121
122 def _create_entry(
123 self,
124 key: str,
125 entry_type: ConfigEntryType,
126 label: str,
127 default_value: ConfigValueType,
128 description: str,
129 value_range: tuple[int, int] | None = None,
130 ) -> ConfigEntry:
131 """Create and register a ConfigEntry."""
132 entry = ConfigEntry(
133 key=key,
134 type=entry_type,
135 label=label,
136 required=False,
137 default_value=default_value,
138 description=description,
139 category=self.category,
140 range=value_range,
141 )
142 _registry.append(entry)
143 return entry
144
145 @classmethod
146 def as_bool(cls, default: bool = False) -> Callable[[ConfigValueType], bool]:
147 """Return a caster that converts a raw value to bool with default."""
148
149 def _cast(v: ConfigValueType) -> bool:
150 return bool(v) if v is not None else default
151
152 return _cast
153
154 @classmethod
155 def as_int(
156 cls, default: int = 0, min_val: int = 1, max_val: int = 100
157 ) -> Callable[[ConfigValueType], int]:
158 """Return a caster that converts a raw value to int with validation and default."""
159
160 def _cast(v: ConfigValueType) -> int:
161 if not isinstance(v, int) or v < min_val:
162 return default
163 return min(v, max_val)
164
165 return _cast
166
167 @classmethod
168 @overload
169 def as_str(cls, default: str) -> Callable[[ConfigValueType], str]: ...
170
171 @classmethod
172 @overload
173 def as_str(cls, default: str | None = None) -> Callable[[ConfigValueType], str | None]: ...
174
175 @classmethod
176 def as_str(cls, default: str | None = None) -> Callable[[ConfigValueType], str | None]:
177 """Return a caster that converts a raw value to str or None (no default)."""
178
179 def _cast(v: ConfigValueType) -> str | None:
180 return str(v) if v is not None else default
181
182 return _cast
183
184 @classmethod
185 def as_str_list(cls) -> Callable[[ConfigValueType], list[str]]:
186 """Return a caster that converts a raw value to list of strings."""
187
188 def _cast(v: ConfigValueType) -> list[str]:
189 if not v or not isinstance(v, str):
190 return []
191 # Split by comma and clean up whitespace
192 return [tag.strip() for tag in v.split(",") if tag.strip()]
193
194 return _cast
195
196
197def get_setup_config_entries() -> tuple[ConfigEntry, ...]:
198 """Return the (credential) config entries collected by the setup flow."""
199 return tuple(_registry)
200
201
202async def build_config_entries() -> tuple[ConfigEntry, ...]:
203 """Return Config entries to setup this provider."""
204 return (CONF_ENTRY_UNOFFICIAL_PROVIDER,)
205