Managing concept properties in ConceptEvaluator

This commit is contained in:
2019-11-16 18:11:29 +01:00
parent 3a1dea19e8
commit 7fa509555d
13 changed files with 808 additions and 57 deletions
+34 -7
View File
@@ -23,9 +23,13 @@ class BuiltinConcepts(Enum):
INVALID_RETURN_VALUE = 14 # the return value of an evaluator is not correct INVALID_RETURN_VALUE = 14 # the return value of an evaluator is not correct
BEFORE_PARSING = 15 # activated before evaluation by the parsers BEFORE_PARSING = 15 # activated before evaluation by the parsers
PARSING = 16 # activated during the parsing. It contains the text to parse PARSING = 16 # activated during the parsing. It contains the text to parse
AFTER_PARSING = 17 # activated when the parsing process seems to be finished AFTER_PARSING = 17 # after parsing
CONCEPT_ALREADY_DEFINED = 18 # when you try to add the same concept twice BEFORE_EVALUATION = 18 # before evalution
NOP = 19 # no operation concept. Does nothing EVALUATION = 19 # activated when the parsing process seems to be finished
AFTER_EVALUATION = 20 # activated when the parsing process seems to be finished
CONCEPT_ALREADY_DEFINED = 21 # when you try to add the same concept twice
NOP = 22 # no operation concept. Does nothing
PROPERTY_EVAL_ERROR = 23
""" """
@@ -198,11 +202,34 @@ class BeforeParsingConcept(Concept):
super().__init__(BuiltinConcepts.BEFORE_PARSING, True, True, BuiltinConcepts.BEFORE_PARSING) super().__init__(BuiltinConcepts.BEFORE_PARSING, True, True, BuiltinConcepts.BEFORE_PARSING)
class ParsingConcept(Concept): class EvaluationConcept(Concept):
def __init__(self): def __init__(self):
super().__init__(BuiltinConcepts.PARSING, True, True, BuiltinConcepts.PARSING) super().__init__(BuiltinConcepts.EVALUATION, True, True, BuiltinConcepts.EVALUATION)
class AfterParsingConcept(Concept): class AfterEvaluationConcept(Concept):
def __init__(self): def __init__(self):
super().__init__(BuiltinConcepts.AFTER_PARSING, True, True, BuiltinConcepts.AFTER_PARSING) super().__init__(BuiltinConcepts.AFTER_EVALUATION, True, True, BuiltinConcepts.AFTER_EVALUATION)
class PropertyEvalError(Concept):
def __init__(self, property_name=None, concept=None, error=None):
super().__init__(BuiltinConcepts.PROPERTY_EVAL_ERROR, True, False, BuiltinConcepts.PROPERTY_EVAL_ERROR)
self.set_prop("concept", concept)
self.set_prop("error", error)
self.body = property_name
def __repr__(self):
return f"PropertyEvalError(property={self.property_name}, concept={self.concept}), error={self.error})"
@property
def concept(self):
return self.props["concept"].value
@property
def error(self):
return self.props["error"].value
@property
def property_name(self):
return self.body
+1 -1
View File
@@ -69,7 +69,7 @@ class Concept:
# check the attributes # check the attributes
for prop in self.props_to_serialize: for prop in self.props_to_serialize:
if getattr(self, prop) != getattr(other, prop): if getattr(self, prop) != getattr(other, prop):
print(prop) # print(prop) # use full to know which id does not match
return False return False
# check the props (Concept variables) # check the props (Concept variables)
+59 -22
View File
@@ -150,19 +150,24 @@ class Sheerka(Concept):
evt_digest = self.sdp.save_event(Event(text)) evt_digest = self.sdp.save_event(Event(text))
exec_context = ExecutionContext(self.key, evt_digest, self) exec_context = ExecutionContext(self.key, evt_digest, self)
before_parsing = self.ret(self.eval.__name__, True, self.new(BuiltinConcepts.BEFORE_PARSING)) # Before parsing
before_parsing = self.new(BuiltinConcepts.BEFORE_PARSING)
return_values = self.process(exec_context, [], [before_parsing]) return_values = self.process(exec_context, [], [before_parsing])
return_values = core.utils.remove_from_list(return_values, [before_parsing]) return_values = core.utils.remove_from_list(return_values, lambda x: x.value == before_parsing)
# parse
parsing_results = self.parse(exec_context, text) parsing_results = self.parse(exec_context, text)
return_values.extend(parsing_results) return_values.extend(parsing_results)
processing_parsing = self.ret(self.eval.__name__, True, self.new(BuiltinConcepts.PARSING))
return_values = self.process(exec_context, return_values, [processing_parsing])
return_values = core.utils.remove_from_list(return_values, [processing_parsing])
after_parsing = self.ret(self.eval.__name__, True, self.new(BuiltinConcepts.AFTER_PARSING)) # evaluate
return_values = self.process(exec_context, return_values, [after_parsing]) evaluating = self.new(BuiltinConcepts.EVALUATION)
return_values = core.utils.remove_from_list(return_values, [after_parsing]) return_values = self.process(exec_context, return_values, [evaluating])
return_values = core.utils.remove_from_list(return_values, lambda x: x.value == evaluating)
# post evaluation
after_evaluation = self.new(BuiltinConcepts.AFTER_EVALUATION)
return_values = self.process(exec_context, return_values, [after_evaluation])
return_values = core.utils.remove_from_list(return_values, lambda x: x.value == after_evaluation)
return return_values return return_values
@@ -207,17 +212,17 @@ class Sheerka(Concept):
result.append(res) result.append(res)
return result return result
def process(self, context, return_values, contextual_concepts=None): def process(self, context, return_values, initial_concepts=None):
contextual_concepts_values = [c.value for c in contextual_concepts] if contextual_concepts else [] log.debug(f"Processing parsing result. context concept={initial_concepts}")
log.debug(f"Processing parsing result. context concept={contextual_concepts_values}")
# return_values must be a list # return_values must be a list
if not isinstance(return_values, list): if not isinstance(return_values, list):
return_values = [return_values] return_values = [return_values]
# adds contextual concepts # adds contextual concepts
if contextual_concepts: if initial_concepts:
return_values.extend(contextual_concepts) for concept in initial_concepts:
return_values.append(self.ret(context.who, True, concept))
# group the evaluators by priority and sort them # group the evaluators by priority and sort them
# The first one to be applied will be the one with the highest priority # The first one to be applied will be the one with the highest priority
@@ -261,6 +266,8 @@ class Sheerka(Concept):
else: else:
if evaluator.matches(context, original_items): if evaluator.matches(context, original_items):
results = evaluator.eval(context, original_items) results = evaluator.eval(context, original_items)
if results is None:
continue
if not isinstance(results, list): if not isinstance(results, list):
results = [results] results = [results]
for result in results: for result in results:
@@ -277,6 +284,24 @@ class Sheerka(Concept):
return return_values return return_values
def chain_process(self, context, return_values, initial_concepts):
"""
Executes process for all initial contexts
:param context:
:param return_values:
:param initial_concepts:
:return:
"""
for concept in initial_concepts:
if isinstance(concept, BuiltinConcepts):
concept = self.new(BuiltinConcepts)
init = [self.ret(context.who, True, concept)]
return_values = self.process(context, return_values, [init])
return_values = core.utils.remove_from_list(return_values, lambda x: x.value == init)
return return_values
def create_new_concept(self, context, concept): def create_new_concept(self, context, concept):
""" """
Adds a new concept to the system Adds a new concept to the system
@@ -319,13 +344,14 @@ class Sheerka(Concept):
for part_key in ConceptParts: for part_key in ConceptParts:
source = getattr(concept, part_key.value) source = getattr(concept, part_key.value)
if source is None or not isinstance(source, str) or source == "": if source is None or not isinstance(source, str) or source == "":
# the only sources that I am sure to parse are strings # the only sources that I am sure to parse are strings
# I refuse empty strings for performance, I don't want to handle useless NOPConcepts # I refuse empty strings for performance, I don't want to handle useless NOPConcepts
continue continue
else:
concept.codes[part_key] = self.parse(context, source)
ret_val = self.expect_one(context, self.parse(context, source)) for prop in concept.props:
concept.codes[part_key] = ret_val concept.codes[prop] = self.parse(context, concept.props[prop].value)
def add_in_cache(self, concept): def add_in_cache(self, concept):
""" """
@@ -334,7 +360,16 @@ class Sheerka(Concept):
:param concept: :param concept:
:return: :return:
""" """
# sanity check
if concept.key is None:
concept.init_key()
if concept.key is None:
raise KeyError()
self.concepts_cache[concept.key] = concept self.concepts_cache[concept.key] = concept
return concept
def get(self, concept_key): def get(self, concept_key):
""" """
@@ -455,7 +490,7 @@ class Sheerka(Concept):
base_class = core.utils.get_class("parsers.BaseParser.BaseParser") base_class = core.utils.get_class("parsers.BaseParser.BaseParser")
for c in core.utils.get_classes_recursive("parsers"): for c in core.utils.get_classes_recursive("parsers"):
#if issubclass(c, base_class) and c != base_class: # if issubclass(c, base_class) and c != base_class:
res.append(c) res.append(c)
return res return res
@@ -470,9 +505,11 @@ class ExecutionContext:
""" """
To keep track of the execution of a request To keep track of the execution of a request
""" """
who: object who: object # who is asking
event_digest: str event_digest: str # what was the (original) trigger
sheerka: Sheerka sheerka: Sheerka # sheerka
desc: str = None # human description of what is going on
obj: Concept = None # what is the subject of the execution context (if known)
def push(self, who): def push(self, who, desc=None, obj=None):
return ExecutionContext(who, self.event_digest, self.sheerka) return ExecutionContext(who, self.event_digest, self.sheerka, desc=desc, obj=obj)
+8 -4
View File
@@ -106,7 +106,6 @@ def get_classes_from_package(package_name):
def get_sub_classes(package_name, base_class_name): def get_sub_classes(package_name, base_class_name):
pkg = __import__(package_name) pkg = __import__(package_name)
prefix = pkg.__name__ + "." prefix = pkg.__name__ + "."
for (module_loader, name, ispkg) in pkgutil.iter_modules(pkg.__path__, prefix): for (module_loader, name, ispkg) in pkgutil.iter_modules(pkg.__path__, prefix):
@@ -123,8 +122,13 @@ def remove_from_list(lst, to_remove):
:param to_remove: :param to_remove:
:return: :return:
""" """
for item in to_remove:
if item in lst: flagged = []
lst.remove(item) for item in lst:
if to_remove(item):
flagged.append(item)
for item in flagged:
lst.remove(item)
return lst return lst
+74 -1
View File
@@ -396,4 +396,77 @@ I must choose between expressing my ideas in this blog and coding.
I have plenty of ideas that I would like to express, sometimes just to put the idea down, I have plenty of ideas that I would like to express, sometimes just to put the idea down,
but I lack of time. It would be great if I can find a tool that will allow me to just to but I lack of time. It would be great if I can find a tool that will allow me to just to
dictate my words. I know that there are plenty out there, I need to spend some time to test dictate my words. I know that there are plenty out there, I need to spend some time to test
them and choose one. them and choose one.
2019-11-15
**********
Managing concepts resolutions
"""""""""""""""""""""""""""""
I am a little stuck on the algorithm I must use to derive (resolve) concepts. This is
one of this day I strongly regret to have someone I can discuss with :-(
Let's write the problem down, sometimes, it helps figure out the best approach.
::
def concept one as 1
one
The concept is first define (it returns the number 1), and then it's called.
During the call
1. During parsing,
Both Python parser and concept parser will recognize 'one'
2. During Evaluation,
* Python Evaluator will fail (one is not know by python)
* Concept Evaluator will success. My question is what should it return ?
The two option are:
1. Python node, to let the Python Evaluator work and return one, in the next row
2. Returns '1' directly
I as write it down, it is obvious that it must return 1, since the purpose of any
evaluation is to give a result, not the path to find the result.
Plus, if don"t resolve the body in the Concept Evaluator, I will loose where the
'1' comes from.
I don't know if I was clear. I don't even know if I will be able to re-read myself.
But I think that I have my solution.
2019-11-16
**********
ExactConceptParser limitation
"""""""""""""""""""""""""""""
From the beginning, my simplest example is to show that addition can be simply
explained to Sheerka
::
def concept a plus b as a + b
def concept one as 1
def concept two as 2
one plus two
The :code:`one plus two` is perfectly recognized, and the result is 3.
:code:`two plus one` also work (with the correct response).
But I was quite surprised to see that :code:`one plus one` was not recognized !!
Indeed, the **ExactConceptParser** looks for :code:`__var0__ plus __var1__`. So
the first operand and the second have to be different.
It's unexpected :-(
Do I need to enhance the parser to recognize it, or no I need to build another parser ?
If I tell the parser that :code:`a plus b`, how do I handle the cases where 'a 'and 'b'
MUST be different ? How I handle when the explicitly have to be the same ?
I seems that the purpose of the **ExactConceptParser** is to find exact match.
I need another way to express that 'a' and 'b' can be the same.
+30 -8
View File
@@ -1,4 +1,4 @@
from core.builtin_concepts import ParserResultConcept from core.builtin_concepts import ParserResultConcept, BuiltinConcepts
from core.concept import Concept, ConceptParts from core.concept import Concept, ConceptParts
from evaluators.BaseEvaluator import OneReturnValueEvaluator from evaluators.BaseEvaluator import OneReturnValueEvaluator
import logging import logging
@@ -9,6 +9,8 @@ log = logging.getLogger(__name__)
class ConceptEvaluator(OneReturnValueEvaluator): class ConceptEvaluator(OneReturnValueEvaluator):
evaluation_steps = [BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION]
def __init__(self): def __init__(self):
super().__init__("Concept Evaluator", 50) super().__init__("Concept Evaluator", 50)
@@ -31,12 +33,32 @@ class ConceptEvaluator(OneReturnValueEvaluator):
# TODO; check pre # TODO; check pre
# if pre is not true, return Concept with a false value # if pre is not true, return Concept with a false value
if ConceptParts.BODY in concept.codes: # Evaluate the properties
body = concept.codes[ConceptParts.BODY] for prop in concept.props:
if body is None: sub_context = context.push(self.name, f"Evaluating property '{prop}'", concept)
return None # nothing to do res = self.evaluate_parsing(sheerka, sub_context, concept.codes[prop])
if res.status:
concept.set_prop(prop, res.value)
else:
return sheerka.ret(
self.name,
False,
sheerka.new(BuiltinConcepts.PROPERTY_EVAL_ERROR, body=prop, concept=concept, error=res.value),
parents=[return_value])
return sheerka.ret(self.name, True, body.value, parents=[return_value]) # Evaluate body
if ConceptParts.BODY not in concept.codes:
else:
return sheerka.ret(self.name, True, concept, parents=[return_value]) return sheerka.ret(self.name, True, concept, parents=[return_value])
body = concept.codes[ConceptParts.BODY]
if body is None:
return None # seems weird
sub_context = context.push(self.name, "Evaluating body", concept)
res = self.evaluate_parsing(sheerka, sub_context, body)
return sheerka.ret(self.name, res.status, res.value, parents=[return_value])
def evaluate_parsing(self, sheerka, context, parsing_result):
res = sheerka.chain_process(context, parsing_result, self.evaluation_steps)
res = sheerka.expect_one(context, res)
return res
+1 -1
View File
@@ -19,7 +19,7 @@ class DuplicateConceptEvaluator(AllReturnValuesEvaluator):
only_parsers = True only_parsers = True
for ret in return_values: for ret in return_values:
if sheerka.isinstance(ret.value, BuiltinConcepts.PARSING): if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_EVALUATION):
if ret.status: if ret.status:
parsing = True parsing = True
elif ret.who == "Evaluators:Add new Concept": elif ret.who == "Evaluators:Add new Concept":
+62
View File
@@ -0,0 +1,62 @@
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept
from evaluators.BaseEvaluator import AllReturnValuesEvaluator, BaseEvaluator
import logging
from parsers.BaseParser import BaseParser
log = logging.getLogger(__name__)
class MultipleSameSuccessEvaluator(AllReturnValuesEvaluator):
"""
Used to filter the responses
It has a low priority to let other evaluators try to resolve the errors
"""
def __init__(self):
super().__init__("Parsers Evaluator", 10)
self.success = []
def matches(self, context, return_values):
sheerka = context.sheerka
after_evaluation = False
nb_successful_evaluators = 0
only_parsers_in_error = True
unlisted = False
for ret in return_values:
if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_EVALUATION):
if ret.status:
after_evaluation = True
elif ret.who.startswith(BaseEvaluator.PREFIX):
if ret.status:
nb_successful_evaluators += 1
self.success.append(ret.value)
elif ret.who.startswith(BaseParser.PREFIX):
if ret.status:
only_parsers_in_error = False
else:
unlisted = True
return after_evaluation and nb_successful_evaluators > 1 and only_parsers_in_error and not unlisted
def eval(self, context, return_values):
reference = self.get_value(self.success[0])
for return_value in self.success[1:]:
actual = self.get_value(return_value)
if actual != reference:
return None
sheerka = context.sheerka
return sheerka.ret(self.name, True, reference, parents=return_values)
@staticmethod
def get_value(obj):
if not isinstance(obj, Concept):
return obj
return obj if obj.body is None else obj.body
@@ -1,5 +1,4 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept
from evaluators.BaseEvaluator import AllReturnValuesEvaluator from evaluators.BaseEvaluator import AllReturnValuesEvaluator
import logging import logging
@@ -8,7 +7,7 @@ from parsers.BaseParser import BaseParser
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ParsersEvaluator(AllReturnValuesEvaluator): class OneSuccessEvaluator(AllReturnValuesEvaluator):
""" """
Used to filter the responses Used to filter the responses
It has a low priority to let other evaluators try to resolve the errors It has a low priority to let other evaluators try to resolve the errors
@@ -20,13 +19,13 @@ class ParsersEvaluator(AllReturnValuesEvaluator):
def matches(self, context, return_values): def matches(self, context, return_values):
sheerka = context.sheerka sheerka = context.sheerka
after_parsing = False after_evaluation = False
nb_successful_evaluators = 0 nb_successful_evaluators = 0
only_parsers = True only_parsers = True
for ret in return_values: for ret in return_values:
if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_PARSING): if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_EVALUATION):
if ret.status: if ret.status:
after_parsing = True after_evaluation = True
elif ret.who.startswith(self.PREFIX): elif ret.who.startswith(self.PREFIX):
if ret.status: if ret.status:
nb_successful_evaluators += 1 nb_successful_evaluators += 1
@@ -35,7 +34,7 @@ class ParsersEvaluator(AllReturnValuesEvaluator):
if not ret.who.startswith(BaseParser.PREFIX): if not ret.who.startswith(BaseParser.PREFIX):
only_parsers = False only_parsers = False
return after_parsing and nb_successful_evaluators == 1 and only_parsers return after_evaluation and nb_successful_evaluators == 1 and only_parsers
def eval(self, context, return_values): def eval(self, context, return_values):
sheerka = context.sheerka sheerka = context.sheerka
+11 -3
View File
@@ -13,8 +13,8 @@ class PythonEvaluator(OneReturnValueEvaluator):
def matches(self, context, return_value): def matches(self, context, return_value):
return return_value.status and \ return return_value.status and \
isinstance(return_value.value, ParserResultConcept) and \ isinstance(return_value.value, ParserResultConcept) and \
isinstance(return_value.value.value, PythonNode) isinstance(return_value.value.value, PythonNode)
def eval(self, context, return_value): def eval(self, context, return_value):
sheerka = context.sheerka sheerka = context.sheerka
@@ -23,10 +23,18 @@ class PythonEvaluator(OneReturnValueEvaluator):
try: try:
log.debug(f"Evaluating python node {node}") log.debug(f"Evaluating python node {node}")
compiled = compile(node.ast_, "<string>", "eval") compiled = compile(node.ast_, "<string>", "eval")
evaluated = eval(compiled, {}, {"sheerka": context.sheerka}) evaluated = eval(compiled, {}, self.get_locals(context))
return sheerka.ret(self.name, True, evaluated, parents=[return_value]) return sheerka.ret(self.name, True, evaluated, parents=[return_value])
except Exception as error: except Exception as error:
error = sheerka.new(BuiltinConcepts.ERROR, body=error) error = sheerka.new(BuiltinConcepts.ERROR, body=error)
return sheerka.ret(self.name, False, error, parents=[return_value]) return sheerka.ret(self.name, False, error, parents=[return_value])
else: else:
return sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.ERROR), parents=[return_value]) return sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.ERROR), parents=[return_value])
@staticmethod
def get_locals(context):
my_locals = {"sheerka": context.sheerka}
if context.obj:
for prop_name, prop_value in context.obj.props.items():
my_locals[prop_name] = prop_value.value
return my_locals
+217
View File
@@ -0,0 +1,217 @@
import os
import shutil
from os import path
import pytest
from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts
from core.concept import Concept
from core.sheerka import Sheerka, ExecutionContext
from evaluators.ConceptEvaluator import ConceptEvaluator
from parsers.BaseParser import BaseParser
tests_root = path.abspath("../build/tests")
root_folder = "init_folder"
@pytest.fixture(autouse=True)
def init_test():
if path.exists(tests_root):
shutil.rmtree(tests_root)
if not path.exists(tests_root):
os.makedirs(tests_root)
current_pwd = os.getcwd()
os.chdir(tests_root)
yield None
os.chdir(current_pwd)
def get_context():
sheerka = Sheerka()
sheerka.initialize(root_folder)
return ExecutionContext("test", "xxx", sheerka)
@pytest.mark.parametrize("ret_val, expected", [
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, Concept()), True),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", False, Concept()), False),
(ReturnValueConcept("Not a parser", True, Concept()), False),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, "not a concept"), False),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept()), False),
])
def test_i_can_match(ret_val, expected):
context = get_context()
assert ConceptEvaluator().matches(context, ret_val) == expected
def test_concept_is_returned_when_no_body():
context = get_context()
concept = Concept(name="one").init_key()
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept)
result = evaluator.eval(context, item)
assert result.who == evaluator.name
assert result.status
assert result.value == concept
assert result.parents == [item]
def test_body_is_evaluated_when_python_body():
context = get_context()
concept = Concept(name="one", body="1").init_key()
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept)
result = evaluator.eval(context, item)
assert result.who == evaluator.name
assert result.status
assert result.value == 1
assert result.parents == [item]
def test_body_is_evaluated_when_concept_body():
context = get_context()
concept_one = Concept(name="one").init_key()
context.sheerka.add_in_cache(concept_one)
concept_un = Concept(name="un", body="one").init_key()
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_un)
result = evaluator.eval(context, item)
assert result.who == evaluator.name
assert result.status
assert result.value == concept_one
assert result.parents == [item]
def test_body_is_evaluated_when_concept_body_with_a_body():
context = get_context()
concept_one = Concept(name="one", body="1").init_key()
context.sheerka.add_in_cache(concept_one)
concept_un = Concept(name="un", body="one").init_key()
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_un)
result = evaluator.eval(context, item)
assert result.who == evaluator.name
assert result.status
assert result.value == 1
assert result.parents == [item]
def test_i_can_evaluate_longer_chains():
context = get_context()
context.sheerka.add_in_cache(Concept(name="a", body="'a'").init_key())
context.sheerka.add_in_cache(Concept(name="b", body="a").init_key())
context.sheerka.add_in_cache(Concept(name="c", body="b").init_key())
concept_d = context.sheerka.add_in_cache(Concept(name="d", body="c").init_key())
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_d)
result = evaluator.eval(context, item)
assert result.status
assert result.value == 'a'
def test_i_can_evaluate_longer_chains_2():
context = get_context()
concept_a = context.sheerka.add_in_cache(Concept(name="a").init_key())
context.sheerka.add_in_cache(Concept(name="b", body="a").init_key())
context.sheerka.add_in_cache(Concept(name="c", body="b").init_key())
concept_d = context.sheerka.add_in_cache(Concept(name="d", body="c").init_key())
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_d)
result = evaluator.eval(context, item)
assert result.status
assert result.value == concept_a
def test_i_can_recognize_concept_properties():
"""
The concept 'plus' has some properties.
Let's check if they are recognized as concepts
:return:
"""
context = get_context()
concept_one = context.sheerka.add_in_cache(Concept(name="one").init_key())
concept_two = context.sheerka.add_in_cache(Concept(name="two").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_plus)
result = evaluator.eval(context, item)
assert result.status
assert context.sheerka.isinstance(result.value, concept_plus)
assert result.value.props["a"].value == concept_one
assert result.value.props["b"].value == concept_two
def test_i_can_recognize_concept_properties_with_body():
"""
The concept 'plus' has some properties.
Let's check if they are recognized as concepts with Python code
:return:
"""
context = get_context()
context.sheerka.add_in_cache(Concept(name="one", body="1").init_key())
context.sheerka.add_in_cache(Concept(name="two", body="2").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_plus)
result = evaluator.eval(context, item)
assert result.status
assert context.sheerka.isinstance(result.value, concept_plus)
assert result.value.props["a"].value == 1
assert result.value.props["b"].value == 2
def test_i_can_recognize_concept_properties_with_body_when_concept_has_a_body():
context = get_context()
context.sheerka.add_in_cache(Concept(name="one", body="1").init_key())
context.sheerka.add_in_cache(Concept(name="two", body="2").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b", body="a + b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_plus)
result = evaluator.eval(context, item)
assert result.status
assert result.value == 3
def test_i_cannot_recognize_a_concept_if_one_of_the_prop_is_unknown():
context = get_context()
context.sheerka.add_in_cache(Concept(name="one").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = ReturnValueConcept(BaseParser.PREFIX + "some_name", True, concept_plus)
result = evaluator.eval(context, item)
assert not result.status
assert context.sheerka.isinstance(result.value, BuiltinConcepts.PROPERTY_EVAL_ERROR)
assert result.value.property_name == "b"
assert context.sheerka.isinstance(result.value.error, BuiltinConcepts.TOO_MANY_ERRORS)
assert result.value.concept == concept_plus
+223
View File
@@ -0,0 +1,223 @@
import os
import shutil
from os import path
import pytest
from core.builtin_concepts import ReturnValueConcept, BuiltinConcepts
from core.concept import Concept
from core.sheerka import Sheerka, ExecutionContext
from evaluators.BaseEvaluator import BaseEvaluator
from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator
from parsers.BaseParser import BaseParser
tests_root = path.abspath("../build/tests")
root_folder = "init_folder"
@pytest.fixture(autouse=True)
def init_test():
if path.exists(tests_root):
shutil.rmtree(tests_root)
if not path.exists(tests_root):
os.makedirs(tests_root)
current_pwd = os.getcwd()
os.chdir(tests_root)
yield None
os.chdir(current_pwd)
def get_context():
sheerka = Sheerka()
sheerka.initialize(root_folder)
return ExecutionContext("test", "xxx", sheerka)
def test_i_can_match_and_eval():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
evaluator = MultipleSameSuccessEvaluator()
assert evaluator.matches(context, return_values)
evaluated = evaluator.eval(context, return_values)
assert evaluated.status
assert evaluated.value == "value"
def test_i_can_match_and_eval_when_no_body():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
evaluator = MultipleSameSuccessEvaluator()
assert evaluator.matches(context, return_values)
evaluated = evaluator.eval(context, return_values)
assert evaluated.status
assert evaluated.value == Concept(name="1")
def test_i_can_match_and_eval_when_value_is_not_a_concept():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, "value"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, "value"),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
evaluator = MultipleSameSuccessEvaluator()
assert evaluator.matches(context, return_values)
evaluated = evaluator.eval(context, return_values)
assert evaluated.status
assert evaluated.value == "value"
def test_i_can_match_even_if_the_value_are_not_the_same_but_eval_will_fail():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value2")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
evaluator = MultipleSameSuccessEvaluator()
assert evaluator.matches(context, return_values)
assert evaluator.eval(context, return_values) is None
def test_i_can_match_even_if_the_value_are_not_the_same_but_eval_will_fail_when_no_body():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
evaluator = MultipleSameSuccessEvaluator()
assert evaluator.matches(context, return_values)
assert evaluator.eval(context, return_values) is None
def test_i_can_match_if_no_parser():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
assert MultipleSameSuccessEvaluator().matches(context, return_values)
def test_i_cannot_match_if_not_after_evaluation():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, Concept(name="2", body="value")),
# ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
assert not MultipleSameSuccessEvaluator().matches(context, return_values)
def test_i_cannot_match_if_only_one_successful_evaluator():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
# ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
assert not MultipleSameSuccessEvaluator().matches(context, return_values)
def test_i_cannot_match_if_at_least_one_parser_is_successful():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", True, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION))
]
assert not MultipleSameSuccessEvaluator().matches(context, return_values)
def test_i_cannot_match_if_i_have_unlisted_return_value_in_success():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION)),
ReturnValueConcept("some_name", True, "not relevant"),
]
assert not MultipleSameSuccessEvaluator().matches(context, return_values)
def test_i_cannot_match_if_i_have_unlisted_return_value_in_error():
context = get_context()
sheerka = context.sheerka
return_values = [
ReturnValueConcept(BaseParser.PREFIX + "some_name", False, "Not relevant"),
ReturnValueConcept(BaseParser.PREFIX + "some_name2", False, "Not relevant"),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="1", body="value")),
ReturnValueConcept(BaseEvaluator.PREFIX + "some_name", True, Concept(name="2", body="value")),
ReturnValueConcept("some_name", True, sheerka.new(BuiltinConcepts.AFTER_EVALUATION)),
ReturnValueConcept("some_name", False, "not relevant"),
]
assert not MultipleSameSuccessEvaluator().matches(context, return_values)
+83 -4
View File
@@ -341,7 +341,7 @@ def test_i_can_use_expect_one_when_not_a_list_false():
("1 + 1", 2), ("1 + 1", 2),
("sheerka.test()", 'I have access to Sheerka !') ("sheerka.test()", 'I have access to Sheerka !')
]) ])
def test_i_can_eval_simple_python_expressions(text, expected): def test_i_can_eval_python_expressions_with_no_variable(text, expected):
sheerka = get_sheerka() sheerka = get_sheerka()
res = sheerka.eval(text) res = sheerka.eval(text)
@@ -351,9 +351,9 @@ def test_i_can_eval_simple_python_expressions(text, expected):
assert res[0].value == expected assert res[0].value == expected
def test_i_can_eval_simple_concept(): def test_i_can_eval_concept_with_python_body():
sheerka = get_sheerka() sheerka = get_sheerka()
concept = Concept(name="one", body="1").init_key() concept = Concept(name="one", body="1")
sheerka.add_in_cache(concept) sheerka.add_in_cache(concept)
text = "one" text = "one"
@@ -363,6 +363,46 @@ def test_i_can_eval_simple_concept():
assert res[0].value == 1 assert res[0].value == 1
def test_i_can_eval_concept_with_concept_body():
sheerka = get_sheerka()
concept_one = Concept(name="one")
concept_un = Concept(name="un", body="one")
sheerka.add_in_cache(concept_one)
sheerka.add_in_cache(concept_un)
res = sheerka.eval("un")
return_value = res[0].value
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(return_value, concept_one)
def test_i_can_eval_concept_with_no_body():
sheerka = get_sheerka()
concept = Concept(name="one")
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.eval(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == concept
assert id(res[0].value) != id(concept)
def test_is_unique_property_is_used_when_evaluating():
sheerka = get_sheerka()
concept = Concept(name="one", is_unique=True)
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.eval(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == concept
assert id(res[0].value) == id(concept)
def test_i_can_eval_def_concept_request(): def test_i_can_eval_def_concept_request():
text = """ text = """
def concept a + b def concept a + b
@@ -405,7 +445,7 @@ def test_i_can_eval_def_concept_part_when_one_part_is_a_ref_of_another_concept()
sheerka = get_sheerka() sheerka = get_sheerka()
# concept 'a plus b' is known # concept 'a plus b' is known
concept_a_plus_b = Concept(name="a plus b").set_prop("a").set_prop("b").init_key() concept_a_plus_b = Concept(name="a plus b").set_prop("a").set_prop("b")
sheerka.add_in_cache(concept_a_plus_b) sheerka.add_in_cache(concept_a_plus_b)
res = sheerka.eval("def concept a xx b as a plus b") res = sheerka.eval("def concept a xx b as a plus b")
@@ -461,6 +501,45 @@ def test_i_can_eval_a_empty_input(text):
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NOP) assert sheerka.isinstance(res[0].value, BuiltinConcepts.NOP)
def test_i_can_eval_concept_with_variable():
sheerka = get_sheerka()
concept_hello = Concept(name="hello a").set_prop("a")
concept_foo = Concept(name="foo")
sheerka.add_in_cache(concept_hello)
sheerka.add_in_cache(concept_foo)
res = sheerka.eval("hello foo")
return_value = res[0].value
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(return_value, concept_hello)
assert return_value.props["a"].value == concept_foo
def test_i_can_eval_concept_with_variable_and_python_as_body():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
res = sheerka.eval("hello foo")
assert len(res) == 1
assert res[0].status
assert res[0].value, "hello foo"
def test_i_can_eval_duplicate_concepts_with_same_value():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
res = sheerka.eval("hello foo")
assert len(res) == 1
assert res[0].status
assert res[0].value, "hello foo"
def get_sheerka(): def get_sheerka():
sheerka = Sheerka() sheerka = Sheerka()
sheerka.initialize(root_folder) sheerka.initialize(root_folder)