From 7af731694f97a7f4249bc8ad1f2d603ee5ca8274 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Fri, 23 May 2025 08:05:43 -0700 Subject: [PATCH] Support readonly selectors in config_flows (#129456) * Allow disabled selectors in config flows. Show hidden options for history_stats. * fix tests * use optional instead of required * rename flag to readonly * rename to read_only * Update to use read_only field as part of selector definition * lint fix * Fix test * All selectors --- .../components/history_stats/config_flow.py | 15 ++ .../components/history_stats/strings.json | 12 ++ .../helpers/schema_config_entry_flow.py | 5 + homeassistant/helpers/selector.py | 165 ++++++++++-------- 4 files changed, 123 insertions(+), 74 deletions(-) diff --git a/homeassistant/components/history_stats/config_flow.py b/homeassistant/components/history_stats/config_flow.py index 8dbca3b1939..96c8f319fbc 100644 --- a/homeassistant/components/history_stats/config_flow.py +++ b/homeassistant/components/history_stats/config_flow.py @@ -18,6 +18,7 @@ from homeassistant.helpers.selector import ( DurationSelector, DurationSelectorConfig, EntitySelector, + EntitySelectorConfig, SelectSelector, SelectSelectorConfig, SelectSelectorMode, @@ -66,6 +67,20 @@ DATA_SCHEMA_SETUP = vol.Schema( ) DATA_SCHEMA_OPTIONS = vol.Schema( { + vol.Optional(CONF_ENTITY_ID): EntitySelector( + EntitySelectorConfig(read_only=True) + ), + vol.Optional(CONF_STATE): TextSelector( + TextSelectorConfig(multiple=True, read_only=True) + ), + vol.Optional(CONF_TYPE): SelectSelector( + SelectSelectorConfig( + options=CONF_TYPE_KEYS, + mode=SelectSelectorMode.DROPDOWN, + translation_key=CONF_TYPE, + read_only=True, + ) + ), vol.Optional(CONF_START): TemplateSelector(), vol.Optional(CONF_END): TemplateSelector(), vol.Optional(CONF_DURATION): DurationSelector( diff --git a/homeassistant/components/history_stats/strings.json b/homeassistant/components/history_stats/strings.json index e10a72f6742..7a33099cf99 100644 --- a/homeassistant/components/history_stats/strings.json +++ b/homeassistant/components/history_stats/strings.json @@ -26,11 +26,17 @@ "options": { "description": "Read the documentation for further details on how to configure the history stats sensor using these options.", "data": { + "entity_id": "[%key:component::history_stats::config::step::user::data::entity_id%]", + "state": "[%key:component::history_stats::config::step::user::data::state%]", + "type": "[%key:component::history_stats::config::step::user::data::type%]", "start": "Start", "end": "End", "duration": "Duration" }, "data_description": { + "entity_id": "[%key:component::history_stats::config::step::user::data_description::entity_id%]", + "state": "[%key:component::history_stats::config::step::user::data_description::state%]", + "type": "[%key:component::history_stats::config::step::user::data_description::type%]", "start": "When to start the measure (timestamp or datetime). Can be a template.", "end": "When to stop the measure (timestamp or datetime). Can be a template", "duration": "Duration of the measure." @@ -49,11 +55,17 @@ "init": { "description": "[%key:component::history_stats::config::step::options::description%]", "data": { + "entity_id": "[%key:component::history_stats::config::step::user::data::entity_id%]", + "state": "[%key:component::history_stats::config::step::user::data::state%]", + "type": "[%key:component::history_stats::config::step::user::data::type%]", "start": "[%key:component::history_stats::config::step::options::data::start%]", "end": "[%key:component::history_stats::config::step::options::data::end%]", "duration": "[%key:component::history_stats::config::step::options::data::duration%]" }, "data_description": { + "entity_id": "[%key:component::history_stats::config::step::user::data_description::entity_id%]", + "state": "[%key:component::history_stats::config::step::user::data_description::state%]", + "type": "[%key:component::history_stats::config::step::user::data_description::type%]", "start": "[%key:component::history_stats::config::step::options::data_description::start%]", "end": "[%key:component::history_stats::config::step::options::data_description::end%]", "duration": "[%key:component::history_stats::config::step::options::data_description::duration%]" diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index af8c4c6402d..93d9a3d06f1 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -214,6 +214,11 @@ class SchemaCommonFlowHandler: and key.description.get("advanced") and not self._handler.show_advanced_options ) + and not ( + # don't remove read_only keys + isinstance(data_schema.schema[key], selector.Selector) + and data_schema.schema[key].config.get("read_only") + ) ): # Key not present, delete keys old value (if present) too values.pop(key.schema, None) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index f2c76d1d019..2d7fd51cac7 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -131,6 +131,19 @@ def _validate_supported_features(supported_features: int | list[str]) -> int: return feature_mask +BASE_SELECTOR_CONFIG_SCHEMA = vol.Schema( + { + vol.Optional("read_only"): bool, + } +) + + +class BaseSelectorConfig(TypedDict, total=False): + """Class to common options of all selectors.""" + + read_only: bool + + ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema( { # Integration that provided the entity @@ -183,7 +196,7 @@ class DeviceFilterSelectorConfig(TypedDict, total=False): model_id: str -class ActionSelectorConfig(TypedDict): +class ActionSelectorConfig(BaseSelectorConfig): """Class to represent an action selector config.""" @@ -193,7 +206,7 @@ class ActionSelector(Selector[ActionSelectorConfig]): selector_type = "action" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: ActionSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -204,7 +217,7 @@ class ActionSelector(Selector[ActionSelectorConfig]): return data -class AddonSelectorConfig(TypedDict, total=False): +class AddonSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an addon selector config.""" name: str @@ -217,7 +230,7 @@ class AddonSelector(Selector[AddonSelectorConfig]): selector_type = "addon" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("name"): str, vol.Optional("slug"): str, @@ -234,7 +247,7 @@ class AddonSelector(Selector[AddonSelectorConfig]): return addon -class AreaSelectorConfig(TypedDict, total=False): +class AreaSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an area selector config.""" entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] @@ -248,7 +261,7 @@ class AreaSelector(Selector[AreaSelectorConfig]): selector_type = "area" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("entity"): vol.All( cv.ensure_list, @@ -276,7 +289,7 @@ class AreaSelector(Selector[AreaSelectorConfig]): return [vol.Schema(str)(val) for val in data] -class AssistPipelineSelectorConfig(TypedDict, total=False): +class AssistPipelineSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an assist pipeline selector config.""" @@ -286,7 +299,7 @@ class AssistPipelineSelector(Selector[AssistPipelineSelectorConfig]): selector_type = "assist_pipeline" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: AssistPipelineSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -298,7 +311,7 @@ class AssistPipelineSelector(Selector[AssistPipelineSelectorConfig]): return pipeline -class AttributeSelectorConfig(TypedDict, total=False): +class AttributeSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an attribute selector config.""" entity_id: Required[str] @@ -311,7 +324,7 @@ class AttributeSelector(Selector[AttributeSelectorConfig]): selector_type = "attribute" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Required("entity_id"): cv.entity_id, # hide_attributes is used to hide attributes in the frontend. @@ -330,7 +343,7 @@ class AttributeSelector(Selector[AttributeSelectorConfig]): return attribute -class BackupLocationSelectorConfig(TypedDict, total=False): +class BackupLocationSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a backup location selector config.""" @@ -340,7 +353,7 @@ class BackupLocationSelector(Selector[BackupLocationSelectorConfig]): selector_type = "backup_location" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: BackupLocationSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -352,7 +365,7 @@ class BackupLocationSelector(Selector[BackupLocationSelectorConfig]): return name -class BooleanSelectorConfig(TypedDict): +class BooleanSelectorConfig(BaseSelectorConfig): """Class to represent a boolean selector config.""" @@ -362,7 +375,7 @@ class BooleanSelector(Selector[BooleanSelectorConfig]): selector_type = "boolean" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: BooleanSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -374,7 +387,7 @@ class BooleanSelector(Selector[BooleanSelectorConfig]): return value -class ColorRGBSelectorConfig(TypedDict): +class ColorRGBSelectorConfig(BaseSelectorConfig): """Class to represent a color RGB selector config.""" @@ -384,7 +397,7 @@ class ColorRGBSelector(Selector[ColorRGBSelectorConfig]): selector_type = "color_rgb" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: ColorRGBSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -396,7 +409,7 @@ class ColorRGBSelector(Selector[ColorRGBSelectorConfig]): return value -class ColorTempSelectorConfig(TypedDict, total=False): +class ColorTempSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a color temp selector config.""" unit: ColorTempSelectorUnit @@ -419,7 +432,7 @@ class ColorTempSelector(Selector[ColorTempSelectorConfig]): selector_type = "color_temp" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("unit", default=ColorTempSelectorUnit.MIRED): vol.All( vol.Coerce(ColorTempSelectorUnit), lambda val: val.value @@ -456,7 +469,7 @@ class ColorTempSelector(Selector[ColorTempSelectorConfig]): return value -class ConditionSelectorConfig(TypedDict): +class ConditionSelectorConfig(BaseSelectorConfig): """Class to represent an condition selector config.""" @@ -466,7 +479,7 @@ class ConditionSelector(Selector[ConditionSelectorConfig]): selector_type = "condition" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: ConditionSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -477,7 +490,7 @@ class ConditionSelector(Selector[ConditionSelectorConfig]): return vol.Schema(cv.CONDITIONS_SCHEMA)(data) -class ConfigEntrySelectorConfig(TypedDict, total=False): +class ConfigEntrySelectorConfig(BaseSelectorConfig, total=False): """Class to represent a config entry selector config.""" integration: str @@ -489,7 +502,7 @@ class ConfigEntrySelector(Selector[ConfigEntrySelectorConfig]): selector_type = "config_entry" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("integration"): str, } @@ -505,7 +518,7 @@ class ConfigEntrySelector(Selector[ConfigEntrySelectorConfig]): return config -class ConstantSelectorConfig(TypedDict, total=False): +class ConstantSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a constant selector config.""" label: str @@ -519,7 +532,7 @@ class ConstantSelector(Selector[ConstantSelectorConfig]): selector_type = "constant" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("label"): str, vol.Optional("translation_key"): cv.string, @@ -546,7 +559,7 @@ class QrErrorCorrectionLevel(StrEnum): HIGH = "high" -class QrCodeSelectorConfig(TypedDict, total=False): +class QrCodeSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a QR code selector config.""" data: str @@ -560,7 +573,7 @@ class QrCodeSelector(Selector[QrCodeSelectorConfig]): selector_type = "qr_code" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Required("data"): str, vol.Optional("scale"): int, @@ -580,7 +593,7 @@ class QrCodeSelector(Selector[QrCodeSelectorConfig]): return self.config["data"] -class ConversationAgentSelectorConfig(TypedDict, total=False): +class ConversationAgentSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a conversation agent selector config.""" language: str @@ -592,7 +605,7 @@ class ConversationAgentSelector(Selector[ConversationAgentSelectorConfig]): selector_type = "conversation_agent" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("language"): str, } @@ -608,7 +621,7 @@ class ConversationAgentSelector(Selector[ConversationAgentSelectorConfig]): return agent -class CountrySelectorConfig(TypedDict, total=False): +class CountrySelectorConfig(BaseSelectorConfig, total=False): """Class to represent a country selector config.""" countries: list[str] @@ -621,7 +634,7 @@ class CountrySelector(Selector[CountrySelectorConfig]): selector_type = "country" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("countries"): [str], vol.Optional("no_sort", default=False): cv.boolean, @@ -642,7 +655,7 @@ class CountrySelector(Selector[CountrySelectorConfig]): return country -class DateSelectorConfig(TypedDict): +class DateSelectorConfig(BaseSelectorConfig): """Class to represent a date selector config.""" @@ -652,7 +665,7 @@ class DateSelector(Selector[DateSelectorConfig]): selector_type = "date" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: DateSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -664,7 +677,7 @@ class DateSelector(Selector[DateSelectorConfig]): return data -class DateTimeSelectorConfig(TypedDict): +class DateTimeSelectorConfig(BaseSelectorConfig): """Class to represent a date time selector config.""" @@ -674,7 +687,7 @@ class DateTimeSelector(Selector[DateTimeSelectorConfig]): selector_type = "datetime" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: DateTimeSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -686,7 +699,7 @@ class DateTimeSelector(Selector[DateTimeSelectorConfig]): return data -class DeviceSelectorConfig(DeviceFilterSelectorConfig, total=False): +class DeviceSelectorConfig(BaseSelectorConfig, DeviceFilterSelectorConfig, total=False): """Class to represent a device selector config.""" entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] @@ -700,7 +713,9 @@ class DeviceSelector(Selector[DeviceSelectorConfig]): selector_type = "device" - CONFIG_SCHEMA = DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA.extend( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( + DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA.schema + ).extend( { vol.Optional("multiple", default=False): cv.boolean, vol.Optional("filter"): vol.All( @@ -724,7 +739,7 @@ class DeviceSelector(Selector[DeviceSelectorConfig]): return [vol.Schema(str)(val) for val in data] -class DurationSelectorConfig(TypedDict, total=False): +class DurationSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a duration selector config.""" enable_day: bool @@ -738,7 +753,7 @@ class DurationSelector(Selector[DurationSelectorConfig]): selector_type = "duration" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { # Enable day field in frontend. A selection with `days` set is allowed # even if `enable_day` is not set @@ -763,7 +778,7 @@ class DurationSelector(Selector[DurationSelectorConfig]): return cast(dict[str, float], data) -class EntitySelectorConfig(EntityFilterSelectorConfig, total=False): +class EntitySelectorConfig(BaseSelectorConfig, EntityFilterSelectorConfig, total=False): """Class to represent an entity selector config.""" exclude_entities: list[str] @@ -778,7 +793,9 @@ class EntitySelector(Selector[EntitySelectorConfig]): selector_type = "entity" - CONFIG_SCHEMA = ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA.extend( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( + ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA.schema + ).extend( { vol.Optional("exclude_entities"): [str], vol.Optional("include_entities"): [str], @@ -824,7 +841,7 @@ class EntitySelector(Selector[EntitySelectorConfig]): return cast(list, vol.Schema([validate])(data)) # Output is a list -class FloorSelectorConfig(TypedDict, total=False): +class FloorSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an floor selector config.""" entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] @@ -838,7 +855,7 @@ class FloorSelector(Selector[FloorSelectorConfig]): selector_type = "floor" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("entity"): vol.All( cv.ensure_list, @@ -866,7 +883,7 @@ class FloorSelector(Selector[FloorSelectorConfig]): return [vol.Schema(str)(val) for val in data] -class IconSelectorConfig(TypedDict, total=False): +class IconSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an icon selector config.""" placeholder: str @@ -878,7 +895,7 @@ class IconSelector(Selector[IconSelectorConfig]): selector_type = "icon" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( {vol.Optional("placeholder"): str} # Frontend also has a fallbackPath option, this is not used by core ) @@ -893,7 +910,7 @@ class IconSelector(Selector[IconSelectorConfig]): return icon -class LabelSelectorConfig(TypedDict, total=False): +class LabelSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a label selector config.""" multiple: bool @@ -905,7 +922,7 @@ class LabelSelector(Selector[LabelSelectorConfig]): selector_type = "label" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("multiple", default=False): cv.boolean, } @@ -925,7 +942,7 @@ class LabelSelector(Selector[LabelSelectorConfig]): return [vol.Schema(str)(val) for val in data] -class LanguageSelectorConfig(TypedDict, total=False): +class LanguageSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an language selector config.""" languages: list[str] @@ -939,7 +956,7 @@ class LanguageSelector(Selector[LanguageSelectorConfig]): selector_type = "language" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("languages"): [str], vol.Optional("native_name", default=False): cv.boolean, @@ -959,7 +976,7 @@ class LanguageSelector(Selector[LanguageSelectorConfig]): return language -class LocationSelectorConfig(TypedDict, total=False): +class LocationSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a location selector config.""" radius: bool @@ -972,7 +989,7 @@ class LocationSelector(Selector[LocationSelectorConfig]): selector_type = "location" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( {vol.Optional("radius"): bool, vol.Optional("icon"): str} ) DATA_SCHEMA = vol.Schema( @@ -993,7 +1010,7 @@ class LocationSelector(Selector[LocationSelectorConfig]): return location -class MediaSelectorConfig(TypedDict): +class MediaSelectorConfig(BaseSelectorConfig): """Class to represent a media selector config.""" @@ -1003,7 +1020,7 @@ class MediaSelector(Selector[MediaSelectorConfig]): selector_type = "media" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA DATA_SCHEMA = vol.Schema( { # Although marked as optional in frontend, this field is required @@ -1026,7 +1043,7 @@ class MediaSelector(Selector[MediaSelectorConfig]): return media -class NumberSelectorConfig(TypedDict, total=False): +class NumberSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a number selector config.""" min: float @@ -1061,7 +1078,7 @@ class NumberSelector(Selector[NumberSelectorConfig]): selector_type = "number" CONFIG_SCHEMA = vol.All( - vol.Schema( + BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("min"): vol.Coerce(float), vol.Optional("max"): vol.Coerce(float), @@ -1096,7 +1113,7 @@ class NumberSelector(Selector[NumberSelectorConfig]): return value -class ObjectSelectorConfig(TypedDict): +class ObjectSelectorConfig(BaseSelectorConfig): """Class to represent an object selector config.""" @@ -1106,7 +1123,7 @@ class ObjectSelector(Selector[ObjectSelectorConfig]): selector_type = "object" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: ObjectSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -1142,7 +1159,7 @@ class SelectSelectorMode(StrEnum): DROPDOWN = "dropdown" -class SelectSelectorConfig(TypedDict, total=False): +class SelectSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a select selector config.""" options: Required[Sequence[SelectOptionDict] | Sequence[str]] @@ -1159,7 +1176,7 @@ class SelectSelector(Selector[SelectSelectorConfig]): selector_type = "select" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Required("options"): vol.All(vol.Any([str], [select_option])), vol.Optional("multiple", default=False): cv.boolean, @@ -1199,14 +1216,14 @@ class SelectSelector(Selector[SelectSelectorConfig]): return [parent_schema(vol.Schema(str)(val)) for val in data] -class TargetSelectorConfig(TypedDict, total=False): +class TargetSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a target selector config.""" entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] device: DeviceFilterSelectorConfig | list[DeviceFilterSelectorConfig] -class StateSelectorConfig(TypedDict, total=False): +class StateSelectorConfig(BaseSelectorConfig, total=False): """Class to represent an state selector config.""" entity_id: Required[str] @@ -1218,7 +1235,7 @@ class StateSelector(Selector[StateSelectorConfig]): selector_type = "state" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Required("entity_id"): cv.entity_id, # The attribute to filter on, is currently deliberately not @@ -1248,7 +1265,7 @@ class TargetSelector(Selector[TargetSelectorConfig]): selector_type = "target" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("entity"): vol.All( cv.ensure_list, @@ -1273,7 +1290,7 @@ class TargetSelector(Selector[TargetSelectorConfig]): return target -class TemplateSelectorConfig(TypedDict): +class TemplateSelectorConfig(BaseSelectorConfig): """Class to represent an template selector config.""" @@ -1283,7 +1300,7 @@ class TemplateSelector(Selector[TemplateSelectorConfig]): selector_type = "template" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: TemplateSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -1295,7 +1312,7 @@ class TemplateSelector(Selector[TemplateSelectorConfig]): return template.template -class TextSelectorConfig(TypedDict, total=False): +class TextSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a text selector config.""" multiline: bool @@ -1330,7 +1347,7 @@ class TextSelector(Selector[TextSelectorConfig]): selector_type = "text" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("multiline", default=False): bool, vol.Optional("prefix"): str, @@ -1359,7 +1376,7 @@ class TextSelector(Selector[TextSelectorConfig]): return [vol.Schema(str)(val) for val in data] -class ThemeSelectorConfig(TypedDict): +class ThemeSelectorConfig(BaseSelectorConfig): """Class to represent a theme selector config.""" @@ -1369,7 +1386,7 @@ class ThemeSelector(Selector[ThemeSelectorConfig]): selector_type = "theme" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { vol.Optional("include_default", default=False): cv.boolean, } @@ -1385,7 +1402,7 @@ class ThemeSelector(Selector[ThemeSelectorConfig]): return theme -class TimeSelectorConfig(TypedDict): +class TimeSelectorConfig(BaseSelectorConfig): """Class to represent a time selector config.""" @@ -1395,7 +1412,7 @@ class TimeSelector(Selector[TimeSelectorConfig]): selector_type = "time" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: TimeSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -1407,7 +1424,7 @@ class TimeSelector(Selector[TimeSelectorConfig]): return cast(str, data) -class TriggerSelectorConfig(TypedDict): +class TriggerSelectorConfig(BaseSelectorConfig): """Class to represent an trigger selector config.""" @@ -1417,7 +1434,7 @@ class TriggerSelector(Selector[TriggerSelectorConfig]): selector_type = "trigger" - CONFIG_SCHEMA = vol.Schema({}) + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA def __init__(self, config: TriggerSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -1428,7 +1445,7 @@ class TriggerSelector(Selector[TriggerSelectorConfig]): return vol.Schema(cv.TRIGGER_SCHEMA)(data) -class FileSelectorConfig(TypedDict): +class FileSelectorConfig(BaseSelectorConfig): """Class to represent a file selector config.""" accept: str # required @@ -1440,7 +1457,7 @@ class FileSelector(Selector[FileSelectorConfig]): selector_type = "file" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( { # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept vol.Required("accept"): str,