Improve device automation validation (#86143)
This commit is contained in:
parent
0c8b6c13fc
commit
1e2f00e186
9 changed files with 255 additions and 116 deletions
|
@ -1,16 +1,17 @@
|
|||
"""Device action validator."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol, cast
|
||||
from typing import Any, Protocol
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_DOMAIN
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import DeviceAutomationType, async_get_device_automation_platform
|
||||
from .exceptions import InvalidDeviceAutomationConfig
|
||||
from .helpers import async_validate_device_automation_config
|
||||
|
||||
|
||||
class DeviceAutomationActionProtocol(Protocol):
|
||||
|
@ -50,15 +51,9 @@ async def async_validate_action_config(
|
|||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
try:
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, config[CONF_DOMAIN], DeviceAutomationType.ACTION
|
||||
)
|
||||
if hasattr(platform, "async_validate_action_config"):
|
||||
return await platform.async_validate_action_config(hass, config)
|
||||
return cast(ConfigType, platform.ACTION_SCHEMA(config))
|
||||
except InvalidDeviceAutomationConfig as err:
|
||||
raise vol.Invalid(str(err) or "Invalid action configuration") from err
|
||||
return await async_validate_device_automation_config(
|
||||
hass, config, cv.DEVICE_ACTION_SCHEMA, DeviceAutomationType.ACTION
|
||||
)
|
||||
|
||||
|
||||
async def async_call_action_from_config(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Validate device conditions."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Protocol, cast
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -11,7 +11,7 @@ from homeassistant.helpers import config_validation as cv
|
|||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import DeviceAutomationType, async_get_device_automation_platform
|
||||
from .exceptions import InvalidDeviceAutomationConfig
|
||||
from .helpers import async_validate_device_automation_config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.helpers import condition
|
||||
|
@ -50,16 +50,9 @@ async def async_validate_condition_config(
|
|||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate device condition config."""
|
||||
try:
|
||||
config = cv.DEVICE_CONDITION_SCHEMA(config)
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION
|
||||
)
|
||||
if hasattr(platform, "async_validate_condition_config"):
|
||||
return await platform.async_validate_condition_config(hass, config)
|
||||
return cast(ConfigType, platform.CONDITION_SCHEMA(config))
|
||||
except InvalidDeviceAutomationConfig as err:
|
||||
raise vol.Invalid(str(err) or "Invalid condition configuration") from err
|
||||
return await async_validate_device_automation_config(
|
||||
hass, config, cv.DEVICE_CONDITION_SCHEMA, DeviceAutomationType.CONDITION
|
||||
)
|
||||
|
||||
|
||||
async def async_condition_from_config(
|
||||
|
|
80
homeassistant/components/device_automation/helpers.py
Normal file
80
homeassistant/components/device_automation/helpers.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
"""Helpers for device oriented automations."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import DeviceAutomationType, async_get_device_automation_platform
|
||||
from .exceptions import InvalidDeviceAutomationConfig
|
||||
|
||||
DYNAMIC_VALIDATOR = {
|
||||
DeviceAutomationType.ACTION: "async_validate_action_config",
|
||||
DeviceAutomationType.CONDITION: "async_validate_condition_config",
|
||||
DeviceAutomationType.TRIGGER: "async_validate_trigger_config",
|
||||
}
|
||||
|
||||
STATIC_VALIDATOR = {
|
||||
DeviceAutomationType.ACTION: "ACTION_SCHEMA",
|
||||
DeviceAutomationType.CONDITION: "CONDITION_SCHEMA",
|
||||
DeviceAutomationType.TRIGGER: "TRIGGER_SCHEMA",
|
||||
}
|
||||
|
||||
|
||||
async def async_validate_device_automation_config(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
automation_schema: vol.Schema,
|
||||
automation_type: DeviceAutomationType,
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
validated_config: ConfigType = automation_schema(config)
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, validated_config[CONF_DOMAIN], automation_type
|
||||
)
|
||||
if not hasattr(platform, DYNAMIC_VALIDATOR[automation_type]):
|
||||
# Pass the unvalidated config to avoid mutating the raw config twice
|
||||
return cast(
|
||||
ConfigType, getattr(platform, STATIC_VALIDATOR[automation_type])(config)
|
||||
)
|
||||
|
||||
# Only call the dynamic validator if the referenced device exists and the relevant
|
||||
# config entry is loaded
|
||||
registry = dr.async_get(hass)
|
||||
if not (device := registry.async_get(validated_config[CONF_DEVICE_ID])):
|
||||
# The device referenced by the device trigger does not exist
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Unknown device '{validated_config[CONF_DEVICE_ID]}'"
|
||||
)
|
||||
|
||||
device_config_entry = None
|
||||
for entry_id in device.config_entries:
|
||||
if (
|
||||
not (entry := hass.config_entries.async_get_entry(entry_id))
|
||||
or entry.domain != validated_config[CONF_DOMAIN]
|
||||
):
|
||||
continue
|
||||
device_config_entry = entry
|
||||
break
|
||||
|
||||
if not device_config_entry:
|
||||
# The config entry referenced by the device trigger does not exist
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Device '{validated_config[CONF_DEVICE_ID]}' has no config entry from "
|
||||
f"domain '{validated_config[CONF_DOMAIN]}'"
|
||||
)
|
||||
|
||||
if not await hass.config_entries.async_wait_component(device_config_entry):
|
||||
# The component could not be loaded, skip the dynamic validation
|
||||
return validated_config
|
||||
|
||||
# Pass the unvalidated config to avoid mutating the raw config twice
|
||||
return cast(
|
||||
ConfigType,
|
||||
await getattr(platform, DYNAMIC_VALIDATOR[automation_type])(hass, config),
|
||||
)
|
|
@ -1,13 +1,12 @@
|
|||
"""Offer device oriented automation."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol, cast
|
||||
from typing import Any, Protocol
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN
|
||||
from homeassistant.const import CONF_DOMAIN
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
|
@ -16,7 +15,7 @@ from . import (
|
|||
DeviceAutomationType,
|
||||
async_get_device_automation_platform,
|
||||
)
|
||||
from .exceptions import InvalidDeviceAutomationConfig
|
||||
from .helpers import async_validate_device_automation_config
|
||||
|
||||
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
@ -58,36 +57,9 @@ async def async_validate_trigger_config(
|
|||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
try:
|
||||
platform = await async_get_device_automation_platform(
|
||||
hass, config[CONF_DOMAIN], DeviceAutomationType.TRIGGER
|
||||
)
|
||||
if not hasattr(platform, "async_validate_trigger_config"):
|
||||
return cast(ConfigType, platform.TRIGGER_SCHEMA(config))
|
||||
|
||||
# Only call the dynamic validator if the relevant config entry is loaded
|
||||
registry = dr.async_get(hass)
|
||||
if not (device := registry.async_get(config[CONF_DEVICE_ID])):
|
||||
return config
|
||||
|
||||
device_config_entry = None
|
||||
for entry_id in device.config_entries:
|
||||
if not (entry := hass.config_entries.async_get_entry(entry_id)):
|
||||
continue
|
||||
if entry.domain != config[CONF_DOMAIN]:
|
||||
continue
|
||||
device_config_entry = entry
|
||||
break
|
||||
|
||||
if not device_config_entry:
|
||||
return config
|
||||
|
||||
if not await hass.config_entries.async_wait_component(device_config_entry):
|
||||
return config
|
||||
|
||||
return await platform.async_validate_trigger_config(hass, config)
|
||||
except InvalidDeviceAutomationConfig as err:
|
||||
raise vol.Invalid(str(err) or "Invalid trigger configuration") from err
|
||||
return await async_validate_device_automation_config(
|
||||
hass, config, TRIGGER_SCHEMA, DeviceAutomationType.TRIGGER
|
||||
)
|
||||
|
||||
|
||||
async def async_attach_trigger(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue