From 185bd6c28a5c8abbfa62d7eeda43c135e012d96d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 30 Sep 2016 23:11:57 -0700 Subject: [PATCH] Make helpers.condition.* async --- homeassistant/helpers/condition.py | 101 ++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 041f514aeda..ae1dc471706 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -20,15 +20,43 @@ import homeassistant.util.dt as dt_util from homeassistant.util.async import run_callback_threadsafe FROM_CONFIG_FORMAT = '{}_from_config' +ASYNC_FROM_CONFIG_FORMAT = 'async_{}_from_config' _LOGGER = logging.getLogger(__name__) +# PyLint does not like the use of _threaded_factory +# pylint: disable=invalid-name -def from_config(config: ConfigType, config_validation: bool=True): - """Turn a condition configuration into a method.""" - factory = getattr( - sys.modules[__name__], - FROM_CONFIG_FORMAT.format(config.get(CONF_CONDITION)), None) + +def _threaded_factory(async_factory): + """Helper method to create threaded versions of async factories.""" + def factory(config, config_validation=True): + """Threaded factory.""" + async_check = async_factory(config, config_validation) + + def condition_if(hass, variables=None): + """Validate condition.""" + return run_callback_threadsafe( + hass.loop, async_check, hass, variables, + ).result() + + return condition_if + + return factory + + +def async_from_config(config: ConfigType, config_validation: bool=True): + """Turn a condition configuration into a method. + + Should be run on the event loop. + """ + for fmt in (ASYNC_FROM_CONFIG_FORMAT, FROM_CONFIG_FORMAT): + factory = getattr( + sys.modules[__name__], + fmt.format(config.get(CONF_CONDITION)), None) + + if factory: + break if factory is None: raise HomeAssistantError('Invalid condition "{}" specified {}'.format( @@ -37,49 +65,70 @@ def from_config(config: ConfigType, config_validation: bool=True): return factory(config, config_validation) -def and_from_config(config: ConfigType, config_validation: bool=True): +from_config = _threaded_factory(async_from_config) + + +def async_and_from_config(config: ConfigType, config_validation: bool=True): """Create multi condition matcher using 'AND'.""" if config_validation: config = cv.AND_CONDITION_SCHEMA(config) - checks = [from_config(entry, False) for entry in config['conditions']] + checks = None def if_and_condition(hass: HomeAssistant, variables=None) -> bool: """Test and condition.""" - for check in checks: - try: + nonlocal checks + + if checks is None: + checks = [async_from_config(entry, False) for entry + in config['conditions']] + + try: + for check in checks: if not check(hass, variables): return False - except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning('Error during and-condition: %s', ex) - return False + except Exception as ex: # pylint: disable=broad-except + _LOGGER.warning('Error during and-condition: %s', ex) + return False return True return if_and_condition -def or_from_config(config: ConfigType, config_validation: bool=True): +and_from_config = _threaded_factory(async_and_from_config) + + +def async_or_from_config(config: ConfigType, config_validation: bool=True): """Create multi condition matcher using 'OR'.""" if config_validation: config = cv.OR_CONDITION_SCHEMA(config) - checks = [from_config(entry, False) for entry in config['conditions']] + checks = None def if_or_condition(hass: HomeAssistant, variables=None) -> bool: """Test and condition.""" - for check in checks: - try: + nonlocal checks + + if checks is None: + checks = [async_from_config(entry, False) for entry + in config['conditions']] + + try: + for check in checks: if check(hass, variables): return True - except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning('Error during or-condition: %s', ex) + except Exception as ex: # pylint: disable=broad-except + _LOGGER.warning('Error during or-condition: %s', ex) return False return if_or_condition +or_from_config = _threaded_factory(async_or_from_config) + + # pylint: disable=too-many-arguments def numeric_state(hass: HomeAssistant, entity, below=None, above=None, value_template=None, variables=None): @@ -125,7 +174,7 @@ def async_numeric_state(hass: HomeAssistant, entity, below=None, above=None, return True -def numeric_state_from_config(config, config_validation=True): +def async_numeric_state_from_config(config, config_validation=True): """Wrap action method with state based condition.""" if config_validation: config = cv.NUMERIC_STATE_CONDITION_SCHEMA(config) @@ -139,12 +188,15 @@ def numeric_state_from_config(config, config_validation=True): if value_template is not None: value_template.hass = hass - return numeric_state(hass, entity_id, below, above, value_template, - variables) + return async_numeric_state( + hass, entity_id, below, above, value_template, variables) return if_numeric_state +numeric_state_from_config = _threaded_factory(async_numeric_state_from_config) + + def state(hass, entity, req_state, for_period=None): """Test if state matches requirements.""" if isinstance(entity, str): @@ -235,7 +287,7 @@ def async_template(hass, value_template, variables=None): return value.lower() == 'true' -def template_from_config(config, config_validation=True): +def async_template_from_config(config, config_validation=True): """Wrap action method with state based condition.""" if config_validation: config = cv.TEMPLATE_CONDITION_SCHEMA(config) @@ -245,11 +297,14 @@ def template_from_config(config, config_validation=True): """Validate template based if-condition.""" value_template.hass = hass - return template(hass, value_template, variables) + return async_template(hass, value_template, variables) return template_if +template_from_config = _threaded_factory(async_template_from_config) + + def time(before=None, after=None, weekday=None): """Test if local time condition matches.