import logging from functools import reduce from .checks.basic import Check from .exceptions import UnresolvedConsequences logger = logging.getLogger(__name__) class Precondition: def __init__(self, *checks, accepted_consequences=None): self.accepted_consequences = accepted_consequences self._is_met = False self._verified = False self._consequence_map = None self._executed = False self._unique_hash = set() self.context = {} for check in checks: self.context.update(check.kwargs) check.set_root() self.checks = [check.check(self) for check in checks] def _verify(self, check): checks, consequences = check.wait_for_result() # print(repr(checks), repr(consequences)) res = True for checking in checks: res &= self._verify(checking) for con in consequences: res &= con.is_accepted(self.accepted_consequences) return res @property def is_met(self): return self.is_verified and self._is_met @property def is_verified(self): if not self._verified: self._is_met = reduce( lambda x, y: x & y, [self._verify(check) for check in self.checks], True ) self._verified = True return self._verified def _execute(self, check): checks, consequences = check.wait_for_result() # immediately execute "pre" exec, before following up with anything else. for con in consequences: con.pre_resolve(self.context, self.accepted_consequences) post_exec_triggers = [] for checking in reversed(checks): triggers = self._execute(checking) if triggers: post_exec_triggers.extend(triggers) for con in consequences: if self.is_unique(con): triggers = con.do_resolve(self.context, self.accepted_consequences) if triggers: post_exec_triggers.extend(triggers) return post_exec_triggers def execute(self): if self.is_met and not self._executed: triggers = [] for check in self.checks: triggers.extend(self._execute(check)) for trigger in triggers: try: trigger(self.context) except Exception as e: logger.exception("TRIGGER FAILED", exc_info=e) self._executed = True def is_unique(self, con): h = hash(con) if h in self._unique_hash: return False self._unique_hash.add(h) return True def _collect_consequences(self, check): checks, consequences = check.wait_for_result() for con in consequences: if con.visible and self.is_unique(con): self._consequence_map.setdefault(type(con), []).append(con) for checking in checks: self._collect_consequences(checking) def prepare_frontend_package(self): if not self.is_met: if self._consequence_map is None: self._consequence_map = {} self._unique_hash = set() for check in self.checks: self._collect_consequences(check) package = [] pid_prefix = Check.random_id() for ctype, con_list in self._consequence_map.items(): accepted_cons, items_cons, count = [], [], 0 for con in con_list: con_hash = str(con) count += len(con) if ( self.accepted_consequences and con_hash in self.accepted_consequences ): accepted_cons.append(con_hash) items_cons.append(con_hash) package.append( { "id": f"{pid_prefix}{len(package)}", "type": ctype.__name__, "description": ctype.description, "count": count, "items": items_cons, "accepted": accepted_cons, } ) return package else: return [] def get_exception(self): return UnresolvedConsequences(self.prepare_frontend_package()) def raise_exception(self): raise self.get_exception()