From 95dc147bbd2d4624c59f6717d281f042e42b5126 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Mon, 18 May 2020 22:35:59 +0200 Subject: [PATCH] Improved PyhtonEvaluator in order to use methods that need context --- src/core/sheerka/Sheerka.py | 111 ++++++++++-------- .../services/SheerkaComparisonManager.py | 8 +- .../services/SheerkaCreateNewConcept.py | 2 +- src/core/sheerka/services/SheerkaDump.py | 6 +- .../services/SheerkaEvaluateConcept.py | 2 +- src/core/sheerka/services/SheerkaExecute.py | 2 +- .../sheerka/services/SheerkaHistoryManager.py | 2 +- .../sheerka/services/SheerkaModifyConcept.py | 2 +- .../sheerka/services/SheerkaSetsManager.py | 12 +- .../services/SheerkaVariableManager.py | 6 +- src/evaluators/PythonEvaluator.py | 48 +++++++- tests/core/test_SheerkaComparisonManager.py | 10 ++ tests/evaluators/test_PythonEvaluator.py | 19 +++ tests/non_reg/test_sheerka_non_reg.py | 31 ++++- 14 files changed, 187 insertions(+), 74 deletions(-) diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index 1ba087d..6d85127 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -1,3 +1,4 @@ +import inspect import logging import core.builtin_helpers @@ -76,6 +77,12 @@ class Sheerka(Concept): self.save_execution_context = True + self.methods_with_context = {"test_using_context"} + self.sheerka_methods = { + "test": self.test, + "test_using_context": self.test_using_context + } + @property def resolved_concepts_by_first_keyword(self): """ @@ -96,18 +103,21 @@ class Sheerka(Concept): def concepts_grammars(self): return self.cache_manager.caches[self.CONCEPTS_GRAMMARS_ENTRY].cache - def bind_service_method(self, instance, method, as_name=None): + def bind_service_method(self, bound_method, as_name=None): """ - Bind service method to sheerka instance for ease to use - :param instance: - :param method: + Bind service method to sheerka instance for ease of use ? + :param bound_method: :param as_name: :return: """ if as_name is None: - as_name = method.__name__ + as_name = bound_method.__name__ + + signature = inspect.signature(bound_method) + if len(signature.parameters) > 0 and list(signature.parameters.keys())[0] == "context": + self.methods_with_context.add(as_name) + self.sheerka_methods[as_name] = bound_method - bound_method = method.__get__(instance, instance.__class__) setattr(self, as_name, bound_method) def initialize(self, root_folder: str = None, save_execution_context=True): @@ -198,11 +208,6 @@ class Sheerka(Concept): cache = Cache() self.cache_manager.register_cache(self.CONCEPTS_GRAMMARS_ENTRY, cache, persist=False) - def first_time_initialisation(self, context): - - self.cache_manager.put(self.CONCEPTS_KEYS_ENTRY, self.USER_CONCEPTS_KEYS, 1000) - self.record(context, self.name, "save_execution_context", True) - def initialize_services(self): """ Introspect to find services and bind them @@ -218,6 +223,11 @@ class Sheerka(Concept): instance.initialize() self.services[service.NAME] = instance + def first_time_initialisation(self, context): + + self.cache_manager.put(self.CONCEPTS_KEYS_ENTRY, self.USER_CONCEPTS_KEYS, 1000) + self.record(context, self.name, "save_execution_context", True) + def initialize_builtin_concepts(self): """ Initializes the builtin concepts @@ -653,42 +663,6 @@ class Sheerka(Concept): raise NotImplementedError() - def is_success(self, obj): - if isinstance(obj, bool): # quick win - return obj - - if isinstance(obj, ReturnValueConcept): - return obj.status - - if isinstance(obj, Concept) and obj.metadata.is_builtin and obj.key in BuiltinErrors: - return False - - return obj - - def is_known(self, obj): - if not isinstance(obj, Concept): - return True - - return obj.key != str(BuiltinConcepts.UNKNOWN_CONCEPT) - - def isinstance(self, a, b): - """ - return true if the concept a is an instance of the concept b - :param a: - :param b: - :return: - """ - - if isinstance(a, BuiltinConcepts): # common KSI error ;-) - raise SyntaxError("Remember that the first parameter of isinstance MUST be a concept") - - if not isinstance(a, Concept): - return False - - b_key = b.key if isinstance(b, Concept) else str(b) - - 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") @@ -741,9 +715,52 @@ class Sheerka(Concept): def test(self): return f"I have access to Sheerka !" + def test_using_context(self, context, param1, param2): + event = context.event.get_digest() + return f"I have access to Sheerka ! {param1=}, {param2=}, {event=}." + def test_error(self): raise Exception("I can raise an error") + @staticmethod + def is_success(obj): + if isinstance(obj, bool): # quick win + return obj + + if isinstance(obj, ReturnValueConcept): + return obj.status + + if isinstance(obj, Concept) and obj.metadata.is_builtin and obj.key in BuiltinErrors: + return False + + return obj + + @staticmethod + def is_known(obj): + if not isinstance(obj, Concept): + return True + + return obj.key != str(BuiltinConcepts.UNKNOWN_CONCEPT) + + @staticmethod + def isinstance(a, b): + """ + return true if the concept a is an instance of the concept b + :param a: + :param b: + :return: + """ + + if isinstance(a, BuiltinConcepts): # common KSI error ;-) + raise SyntaxError("Remember that the first parameter of isinstance MUST be a concept") + + if not isinstance(a, Concept): + return False + + b_key = b.key if isinstance(b, Concept) else str(b) + + return a.key == b_key + @staticmethod def _get_unknown(metadata): """ diff --git a/src/core/sheerka/services/SheerkaComparisonManager.py b/src/core/sheerka/services/SheerkaComparisonManager.py index e0d3633..23afe60 100644 --- a/src/core/sheerka/services/SheerkaComparisonManager.py +++ b/src/core/sheerka/services/SheerkaComparisonManager.py @@ -89,10 +89,10 @@ class SheerkaComparisonManager(BaseService): cache = Cache() self.sheerka.cache_manager.register_cache(self.RESOLVED_COMPARISON_ENTRY, cache, persist=False) - self.sheerka.bind_service_method(self, SheerkaComparisonManager.is_greater_than) - self.sheerka.bind_service_method(self, SheerkaComparisonManager.is_less_than) - self.sheerka.bind_service_method(self, SheerkaComparisonManager.get_partition) - self.sheerka.bind_service_method(self, SheerkaComparisonManager.get_concepts_weights) + self.sheerka.bind_service_method(self.is_greater_than) + self.sheerka.bind_service_method(self.is_less_than) + self.sheerka.bind_service_method(self.get_partition) + self.sheerka.bind_service_method(self.get_concepts_weights) def is_greater_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"): """ diff --git a/src/core/sheerka/services/SheerkaCreateNewConcept.py b/src/core/sheerka/services/SheerkaCreateNewConcept.py index ca8d7fa..d841d8b 100644 --- a/src/core/sheerka/services/SheerkaCreateNewConcept.py +++ b/src/core/sheerka/services/SheerkaCreateNewConcept.py @@ -20,7 +20,7 @@ class SheerkaCreateNewConcept(BaseService): self.bnp = core.utils.get_class(BASE_NODE_PARSER_CLASS) # BaseNodeParser def initialize(self): - self.sheerka.bind_service_method(self, SheerkaCreateNewConcept.create_new_concept) + self.sheerka.bind_service_method(self.create_new_concept) def create_new_concept(self, context, concept: Concept): """ diff --git a/src/core/sheerka/services/SheerkaDump.py b/src/core/sheerka/services/SheerkaDump.py index 1da4bf6..c785729 100644 --- a/src/core/sheerka/services/SheerkaDump.py +++ b/src/core/sheerka/services/SheerkaDump.py @@ -22,9 +22,9 @@ class SheerkaDump(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaDump.dump_concepts, "concepts") - self.sheerka.bind_service_method(self, SheerkaDump.dump_desc, "desc") - self.sheerka.bind_service_method(self, SheerkaDump.dump_state, "state") + self.sheerka.bind_service_method(self.dump_concepts, "concepts") + self.sheerka.bind_service_method(self.dump_desc, "desc") + self.sheerka.bind_service_method(self.dump_state, "state") def dump_concepts(self): lst = self.sheerka.sdp.list(self.sheerka.CONCEPTS_BY_ID_ENTRY) diff --git a/src/core/sheerka/services/SheerkaEvaluateConcept.py b/src/core/sheerka/services/SheerkaEvaluateConcept.py index d6ca0c5..fc3a7e5 100644 --- a/src/core/sheerka/services/SheerkaEvaluateConcept.py +++ b/src/core/sheerka/services/SheerkaEvaluateConcept.py @@ -16,7 +16,7 @@ class SheerkaEvaluateConcept(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaEvaluateConcept.evaluate_concept) + self.sheerka.bind_service_method(self.evaluate_concept) @staticmethod def infinite_recursion_detected(context, concept): diff --git a/src/core/sheerka/services/SheerkaExecute.py b/src/core/sheerka/services/SheerkaExecute.py index 78aec4c..f04eb71 100644 --- a/src/core/sheerka/services/SheerkaExecute.py +++ b/src/core/sheerka/services/SheerkaExecute.py @@ -16,7 +16,7 @@ class SheerkaExecute(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaExecute.execute) + self.sheerka.bind_service_method(self.execute) def call_parsers(self, context, return_values): diff --git a/src/core/sheerka/services/SheerkaHistoryManager.py b/src/core/sheerka/services/SheerkaHistoryManager.py index e4a03cc..a643d8b 100644 --- a/src/core/sheerka/services/SheerkaHistoryManager.py +++ b/src/core/sheerka/services/SheerkaHistoryManager.py @@ -50,7 +50,7 @@ class SheerkaHistoryManager(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaHistoryManager.history) + self.sheerka.bind_service_method(self.history) def history(self, depth=10, start=0): """ diff --git a/src/core/sheerka/services/SheerkaModifyConcept.py b/src/core/sheerka/services/SheerkaModifyConcept.py index 319bfd0..1ce10c5 100644 --- a/src/core/sheerka/services/SheerkaModifyConcept.py +++ b/src/core/sheerka/services/SheerkaModifyConcept.py @@ -9,7 +9,7 @@ class SheerkaModifyConcept(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaModifyConcept.modify_concept) + self.sheerka.bind_service_method(self.modify_concept) def modify_concept(self, context, concept): old_version = self.sheerka.get_by_id(concept.id) diff --git a/src/core/sheerka/services/SheerkaSetsManager.py b/src/core/sheerka/services/SheerkaSetsManager.py index 57c0b65..332bf16 100644 --- a/src/core/sheerka/services/SheerkaSetsManager.py +++ b/src/core/sheerka/services/SheerkaSetsManager.py @@ -16,12 +16,12 @@ class SheerkaSetsManager(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaSetsManager.set_isa) - self.sheerka.bind_service_method(self, SheerkaSetsManager.get_set_elements) - self.sheerka.bind_service_method(self, SheerkaSetsManager.add_concept_to_set) - self.sheerka.bind_service_method(self, SheerkaSetsManager.isinset) - self.sheerka.bind_service_method(self, SheerkaSetsManager.isa) - self.sheerka.bind_service_method(self, SheerkaSetsManager.isaset) + self.sheerka.bind_service_method(self.set_isa) + self.sheerka.bind_service_method(self.get_set_elements) + self.sheerka.bind_service_method(self.add_concept_to_set) + self.sheerka.bind_service_method(self.isinset) + self.sheerka.bind_service_method(self.isa) + self.sheerka.bind_service_method(self.isaset) cache = SetCache(default=lambda k: self.sheerka.sdp.get(self.CONCEPTS_GROUPS_ENTRY, k)) self.sheerka.cache_manager.register_cache(self.CONCEPTS_GROUPS_ENTRY, cache) diff --git a/src/core/sheerka/services/SheerkaVariableManager.py b/src/core/sheerka/services/SheerkaVariableManager.py index c40b745..c6224ad 100644 --- a/src/core/sheerka/services/SheerkaVariableManager.py +++ b/src/core/sheerka/services/SheerkaVariableManager.py @@ -27,9 +27,9 @@ class SheerkaVariableManager(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self, SheerkaVariableManager.record) - self.sheerka.bind_service_method(self, SheerkaVariableManager.load) - self.sheerka.bind_service_method(self, SheerkaVariableManager.delete) + self.sheerka.bind_service_method(self.record) + self.sheerka.bind_service_method(self.load) + self.sheerka.bind_service_method(self.delete) cache = Cache(default=lambda k: self.sheerka.sdp.get(self.VARIABLES_ENTRY, k)) self.sheerka.cache_manager.register_cache(self.VARIABLES_ENTRY, cache, True, True) diff --git a/src/evaluators/PythonEvaluator.py b/src/evaluators/PythonEvaluator.py index da7a775..de0466a 100644 --- a/src/evaluators/PythonEvaluator.py +++ b/src/evaluators/PythonEvaluator.py @@ -11,6 +11,28 @@ from evaluators.BaseEvaluator import OneReturnValueEvaluator from parsers.PythonParser import PythonNode +def inject_context(context): + """ + function Decorator used to inject the context in methods that needed + :param context: + :return: + """ + + def wrapped(func): + def inner(*args, **kwargs): + return func(context, *args, **kwargs) + + return inner + + return wrapped + + +class Expando: + def __init__(self, bag): + for k, v in bag.items(): + setattr(self, k, v) + + class PythonEvaluator(OneReturnValueEvaluator): NAME = "Python" @@ -65,14 +87,12 @@ class PythonEvaluator(OneReturnValueEvaluator): def get_globals(self, context, node): my_locals = { - "sheerka": context.sheerka, - # "desc": context.sheerka.dump_handler.dump_desc, - # "concepts": context.sheerka.dump_handler.dump_concepts, - # "history": context.sheerka.dump_handler.dump_history, - # "state": context.sheerka.dump_handler.dump_state, "Concept": core.concept.Concept, "BuiltinConcepts": core.builtin_concepts.BuiltinConcepts, } + + method_from_sheerka = self.update_globals_with_sheerka_methods(my_locals, context) + if context.obj: context.log(f"Concept '{context.obj}' is in context. Adding it and its properties to locals.", self.name) @@ -123,8 +143,26 @@ class PythonEvaluator(OneReturnValueEvaluator): if self.locals: # when exta values are given. Add them my_locals.update(self.locals) + my_locals["sheerka"] = Expando(method_from_sheerka) # it's the last, so I cannot be overridden return my_locals + @staticmethod + def update_globals_with_sheerka_methods(my_locals, context): + methods_from_sheerka = {} + + # Make sure that methods that need the concept are correctly wrapped + for method_name, method in context.sheerka.sheerka_methods.items(): + if method_name in context.sheerka.methods_with_context: + methods_from_sheerka[method_name] = inject_context(context)(method) + else: + methods_from_sheerka[method_name] = method + + # Add all the methods as a direct access + for method_name, method in methods_from_sheerka.items(): + my_locals[method_name] = method + + return methods_from_sheerka # to allow access using prefix "sheerka." + @staticmethod def resolve_name(to_resolve): """ diff --git a/tests/core/test_SheerkaComparisonManager.py b/tests/core/test_SheerkaComparisonManager.py index 9cdac4d..f76183c 100644 --- a/tests/core/test_SheerkaComparisonManager.py +++ b/tests/core/test_SheerkaComparisonManager.py @@ -143,6 +143,16 @@ class TestSheerkaGreaterThanManager(TestUsingMemoryBasedSheerka): assert sheerka.isinstance(res.body, BuiltinConcepts.CHICKEN_AND_EGG) assert set(res.body.body) == {one, two, five} + def test_i_can_give_the_same_information_in_many_ways(self): + sheerka, context, one, two = self.init_concepts("one", "two") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.is_greater_than(context, "prop_name", two, one) + service.is_less_than(context, "prop_name", one, two) + + weighted = sheerka.cache_manager.get(SheerkaComparisonManager.RESOLVED_COMPARISON_ENTRY, "prop_name|#") + assert weighted == {"1001": 1, "1002": 2} + def test_methods_are_correctly_bound(self): sheerka, context, one, two = self.init_concepts("one", "two") res = sheerka.is_greater_than(context, "prop_name", two, one) diff --git a/tests/evaluators/test_PythonEvaluator.py b/tests/evaluators/test_PythonEvaluator.py index 60f4924..26324dc 100644 --- a/tests/evaluators/test_PythonEvaluator.py +++ b/tests/evaluators/test_PythonEvaluator.py @@ -25,6 +25,7 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka): @pytest.mark.parametrize("text, expected", [ ("1 + 1", 2), + ("test()", "I have access to Sheerka !"), ("sheerka.test()", "I have access to Sheerka !"), ("a=10\na", 10), ]) @@ -37,6 +38,24 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka): assert evaluated.status assert evaluated.value == expected + def test_i_can_eval_using_context(self): + context = self.get_context() + parsed = PythonParser().parse(context, "test_using_context('value for param1', 10)") + + evaluated = PythonEvaluator().eval(context, parsed) + + assert evaluated.status + assert evaluated.value.startswith("I have access to Sheerka ! param1='value for param1', param2=10, event=") + + def test_i_can_eval_using_context_when_self_is_not_sheerka(self): + sheerka, context = self.init_concepts() + parsed = PythonParser().parse(context, "create_new_concept(Concept('foo'))") + + evaluated = PythonEvaluator().eval(context, parsed) + + assert evaluated.status + assert sheerka.has_key("foo") + @pytest.mark.parametrize("concept", [ Concept("foo"), Concept("foo", body="2"), diff --git a/tests/non_reg/test_sheerka_non_reg.py b/tests/non_reg/test_sheerka_non_reg.py index 9a615be..a6619f7 100644 --- a/tests/non_reg/test_sheerka_non_reg.py +++ b/tests/non_reg/test_sheerka_non_reg.py @@ -192,7 +192,7 @@ as: assert sheerka.isinstance(res[0].value, BuiltinConcepts.NOP) def test_i_can_recognize_concept_with_variable(self): - sheerka, context, concept_foo, concept_hello = self.init_concepts( + sheerka, context, concept_foo, concept_hello = self.init_concepts( "foo", Concept(name="hello a").def_var("a"), create_new=True) @@ -869,6 +869,35 @@ as: # assert res[0].status # assert isinstance(res[0].body, Concept) + def test_i_can_express_comparison(self): + definitions = [ + "def concept one", + "def concept two", + "def concept three", + "def concept four", + ] + + sheerka = self.init_scenario(definitions) + + res = sheerka.evaluate_user_input("is_greater_than('some_prop', two, one)") + assert res[0].status + + res = sheerka.evaluate_user_input("is_less_than('some_prop', two, three)") + assert res[0].status + + res = sheerka.evaluate_user_input("get_concepts_weights('some_prop')") + assert res[0].status + assert res[0].body == {'1001': 1, '1002': 2, '1003': 3} + + # test i use a concept to define relation + sheerka.evaluate_user_input("def concept a > b as is_greater_than('some_prop', a, b)") + res = sheerka.evaluate_user_input("eval four > three") + assert res[0].status + + res = sheerka.evaluate_user_input("get_concepts_weights('some_prop')") + assert res[0].status + assert res[0].body == {'1001': 1, '1002': 2, '1003': 3, '1004': 4} + class TestSheerkaNonRegFile(TestUsingFileBasedSheerka): def test_i_can_def_several_concepts(self):