mirror of
https://github.com/home-assistant/core.git
synced 2026-06-29 18:16:13 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cb69dff34 | |||
| ff460901b7 | |||
| 30512f08a8 | |||
| 9dd1a59d50 | |||
| 91aded4474 | |||
| 543eab3354 | |||
| 47b331a869 | |||
| 696dd45803 | |||
| f92239877f | |||
| 45ceb13937 | |||
| c5aeee8097 | |||
| bfc750b608 |
@@ -54,3 +54,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
|
||||
@@ -43,3 +43,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
|
||||
@@ -298,7 +298,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
):
|
||||
self.options.pop(CONF_LLM_HASS_API)
|
||||
if not errors:
|
||||
return await self.async_step_advanced()
|
||||
return await self.async_step_additional()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
@@ -308,10 +308,10 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
errors=errors or None,
|
||||
)
|
||||
|
||||
async def async_step_advanced(
|
||||
async def async_step_additional(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage advanced options."""
|
||||
"""Manage additional options."""
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
|
||||
@@ -360,7 +360,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
return await self.async_step_model()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced",
|
||||
step_id="additional",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(step_schema), self.options
|
||||
),
|
||||
|
||||
@@ -48,16 +48,16 @@
|
||||
"user": "Add AI task"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]"
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::additional::data::prompt_caching%]"
|
||||
},
|
||||
"data_description": {
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]"
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::additional::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::additional::data_description::prompt_caching%]"
|
||||
},
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::advanced::title%]"
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::additional::title%]"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
@@ -115,7 +115,7 @@
|
||||
"user": "Add conversation agent"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "Caching strategy"
|
||||
@@ -124,7 +124,7 @@
|
||||
"chat_model": "The model to serve the responses.",
|
||||
"prompt_caching": "Optimize your API cost and response times based on your usage."
|
||||
},
|
||||
"title": "Advanced settings"
|
||||
"title": "Additional settings"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.data_entry_flow import SectionConfig, section
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_HOSTNAME,
|
||||
CONF_IPV4,
|
||||
CONF_IPV6,
|
||||
@@ -39,7 +39,7 @@ from .const import (
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
|
||||
vol.Required(CONF_ADVANCED_OPTIONS): section(
|
||||
vol.Required(CONF_ADDITIONAL_OPTIONS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_RESOLVER): cv.string,
|
||||
@@ -117,13 +117,13 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if user_input:
|
||||
hostname = user_input[CONF_HOSTNAME]
|
||||
name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname
|
||||
advanced_options = user_input[CONF_ADVANCED_OPTIONS]
|
||||
resolver = advanced_options.get(CONF_RESOLVER, DEFAULT_RESOLVER)
|
||||
resolver_ipv6 = advanced_options.get(
|
||||
additional_options = user_input[CONF_ADDITIONAL_OPTIONS]
|
||||
resolver = additional_options.get(CONF_RESOLVER, DEFAULT_RESOLVER)
|
||||
resolver_ipv6 = additional_options.get(
|
||||
CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6
|
||||
)
|
||||
port = advanced_options.get(CONF_PORT, DEFAULT_PORT)
|
||||
port_ipv6 = advanced_options.get(CONF_PORT_IPV6, DEFAULT_PORT)
|
||||
port = additional_options.get(CONF_PORT, DEFAULT_PORT)
|
||||
port_ipv6 = additional_options.get(CONF_PORT_IPV6, DEFAULT_PORT)
|
||||
|
||||
validate = await async_validate_hostname(
|
||||
hostname, resolver, resolver_ipv6, port, port_ipv6
|
||||
|
||||
@@ -12,7 +12,7 @@ CONF_PORT_IPV6 = "port_ipv6"
|
||||
CONF_IPV4 = "ipv4"
|
||||
CONF_IPV6 = "ipv6"
|
||||
CONF_IPV6_V4 = "ipv6_v4"
|
||||
CONF_ADVANCED_OPTIONS = "advanced_options"
|
||||
CONF_ADDITIONAL_OPTIONS = "additional_options"
|
||||
|
||||
DEFAULT_HOSTNAME = "myip.opendns.com"
|
||||
DEFAULT_IPV6 = False
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"hostname": "The hostname for which to perform the DNS query."
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"additional_options": {
|
||||
"data": {
|
||||
"port": "IPv4 port",
|
||||
"port_ipv6": "IPv6 port",
|
||||
@@ -63,16 +63,16 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::resolver_ipv6%]"
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::additional_options::data::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::additional_options::data::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data::resolver_ipv6%]"
|
||||
},
|
||||
"data_description": {
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::resolver_ipv6%]"
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::resolver_ipv6%]"
|
||||
},
|
||||
"description": "Optionally change resolvers and ports."
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
},
|
||||
"get_queue": {
|
||||
"description": "Retrieves the details of the currently active queue of a Music Assistant player.",
|
||||
"name": "Get playerQueue details (advanced)"
|
||||
"name": "Get playerQueue details"
|
||||
},
|
||||
"play_announcement": {
|
||||
"description": "Plays an announcement on a Music Assistant player with more fine-grained control options.",
|
||||
|
||||
@@ -326,7 +326,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
options.update(user_input)
|
||||
if CONF_LLM_HASS_API in options and CONF_LLM_HASS_API not in user_input:
|
||||
options.pop(CONF_LLM_HASS_API)
|
||||
return await self.async_step_advanced()
|
||||
return await self.async_step_additional()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
@@ -335,10 +335,10 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_advanced(
|
||||
async def async_step_additional(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage advanced options."""
|
||||
"""Manage additional options."""
|
||||
options = self.options
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
@@ -374,7 +374,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
return await self.async_step_model()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced",
|
||||
step_id="additional",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(step_schema), options
|
||||
),
|
||||
|
||||
@@ -47,18 +47,18 @@
|
||||
"user": "Add AI task"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"max_tokens": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::max_tokens%]",
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::store_responses%]",
|
||||
"temperature": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::temperature%]",
|
||||
"top_p": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::top_p%]"
|
||||
"max_tokens": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::max_tokens%]",
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::store_responses%]",
|
||||
"temperature": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::temperature%]",
|
||||
"top_p": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::top_p%]"
|
||||
},
|
||||
"data_description": {
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data_description::store_responses%]"
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data_description::store_responses%]"
|
||||
},
|
||||
"title": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::title%]"
|
||||
"title": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::title%]"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
@@ -109,7 +109,7 @@
|
||||
"user": "Add conversation agent"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"max_tokens": "Maximum tokens to return in response",
|
||||
@@ -120,7 +120,7 @@
|
||||
"data_description": {
|
||||
"store_responses": "If enabled, requests and responses are stored by OpenAI and visible in your OpenAI dashboard logs"
|
||||
},
|
||||
"title": "Advanced settings"
|
||||
"title": "Additional settings"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
|
||||
@@ -34,6 +34,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
from .util import sanitize_container_name
|
||||
|
||||
type PortainerConfigEntry = ConfigEntry[PortainerCoordinator]
|
||||
|
||||
@@ -263,7 +264,7 @@ class PortainerCoordinator(
|
||||
|
||||
# Map containers, started and stopped
|
||||
for container in containers:
|
||||
container_name = self._get_container_name(container.names[0])
|
||||
container_name = sanitize_container_name(container.names[0])
|
||||
prev_container = (
|
||||
prev_endpoint.containers.get(container_name)
|
||||
if prev_endpoint
|
||||
@@ -313,7 +314,7 @@ class PortainerCoordinator(
|
||||
container_stats = dict(
|
||||
zip(
|
||||
(
|
||||
self._get_container_name(container.names[0])
|
||||
sanitize_container_name(container.names[0])
|
||||
for container in active_containers
|
||||
),
|
||||
await asyncio.gather(
|
||||
@@ -431,10 +432,6 @@ class PortainerCoordinator(
|
||||
for stack_callback in self.new_stacks_callbacks:
|
||||
stack_callback(new_stack_data)
|
||||
|
||||
def _get_container_name(self, container_name: str) -> str:
|
||||
"""Sanitize to get a proper container name."""
|
||||
return container_name.replace("/", " ").strip()
|
||||
|
||||
|
||||
class PortainerDockerDiskSpaceCoordinator(
|
||||
PortainerBaseCoordinator[dict[int, DockerSystemDF]]
|
||||
|
||||
@@ -19,6 +19,7 @@ from .coordinator import (
|
||||
PortainerStackData,
|
||||
PortainerVolumeData,
|
||||
)
|
||||
from .util import sanitize_container_name
|
||||
|
||||
|
||||
class PortainerCoordinatorEntity(CoordinatorEntity[PortainerCoordinator]):
|
||||
@@ -95,7 +96,7 @@ class PortainerContainerEntity(PortainerCoordinatorEntity):
|
||||
# According to Docker's API docs, the first name is unique
|
||||
names = self._device_info.container.names
|
||||
assert names, "Container names list unexpectedly empty"
|
||||
self.device_name = names[0].replace("/", " ").strip()
|
||||
self.device_name = sanitize_container_name(names[0])
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Utility functions for the Portainer integration."""
|
||||
|
||||
|
||||
def sanitize_container_name(container_name: str) -> str:
|
||||
"""Sanitize to get a proper container name."""
|
||||
return container_name.replace("/", " ").strip()
|
||||
@@ -32,8 +32,8 @@
|
||||
"timeout": "Timeout for connection to website.",
|
||||
"verify_ssl": "Enables/disables verification of SSL/TLS certificate, for example if it is self-signed."
|
||||
},
|
||||
"description": "Provide additional advanced settings for the resource.",
|
||||
"name": "Advanced settings"
|
||||
"description": "Provide additional settings for the resource.",
|
||||
"name": "Additional settings"
|
||||
},
|
||||
"auth": {
|
||||
"data": {
|
||||
@@ -117,8 +117,8 @@
|
||||
"unit_of_measurement": "Choose unit of measurement or create your own.",
|
||||
"value_template": "Defines a template to get the state of the sensor."
|
||||
},
|
||||
"description": "Provide additional advanced settings for the sensor.",
|
||||
"name": "Advanced settings"
|
||||
"description": "Provide additional settings for the sensor.",
|
||||
"name": "Additional settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ from homeassistant.helpers.trigger_template_entity import (
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_COLUMN_NAME,
|
||||
CONF_QUERY,
|
||||
DOMAIN,
|
||||
@@ -120,7 +120,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
new_options[CONF_COLUMN_NAME] = old_options.get(CONF_COLUMN_NAME)
|
||||
new_options[CONF_QUERY] = old_options.get(CONF_QUERY)
|
||||
new_options[CONF_ADVANCED_OPTIONS] = {}
|
||||
new_options[CONF_ADDITIONAL_OPTIONS] = {}
|
||||
|
||||
for key in (
|
||||
CONF_VALUE_TEMPLATE,
|
||||
@@ -129,12 +129,21 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
CONF_STATE_CLASS,
|
||||
):
|
||||
if (value := old_options.get(key)) is not None:
|
||||
new_options[CONF_ADVANCED_OPTIONS][key] = value
|
||||
new_options[CONF_ADDITIONAL_OPTIONS][key] = value
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data=new_data, options=new_options, version=2
|
||||
)
|
||||
|
||||
if entry.version == 2 and entry.minor_version < 2:
|
||||
new_options = {**entry.options}
|
||||
# The "advanced_options" section was renamed to "additional_options"
|
||||
if (additional := new_options.pop("advanced_options", None)) is not None:
|
||||
new_options[CONF_ADDITIONAL_OPTIONS] = additional
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, options=new_options, minor_version=2
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Migration to version %s.%s successful",
|
||||
entry.version,
|
||||
|
||||
@@ -32,7 +32,7 @@ from homeassistant.data_entry_flow import section
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import selector
|
||||
|
||||
from .const import CONF_ADVANCED_OPTIONS, CONF_COLUMN_NAME, CONF_QUERY, DOMAIN
|
||||
from .const import CONF_ADDITIONAL_OPTIONS, CONF_COLUMN_NAME, CONF_QUERY, DOMAIN
|
||||
from .util import (
|
||||
EmptyQueryError,
|
||||
InvalidSqlQuery,
|
||||
@@ -50,7 +50,7 @@ OPTIONS_SCHEMA: vol.Schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_QUERY): selector.TemplateSelector(),
|
||||
vol.Required(CONF_COLUMN_NAME): selector.TextSelector(),
|
||||
vol.Required(CONF_ADVANCED_OPTIONS): section(
|
||||
vol.Required(CONF_ADDITIONAL_OPTIONS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
|
||||
@@ -165,6 +165,7 @@ class SQLConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for SQL integration."""
|
||||
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 2
|
||||
|
||||
data: dict[str, Any]
|
||||
|
||||
@@ -239,12 +240,12 @@ class SQLConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.debug("Invalid query: %s", err)
|
||||
errors["query"] = "query_invalid"
|
||||
|
||||
mod_advanced_options = {
|
||||
mod_additional_options = {
|
||||
k: v
|
||||
for k, v in user_input[CONF_ADVANCED_OPTIONS].items()
|
||||
for k, v in user_input[CONF_ADDITIONAL_OPTIONS].items()
|
||||
if v is not None
|
||||
}
|
||||
user_input[CONF_ADVANCED_OPTIONS] = mod_advanced_options
|
||||
user_input[CONF_ADDITIONAL_OPTIONS] = mod_additional_options
|
||||
|
||||
if not errors:
|
||||
name = self.data[CONF_NAME]
|
||||
@@ -305,12 +306,12 @@ class SQLOptionsFlowHandler(OptionsFlowWithReload):
|
||||
recorder_db,
|
||||
)
|
||||
|
||||
mod_advanced_options = {
|
||||
mod_additional_options = {
|
||||
k: v
|
||||
for k, v in user_input[CONF_ADVANCED_OPTIONS].items()
|
||||
for k, v in user_input[CONF_ADDITIONAL_OPTIONS].items()
|
||||
if v is not None
|
||||
}
|
||||
user_input[CONF_ADVANCED_OPTIONS] = mod_advanced_options
|
||||
user_input[CONF_ADDITIONAL_OPTIONS] = mod_additional_options
|
||||
|
||||
return self.async_create_entry(
|
||||
data=user_input,
|
||||
|
||||
@@ -9,5 +9,5 @@ PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
CONF_COLUMN_NAME = "column"
|
||||
CONF_QUERY = "query"
|
||||
CONF_ADVANCED_OPTIONS = "advanced_options"
|
||||
CONF_ADDITIONAL_OPTIONS = "additional_options"
|
||||
DB_URL_RE = re.compile("//.*:.*@")
|
||||
|
||||
@@ -36,7 +36,7 @@ from homeassistant.helpers.trigger_template_entity import (
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_ADVANCED_OPTIONS, CONF_COLUMN_NAME, CONF_QUERY, DOMAIN
|
||||
from .const import CONF_ADDITIONAL_OPTIONS, CONF_COLUMN_NAME, CONF_QUERY, DOMAIN
|
||||
from .util import (
|
||||
InvalidSqlQuery,
|
||||
async_create_sessionmaker,
|
||||
@@ -115,7 +115,9 @@ async def async_setup_entry(
|
||||
db_url: str = resolve_db_url(hass, entry.data.get(CONF_DB_URL))
|
||||
name: str = entry.title
|
||||
query_str: str = entry.options[CONF_QUERY]
|
||||
template: str | None = entry.options[CONF_ADVANCED_OPTIONS].get(CONF_VALUE_TEMPLATE)
|
||||
template: str | None = entry.options[CONF_ADDITIONAL_OPTIONS].get(
|
||||
CONF_VALUE_TEMPLATE
|
||||
)
|
||||
column_name: str = entry.options[CONF_COLUMN_NAME]
|
||||
|
||||
query_template: ValueTemplate | None = None
|
||||
@@ -136,9 +138,9 @@ async def async_setup_entry(
|
||||
name_template = Template(name, hass)
|
||||
trigger_entity_config = {CONF_NAME: name_template, CONF_UNIQUE_ID: entry.entry_id}
|
||||
for key in TRIGGER_ENTITY_OPTIONS:
|
||||
if key not in entry.options[CONF_ADVANCED_OPTIONS]:
|
||||
if key not in entry.options[CONF_ADDITIONAL_OPTIONS]:
|
||||
continue
|
||||
trigger_entity_config[key] = entry.options[CONF_ADVANCED_OPTIONS][key]
|
||||
trigger_entity_config[key] = entry.options[CONF_ADDITIONAL_OPTIONS][key]
|
||||
|
||||
await async_setup_sensor(
|
||||
hass,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"query": "Query to run, needs to start with 'SELECT'"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"additional_options": {
|
||||
"data": {
|
||||
"device_class": "Device class",
|
||||
"state_class": "State class",
|
||||
@@ -87,21 +87,21 @@
|
||||
"query": "[%key:component::sql::config::step::options::data_description::query%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"additional_options": {
|
||||
"data": {
|
||||
"device_class": "[%key:component::sql::config::step::options::sections::advanced_options::data::device_class%]",
|
||||
"state_class": "[%key:component::sql::config::step::options::sections::advanced_options::data::state_class%]",
|
||||
"unit_of_measurement": "[%key:component::sql::config::step::options::sections::advanced_options::data::unit_of_measurement%]",
|
||||
"value_template": "[%key:component::sql::config::step::options::sections::advanced_options::data::value_template%]"
|
||||
"device_class": "[%key:component::sql::config::step::options::sections::additional_options::data::device_class%]",
|
||||
"state_class": "[%key:component::sql::config::step::options::sections::additional_options::data::state_class%]",
|
||||
"unit_of_measurement": "[%key:component::sql::config::step::options::sections::additional_options::data::unit_of_measurement%]",
|
||||
"value_template": "[%key:component::sql::config::step::options::sections::additional_options::data::value_template%]"
|
||||
},
|
||||
"data_description": {
|
||||
"device_class": "[%key:component::sql::config::step::options::sections::advanced_options::data_description::device_class%]",
|
||||
"state_class": "[%key:component::sql::config::step::options::sections::advanced_options::data_description::state_class%]",
|
||||
"unit_of_measurement": "[%key:component::sql::config::step::options::sections::advanced_options::data_description::unit_of_measurement%]",
|
||||
"value_template": "[%key:component::sql::config::step::options::sections::advanced_options::data_description::value_template%]"
|
||||
"device_class": "[%key:component::sql::config::step::options::sections::additional_options::data_description::device_class%]",
|
||||
"state_class": "[%key:component::sql::config::step::options::sections::additional_options::data_description::state_class%]",
|
||||
"unit_of_measurement": "[%key:component::sql::config::step::options::sections::additional_options::data_description::unit_of_measurement%]",
|
||||
"value_template": "[%key:component::sql::config::step::options::sections::additional_options::data_description::value_template%]"
|
||||
},
|
||||
"description": "[%key:component::sql::config::step::options::sections::advanced_options::name%]",
|
||||
"name": "[%key:component::sql::config::step::options::sections::advanced_options::name%]"
|
||||
"description": "[%key:component::sql::config::step::options::sections::additional_options::name%]",
|
||||
"name": "[%key:component::sql::config::step::options::sections::additional_options::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["verisure"],
|
||||
"requirements": ["vsure==2.7.1"]
|
||||
"requirements": ["vsure==2.8.0"]
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"migration_strategy_recommended": "This is the quickest option to migrate to a new adapter."
|
||||
},
|
||||
"menu_options": {
|
||||
"migration_strategy_advanced": "Advanced migration",
|
||||
"migration_strategy_advanced": "Migrate manually",
|
||||
"migration_strategy_recommended": "Migrate automatically (recommended)"
|
||||
},
|
||||
"title": "Migrate to a new adapter"
|
||||
@@ -74,7 +74,7 @@
|
||||
"setup_strategy_recommended": "This is the quickest option to create a new network and get started."
|
||||
},
|
||||
"menu_options": {
|
||||
"setup_strategy_advanced": "Advanced setup",
|
||||
"setup_strategy_advanced": "Set up manually",
|
||||
"setup_strategy_recommended": "Set up automatically (recommended)"
|
||||
},
|
||||
"title": "Set up Zigbee"
|
||||
|
||||
@@ -441,18 +441,6 @@ class EntityTriggerBase(Trigger):
|
||||
"""
|
||||
return True
|
||||
|
||||
def _is_valid_to_state(
|
||||
self, to_state: State, report_not_triggered: _NotTriggeredReasonReporter
|
||||
) -> bool:
|
||||
"""Check if to_state can fire the trigger, optionally reporting why if it can't.
|
||||
|
||||
When the state cannot fire the trigger, subclasses may use
|
||||
`report_not_triggered` to record an interesting reason - e.g. a
|
||||
non-numeric value or an unsupported unit - in the automation trace.
|
||||
The base implementation never reports.
|
||||
"""
|
||||
return self.is_valid_state(to_state)
|
||||
|
||||
def _should_include(self, state: State) -> bool:
|
||||
"""Check if an entity should participate in all/count checks.
|
||||
|
||||
@@ -604,23 +592,11 @@ class EntityTriggerBase(Trigger):
|
||||
if not from_state or not to_state:
|
||||
return
|
||||
|
||||
@callback
|
||||
def report_not_triggered(reason: str, /, **data: Any) -> None:
|
||||
"""Report why this evaluated change did not fire the trigger."""
|
||||
if did_not_trigger is None:
|
||||
return
|
||||
did_not_trigger(
|
||||
NotTriggeredInfo(reason=reason, data=data), event.context
|
||||
)
|
||||
|
||||
# The trigger should never fire if the new state is excluded.
|
||||
if to_state.state in self._excluded_states:
|
||||
return
|
||||
|
||||
# The trigger should never fire if the new state is not a target
|
||||
# state. Interesting reasons (e.g. a non-numeric value or an
|
||||
# unsupported unit) are reported for the trace.
|
||||
if not self._is_valid_to_state(to_state, report_not_triggered):
|
||||
# The trigger should never fire if the new state is excluded
|
||||
# or not a target state.
|
||||
if to_state.state in self._excluded_states or not self.is_valid_state(
|
||||
to_state
|
||||
):
|
||||
return
|
||||
|
||||
# The trigger should never fire if the origin state is excluded
|
||||
@@ -827,11 +803,7 @@ class EntityNumericalStateTriggerBase(EntityTriggerBase):
|
||||
return True
|
||||
return unit == self._valid_unit
|
||||
|
||||
def _get_threshold_value(
|
||||
self,
|
||||
threshold: ThresholdConfig | None,
|
||||
report_not_triggered: _NotTriggeredReasonReporter | None = None,
|
||||
) -> float | None:
|
||||
def _get_threshold_value(self, threshold: ThresholdConfig | None) -> float | None:
|
||||
"""Get threshold value from float or entity state."""
|
||||
if threshold is None:
|
||||
return None
|
||||
@@ -840,32 +812,14 @@ class EntityNumericalStateTriggerBase(EntityTriggerBase):
|
||||
|
||||
if not (state := self._hass.states.get(threshold.entity)): # type: ignore[arg-type]
|
||||
# Entity not found
|
||||
if report_not_triggered is not None:
|
||||
report_not_triggered(
|
||||
"threshold_entity_not_found",
|
||||
entity_id=threshold.entity,
|
||||
)
|
||||
return None
|
||||
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
if not self._is_valid_unit(unit):
|
||||
if not self._is_valid_unit(state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)):
|
||||
# Entity unit does not match the expected unit
|
||||
if report_not_triggered is not None:
|
||||
report_not_triggered(
|
||||
"threshold_unit_not_supported",
|
||||
entity_id=threshold.entity,
|
||||
unit=unit,
|
||||
)
|
||||
return None
|
||||
try:
|
||||
return float(state.state)
|
||||
except TypeError, ValueError:
|
||||
# Entity state is not a valid number
|
||||
if report_not_triggered is not None:
|
||||
report_not_triggered(
|
||||
"threshold_value_not_numeric",
|
||||
entity_id=threshold.entity,
|
||||
value=state.state,
|
||||
)
|
||||
return None
|
||||
|
||||
@override
|
||||
@@ -887,53 +841,10 @@ class EntityNumericalStateTriggerBase(EntityTriggerBase):
|
||||
return None
|
||||
|
||||
@override
|
||||
def _is_valid_to_state(
|
||||
self, to_state: State, report_not_triggered: _NotTriggeredReasonReporter
|
||||
) -> bool:
|
||||
"""Check if to_state can fire, reporting non-numeric / unit reasons."""
|
||||
return self.is_valid_state(to_state, report_not_triggered)
|
||||
|
||||
def _report_tracked_value_problem(
|
||||
self, state: State, report_not_triggered: _NotTriggeredReasonReporter
|
||||
) -> None:
|
||||
"""Report why `_get_tracked_value` rejected this state.
|
||||
|
||||
Called only when the tracked value is invalid. It mirrors the failure
|
||||
modes of `_get_tracked_value` - which integrations override, so the
|
||||
reason is derived here rather than reported inline: a state-sourced
|
||||
value with an unsupported unit, otherwise a value that is not a number.
|
||||
"""
|
||||
domain_spec = self._domain_specs[state.domain]
|
||||
raw_value: Any
|
||||
if domain_spec.value_source is None:
|
||||
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
if not self._is_valid_unit(unit):
|
||||
report_not_triggered(
|
||||
"entity_unit_not_supported",
|
||||
entity_id=state.entity_id,
|
||||
unit=unit,
|
||||
)
|
||||
return
|
||||
raw_value = state.state
|
||||
else:
|
||||
raw_value = state.attributes.get(domain_spec.value_source)
|
||||
report_not_triggered(
|
||||
"entity_value_not_numeric",
|
||||
entity_id=state.entity_id,
|
||||
value=raw_value,
|
||||
)
|
||||
|
||||
@override
|
||||
def is_valid_state(
|
||||
self,
|
||||
state: State,
|
||||
report_not_triggered: _NotTriggeredReasonReporter | None = None,
|
||||
) -> bool:
|
||||
def is_valid_state(self, state: State) -> bool:
|
||||
"""Check if the new state or state attribute matches the expected one."""
|
||||
# Handle missing or None value case first to avoid expensive exceptions
|
||||
if (current_value := self._get_tracked_value(state)) is None:
|
||||
if report_not_triggered is not None:
|
||||
self._report_tracked_value_problem(state, report_not_triggered)
|
||||
return False
|
||||
|
||||
if self._threshold_type == NumericThresholdType.ANY:
|
||||
@@ -942,32 +853,20 @@ class EntityNumericalStateTriggerBase(EntityTriggerBase):
|
||||
return True
|
||||
|
||||
if self._threshold_type == NumericThresholdType.ABOVE:
|
||||
if (
|
||||
limit := self._get_threshold_value(self.threshold, report_not_triggered)
|
||||
) is None:
|
||||
if (limit := self._get_threshold_value(self.threshold)) is None:
|
||||
# Entity not found or invalid number, don't trigger
|
||||
return False
|
||||
return current_value > limit
|
||||
if self._threshold_type == NumericThresholdType.BELOW:
|
||||
if (
|
||||
limit := self._get_threshold_value(self.threshold, report_not_triggered)
|
||||
) is None:
|
||||
if (limit := self._get_threshold_value(self.threshold)) is None:
|
||||
# Entity not found or invalid number, don't trigger
|
||||
return False
|
||||
return current_value < limit
|
||||
|
||||
# Mode is BETWEEN or OUTSIDE. Evaluate the lower limit first so at most
|
||||
# one not-triggered reason is reported per change.
|
||||
lower_limit = self._get_threshold_value(
|
||||
self.lower_threshold, report_not_triggered
|
||||
)
|
||||
if lower_limit is None:
|
||||
# Entity not found or invalid number, don't trigger
|
||||
return False
|
||||
upper_limit = self._get_threshold_value(
|
||||
self.upper_threshold, report_not_triggered
|
||||
)
|
||||
if upper_limit is None:
|
||||
# Mode is BETWEEN or OUTSIDE
|
||||
lower_limit = self._get_threshold_value(self.lower_threshold)
|
||||
upper_limit = self._get_threshold_value(self.upper_threshold)
|
||||
if lower_limit is None or upper_limit is None:
|
||||
# Entity not found or invalid number, don't trigger
|
||||
return False
|
||||
between = lower_limit <= current_value <= upper_limit
|
||||
@@ -987,41 +886,7 @@ class EntityNumericalStateTriggerWithUnitBase(EntityNumericalStateTriggerBase):
|
||||
return state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
@override
|
||||
def _report_tracked_value_problem(
|
||||
self, state: State, report_not_triggered: _NotTriggeredReasonReporter
|
||||
) -> None:
|
||||
"""Report why `_get_tracked_value` rejected this state.
|
||||
|
||||
Mirrors the with-unit failure modes: a value that is not a number,
|
||||
otherwise a unit that cannot be converted to the base unit.
|
||||
"""
|
||||
domain_spec = self._domain_specs[state.domain]
|
||||
raw_value: Any
|
||||
if domain_spec.value_source is None:
|
||||
raw_value = state.state
|
||||
else:
|
||||
raw_value = state.attributes.get(domain_spec.value_source)
|
||||
try:
|
||||
float(raw_value)
|
||||
except TypeError, ValueError:
|
||||
report_not_triggered(
|
||||
"entity_value_not_numeric",
|
||||
entity_id=state.entity_id,
|
||||
value=raw_value,
|
||||
)
|
||||
return
|
||||
report_not_triggered(
|
||||
"entity_unit_not_supported",
|
||||
entity_id=state.entity_id,
|
||||
unit=self._get_entity_unit(state),
|
||||
)
|
||||
|
||||
@override
|
||||
def _get_threshold_value(
|
||||
self,
|
||||
threshold: ThresholdConfig | None,
|
||||
report_not_triggered: _NotTriggeredReasonReporter | None = None,
|
||||
) -> float | None:
|
||||
def _get_threshold_value(self, threshold: ThresholdConfig | None) -> float | None:
|
||||
"""Get threshold value from float or entity state."""
|
||||
if threshold is None:
|
||||
return None
|
||||
@@ -1034,35 +899,19 @@ class EntityNumericalStateTriggerWithUnitBase(EntityNumericalStateTriggerBase):
|
||||
|
||||
if not (state := self._hass.states.get(threshold.entity)): # type: ignore[arg-type]
|
||||
# Entity not found
|
||||
if report_not_triggered is not None:
|
||||
report_not_triggered(
|
||||
"threshold_entity_not_found",
|
||||
entity_id=threshold.entity,
|
||||
)
|
||||
return None
|
||||
try:
|
||||
value = float(state.state)
|
||||
except TypeError, ValueError:
|
||||
# Entity state is not a valid number
|
||||
if report_not_triggered is not None:
|
||||
report_not_triggered(
|
||||
"threshold_value_not_numeric",
|
||||
entity_id=threshold.entity,
|
||||
value=state.state,
|
||||
)
|
||||
return None
|
||||
|
||||
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
try:
|
||||
return self._unit_converter.convert(value, unit, self._base_unit)
|
||||
return self._unit_converter.convert(
|
||||
value, state.attributes.get(ATTR_UNIT_OF_MEASUREMENT), self._base_unit
|
||||
)
|
||||
except HomeAssistantError:
|
||||
# Unit conversion failed (i.e. incompatible units), treat as invalid number
|
||||
if report_not_triggered is not None:
|
||||
report_not_triggered(
|
||||
"threshold_unit_not_supported",
|
||||
entity_id=threshold.entity,
|
||||
unit=unit,
|
||||
)
|
||||
return None
|
||||
|
||||
@override
|
||||
@@ -1439,13 +1288,6 @@ class TriggerNotTriggeredReporter(Protocol):
|
||||
"""Report that the trigger did not fire."""
|
||||
|
||||
|
||||
class _NotTriggeredReasonReporter(Protocol):
|
||||
"""Reports why an evaluated change did not fire an entity trigger."""
|
||||
|
||||
def __call__(self, reason: str, /, **data: Any) -> None:
|
||||
"""Report, with diagnostic data, why the change did not fire."""
|
||||
|
||||
|
||||
class TriggerNotTriggeredAction(Protocol):
|
||||
"""Protocol type for the did_not_trigger consumer callback.
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -3313,7 +3313,7 @@ volkszaehler==0.4.0
|
||||
volvocarsapi==0.4.3
|
||||
|
||||
# homeassistant.components.verisure
|
||||
vsure==2.7.1
|
||||
vsure==2.8.0
|
||||
|
||||
# homeassistant.components.vasttrafik
|
||||
vtjp==0.2.1
|
||||
|
||||
@@ -275,9 +275,9 @@ async def test_subentry_options_thinking_budget_more_than_max(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
options = await hass.config_entries.subentries.async_configure(
|
||||
options["flow_id"],
|
||||
{"chat_model": "claude-sonnet-4-5"},
|
||||
@@ -330,9 +330,9 @@ async def test_subentry_web_search_user_location(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
options = await hass.config_entries.subentries.async_configure(
|
||||
options["flow_id"],
|
||||
{
|
||||
@@ -424,7 +424,7 @@ async def test_model_list(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
assert options["data_schema"].schema["chat_model"].config["options"] == snapshot
|
||||
|
||||
|
||||
@@ -447,9 +447,9 @@ async def test_invalid_model(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step but with api error
|
||||
# Configure additional step but with api error
|
||||
with patch(
|
||||
"homeassistant.components.anthropic.config_flow.anthropic.resources.models.AsyncModels.retrieve",
|
||||
new_callable=AsyncMock,
|
||||
@@ -877,12 +877,12 @@ async def test_ai_task_subentry_not_loaded(
|
||||
assert result.get("reason") == "entry_not_loaded"
|
||||
|
||||
|
||||
async def test_creating_ai_task_subentry_advanced(
|
||||
async def test_creating_ai_task_subentry_additional(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test creating an AI task subentry with advanced settings."""
|
||||
"""Test creating an AI task subentry with additional settings."""
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
(mock_config_entry.entry_id, "ai_task_data"),
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
@@ -891,7 +891,7 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "init"
|
||||
|
||||
# Go to advanced settings
|
||||
# Go to additional settings
|
||||
result2 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -901,9 +901,9 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "advanced"
|
||||
assert result2.get("step_id") == "additional"
|
||||
|
||||
# Configure advanced settings
|
||||
# Configure additional settings
|
||||
result3 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ import pytest
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.dnsip.config_flow import DATA_SCHEMA
|
||||
from homeassistant.components.dnsip.const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_HOSTNAME,
|
||||
CONF_IPV4,
|
||||
CONF_IPV6,
|
||||
@@ -50,7 +50,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOSTNAME: "home-assistant.io", CONF_ADVANCED_OPTIONS: {}},
|
||||
{CONF_HOSTNAME: "home-assistant.io", CONF_ADDITIONAL_OPTIONS: {}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -71,7 +71,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_with_advanced_options(hass: HomeAssistant) -> None:
|
||||
async def test_form_with_additional_options(hass: HomeAssistant) -> None:
|
||||
"""Test we can submit the form with custom resolver and port options."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -95,7 +95,7 @@ async def test_form_with_advanced_options(hass: HomeAssistant) -> None:
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOSTNAME: "home-assistant.io",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_RESOLVER: "8.8.8.8",
|
||||
CONF_RESOLVER_IPV6: "2620:119:53::53",
|
||||
CONF_PORT: 53,
|
||||
@@ -136,7 +136,7 @@ async def test_form_error(hass: HomeAssistant) -> None:
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOSTNAME: "home-assistant.io",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -185,7 +185,7 @@ async def test_flow_already_exist(hass: HomeAssistant) -> None:
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOSTNAME: "home-assistant.io",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -246,9 +246,9 @@ async def test_subentry_unsupported_model(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -300,9 +300,9 @@ async def test_subentry_reasoning_effort_list(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -354,9 +354,9 @@ async def test_subentry_reasoning_summary_visibility(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -403,7 +403,7 @@ async def test_subentry_reasoning_summary_options(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
@@ -450,7 +450,7 @@ async def test_subentry_reasoning_summary_default_sanitized_on_model_switch(
|
||||
CONF_LLM_HASS_API: ["assist"],
|
||||
},
|
||||
)
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
@@ -515,9 +515,9 @@ async def test_subentry_service_tier_list(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -561,9 +561,9 @@ async def test_subentry_unsupported_reasoning_effort(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -1144,9 +1144,9 @@ async def test_subentry_web_search_user_location(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -1292,12 +1292,12 @@ async def test_ai_task_subentry_not_loaded(
|
||||
assert result.get("reason") == "entry_not_loaded"
|
||||
|
||||
|
||||
async def test_creating_ai_task_subentry_advanced(
|
||||
async def test_creating_ai_task_subentry_additional(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test creating an AI task subentry with advanced settings."""
|
||||
"""Test creating an AI task subentry with additional settings."""
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
(mock_config_entry.entry_id, "ai_task_data"),
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
@@ -1306,7 +1306,7 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "init"
|
||||
|
||||
# Go to advanced settings
|
||||
# Go to additional settings
|
||||
result2 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -1316,9 +1316,9 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "advanced"
|
||||
assert result2.get("step_id") == "additional"
|
||||
|
||||
# Configure advanced settings
|
||||
# Configure additional settings
|
||||
result3 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sql.const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_COLUMN_NAME,
|
||||
CONF_QUERY,
|
||||
DOMAIN,
|
||||
@@ -35,7 +35,7 @@ from tests.common import MockConfigEntry
|
||||
ENTRY_CONFIG = {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -46,7 +46,7 @@ ENTRY_CONFIG_BLANK_QUERY = {
|
||||
CONF_NAME: "Get Value",
|
||||
CONF_QUERY: " ",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -56,7 +56,7 @@ ENTRY_CONFIG_BLANK_QUERY = {
|
||||
ENTRY_CONFIG_WITH_VALUE_TEMPLATE = {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -68,7 +68,7 @@ ENTRY_CONFIG_WITH_QUERY_TEMPLATE = {
|
||||
" 5 {% else %} 6 {% endif %} as value"
|
||||
),
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -77,7 +77,7 @@ ENTRY_CONFIG_WITH_QUERY_TEMPLATE = {
|
||||
ENTRY_CONFIG_WITH_BROKEN_QUERY_TEMPLATE = {
|
||||
CONF_QUERY: "SELECT {{ 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -86,7 +86,7 @@ ENTRY_CONFIG_WITH_BROKEN_QUERY_TEMPLATE = {
|
||||
ENTRY_CONFIG_WITH_BROKEN_QUERY_TEMPLATE_OPT = {
|
||||
CONF_QUERY: "SELECT {{ 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -95,7 +95,7 @@ ENTRY_CONFIG_WITH_BROKEN_QUERY_TEMPLATE_OPT = {
|
||||
ENTRY_CONFIG_INVALID_QUERY = {
|
||||
CONF_QUERY: "SELECT 5 FROM as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -104,7 +104,7 @@ ENTRY_CONFIG_INVALID_QUERY = {
|
||||
ENTRY_CONFIG_INVALID_QUERY_2 = {
|
||||
CONF_QUERY: "SELECT5 FROM as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -113,7 +113,7 @@ ENTRY_CONFIG_INVALID_QUERY_2 = {
|
||||
ENTRY_CONFIG_INVALID_QUERY_3 = {
|
||||
CONF_QUERY: ";;",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -122,7 +122,7 @@ ENTRY_CONFIG_INVALID_QUERY_3 = {
|
||||
ENTRY_CONFIG_INVALID_QUERY_OPT = {
|
||||
CONF_QUERY: "SELECT 5 FROM as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -131,7 +131,7 @@ ENTRY_CONFIG_INVALID_QUERY_OPT = {
|
||||
ENTRY_CONFIG_INVALID_QUERY_2_OPT = {
|
||||
CONF_QUERY: "SELECT5 FROM as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -140,7 +140,7 @@ ENTRY_CONFIG_INVALID_QUERY_2_OPT = {
|
||||
ENTRY_CONFIG_INVALID_QUERY_3_OPT = {
|
||||
CONF_QUERY: ";;",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -152,7 +152,7 @@ ENTRY_CONFIG_QUERY_READ_ONLY_CTE = {
|
||||
" SELECT state FROM test WHERE row_num = 1 LIMIT 1;"
|
||||
),
|
||||
CONF_COLUMN_NAME: "state",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -160,7 +160,7 @@ ENTRY_CONFIG_QUERY_READ_ONLY_CTE = {
|
||||
ENTRY_CONFIG_QUERY_NO_READ_ONLY = {
|
||||
CONF_QUERY: "UPDATE states SET state = 999999 WHERE state_id = 11125",
|
||||
CONF_COLUMN_NAME: "state",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -171,7 +171,7 @@ ENTRY_CONFIG_QUERY_NO_READ_ONLY_CTE = {
|
||||
" UPDATE states SET states.state = test.state;"
|
||||
),
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -182,7 +182,7 @@ ENTRY_CONFIG_QUERY_READ_ONLY_CTE_OPT = {
|
||||
" SELECT state FROM test WHERE row_num = 1 LIMIT 1;"
|
||||
),
|
||||
CONF_COLUMN_NAME: "state",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -190,7 +190,7 @@ ENTRY_CONFIG_QUERY_READ_ONLY_CTE_OPT = {
|
||||
ENTRY_CONFIG_QUERY_NO_READ_ONLY_OPT = {
|
||||
CONF_QUERY: "UPDATE 5 as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -201,7 +201,7 @@ ENTRY_CONFIG_QUERY_NO_READ_ONLY_CTE_OPT = {
|
||||
" UPDATE states SET states.state = test.state;"
|
||||
),
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -210,7 +210,7 @@ ENTRY_CONFIG_QUERY_NO_READ_ONLY_CTE_OPT = {
|
||||
ENTRY_CONFIG_MULTIPLE_QUERIES = {
|
||||
CONF_QUERY: "SELECT 5 as state; UPDATE states SET state = 10;",
|
||||
CONF_COLUMN_NAME: "state",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -219,7 +219,7 @@ ENTRY_CONFIG_MULTIPLE_QUERIES = {
|
||||
ENTRY_CONFIG_MULTIPLE_QUERIES_OPT = {
|
||||
CONF_QUERY: "SELECT 5 as state; UPDATE states SET state = 10;",
|
||||
CONF_COLUMN_NAME: "state",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -228,7 +228,7 @@ ENTRY_CONFIG_MULTIPLE_QUERIES_OPT = {
|
||||
ENTRY_CONFIG_INVALID_COLUMN_NAME = {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -236,7 +236,7 @@ ENTRY_CONFIG_INVALID_COLUMN_NAME = {
|
||||
ENTRY_CONFIG_INVALID_COLUMN_NAME_OPT = {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -244,7 +244,7 @@ ENTRY_CONFIG_INVALID_COLUMN_NAME_OPT = {
|
||||
ENTRY_CONFIG_NO_RESULTS = {
|
||||
CONF_QUERY: "SELECT kalle as value from no_table;",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -365,8 +365,8 @@ async def init_integration(
|
||||
"""Set up the SQL integration in Home Assistant."""
|
||||
if not options:
|
||||
options = ENTRY_CONFIG
|
||||
if CONF_ADVANCED_OPTIONS not in options:
|
||||
options[CONF_ADVANCED_OPTIONS] = {}
|
||||
if CONF_ADDITIONAL_OPTIONS not in options:
|
||||
options[CONF_ADDITIONAL_OPTIONS] = {}
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
title=title,
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sql.const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_COLUMN_NAME,
|
||||
CONF_QUERY,
|
||||
DOMAIN,
|
||||
@@ -100,7 +100,7 @@ async def test_form_simple(
|
||||
assert result["options"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -141,7 +141,7 @@ async def test_form_with_query_template(
|
||||
" 5 {% else %} 6 {% endif %} as value"
|
||||
),
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -188,7 +188,7 @@ async def test_form_with_broken_query_template(
|
||||
" 5 {% else %} 6 {% endif %} as value"
|
||||
),
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -225,7 +225,7 @@ async def test_form_with_value_template(
|
||||
assert result["options"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
},
|
||||
@@ -348,7 +348,7 @@ async def test_flow_fails_invalid_query(hass: HomeAssistant) -> None:
|
||||
assert result["options"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -391,7 +391,7 @@ async def test_flow_fails_invalid_column_name(hass: HomeAssistant) -> None:
|
||||
assert result["options"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -407,7 +407,7 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -430,7 +430,7 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
@@ -443,7 +443,7 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_VALUE_TEMPLATE: "{{ value }}",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
@@ -460,7 +460,7 @@ async def test_options_flow_name_previously_removed(hass: HomeAssistant) -> None
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -482,7 +482,7 @@ async def test_options_flow_name_previously_removed(hass: HomeAssistant) -> None
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -493,7 +493,7 @@ async def test_options_flow_name_previously_removed(hass: HomeAssistant) -> None
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -507,7 +507,7 @@ async def test_options_flow_fails_db_url(hass: HomeAssistant) -> None:
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -529,7 +529,7 @@ async def test_options_flow_fails_db_url(hass: HomeAssistant) -> None:
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -546,7 +546,7 @@ async def test_options_flow_fails_invalid_query(hass: HomeAssistant) -> None:
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -630,7 +630,7 @@ async def test_options_flow_fails_invalid_query(hass: HomeAssistant) -> None:
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -640,7 +640,7 @@ async def test_options_flow_fails_invalid_query(hass: HomeAssistant) -> None:
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -654,7 +654,7 @@ async def test_options_flow_fails_invalid_column_name(hass: HomeAssistant) -> No
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -682,7 +682,7 @@ async def test_options_flow_fails_invalid_column_name(hass: HomeAssistant) -> No
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -692,7 +692,7 @@ async def test_options_flow_fails_invalid_column_name(hass: HomeAssistant) -> No
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -706,7 +706,7 @@ async def test_options_flow_db_url_empty(hass: HomeAssistant) -> None:
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -727,7 +727,7 @@ async def test_options_flow_db_url_empty(hass: HomeAssistant) -> None:
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -738,7 +738,7 @@ async def test_options_flow_db_url_empty(hass: HomeAssistant) -> None:
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as size",
|
||||
CONF_COLUMN_NAME: "size",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -770,7 +770,7 @@ async def test_full_flow_not_recorder_db(
|
||||
{
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -781,7 +781,7 @@ async def test_full_flow_not_recorder_db(
|
||||
assert result["options"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
}
|
||||
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
@@ -796,7 +796,7 @@ async def test_full_flow_not_recorder_db(
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -807,7 +807,7 @@ async def test_full_flow_not_recorder_db(
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
@@ -822,7 +822,7 @@ async def test_device_state_class(hass: HomeAssistant) -> None:
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -839,7 +839,7 @@ async def test_device_state_class(hass: HomeAssistant) -> None:
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -852,7 +852,7 @@ async def test_device_state_class(hass: HomeAssistant) -> None:
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -868,7 +868,7 @@ async def test_device_state_class(hass: HomeAssistant) -> None:
|
||||
user_input={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
@@ -881,7 +881,7 @@ async def test_device_state_class(hass: HomeAssistant) -> None:
|
||||
assert result["data"] == {
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sql.const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_COLUMN_NAME,
|
||||
CONF_QUERY,
|
||||
DOMAIN,
|
||||
@@ -122,7 +122,7 @@ async def test_migration_from_future(
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5.01 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
},
|
||||
entry_id="1",
|
||||
version=3,
|
||||
@@ -168,7 +168,54 @@ async def test_migration_from_v1_to_v2(
|
||||
assert config_entry.options == {
|
||||
CONF_QUERY: "SELECT 5.01 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_VALUE_TEMPLATE: "{{ value | int }}",
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
}
|
||||
|
||||
state = hass.states.get("sensor.test_migration")
|
||||
assert state.state == "5"
|
||||
|
||||
|
||||
async def test_migration_from_v2_1_to_v2_2(
|
||||
recorder_mock: Recorder, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Test migration from version 2.1 to 2.2 renames the options section."""
|
||||
config_entry = MockConfigEntry(
|
||||
title="Test migration",
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_USER,
|
||||
data={},
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5.01 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
"advanced_options": {
|
||||
CONF_VALUE_TEMPLATE: "{{ value | int }}",
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
},
|
||||
entry_id="1",
|
||||
version=2,
|
||||
minor_version=1,
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
assert config_entry.minor_version == 2
|
||||
|
||||
assert "advanced_options" not in config_entry.options
|
||||
assert config_entry.options == {
|
||||
CONF_QUERY: "SELECT 5.01 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_VALUE_TEMPLATE: "{{ value | int }}",
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.sql.const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_COLUMN_NAME,
|
||||
CONF_QUERY,
|
||||
DOMAIN,
|
||||
@@ -94,7 +94,7 @@ async def test_query_value_template(
|
||||
options = {
|
||||
CONF_QUERY: "SELECT 5.01 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_VALUE_TEMPLATE: "{{ value | int }}",
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
@@ -126,7 +126,7 @@ async def test_template_query(
|
||||
" 5 {% else %} 6 {% endif %} as value"
|
||||
),
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_VALUE_TEMPLATE: "{{ value | int }}",
|
||||
},
|
||||
}
|
||||
@@ -168,7 +168,7 @@ async def test_broken_template_query(
|
||||
options = {
|
||||
CONF_QUERY: "SELECT {{ 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_VALUE_TEMPLATE: "{{ value | int }}",
|
||||
},
|
||||
}
|
||||
@@ -672,7 +672,7 @@ async def test_attributes_from_entry_config(
|
||||
options={
|
||||
CONF_QUERY: "SELECT 5 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
CONF_DEVICE_CLASS: SensorDeviceClass.DATA_SIZE,
|
||||
CONF_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
@@ -694,7 +694,7 @@ async def test_attributes_from_entry_config(
|
||||
options={
|
||||
CONF_QUERY: "SELECT 6 as value",
|
||||
CONF_COLUMN_NAME: "value",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_UNIT_OF_MEASUREMENT: "MiB",
|
||||
},
|
||||
},
|
||||
|
||||
+26
-437
@@ -84,7 +84,6 @@ from homeassistant.helpers.trigger import (
|
||||
async_initialize_triggers,
|
||||
async_validate_trigger_config,
|
||||
make_entity_numerical_state_changed_trigger,
|
||||
make_entity_numerical_state_changed_with_unit_trigger,
|
||||
make_entity_numerical_state_crossed_threshold_trigger,
|
||||
make_entity_origin_state_trigger,
|
||||
make_entity_target_state_trigger,
|
||||
@@ -106,13 +105,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
def _reported_reasons(
|
||||
did_not_trigger_reports: list[NotTriggeredInfo],
|
||||
) -> list[tuple[str, Any]]:
|
||||
"""Return the (reason, data) pair of each recorded did-not-trigger report."""
|
||||
return [(report.reason, report.data) for report in did_not_trigger_reports]
|
||||
|
||||
|
||||
async def _arm_numerical_trigger(
|
||||
hass: HomeAssistant,
|
||||
trigger_cls: type[Trigger],
|
||||
@@ -1845,23 +1837,14 @@ async def test_numerical_state_attribute_changed_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("entity_value_not_numeric", {"entity_id": "test.test_entity", "value": None})
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the attribute value is invalid
|
||||
for value in ("cat", None):
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": value})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": value},
|
||||
)
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the above sensor does not exist
|
||||
hass.states.async_remove("sensor.above")
|
||||
@@ -1869,10 +1852,7 @@ async def test_numerical_state_attribute_changed_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.above"})
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the above sensor state is not numeric
|
||||
for invalid_value in ("cat", None):
|
||||
@@ -1881,17 +1861,7 @@ async def test_numerical_state_attribute_changed_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": None},
|
||||
),
|
||||
(
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.above", "value": str(invalid_value)},
|
||||
),
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Reset the above sensor state to a valid numeric value
|
||||
hass.states.async_set("sensor.above", "10")
|
||||
@@ -1902,11 +1872,7 @@ async def test_numerical_state_attribute_changed_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("entity_value_not_numeric", {"entity_id": "test.test_entity", "value": None}),
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.below"}),
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the below sensor state is not numeric
|
||||
for invalid_value in ("cat", None):
|
||||
@@ -1915,17 +1881,7 @@ async def test_numerical_state_attribute_changed_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": None},
|
||||
),
|
||||
(
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.below", "value": str(invalid_value)},
|
||||
),
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
unsub()
|
||||
|
||||
@@ -2332,11 +2288,6 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
entity_did_not_trigger_reports,
|
||||
)
|
||||
)
|
||||
# Both triggers report a non-numeric tracked value identically.
|
||||
entity_not_numeric = (
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": None},
|
||||
)
|
||||
|
||||
# 77°F = 25°C, within range (above 20, below 30) - should trigger numerical
|
||||
# Entity automation won't trigger because sensor.above/below don't exist yet
|
||||
@@ -2351,13 +2302,8 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
await hass.async_block_till_done()
|
||||
assert len(numeric_calls) == 1
|
||||
assert entity_calls == []
|
||||
# The entity-threshold trigger can't resolve its limits yet (sensors absent)
|
||||
assert numeric_did_not_trigger_reports == []
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.above"})
|
||||
]
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
numeric_calls.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
|
||||
# 59°F = 15°C, below 20°C - should NOT trigger
|
||||
hass.states.async_set(
|
||||
@@ -2370,11 +2316,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert numeric_did_not_trigger_reports == []
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.above"})
|
||||
]
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# 95°F = 35°C, above 30°C - should NOT trigger
|
||||
hass.states.async_set(
|
||||
@@ -2387,11 +2329,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert numeric_did_not_trigger_reports == []
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.above"})
|
||||
]
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Set up entity limits referencing sensors that report in °F
|
||||
hass.states.async_set(
|
||||
@@ -2425,10 +2363,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {})
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [entity_not_numeric]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [entity_not_numeric]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the attribute value is invalid
|
||||
for value in ("cat", None):
|
||||
@@ -2442,20 +2377,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": value},
|
||||
)
|
||||
]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": value},
|
||||
)
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the unit is incompatible
|
||||
hass.states.async_set(
|
||||
@@ -2468,20 +2390,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_unit_not_supported",
|
||||
{"entity_id": "test.test_entity", "unit": "invalid_unit"},
|
||||
)
|
||||
]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_unit_not_supported",
|
||||
{"entity_id": "test.test_entity", "unit": "invalid_unit"},
|
||||
)
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the above sensor does not exist
|
||||
hass.states.async_remove("sensor.above")
|
||||
@@ -2500,15 +2409,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
# The intermediate None reports a non-numeric value on both triggers; the
|
||||
# missing threshold entity is reported only by the entity-threshold trigger.
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [entity_not_numeric]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
entity_not_numeric,
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.above"}),
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the above sensor state is not numeric
|
||||
for invalid_value in ("cat", None):
|
||||
@@ -2535,18 +2436,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [
|
||||
entity_not_numeric
|
||||
]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
entity_not_numeric,
|
||||
(
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.above", "value": str(invalid_value)},
|
||||
),
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the above sensor's unit is incompatible
|
||||
hass.states.async_set(
|
||||
@@ -2569,16 +2459,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [entity_not_numeric]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
entity_not_numeric,
|
||||
(
|
||||
"threshold_unit_not_supported",
|
||||
{"entity_id": "sensor.above", "unit": "invalid_unit"},
|
||||
),
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Reset the above sensor state to a valid numeric value
|
||||
hass.states.async_set(
|
||||
@@ -2604,13 +2485,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [entity_not_numeric]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
entity_not_numeric,
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.below"}),
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the below sensor state is not numeric
|
||||
for invalid_value in ("cat", None):
|
||||
@@ -2633,18 +2508,7 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [
|
||||
entity_not_numeric
|
||||
]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
entity_not_numeric,
|
||||
(
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.below", "value": str(invalid_value)},
|
||||
),
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the below sensor's unit is incompatible
|
||||
hass.states.async_set(
|
||||
@@ -2667,242 +2531,12 @@ async def test_numerical_state_attribute_changed_with_unit_error_handling(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert numeric_calls == entity_calls == []
|
||||
assert _reported_reasons(numeric_did_not_trigger_reports) == [entity_not_numeric]
|
||||
assert _reported_reasons(entity_did_not_trigger_reports) == [
|
||||
entity_not_numeric,
|
||||
(
|
||||
"threshold_unit_not_supported",
|
||||
{"entity_id": "sensor.below", "unit": "invalid_unit"},
|
||||
),
|
||||
]
|
||||
numeric_did_not_trigger_reports.clear()
|
||||
entity_did_not_trigger_reports.clear()
|
||||
assert numeric_did_not_trigger_reports == entity_did_not_trigger_reports == []
|
||||
|
||||
for unsub in unsubs:
|
||||
unsub()
|
||||
|
||||
|
||||
# State-sourced numerical triggers: brightness-style (percentage) and
|
||||
# temperature-style (with unit conversion to a base unit).
|
||||
_PERCENT_CHANGED_TRIGGER = make_entity_numerical_state_changed_trigger(
|
||||
{"test": DomainSpec()}, "%"
|
||||
)
|
||||
_TEMPERATURE_CHANGED_TRIGGER = make_entity_numerical_state_changed_with_unit_trigger(
|
||||
{"test": DomainSpec()}, UnitOfTemperature.CELSIUS, TemperatureConverter
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"trigger_cls",
|
||||
"good_unit",
|
||||
"bad_state",
|
||||
"bad_unit",
|
||||
"expected_reason",
|
||||
"expected_data",
|
||||
),
|
||||
[
|
||||
pytest.param(
|
||||
_PERCENT_CHANGED_TRIGGER,
|
||||
"%",
|
||||
"cat",
|
||||
"%",
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": "cat"},
|
||||
id="non-numeric",
|
||||
),
|
||||
pytest.param(
|
||||
_PERCENT_CHANGED_TRIGGER,
|
||||
"%",
|
||||
"50",
|
||||
"kg",
|
||||
"entity_unit_not_supported",
|
||||
{"entity_id": "test.test_entity", "unit": "kg"},
|
||||
id="unsupported-unit",
|
||||
),
|
||||
pytest.param(
|
||||
_TEMPERATURE_CHANGED_TRIGGER,
|
||||
"°C",
|
||||
"cat",
|
||||
"°C",
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": "cat"},
|
||||
id="with-unit-non-numeric",
|
||||
),
|
||||
pytest.param(
|
||||
_TEMPERATURE_CHANGED_TRIGGER,
|
||||
"°C",
|
||||
"50",
|
||||
"kg",
|
||||
"entity_unit_not_supported",
|
||||
{"entity_id": "test.test_entity", "unit": "kg"},
|
||||
id="with-unit-incompatible-unit",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_numerical_trigger_reports_invalid_tracked_value(
|
||||
hass: HomeAssistant,
|
||||
trigger_cls: type[Trigger],
|
||||
good_unit: str,
|
||||
bad_state: str,
|
||||
bad_unit: str,
|
||||
expected_reason: str,
|
||||
expected_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Report a non-numeric value or unsupported unit on the tracked entity."""
|
||||
calls: list[dict[str, Any]] = []
|
||||
did_not_trigger_reports: list[NotTriggeredInfo] = []
|
||||
hass.states.async_set(
|
||||
"test.test_entity", "10", {ATTR_UNIT_OF_MEASUREMENT: good_unit}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
unsub = await _arm_numerical_trigger(
|
||||
hass,
|
||||
trigger_cls,
|
||||
{"threshold": {"type": "any"}},
|
||||
calls,
|
||||
did_not_trigger_reports,
|
||||
)
|
||||
|
||||
hass.states.async_set(
|
||||
"test.test_entity", bad_state, {ATTR_UNIT_OF_MEASUREMENT: bad_unit}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(expected_reason, expected_data)
|
||||
]
|
||||
|
||||
unsub()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"trigger_cls",
|
||||
"good_unit",
|
||||
"threshold_state",
|
||||
"threshold_unit",
|
||||
"expected_reason",
|
||||
"expected_data",
|
||||
),
|
||||
[
|
||||
pytest.param(
|
||||
_PERCENT_CHANGED_TRIGGER,
|
||||
"%",
|
||||
"cat",
|
||||
"%",
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.limit", "value": "cat"},
|
||||
id="non-numeric",
|
||||
),
|
||||
pytest.param(
|
||||
_PERCENT_CHANGED_TRIGGER,
|
||||
"%",
|
||||
"30",
|
||||
"kg",
|
||||
"threshold_unit_not_supported",
|
||||
{"entity_id": "sensor.limit", "unit": "kg"},
|
||||
id="unsupported-unit",
|
||||
),
|
||||
pytest.param(
|
||||
_TEMPERATURE_CHANGED_TRIGGER,
|
||||
"°C",
|
||||
"cat",
|
||||
"°C",
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.limit", "value": "cat"},
|
||||
id="with-unit-non-numeric",
|
||||
),
|
||||
pytest.param(
|
||||
_TEMPERATURE_CHANGED_TRIGGER,
|
||||
"°C",
|
||||
"30",
|
||||
"kg",
|
||||
"threshold_unit_not_supported",
|
||||
{"entity_id": "sensor.limit", "unit": "kg"},
|
||||
id="with-unit-incompatible-unit",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_numerical_trigger_reports_invalid_threshold_entity(
|
||||
hass: HomeAssistant,
|
||||
trigger_cls: type[Trigger],
|
||||
good_unit: str,
|
||||
threshold_state: str,
|
||||
threshold_unit: str,
|
||||
expected_reason: str,
|
||||
expected_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Report a non-numeric value or unsupported unit on a threshold entity."""
|
||||
calls: list[dict[str, Any]] = []
|
||||
did_not_trigger_reports: list[NotTriggeredInfo] = []
|
||||
hass.states.async_set(
|
||||
"sensor.limit", threshold_state, {ATTR_UNIT_OF_MEASUREMENT: threshold_unit}
|
||||
)
|
||||
hass.states.async_set(
|
||||
"test.test_entity", "10", {ATTR_UNIT_OF_MEASUREMENT: good_unit}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
unsub = await _arm_numerical_trigger(
|
||||
hass,
|
||||
trigger_cls,
|
||||
{"threshold": {"type": "above", "value": {"entity": "sensor.limit"}}},
|
||||
calls,
|
||||
did_not_trigger_reports,
|
||||
)
|
||||
|
||||
hass.states.async_set(
|
||||
"test.test_entity", "20", {ATTR_UNIT_OF_MEASUREMENT: good_unit}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(expected_reason, expected_data)
|
||||
]
|
||||
|
||||
unsub()
|
||||
|
||||
|
||||
async def test_numerical_trigger_reports_single_reason_for_between(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Two invalid between-thresholds yield a single diagnostic for the lower one."""
|
||||
calls: list[dict[str, Any]] = []
|
||||
did_not_trigger_reports: list[NotTriggeredInfo] = []
|
||||
hass.states.async_set("sensor.low", "cat", {ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
hass.states.async_set("sensor.high", "dog", {ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
hass.states.async_set("test.test_entity", "10", {ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
unsub = await _arm_numerical_trigger(
|
||||
hass,
|
||||
_PERCENT_CHANGED_TRIGGER,
|
||||
{
|
||||
"threshold": {
|
||||
"type": "between",
|
||||
"value_min": {"entity": "sensor.low"},
|
||||
"value_max": {"entity": "sensor.high"},
|
||||
}
|
||||
},
|
||||
calls,
|
||||
did_not_trigger_reports,
|
||||
)
|
||||
|
||||
hass.states.async_set("test.test_entity", "20", {ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("threshold_value_not_numeric", {"entity_id": "sensor.low", "value": "cat"})
|
||||
]
|
||||
|
||||
unsub()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("trigger_options", "expected_result"),
|
||||
[
|
||||
@@ -3345,11 +2979,8 @@ async def test_numerical_state_attribute_crossed_threshold_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("entity_value_not_numeric", {"entity_id": "test.test_entity", "value": None})
|
||||
]
|
||||
assert did_not_trigger_reports == []
|
||||
calls.clear()
|
||||
did_not_trigger_reports.clear()
|
||||
|
||||
# Test the trigger does not fire when the attribute value is outside the limits
|
||||
for value in (5, 95):
|
||||
@@ -3362,23 +2993,14 @@ async def test_numerical_state_attribute_crossed_threshold_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("entity_value_not_numeric", {"entity_id": "test.test_entity", "value": None})
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the attribute value is invalid
|
||||
for value in ("cat", None):
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": value})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": value},
|
||||
)
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the lower sensor does not exist
|
||||
hass.states.async_remove("sensor.lower")
|
||||
@@ -3386,10 +3008,7 @@ async def test_numerical_state_attribute_crossed_threshold_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.lower"})
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the lower sensor state is not numeric
|
||||
for invalid_value in ("cat", None):
|
||||
@@ -3398,17 +3017,7 @@ async def test_numerical_state_attribute_crossed_threshold_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": None},
|
||||
),
|
||||
(
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.lower", "value": str(invalid_value)},
|
||||
),
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Reset the lower sensor state to a valid numeric value
|
||||
hass.states.async_set("sensor.lower", "10")
|
||||
@@ -3419,11 +3028,7 @@ async def test_numerical_state_attribute_crossed_threshold_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
("entity_value_not_numeric", {"entity_id": "test.test_entity", "value": None}),
|
||||
("threshold_entity_not_found", {"entity_id": "sensor.upper"}),
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
# Test the trigger does not fire when the upper sensor state is not numeric
|
||||
for invalid_value in ("cat", None):
|
||||
@@ -3432,17 +3037,7 @@ async def test_numerical_state_attribute_crossed_threshold_error_handling(
|
||||
hass.states.async_set("test.test_entity", "on", {"test_attribute": 50})
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_value_not_numeric",
|
||||
{"entity_id": "test.test_entity", "value": None},
|
||||
),
|
||||
(
|
||||
"threshold_value_not_numeric",
|
||||
{"entity_id": "sensor.upper", "value": str(invalid_value)},
|
||||
),
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
unsub()
|
||||
|
||||
@@ -3823,13 +3418,7 @@ async def test_numerical_state_attribute_crossed_threshold_with_unit_error_handl
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert calls == []
|
||||
assert _reported_reasons(did_not_trigger_reports) == [
|
||||
(
|
||||
"entity_unit_not_supported",
|
||||
{"entity_id": "test.test_entity", "unit": "invalid_unit"},
|
||||
)
|
||||
]
|
||||
did_not_trigger_reports.clear()
|
||||
assert did_not_trigger_reports == []
|
||||
|
||||
unsub()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user