Add SelectorType enum and TypedDicts for each selector's data (#68399)
* rebase off current * rearrange * Overload selector function * Update/fix all selector references * better typing? * remove extra option * move things around * Switch to Sequence type to avoid ignoring mypy error * Get rid of ...'s * Improve typing to reduce number of ignores * Remove all typing ignores * Make config optional for selectors that don't need a config * add missing unit prefixes * Rename TypedDicts * Update homeassistant/helpers/selector.py Co-authored-by: Erik Montnemery <erik@montnemery.com> * review feedback * remove peta from integration integration * Fix min_max * Revert change to selector function * Fix logic * Add typing for selector classes * Update selector.py * Fix indent Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
e996142592
commit
b325c112b4
16 changed files with 525 additions and 239 deletions
|
@ -9,7 +9,6 @@ import voluptuous as vol
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
|
||||||
TIME_DAYS,
|
TIME_DAYS,
|
||||||
TIME_HOURS,
|
TIME_HOURS,
|
||||||
TIME_MINUTES,
|
TIME_MINUTES,
|
||||||
|
@ -31,50 +30,48 @@ from .const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
UNIT_PREFIXES = [
|
UNIT_PREFIXES = [
|
||||||
{"value": "none", "label": "none"},
|
selector.SelectOptionDict(value="none", label="none"),
|
||||||
{"value": "n", "label": "n (nano)"},
|
selector.SelectOptionDict(value="n", label="n (nano)"),
|
||||||
{"value": "µ", "label": "µ (micro)"},
|
selector.SelectOptionDict(value="µ", label="µ (micro)"),
|
||||||
{"value": "m", "label": "m (milli)"},
|
selector.SelectOptionDict(value="m", label="m (milli)"),
|
||||||
{"value": "k", "label": "k (kilo)"},
|
selector.SelectOptionDict(value="k", label="k (kilo)"),
|
||||||
{"value": "M", "label": "M (mega)"},
|
selector.SelectOptionDict(value="M", label="M (mega)"),
|
||||||
{"value": "G", "label": "G (giga)"},
|
selector.SelectOptionDict(value="G", label="G (giga)"),
|
||||||
{"value": "T", "label": "T (tera)"},
|
selector.SelectOptionDict(value="T", label="T (tera)"),
|
||||||
{"value": "P", "label": "P (peta)"},
|
selector.SelectOptionDict(value="P", label="P (peta)"),
|
||||||
]
|
]
|
||||||
TIME_UNITS = [
|
TIME_UNITS = [
|
||||||
{"value": TIME_SECONDS, "label": "Seconds"},
|
selector.SelectOptionDict(value=TIME_SECONDS, label="Seconds"),
|
||||||
{"value": TIME_MINUTES, "label": "Minutes"},
|
selector.SelectOptionDict(value=TIME_MINUTES, label="Minutes"),
|
||||||
{"value": TIME_HOURS, "label": "Hours"},
|
selector.SelectOptionDict(value=TIME_HOURS, label="Hours"),
|
||||||
{"value": TIME_DAYS, "label": "Days"},
|
selector.SelectOptionDict(value=TIME_DAYS, label="Days"),
|
||||||
]
|
]
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector(
|
vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector(
|
||||||
{
|
selector.NumberSelectorConfig(
|
||||||
"number": {
|
min=0,
|
||||||
"min": 0,
|
max=6,
|
||||||
"max": 6,
|
mode=selector.NumberSelectorMode.BOX,
|
||||||
"mode": "box",
|
unit_of_measurement="decimals",
|
||||||
CONF_UNIT_OF_MEASUREMENT: "decimals",
|
),
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
vol.Required(CONF_TIME_WINDOW): selector.selector({"duration": {}}),
|
vol.Required(CONF_TIME_WINDOW): selector.DurationSelector(),
|
||||||
vol.Required(CONF_UNIT_PREFIX, default="none"): selector.selector(
|
vol.Required(CONF_UNIT_PREFIX, default="none"): selector.SelectSelector(
|
||||||
{"select": {"options": UNIT_PREFIXES}}
|
selector.SelectSelectorConfig(options=UNIT_PREFIXES),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.selector(
|
vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.SelectSelector(
|
||||||
{"select": {"options": TIME_UNITS}}
|
selector.SelectSelectorConfig(options=TIME_UNITS),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
vol.Required(CONF_NAME): selector.TextSelector(),
|
||||||
vol.Required(CONF_SOURCE): selector.selector(
|
vol.Required(CONF_SOURCE): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor"}},
|
selector.EntitySelectorConfig(domain="sensor"),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(OPTIONS_SCHEMA.schema)
|
).extend(OPTIONS_SCHEMA.schema)
|
||||||
|
|
|
@ -33,11 +33,9 @@ def basic_group_options_schema(
|
||||||
return vol.Schema(
|
return vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITIES): entity_selector_without_own_entities(
|
vol.Required(CONF_ENTITIES): entity_selector_without_own_entities(
|
||||||
handler, {"domain": domain, "multiple": True}
|
handler, selector.EntitySelectorConfig(domain=domain, multiple=True)
|
||||||
),
|
|
||||||
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.selector(
|
|
||||||
{"boolean": {}}
|
|
||||||
),
|
),
|
||||||
|
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,13 +44,11 @@ def basic_group_config_schema(domain: str) -> vol.Schema:
|
||||||
"""Generate config schema."""
|
"""Generate config schema."""
|
||||||
return vol.Schema(
|
return vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("name"): selector.selector({"text": {}}),
|
vol.Required("name"): selector.TextSelector(),
|
||||||
vol.Required(CONF_ENTITIES): selector.selector(
|
vol.Required(CONF_ENTITIES): selector.EntitySelector(
|
||||||
{"entity": {"domain": domain, "multiple": True}}
|
selector.EntitySelectorConfig(domain=domain, multiple=True),
|
||||||
),
|
|
||||||
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.selector(
|
|
||||||
{"boolean": {}}
|
|
||||||
),
|
),
|
||||||
|
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,14 +60,14 @@ def binary_sensor_options_schema(
|
||||||
"""Generate options schema."""
|
"""Generate options schema."""
|
||||||
return basic_group_options_schema("binary_sensor", handler, options).extend(
|
return basic_group_options_schema("binary_sensor", handler, options).extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ALL, default=False): selector.selector({"boolean": {}}),
|
vol.Required(CONF_ALL, default=False): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
BINARY_SENSOR_CONFIG_SCHEMA = basic_group_config_schema("binary_sensor").extend(
|
BINARY_SENSOR_CONFIG_SCHEMA = basic_group_config_schema("binary_sensor").extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ALL, default=False): selector.selector({"boolean": {}}),
|
vol.Required(CONF_ALL, default=False): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,7 +82,7 @@ def light_switch_options_schema(
|
||||||
{
|
{
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_ALL, default=False, description={"advanced": True}
|
CONF_ALL, default=False, description={"advanced": True}
|
||||||
): selector.selector({"boolean": {}}),
|
): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import voluptuous as vol
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_METHOD,
|
CONF_METHOD,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
|
||||||
TIME_DAYS,
|
TIME_DAYS,
|
||||||
TIME_HOURS,
|
TIME_HOURS,
|
||||||
TIME_MINUTES,
|
TIME_MINUTES,
|
||||||
|
@ -34,56 +33,58 @@ from .const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
UNIT_PREFIXES = [
|
UNIT_PREFIXES = [
|
||||||
{"value": "none", "label": "none"},
|
selector.SelectOptionDict(value="none", label="none"),
|
||||||
{"value": "k", "label": "k (kilo)"},
|
selector.SelectOptionDict(value="k", label="k (kilo)"),
|
||||||
{"value": "M", "label": "M (mega)"},
|
selector.SelectOptionDict(value="M", label="M (mega)"),
|
||||||
{"value": "G", "label": "G (giga)"},
|
selector.SelectOptionDict(value="G", label="G (giga)"),
|
||||||
{"value": "T", "label": "T (tera)"},
|
selector.SelectOptionDict(value="T", label="T (tera)"),
|
||||||
]
|
]
|
||||||
TIME_UNITS = [
|
TIME_UNITS = [
|
||||||
{"value": TIME_SECONDS, "label": "s (seconds)"},
|
selector.SelectOptionDict(value=TIME_SECONDS, label="s (seconds)"),
|
||||||
{"value": TIME_MINUTES, "label": "min (minutes)"},
|
selector.SelectOptionDict(value=TIME_MINUTES, label="min (minutes)"),
|
||||||
{"value": TIME_HOURS, "label": "h (hours)"},
|
selector.SelectOptionDict(value=TIME_HOURS, label="h (hours)"),
|
||||||
{"value": TIME_DAYS, "label": "d (days)"},
|
selector.SelectOptionDict(value=TIME_DAYS, label="d (days)"),
|
||||||
]
|
]
|
||||||
INTEGRATION_METHODS = [
|
INTEGRATION_METHODS = [
|
||||||
{"value": METHOD_TRAPEZOIDAL, "label": "Trapezoidal rule"},
|
selector.SelectOptionDict(value=METHOD_TRAPEZOIDAL, label="Trapezoidal rule"),
|
||||||
{"value": METHOD_LEFT, "label": "Left Riemann sum"},
|
selector.SelectOptionDict(value=METHOD_LEFT, label="Left Riemann sum"),
|
||||||
{"value": METHOD_RIGHT, "label": "Right Riemann sum"},
|
selector.SelectOptionDict(value=METHOD_RIGHT, label="Right Riemann sum"),
|
||||||
]
|
]
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector(
|
vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector(
|
||||||
{"number": {"min": 0, "max": 6, "mode": "box"}}
|
selector.NumberSelectorConfig(
|
||||||
|
min=0, max=6, mode=selector.NumberSelectorMode.BOX
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
vol.Required(CONF_NAME): selector.TextSelector(),
|
||||||
vol.Required(CONF_SOURCE_SENSOR): selector.selector(
|
vol.Required(CONF_SOURCE_SENSOR): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor"}},
|
selector.EntitySelectorConfig(domain="sensor")
|
||||||
),
|
),
|
||||||
vol.Required(CONF_METHOD, default=METHOD_TRAPEZOIDAL): selector.selector(
|
vol.Required(CONF_METHOD, default=METHOD_TRAPEZOIDAL): selector.SelectSelector(
|
||||||
{"select": {"options": INTEGRATION_METHODS}}
|
selector.SelectSelectorConfig(options=INTEGRATION_METHODS),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector(
|
vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector(
|
||||||
{
|
selector.NumberSelectorConfig(
|
||||||
"number": {
|
min=0,
|
||||||
"min": 0,
|
max=6,
|
||||||
"max": 6,
|
mode=selector.NumberSelectorMode.BOX,
|
||||||
"mode": "box",
|
unit_of_measurement="decimals",
|
||||||
CONF_UNIT_OF_MEASUREMENT: "decimals",
|
),
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
vol.Required(CONF_UNIT_PREFIX, default="none"): selector.selector(
|
vol.Required(CONF_UNIT_PREFIX, default="none"): selector.SelectSelector(
|
||||||
{"select": {"options": UNIT_PREFIXES}}
|
selector.SelectSelectorConfig(options=UNIT_PREFIXES),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.selector(
|
vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.SelectSelector(
|
||||||
{"select": {"options": TIME_UNITS, "mode": "dropdown"}}
|
selector.SelectSelectorConfig(
|
||||||
|
options=TIME_UNITS, mode=selector.SelectSelectorMode.DROPDOWN
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -63,10 +63,14 @@ CONF_KNX_LABEL_TUNNELING_TCP_SECURE: Final = "TCP with IP Secure"
|
||||||
CONF_KNX_LABEL_TUNNELING_UDP: Final = "UDP"
|
CONF_KNX_LABEL_TUNNELING_UDP: Final = "UDP"
|
||||||
CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK: Final = "UDP with route back / NAT mode"
|
CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK: Final = "UDP with route back / NAT mode"
|
||||||
|
|
||||||
_IA_SELECTOR = selector.selector({"text": {}})
|
_IA_SELECTOR = selector.TextSelector()
|
||||||
_IP_SELECTOR = selector.selector({"text": {}})
|
_IP_SELECTOR = selector.TextSelector()
|
||||||
_PORT_SELECTOR = vol.All(
|
_PORT_SELECTOR = vol.All(
|
||||||
selector.selector({"number": {"min": 1, "max": 65535, "mode": "box"}}),
|
selector.NumberSelector(
|
||||||
|
selector.NumberSelectorConfig(
|
||||||
|
min=1, max=65535, mode=selector.NumberSelectorMode.BOX
|
||||||
|
),
|
||||||
|
),
|
||||||
vol.Coerce(int),
|
vol.Coerce(int),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -254,14 +258,18 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
vol.Required(CONF_KNX_SECURE_USER_ID, default=2): vol.All(
|
vol.Required(CONF_KNX_SECURE_USER_ID, default=2): vol.All(
|
||||||
selector.selector({"number": {"min": 1, "max": 127, "mode": "box"}}),
|
selector.NumberSelector(
|
||||||
|
selector.NumberSelectorConfig(
|
||||||
|
min=1, max=127, mode=selector.NumberSelectorMode.BOX
|
||||||
|
),
|
||||||
|
),
|
||||||
vol.Coerce(int),
|
vol.Coerce(int),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_KNX_SECURE_USER_PASSWORD): selector.selector(
|
vol.Required(CONF_KNX_SECURE_USER_PASSWORD): selector.TextSelector(
|
||||||
{"text": {"type": "password"}}
|
selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_KNX_SECURE_DEVICE_AUTHENTICATION): selector.selector(
|
vol.Required(CONF_KNX_SECURE_DEVICE_AUTHENTICATION): selector.TextSelector(
|
||||||
{"text": {"type": "password"}}
|
selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,8 +309,8 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
)
|
)
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
vol.Required(CONF_KNX_KNXKEY_FILENAME): selector.selector({"text": {}}),
|
vol.Required(CONF_KNX_KNXKEY_FILENAME): selector.TextSelector(),
|
||||||
vol.Required(CONF_KNX_KNXKEY_PASSWORD): selector.selector({"text": {}}),
|
vol.Required(CONF_KNX_KNXKEY_PASSWORD): selector.TextSelector(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -405,7 +413,7 @@ class KNXOptionsFlowHandler(OptionsFlow):
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_KNX_INDIVIDUAL_ADDRESS,
|
CONF_KNX_INDIVIDUAL_ADDRESS,
|
||||||
default=self.current_config[CONF_KNX_INDIVIDUAL_ADDRESS],
|
default=self.current_config[CONF_KNX_INDIVIDUAL_ADDRESS],
|
||||||
): selector.selector({"text": {}}),
|
): selector.TextSelector(),
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_KNX_MCAST_GRP,
|
CONF_KNX_MCAST_GRP,
|
||||||
default=self.current_config.get(CONF_KNX_MCAST_GRP, DEFAULT_MCAST_GRP),
|
default=self.current_config.get(CONF_KNX_MCAST_GRP, DEFAULT_MCAST_GRP),
|
||||||
|
@ -438,7 +446,7 @@ class KNXOptionsFlowHandler(OptionsFlow):
|
||||||
CONF_KNX_DEFAULT_STATE_UPDATER,
|
CONF_KNX_DEFAULT_STATE_UPDATER,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
] = selector.selector({"boolean": {}})
|
] = selector.BooleanSelector()
|
||||||
data_schema[
|
data_schema[
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_KNX_RATE_LIMIT,
|
CONF_KNX_RATE_LIMIT,
|
||||||
|
@ -448,14 +456,12 @@ class KNXOptionsFlowHandler(OptionsFlow):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
] = vol.All(
|
] = vol.All(
|
||||||
selector.selector(
|
selector.NumberSelector(
|
||||||
{
|
selector.NumberSelectorConfig(
|
||||||
"number": {
|
min=0,
|
||||||
"min": 1,
|
max=CONF_MAX_RATE_LIMIT,
|
||||||
"max": CONF_MAX_RATE_LIMIT,
|
mode=selector.NumberSelectorMode.BOX,
|
||||||
"mode": "box",
|
),
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
vol.Coerce(int),
|
vol.Coerce(int),
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,31 +17,33 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
|
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
|
||||||
|
|
||||||
_STATISTIC_MEASURES = [
|
_STATISTIC_MEASURES = [
|
||||||
{"value": "min", "label": "Minimum"},
|
selector.SelectOptionDict(value="min", label="Minimum"),
|
||||||
{"value": "max", "label": "Maximum"},
|
selector.SelectOptionDict(value="max", label="Maximum"),
|
||||||
{"value": "mean", "label": "Arithmetic mean"},
|
selector.SelectOptionDict(value="mean", label="Arithmetic mean"),
|
||||||
{"value": "median", "label": "Median"},
|
selector.SelectOptionDict(value="median", label="Median"),
|
||||||
{"value": "last", "label": "Most recently updated"},
|
selector.SelectOptionDict(value="last", label="Most recently updated"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY_IDS): selector.selector(
|
vol.Required(CONF_ENTITY_IDS): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor", "multiple": True}}
|
selector.EntitySelectorConfig(domain="sensor", multiple=True),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_TYPE): selector.selector(
|
vol.Required(CONF_TYPE): selector.SelectSelector(
|
||||||
{"select": {"options": _STATISTIC_MEASURES}}
|
selector.SelectSelectorConfig(options=_STATISTIC_MEASURES),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector(
|
vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector(
|
||||||
{"number": {"min": 0, "max": 6, "mode": "box"}}
|
selector.NumberSelectorConfig(
|
||||||
|
min=0, max=6, mode=selector.NumberSelectorMode.BOX
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("name"): selector.selector({"text": {}}),
|
vol.Required("name"): selector.TextSelector(),
|
||||||
}
|
}
|
||||||
).extend(OPTIONS_SCHEMA.schema)
|
).extend(OPTIONS_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigFlow
|
from homeassistant.config_entries import ConfigFlow
|
||||||
from homeassistant.const import CONF_ZONE
|
from homeassistant.const import CONF_ZONE
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers.selector import selector
|
from homeassistant.helpers.selector import EntitySelector, EntitySelectorConfig
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ class OpenMeteoFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=vol.Schema(
|
data_schema=vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ZONE): selector(
|
vol.Required(CONF_ZONE): EntitySelector(
|
||||||
{"entity": {"domain": ZONE_DOMAIN}}
|
EntitySelectorConfig(domain=ZONE_DOMAIN),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,25 +17,23 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
|
|
||||||
from .const import CONF_TARGET_DOMAIN, DOMAIN
|
from .const import CONF_TARGET_DOMAIN, DOMAIN
|
||||||
|
|
||||||
|
TARGET_DOMAIN_OPTIONS = [
|
||||||
|
selector.SelectOptionDict(value=Platform.COVER, label="Cover"),
|
||||||
|
selector.SelectOptionDict(value=Platform.FAN, label="Fan"),
|
||||||
|
selector.SelectOptionDict(value=Platform.LIGHT, label="Light"),
|
||||||
|
selector.SelectOptionDict(value=Platform.LOCK, label="Lock"),
|
||||||
|
selector.SelectOptionDict(value=Platform.SIREN, label="Siren"),
|
||||||
|
]
|
||||||
|
|
||||||
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
|
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
|
||||||
"user": SchemaFlowFormStep(
|
"user": SchemaFlowFormStep(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY_ID): selector.selector(
|
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
|
||||||
{"entity": {"domain": Platform.SWITCH}}
|
selector.EntitySelectorConfig(domain=Platform.SWITCH),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_TARGET_DOMAIN): selector.selector(
|
vol.Required(CONF_TARGET_DOMAIN): selector.SelectSelector(
|
||||||
{
|
selector.SelectSelectorConfig(options=TARGET_DOMAIN_OPTIONS),
|
||||||
"select": {
|
|
||||||
"options": [
|
|
||||||
{"value": Platform.COVER, "label": "Cover"},
|
|
||||||
{"value": Platform.FAN, "label": "Fan"},
|
|
||||||
{"value": Platform.LIGHT, "label": "Light"},
|
|
||||||
{"value": Platform.LOCK, "label": "Lock"},
|
|
||||||
{"value": Platform.SIREN, "label": "Siren"},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,13 +15,16 @@ from homeassistant.const import (
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_RADIUS,
|
CONF_RADIUS,
|
||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
|
||||||
LENGTH_KILOMETERS,
|
LENGTH_KILOMETERS,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.selector import selector
|
from homeassistant.helpers.selector import (
|
||||||
|
LocationSelector,
|
||||||
|
NumberSelector,
|
||||||
|
NumberSelectorConfig,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import CONF_FUEL_TYPES, CONF_STATIONS, DEFAULT_RADIUS, DOMAIN, FUEL_TYPES
|
from .const import CONF_FUEL_TYPES, CONF_STATIONS, DEFAULT_RADIUS, DOMAIN, FUEL_TYPES
|
||||||
|
|
||||||
|
@ -154,18 +157,16 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"longitude": self.hass.config.longitude,
|
"longitude": self.hass.config.longitude,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
): selector({"location": {}}),
|
): LocationSelector(),
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_RADIUS, default=user_input.get(CONF_RADIUS, DEFAULT_RADIUS)
|
CONF_RADIUS, default=user_input.get(CONF_RADIUS, DEFAULT_RADIUS)
|
||||||
): selector(
|
): NumberSelector(
|
||||||
{
|
NumberSelectorConfig(
|
||||||
"number": {
|
min=0.1,
|
||||||
"min": 0.1,
|
max=25,
|
||||||
"max": 25,
|
step=0.1,
|
||||||
"step": 0.1,
|
unit_of_measurement=LENGTH_KILOMETERS,
|
||||||
CONF_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
|
),
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -27,19 +27,25 @@ def _validate_mode(data: Any) -> Any:
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): selector.selector(
|
vol.Required(
|
||||||
{"number": {"mode": "box"}}
|
CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS
|
||||||
|
): selector.NumberSelector(
|
||||||
|
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX),
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_LOWER): selector.NumberSelector(
|
||||||
|
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX),
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_UPPER): selector.NumberSelector(
|
||||||
|
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX),
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_LOWER): selector.selector({"number": {"mode": "box"}}),
|
|
||||||
vol.Optional(CONF_UPPER): selector.selector({"number": {"mode": "box"}}),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
vol.Required(CONF_NAME): selector.TextSelector(),
|
||||||
vol.Required(CONF_ENTITY_ID): selector.selector(
|
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor"}}
|
selector.EntitySelectorConfig(domain="sensor")
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(OPTIONS_SCHEMA.schema)
|
).extend(OPTIONS_SCHEMA.schema)
|
||||||
|
|
|
@ -18,14 +18,14 @@ from .const import CONF_AFTER_TIME, CONF_BEFORE_TIME, DOMAIN
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_AFTER_TIME): selector.selector({"time": {}}),
|
vol.Required(CONF_AFTER_TIME): selector.TimeSelector(),
|
||||||
vol.Required(CONF_BEFORE_TIME): selector.selector({"time": {}}),
|
vol.Required(CONF_BEFORE_TIME): selector.TimeSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
vol.Required(CONF_NAME): selector.TextSelector(),
|
||||||
}
|
}
|
||||||
).extend(OPTIONS_SCHEMA.schema)
|
).extend(OPTIONS_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ from homeassistant.const import (
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.selector import selector
|
from homeassistant.helpers.selector import LocationSelector, LocationSelectorConfig
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
AUTO_MIGRATION_MESSAGE,
|
AUTO_MIGRATION_MESSAGE,
|
||||||
|
@ -78,7 +78,7 @@ def _get_config_schema(
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_LOCATION,
|
CONF_LOCATION,
|
||||||
default=default_location,
|
default=default_location,
|
||||||
): selector({"location": {"radius": False}}),
|
): LocationSelector(LocationSelectorConfig(radius=False)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from typing import Any, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
from homeassistant.const import CONF_NAME
|
||||||
from homeassistant.helpers import selector
|
from homeassistant.helpers import selector
|
||||||
from homeassistant.helpers.schema_config_entry_flow import (
|
from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
SchemaConfigFlowHandler,
|
SchemaConfigFlowHandler,
|
||||||
|
@ -34,15 +34,15 @@ from .const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
METER_TYPES = [
|
METER_TYPES = [
|
||||||
{"value": "none", "label": "No cycle"},
|
selector.SelectOptionDict(value="none", label="No cycle"),
|
||||||
{"value": QUARTER_HOURLY, "label": "Every 15 minutes"},
|
selector.SelectOptionDict(value=QUARTER_HOURLY, label="Every 15 minutes"),
|
||||||
{"value": HOURLY, "label": "Hourly"},
|
selector.SelectOptionDict(value=HOURLY, label="Hourly"),
|
||||||
{"value": DAILY, "label": "Daily"},
|
selector.SelectOptionDict(value=DAILY, label="Daily"),
|
||||||
{"value": WEEKLY, "label": "Weekly"},
|
selector.SelectOptionDict(value=WEEKLY, label="Weekly"),
|
||||||
{"value": MONTHLY, "label": "Monthly"},
|
selector.SelectOptionDict(value=MONTHLY, label="Monthly"),
|
||||||
{"value": BIMONTHLY, "label": "Every two months"},
|
selector.SelectOptionDict(value=BIMONTHLY, label="Every two months"),
|
||||||
{"value": QUARTERLY, "label": "Quarterly"},
|
selector.SelectOptionDict(value=QUARTERLY, label="Quarterly"),
|
||||||
{"value": YEARLY, "label": "Yearly"},
|
selector.SelectOptionDict(value=YEARLY, label="Yearly"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,40 +58,38 @@ def _validate_config(data: Any) -> Any:
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_SOURCE_SENSOR): selector.selector(
|
vol.Required(CONF_SOURCE_SENSOR): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor"}},
|
selector.EntitySelectorConfig(domain="sensor"),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
vol.Required(CONF_NAME): selector.TextSelector(),
|
||||||
vol.Required(CONF_SOURCE_SENSOR): selector.selector(
|
vol.Required(CONF_SOURCE_SENSOR): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor"}},
|
selector.EntitySelectorConfig(domain="sensor"),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_METER_TYPE): selector.selector(
|
vol.Required(CONF_METER_TYPE): selector.SelectSelector(
|
||||||
{"select": {"options": METER_TYPES}}
|
selector.SelectSelectorConfig(options=METER_TYPES),
|
||||||
),
|
),
|
||||||
vol.Required(CONF_METER_OFFSET, default=0): selector.selector(
|
vol.Required(CONF_METER_OFFSET, default=0): selector.NumberSelector(
|
||||||
{
|
selector.NumberSelectorConfig(
|
||||||
"number": {
|
min=0,
|
||||||
"min": 0,
|
max=28,
|
||||||
"max": 28,
|
mode=selector.NumberSelectorMode.BOX,
|
||||||
"mode": "box",
|
unit_of_measurement="days",
|
||||||
CONF_UNIT_OF_MEASUREMENT: "days",
|
),
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
vol.Required(CONF_TARIFFS, default=[]): selector.selector(
|
vol.Required(CONF_TARIFFS, default=[]): selector.SelectSelector(
|
||||||
{"select": {"options": [], "custom_value": True, "multiple": True}}
|
selector.SelectSelectorConfig(options=[], custom_value=True, multiple=True),
|
||||||
),
|
|
||||||
vol.Required(CONF_METER_NET_CONSUMPTION, default=False): selector.selector(
|
|
||||||
{"boolean": {}}
|
|
||||||
),
|
|
||||||
vol.Required(CONF_METER_DELTA_VALUES, default=False): selector.selector(
|
|
||||||
{"boolean": {}}
|
|
||||||
),
|
),
|
||||||
|
vol.Required(
|
||||||
|
CONF_METER_NET_CONSUMPTION, default=False
|
||||||
|
): selector.BooleanSelector(),
|
||||||
|
vol.Required(
|
||||||
|
CONF_METER_DELTA_VALUES, default=False
|
||||||
|
): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -371,7 +371,7 @@ def wrapped_entity_config_entry_title(
|
||||||
@callback
|
@callback
|
||||||
def entity_selector_without_own_entities(
|
def entity_selector_without_own_entities(
|
||||||
handler: SchemaOptionsFlowHandler,
|
handler: SchemaOptionsFlowHandler,
|
||||||
entity_selector_config: dict[str, Any],
|
entity_selector_config: selector.EntitySelectorConfig,
|
||||||
) -> vol.Schema:
|
) -> vol.Schema:
|
||||||
"""Return an entity selector which excludes own entities."""
|
"""Return an entity selector which excludes own entities."""
|
||||||
entity_registry = er.async_get(handler.hass)
|
entity_registry = er.async_get(handler.hass)
|
||||||
|
@ -381,6 +381,7 @@ def entity_selector_without_own_entities(
|
||||||
)
|
)
|
||||||
entity_ids = [ent.entity_id for ent in entities]
|
entity_ids = [ent.entity_id for ent in entities]
|
||||||
|
|
||||||
return selector.selector(
|
final_selector_config = entity_selector_config.copy()
|
||||||
{"entity": {**entity_selector_config, "exclude_entities": entity_ids}}
|
final_selector_config["exclude_entities"] = entity_ids
|
||||||
)
|
|
||||||
|
return selector.EntitySelector(final_selector_config)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
"""Selectors for Home Assistant."""
|
"""Selectors for Home Assistant."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, Sequence
|
||||||
from typing import Any, cast
|
from typing import Any, TypedDict, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.backports.enum import StrEnum
|
||||||
from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT
|
from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT
|
||||||
from homeassistant.core import split_entity_id, valid_entity_id
|
from homeassistant.core import split_entity_id, valid_entity_id
|
||||||
from homeassistant.util import decorator
|
from homeassistant.util import decorator
|
||||||
|
@ -36,11 +37,7 @@ def selector(config: Any) -> Selector:
|
||||||
selector_class = _get_selector_class(config)
|
selector_class = _get_selector_class(config)
|
||||||
selector_type = list(config)[0]
|
selector_type = list(config)[0]
|
||||||
|
|
||||||
# Selectors can be empty
|
return selector_class(config[selector_type])
|
||||||
if config[selector_type] is None:
|
|
||||||
return selector_class({selector_type: {}})
|
|
||||||
|
|
||||||
return selector_class(config)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_selector(config: Any) -> dict:
|
def validate_selector(config: Any) -> dict:
|
||||||
|
@ -64,9 +61,13 @@ class Selector:
|
||||||
config: Any
|
config: Any
|
||||||
selector_type: str
|
selector_type: str
|
||||||
|
|
||||||
def __init__(self, config: Any) -> None:
|
def __init__(self, config: Any = None) -> None:
|
||||||
"""Instantiate a selector."""
|
"""Instantiate a selector."""
|
||||||
self.config = self.CONFIG_SCHEMA(config[self.selector_type])
|
# Selectors can be empty
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
self.config = self.CONFIG_SCHEMA(config)
|
||||||
|
|
||||||
def serialize(self) -> Any:
|
def serialize(self) -> Any:
|
||||||
"""Serialize Selector for voluptuous_serialize."""
|
"""Serialize Selector for voluptuous_serialize."""
|
||||||
|
@ -84,6 +85,15 @@ SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleEntitySelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a single entity selector config."""
|
||||||
|
|
||||||
|
integration: str
|
||||||
|
domain: str
|
||||||
|
device_class: str
|
||||||
|
|
||||||
|
|
||||||
SINGLE_DEVICE_SELECTOR_CONFIG_SCHEMA = vol.Schema(
|
SINGLE_DEVICE_SELECTOR_CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
# Integration linked to it with a config entry
|
# Integration linked to it with a config entry
|
||||||
|
@ -98,6 +108,19 @@ SINGLE_DEVICE_SELECTOR_CONFIG_SCHEMA = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleDeviceSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a single device selector config."""
|
||||||
|
|
||||||
|
integration: str
|
||||||
|
manufacturer: str
|
||||||
|
model: str
|
||||||
|
entity: SingleEntitySelectorConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ActionSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent an action selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("action")
|
@SELECTORS.register("action")
|
||||||
class ActionSelector(Selector):
|
class ActionSelector(Selector):
|
||||||
"""Selector of an action sequence (script syntax)."""
|
"""Selector of an action sequence (script syntax)."""
|
||||||
|
@ -106,11 +129,22 @@ class ActionSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: ActionSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> Any:
|
def __call__(self, data: Any) -> Any:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class AddonSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent an addon selector config."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
slug: str
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("addon")
|
@SELECTORS.register("addon")
|
||||||
class AddonSelector(Selector):
|
class AddonSelector(Selector):
|
||||||
"""Selector of a add-on."""
|
"""Selector of a add-on."""
|
||||||
|
@ -124,12 +158,24 @@ class AddonSelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: AddonSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str:
|
def __call__(self, data: Any) -> str:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
addon: str = vol.Schema(str)(data)
|
addon: str = vol.Schema(str)(data)
|
||||||
return addon
|
return addon
|
||||||
|
|
||||||
|
|
||||||
|
class AreaSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent an area selector config."""
|
||||||
|
|
||||||
|
entity: SingleEntitySelectorConfig
|
||||||
|
device: SingleDeviceSelectorConfig
|
||||||
|
multiple: bool
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("area")
|
@SELECTORS.register("area")
|
||||||
class AreaSelector(Selector):
|
class AreaSelector(Selector):
|
||||||
"""Selector of a single or list of areas."""
|
"""Selector of a single or list of areas."""
|
||||||
|
@ -144,6 +190,10 @@ class AreaSelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: AreaSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str | list[str]:
|
def __call__(self, data: Any) -> str | list[str]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
if not self.config["multiple"]:
|
if not self.config["multiple"]:
|
||||||
|
@ -154,6 +204,12 @@ class AreaSelector(Selector):
|
||||||
return [vol.Schema(str)(val) for val in data]
|
return [vol.Schema(str)(val) for val in data]
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent an attribute selector config."""
|
||||||
|
|
||||||
|
entity_id: str
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("attribute")
|
@SELECTORS.register("attribute")
|
||||||
class AttributeSelector(Selector):
|
class AttributeSelector(Selector):
|
||||||
"""Selector for an entity attribute."""
|
"""Selector for an entity attribute."""
|
||||||
|
@ -162,12 +218,20 @@ class AttributeSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({vol.Required("entity_id"): cv.entity_id})
|
CONFIG_SCHEMA = vol.Schema({vol.Required("entity_id"): cv.entity_id})
|
||||||
|
|
||||||
|
def __init__(self, config: AttributeSelectorConfig) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str:
|
def __call__(self, data: Any) -> str:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
attribute: str = vol.Schema(str)(data)
|
attribute: str = vol.Schema(str)(data)
|
||||||
return attribute
|
return attribute
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a boolean selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("boolean")
|
@SELECTORS.register("boolean")
|
||||||
class BooleanSelector(Selector):
|
class BooleanSelector(Selector):
|
||||||
"""Selector of a boolean value."""
|
"""Selector of a boolean value."""
|
||||||
|
@ -176,12 +240,20 @@ class BooleanSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: BooleanSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> bool:
|
def __call__(self, data: Any) -> bool:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
value: bool = vol.Coerce(bool)(data)
|
value: bool = vol.Coerce(bool)(data)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class ColorRGBSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a color RGB selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("color_rgb")
|
@SELECTORS.register("color_rgb")
|
||||||
class ColorRGBSelector(Selector):
|
class ColorRGBSelector(Selector):
|
||||||
"""Selector of an RGB color value."""
|
"""Selector of an RGB color value."""
|
||||||
|
@ -190,12 +262,23 @@ class ColorRGBSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: ColorRGBSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> list[int]:
|
def __call__(self, data: Any) -> list[int]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
value: list[int] = vol.All(list, vol.ExactSequence((cv.byte,) * 3))(data)
|
value: list[int] = vol.All(list, vol.ExactSequence((cv.byte,) * 3))(data)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class ColorTempSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a color temp selector config."""
|
||||||
|
|
||||||
|
max_mireds: int
|
||||||
|
min_mireds: int
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("color_temp")
|
@SELECTORS.register("color_temp")
|
||||||
class ColorTempSelector(Selector):
|
class ColorTempSelector(Selector):
|
||||||
"""Selector of an color temperature."""
|
"""Selector of an color temperature."""
|
||||||
|
@ -209,6 +292,10 @@ class ColorTempSelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: ColorTempSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> int:
|
def __call__(self, data: Any) -> int:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
value: int = vol.All(
|
value: int = vol.All(
|
||||||
|
@ -221,6 +308,10 @@ class ColorTempSelector(Selector):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class DateSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a date selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("date")
|
@SELECTORS.register("date")
|
||||||
class DateSelector(Selector):
|
class DateSelector(Selector):
|
||||||
"""Selector of a date."""
|
"""Selector of a date."""
|
||||||
|
@ -229,12 +320,20 @@ class DateSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: DateSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> Any:
|
def __call__(self, data: Any) -> Any:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
cv.date(data)
|
cv.date(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimeSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a date time selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("datetime")
|
@SELECTORS.register("datetime")
|
||||||
class DateTimeSelector(Selector):
|
class DateTimeSelector(Selector):
|
||||||
"""Selector of a datetime."""
|
"""Selector of a datetime."""
|
||||||
|
@ -243,12 +342,26 @@ class DateTimeSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: DateTimeSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> Any:
|
def __call__(self, data: Any) -> Any:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
cv.datetime(data)
|
cv.datetime(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a device selector config."""
|
||||||
|
|
||||||
|
integration: str
|
||||||
|
manufacturer: str
|
||||||
|
model: str
|
||||||
|
entity: SingleEntitySelectorConfig
|
||||||
|
multiple: bool
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("device")
|
@SELECTORS.register("device")
|
||||||
class DeviceSelector(Selector):
|
class DeviceSelector(Selector):
|
||||||
"""Selector of a single or list of devices."""
|
"""Selector of a single or list of devices."""
|
||||||
|
@ -259,6 +372,10 @@ class DeviceSelector(Selector):
|
||||||
{vol.Optional("multiple", default=False): cv.boolean}
|
{vol.Optional("multiple", default=False): cv.boolean}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: DeviceSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str | list[str]:
|
def __call__(self, data: Any) -> str | list[str]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
if not self.config["multiple"]:
|
if not self.config["multiple"]:
|
||||||
|
@ -269,6 +386,12 @@ class DeviceSelector(Selector):
|
||||||
return [vol.Schema(str)(val) for val in data]
|
return [vol.Schema(str)(val) for val in data]
|
||||||
|
|
||||||
|
|
||||||
|
class DurationSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a duration selector config."""
|
||||||
|
|
||||||
|
enable_day: bool
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("duration")
|
@SELECTORS.register("duration")
|
||||||
class DurationSelector(Selector):
|
class DurationSelector(Selector):
|
||||||
"""Selector for a duration."""
|
"""Selector for a duration."""
|
||||||
|
@ -283,12 +406,24 @@ class DurationSelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: DurationSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> dict[str, float]:
|
def __call__(self, data: Any) -> dict[str, float]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
cv.time_period_dict(data)
|
cv.time_period_dict(data)
|
||||||
return cast(dict[str, float], data)
|
return cast(dict[str, float], data)
|
||||||
|
|
||||||
|
|
||||||
|
class EntitySelectorConfig(SingleEntitySelectorConfig, total=False):
|
||||||
|
"""Class to represent an entity selector config."""
|
||||||
|
|
||||||
|
exclude_entities: list[str]
|
||||||
|
include_entities: list[str]
|
||||||
|
multiple: bool
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("entity")
|
@SELECTORS.register("entity")
|
||||||
class EntitySelector(Selector):
|
class EntitySelector(Selector):
|
||||||
"""Selector of a single or list of entities."""
|
"""Selector of a single or list of entities."""
|
||||||
|
@ -303,6 +438,10 @@ class EntitySelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: EntitySelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str | list[str]:
|
def __call__(self, data: Any) -> str | list[str]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
|
|
||||||
|
@ -333,6 +472,12 @@ class EntitySelector(Selector):
|
||||||
return cast(list, vol.Schema([validate])(data)) # Output is a list
|
return cast(list, vol.Schema([validate])(data)) # Output is a list
|
||||||
|
|
||||||
|
|
||||||
|
class IconSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent an icon selector config."""
|
||||||
|
|
||||||
|
placeholder: str
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("icon")
|
@SELECTORS.register("icon")
|
||||||
class IconSelector(Selector):
|
class IconSelector(Selector):
|
||||||
"""Selector for an icon."""
|
"""Selector for an icon."""
|
||||||
|
@ -344,12 +489,23 @@ class IconSelector(Selector):
|
||||||
# Frontend also has a fallbackPath option, this is not used by core
|
# Frontend also has a fallbackPath option, this is not used by core
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: IconSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str:
|
def __call__(self, data: Any) -> str:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
icon: str = vol.Schema(str)(data)
|
icon: str = vol.Schema(str)(data)
|
||||||
return icon
|
return icon
|
||||||
|
|
||||||
|
|
||||||
|
class LocationSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a location selector config."""
|
||||||
|
|
||||||
|
radius: bool
|
||||||
|
icon: str
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("location")
|
@SELECTORS.register("location")
|
||||||
class LocationSelector(Selector):
|
class LocationSelector(Selector):
|
||||||
"""Selector for a location."""
|
"""Selector for a location."""
|
||||||
|
@ -367,12 +523,20 @@ class LocationSelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: LocationSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> dict[str, float]:
|
def __call__(self, data: Any) -> dict[str, float]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
location: dict[str, float] = self.DATA_SCHEMA(data)
|
location: dict[str, float] = self.DATA_SCHEMA(data)
|
||||||
return location
|
return location
|
||||||
|
|
||||||
|
|
||||||
|
class MediaSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a media selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("media")
|
@SELECTORS.register("media")
|
||||||
class MediaSelector(Selector):
|
class MediaSelector(Selector):
|
||||||
"""Selector for media."""
|
"""Selector for media."""
|
||||||
|
@ -392,12 +556,33 @@ class MediaSelector(Selector):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: MediaSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> dict[str, float]:
|
def __call__(self, data: Any) -> dict[str, float]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
media: dict[str, float] = self.DATA_SCHEMA(data)
|
media: dict[str, float] = self.DATA_SCHEMA(data)
|
||||||
return media
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
class NumberSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a number selector config."""
|
||||||
|
|
||||||
|
min: float
|
||||||
|
max: float
|
||||||
|
step: float
|
||||||
|
unit_of_measurement: str
|
||||||
|
mode: NumberSelectorMode
|
||||||
|
|
||||||
|
|
||||||
|
class NumberSelectorMode(StrEnum):
|
||||||
|
"""Possible modes for a number selector."""
|
||||||
|
|
||||||
|
BOX = "box"
|
||||||
|
SLIDER = "slider"
|
||||||
|
|
||||||
|
|
||||||
def has_min_max_if_slider(data: Any) -> Any:
|
def has_min_max_if_slider(data: Any) -> Any:
|
||||||
"""Validate configuration."""
|
"""Validate configuration."""
|
||||||
if data["mode"] == "box":
|
if data["mode"] == "box":
|
||||||
|
@ -426,12 +611,18 @@ class NumberSelector(Selector):
|
||||||
vol.Coerce(float), vol.Range(min=1e-3)
|
vol.Coerce(float), vol.Range(min=1e-3)
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): str,
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): str,
|
||||||
vol.Optional(CONF_MODE, default="slider"): vol.In(["box", "slider"]),
|
vol.Optional(CONF_MODE, default=NumberSelectorMode.SLIDER): vol.Coerce(
|
||||||
|
NumberSelectorMode
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
has_min_max_if_slider,
|
has_min_max_if_slider,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: NumberSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> float:
|
def __call__(self, data: Any) -> float:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
value: float = vol.Coerce(float)(data)
|
value: float = vol.Coerce(float)(data)
|
||||||
|
@ -445,6 +636,10 @@ class NumberSelector(Selector):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent an object selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("object")
|
@SELECTORS.register("object")
|
||||||
class ObjectSelector(Selector):
|
class ObjectSelector(Selector):
|
||||||
"""Selector for an arbitrary object."""
|
"""Selector for an arbitrary object."""
|
||||||
|
@ -453,6 +648,10 @@ class ObjectSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: ObjectSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> Any:
|
def __call__(self, data: Any) -> Any:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
return data
|
return data
|
||||||
|
@ -469,9 +668,32 @@ select_option = vol.All(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectOptionDict(TypedDict):
|
||||||
|
"""Class to represent a select option dict."""
|
||||||
|
|
||||||
|
value: str
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
|
class SelectSelectorMode(StrEnum):
|
||||||
|
"""Possible modes for a number selector."""
|
||||||
|
|
||||||
|
LIST = "list"
|
||||||
|
DROPDOWN = "dropdown"
|
||||||
|
|
||||||
|
|
||||||
|
class SelectSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a select selector config."""
|
||||||
|
|
||||||
|
options: Sequence[SelectOptionDict] | Sequence[str] # required
|
||||||
|
multiple: bool
|
||||||
|
custom_value: bool
|
||||||
|
mode: SelectSelectorMode
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("select")
|
@SELECTORS.register("select")
|
||||||
class SelectSelector(Selector):
|
class SelectSelector(Selector):
|
||||||
"""Selector for an single or multi-choice input select."""
|
"""Selector for an single-choice input select."""
|
||||||
|
|
||||||
selector_type = "select"
|
selector_type = "select"
|
||||||
|
|
||||||
|
@ -480,10 +702,14 @@ class SelectSelector(Selector):
|
||||||
vol.Required("options"): vol.All(vol.Any([str], [select_option])),
|
vol.Required("options"): vol.All(vol.Any([str], [select_option])),
|
||||||
vol.Optional("multiple", default=False): cv.boolean,
|
vol.Optional("multiple", default=False): cv.boolean,
|
||||||
vol.Optional("custom_value", default=False): cv.boolean,
|
vol.Optional("custom_value", default=False): cv.boolean,
|
||||||
vol.Optional("mode"): vol.In(("list", "dropdown")),
|
vol.Optional("mode"): vol.Coerce(SelectSelectorMode),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: SelectSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> Any:
|
def __call__(self, data: Any) -> Any:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
options = []
|
options = []
|
||||||
|
@ -504,41 +730,11 @@ class SelectSelector(Selector):
|
||||||
return [parent_schema(vol.Schema(str)(val)) for val in data]
|
return [parent_schema(vol.Schema(str)(val)) for val in data]
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("text")
|
class TargetSelectorConfig(TypedDict, total=False):
|
||||||
class StringSelector(Selector):
|
"""Class to represent a target selector config."""
|
||||||
"""Selector for a multi-line text string."""
|
|
||||||
|
|
||||||
selector_type = "text"
|
entity: SingleEntitySelectorConfig
|
||||||
|
device: SingleDeviceSelectorConfig
|
||||||
STRING_TYPES = [
|
|
||||||
"number",
|
|
||||||
"text",
|
|
||||||
"search",
|
|
||||||
"tel",
|
|
||||||
"url",
|
|
||||||
"email",
|
|
||||||
"password",
|
|
||||||
"date",
|
|
||||||
"month",
|
|
||||||
"week",
|
|
||||||
"time",
|
|
||||||
"datetime-local",
|
|
||||||
"color",
|
|
||||||
]
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional("multiline", default=False): bool,
|
|
||||||
vol.Optional("suffix"): str,
|
|
||||||
# The "type" controls the input field in the browser, the resulting
|
|
||||||
# data can be any string so we don't validate it.
|
|
||||||
vol.Optional("type"): vol.In(STRING_TYPES),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str:
|
|
||||||
"""Validate the passed selection."""
|
|
||||||
text: str = vol.Schema(str)(data)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("target")
|
@SELECTORS.register("target")
|
||||||
|
@ -559,12 +755,72 @@ class TargetSelector(Selector):
|
||||||
|
|
||||||
TARGET_SELECTION_SCHEMA = vol.Schema(cv.TARGET_SERVICE_FIELDS)
|
TARGET_SELECTION_SCHEMA = vol.Schema(cv.TARGET_SERVICE_FIELDS)
|
||||||
|
|
||||||
|
def __init__(self, config: TargetSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> dict[str, list[str]]:
|
def __call__(self, data: Any) -> dict[str, list[str]]:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
target: dict[str, list[str]] = self.TARGET_SELECTION_SCHEMA(data)
|
target: dict[str, list[str]] = self.TARGET_SELECTION_SCHEMA(data)
|
||||||
return target
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
class TextSelectorConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a text selector config."""
|
||||||
|
|
||||||
|
multiline: bool
|
||||||
|
suffix: str
|
||||||
|
type: TextSelectorType
|
||||||
|
|
||||||
|
|
||||||
|
class TextSelectorType(StrEnum):
|
||||||
|
"""Enum for text selector types."""
|
||||||
|
|
||||||
|
COLOR = "color"
|
||||||
|
DATE = "date"
|
||||||
|
DATETIME_LOCAL = "datetime-local"
|
||||||
|
EMAIL = "email"
|
||||||
|
MONTH = "month"
|
||||||
|
NUMBER = "number"
|
||||||
|
PASSWORD = "password"
|
||||||
|
SEARCH = "search"
|
||||||
|
TEL = "tel"
|
||||||
|
TEXT = "text"
|
||||||
|
TIME = "time"
|
||||||
|
URL = "url"
|
||||||
|
WEEK = "week"
|
||||||
|
|
||||||
|
|
||||||
|
@SELECTORS.register("text")
|
||||||
|
class TextSelector(Selector):
|
||||||
|
"""Selector for a multi-line text string."""
|
||||||
|
|
||||||
|
selector_type = "text"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional("multiline", default=False): bool,
|
||||||
|
vol.Optional("suffix"): str,
|
||||||
|
# The "type" controls the input field in the browser, the resulting
|
||||||
|
# data can be any string so we don't validate it.
|
||||||
|
vol.Optional("type"): vol.Coerce(TextSelectorType),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: TextSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
|
def __call__(self, data: Any) -> str:
|
||||||
|
"""Validate the passed selection."""
|
||||||
|
text: str = vol.Schema(str)(data)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a theme selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("theme")
|
@SELECTORS.register("theme")
|
||||||
class ThemeSelector(Selector):
|
class ThemeSelector(Selector):
|
||||||
"""Selector for an theme."""
|
"""Selector for an theme."""
|
||||||
|
@ -573,12 +829,20 @@ class ThemeSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: ThemeSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str:
|
def __call__(self, data: Any) -> str:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
theme: str = vol.Schema(str)(data)
|
theme: str = vol.Schema(str)(data)
|
||||||
return theme
|
return theme
|
||||||
|
|
||||||
|
|
||||||
|
class TimeSelectorConfig(TypedDict):
|
||||||
|
"""Class to represent a time selector config."""
|
||||||
|
|
||||||
|
|
||||||
@SELECTORS.register("time")
|
@SELECTORS.register("time")
|
||||||
class TimeSelector(Selector):
|
class TimeSelector(Selector):
|
||||||
"""Selector of a time value."""
|
"""Selector of a time value."""
|
||||||
|
@ -587,6 +851,10 @@ class TimeSelector(Selector):
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({})
|
CONFIG_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
def __init__(self, config: TimeSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
def __call__(self, data: Any) -> str:
|
def __call__(self, data: Any) -> str:
|
||||||
"""Validate the passed selection."""
|
"""Validate the passed selection."""
|
||||||
cv.time(data)
|
cv.time(data)
|
||||||
|
|
|
@ -18,15 +18,15 @@ from .const import DOMAIN
|
||||||
|
|
||||||
OPTIONS_SCHEMA = vol.Schema(
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY_ID): selector.selector(
|
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
|
||||||
{"entity": {"domain": "sensor"}}
|
selector.EntitySelectorConfig(domain="sensor")
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("name"): selector.selector({"text": {}}),
|
vol.Required("name"): selector.TextSelector(),
|
||||||
}
|
}
|
||||||
).extend(OPTIONS_SCHEMA.schema)
|
).extend(OPTIONS_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
|
@ -266,7 +266,13 @@ def test_addon_selector_schema(schema, valid_selections, invalid_selections):
|
||||||
)
|
)
|
||||||
def test_boolean_selector_schema(schema, valid_selections, invalid_selections):
|
def test_boolean_selector_schema(schema, valid_selections, invalid_selections):
|
||||||
"""Test boolean selector."""
|
"""Test boolean selector."""
|
||||||
_test_selector("boolean", schema, valid_selections, invalid_selections, bool)
|
_test_selector(
|
||||||
|
"boolean",
|
||||||
|
schema,
|
||||||
|
valid_selections,
|
||||||
|
invalid_selections,
|
||||||
|
bool,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -512,7 +518,13 @@ def test_media_selector_schema(schema, valid_selections, invalid_selections):
|
||||||
data.pop("metadata", None)
|
data.pop("metadata", None)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
_test_selector("media", schema, valid_selections, invalid_selections, drop_metadata)
|
_test_selector(
|
||||||
|
"media",
|
||||||
|
schema,
|
||||||
|
valid_selections,
|
||||||
|
invalid_selections,
|
||||||
|
drop_metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue