241
241
translation_key = CONF_PLATFORM ,
242
242
)
243
243
)
244
- RESET_IF_EMPTY = {CONF_OPTIONS }
245
-
246
244
TEMPLATE_SELECTOR = TemplateSelector (TemplateSelectorConfig ())
247
245
248
246
SUBENTRY_AVAILABILITY_SCHEMA = vol .Schema (
@@ -346,7 +344,12 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
346
344
cv .positive_int ,
347
345
section = "advanced_settings" ,
348
346
),
349
- CONF_OPTIONS : PlatformField (OPTIONS_SELECTOR , False , cv .ensure_list ),
347
+ CONF_OPTIONS : PlatformField (
348
+ OPTIONS_SELECTOR ,
349
+ False ,
350
+ cv .ensure_list ,
351
+ conditions = ({"device_class" : "enum" },),
352
+ ),
350
353
},
351
354
}
352
355
PLATFORM_MQTT_FIELDS = {
@@ -379,7 +382,8 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
379
382
},
380
383
}
381
384
ENTITY_CONFIG_VALIDATOR : dict [
382
- str , Callable [[dict [str , Any ], dict [str , str ]], dict [str , Any ]] | None
385
+ str ,
386
+ Callable [[dict [str , Any ], dict [str , str ], list [str ] | None ], dict [str , Any ]] | None ,
383
387
] = {
384
388
Platform .NOTIFY .value : None ,
385
389
Platform .SENSOR .value : validate_sensor_state_and_device_class_config ,
@@ -445,22 +449,36 @@ def validate_field(
445
449
errors [field ] = error
446
450
447
451
452
+ @callback
453
+ def _check_conditions (
454
+ platform_field : PlatformField , component : dict [str , Any ] | None = None
455
+ ) -> bool :
456
+ """Only include field if one of conditions match, or no conditions are set."""
457
+ if platform_field .conditions is None or component is None :
458
+ return True
459
+ return any (
460
+ all (component .get (key ) == value for key , value in condition .items ())
461
+ for condition in platform_field .conditions
462
+ )
463
+
464
+
448
465
@callback
449
466
def validate_user_input (
450
467
user_input : dict [str , Any ],
451
468
data_schema_fields : dict [str , PlatformField ],
452
469
errors : dict [str , str ],
453
- config_validator : Callable [[dict [str , Any ], dict [str , str ]], dict [str , str ]]
470
+ component_data : dict [str , Any ] | None ,
471
+ config_validator : Callable [
472
+ [dict [str , Any ], dict [str , str ], list [str ] | None ], dict [str , str ]
473
+ ]
454
474
| None = None ,
455
- component_data : dict [str , Any ] | None = None ,
456
475
) -> dict [str , Any ]:
457
476
"""Validate user input."""
458
477
# Merge sections
478
+ reset_fields : list [str ] = []
459
479
merged_user_input : dict [str , Any ] = {}
460
480
for key , value in user_input .items ():
461
481
# Omit empty lists that are not allowed to be empty
462
- if key in RESET_IF_EMPTY and not value :
463
- continue
464
482
if isinstance (value , dict ):
465
483
merged_user_input .update (value )
466
484
else :
@@ -474,10 +492,30 @@ def validate_user_input(
474
492
errors [field ] = data_schema_fields [field ].error or "invalid_input"
475
493
476
494
if config_validator is not None :
477
- config = merged_user_input
478
- if component_data is not None :
479
- config |= component_data
480
- config_validator (config , errors )
495
+ if TYPE_CHECKING :
496
+ assert component_data is not None
497
+ schema_fields = tuple (
498
+ {
499
+ key
500
+ for key , platform_field in data_schema_fields .items ()
501
+ if _check_conditions (platform_field , component_data )
502
+ }
503
+ - set (merged_user_input )
504
+ )
505
+ config_validator (
506
+ {
507
+ key : value
508
+ for key , value in component_data .items ()
509
+ if key not in schema_fields
510
+ }
511
+ | merged_user_input ,
512
+ errors ,
513
+ reset_fields ,
514
+ )
515
+
516
+ for field in reset_fields :
517
+ if component_data and field in component_data :
518
+ del component_data [field ]
481
519
482
520
return merged_user_input
483
521
@@ -490,25 +528,14 @@ def data_schema_from_fields(
490
528
user_input : dict [str , Any ] | None = None ,
491
529
) -> vol .Schema :
492
530
"""Generate custom data schema from platform fields."""
493
-
494
- def _check_conditions (
495
- platform_field : PlatformField ,
496
- ) -> bool :
497
- """Only include field if one of conditions match, or no conditions are set."""
498
- if platform_field .conditions is None or component is None :
499
- return True
500
- return any (
501
- all (component .get (key ) == value for key , value in condition .items ())
502
- for condition in platform_field .conditions
503
- )
504
-
505
- user_data = component
531
+ user_data = deepcopy (component )
506
532
if user_data is not None and user_input is not None :
507
533
user_data |= user_input
508
534
sections : dict [str | None , None ] = {
509
535
field_details .section : None for field_details in data_schema_fields .values ()
510
536
}
511
537
data_schema : dict [Any , Any ] = {}
538
+ all_data_element_options : set [Any ] = set ()
512
539
for schema_section in sections :
513
540
data_schema_element = {
514
541
vol .Required (field_name , default = field_details .default )
@@ -521,23 +548,25 @@ def _check_conditions(
521
548
for field_name , field_details in data_schema_fields .items ()
522
549
if field_details .section == schema_section
523
550
and (not field_details .exclude_from_reconfig or not reconfig )
524
- and _check_conditions (field_details )
551
+ and _check_conditions (field_details , user_data )
525
552
}
553
+ data_element_options = set (data_schema_element )
554
+ all_data_element_options |= data_element_options
555
+ if schema_section is None :
556
+ data_schema .update (data_schema_element )
557
+ continue
526
558
collapsed = (
527
- bool (set ( data_schema_element ) - set (user_data )) # type: ignore[arg-type]
559
+ bool (data_element_options - set (user_data )) # type: ignore[arg-type]
528
560
if user_data is not None
529
561
else True
530
562
)
531
- if schema_section is None :
532
- data_schema .update (data_schema_element )
533
- continue
534
563
data_schema [vol .Optional (schema_section )] = section (
535
564
vol .Schema (data_schema_element ), SectionConfig ({"collapsed" : collapsed })
536
565
)
537
566
538
567
# Reset all fields from the component not in the schema
539
568
if component :
540
- filtered_fields = set (data_schema_fields ) - set ( data_schema )
569
+ filtered_fields = set (data_schema_fields ) - all_data_element_options
541
570
for field in filtered_fields :
542
571
if field in component :
543
572
del component [field ]
@@ -1066,7 +1095,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
1066
1095
1067
1096
@callback
1068
1097
def update_component_fields (
1069
- self , data_schema : vol .Schema , user_input : dict [str , Any ]
1098
+ self , data_schema : vol .Schema , merged_user_input : dict [str , Any ]
1070
1099
) -> None :
1071
1100
"""Update the componment fields."""
1072
1101
if TYPE_CHECKING :
@@ -1076,10 +1105,10 @@ def update_component_fields(
1076
1105
for field in [
1077
1106
form_field
1078
1107
for form_field in data_schema .schema
1079
- if form_field in component_data and form_field not in user_input
1108
+ if form_field in component_data and form_field not in merged_user_input
1080
1109
]:
1081
1110
component_data .pop (field )
1082
- component_data .update (user_input )
1111
+ component_data .update (merged_user_input )
1083
1112
1084
1113
@callback
1085
1114
def generate_names (self ) -> tuple [str , str ]:
@@ -1097,7 +1126,7 @@ def generate_names(self) -> tuple[str, str]:
1097
1126
1098
1127
@callback
1099
1128
def add_suggested_values_from_component_data_to_schema (
1100
- self , data_schema : vol .Schema , data_schema_fields : dict [ str , PlatformField ]
1129
+ self , data_schema : vol .Schema
1101
1130
) -> vol .Schema :
1102
1131
"""Add suggestions from component data to data schema."""
1103
1132
if TYPE_CHECKING :
@@ -1123,14 +1152,15 @@ def _apply_suggested_value(
1123
1152
if not isinstance (value , section ):
1124
1153
continue
1125
1154
data_section_schema = value .schema .schema
1126
- value . schema = vol .Schema (
1155
+ new_schema = vol .Schema (
1127
1156
{
1128
1157
_apply_suggested_value (
1129
1158
section_field , component .get (section_field )
1130
1159
): section_field_selector
1131
1160
for section_field , section_field_selector in data_section_schema .items ()
1132
1161
}
1133
1162
)
1163
+ value .schema = new_schema
1134
1164
1135
1165
return vol .Schema (schema )
1136
1166
@@ -1182,16 +1212,16 @@ async def async_step_entity(
1182
1212
data_schema_fields = COMMON_ENTITY_FIELDS
1183
1213
entity_name_label : str = ""
1184
1214
platform_label : str = ""
1215
+ component : dict [str , Any ] | None = None
1185
1216
if reconfig := (self ._component_id is not None ):
1186
- name : str | None = self ._subentry_data ["components" ][
1187
- self ._component_id
1188
- ].get (CONF_NAME )
1217
+ component = self ._subentry_data ["components" ][self ._component_id ]
1218
+ name : str | None = component .get (CONF_NAME )
1189
1219
platform_label = f"{ self ._subentry_data ['components' ][self ._component_id ][CONF_PLATFORM ]} "
1190
1220
entity_name_label = f" ({ name } )" if name is not None else ""
1191
1221
data_schema = data_schema_from_fields (data_schema_fields , reconfig = reconfig )
1192
1222
if user_input is not None :
1193
1223
merged_user_input = validate_user_input (
1194
- user_input , data_schema_fields , errors
1224
+ user_input , data_schema_fields , errors , component
1195
1225
)
1196
1226
if not errors :
1197
1227
if self ._component_id is None :
@@ -1202,7 +1232,7 @@ async def async_step_entity(
1202
1232
data_schema = self .add_suggested_values_to_schema (data_schema , user_input )
1203
1233
elif self .source == SOURCE_RECONFIGURE and self ._component_id is not None :
1204
1234
data_schema = self .add_suggested_values_from_component_data_to_schema (
1205
- data_schema , data_schema_fields
1235
+ data_schema
1206
1236
)
1207
1237
device_name = self ._subentry_data [CONF_DEVICE ][CONF_NAME ]
1208
1238
return self .async_show_form (
@@ -1291,6 +1321,7 @@ async def async_step_entity_platform_config(
1291
1321
user_input ,
1292
1322
data_schema_fields ,
1293
1323
errors ,
1324
+ component ,
1294
1325
ENTITY_CONFIG_VALIDATOR [platform ],
1295
1326
)
1296
1327
if not errors :
@@ -1300,7 +1331,7 @@ async def async_step_entity_platform_config(
1300
1331
data_schema = self .add_suggested_values_to_schema (data_schema , user_input )
1301
1332
else :
1302
1333
data_schema = self .add_suggested_values_from_component_data_to_schema (
1303
- data_schema , data_schema_fields
1334
+ data_schema
1304
1335
)
1305
1336
1306
1337
device_name , full_entity_name = self .generate_names ()
@@ -1340,8 +1371,8 @@ async def async_step_mqtt_platform_config(
1340
1371
user_input ,
1341
1372
data_schema_fields ,
1342
1373
errors ,
1374
+ component ,
1343
1375
ENTITY_CONFIG_VALIDATOR [platform ],
1344
- self ._subentry_data ["components" ][self ._component_id ],
1345
1376
)
1346
1377
if not errors :
1347
1378
self .update_component_fields (data_schema , merged_user_input )
@@ -1353,7 +1384,7 @@ async def async_step_mqtt_platform_config(
1353
1384
data_schema = self .add_suggested_values_to_schema (data_schema , user_input )
1354
1385
else :
1355
1386
data_schema = self .add_suggested_values_from_component_data_to_schema (
1356
- data_schema , data_schema_fields
1387
+ data_schema
1357
1388
)
1358
1389
device_name , full_entity_name = self .generate_names ()
1359
1390
return self .async_show_form (
0 commit comments