Do not warn for weak referenced entities (#43848)
parent
648f9e100d
commit
f3bb243b1d
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||
import homeassistant.core as ha
|
||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized, UnknownUser
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.service import async_extract_entity_ids
|
||||
from homeassistant.helpers.service import async_extract_referenced_entity_ids
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = ha.DOMAIN
|
||||
|
@ -37,39 +37,37 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool:
|
|||
|
||||
async def async_handle_turn_service(service):
|
||||
"""Handle calls to homeassistant.turn_on/off."""
|
||||
entity_ids = await async_extract_entity_ids(hass, service)
|
||||
referenced = await async_extract_referenced_entity_ids(hass, service)
|
||||
all_referenced = referenced.referenced | referenced.indirectly_referenced
|
||||
|
||||
# Generic turn on/off method requires entity id
|
||||
if not entity_ids:
|
||||
if not all_referenced:
|
||||
_LOGGER.error(
|
||||
"homeassistant/%s cannot be called without entity_id", service.service
|
||||
"homeassistant.%s cannot be called without a target", service.service
|
||||
)
|
||||
return
|
||||
|
||||
# Group entity_ids by domain. groupby requires sorted data.
|
||||
by_domain = it.groupby(
|
||||
sorted(entity_ids), lambda item: ha.split_entity_id(item)[0]
|
||||
sorted(all_referenced), lambda item: ha.split_entity_id(item)[0]
|
||||
)
|
||||
|
||||
tasks = []
|
||||
unsupported_entities = set()
|
||||
|
||||
for domain, ent_ids in by_domain:
|
||||
# This leads to endless loop.
|
||||
if domain == DOMAIN:
|
||||
_LOGGER.warning(
|
||||
"Called service homeassistant.%s with invalid entity IDs %s",
|
||||
"Called service homeassistant.%s with invalid entities %s",
|
||||
service.service,
|
||||
", ".join(ent_ids),
|
||||
)
|
||||
continue
|
||||
|
||||
# We want to block for all calls and only return when all calls
|
||||
# have been processed. If a service does not exist it causes a 10
|
||||
# second delay while we're blocking waiting for a response.
|
||||
# But services can be registered on other HA instances that are
|
||||
# listening to the bus too. So as an in between solution, we'll
|
||||
# block only if the service is defined in the current HA instance.
|
||||
blocking = hass.services.has_service(domain, service.service)
|
||||
if not hass.services.has_service(domain, service.service):
|
||||
unsupported_entities.update(set(ent_ids) & referenced.referenced)
|
||||
continue
|
||||
|
||||
# Create a new dict for this call
|
||||
data = dict(service.data)
|
||||
|
@ -79,10 +77,21 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool:
|
|||
|
||||
tasks.append(
|
||||
hass.services.async_call(
|
||||
domain, service.service, data, blocking, context=service.context
|
||||
domain,
|
||||
service.service,
|
||||
data,
|
||||
blocking=True,
|
||||
context=service.context,
|
||||
)
|
||||
)
|
||||
|
||||
if unsupported_entities:
|
||||
_LOGGER.warning(
|
||||
"The service homeassistant.%s does not support entities %s",
|
||||
service.service,
|
||||
", ".join(sorted(unsupported_entities)),
|
||||
)
|
||||
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ class TestComponentsCore(unittest.TestCase):
|
|||
assert not mock_stop.called
|
||||
|
||||
|
||||
async def test_turn_on_to_not_block_for_domains_without_service(hass):
|
||||
async def test_turn_on_skips_domains_without_service(hass, caplog):
|
||||
"""Test if turn_on is blocking domain with no service."""
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
async_mock_service(hass, "light", SERVICE_TURN_ON)
|
||||
|
@ -261,7 +261,7 @@ async def test_turn_on_to_not_block_for_domains_without_service(hass):
|
|||
service_call = ha.ServiceCall(
|
||||
"homeassistant",
|
||||
"turn_on",
|
||||
{"entity_id": ["light.test", "sensor.bla", "light.bla"]},
|
||||
{"entity_id": ["light.test", "sensor.bla", "binary_sensor.blub", "light.bla"]},
|
||||
)
|
||||
service = hass.services._services["homeassistant"]["turn_on"]
|
||||
|
||||
|
@ -271,18 +271,19 @@ async def test_turn_on_to_not_block_for_domains_without_service(hass):
|
|||
) as mock_call:
|
||||
await service.job.target(service_call)
|
||||
|
||||
assert mock_call.call_count == 2
|
||||
assert mock_call.call_count == 1
|
||||
assert mock_call.call_args_list[0][0] == (
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": ["light.bla", "light.test"]},
|
||||
True,
|
||||
)
|
||||
assert mock_call.call_args_list[1][0] == (
|
||||
"sensor",
|
||||
"turn_on",
|
||||
{"entity_id": ["sensor.bla"]},
|
||||
False,
|
||||
assert mock_call.call_args_list[0][1] == {
|
||||
"blocking": True,
|
||||
"context": service_call.context,
|
||||
}
|
||||
assert (
|
||||
"The service homeassistant.turn_on does not support entities binary_sensor.blub, sensor.bla"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
|
@ -381,6 +382,6 @@ async def test_not_allowing_recursion(hass, caplog):
|
|||
blocking=True,
|
||||
)
|
||||
assert (
|
||||
f"Called service homeassistant.{service} with invalid entity IDs homeassistant.light"
|
||||
f"Called service homeassistant.{service} with invalid entities homeassistant.light"
|
||||
in caplog.text
|
||||
), service
|
||||
|
|
Loading…
Reference in New Issue