Add support for simultaneous runs of Script helper - Part 2 (#32442)

* Add limit parameter to service call methods

* Break out prep part of async_call_from_config for use elsewhere

* Minor cleanup

* Fix improper use of asyncio.wait

* Fix state update

Call change listener immediately if its a callback

* Fix exception handling and logging

* Merge Script helper if_running/run_mode parameters into script_mode

- Remove background/blocking _ScriptRun subclasses which are no longer needed.

* Add queued script mode

* Disable timeout when making fully blocking script call

* Don't call change listener when restarting script

This makes restart mode behavior consistent with parallel & queue modes.

* Changes per review

- Call all script services (except script.turn_off) with no time limit.
- Fix handling of lock in _QueuedScriptRun and add comments to make it
  clearer how this code works.

* Changes per review 2

- Move cancel shielding "up" from _ScriptRun.async_run to Script.async_run
  (and apply to new style scripts only.) This makes sure Script class also
  properly handles cancellation which it wasn't doing before.
- In _ScriptRun._async_call_service_step, instead of using script.turn_off
  service, just cancel service call and let it handle the cancellation
  accordingly.

* Fix bugs

- Add missing call to change listener in Script.async_run
  in cancelled path.
- Cancel service task if ServiceRegistry.async_call cancelled.

* Revert last changes to ServiceRegistry.async_call

* Minor Script helper fixes & test improvements

- Don't log asyncio.CancelledError exceptions.
- Make change_listener a public attribute.
- Test overhaul
  - Parametrize tests.
  - Use common test functions.
  - Mock timeout so tests don't need to wait for real time to elapse.
  - Add common function for waiting for script action step.
This commit is contained in:
Phil Bruckner 2020-03-11 18:34:50 -05:00 committed by GitHub
parent da761fdd39
commit 5f5cb8bea8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 948 additions and 1444 deletions

View file

@ -56,12 +56,27 @@ async def async_call_from_config(
hass, config, blocking=False, variables=None, validate_config=True, context=None
):
"""Call a service based on a config hash."""
try:
parms = async_prepare_call_from_config(hass, config, variables, validate_config)
except HomeAssistantError as ex:
if blocking:
raise
_LOGGER.error(ex)
else:
await hass.services.async_call(*parms, blocking, context)
@ha.callback
@bind_hass
def async_prepare_call_from_config(hass, config, variables=None, validate_config=False):
"""Prepare to call a service based on a config hash."""
if validate_config:
try:
config = cv.SERVICE_SCHEMA(config)
except vol.Invalid as ex:
_LOGGER.error("Invalid config for calling service: %s", ex)
return
raise HomeAssistantError(
f"Invalid config for calling service: {ex}"
) from ex
if CONF_SERVICE in config:
domain_service = config[CONF_SERVICE]
@ -71,17 +86,15 @@ async def async_call_from_config(
domain_service = config[CONF_SERVICE_TEMPLATE].async_render(variables)
domain_service = cv.service(domain_service)
except TemplateError as ex:
if blocking:
raise
_LOGGER.error("Error rendering service name template: %s", ex)
return
except vol.Invalid:
if blocking:
raise
_LOGGER.error("Template rendered invalid service: %s", domain_service)
return
raise HomeAssistantError(
f"Error rendering service name template: {ex}"
) from ex
except vol.Invalid as ex:
raise HomeAssistantError(
f"Template rendered invalid service: {domain_service}"
) from ex
domain, service_name = domain_service.split(".", 1)
domain, service = domain_service.split(".", 1)
service_data = dict(config.get(CONF_SERVICE_DATA, {}))
if CONF_SERVICE_DATA_TEMPLATE in config:
@ -91,15 +104,12 @@ async def async_call_from_config(
template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE], variables)
)
except TemplateError as ex:
_LOGGER.error("Error rendering data template: %s", ex)
return
raise HomeAssistantError(f"Error rendering data template: {ex}") from ex
if CONF_SERVICE_ENTITY_ID in config:
service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID]
await hass.services.async_call(
domain, service_name, service_data, blocking=blocking, context=context
)
return domain, service, service_data
@bind_hass