Files
Sheerka-Old/src/core/sheerka/ExecutionContext.py
T
2020-09-21 21:30:38 +02:00

437 lines
14 KiB
Python

import logging
import time
from core.builtin_concepts import BuiltinConcepts, ParserResultConcept
from core.concept import Concept
from core.sheerka.services.SheerkaExecute import NO_MATCH
from core.sheerka.services.SheerkaMemory import SheerkaMemory
from core.sheerka_logger import get_logger
from sdp.sheerkaDataProvider import Event
DEBUG_TAB_SIZE = 4
PROPERTIES_TO_SERIALIZE = ("_id",
"_bag",
"_children",
"_start",
"_stop",
"who",
"action",
"action_context",
"desc",
"inputs",
"values",
"obj",
"concepts")
class ExecutionContext:
"""
To keep track of the execution of a request
"""
ids = {}
@staticmethod
def get_id(event_digest):
if event_digest in ExecutionContext.ids:
ExecutionContext.ids[event_digest] += 1
else:
ExecutionContext.ids[event_digest] = 0
return ExecutionContext.ids[event_digest]
def __init__(self,
who,
event: Event,
sheerka,
action: BuiltinConcepts,
action_context,
desc: str = None,
logger=None,
global_hints=None,
errors=None,
**kwargs):
self._id = ExecutionContext.get_id(event.get_digest()) if event else None
self._parent = None
self._children = []
self._tab = ""
self._bag = {} # context variables
self._start = 0 # when the execution starts (to measure elapsed time)
self._stop = 0 # when the execution stops (to measure elapses time)
self._logger = logger
self._format_instructions = None # how to print the execution context
self._stat_log = get_logger("stats")
self._show_stats = False
self.who = who # who is asking
self.event = event # what was the (original) trigger
self.sheerka = sheerka # sheerka
self.action = action
self.action_context = action_context
self.desc = desc # human description of what is going on
self.preprocess = None
self.stm = False # True if the context has short term memory entries
self.private_hints = set()
self.protected_hints = set()
self.global_hints = set() if global_hints is None else global_hints
self.errors = [] if errors is None else errors # error are global
self.inputs = {} # what were the parameters of the execution context
self.values = {} # what was produced by the execution context
self.obj = kwargs.pop("obj", None) # current obj we are working on
self.concepts = kwargs.pop("concepts", {}) # known concepts specific to this context
# update the other elements
for k, v in kwargs.items():
self._bag[k] = v
@property
def elapsed(self):
if self._start == 0:
return 0
return (self._stop if self._stop > 0 else time.time_ns()) - self._start
@property
def elapsed_str(self):
nano_sec = self.elapsed
dt = nano_sec / 1e6
return f"{dt} ms" if dt < 1000 else f"{dt / 1000} s"
@property
def logger(self):
return self._logger
@property
def id(self):
return self._id
@property
def achildren(self):
"""
I prefixed with an 'a' to make it appear on the top when debugging
:return:
"""
return self._children
def __getattr__(self, item):
if item in self._bag:
return self._bag[item]
raise AttributeError(f"'ExecutionContext' object has no attribute '{item}'")
def __enter__(self):
self._start = time.time_ns()
self.log_new()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.stm:
self.sheerka.services[SheerkaMemory.NAME].remove_context(self)
self._stop = time.time_ns()
if self._show_stats:
self._stat_log.debug(f"[{self._id:2}]" + self._tab + "Execution time: " + self.elapsed_str)
def __repr__(self):
msg = f"ExecutionContext(who={self.who}, id={self._id}, action={self.action}, context={self.action_context}"
if self.desc:
msg += f", desc='{self.desc}'"
msg += ")"
return msg
# def __str__(self):
# msg = self.desc or "New Context"
# msg += f", who={self.who}, id={self.id}"
# return msg
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, ExecutionContext):
return False
for prop in PROPERTIES_TO_SERIALIZE:
if prop in ("who", "action", "action_context"):
value = str(getattr(self, prop))
other_value = str(getattr(other, prop))
else:
value = getattr(self, prop)
other_value = getattr(other, prop)
if value != other_value:
return False
return True
def push(self, action: BuiltinConcepts, action_context, who=None, desc=None, logger=None, **kwargs):
who = who or self.who
logger = logger or self._logger
_kwargs = {"obj": self.obj, "concepts": self.concepts}
_kwargs.update(self._bag)
_kwargs.update(kwargs)
new = ExecutionContext(
who,
self.event,
self.sheerka,
action,
action_context,
desc,
logger,
self.global_hints,
self.errors,
**_kwargs)
new._parent = self
new._tab = self._tab + " " * DEBUG_TAB_SIZE
new.preprocess = self.preprocess
new.protected_hints.update(self.protected_hints)
self._children.append(new)
return new
def add_preprocess(self, name, **kwargs):
preprocess = self.sheerka.new(BuiltinConcepts.EVALUATOR_PRE_PROCESS)
preprocess.set_value("name", name)
for k, v in kwargs.items():
preprocess.set_value(k, v)
if not self.preprocess:
self.preprocess = []
self.preprocess.append(preprocess)
return self
def add_inputs(self, **kwargs):
for k, v in kwargs.items():
self.inputs[k] = v
return self
def add_values(self, **kwargs):
for k, v in kwargs.items():
self.values[k] = v
return self
def add_to_short_term_memory(self, key, concept):
"""
Add a concept to the short term memory (relative to the current execution context)
:param key:
:param concept:
:return:
"""
self.sheerka.add_to_short_term_memory(self, key, concept)
def get_from_short_term_memory(self, key):
"""
:param key:
:return:
"""
return self.sheerka.get_from_short_term_memory(self, key)
def get_concept(self, key):
# search in obj
if isinstance(self.obj, Concept):
if self.obj.key == key:
return self.obj
for var_name in self.obj.values:
if var_name == key:
value = self.obj.get_value(var_name)
if isinstance(value, Concept):
return value
# search in concepts
if self.concepts:
for k, c in self.concepts.items():
if k == key:
return c
return self.sheerka.get_by_key(key)
def new_concept(self, key, **kwargs):
# search in obj
if self.obj:
if self.obj.key == key:
return self.sheerka.new_from_template(self.obj, key, **kwargs)
for var_name in self.obj.values:
if var_name == key:
value = self.obj.get_value(var_name)
if isinstance(value, Concept):
return self.sheerka.new_from_template(value, key, **kwargs)
else:
return value
if self.concepts:
for k, c in self.concepts.items():
if k == key:
return self.sheerka.new_from_template(c, key, **kwargs)
return self.sheerka.new(key, **kwargs)
def log_new(self):
if self._logger and not self._logger.disabled:
self._logger.debug(f"[{self._id:2}]" + self._tab + str(self))
self._show_stats = True
def log(self, message, who=None):
if self._logger and not self._logger.disabled:
self._logger.debug(f"[{self._id:2}]" + self._tab + (f"[{who}] " if who else "") + str(message))
def log_error(self, message, who=None, exc=None):
self.errors.append(exc or message)
if self._logger and not self._logger.disabled:
self._logger.exception(f"[{self._id:2}]" + self._tab + (f"[{who}] " if who else "") + str(message))
def log_result(self, return_values):
if not self._logger or not self._logger.isEnabledFor(logging.DEBUG):
return
if len(return_values) == 0:
self._logger.debug(self._tab + "No return value")
for r in return_values:
to_str = self.return_value_to_str(r)
self._logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
def get_parent(self):
return self._parent
def in_context(self, concept_key):
return concept_key in self.protected_hints or \
concept_key in self.global_hints or \
concept_key in self.private_hints
def in_current_context(self, concept_key):
return concept_key in self.protected_hints or concept_key in self.private_hints
def in_private_context(self, concept_key):
return concept_key in self.private_hints
def add_to_private_hints (self, concept_key):
self.private_hints.add(concept_key)
def add_to_protected_hints(self, concept_key):
self.protected_hints.add(concept_key)
def add_to_global_hints(self, concept_key):
self.global_hints.add(concept_key)
@staticmethod
def _is_return_value(obj):
return isinstance(obj, Concept) and obj.key == str(BuiltinConcepts.RETURN_VALUE)
def _at_least_one_success(self, return_values):
status = False
for ret_val in return_values:
if not self._is_return_value(ret_val):
return None
status |= ret_val.status
return status
def _all_success(self, return_values):
status = True
for ret_val in return_values:
if not self._is_return_value(ret_val):
return None
status &= ret_val.status
return status
def get_status(self):
# In the function, I cannot use sheerka.isinstance() as self.sheerka may not be initialized
# This is the case when ExecutionContext is deserialized
if "return_values" not in self.values:
return None
if hasattr(self.values["return_values"], "__iter__"):
values = self.values["return_values"]
if len(values) == 0:
return None
if isinstance(values, str):
return "No Match" if values == NO_MATCH else values
if isinstance(values[0], dict):
for result in values:
if "return_value" not in result:
return None
if self._is_return_value(result["return_value"]):
return result["return_value"].status
return "No Match"
else:
return self._at_least_one_success(self.values["return_values"])
else:
ret_val = self.values["return_values"]
if not isinstance(ret_val, Concept) or not ret_val.key == str(BuiltinConcepts.RETURN_VALUE):
return None
if ret_val.status:
return True
if isinstance(ret_val.body, ParserResultConcept):
return "Almost"
return False
def as_bag(self):
"""
Creates a dictionary with the useful properties of the concept
It quicker to implement than creating the actual property mechanism with @property
And it removes the visibility from the other attributes/methods
"""
bag = {}
for k, v in self._bag.items():
bag[k] = v
bag["bag." + k] = v
for prop in ("id", "who", "action", "desc", "obj", "inputs", "values", "concepts"):
bag[prop] = getattr(self, prop)
bag["context"] = self.action_context
for prop in ("desc", "obj", "inputs", "values", "concepts"):
bag[prop] = getattr(self, prop)
bag["status"] = self.get_status()
bag["elapsed"] = self.elapsed
bag["elapsed_str"] = self.elapsed_str
bag["digest"] = self.event.get_digest() if self.event else None
return bag
@staticmethod
def return_value_to_str(r):
value = str(r.value)
if len(value) > 50:
value = value[:47] + "..."
to_str = f"ReturnValue(who={r.who}, status={r.status}, value={value})"
return to_str
def get_format_instructions(self):
return self._format_instructions
def set_format_instructions(self, instructions):
self._format_instructions = instructions
def get_parents(self, predicate=None):
"""
Gets all the parents that match the given predicate
:param predicate:
:return:
"""
return list(self.search(predicate, None, False))
def search(self, predicate=None, get_obj=None, start_with_self=False, stop=None):
"""
Iter thru execution context parent and return the list of obj
:param predicate: what execution context to keep
:param get_obj: lambda to compute what to return
:param start_with_self: include the current execution context in the search
:param stop: condition to stop
:return:
"""
current = self if start_with_self else self._parent
while current:
if predicate is None or predicate(current):
yield current if get_obj is None else get_obj(current)
if stop and stop(current):
break
current = current._parent