Implemented ConceptManager with concept creation, modification and deletion

This commit is contained in:
2020-12-08 15:36:21 +01:00
parent d364878ddb
commit 4b6e1dd55b
40 changed files with 1847 additions and 979 deletions
+87 -248
View File
@@ -8,10 +8,7 @@ from cache.Cache import Cache
from cache.CacheManager import CacheManager
from cache.DictionaryCache import DictionaryCache
from cache.IncCache import IncCache
from cache.ListIfNeededCache import ListIfNeededCache
from cache.SetCache import SetCache
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique, \
UnknownConcept, AllBuiltinConcepts
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, UnknownConcept
from core.concept import Concept, ConceptParts, NotInit, get_concept_attrs
from core.error import ErrorObj
from core.global_symbols import EVENT_USER_INPUT_EVALUATED
@@ -49,12 +46,8 @@ class Sheerka(Concept):
Main controller for the project
"""
CONCEPTS_BY_ID_ENTRY = "Concepts_By_ID" # to store all the concepts
CONCEPTS_BY_KEY_ENTRY = "Concepts_By_Key"
CONCEPTS_BY_NAME_ENTRY = "Concepts_By_Name"
CONCEPTS_BY_HASH_ENTRY = "Concepts_By_Hash" # store hash of concepts definitions (not values)
CONCEPTS_REFERENCES_ENTRY = "Concepts_References" # tracks references between concepts
CONCEPTS_BY_ID_ENTRY = "ConceptManager:Concepts_By_ID"
CONCEPTS_BY_NAME_ENTRY = "ConceptManager:Concepts_By_Name"
CONCEPTS_BY_FIRST_KEYWORD_ENTRY = "Concepts_By_First_Keyword"
RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY = "Resolved_Concepts_By_First_Keyword"
@@ -63,13 +56,10 @@ class Sheerka(Concept):
CONCEPTS_GRAMMARS_ENTRY = "Concepts_Grammars"
CHICKEN_AND_EGG_CONCEPTS_ENTRY = "Chicken_And_Egg_Concepts"
CONCEPTS_KEYS_ENTRY = "Concepts_Keys"
OBJECTS_IDS_ENTRY = "Objects_Ids"
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts" # sequential key for builtin concepts
USER_CONCEPTS_KEYS = "User_Concepts" # sequential key for user defined concepts
MAX_EXECUTION_HISTORY = 100
MAX_RETURN_VALUES_HISTORY = 100
ALL_ATTRIBUTES = []
def __init__(self, cache_only=False, debug=False, loggers=None):
@@ -191,6 +181,8 @@ class Sheerka(Concept):
initialize_pickle_handlers()
self.sdp = SheerkaDataProvider(root_folder, self)
self.builtin_cache = self.get_builtins_classes_as_dict()
self.initialize_caching()
self.get_builtin_parsers()
self.get_builtin_evaluators()
@@ -233,31 +225,8 @@ class Sheerka(Concept):
def initialize_caching(self):
def params(cache_name):
return {
'default': lambda k: self.sdp.get(cache_name, k),
'extend_exists': lambda k: self.sdp.exists(cache_name, k)
}
cache = IncCache(default=lambda k: self.sdp.get(self.CONCEPTS_KEYS_ENTRY, k))
self.cache_manager.register_cache(self.CONCEPTS_KEYS_ENTRY, cache)
register_concept_cache = self.cache_manager.register_concept_cache
cache = Cache(**params(self.CONCEPTS_BY_ID_ENTRY))
register_concept_cache(self.CONCEPTS_BY_ID_ENTRY, cache, lambda c: c.id, True)
cache = ListIfNeededCache(**params(self.CONCEPTS_BY_KEY_ENTRY))
register_concept_cache(self.CONCEPTS_BY_KEY_ENTRY, cache, lambda c: c.key, True)
cache = ListIfNeededCache(**params(self.CONCEPTS_BY_NAME_ENTRY))
register_concept_cache(self.CONCEPTS_BY_NAME_ENTRY, cache, lambda c: c.name, True)
cache = ListIfNeededCache(**params(self.CONCEPTS_BY_HASH_ENTRY))
register_concept_cache(self.CONCEPTS_BY_HASH_ENTRY, cache, lambda c: c.get_definition_hash(), True)
cache = SetCache(default=lambda k: self.sdp.get(self.CONCEPTS_REFERENCES_ENTRY, k))
self.cache_manager.register_cache(self.CONCEPTS_REFERENCES_ENTRY, cache)
cache = IncCache(default=lambda k: self.sdp.get(self.OBJECTS_IDS_ENTRY, k))
self.cache_manager.register_cache(self.OBJECTS_IDS_ENTRY, cache)
cache = DictionaryCache(default=lambda k: self.sdp.get(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, k))
self.cache_manager.register_cache(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, cache)
@@ -307,8 +276,6 @@ class Sheerka(Concept):
service.initialize_deferred(context, is_first_time)
def first_time_initialisation(self, context):
self.cache_manager.put(self.CONCEPTS_KEYS_ENTRY, self.USER_CONCEPTS_KEYS, 1000)
self.record_var(context, self.name, "save_execution_context", True)
def initialize_builtin_concepts(self):
@@ -317,38 +284,11 @@ class Sheerka(Concept):
:return: None
"""
# self.init_log.debug("Initializing builtin concepts")
builtins_classes = self.get_builtins_classes_as_dict()
# this all initialization of the builtins seems to be little bit complicated
# why do we need to update it from DB ?
for key in AllBuiltinConcepts:
concept = self if key == BuiltinConcepts.SHEERKA \
else builtins_classes[str(key)]() if str(key) in builtins_classes \
else Concept(key, True, False, key)
if key in BuiltinUnique:
concept._metadata.is_unique = True
concept._metadata.is_evaluated = True
if not concept._metadata.is_unique and str(key) in builtins_classes:
self.builtin_cache[key] = builtins_classes[str(key)]
from_db = self.cache_manager.get(self.CONCEPTS_BY_KEY_ENTRY, concept._metadata.key)
if from_db is None:
# self.init_log.debug(f"'{concept.name}' concept is not found in db. Adding.")
self.set_id_if_needed(concept, True)
self.cache_manager.add_concept(concept)
if key == BuiltinConcepts.RETURN_VALUE:
self.return_value_concept_id = concept.id
elif key == BuiltinConcepts.ERROR:
self.error_concept_id = concept.id
else:
# self.init_log.debug(f"Found concept '{from_db}' in db. Updating.")
concept.update_from(from_db)
return
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
concept_service = self.services[SheerkaConceptManager.NAME]
concepts_ids = concept_service.initialize_builtin_concepts()
self.return_value_concept_id = concepts_ids[BuiltinConcepts.RETURN_VALUE]
self.error_concept_id = concepts_ids[BuiltinConcepts.ERROR]
def get_builtin_parsers(self):
"""
@@ -479,114 +419,6 @@ class Sheerka(Concept):
"""
self.printer_handler.print(result, instructions)
def set_id_if_needed(self, obj: Concept, is_builtin: bool):
"""
Set the key for the concept if needed
:param obj:
:param is_builtin:
:return:
"""
if obj._metadata.id is not None:
return
key = self.BUILTIN_CONCEPTS_KEYS if is_builtin else self.USER_CONCEPTS_KEYS
obj._metadata.id = str(self.cache_manager.get(self.CONCEPTS_KEYS_ENTRY, key))
# self.log.debug(f"Setting id '{obj.metadata.id}' to concept '{obj.metadata.name}'.")
def force_sya_def(self, context, list_of_def):
"""
Set the precedence and/or the associativity of a concept
FOR TESTS PURPOSE. TO REMOVE EVENTUALLY
:param context:
:param list_of_def list of tuple(concept_id, precedence (int), SyaAssociativity)
:return:
"""
# validate the entries
# If one entry is an invalid concept, rollback everything
for concept_id, precedence, associativity in list_of_def:
if concept_id == BuiltinConcepts.UNKNOWN_CONCEPT:
return self.ret(self.name,
False,
self.new(BuiltinConcepts.ERROR, body=f"Concept {concept_id} is not known"))
sya_def = self.cache_manager.copy(self.RESOLVED_CONCEPTS_SYA_DEFINITION_ENTRY) or {}
# update the definitions
for concept_id, precedence, associativity in list_of_def:
if precedence is None and associativity is None:
try:
del self.sya_definitions[concept_id]
except KeyError:
pass
else:
sya_def[concept_id] = (precedence, associativity)
# put in cache
self.cache_manager.put(self.RESOLVED_CONCEPTS_SYA_DEFINITION_ENTRY, False, sya_def)
return self.ret(self.name, True, self.new(BuiltinConcepts.SUCCESS))
def add_in_cache(self, concept: Concept):
"""
Adds a concept template in cache.
The cache is used as a proxy before looking at sdp
:param concept:
:return:
"""
# sanity check
if concept.key is None:
concept.init_key()
if concept.key is None:
raise KeyError()
self.cache_manager.add_concept(concept)
return concept
def get_by_key(self, concept_key, concept_id=None):
concept_key = str(concept_key) if isinstance(concept_key, BuiltinConcepts) else concept_key
return self.internal_get("key", concept_key, self.CONCEPTS_BY_KEY_ENTRY, concept_id)
def get_by_name(self, concept_name, concept_id=None):
return self.internal_get("name", concept_name, self.CONCEPTS_BY_NAME_ENTRY, concept_id)
def get_by_hash(self, concept_hash, concept_id=None):
return self.internal_get("hash", concept_hash, self.CONCEPTS_BY_HASH_ENTRY, concept_id)
def get_by_id(self, concept_id):
return self.internal_get("id", concept_id, self.CONCEPTS_BY_ID_ENTRY, None)
def internal_get(self, index_name, key, cache_name, concept_id=None):
"""
Tries to find an entry
:param index_name: name of the index (ex by_id, by_key...)
:param key: index value
:param cache_name: name of the cache (ex Concepts_By_ID...)
:param concept_id: id of the concept if none, in case where there are multiple results
:return:
"""
if key is None:
return ErrorConcept(f"Concept '{key}' is undefined.")
concepts = self.cache_manager.get(cache_name, key)
if concepts:
if concept_id is None:
return concepts
if not hasattr(concepts, "__iter__"):
return concepts
for c in concepts:
if c.id == concept_id:
return c
metadata = [(index_name, key), ("id", concept_id)] if concept_id else (index_name, key)
return self.get_unknown(metadata)
def resolve(self, concept):
"""
Try to find a concept by its name, id, or c:: definition
@@ -655,12 +487,19 @@ class Sheerka(Concept):
if isinstance(key, Token):
if key.type == TokenKind.RULE: # do not recognize rules !!!
return None
if key.value[1]:
concept = self.cache_manager.get(self.CONCEPTS_BY_ID_ENTRY, key.value[1])
else:
concept = self.cache_manager.get(self.CONCEPTS_BY_NAME_ENTRY, key.value[0])
key = key.value
elif isinstance(key, str) and key.startswith("c:"):
key = core.utils.unstr_concept(key)
if isinstance(key, tuple):
if key == (None, None):
return None
if key[1]:
concept = self.cache_manager.get(self.CONCEPTS_BY_ID_ENTRY, key[1])
else:
concept = self.cache_manager.get(self.CONCEPTS_BY_NAME_ENTRY, key[0])
else:
concept = self.cache_manager.get(self.CONCEPTS_BY_NAME_ENTRY, key)
@@ -668,44 +507,6 @@ class Sheerka(Concept):
return None
return new_instances(concept) if return_new else concept
def has_id(self, concept_id):
"""
Returns True if a concept with this id exists in cache
It does not search in the remote repository
:param concept_id:
:return:
"""
if concept_id is None:
return False
return self.cache_manager.has(self.CONCEPTS_BY_ID_ENTRY, concept_id)
def has_key(self, concept_key):
"""
Returns True if concept(s) with this key exist in cache
It does not search in the remote repository
:param concept_key:
:return:
"""
return self.cache_manager.has(self.CONCEPTS_BY_KEY_ENTRY, concept_key)
def has_name(self, concept_name):
"""
Returns True if concept(s) with this name exist in cache
It does not search in the remote repository
:param concept_name:
:return:
"""
return self.cache_manager.has(self.CONCEPTS_BY_NAME_ENTRY, concept_name)
def has_hash(self, concept_hash):
"""
Returns True if concept(s) with this hash exist in cache
It does not search in the remote repository
:param concept_hash:
:return:
"""
return self.cache_manager.has(self.CONCEPTS_BY_HASH_ENTRY, concept_hash)
def new(self, concept_key, **kwargs):
"""
Returns an instance of a new concept
@@ -816,21 +617,6 @@ class Sheerka(Concept):
return (self.objvalue(obj) for obj in objs.body)
def value_by_concept(self, obj, concept):
if obj is None:
return None
if not isinstance(obj, Concept):
return None
if isinstance(concept, tuple) and obj.key in [str(key) for key in concept]:
return obj
if obj.key == str(concept):
return obj
return self.value_by_concept(obj.body, concept)
def get_error(self, obj):
if isinstance(obj, Concept) and obj._metadata.is_builtin and obj.key in BuiltinErrors:
return obj
@@ -863,16 +649,6 @@ class Sheerka(Concept):
return self.parsers_prefix + name
def test(self):
return f"I have access to Sheerka !"
def test_using_context(self, context, param):
event = context.event.get_digest()
return f"I have access to Sheerka ! {param=}, {event=}."
def test_error(self):
raise Exception("I can raise an error")
@staticmethod
def is_success(obj):
if isinstance(obj, bool): # quick win
@@ -1014,6 +790,69 @@ class Sheerka(Concept):
}
return self.new(BuiltinConcepts.TO_DICT, body=bag)
def test(self):
return f"I have access to Sheerka !"
def test_using_context(self, context, param):
event = context.event.get_digest()
return f"I have access to Sheerka ! {param=}, {event=}."
def test_error(self):
raise Exception("I can raise an error")
def test_only_force_sya_def(self, context, list_of_def):
"""
Set the precedence and/or the associativity of a concept
FOR TESTS PURPOSE. TO REMOVE EVENTUALLY
:param context:
:param list_of_def list of tuple(concept_id, precedence (int), SyaAssociativity)
:return:
"""
# validate the entries
# If one entry is an invalid concept, rollback everything
for concept_id, precedence, associativity in list_of_def:
if concept_id == BuiltinConcepts.UNKNOWN_CONCEPT:
return self.ret(self.name,
False,
self.new(BuiltinConcepts.ERROR, body=f"Concept {concept_id} is not known"))
sya_def = self.cache_manager.copy(self.RESOLVED_CONCEPTS_SYA_DEFINITION_ENTRY) or {}
# update the definitions
for concept_id, precedence, associativity in list_of_def:
if precedence is None and associativity is None:
try:
del self.sya_definitions[concept_id]
except KeyError:
pass
else:
sya_def[concept_id] = (precedence, associativity)
# put in cache
self.cache_manager.put(self.RESOLVED_CONCEPTS_SYA_DEFINITION_ENTRY, False, sya_def)
return self.ret(self.name, True, self.new(BuiltinConcepts.SUCCESS))
def test_only_add_in_cache(self, concept: Concept):
"""
Adds a concept template in cache.
The cache is used as a proxy before looking at sdp
:param concept:
:return:
"""
# sanity check
if concept.key is None:
concept.init_key()
if concept.key is None:
raise KeyError()
self.cache_manager.add_concept(concept)
return concept
def to_profile():
sheerka = Sheerka()