diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 42202d905..4b1ea1577 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -175,6 +175,22 @@ def ab_alive(self): def ab_alive(self, value): self.__ab_alive = value + @property + def eval_cache(self): + return self.__eval_cache + + @eval_cache.setter + def eval_cache(self, value): + def contains_any_key(dict_list, new_dict): + new_dict_keys = set(new_dict.keys()) + for item in dict_list: + if not new_dict_keys.isdisjoint(item.keys()): # Checks for any common key + return True + return False + + if not contains_any_key(self.__eval_cache, value): + self.__eval_cache.append(value) + # Constructor # smarthome: instance of smarthome.py # item: item to use @@ -191,6 +207,7 @@ def __init__(self, smarthome, item, se_plugin): self.__se_plugin = se_plugin self.__active_schedulers = [] self.__release_info = {} + self.__eval_cache = [] self.__default_instant_leaveaction = StateEngineValue.SeValue(self, "Default Instant Leave Action", False, "bool") self.__instant_leaveaction = StateEngineValue.SeValue(self, "Instant Leave Action", False, "num") try: @@ -239,7 +256,6 @@ def __init__(self, smarthome, item, se_plugin): self.__logger.header("Initialize Item {0} (Log {1}, Level set" " to {2}, default log level is {3})".format(self.id, self.__logger.name, _returnvalue, _default_log_level)) - # get startup delay self.__startup_delay = StateEngineValue.SeValue(self, "Startup Delay", False, "num") self.__startup_delay.set_from_attr(self.__item, "se_startup_delay", StateEngineDefaults.startup_delay) @@ -541,6 +557,7 @@ def run_queue(self): (_, item, caller, source, dest) = job item_id = item.property.path if item is not None else "(no item)" self.__logger.update_logfile() + self.__eval_cache = [] self.__logger.header("Update state of item {0}".format(self.__name)) if caller: self.__logger.debug("Update triggered by {0} (item={1} source={2} dest={3})", caller, item_id, @@ -667,6 +684,7 @@ def run_queue(self): last_state.run_stay(self.__repeat_actions.get()) if self.update_lock.locked(): self.update_lock.release() + self.__eval_cache = [] self.__logger.decrease_indent(50) self.__logger.debug("State evaluation finished") self.__logger.info("State evaluation queue empty.") @@ -763,7 +781,7 @@ def run_queue(self): self.__logger.debug("State evaluation finished") all_released_by = self.__handle_releasedby(new_state, last_state, _instant_leaveaction) - + self.__eval_cache = [] self.__logger.decrease_indent(50) self.__logger.info("State evaluation queue empty.") if new_state: diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index bfc2c87b8..82ee9a021 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -45,6 +45,7 @@ def __init__(self, abitem, name, allow_value_list=False, value_type=None): except Exception: pass self.__name = name + self._abitem = abitem self.__allow_value_list = allow_value_list self.__value = None self.__item = None @@ -416,6 +417,7 @@ def set_cast(self, cast_func): # determine and return value def get(self, default=None, originalorder=True): returnvalues = [] + _original_listorder = [] try: _original_listorder = self.__listorder.copy() except Exception as ex: @@ -744,118 +746,96 @@ def __get_from_regex(self): def __get_eval(self): # noinspection PyUnusedLocal sh = self._sh + # noinspection PyUnusedLocal shtime = self._shtime - if isinstance(self.__eval, str): - self.__eval = StateEngineTools.parse_relative(self.__eval, 'sh.', ['()', '.property.']) - if "stateengine_eval" in self.__eval or "se_eval" in self.__eval: + + def contains_get_variable(eval_str): + return 'get_variable("current.' in eval_str or "get_variable('current." in eval_str + + def handle_eval_cache(eval_str): + for item in self._abitem.eval_cache: + if eval_str in item: + values = item.get(eval_str) + if f'eval:{eval_str}' in self.__listorder: + self.__listorder[self.__listorder.index(f'eval:{eval_str}')] = [values] + self._log_debug("Got value {} ({}) for eval {} from cache.", values, type(values), eval_str) + return values + return None + + def handle_eval(eval_str): + nonlocal sh, shtime + if "stateengine_eval" in eval_str or "se_eval" in eval_str: # noinspection PyUnusedLocal stateengine_eval = se_eval = StateEngineEval.SeEval(self._abitem) - self._log_debug("Checking eval: {0}", self.__eval) + + self._log_debug("Checking eval: {}", eval_str) self._log_increase_indent() try: - _newvalue, _issue = self.__do_cast(eval(self.__eval)) - _issue_dict = {StateEngineTools.get_eval_name(self.__eval): _issue} - if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [_newvalue] - values = _newvalue + new_value, issue = self.__do_cast(eval(eval_str)) + if not isinstance(eval_str, str): + eval_str = str(eval_str) + issue_dict = {eval_str: issue} + else: + issue_dict = {StateEngineTools.get_eval_name(eval_str): issue} + if issue not in [[], None, [None]] and issue_dict not in self.__get_issues['eval']: + self.__get_issues['eval'].append(issue_dict) + if f'eval:{eval_str}' in self.__listorder: + self.__listorder[self.__listorder.index(f'eval:{eval_str}')] = [new_value] self._log_decrease_indent() - self._log_debug("Eval result: {0} ({1}).", values, type(values)) + self._log_debug("Eval result: {} ({}).", new_value, type(new_value)) + if not contains_get_variable(eval_str): + self._abitem.eval_cache = {eval_str: new_value} self._log_increase_indent() + return new_value except Exception as ex: - self._log_decrease_indent() - _name = StateEngineTools.get_eval_name(self.__eval) - _issue = "Problem evaluating '{0}': {1}.".format(_name, ex) - _issue_dict = {_name: _issue} - if _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - self._log_warning(_issue) - self._log_increase_indent() - values = None + if not isinstance(eval_str, str): + eval_str = str(eval_str) + else: + eval_str = StateEngineTools.get_eval_name(eval_str) + issue = f"Problem evaluating '{eval_str}': {ex}." + issue_dict = {eval_str: issue} + if issue_dict not in self.__get_issues['eval']: + self.__get_issues['eval'].append(issue_dict) + self._log_warning(issue) + return None finally: self._log_decrease_indent() - else: - if isinstance(self.__eval, list): - values = [] - for val in self.__eval: - try: - val = val.replace("\n", "") - except Exception: - pass - self._log_debug("Checking eval {0} from list {1}.", val, self.__eval) - self._log_increase_indent() - if isinstance(val, str): - if "stateengine_eval" in val or "se_eval" in val: - # noinspection PyUnusedLocal - stateengine_eval = se_eval = StateEngineEval.SeEval(self._abitem) - try: - _newvalue, _issue = self.__do_cast(eval(val)) - _issue_dict = {val: _issue} - if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(val) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(val))] = [_newvalue] - value = _newvalue - self._log_decrease_indent() - self._log_debug("Eval result from list: {0}.", value) - self._log_increase_indent() - except Exception as ex: - self._log_decrease_indent() - _issue = "Problem evaluating from list '{0}': {1}.".format( - StateEngineTools.get_eval_name(val), ex) - _issue_dict = {val: _issue} - if _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - self._log_warning(_issue) - self._log_increase_indent() - value = None - else: - try: - _newvalue, _issue = self.__do_cast(val()) - _issue_dict = {str(val): _issue} - if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(val) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(val))] = [_newvalue] - value = _newvalue - except Exception as ex: - self._log_decrease_indent() - _issue = "Problem evaluating '{0}': {1}.".format( - StateEngineTools.get_eval_name(val), ex) - _issue_dict = {str(val): _issue} - if _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - self._log_info(_issue) - value = None - if value is not None: - values.append(value) - self._log_decrease_indent() - else: - self._log_debug("Checking eval (no str, no list): {0}.", self.__eval) + + def process_eval_list(eval_list): + values = [] + self._log_debug("Processing eval list {}", eval_list) + self._log_increase_indent() + for val in eval_list: try: - self._log_increase_indent() - _newvalue, _issue = self.__do_cast(self.__eval()) - _issue_dict = {_newvalue: _issue} - if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [_newvalue] - values = _newvalue - self._log_decrease_indent() - self._log_debug("Eval result (no str, no list): {0}.", values) - self._log_increase_indent() - except Exception as ex: - self._log_decrease_indent() - _name = StateEngineTools.get_eval_name(self.__eval) - _issue = "Problem evaluating '{0}': {1}.".format(_name, ex) - self._log_warning(_issue) - self._log_increase_indent() - _issue_dict = {_name: _issue} - if _issue_dict not in self.__get_issues['eval']: - self.__get_issues['eval'].append(_issue_dict) - return None - return values + val = val.replace("\n", "") + except Exception: + pass + cached_value = handle_eval_cache(val) + if cached_value is not None: + values.append(cached_value) + continue + + eval_result = handle_eval(val) + if eval_result is not None: + values.append(eval_result) + self._log_decrease_indent() + return values + + if isinstance(self.__eval, str): + values = handle_eval_cache(self.__eval) + if values is not None: + return values + + self.__eval = StateEngineTools.parse_relative(self.__eval, 'sh.', ['()', '.property.']) + return handle_eval(self.__eval) + + elif isinstance(self.__eval, list): + return process_eval_list(self.__eval) + else: + values = handle_eval_cache(str(self.__eval)) + if values is not None: + return values + return handle_eval(str(self.__eval)) # Determine value from item def __get_from_item(self):