Added concepts with the same key handling
This commit is contained in:
@@ -24,12 +24,13 @@ class BuiltinConcepts(Enum):
|
||||
BEFORE_PARSING = 15 # activated before evaluation by the parsers
|
||||
PARSING = 16 # activated during the parsing. It contains the text to parse
|
||||
AFTER_PARSING = 17 # after parsing
|
||||
BEFORE_EVALUATION = 18 # before evalution
|
||||
BEFORE_EVALUATION = 18 # before evaluation
|
||||
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
|
||||
PROPERTY_EVAL_ERROR = 23 # cannot evaluate a property of a concept
|
||||
ENUMERATION = 24 # represents a list or a set
|
||||
|
||||
|
||||
"""
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ class Concept:
|
||||
A concept is a the base object of our universe
|
||||
Everything is a concept
|
||||
"""
|
||||
props_to_serialize = ("id", "is_builtin", "key", "name", "where", "pre", "post", "body", "desc", "obj")
|
||||
props_for_digest = ("is_builtin", "key", "name", "where", "pre", "post", "body", "desc")
|
||||
props_for_digest = ("is_builtin", "is_unique", "key", "name", "where", "pre", "post", "body", "desc")
|
||||
props_to_serialize = ("id", "is_builtin", "is_unique", "key", "name", "where", "pre", "post", "body", "desc")
|
||||
concept_parts = set(item.value for item in ConceptParts)
|
||||
|
||||
PROPERTY_PREFIX = "__var__"
|
||||
|
||||
+60
-24
@@ -46,6 +46,9 @@ class Sheerka(Concept):
|
||||
self.parsers = []
|
||||
self.evaluators = []
|
||||
|
||||
self.evaluators_prefix = None
|
||||
self.parsers_prefix = None
|
||||
|
||||
self.debug = debug
|
||||
|
||||
def initialize(self, root_folder=None):
|
||||
@@ -327,7 +330,7 @@ class Sheerka(Concept):
|
||||
return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0])
|
||||
|
||||
# add in cache for quick further reference
|
||||
self.concepts_cache[concept.key] = concept
|
||||
self.concepts_cache[concept.key] = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key)
|
||||
|
||||
# process the return in needed
|
||||
ret = self.ret(self.create_new_concept.__name__, True, self.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||
@@ -415,25 +418,12 @@ class Sheerka(Concept):
|
||||
concept_key != BuiltinConcepts.UNKNOWN_CONCEPT:
|
||||
return template
|
||||
|
||||
# manage singleton
|
||||
if template.is_unique:
|
||||
return template
|
||||
if not isinstance(template, list):
|
||||
return self._new_from_template(template, concept_key, **kwargs)
|
||||
|
||||
# otherwise, create another instance
|
||||
concept = self.builtin_cache[concept_key]() if concept_key in self.builtin_cache else Concept()
|
||||
concept.update_from(template)
|
||||
|
||||
# update the properties
|
||||
for k, v in kwargs.items():
|
||||
if k in concept.props:
|
||||
concept.set_prop(k, v)
|
||||
elif hasattr(concept, k):
|
||||
setattr(concept, k, v)
|
||||
else:
|
||||
return self.new(BuiltinConcepts.UNKNOWN_PROPERTY, body=k, concept=concept)
|
||||
|
||||
# TODO : add the concept to the list of known concepts (self.instances)
|
||||
return concept
|
||||
# if template is a list, it means that there a multiple concepts under the same key
|
||||
concepts = [self._new_from_template(t, concept_key, **kwargs) for t in template]
|
||||
return self.new(BuiltinConcepts.ENUMERATION, body=concepts)
|
||||
|
||||
def ret(self, who, status, value, message=None, parents=None):
|
||||
"""
|
||||
@@ -473,6 +463,52 @@ class Sheerka(Concept):
|
||||
# for example, if a is a color, it will be found the entry 'All_Colors'
|
||||
return a.key == b_key
|
||||
|
||||
def get_evaluator_name(self, name):
|
||||
if self.evaluators_prefix is None:
|
||||
base_evaluator_class = core.utils.get_class("evaluators.BaseEvaluator.BaseEvaluator")
|
||||
self.evaluators_prefix = base_evaluator_class.PREFIX
|
||||
|
||||
return self.evaluators_prefix + name
|
||||
|
||||
def get_parser_name(self, name):
|
||||
if self.parsers_prefix is None:
|
||||
base_parser_class = core.utils.get_class("parsers.BaseParser.BaseParser")
|
||||
self.parsers_prefix = base_parser_class.PREFIX
|
||||
|
||||
return self.parsers_prefix + name
|
||||
|
||||
def concepts(self):
|
||||
res = []
|
||||
lst = self.sdp.list(self.CONCEPTS_ENTRY)
|
||||
for item in lst:
|
||||
if isinstance(item, list):
|
||||
res.extend(item)
|
||||
else:
|
||||
res.append(item)
|
||||
|
||||
return sorted(res, key=lambda i: i.key)
|
||||
|
||||
def _new_from_template(self, template, concept_key, **kwargs):
|
||||
# manage singleton
|
||||
if template.is_unique:
|
||||
return template
|
||||
|
||||
# otherwise, create another instance
|
||||
concept = self.builtin_cache[concept_key]() if concept_key in self.builtin_cache else Concept()
|
||||
concept.update_from(template)
|
||||
|
||||
# update the properties
|
||||
for k, v in kwargs.items():
|
||||
if k in concept.props:
|
||||
concept.set_prop(k, v)
|
||||
elif hasattr(concept, k):
|
||||
setattr(concept, k, v)
|
||||
else:
|
||||
return self.new(BuiltinConcepts.UNKNOWN_PROPERTY, body=k, concept=concept)
|
||||
|
||||
# TODO : add the concept to the list of known concepts (self.instances)
|
||||
return concept
|
||||
|
||||
@staticmethod
|
||||
def get_builtins_classes_as_dict():
|
||||
res = {}
|
||||
@@ -505,11 +541,11 @@ class ExecutionContext:
|
||||
"""
|
||||
To keep track of the execution of a request
|
||||
"""
|
||||
who: object # who is asking
|
||||
event_digest: str # what was the (original) trigger
|
||||
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)
|
||||
who: object # who is asking
|
||||
event_digest: str # what was the (original) trigger
|
||||
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, desc=None, obj=None):
|
||||
return ExecutionContext(who, self.event_digest, self.sheerka, desc=desc, obj=obj)
|
||||
|
||||
@@ -132,3 +132,5 @@ def remove_from_list(lst, to_remove):
|
||||
lst.remove(item)
|
||||
|
||||
return lst
|
||||
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
Used to add a new concept
|
||||
"""
|
||||
NAME = "AddNewConcept"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Add new Concept", 50)
|
||||
super().__init__(self.NAME, 50)
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
|
||||
@@ -9,10 +9,11 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConceptEvaluator(OneReturnValueEvaluator):
|
||||
NAME = "Concept"
|
||||
evaluation_steps = [BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Concept Evaluator", 50)
|
||||
super().__init__(self.NAME, 50)
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from evaluators.AddConceptEvaluator import AddConceptEvaluator
|
||||
from evaluators.BaseEvaluator import AllReturnValuesEvaluator
|
||||
from parsers.BaseParser import BaseParser
|
||||
|
||||
@@ -8,8 +9,10 @@ class DuplicateConceptEvaluator(AllReturnValuesEvaluator):
|
||||
Use to recognize when we tried to add the same concept twice
|
||||
"""
|
||||
|
||||
NAME = "DuplicateConcept"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Duplicate Concept Evaluator", 10)
|
||||
super().__init__(self.NAME, 10)
|
||||
self.already_defined = None
|
||||
|
||||
def matches(self, context, return_values):
|
||||
@@ -22,7 +25,7 @@ class DuplicateConceptEvaluator(AllReturnValuesEvaluator):
|
||||
if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_EVALUATION):
|
||||
if ret.status:
|
||||
parsing = True
|
||||
elif ret.who == "Evaluators:Add new Concept":
|
||||
elif ret.who == sheerka.get_evaluator_name(AddConceptEvaluator.NAME):
|
||||
if not ret.status and ret.value.body.args[0] == "Duplicate object.":
|
||||
add_concept_in_error = True
|
||||
self.already_defined = ret.value.body.obj
|
||||
|
||||
@@ -14,8 +14,10 @@ class MultipleSameSuccessEvaluator(AllReturnValuesEvaluator):
|
||||
It has a low priority to let other evaluators try to resolve the errors
|
||||
"""
|
||||
|
||||
NAME = "MultipleSameSuccess"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Parsers Evaluator", 10)
|
||||
super().__init__(self.NAME, 10)
|
||||
self.success = []
|
||||
|
||||
def matches(self, context, return_values):
|
||||
|
||||
@@ -13,8 +13,10 @@ class OneSuccessEvaluator(AllReturnValuesEvaluator):
|
||||
It has a low priority to let other evaluators try to resolve the errors
|
||||
"""
|
||||
|
||||
NAME = "OneSuccess"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Parsers Evaluator", 10)
|
||||
super().__init__(self.NAME, 10)
|
||||
self.successful_return_value = None
|
||||
|
||||
def matches(self, context, return_values):
|
||||
|
||||
@@ -8,8 +8,11 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PythonEvaluator(OneReturnValueEvaluator):
|
||||
|
||||
NAME = "Python"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Python Evaluator", 50)
|
||||
super().__init__(self.NAME, 50)
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
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 TooManySuccessEvaluator(AllReturnValuesEvaluator):
|
||||
"""
|
||||
Used to filter the responses
|
||||
It has a low priority to let other evaluators try to resolve the errors
|
||||
"""
|
||||
|
||||
NAME = "TooManySuccess"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, 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)
|
||||
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].value)
|
||||
|
||||
for return_value in self.success[1:]:
|
||||
actual = self.get_value(return_value.value)
|
||||
if actual != reference:
|
||||
sheerka = context.sheerka
|
||||
too_many_success = sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS, obj=self.success)
|
||||
return sheerka.ret(self.name, False, too_many_success, parents=return_values)
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_value(obj):
|
||||
if not isinstance(obj, Concept):
|
||||
return obj
|
||||
|
||||
return obj if obj.body is None else obj.body
|
||||
@@ -32,14 +32,16 @@ class ExactConceptParser(BaseParser):
|
||||
|
||||
recognized = False
|
||||
for combination in self.combinations(words):
|
||||
concept_key = " ".join(combination)
|
||||
|
||||
# Very important question to think about later
|
||||
# Must we return a new instance or the existing one
|
||||
# That will depend on the context
|
||||
# Let's return a new one for now and see if it works
|
||||
concept = sheerka.new(concept_key)
|
||||
if not sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
|
||||
concept_key = " ".join(combination)
|
||||
result = sheerka.new(concept_key)
|
||||
|
||||
if sheerka.isinstance(result, BuiltinConcepts.UNKNOWN_CONCEPT):
|
||||
continue
|
||||
|
||||
concepts = result.body if sheerka.isinstance(result, BuiltinConcepts.ENUMERATION) else [result]
|
||||
|
||||
for concept in concepts:
|
||||
# update the properties if needed
|
||||
for i, token in enumerate(combination):
|
||||
if token.startswith(Concept.PROPERTY_PREFIX):
|
||||
|
||||
@@ -576,7 +576,11 @@ class SheerkaDataProvider:
|
||||
if key is not None and key not in state.data[entry]:
|
||||
return None
|
||||
|
||||
return self.load_ref_if_needed(state.data[entry] if key is None else state.data[entry][key])[0]
|
||||
item = state.data[entry] if key is None else state.data[entry][key]
|
||||
if isinstance(item, list):
|
||||
return [self.load_ref_if_needed(i)[0] for i in item]
|
||||
|
||||
return self.load_ref_if_needed(item)[0]
|
||||
|
||||
def exists(self, entry, key=None, digest=None):
|
||||
"""
|
||||
|
||||
@@ -9,6 +9,7 @@ from core import utils
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
from core.concept import Concept, ConceptParts
|
||||
from core.sheerka import Sheerka, ExecutionContext
|
||||
from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator
|
||||
from parsers.DefaultParser import DefaultParser
|
||||
from parsers.PythonParser import PythonParser
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, SheerkaDataProviderDuplicateKeyError
|
||||
@@ -158,6 +159,44 @@ def test_i_can_get_a_known_concept_when_not_in_cache():
|
||||
assert loaded == concept
|
||||
|
||||
|
||||
def test_i_can_get_list_of_concept_when_same_key_when_no_cache():
|
||||
sheerka = get_sheerka()
|
||||
concept1 = get_default_concept()
|
||||
concept2 = get_default_concept()
|
||||
concept2.body = "a+b"
|
||||
|
||||
res1 = sheerka.create_new_concept(get_context(sheerka), concept1)
|
||||
res2 = sheerka.create_new_concept(get_context(sheerka), concept2)
|
||||
|
||||
assert res1.value.body.key == res2.value.body.key # same key
|
||||
|
||||
sheerka.concepts_cache = {} # reset the cache
|
||||
|
||||
from_cache = sheerka.get(concept1.key)
|
||||
assert len(from_cache) == 2
|
||||
assert from_cache[0] == concept1
|
||||
assert from_cache[1] == concept2
|
||||
|
||||
|
||||
def test_i_can_get_list_of_concept_when_same_key_when_cache():
|
||||
sheerka = get_sheerka()
|
||||
concept1 = get_default_concept()
|
||||
concept2 = get_default_concept()
|
||||
concept2.body = "a+b"
|
||||
|
||||
res1 = sheerka.create_new_concept(get_context(sheerka), concept1)
|
||||
res2 = sheerka.create_new_concept(get_context(sheerka), concept2)
|
||||
|
||||
assert res1.value.body.key == res2.value.body.key # same key
|
||||
|
||||
# sheerka.concepts_cache = {} # Do not reset the cache
|
||||
|
||||
from_cache = sheerka.get(concept1.key)
|
||||
assert len(from_cache) == 2
|
||||
assert from_cache[0] == concept1
|
||||
assert from_cache[1] == concept2
|
||||
|
||||
|
||||
def test_unknown_concept_is_return_when_the_concept_is_not_found():
|
||||
sheerka = get_sheerka()
|
||||
|
||||
@@ -538,6 +577,40 @@ def test_i_can_eval_duplicate_concepts_with_same_value():
|
||||
assert len(res) == 1
|
||||
assert res[0].status
|
||||
assert res[0].value, "hello foo"
|
||||
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
|
||||
|
||||
|
||||
def test_i_cannot_manage_duplicate_concepts_when_the_values_are_different():
|
||||
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="'another value'"))
|
||||
|
||||
res = sheerka.eval("hello foo")
|
||||
assert len(res) == 1
|
||||
assert not res[0].status
|
||||
assert sheerka.isinstance(res[0].value, BuiltinConcepts.TOO_MANY_SUCCESS)
|
||||
|
||||
concepts = res[0].value.obj
|
||||
assert len(concepts) == 2
|
||||
sorted_values = sorted(concepts, key=lambda x: x.value)
|
||||
assert sorted_values[0].value == "hello another value"
|
||||
assert sorted_values[1].value == "hello foo"
|
||||
|
||||
|
||||
def test_i_can_manage_concepts_with_the_same_key_when_values_are_the_same():
|
||||
sheerka = get_sheerka()
|
||||
context = get_context(sheerka)
|
||||
|
||||
sheerka.create_new_concept(context, Concept(name="hello a", body="'hello ' + a").set_prop("a"))
|
||||
sheerka.create_new_concept(context, Concept(name="hello b", body="'hello ' + b").set_prop("b"))
|
||||
|
||||
res = sheerka.eval("hello 'foo'")
|
||||
assert len(res) == 1
|
||||
assert res[0].status
|
||||
assert res[0].value, "hello foo"
|
||||
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
|
||||
|
||||
|
||||
def get_sheerka():
|
||||
|
||||
@@ -1316,3 +1316,20 @@ def test_i_can_save_and_load_object_ref_with_history():
|
||||
state = sdp.load_state(sdp.get_snapshot())
|
||||
assert state.data == {"entry": {
|
||||
"my_key": '##REF##:e6bf5b56428cfce0f08c94f2c3625dc3b3a8180d7229eaa9f8aa967fb16e5256'}}
|
||||
|
||||
|
||||
def test_i_can_add_obj_with_same_key_and_get_them_back():
|
||||
sdp = SheerkaDataProvider(".sheerka")
|
||||
obj1 = ObjDumpJson("key", "value1")
|
||||
obj2 = ObjDumpJson("key", "value2")
|
||||
sdp.serializer.register(ObjectSerializer(core.utils.get_full_qualified_name(obj1)))
|
||||
|
||||
entry1, key1 = sdp.add(evt_digest, "entry", obj1, use_ref=True)
|
||||
entry2, key2 = sdp.add(evt_digest, "entry", obj2, use_ref=True)
|
||||
|
||||
loaded = sdp.get_safe(entry1, key1)
|
||||
|
||||
assert len(loaded) == 2
|
||||
assert loaded[0] == obj1
|
||||
assert loaded[1] == obj2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user