66from collections .abc import Callable , Iterable
77from dataclasses import dataclass , field
88from enum import Enum
9- from typing import Any , cast
9+ from typing import Any , Final , cast
1010
1111from mashumaro import DataClassDictMixin
1212
2121ConfigValueType = (
2222 # order is important here for the (de)serialization!
2323 # https://github.com/Fatal1ty/mashumaro/pull/256
24- bool | float | int | str | tuple [int , int ]
25- )
26- ConfigValueTypeMulti = (
27- # order is important here for the (de)serialization!
28- # https://github.com/Fatal1ty/mashumaro/pull/256
29- list [bool ] | list [float ] | list [int ] | list [str ] | list [tuple [int , int ]]
24+ bool | float | int | str | list [float ] | list [int ] | list [str ] | list [bool ] | None
3025)
3126
32- ConfigValueTypes = ConfigValueType | ConfigValueTypeMulti | None
3327
3428ConfigEntryTypeMap : dict [ConfigEntryType , type [ConfigValueType ]] = {
3529 ConfigEntryType .BOOLEAN : bool ,
3630 ConfigEntryType .STRING : str ,
3731 ConfigEntryType .SECURE_STRING : str ,
3832 ConfigEntryType .INTEGER : int ,
39- ConfigEntryType .INTEGER_TUPLE : tuple [ int , int ] ,
33+ ConfigEntryType .SPLITTED_STRING : str ,
4034 ConfigEntryType .FLOAT : float ,
4135 ConfigEntryType .LABEL : str ,
4236 ConfigEntryType .DIVIDER : str ,
@@ -61,6 +55,9 @@ class ConfigValueOption(DataClassDictMixin):
6155 value : ConfigValueType
6256
6357
58+ MULTI_VALUE_SPLITTER : Final [str ] = "||"
59+
60+
6461@dataclass (kw_only = True )
6562class ConfigEntry (DataClassDictMixin ):
6663 """Model for a Config Entry.
@@ -75,7 +72,7 @@ class ConfigEntry(DataClassDictMixin):
7572 type : ConfigEntryType
7673 # label: default label when no translation for the key is present
7774 label : str
78- default_value : ConfigValueType | ConfigValueTypeMulti | None = None
75+ default_value : ConfigValueType = None
7976 required : bool = True
8077 # options [optional]: select from list of possible values/options
8178 options : list [ConfigValueOption ] = field (default_factory = list )
@@ -102,81 +99,58 @@ class ConfigEntry(DataClassDictMixin):
10299 # action_label: default label for the action when no translation for the action is present
103100 action_label : str | None = None
104101 # value: set by the config manager/flow (or in rare cases by the provider itself)
105- value : ConfigValueType | ConfigValueTypeMulti | None = None
102+ value : ConfigValueType = None
106103
107104 def __post_init__ (self ) -> None :
108105 """Run some basic sanity checks after init."""
109- if self .multi_value and not isinstance (self , MultiValueConfigEntry ):
110- raise ValueError (f"{ self .key } must be a MultiValueConfigEntry" )
111106 if self .type in UI_ONLY :
112107 self .required = False
113108
114109 def parse_value (
115110 self ,
116- value : ConfigValueTypes ,
111+ value : ConfigValueType ,
117112 allow_none : bool = True ,
118- ) -> ConfigValueTypes :
113+ ) -> ConfigValueType :
119114 """Parse value from the config entry details and plain value."""
120115 if self .type == ConfigEntryType .LABEL :
121116 value = self .label
122117 elif self .type in UI_ONLY :
123- value = cast ( str | None , value or self .default_value )
118+ value = value or self .default_value
124119
125- if value is None and ( not self . required or allow_none ) :
126- value = cast ( ConfigValueType | None , self .default_value )
120+ if value is None :
121+ value = self .default_value
127122
128123 if isinstance (value , list ) and not self .multi_value :
129124 raise ValueError (f"{ self .key } must be a single value" )
125+ if self .multi_value and not isinstance (value , list ):
126+ raise ValueError (f"value for { self .key } must be a list" )
130127
131- if value is None and self .required :
132- raise ValueError (f"{ self .key } is required" )
133-
134- self .value = value
135- return self .value
136-
137-
138- @dataclass (kw_only = True )
139- class MultiValueConfigEntry (ConfigEntry ):
140- """Model for a Config Entry which allows multiple values to be selected.
141-
142- This is a helper class to handle multiple values in a single config entry,
143- otherwise the serializer gets confused with the types.
144- """
145-
146- multi_value : bool = True
147- default_value : ConfigValueTypeMulti = field (default_factory = list )
148- value : ConfigValueTypeMulti | None = None
149-
150- def parse_value ( # type: ignore[override]
151- self ,
152- value : ConfigValueTypeMulti | None ,
153- allow_none : bool = True ,
154- ) -> ConfigValueTypeMulti :
155- """Parse value from the config entry details and plain value."""
156- if value is None and (not self .required or allow_none ):
157- value = self .default_value
158- if value is None :
128+ if value is None and self .required and not allow_none :
159129 raise ValueError (f"{ self .key } is required" )
160- if self .multi_value and not isinstance (value , list ):
161- raise ValueError (f"{ self .key } must be a list" )
162130
163131 self .value = value
164132 return self .value
165133
166- def __post_init__ (self ) -> None :
167- """Run some basic sanity checks after init."""
168- super ().__post_init__ ()
169- if self .multi_value and not isinstance (self .default_value , list ):
170- raise ValueError (f"default value for { self .key } must be a list" )
134+ def get_splitted_values (self ) -> tuple [str , ...] | list [tuple [str , ...]]:
135+ """Return split values for SPLITTED_STRING type."""
136+ if self .type != ConfigEntryType .SPLITTED_STRING :
137+ raise ValueError (f"{ self .key } is not a SPLITTED_STRING" )
138+ value = self .value or self .default_value
139+ if self .multi_value :
140+ assert isinstance (value , list )
141+ value = cast (list [str ], value )
142+ return [tuple (x .split (MULTI_VALUE_SPLITTER , 1 )) for x in value ]
143+ assert isinstance (value , str )
144+ return tuple (value .split (MULTI_VALUE_SPLITTER , 1 ))
171145
172146
173147@dataclass
174148class Config (DataClassDictMixin ):
175149 """Base Configuration object."""
176150
177- values : dict [str , ConfigEntry | MultiValueConfigEntry ]
151+ values : dict [str , ConfigEntry ]
178152
179- def get_value (self , key : str ) -> ConfigValueTypes :
153+ def get_value (self , key : str ) -> ConfigValueType :
180154 """Return config value for given key."""
181155 config_value = self .values [key ]
182156 if config_value .type == ConfigEntryType .SECURE_STRING and config_value .value :
@@ -189,7 +163,7 @@ def get_value(self, key: str) -> ConfigValueTypes:
189163 @classmethod
190164 def parse (
191165 cls ,
192- config_entries : Iterable [ConfigEntry | MultiValueConfigEntry ],
166+ config_entries : Iterable [ConfigEntry ],
193167 raw : dict [str , Any ],
194168 ) -> Config :
195169 """Parse Config from the raw values (as stored in persistent storage)."""
@@ -199,10 +173,7 @@ def parse(
199173 if isinstance (entry .default_value , Enum ):
200174 entry .default_value = entry .default_value .value # type: ignore[unreachable]
201175 # create a copy of the entry
202- if entry .multi_value :
203- conf .values [entry .key ] = MultiValueConfigEntry .from_dict (entry .to_dict ())
204- else :
205- conf .values [entry .key ] = ConfigEntry .from_dict (entry .to_dict ())
176+ conf .values [entry .key ] = ConfigEntry .from_dict (entry .to_dict ())
206177 conf .values [entry .key ].parse_value (
207178 raw .get ("values" , {}).get (entry .key ), allow_none = True
208179 )
@@ -212,8 +183,8 @@ def to_raw(self) -> dict[str, Any]:
212183 """Return minimized/raw dict to store in persistent storage."""
213184
214185 def _handle_value (
215- value : ConfigEntry | MultiValueConfigEntry ,
216- ) -> ConfigValueTypes :
186+ value : ConfigEntry ,
187+ ) -> ConfigValueType :
217188 if value .type == ConfigEntryType .SECURE_STRING :
218189 assert isinstance (value .value , str )
219190 assert ENCRYPT_CALLBACK is not None
@@ -238,7 +209,7 @@ def __post_serialize__(self, d: dict[str, Any]) -> dict[str, Any]:
238209 d ["values" ][key ]["value" ] = SECURE_STRING_SUBSTITUTE
239210 return d
240211
241- def update (self , update : dict [str , ConfigValueTypes ]) -> set [str ]:
212+ def update (self , update : dict [str , ConfigValueType ]) -> set [str ]:
242213 """Update Config with updated values."""
243214 changed_keys : set [str ] = set ()
244215
@@ -261,7 +232,7 @@ def update(self, update: dict[str, ConfigValueTypes]) -> set[str]:
261232 continue
262233 cur_val = self .values [key ].value if key in self .values else None
263234 # parse entry to do type validation
264- parsed_val = self .values [key ].parse_value (new_val ) # type: ignore[arg-type]
235+ parsed_val = self .values [key ].parse_value (new_val )
265236 if cur_val != parsed_val :
266237 changed_keys .add (f"values/{ key } " )
267238
@@ -272,7 +243,7 @@ def validate(self) -> None:
272243 # For now we just use the parse method to check for not allowed None values
273244 # this can be extended later
274245 for value in self .values .values ():
275- value .parse_value (value .value , allow_none = False ) # type: ignore[arg-type]
246+ value .parse_value (value .value , allow_none = False )
276247
277248
278249@dataclass
0 commit comments