Speed up single entity/response service calls (#96729)
* Significantly speed up single entity/response service calls Since the majority of service calls are single entity, we can avoid creating tasks in this case. Since the multi-entity service calls always check the result and raise, we can switch the asyncio.wait to asyncio.gather * Significantly speed up single entity/response service calls Since the majority of service calls are single entity, we can avoid creating tasks in this case. Since the multi-entity service calls always check the result and raise, we can switch the asyncio.wait to asyncio.gather * revert * cannot be inside pytest.raises * one more * Update homeassistant/helpers/service.py
This commit is contained in:
parent
c76fac0633
commit
3a06659120
11 changed files with 50 additions and 18 deletions
|
@ -741,6 +741,8 @@ async def entity_service_call( # noqa: C901
|
||||||
Calls all platforms simultaneously.
|
Calls all platforms simultaneously.
|
||||||
"""
|
"""
|
||||||
entity_perms: None | (Callable[[str, str], bool]) = None
|
entity_perms: None | (Callable[[str, str], bool]) = None
|
||||||
|
return_response = call.return_response
|
||||||
|
|
||||||
if call.context.user_id:
|
if call.context.user_id:
|
||||||
user = await hass.auth.async_get_user(call.context.user_id)
|
user = await hass.auth.async_get_user(call.context.user_id)
|
||||||
if user is None:
|
if user is None:
|
||||||
|
@ -851,13 +853,27 @@ async def entity_service_call( # noqa: C901
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
|
||||||
if not entities:
|
if not entities:
|
||||||
if call.return_response:
|
if return_response:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
"Service call requested response data but did not match any entities"
|
"Service call requested response data but did not match any entities"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if call.return_response and len(entities) != 1:
|
if len(entities) == 1:
|
||||||
|
# Single entity case avoids creating tasks and allows returning
|
||||||
|
# ServiceResponse
|
||||||
|
entity = entities[0]
|
||||||
|
response_data = await _handle_entity_call(
|
||||||
|
hass, entity, func, data, call.context
|
||||||
|
)
|
||||||
|
if entity.should_poll:
|
||||||
|
# Context expires if the turn on commands took a long time.
|
||||||
|
# Set context again so it's there when we update
|
||||||
|
entity.async_set_context(call.context)
|
||||||
|
await entity.async_update_ha_state(True)
|
||||||
|
return response_data if return_response else None
|
||||||
|
|
||||||
|
if return_response:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
"Service call requested response data but matched more than one entity"
|
"Service call requested response data but matched more than one entity"
|
||||||
)
|
)
|
||||||
|
@ -874,9 +890,8 @@ async def entity_service_call( # noqa: C901
|
||||||
)
|
)
|
||||||
assert not pending
|
assert not pending
|
||||||
|
|
||||||
response_data: ServiceResponse | None
|
|
||||||
for task in done:
|
for task in done:
|
||||||
response_data = task.result() # pop exception if have
|
task.result() # pop exception if have
|
||||||
|
|
||||||
tasks: list[asyncio.Task[None]] = []
|
tasks: list[asyncio.Task[None]] = []
|
||||||
|
|
||||||
|
@ -895,7 +910,7 @@ async def entity_service_call( # noqa: C901
|
||||||
for future in done:
|
for future in done:
|
||||||
future.result() # pop exception if have
|
future.result() # pop exception if have
|
||||||
|
|
||||||
return response_data if call.return_response else None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def _handle_entity_call(
|
async def _handle_entity_call(
|
||||||
|
|
|
@ -228,7 +228,8 @@ async def test_auth_failed(hass: HomeAssistant, mock_device: MockDevice) -> None
|
||||||
{ATTR_ENTITY_ID: state_key},
|
{ATTR_ENTITY_ID: state_key},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
flows = hass.config_entries.flow.async_progress()
|
flows = hass.config_entries.flow.async_progress()
|
||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
|
|
||||||
|
|
|
@ -307,6 +307,9 @@ async def test_auth_failed(
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
PLATFORM, SERVICE_TURN_ON, {"entity_id": state_key}, blocking=True
|
PLATFORM, SERVICE_TURN_ON, {"entity_id": state_key}, blocking=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
flows = hass.config_entries.flow.async_progress()
|
||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ async def test_if_fires_using_at_input_datetime(
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
time_that_will_not_match_right_away = trigger_dt - timedelta(minutes=1)
|
time_that_will_not_match_right_away = trigger_dt - timedelta(minutes=1)
|
||||||
|
|
||||||
|
@ -148,6 +149,7 @@ async def test_if_fires_using_at_input_datetime(
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
async_fire_time_changed(hass, trigger_dt + timedelta(seconds=1))
|
async_fire_time_changed(hass, trigger_dt + timedelta(seconds=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -556,6 +558,7 @@ async def test_datetime_in_past_on_load(hass: HomeAssistant, calls) -> None:
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
|
@ -587,6 +590,7 @@ async def test_datetime_in_past_on_load(hass: HomeAssistant, calls) -> None:
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
async_fire_time_changed(hass, future + timedelta(seconds=1))
|
async_fire_time_changed(hass, future + timedelta(seconds=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
|
@ -285,11 +285,13 @@ async def test_signal_repetitions_cancelling(hass: HomeAssistant, monkeypatch) -
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"}
|
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get background service time to start running
|
# Get background service time to start running
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"}, blocking=True
|
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"}, blocking=True
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert [call[0][1] for call in protocol.send_command_ack.call_args_list] == [
|
assert [call[0][1] for call in protocol.send_command_ack.call_args_list] == [
|
||||||
"off",
|
"off",
|
||||||
|
|
|
@ -427,6 +427,7 @@ async def test_block_set_mode_auth_error(
|
||||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.HEAT},
|
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.HEAT},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,7 @@ async def test_block_set_value_auth_error(
|
||||||
{ATTR_ENTITY_ID: "number.test_name_valve_position", ATTR_VALUE: 30},
|
{ATTR_ENTITY_ID: "number.test_name_valve_position", ATTR_VALUE: 30},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ async def test_block_set_state_auth_error(
|
||||||
{ATTR_ENTITY_ID: "switch.test_name_channel_1"},
|
{ATTR_ENTITY_ID: "switch.test_name_channel_1"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
@ -211,6 +212,7 @@ async def test_rpc_auth_error(
|
||||||
{ATTR_ENTITY_ID: "switch.test_switch_0"},
|
{ATTR_ENTITY_ID: "switch.test_switch_0"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,7 @@ async def test_block_update_auth_error(
|
||||||
{ATTR_ENTITY_ID: "update.test_name_firmware_update"},
|
{ATTR_ENTITY_ID: "update.test_name_firmware_update"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
@ -541,6 +542,7 @@ async def test_rpc_update_auth_error(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
flows = hass.config_entries.flow.async_progress()
|
||||||
|
|
|
@ -129,7 +129,7 @@ async def test_arm_home_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect failed to arm home test."
|
assert f"{err.value}" == "TotalConnect failed to arm home test."
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
assert mock_request.call_count == 2
|
assert mock_request.call_count == 2
|
||||||
|
@ -139,7 +139,7 @@ async def test_arm_home_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not arm home"
|
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not arm home"
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
# should have started a re-auth flow
|
# should have started a re-auth flow
|
||||||
|
@ -183,7 +183,7 @@ async def test_arm_home_instant_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
|
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect failed to arm home instant test."
|
assert f"{err.value}" == "TotalConnect failed to arm home instant test."
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
assert mock_request.call_count == 2
|
assert mock_request.call_count == 2
|
||||||
|
@ -193,7 +193,7 @@ async def test_arm_home_instant_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
|
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert (
|
assert (
|
||||||
f"{err.value}"
|
f"{err.value}"
|
||||||
== "TotalConnect usercode is invalid. Did not arm home instant"
|
== "TotalConnect usercode is invalid. Did not arm home instant"
|
||||||
|
@ -240,7 +240,7 @@ async def test_arm_away_instant_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
|
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect failed to arm away instant test."
|
assert f"{err.value}" == "TotalConnect failed to arm away instant test."
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
assert mock_request.call_count == 2
|
assert mock_request.call_count == 2
|
||||||
|
@ -250,7 +250,7 @@ async def test_arm_away_instant_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
|
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert (
|
assert (
|
||||||
f"{err.value}"
|
f"{err.value}"
|
||||||
== "TotalConnect usercode is invalid. Did not arm away instant"
|
== "TotalConnect usercode is invalid. Did not arm away instant"
|
||||||
|
@ -296,7 +296,7 @@ async def test_arm_away_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect failed to arm away test."
|
assert f"{err.value}" == "TotalConnect failed to arm away test."
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
assert mock_request.call_count == 2
|
assert mock_request.call_count == 2
|
||||||
|
@ -306,7 +306,7 @@ async def test_arm_away_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not arm away"
|
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not arm away"
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
# should have started a re-auth flow
|
# should have started a re-auth flow
|
||||||
|
@ -353,7 +353,7 @@ async def test_disarm_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect failed to disarm test."
|
assert f"{err.value}" == "TotalConnect failed to disarm test."
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY
|
||||||
assert mock_request.call_count == 2
|
assert mock_request.call_count == 2
|
||||||
|
@ -363,7 +363,7 @@ async def test_disarm_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not disarm"
|
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not disarm"
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY
|
||||||
# should have started a re-auth flow
|
# should have started a re-auth flow
|
||||||
|
@ -406,7 +406,7 @@ async def test_arm_night_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect failed to arm night test."
|
assert f"{err.value}" == "TotalConnect failed to arm night test."
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
assert mock_request.call_count == 2
|
assert mock_request.call_count == 2
|
||||||
|
@ -416,7 +416,7 @@ async def test_arm_night_failure(hass: HomeAssistant) -> None:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
|
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not arm night"
|
assert f"{err.value}" == "TotalConnect usercode is invalid. Did not arm night"
|
||||||
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED
|
||||||
# should have started a re-auth flow
|
# should have started a re-auth flow
|
||||||
|
|
|
@ -456,6 +456,7 @@ async def test_options_update(
|
||||||
options=new_options,
|
options=new_options,
|
||||||
)
|
)
|
||||||
assert config_entry.options == updated_options
|
assert config_entry.options == updated_options
|
||||||
|
await hass.async_block_till_done()
|
||||||
await _test_service(
|
await _test_service(
|
||||||
hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_UP, None, num=VOLUME_STEP
|
hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_UP, None, num=VOLUME_STEP
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue