Simplify access to hass in service calls (#133062)

This commit is contained in:
epenet 2024-12-13 09:31:21 +01:00 committed by GitHub
parent f9f37b9932
commit 899fb091fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 204 additions and 94 deletions

View File

@ -2432,10 +2432,11 @@ class Service:
class ServiceCall: class ServiceCall:
"""Representation of a call to a service.""" """Representation of a call to a service."""
__slots__ = ("domain", "service", "data", "context", "return_response") __slots__ = ("hass", "domain", "service", "data", "context", "return_response")
def __init__( def __init__(
self, self,
hass: HomeAssistant,
domain: str, domain: str,
service: str, service: str,
data: dict[str, Any] | None = None, data: dict[str, Any] | None = None,
@ -2443,6 +2444,7 @@ class ServiceCall:
return_response: bool = False, return_response: bool = False,
) -> None: ) -> None:
"""Initialize a service call.""" """Initialize a service call."""
self.hass = hass
self.domain = domain self.domain = domain
self.service = service self.service = service
self.data = ReadOnlyDict(data or {}) self.data = ReadOnlyDict(data or {})
@ -2768,7 +2770,7 @@ class ServiceRegistry:
processed_data = service_data processed_data = service_data
service_call = ServiceCall( service_call = ServiceCall(
domain, service, processed_data, context, return_response self._hass, domain, service, processed_data, context, return_response
) )
self._hass.bus.async_fire_internal( self._hass.bus.async_fire_internal(

View File

@ -184,6 +184,7 @@ async def test_turn_on_skips_domains_without_service(
# because by mocking out the call service method, we mock out all # because by mocking out the call service method, we mock out all
# So we mimic how the service registry calls services # So we mimic how the service registry calls services
service_call = ha.ServiceCall( service_call = ha.ServiceCall(
hass,
"homeassistant", "homeassistant",
"turn_on", "turn_on",
{"entity_id": ["light.test", "sensor.bla", "binary_sensor.blub", "light.bla"]}, {"entity_id": ["light.test", "sensor.bla", "binary_sensor.blub", "light.bla"]},

View File

@ -64,21 +64,22 @@ async def test_text_set_value(hass: HomeAssistant) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
await _async_set_value( await _async_set_value(
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: ""}) text, ServiceCall(hass, DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: ""})
) )
with pytest.raises(ValueError): with pytest.raises(ValueError):
await _async_set_value( await _async_set_value(
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "hello world!"}) text,
ServiceCall(hass, DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "hello world!"}),
) )
with pytest.raises(ValueError): with pytest.raises(ValueError):
await _async_set_value( await _async_set_value(
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "HELLO"}) text, ServiceCall(hass, DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "HELLO"})
) )
await _async_set_value( await _async_set_value(
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "test2"}) text, ServiceCall(hass, DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "test2"})
) )
assert text.state == "test2" assert text.state == "test2"

View File

