Added concepts with the same key handling

This commit is contained in:
2019-11-18 17:02:02 +01:00
parent 7fa509555d
commit cb6be9fec7
15 changed files with 255 additions and 43 deletions
+3 -2
View File
@@ -24,12 +24,13 @@ class BuiltinConcepts(Enum):
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 # after parsing 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 EVALUATION = 19 # activated when the parsing process seems to be finished
AFTER_EVALUATION = 20 # 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 CONCEPT_ALREADY_DEFINED = 21 # when you try to add the same concept twice
NOP = 22 # no operation concept. Does nothing 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
View File
@@ -24,8 +24,8 @@ class Concept:
A concept is a the base object of our universe A concept is a the base object of our universe
Everything is a concept Everything is a concept
""" """
props_to_serialize = ("id", "is_builtin", "key", "name", "where", "pre", "post", "body", "desc", "obj") props_for_digest = ("is_builtin", "is_unique", "key", "name", "where", "pre", "post", "body", "desc")
props_for_digest = ("is_builtin", "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) concept_parts = set(item.value for item in ConceptParts)
PROPERTY_PREFIX = "__var__" PROPERTY_PREFIX = "__var__"
+60 -24
View File
@@ -46,6 +46,9 @@ class Sheerka(Concept):
self.parsers = [] self.parsers = []
self.evaluators = [] self.evaluators = []
self.evaluators_prefix = None
self.parsers_prefix = None
self.debug = debug self.debug = debug
def initialize(self, root_folder=None): 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]) return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0])
# add in cache for quick further reference # 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 # process the return in needed
ret = self.ret(self.create_new_concept.__name__, True, self.new(BuiltinConcepts.NEW_CONCEPT, body=concept)) 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: concept_key != BuiltinConcepts.UNKNOWN_CONCEPT:
return template return template
# manage singleton if not isinstance(template, list):
if template.is_unique: return self._new_from_template(template, concept_key, **kwargs)
return template
# otherwise, create another instance # if template is a list, it means that there a multiple concepts under the same key
concept = self.builtin_cache[concept_key]() if concept_key in self.builtin_cache else Concept() concepts = [self._new_from_template(t, concept_key, **kwargs) for t in template]
concept.update_from(template) return self.new(BuiltinConcepts.ENUMERATION, body=concepts)
# 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
def ret(self, who, status, value, message=None, parents=None): 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' # for example, if a is a color, it will be found the entry 'All_Colors'
return a.key == b_key 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 @staticmethod
def get_builtins_classes_as_dict(): def get_builtins_classes_as_dict():
res = {} res = {}
@@ -505,11 +541,11 @@ class ExecutionContext:
""" """
To keep track of the execution of a request To keep track of the execution of a request
""" """
who: object # who is asking who: object # who is asking
event_digest: str # what was the (original) trigger event_digest: str # what was the (original) trigger
sheerka: Sheerka # sheerka sheerka: Sheerka # sheerka
desc: str = None # human description of what is going on desc: str = None # human description of what is going on
obj: Concept = None # what is the subject of the execution context (if known) obj: Concept = None # what is the subject of the execution context (if known)
def push(self, who, desc=None, obj=None): def push(self, who, desc=None, obj=None):
return ExecutionContext(who, self.event_digest, self.sheerka, desc=desc, obj=obj) return ExecutionContext(who, self.event_digest, self.sheerka, desc=desc, obj=obj)
+2
View File
@@ -132,3 +132,5 @@ def remove_from_list(lst, to_remove):
lst.remove(item) lst.remove(item)
return lst return lst
+2 -1
View File
@@ -13,9 +13,10 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
""" """
Used to add a new concept Used to add a new concept
""" """
NAME = "AddNewConcept"
def __init__(self): def __init__(self):
super().__init__("Add new Concept", 50) super().__init__(self.NAME, 50)
def matches(self, context, return_value): def matches(self, context, return_value):
return return_value.status and \ return return_value.status and \
+2 -1
View File
@@ -9,10 +9,11 @@ log = logging.getLogger(__name__)
class ConceptEvaluator(OneReturnValueEvaluator): class ConceptEvaluator(OneReturnValueEvaluator):
NAME = "Concept"
evaluation_steps = [BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION] evaluation_steps = [BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION]
def __init__(self): def __init__(self):
super().__init__("Concept Evaluator", 50) super().__init__(self.NAME, 50)
def matches(self, context, return_value): def matches(self, context, return_value):
return return_value.status and \ return return_value.status and \
+5 -2
View File
@@ -1,4 +1,5 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from evaluators.AddConceptEvaluator import AddConceptEvaluator
from evaluators.BaseEvaluator import AllReturnValuesEvaluator from evaluators.BaseEvaluator import AllReturnValuesEvaluator
from parsers.BaseParser import BaseParser from parsers.BaseParser import BaseParser
@@ -8,8 +9,10 @@ class DuplicateConceptEvaluator(AllReturnValuesEvaluator):
Use to recognize when we tried to add the same concept twice Use to recognize when we tried to add the same concept twice
""" """
NAME = "DuplicateConcept"
def __init__(self): def __init__(self):
super().__init__("Duplicate Concept Evaluator", 10) super().__init__(self.NAME, 10)
self.already_defined = None self.already_defined = None
def matches(self, context, return_values): def matches(self, context, return_values):
@@ -22,7 +25,7 @@ class DuplicateConceptEvaluator(AllReturnValuesEvaluator):
if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_EVALUATION): 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 == sheerka.get_evaluator_name(AddConceptEvaluator.NAME):
if not ret.status and ret.value.body.args[0] == "Duplicate object.": if not ret.status and ret.value.body.args[0] == "Duplicate object.":
add_concept_in_error = True add_concept_in_error = True
self.already_defined = ret.value.body.obj self.already_defined = ret.value.body.obj
+3 -1
View File
@@ -14,8 +14,10 @@ class MultipleSameSuccessEvaluator(AllReturnValuesEvaluator):
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
""" """
NAME = "MultipleSameSuccess"
def __init__(self): def __init__(self):
super().__init__("Parsers Evaluator", 10) super().__init__(self.NAME, 10)
self.success = [] self.success = []
def matches(self, context, return_values): def matches(self, context, return_values):
+3 -1
View File
@@ -13,8 +13,10 @@ class OneSuccessEvaluator(AllReturnValuesEvaluator):
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
""" """
NAME = "OneSuccess"
def __init__(self): def __init__(self):
super().__init__("Parsers Evaluator", 10) super().__init__(self.NAME, 10)
self.successful_return_value = None self.successful_return_value = None
def matches(self, context, return_values): def matches(self, context, return_values):
+4 -1
View File
@@ -8,8 +8,11 @@ log = logging.getLogger(__name__)
class PythonEvaluator(OneReturnValueEvaluator): class PythonEvaluator(OneReturnValueEvaluator):
NAME = "Python"
def __init__(self): def __init__(self):
super().__init__("Python Evaluator", 50) super().__init__(self.NAME, 50)
def matches(self, context, return_value): def matches(self, context, return_value):
return return_value.status and \ return return_value.status and \
+65
View File
@@ -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
+9 -7
View File
@@ -32,14 +32,16 @@ class ExactConceptParser(BaseParser):
recognized = False recognized = False
for combination in self.combinations(words): for combination in self.combinations(words):
concept_key = " ".join(combination)
# Very important question to think about later concept_key = " ".join(combination)
# Must we return a new instance or the existing one result = sheerka.new(concept_key)
# That will depend on the context
# Let's return a new one for now and see if it works if sheerka.isinstance(result, BuiltinConcepts.UNKNOWN_CONCEPT):
concept = sheerka.new(concept_key) continue
if not sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
concepts = result.body if sheerka.isinstance(result, BuiltinConcepts.ENUMERATION) else [result]
for concept in concepts:
# update the properties if needed # update the properties if needed
for i, token in enumerate(combination): for i, token in enumerate(combination):
if token.startswith(Concept.PROPERTY_PREFIX): if token.startswith(Concept.PROPERTY_PREFIX):
+5 -1
View File
@@ -576,7 +576,11 @@ class SheerkaDataProvider:
if key is not None and key not in state.data[entry]: if key is not None and key not in state.data[entry]:
return None 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): def exists(self, entry, key=None, digest=None):
""" """
+73
View File
@@ -9,6 +9,7 @@ from core import utils
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
from core.concept import Concept, ConceptParts from core.concept import Concept, ConceptParts
from core.sheerka import Sheerka, ExecutionContext from core.sheerka import Sheerka, ExecutionContext
from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator
from parsers.DefaultParser import DefaultParser from parsers.DefaultParser import DefaultParser
from parsers.PythonParser import PythonParser from parsers.PythonParser import PythonParser
from sdp.sheerkaDataProvider import SheerkaDataProvider, SheerkaDataProviderDuplicateKeyError 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 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(): def test_unknown_concept_is_return_when_the_concept_is_not_found():
sheerka = get_sheerka() sheerka = get_sheerka()
@@ -538,6 +577,40 @@ def test_i_can_eval_duplicate_concepts_with_same_value():
assert len(res) == 1 assert len(res) == 1
assert res[0].status assert res[0].status
assert res[0].value, "hello foo" 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(): def get_sheerka():
+17
View File
@@ -1316,3 +1316,20 @@ def test_i_can_save_and_load_object_ref_with_history():
state = sdp.load_state(sdp.get_snapshot()) state = sdp.load_state(sdp.get_snapshot())
assert state.data == {"entry": { assert state.data == {"entry": {
"my_key": '##REF##:e6bf5b56428cfce0f08c94f2c3625dc3b3a8180d7229eaa9f8aa967fb16e5256'}} "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