Do not warn for weak referenced entities (#43848)

pull/43832/head
Paulus Schoutsen 2020-12-02 11:18:08 +01:00 committed by GitHub
parent 648f9e100d
commit f3bb243b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 34 additions and 24 deletions

View File

@ -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)

View File

@ -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