@ -1899,7 +1899,7 @@ def service_calls(hass: HomeAssistant) -> Generator[list[ServiceCall]]:
return_response: bool = False, return_response: bool = False,
) -> ServiceResponse: ) -> ServiceResponse:
calls.append( calls.append(
ServiceCall(domain, service, service_data, context, return_response) ServiceCall(hass, domain, service, service_data, context, return_response)
) )
try: try:
return await _original_async_call( return await _original_async_call(

View File

@ -189,13 +189,14 @@ async def test_extract_from_service_available_device(hass: HomeAssistant) -> Non
] ]
) )
call_1 = ServiceCall("test", "service", data={"entity_id": ENTITY_MATCH_ALL}) call_1 = ServiceCall(hass, "test", "service", data={"entity_id": ENTITY_MATCH_ALL})
assert sorted( assert sorted(
ent.entity_id for ent in (await component.async_extract_from_service(call_1)) ent.entity_id for ent in (await component.async_extract_from_service(call_1))
) == ["test_domain.test_1", "test_domain.test_3"] ) == ["test_domain.test_1", "test_domain.test_3"]
call_2 = ServiceCall( call_2 = ServiceCall(
hass,
"test", "test",
"service", "service",
data={"entity_id": ["test_domain.test_3", "test_domain.test_4"]}, data={"entity_id": ["test_domain.test_3", "test_domain.test_4"]},
@ -256,17 +257,18 @@ async def test_extract_from_service_fails_if_no_entity_id(hass: HomeAssistant) -
) )
assert ( assert (
await component.async_extract_from_service(ServiceCall("test", "service")) == [] await component.async_extract_from_service(ServiceCall(hass, "test", "service"))
== []
) )
assert ( assert (
await component.async_extract_from_service( await component.async_extract_from_service(
ServiceCall("test", "service", {"entity_id": ENTITY_MATCH_NONE}) ServiceCall(hass, "test", "service", {"entity_id": ENTITY_MATCH_NONE})
) )
== [] == []
) )
assert ( assert (
await component.async_extract_from_service( await component.async_extract_from_service(
ServiceCall("test", "service", {"area_id": ENTITY_MATCH_NONE}) ServiceCall(hass, "test", "service", {"area_id": ENTITY_MATCH_NONE})
) )
== [] == []
) )
@ -283,6 +285,7 @@ async def test_extract_from_service_filter_out_non_existing_entities(
) )
call = ServiceCall( call = ServiceCall(
hass,
"test", "test",
"service", "service",
{"entity_id": ["test_domain.test_2", "test_domain.non_exist"]}, {"entity_id": ["test_domain.test_2", "test_domain.non_exist"]},
@ -299,7 +302,7 @@ async def test_extract_from_service_no_group_expand(hass: HomeAssistant) -> None
await component.async_setup({}) await component.async_setup({})
await component.async_add_entities([MockEntity(entity_id="group.test_group")]) await component.async_add_entities([MockEntity(entity_id="group.test_group")])
call = ServiceCall("test", "service", {"entity_id": ["group.test_group"]}) call = ServiceCall(hass, "test", "service", {"entity_id": ["group.test_group"]})
extracted = await component.async_extract_from_service(call, expand_group=False) extracted = await component.async_extract_from_service(call, expand_group=False)
assert len(extracted) == 1 assert len(extracted) == 1
@ -465,7 +468,7 @@ async def test_extract_all_omit_entity_id(
[MockEntity(name="test_1"), MockEntity(name="test_2")] [MockEntity(name="test_1"), MockEntity(name="test_2")]
) )
call = ServiceCall("test", "service") call = ServiceCall(hass, "test", "service")
assert ( assert (
sorted( sorted(
@ -485,7 +488,7 @@ async def test_extract_all_use_match_all(
[MockEntity(name="test_1"), MockEntity(name="test_2")] [MockEntity(name="test_1"), MockEntity(name="test_2")]
) )
call = ServiceCall("test", "service", {"entity_id": "all"}) call = ServiceCall(hass, "test", "service", {"entity_id": "all"})
assert sorted( assert sorted(
ent.entity_id for ent in await component.async_extract_from_service(call) ent.entity_id for ent in await component.async_extract_from_service(call)

View File

@ -642,11 +642,11 @@ async def test_extract_entity_ids(hass: HomeAssistant) -> None:
order=None, order=None,
) )
call = ServiceCall("light", "turn_on", {ATTR_ENTITY_ID: "light.Bowl"}) call = ServiceCall(hass, "light", "turn_on", {ATTR_ENTITY_ID: "light.Bowl"})
assert {"light.bowl"} == await service.async_extract_entity_ids(hass, call) assert {"light.bowl"} == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {ATTR_ENTITY_ID: "group.test"}) call = ServiceCall(hass, "light", "turn_on", {ATTR_ENTITY_ID: "group.test"})
assert {"light.ceiling", "light.kitchen"} == await service.async_extract_entity_ids( assert {"light.ceiling", "light.kitchen"} == await service.async_extract_entity_ids(
hass, call hass, call
@ -659,7 +659,7 @@ async def test_extract_entity_ids(hass: HomeAssistant) -> None:
assert ( assert (
await service.async_extract_entity_ids( await service.async_extract_entity_ids(
hass, hass,
ServiceCall("light", "turn_on", {ATTR_ENTITY_ID: ENTITY_MATCH_NONE}), ServiceCall(hass, "light", "turn_on", {ATTR_ENTITY_ID: ENTITY_MATCH_NONE}),
) )
== set() == set()
) )
@ -669,20 +669,22 @@ async def test_extract_entity_ids_from_area(
hass: HomeAssistant, floor_area_mock hass: HomeAssistant, floor_area_mock
) -> None: ) -> None:
"""Test extract_entity_ids method with areas.""" """Test extract_entity_ids method with areas."""
call = ServiceCall("light", "turn_on", {"area_id": "own-area"}) call = ServiceCall(hass, "light", "turn_on", {"area_id": "own-area"})
assert { assert {
"light.in_own_area", "light.in_own_area",
} == await service.async_extract_entity_ids(hass, call) } == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {"area_id": "test-area"}) call = ServiceCall(hass, "light", "turn_on", {"area_id": "test-area"})
assert { assert {
"light.in_area", "light.in_area",
"light.assigned_to_area", "light.assigned_to_area",
} == await service.async_extract_entity_ids(hass, call) } == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {"area_id": ["test-area", "diff-area"]}) call = ServiceCall(
hass, "light", "turn_on", {"area_id": ["test-area", "diff-area"]}
)
assert { assert {
"light.in_area", "light.in_area",
@ -692,7 +694,7 @@ async def test_extract_entity_ids_from_area(
assert ( assert (
await service.async_extract_entity_ids( await service.async_extract_entity_ids(
hass, ServiceCall("light", "turn_on", {"area_id": ENTITY_MATCH_NONE}) hass, ServiceCall(hass, "light", "turn_on", {"area_id": ENTITY_MATCH_NONE})
) )
== set() == set()
) )
@ -703,13 +705,13 @@ async def test_extract_entity_ids_from_devices(
) -> None: ) -> None:
"""Test extract_entity_ids method with devices.""" """Test extract_entity_ids method with devices."""
assert await service.async_extract_entity_ids( assert await service.async_extract_entity_ids(
hass, ServiceCall("light", "turn_on", {"device_id": "device-no-area-id"}) hass, ServiceCall(hass, "light", "turn_on", {"device_id": "device-no-area-id"})
) == { ) == {
"light.no_area", "light.no_area",
} }
assert await service.async_extract_entity_ids( assert await service.async_extract_entity_ids(
hass, ServiceCall("light", "turn_on", {"device_id": "device-area-a-id"}) hass, ServiceCall(hass, "light", "turn_on", {"device_id": "device-area-a-id"})
) == { ) == {
"light.in_area_a", "light.in_area_a",
"light.in_area_b", "light.in_area_b",
@ -717,7 +719,8 @@ async def test_extract_entity_ids_from_devices(
assert ( assert (
await service.async_extract_entity_ids( await service.async_extract_entity_ids(
hass, ServiceCall("light", "turn_on", {"device_id": "non-existing-id"}) hass,
ServiceCall(hass, "light", "turn_on", {"device_id": "non-existing-id"}),
) )
== set() == set()
) )
@ -726,14 +729,16 @@ async def test_extract_entity_ids_from_devices(
@pytest.mark.usefixtures("floor_area_mock") @pytest.mark.usefixtures("floor_area_mock")
async def test_extract_entity_ids_from_floor(hass: HomeAssistant) -> None: async def test_extract_entity_ids_from_floor(hass: HomeAssistant) -> None:
"""Test extract_entity_ids method with floors.""" """Test extract_entity_ids method with floors."""
call = ServiceCall("light", "turn_on", {"floor_id": "test-floor"}) call = ServiceCall(hass, "light", "turn_on", {"floor_id": "test-floor"})
assert { assert {
"light.in_area", "light.in_area",
"light.assigned_to_area", "light.assigned_to_area",
} == await service.async_extract_entity_ids(hass, call) } == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {"floor_id": ["test-floor", "floor-a"]}) call = ServiceCall(
hass, "light", "turn_on", {"floor_id": ["test-floor", "floor-a"]}
)
assert { assert {
"light.in_area", "light.in_area",
@ -743,7 +748,7 @@ async def test_extract_entity_ids_from_floor(hass: HomeAssistant) -> None:
assert ( assert (
await service.async_extract_entity_ids( await service.async_extract_entity_ids(
hass, ServiceCall("light", "turn_on", {"floor_id": ENTITY_MATCH_NONE}) hass, ServiceCall(hass, "light", "turn_on", {"floor_id": ENTITY_MATCH_NONE})
) )
== set() == set()
) )
@ -752,13 +757,13 @@ async def test_extract_entity_ids_from_floor(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("label_mock") @pytest.mark.usefixtures("label_mock")
async def test_extract_entity_ids_from_labels(hass: HomeAssistant) -> None: async def test_extract_entity_ids_from_labels(hass: HomeAssistant) -> None:
"""Test extract_entity_ids method with labels.""" """Test extract_entity_ids method with labels."""
call = ServiceCall("light", "turn_on", {"label_id": "my-label"}) call = ServiceCall(hass, "light", "turn_on", {"label_id": "my-label"})
assert { assert {
"light.with_my_label", "light.with_my_label",
} == await service.async_extract_entity_ids(hass, call) } == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {"label_id": "label1"}) call = ServiceCall(hass, "light", "turn_on", {"label_id": "label1"})
assert { assert {
"light.with_label1_from_device", "light.with_label1_from_device",
@ -767,14 +772,14 @@ async def test_extract_entity_ids_from_labels(hass: HomeAssistant) -> None:
"light.with_label1_and_label2_from_device", "light.with_label1_and_label2_from_device",
} == await service.async_extract_entity_ids(hass, call) } == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {"label_id": ["label2"]}) call = ServiceCall(hass, "light", "turn_on", {"label_id": ["label2"]})
assert { assert {
"light.with_labels_from_device", "light.with_labels_from_device",
"light.with_label1_and_label2_from_device", "light.with_label1_and_label2_from_device",
} == await service.async_extract_entity_ids(hass, call) } == await service.async_extract_entity_ids(hass, call)
call = ServiceCall("light", "turn_on", {"label_id": ["label_area"]}) call = ServiceCall(hass, "light", "turn_on", {"label_id": ["label_area"]})
assert { assert {
"light.with_labels_from_device", "light.with_labels_from_device",
@ -782,7 +787,7 @@ async def test_extract_entity_ids_from_labels(hass: HomeAssistant) -> None:
assert ( assert (
await service.async_extract_entity_ids( await service.async_extract_entity_ids(
hass, ServiceCall("light", "turn_on", {"label_id": ENTITY_MATCH_NONE}) hass, ServiceCall(hass, "light", "turn_on", {"label_id": ENTITY_MATCH_NONE})
) )
== set() == set()
) )
@ -1281,7 +1286,7 @@ async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -
hass, hass,
mock_entities, mock_entities,
HassJob(test_service_mock), HassJob(test_service_mock),
ServiceCall("test_domain", "test_service", {"entity_id": "all"}), ServiceCall(hass, "test_domain", "test_service", {"entity_id": "all"}),
required_features=[SUPPORT_A], required_features=[SUPPORT_A],
) )
@ -1305,7 +1310,7 @@ async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -
mock_entities, mock_entities,
HassJob(test_service_mock), HassJob(test_service_mock),
ServiceCall( ServiceCall(
"test_domain", "test_service", {"entity_id": "light.living_room"} hass, "test_domain", "test_service", {"entity_id": "light.living_room"}
), ),
required_features=[SUPPORT_A], required_features=[SUPPORT_A],
) )
@ -1321,7 +1326,7 @@ async def test_call_with_both_required_features(
hass, hass,
mock_entities, mock_entities,
HassJob(test_service_mock), HassJob(test_service_mock),
ServiceCall("test_domain", "test_service", {"entity_id": "all"}), ServiceCall(hass, "test_domain", "test_service", {"entity_id": "all"}),
required_features=[SUPPORT_A | SUPPORT_B], required_features=[SUPPORT_A | SUPPORT_B],
) )
@ -1340,7 +1345,7 @@ async def test_call_with_one_of_required_features(
hass, hass,
mock_entities, mock_entities,
HassJob(test_service_mock), HassJob(test_service_mock),
ServiceCall("test_domain", "test_service", {"entity_id": "all"}), ServiceCall(hass, "test_domain", "test_service", {"entity_id": "all"}),
required_features=[SUPPORT_A, SUPPORT_C], required_features=[SUPPORT_A, SUPPORT_C],
) )
@ -1361,7 +1366,9 @@ async def test_call_with_sync_func(hass: HomeAssistant, mock_entities) -> None:
hass, hass,
mock_entities, mock_entities,
HassJob(test_service_mock), HassJob(test_service_mock),
ServiceCall("test_domain", "test_service", {"entity_id": "light.kitchen"}), ServiceCall(
hass, "test_domain", "test_service", {"entity_id": "light.kitchen"}
),
) )
assert test_service_mock.call_count == 1 assert test_service_mock.call_count == 1
@ -1374,6 +1381,7 @@ async def test_call_with_sync_attr(hass: HomeAssistant, mock_entities) -> None:
mock_entities, mock_entities,
"sync_method", "sync_method",
ServiceCall( ServiceCall(
hass,
"test_domain", "test_domain",
"test_service", "test_service",
{"entity_id": "light.kitchen", "area_id": "abcd"}, {"entity_id": "light.kitchen", "area_id": "abcd"},
@ -1392,6 +1400,7 @@ async def test_call_context_user_not_exist(hass: HomeAssistant) -> None:
{}, {},
Mock(), Mock(),
ServiceCall( ServiceCall(
hass,
"test_domain", "test_domain",
"test_service", "test_service",
context=Context(user_id="non-existing"), context=Context(user_id="non-existing"),
@ -1419,6 +1428,7 @@ async def test_call_context_target_all(
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall( ServiceCall(
hass,
"test_domain", "test_domain",
"test_service", "test_service",
data={"entity_id": ENTITY_MATCH_ALL}, data={"entity_id": ENTITY_MATCH_ALL},
@ -1447,6 +1457,7 @@ async def test_call_context_target_specific(
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall( ServiceCall(
hass,
"test_domain", "test_domain",
"test_service", "test_service",
{"entity_id": "light.kitchen"}, {"entity_id": "light.kitchen"},
@ -1474,6 +1485,7 @@ async def test_call_context_target_specific_no_auth(
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall( ServiceCall(
hass,
"test_domain", "test_domain",
"test_service", "test_service",
{"entity_id": "light.kitchen"}, {"entity_id": "light.kitchen"},
@ -1494,7 +1506,7 @@ async def test_call_no_context_target_all(
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall( ServiceCall(
"test_domain", "test_service", data={"entity_id": ENTITY_MATCH_ALL} hass, "test_domain", "test_service", data={"entity_id": ENTITY_MATCH_ALL}
), ),
) )
@ -1513,6 +1525,7 @@ async def test_call_no_context_target_specific(
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall( ServiceCall(
hass,
"test_domain", "test_domain",
"test_service", "test_service",
{"entity_id": ["light.kitchen", "light.non-existing"]}, {"entity_id": ["light.kitchen", "light.non-existing"]},
@ -1534,7 +1547,7 @@ async def test_call_with_match_all(
hass, hass,
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall("test_domain", "test_service", {"entity_id": "all"}), ServiceCall(hass, "test_domain", "test_service", {"entity_id": "all"}),
) )
assert len(mock_handle_entity_call.mock_calls) == 4 assert len(mock_handle_entity_call.mock_calls) == 4
@ -1551,7 +1564,7 @@ async def test_call_with_omit_entity_id(
hass, hass,
mock_entities, mock_entities,
Mock(), Mock(),
ServiceCall("test_domain", "test_service"), ServiceCall(hass, "test_domain", "test_service"),
) )
assert len(mock_handle_entity_call.mock_calls) == 0 assert len(mock_handle_entity_call.mock_calls) == 0
@ -1797,7 +1810,7 @@ async def test_extract_from_service_available_device(hass: HomeAssistant) -> Non
MockEntity(name="test_4", entity_id="test_domain.test_4", available=False), MockEntity(name="test_4", entity_id="test_domain.test_4", available=False),
] ]
call_1 = ServiceCall("test", "service", data={"entity_id": ENTITY_MATCH_ALL}) call_1 = ServiceCall(hass, "test", "service", data={"entity_id": ENTITY_MATCH_ALL})
assert [ assert [
ent.entity_id ent.entity_id
@ -1805,6 +1818,7 @@ async def test_extract_from_service_available_device(hass: HomeAssistant) -> Non
] == ["test_domain.test_1", "test_domain.test_3"] ] == ["test_domain.test_1", "test_domain.test_3"]
call_2 = ServiceCall( call_2 = ServiceCall(
hass,
"test", "test",
"service", "service",
data={"entity_id": ["test_domain.test_3", "test_domain.test_4"]}, data={"entity_id": ["test_domain.test_3", "test_domain.test_4"]},
@ -1820,6 +1834,7 @@ async def test_extract_from_service_available_device(hass: HomeAssistant) -> Non
hass, hass,
entities, entities,
ServiceCall( ServiceCall(
hass,
"test", "test",
"service", "service",
data={"entity_id": ENTITY_MATCH_NONE}, data={"entity_id": ENTITY_MATCH_NONE},
@ -1835,7 +1850,7 @@ async def test_extract_from_service_empty_if_no_entity_id(hass: HomeAssistant) -
MockEntity(name="test_1", entity_id="test_domain.test_1"), MockEntity(name="test_1", entity_id="test_domain.test_1"),
MockEntity(name="test_2", entity_id="test_domain.test_2"), MockEntity(name="test_2", entity_id="test_domain.test_2"),
] ]
call = ServiceCall("test", "service") call = ServiceCall(hass, "test", "service")
assert [ assert [
ent.entity_id ent.entity_id
@ -1853,6 +1868,7 @@ async def test_extract_from_service_filter_out_non_existing_entities(
] ]
call = ServiceCall( call = ServiceCall(
hass,
"test", "test",
"service", "service",
{"entity_id": ["test_domain.test_2", "test_domain.non_exist"]}, {"entity_id": ["test_domain.test_2", "test_domain.non_exist"]},
@ -1874,12 +1890,14 @@ async def test_extract_from_service_area_id(
MockEntity(name="diff_area", entity_id="light.diff_area"), MockEntity(name="diff_area", entity_id="light.diff_area"),
] ]
call = ServiceCall("light", "turn_on", {"area_id": "test-area"}) call = ServiceCall(hass, "light", "turn_on", {"area_id": "test-area"})
extracted = await service.async_extract_entities(hass, entities, call) extracted = await service.async_extract_entities(hass, entities, call)
assert len(extracted) == 1 assert len(extracted) == 1
assert extracted[0].entity_id == "light.in_area" assert extracted[0].entity_id == "light.in_area"
call = ServiceCall("light", "turn_on", {"area_id": ["test-area", "diff-area"]}) call = ServiceCall(
hass, "light", "turn_on", {"area_id": ["test-area", "diff-area"]}
)
extracted = await service.async_extract_entities(hass, entities, call) extracted = await service.async_extract_entities(hass, entities, call)
assert len(extracted) == 2 assert len(extracted) == 2
assert sorted(ent.entity_id for ent in extracted) == [ assert sorted(ent.entity_id for ent in extracted) == [
@ -1888,6 +1906,7 @@ async def test_extract_from_service_area_id(
] ]
call = ServiceCall( call = ServiceCall(
hass,
"light", "light",
"turn_on", "turn_on",
{"area_id": ["test-area", "diff-area"], "device_id": "device-no-area-id"}, {"area_id": ["test-area", "diff-area"], "device_id": "device-no-area-id"},
@ -1912,17 +1931,17 @@ async def test_extract_from_service_label_id(hass: HomeAssistant) -> None:
), ),
] ]
call = ServiceCall("light", "turn_on", {"label_id": "label_area"}) call = ServiceCall(hass, "light", "turn_on", {"label_id": "label_area"})
extracted = await service.async_extract_entities(hass, entities, call) extracted = await service.async_extract_entities(hass, entities, call)
assert len(extracted) == 1 assert len(extracted) == 1
assert extracted[0].entity_id == "light.with_labels_from_device" assert extracted[0].entity_id == "light.with_labels_from_device"
call = ServiceCall("light", "turn_on", {"label_id": "my-label"}) call = ServiceCall(hass, "light", "turn_on", {"label_id": "my-label"})
extracted = await service.async_extract_entities(hass, entities, call) extracted = await service.async_extract_entities(hass, entities, call)
assert len(extracted) == 1 assert len(extracted) == 1
assert extracted[0].entity_id == "light.with_my_label" assert extracted[0].entity_id == "light.with_my_label"
call = ServiceCall("light", "turn_on", {"label_id": ["my-label", "label1"]}) call = ServiceCall(hass, "light", "turn_on", {"label_id": ["my-label", "label1"]})
extracted = await service.async_extract_entities(hass, entities, call) extracted = await service.async_extract_entities(hass, entities, call)
assert len(extracted) == 2 assert len(extracted) == 2
assert sorted(ent.entity_id for ent in extracted) == [ assert sorted(ent.entity_id for ent in extracted) == [
@ -1931,6 +1950,7 @@ async def test_extract_from_service_label_id(hass: HomeAssistant) -> None:
] ]
call = ServiceCall( call = ServiceCall(
hass,
"light", "light",
"turn_on", "turn_on",
{"label_id": ["my-label", "label1"], "device_id": "device-no-labels"}, {"label_id": ["my-label", "label1"], "device_id": "device-no-labels"},
@ -1949,6 +1969,7 @@ async def test_entity_service_call_warn_referenced(
) -> None: ) -> None:
"""Test we only warn for referenced entities in entity_service_call.""" """Test we only warn for referenced entities in entity_service_call."""
call = ServiceCall( call = ServiceCall(
hass,
"light", "light",
"turn_on", "turn_on",
{ {
@ -1972,6 +1993,7 @@ async def test_async_extract_entities_warn_referenced(
) -> None: ) -> None:
"""Test we only warn for referenced entities in async_extract_entities.""" """Test we only warn for referenced entities in async_extract_entities."""
call = ServiceCall( call = ServiceCall(
hass,
"light", "light",
"turn_on", "turn_on",
{ {
@ -1997,6 +2019,7 @@ async def test_async_extract_config_entry_ids(hass: HomeAssistant) -> None:
device_no_entities = dr.DeviceEntry(id="device-no-entities", config_entries={"abc"}) device_no_entities = dr.DeviceEntry(id="device-no-entities", config_entries={"abc"})
call = ServiceCall( call = ServiceCall(
hass,
"homeassistant", "homeassistant",
"reload_config_entry", "reload_config_entry",
{ {
@ -2042,17 +2065,33 @@ async def test_reload_service_helper(hass: HomeAssistant) -> None:
reloader = service.ReloadServiceHelper(reload_service_handler, reload_targets) reloader = service.ReloadServiceHelper(reload_service_handler, reload_targets)
tasks = [ tasks = [
# This reload task will start executing first, (target1) # This reload task will start executing first, (target1)
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
# These reload tasks will be deduplicated to (target2, target3, target4, target1) # These reload tasks will be deduplicated to (target2, target3, target4, target1)
# while the first task is reloaded, note that target1 can't be deduplicated # while the first task is reloaded, note that target1 can't be deduplicated
# because it's already being reloaded. # because it's already being reloaded.
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ServiceCall(hass, "test", "test", {"target": "target2"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), ServiceCall(hass, "test", "test", {"target": "target3"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target2"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
assert reloaded == unordered( assert reloaded == unordered(
@ -2063,13 +2102,21 @@ async def test_reload_service_helper(hass: HomeAssistant) -> None:
reloaded.clear() reloaded.clear()
tasks = [ tasks = [
# This reload task will start executing first, (target1) # This reload task will start executing first, (target1)
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
# These reload tasks will be deduplicated to (target2, target3, target4, all) # These reload tasks will be deduplicated to (target2, target3, target4, all)
# while the first task is reloaded. # while the first task is reloaded.
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ServiceCall(hass, "test", "test", {"target": "target2"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), ),
reloader.execute_service(ServiceCall("test", "test")), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
reloader.execute_service(ServiceCall(hass, "test", "test")),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
assert reloaded == unordered(["target1", "target2", "target3", "target4", "all"]) assert reloaded == unordered(["target1", "target2", "target3", "target4", "all"])
@ -2078,13 +2125,21 @@ async def test_reload_service_helper(hass: HomeAssistant) -> None:
reloaded.clear() reloaded.clear()
tasks = [ tasks = [
# This reload task will start executing first, (all) # This reload task will start executing first, (all)
reloader.execute_service(ServiceCall("test", "test")), reloader.execute_service(ServiceCall(hass, "test", "test")),
# These reload tasks will be deduplicated to (target1, target2, target3, target4) # These reload tasks will be deduplicated to (target1, target2, target3, target4)
# while the first task is reloaded. # while the first task is reloaded.
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), ServiceCall(hass, "test", "test", {"target": "target1"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target2"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
assert reloaded == unordered(["all", "target1", "target2", "target3", "target4"]) assert reloaded == unordered(["all", "target1", "target2", "target3", "target4"])
@ -2093,21 +2148,45 @@ async def test_reload_service_helper(hass: HomeAssistant) -> None:
reloaded.clear() reloaded.clear()
tasks = [ tasks = [
# This reload task will start executing first, (target1) # This reload task will start executing first, (target1)
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
# These reload tasks will be deduplicated to (target2, target3, target4, target1) # These reload tasks will be deduplicated to (target2, target3, target4, target1)
# while the first task is reloaded, note that target1 can't be deduplicated # while the first task is reloaded, note that target1 can't be deduplicated
# because it's already being reloaded. # because it's already being reloaded.
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ServiceCall(hass, "test", "test", {"target": "target2"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), ServiceCall(hass, "test", "test", {"target": "target3"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), ServiceCall(hass, "test", "test", {"target": "target4"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), ServiceCall(hass, "test", "test", {"target": "target1"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target2"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target2"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
assert reloaded == unordered( assert reloaded == unordered(
@ -2118,14 +2197,22 @@ async def test_reload_service_helper(hass: HomeAssistant) -> None:
reloaded.clear() reloaded.clear()
tasks = [ tasks = [
# This reload task will start executing first, (target1) # This reload task will start executing first, (target1)
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
# These reload tasks will be deduplicated to (target2, target3, target4, all) # These reload tasks will be deduplicated to (target2, target3, target4, all)
# while the first task is reloaded. # while the first task is reloaded.
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ServiceCall(hass, "test", "test", {"target": "target2"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), ),
reloader.execute_service(ServiceCall("test", "test")), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test")), ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
reloader.execute_service(ServiceCall(hass, "test", "test")),
reloader.execute_service(ServiceCall(hass, "test", "test")),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
assert reloaded == unordered(["target1", "target2", "target3", "target4", "all"]) assert reloaded == unordered(["target1", "target2", "target3", "target4", "all"])
@ -2134,17 +2221,33 @@ async def test_reload_service_helper(hass: HomeAssistant) -> None:
reloaded.clear() reloaded.clear()
tasks = [ tasks = [
# This reload task will start executing first, (all) # This reload task will start executing first, (all)
reloader.execute_service(ServiceCall("test", "test")), reloader.execute_service(ServiceCall(hass, "test", "test")),
# These reload tasks will be deduplicated to (target1, target2, target3, target4) # These reload tasks will be deduplicated to (target1, target2, target3, target4)
# while the first task is reloaded. # while the first task is reloaded.
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), ServiceCall(hass, "test", "test", {"target": "target1"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target1"})), ServiceCall(hass, "test", "test", {"target": "target2"})
reloader.execute_service(ServiceCall("test", "test", {"target": "target2"})), ),
reloader.execute_service(ServiceCall("test", "test", {"target": "target3"})), reloader.execute_service(
reloader.execute_service(ServiceCall("test", "test", {"target": "target4"})), ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target1"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target2"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target3"})
),
reloader.execute_service(
ServiceCall(hass, "test", "test", {"target": "target4"})
),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
assert reloaded == unordered(["all", "target1", "target2", "target3", "target4"]) assert reloaded == unordered(["all", "target1", "target2", "target3", "target4"])

View File

@ -1562,10 +1562,10 @@ async def test_statemachine_avoids_updating_attributes(hass: HomeAssistant) -> N
def test_service_call_repr() -> None: def test_service_call_repr() -> None:
"""Test ServiceCall repr.""" """Test ServiceCall repr."""
call = ha.ServiceCall("homeassistant", "start") call = ha.ServiceCall(None, "homeassistant", "start")
assert str(call) == f"<ServiceCall homeassistant.start (c:{call.context.id})>" assert str(call) == f"<ServiceCall homeassistant.start (c:{call.context.id})>"
call2 = ha.ServiceCall("homeassistant", "start", {"fast": "yes"}) call2 = ha.ServiceCall(None, "homeassistant", "start", {"fast": "yes"})
assert ( assert (
str(call2) str(call2)
== f"<ServiceCall homeassistant.start (c:{call2.context.id}): fast=yes>" == f"<ServiceCall homeassistant.start (c:{call2.context.id}): fast=yes>"