Enhanced ExecutionContext to keep track of the execution flow
This commit is contained in:
+164
-102
@@ -1,5 +1,3 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique
|
||||
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW
|
||||
from parsers.BaseParser import BaseParser
|
||||
@@ -10,6 +8,7 @@ import core.builtin_helpers
|
||||
from core.sheerka_logger import console_handler
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
CONCEPT_EVALUATION_STEPS = [
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
@@ -189,21 +188,26 @@ class Sheerka(Concept):
|
||||
event = Event(text, user_name)
|
||||
evt_digest = self.sdp.save_event(event)
|
||||
self.log.debug(f"{evt_digest=}")
|
||||
execution_context = ExecutionContext(self.key, event, self)
|
||||
|
||||
user_input = self.ret(self.name, True, self.new(BuiltinConcepts.USER_INPUT, body=text, user_name=user_name))
|
||||
reduce_requested = self.ret(self.name, True, self.new(BuiltinConcepts.REDUCE_REQUESTED))
|
||||
with ExecutionContext(self.key, event, self, f"Evaluating '{text}'") as execution_context:
|
||||
user_input = self.ret(self.name, True, self.new(BuiltinConcepts.USER_INPUT, body=text, user_name=user_name))
|
||||
reduce_requested = self.ret(self.name, True, self.new(BuiltinConcepts.REDUCE_REQUESTED))
|
||||
|
||||
steps = [
|
||||
BuiltinConcepts.BEFORE_PARSING,
|
||||
BuiltinConcepts.PARSING,
|
||||
BuiltinConcepts.AFTER_PARSING,
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
BuiltinConcepts.EVALUATION,
|
||||
BuiltinConcepts.AFTER_EVALUATION
|
||||
]
|
||||
steps = [
|
||||
BuiltinConcepts.BEFORE_PARSING,
|
||||
BuiltinConcepts.PARSING,
|
||||
BuiltinConcepts.AFTER_PARSING,
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
BuiltinConcepts.EVALUATION,
|
||||
BuiltinConcepts.AFTER_EVALUATION
|
||||
]
|
||||
|
||||
return self.execute(execution_context, [user_input, reduce_requested], steps)
|
||||
ret = self.execute(execution_context, [user_input, reduce_requested], steps)
|
||||
execution_context.add_values(return_values=ret)
|
||||
|
||||
if not self.skip_builtins_in_db:
|
||||
self.sdp.save_result(execution_context)
|
||||
return ret
|
||||
|
||||
def _call_parsers(self, execution_context, return_values, logger=None):
|
||||
|
||||
@@ -229,15 +233,19 @@ class Sheerka(Concept):
|
||||
p = parser(sheerka=self)
|
||||
if logger:
|
||||
p.log = logger
|
||||
res = p.parse(execution_context, to_parse)
|
||||
|
||||
if hasattr(res, "__iter__"):
|
||||
for r in res:
|
||||
r.parents = [return_value]
|
||||
result.append(r)
|
||||
else:
|
||||
res.parents = [return_value]
|
||||
result.append(res)
|
||||
with execution_context.push(desc=f"Parsing using {p.name}") as sub_context:
|
||||
res = p.parse(sub_context, to_parse)
|
||||
|
||||
if hasattr(res, "__iter__"):
|
||||
for r in res:
|
||||
r.parents = [return_value]
|
||||
result.append(r)
|
||||
else:
|
||||
res.parents = [return_value]
|
||||
result.append(res)
|
||||
|
||||
sub_context.add_values(return_values=res)
|
||||
|
||||
return result
|
||||
|
||||
@@ -368,18 +376,19 @@ class Sheerka(Concept):
|
||||
"""
|
||||
|
||||
for step in execution_steps:
|
||||
sub_context = execution_context.push(step=step)
|
||||
sub_context.log(logger or self.log, f"{step=}, context='{sub_context}'")
|
||||
|
||||
copy = return_values[:] if hasattr(return_values, "__iter__") else [return_values]
|
||||
with execution_context.push(step=step, iteration=0, desc=f"{step=}", return_values=copy) as sub_context:
|
||||
sub_context.log(logger or self.log, f"{step=}, context='{sub_context}'")
|
||||
|
||||
if step == BuiltinConcepts.PARSING:
|
||||
return_values = self._call_parsers(sub_context, return_values, logger)
|
||||
else:
|
||||
return_values = self._call_evaluators(sub_context, return_values, step, None, logger)
|
||||
if step == BuiltinConcepts.PARSING:
|
||||
return_values = self._call_parsers(sub_context, return_values, logger)
|
||||
else:
|
||||
return_values = self._call_evaluators(sub_context, return_values, step, None, logger)
|
||||
|
||||
if copy != return_values:
|
||||
sub_context.log_result(logger or self.log, return_values)
|
||||
if copy != return_values:
|
||||
sub_context.log_result(logger or self.log, return_values)
|
||||
|
||||
sub_context.add_values(return_values=return_values)
|
||||
|
||||
return return_values
|
||||
|
||||
@@ -430,14 +439,15 @@ class Sheerka(Concept):
|
||||
|
||||
# check if it's a valid BNF or whether it breaks the known rules
|
||||
concept_lexer_parser = self.parsers[CONCEPT_LEXER_PARSER_CLASS]()
|
||||
sub_context = context.push(self.name, desc=f"Initializing concept definition for {concept}")
|
||||
sub_context.concepts[concept.key] = concept # the concept is not in the real cache yet
|
||||
sub_context.log_new(logger)
|
||||
init_ret_value = concept_lexer_parser.initialize(sub_context, concepts_definitions)
|
||||
if not init_ret_value.status:
|
||||
return self.ret(self.create_new_concept.__name__, False, ErrorConcept(init_ret_value.value))
|
||||
with context.push(self.name, desc=f"Initializing concept definition for {concept}") as sub_context:
|
||||
sub_context.concepts[concept.key] = concept # the concept is not in the real cache yet
|
||||
sub_context.log_new(logger)
|
||||
init_ret_value = concept_lexer_parser.initialize(sub_context, concepts_definitions)
|
||||
sub_context.add_values(return_values=init_ret_value)
|
||||
if not init_ret_value.status:
|
||||
return self.ret(self.create_new_concept.__name__, False, ErrorConcept(init_ret_value.value))
|
||||
|
||||
# save the new context in sdp
|
||||
# save the new concept in sdp
|
||||
try:
|
||||
self.sdp.add(context.event.get_digest(), self.CONCEPTS_ENTRY, concept, use_ref=True)
|
||||
if concepts_definitions is not None:
|
||||
@@ -507,10 +517,12 @@ class Sheerka(Concept):
|
||||
# I refuse empty strings for performance matters, I don't want to handle useless NOPConcepts
|
||||
continue
|
||||
else:
|
||||
sub_context = context.push(desc=f"Initializing AST for {part_key}")
|
||||
sub_context.log_new(logger)
|
||||
to_parse = self.ret(context.who, True, self.new(BuiltinConcepts.USER_INPUT, body=source))
|
||||
concept.cached_asts[part_key] = self.execute(sub_context, to_parse, steps, logger)
|
||||
with context.push(desc=f"Initializing AST for {part_key}") as sub_context:
|
||||
sub_context.log_new(logger)
|
||||
to_parse = self.ret(context.who, True, self.new(BuiltinConcepts.USER_INPUT, body=source))
|
||||
res = self.execute(sub_context, to_parse, steps, logger)
|
||||
concept.cached_asts[part_key] = res
|
||||
sub_context.add_values(return_values=res)
|
||||
|
||||
for prop in concept.props:
|
||||
value = concept.props[prop].value
|
||||
@@ -522,9 +534,11 @@ class Sheerka(Concept):
|
||||
context.who,
|
||||
True,
|
||||
self.new(BuiltinConcepts.USER_INPUT, body=value))
|
||||
sub_context = context.push(desc=f"Initializing AST for property {prop}")
|
||||
sub_context.log_new(logger)
|
||||
concept.cached_asts[prop] = self.execute(context, to_parse, steps)
|
||||
with context.push(desc=f"Initializing AST for property {prop}") as sub_context:
|
||||
sub_context.log_new(logger)
|
||||
res = self.execute(context, to_parse, steps)
|
||||
concept.cached_asts[prop] = res
|
||||
sub_context.add_values(return_values=res)
|
||||
|
||||
# Updates the cache of concepts when possible
|
||||
if concept.key in self.concepts_cache:
|
||||
@@ -552,10 +566,12 @@ class Sheerka(Concept):
|
||||
|
||||
def _resolve(return_value, desc, obj):
|
||||
context.log(logger, desc, self.evaluate_concept.__name__)
|
||||
sub_context = context.push(desc=desc, obj=obj)
|
||||
sub_context.log_new(logger)
|
||||
r = self.execute(sub_context, return_value, CONCEPT_EVALUATION_STEPS, logger)
|
||||
return core.builtin_helpers.expect_one(context, r)
|
||||
with context.push(desc=desc, obj=obj) as sub_context:
|
||||
sub_context.log_new(logger)
|
||||
r = self.execute(sub_context, return_value, CONCEPT_EVALUATION_STEPS, logger)
|
||||
one_r = core.builtin_helpers.expect_one(context, r)
|
||||
sub_context.add_values(return_values=one_r)
|
||||
return one_r
|
||||
|
||||
# WHERE condition should already be validated by the parser.
|
||||
# It's a mandatory condition for the concept before it can be recognized
|
||||
@@ -579,10 +595,11 @@ class Sheerka(Concept):
|
||||
if isinstance(concept.cached_asts[prop_name], Concept):
|
||||
context.log(
|
||||
logger, f"Evaluation prop={prop_name}, value={prop_ast}", self.evaluate_concept.__name__)
|
||||
sub_context = context.push(f"Evaluation property '{prop_name}', value='{prop_ast}'")
|
||||
sub_context.log_new(logger)
|
||||
evaluated = self.evaluate_concept(sub_context, prop_ast)
|
||||
concept.set_prop(prop_name, evaluated)
|
||||
with context.push(f"Evaluation property '{prop_name}', value='{prop_ast}'") as sub_context:
|
||||
sub_context.log_new(logger)
|
||||
evaluated = self.evaluate_concept(sub_context, prop_ast)
|
||||
sub_context.add_values(return_values=evaluated)
|
||||
concept.set_prop(prop_name, evaluated)
|
||||
else:
|
||||
res = _resolve(prop_ast, f"Evaluating property '{prop_name}'", None)
|
||||
if res.status:
|
||||
@@ -631,26 +648,36 @@ class Sheerka(Concept):
|
||||
self.concepts_cache[concept.key] = concept
|
||||
return concept
|
||||
|
||||
def get(self, concept_key):
|
||||
def get(self, concept_key, concept_id=None):
|
||||
"""
|
||||
Tries to find a concept
|
||||
What is return must be used a template for another concept.
|
||||
You must not modify the returned concept
|
||||
:param concept_key:
|
||||
:param concept_key: key of the concept
|
||||
:param concept_id: when multiple concepts with the same key, use the id
|
||||
:return:
|
||||
"""
|
||||
|
||||
if concept_key is None:
|
||||
return ErrorConcept("Concept key is undefined.")
|
||||
|
||||
if isinstance(concept_key, BuiltinConcepts):
|
||||
concept_key = str(concept_key)
|
||||
|
||||
# first search in cache
|
||||
if concept_key in self.concepts_cache:
|
||||
return self.concepts_cache[concept_key]
|
||||
result = self.concepts_cache[concept_key] if concept_key in self.concepts_cache else \
|
||||
self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
|
||||
|
||||
# else look in sdp
|
||||
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
|
||||
if from_db is not None:
|
||||
return from_db
|
||||
if result and (concept_id is None or not isinstance(result, list)):
|
||||
return result
|
||||
|
||||
if isinstance(result, list):
|
||||
if concept_id:
|
||||
for c in result:
|
||||
if c.id == concept_id:
|
||||
return c
|
||||
else:
|
||||
return result
|
||||
|
||||
# else return new Unknown concept
|
||||
# Note that I don't call the new() method to prevent cyclic call
|
||||
@@ -871,10 +898,11 @@ class Sheerka(Concept):
|
||||
for c in concepts:
|
||||
if not first:
|
||||
self.log.info("")
|
||||
self.log.info(f"name : {c.name}")
|
||||
self.log.info(f"bnf : {c.metadata.definition}")
|
||||
self.log.info(f"key : {c.key}")
|
||||
self.log.info(f"body : {c.body}")
|
||||
self.log.info(f"name : {c.name}")
|
||||
self.log.info(f"bnf : {c.metadata.definition}")
|
||||
self.log.info(f"key : {c.key}")
|
||||
self.log.info(f"body : {c.body}")
|
||||
self.log.info(f"digest : {c.get_digest()}")
|
||||
first = False
|
||||
|
||||
@staticmethod
|
||||
@@ -900,7 +928,6 @@ class Sheerka(Concept):
|
||||
logging.basicConfig(format=log_format, level=log_level, handlers=[console_handler])
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExecutionContext:
|
||||
"""
|
||||
To keep track of the execution of a request
|
||||
@@ -910,28 +937,66 @@ class ExecutionContext:
|
||||
who,
|
||||
event: Event,
|
||||
sheerka: Sheerka,
|
||||
/,
|
||||
desc: str = None,
|
||||
obj: Concept = None,
|
||||
step: BuiltinConcepts = None,
|
||||
iteration: int = 0,
|
||||
concepts: dict = None):
|
||||
**kwargs):
|
||||
|
||||
self._parent = None
|
||||
self._id = ExecutionContextIdManager.get_id(event.get_digest())
|
||||
self._tab = ""
|
||||
self._bag = {} # other variables
|
||||
self._start = 0
|
||||
self._stop = 0
|
||||
|
||||
self.who = who # who is asking
|
||||
self.event = event # what was the (original) trigger
|
||||
self.sheerka = sheerka # sheerka
|
||||
|
||||
self.step = step
|
||||
self.iteration = iteration
|
||||
self.preprocess = None
|
||||
|
||||
self.desc = desc # human description of what is going on
|
||||
self.obj = obj # what is the subject of the execution context (if known)
|
||||
self.children = []
|
||||
self.preprocess = None
|
||||
self.values = {} # what was produced by the execution context
|
||||
|
||||
self.concepts = concepts or {} # cache for concepts that are specific to this execution
|
||||
self.obj = kwargs.pop("obj", None)
|
||||
self.concepts = kwargs.pop("concepts", {})
|
||||
# update the other elements
|
||||
for k, v in kwargs.items():
|
||||
self._bag[k] = v
|
||||
|
||||
self._id = ExecutionContextIdManager.get_id(event.get_digest())
|
||||
self._tab = ""
|
||||
@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 id(self):
|
||||
return self._id
|
||||
|
||||
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()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self._stop = time.time_ns()
|
||||
|
||||
def __repr__(self):
|
||||
msg = f"ExecutionContext(who={self.who}, id={self._id}"
|
||||
if self.desc:
|
||||
msg += f", desc='{self.desc}'"
|
||||
msg += ")"
|
||||
return msg
|
||||
|
||||
def add_preprocess(self, name, **kwargs):
|
||||
preprocess = self.sheerka.new(BuiltinConcepts.EVALUATOR_PRE_PROCESS)
|
||||
@@ -944,6 +1009,11 @@ class ExecutionContext:
|
||||
self.preprocess.add(preprocess)
|
||||
return self
|
||||
|
||||
def add_values(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
self.values[k] = v
|
||||
return self
|
||||
|
||||
def new_concept(self, key, **kwargs):
|
||||
# search in obj
|
||||
if self.obj:
|
||||
@@ -964,29 +1034,23 @@ class ExecutionContext:
|
||||
|
||||
return self.sheerka.new(key, **kwargs)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
def push(self, who=None, /, **kwargs):
|
||||
def push(self, who=None, desc=None, **kwargs):
|
||||
who = who or self.who
|
||||
desc = kwargs.get("desc", "")
|
||||
obj = kwargs.get("obj", self.obj)
|
||||
concepts = kwargs.get("concepts", self.concepts)
|
||||
step = kwargs.get("step", self.step)
|
||||
iteration = kwargs.get("iteration", self.iteration)
|
||||
_kwargs = {"obj": self.obj, "concepts": self.concepts}
|
||||
_kwargs.update(self._bag)
|
||||
_kwargs.update(kwargs)
|
||||
new = ExecutionContext(
|
||||
who,
|
||||
self.event,
|
||||
self.sheerka,
|
||||
desc=desc,
|
||||
obj=obj,
|
||||
concepts=concepts,
|
||||
step=step,
|
||||
iteration=iteration,
|
||||
desc,
|
||||
**_kwargs,
|
||||
)
|
||||
new._parent = self
|
||||
new._tab = self._tab + " " * DEBUG_TAB_SIZE
|
||||
new.preprocess = self.preprocess
|
||||
|
||||
self.children.append(new)
|
||||
return new
|
||||
|
||||
def log_new(self, logger):
|
||||
@@ -1009,6 +1073,11 @@ class ExecutionContext:
|
||||
to_str = self.return_value_to_str(r)
|
||||
logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
|
||||
|
||||
def to_dict(self):
|
||||
from core.sheerka_transform import SheerkaTransform
|
||||
st = SheerkaTransform(self.sheerka)
|
||||
return st.to_dict(self)
|
||||
|
||||
@staticmethod
|
||||
def return_value_to_str(r):
|
||||
value = str(r.value)
|
||||
@@ -1017,13 +1086,6 @@ class ExecutionContext:
|
||||
to_str = f"ReturnValue(who={r.who}, status={r.status}, value={value})"
|
||||
return to_str
|
||||
|
||||
def __repr__(self):
|
||||
msg = f"ExecutionContext(who={self.who}, id={self._id}"
|
||||
if self.desc:
|
||||
msg += f", desc='{self.desc}'"
|
||||
msg += ")"
|
||||
return msg
|
||||
|
||||
|
||||
class ExecutionContextIdManager:
|
||||
ids = {}
|
||||
|
||||
Reference in New Issue
Block a user