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