Added basic implementation for Python code evaluation

This commit is contained in:
2019-11-07 17:18:07 +01:00
parent b818c992ec
commit 448ebc696a
18 changed files with 501 additions and 156 deletions
+121 -41
View File
@@ -1,9 +1,10 @@
from dataclasses import dataclass
from core.concept import Concept, ErrorConcept, Property
from core.concept import Concept, ErrorConcept, Property, TooManySuccessConcept, ReturnValueConcept
from parsers.PythonParser import PythonParser, PythonGetNamesVisitor, PythonNode
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError
from parsers.DefaultParser import DefaultParser, DefConceptNode
import core.utils
import logging
@@ -26,6 +27,7 @@ class ReturnValue:
To avoid using the try/except pattern for each and every call
To give context (ie return message) even when the call is successful
"""
who: object
status: bool
value: Concept
message: str = None
@@ -36,29 +38,29 @@ class ExecutionContext:
"""
To keep track of the execution of a request
"""
sheerka: object
event_digest: str
class Sheerka(Concept, metaclass=Singleton):
class Sheerka(Concept):
"""
Main controller for the project
"""
NAME = "Sheerka"
UNKNOWN_CONCEPT_NAME = "Unknown Concept"
ERROR_CONCEPT_NAME = "Error"
SUCCESS_CONCEPT_NAME = "Success"
CONCEPTS_ENTRY = "Concepts"
BUILTIN_CONCEPTS_KEYS = "Builtins"
USER_CONCEPTS_KEYS = "Concepts"
CONCEPTS_ENTRY = "All_Concepts"
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts"
USER_CONCEPTS_KEYS = "User_Concepts"
def __init__(self):
def __init__(self, debug=False):
log.debug("Starting Sheerka.")
super().__init__(Sheerka.NAME)
# cache of the most used concepts
self.concepts_cache = []
self.concepts_cache = {}
# a concept can be instantiated
# ex: File is a concept, but File('foo.txt') is an instance
@@ -71,30 +73,39 @@ class Sheerka(Concept, metaclass=Singleton):
self.sdp = None
self.parsers = []
self.evaluators = []
self.key = self.NAME
self.debug = debug
def initialize(self, root_folder=None):
"""
Starting Sheerka
Loads the current configuration
Notes that when it's the first time, it also create the needed working folders
:param debug:
:param root_folder: root configuration folder
:return: ReturnValue(Success or Error)
"""
try:
self.init_logging()
self.sdp = SheerkaDataProvider(root_folder)
self.parsers.append(lambda text: DefaultParser(text, PythonParser))
self.parsers.append(lambda text: PythonParser(text))
self.evaluators.append(core.utils.get_object("evaluators.DefaultEvaluator.DefaultEvaluator"))
self.evaluators.append(core.utils.get_object("evaluators.AddConceptEvaluator.AddConceptEvaluator"))
self.evaluators.append(core.utils.get_object("evaluators.PythonEvaluator.PythonEvaluator"))
if self.sdp.first_time:
self.sdp.set_key(self.USER_CONCEPTS_KEYS, 1000)
self.create_builtin_concepts()
except IOError as e:
return ReturnValue(False, self.get_concept(Sheerka.ERROR_CONCEPT_NAME), e)
return ReturnValue(self, False, self.get(Sheerka.ERROR_CONCEPT_NAME), e)
return ReturnValue(True, self.get_concept(Sheerka.SUCCESS_CONCEPT_NAME))
return ReturnValue(self, True, self.get(Sheerka.SUCCESS_CONCEPT_NAME))
def set_id_if_needed(self, obj, is_builtin):
"""
@@ -118,11 +129,13 @@ class Sheerka(Concept, metaclass=Singleton):
self,
Concept(Sheerka.UNKNOWN_CONCEPT_NAME, key=Sheerka.UNKNOWN_CONCEPT_NAME),
Concept(Sheerka.SUCCESS_CONCEPT_NAME, key=Sheerka.SUCCESS_CONCEPT_NAME),
Concept(Sheerka.ERROR_CONCEPT_NAME, key=Sheerka.ERROR_CONCEPT_NAME),
ErrorConcept(),
TooManySuccessConcept(),
ReturnValueConcept(),
]
for concept in builtins:
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.name)
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key)
if from_db is None:
log.debug(f"'{concept.name}' concept is not found. Adding.")
self.set_id_if_needed(concept, True)
@@ -130,18 +143,32 @@ class Sheerka(Concept, metaclass=Singleton):
else:
log.debug(f"Found concept '{from_db}'. Updating.")
concept.update_from(from_db)
self.concepts_cache[concept.key] = concept
def init_logging(self):
if self.debug:
log_format = "%(asctime)s %(name)s [%(levelname)s] %(message)s"
log_level = logging.DEBUG
else:
log_format = "%(message)s"
log_level = logging.INFO
logging.basicConfig(format=log_format, level=log_level)
def eval(self, text):
evt_digest = self.sdp.save_event(Event(text))
exec_context = ExecutionContext(evt_digest)
result = self.try_parse(text)
exec_context = ExecutionContext(self, evt_digest)
return_values = self.try_parse(text)
return_values = self.try_eval(exec_context, return_values)
return_values = []
for parser_name, status, node in result:
if not status:
return_values.append(ReturnValue(False, ErrorConcept(body=node)))
elif status and isinstance(node, DefConceptNode):
return_values.append(self.add_concept(exec_context, node))
# return_values = []
# for parser_name, status, node in result:
# if not status:
# return_values.append(ReturnValue(False, ErrorConcept(body=node)))
# elif status and isinstance(node, DefConceptNode):
# return_values.append(self.add_concept(exec_context, node))
# else:
# return_values.append(ReturnValue(True, node))
return return_values
@@ -156,20 +183,36 @@ class Sheerka(Concept, metaclass=Singleton):
# except Exception as e:
# result.append((p.name, e))
tree = p.parse()
result.append((p.name, not p.has_error, p.error_sink if p.has_error else tree))
result.append(ReturnValue(p.name, not p.has_error, p.error_sink if p.has_error else tree))
return result
def get_concept(self, name):
"""
Given a concept name, tries to find it
:param name: name of the concept to look for
:param is_builtin: is it a builtin concept ?
:return: concept if found, UNKNOWN_CONCEPT otherwise
"""
for concept in self.concepts_cache:
if concept.name == name:
return concept
return ErrorConcept()
def try_eval(self, context, items):
log.debug("Evaluating parsing result.")
# group the evaluators by priority and sort them
# The first one to be applied will be the one with the highest priority
grouped_evaluators = {}
for item in self.evaluators:
grouped_evaluators.setdefault(item.priority, []).append(item)
sorted_priorities = sorted(grouped_evaluators.keys(), reverse=True)
for priority in sorted_priorities:
log.debug("Processing priority " + str(priority))
for item in items:
log.debug(item)
original_items = items[:]
evaluated_items = []
for evaluator in grouped_evaluators[priority]:
if evaluator.matches(context, original_items):
result = evaluator.eval(context, original_items)
if isinstance(result, list):
evaluated_items.extend(result)
else:
evaluated_items.append(result)
# what was computed by this group will be the input of the following group
items = evaluated_items if len(evaluated_items) > 0 else original_items
return items
def add_concept(self, exec_context, def_concept_node: DefConceptNode):
"""
@@ -203,16 +246,53 @@ class Sheerka(Concept, metaclass=Singleton):
try:
self.sdp.add(exec_context.event_digest, self.CONCEPTS_ENTRY, concept, use_ref=True)
except SheerkaDataProviderDuplicateKeyError as error:
return ReturnValue(False, ErrorConcept(body=error), error.args[0])
return ReturnValue(True, concept)
return ReturnValue(self.add_concept.__name__, False, ErrorConcept(body=error), error.args[0])
return ReturnValue(self.add_concept.__name__, True, concept)
@staticmethod
def concept_equals(concept1, concept2):
"""True if the two concepts refer to the same concept"""
if concept1 is None and concept2 is None:
return True
def get(self, concept_name):
"""
Tries to find a concept
:param concept_name:
:return:
"""
if concept1 is None or concept2 is None:
# first search in cache
if concept_name in self.concepts_cache:
return self.concepts_cache[concept_name]
return self.sdp.get(self.CONCEPTS_ENTRY, concept_name)
def new(self, concept, **kwargs):
"""
Returns an instance of a new concept
:param concept:
:param kwargs:
:return:
"""
if isinstance(concept, str):
concept = self.get(concept)
for k, v in kwargs.items():
if hasattr(concept, k):
setattr(concept, k, v)
return 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 not isinstance(a, Concept) or not isinstance(b, Concept):
return False
return concept1.key == concept2.key
# TODO : manage when a is the list of all possible b
return a.key == b.key
@staticmethod
def test():
return "I have access to Sheerka !"