Added ModifyConcept function, and fixed 'isa' not working
This commit is contained in:
+27
-2
@@ -1,6 +1,6 @@
|
||||
import hashlib
|
||||
from collections import namedtuple
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from core.sheerka_logger import get_logger
|
||||
|
||||
@@ -15,6 +15,7 @@ PROPERTIES_FOR_DIGEST = ("name", "key",
|
||||
PROPERTIES_TO_SERIALIZE = PROPERTIES_FOR_DIGEST + tuple(["id"])
|
||||
PROPERTIES_FOR_NEW = ("where", "pre", "post", "body", "desc")
|
||||
VARIABLE_PREFIX = "__var__"
|
||||
ORIGIN = "##origin##" # same as Serializer.ORIGIN but I don't want to include the reference
|
||||
|
||||
|
||||
class ConceptParts(Enum):
|
||||
@@ -97,6 +98,7 @@ class Concept:
|
||||
self.bnf = None
|
||||
self.log = get_logger("core." + self.__class__.__name__)
|
||||
self.init_log = get_logger("init.core." + self.__class__.__name__)
|
||||
self.original_definition_hash = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"({self.metadata.id}){self.metadata.name}"
|
||||
@@ -243,7 +245,19 @@ class Concept:
|
||||
def body(self):
|
||||
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
|
||||
|
||||
def get_digest(self):
|
||||
def get_origin(self):
|
||||
"""
|
||||
Return the digest used to save the concept if it exists
|
||||
:return:
|
||||
"""
|
||||
if hasattr(self, ORIGIN):
|
||||
return getattr(self, ORIGIN)
|
||||
return None
|
||||
|
||||
def set_origin(self, origin):
|
||||
setattr(self, ORIGIN, origin)
|
||||
|
||||
def get_definition_hash(self):
|
||||
"""
|
||||
Returns the digest of the event
|
||||
:return: hexa form of the sha256
|
||||
@@ -301,6 +315,11 @@ class Concept:
|
||||
for k, v in other.props.items():
|
||||
self.set_prop(k, v.value)
|
||||
|
||||
# origin
|
||||
from sdp.sheerkaSerializer import Serializer
|
||||
if hasattr(other, Serializer.ORIGIN):
|
||||
setattr(self, Serializer.ORIGIN, getattr(other, Serializer.ORIGIN))
|
||||
|
||||
return self
|
||||
|
||||
def set_prop(self, prop_name, prop_value):
|
||||
@@ -362,6 +381,12 @@ class Concept:
|
||||
self.metadata.is_evaluated = True
|
||||
return self
|
||||
|
||||
def freeze_definition_hash(self):
|
||||
self.original_definition_hash = self.get_definition_hash()
|
||||
|
||||
def get_original_definition_hash(self):
|
||||
return self.original_definition_hash
|
||||
|
||||
|
||||
class Property:
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from core.builtin_concepts import BuiltinConcepts, ErrorConcept
|
||||
from core.concept import Concept
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError, SheerkaDataProviderRef
|
||||
|
||||
CONCEPT_LEXER_PARSER_CLASS = "parsers.ConceptLexerParser.ConceptLexerParser"
|
||||
|
||||
@@ -30,7 +30,7 @@ class SheerkaCreateNewConcept:
|
||||
# checks for duplicate concepts
|
||||
# TODO checks if it exists in cache first
|
||||
|
||||
if self.sheerka.sdp.exists(self.sheerka.CONCEPTS_ENTRY, concept.key, concept.get_digest()):
|
||||
if self.sheerka.sdp.exists(self.sheerka.CONCEPTS_BY_HASH_ENTRY, concept.get_definition_hash()):
|
||||
error = SheerkaDataProviderDuplicateKeyError(self.sheerka.CONCEPTS_ENTRY + "." + concept.key, concept)
|
||||
return self.sheerka.ret(
|
||||
self.logger_name,
|
||||
@@ -55,22 +55,35 @@ class SheerkaCreateNewConcept:
|
||||
if not init_ret_value.status:
|
||||
return self.sheerka.ret(self.logger_name, False, ErrorConcept(init_ret_value.value))
|
||||
|
||||
concept.freeze_definition_hash()
|
||||
|
||||
# save the new concept in sdp
|
||||
concept.metadata.full_serialization = True
|
||||
try:
|
||||
# TODO : needs to make these calls atomic (or at least one single call)
|
||||
# save the new concept
|
||||
self.sheerka.sdp.add(
|
||||
concept.metadata.full_serialization = True
|
||||
result = self.sheerka.sdp.add(
|
||||
context.event.get_digest(),
|
||||
self.sheerka.CONCEPTS_ENTRY,
|
||||
concept,
|
||||
use_ref=True)
|
||||
concept.metadata.full_serialization = False
|
||||
|
||||
# update the concept (I hope that it's enough)
|
||||
concept.set_origin(result.digest)
|
||||
|
||||
# save it by id
|
||||
self.sheerka.sdp.add(
|
||||
context.event.get_digest(),
|
||||
self.sheerka.CONCEPTS_BY_ID_ENTRY,
|
||||
{concept.id: concept.get_digest()},
|
||||
is_ref=True)
|
||||
SheerkaDataProviderRef(concept.id, result.digest))
|
||||
|
||||
# records the hash
|
||||
self.sheerka.sdp.add(
|
||||
context.event.get_digest(),
|
||||
self.sheerka.CONCEPTS_BY_HASH_ENTRY,
|
||||
SheerkaDataProviderRef(concept.get_definition_hash(), result.digest))
|
||||
|
||||
# update the definition table
|
||||
if concepts_definitions is not None:
|
||||
self.sheerka.sdp.set(
|
||||
@@ -88,7 +101,7 @@ class SheerkaCreateNewConcept:
|
||||
error.args[0])
|
||||
|
||||
# Updates the caches
|
||||
concept.metadata.full_serialization = False
|
||||
|
||||
self.sheerka.cache_by_key[concept.key] = self.sheerka.sdp.get_safe(self.sheerka.CONCEPTS_ENTRY, concept.key)
|
||||
self.sheerka.cache_by_id[concept.id] = concept
|
||||
if init_ret_value is not None and init_ret_value.status:
|
||||
|
||||
@@ -63,7 +63,7 @@ class SheerkaDump:
|
||||
else:
|
||||
self.sheerka.log.info("No property")
|
||||
|
||||
self.sheerka.log.info(f"digest : {c.get_digest()}")
|
||||
self.sheerka.log.info(f"digest : {c.get_origin()}")
|
||||
|
||||
if self.sheerka.isaset(context, c):
|
||||
items = self.sheerka.get_set_elements(context, c)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProviderRef
|
||||
|
||||
|
||||
class SheerkaModifyConcept:
|
||||
@@ -8,7 +9,41 @@ class SheerkaModifyConcept:
|
||||
|
||||
def modify_concept(self, context, concept):
|
||||
|
||||
self.sheerka.sdp.modify(context.event.get_digest(), self.sheerka.CONCEPTS_ENTRY, concept.key, concept)
|
||||
try:
|
||||
# modify the entry
|
||||
concept.metadata.full_serialization = True
|
||||
result = self.sheerka.sdp.modify(
|
||||
context.event.get_digest(),
|
||||
self.sheerka.CONCEPTS_ENTRY,
|
||||
concept.key,
|
||||
concept)
|
||||
concept.metadata.full_serialization = False
|
||||
|
||||
# update its reference
|
||||
self.sheerka.sdp.modify(
|
||||
context.event.get_digest(),
|
||||
self.sheerka.CONCEPTS_BY_ID_ENTRY,
|
||||
concept.id,
|
||||
SheerkaDataProviderRef(concept.id, result.digest, concept.get_origin()))
|
||||
|
||||
# update the hash entry
|
||||
self.sheerka.sdp.modify(
|
||||
context.event.get_digest(),
|
||||
self.sheerka.CONCEPTS_BY_HASH_ENTRY,
|
||||
concept.get_original_definition_hash(),
|
||||
SheerkaDataProviderRef(concept.get_definition_hash(), result.digest, concept.get_origin()))
|
||||
|
||||
except IndexError as error:
|
||||
context.log_error(f"Failed to update concept '{concept}'.", who=self.logger_name)
|
||||
return self.sheerka.ret(
|
||||
self.logger_name,
|
||||
False,
|
||||
self.sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=concept),
|
||||
error.args[0])
|
||||
|
||||
# update cache
|
||||
self.sheerka.cache_by_key[concept.key] = self.sheerka.sdp.get_safe(self.sheerka.CONCEPTS_ENTRY, concept.key)
|
||||
self.sheerka.cache_by_id[concept.id] = concept
|
||||
|
||||
ret = self.sheerka.ret(self.logger_name, True, self.sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||
return ret
|
||||
|
||||
@@ -48,8 +48,8 @@ class SheerkaSetsManager:
|
||||
assert concept_set.id
|
||||
|
||||
try:
|
||||
ret = self.sheerka.sdp.add_unique(context.event.get_digest(), GROUP_PREFIX + concept_set.id, concept.id)
|
||||
if ret == (None, None): # concept already in set
|
||||
result = self.sheerka.sdp.add_unique(context.event.get_digest(), GROUP_PREFIX + concept_set.id, concept.id)
|
||||
if result.already_exists: # concept already in set
|
||||
return self.sheerka.ret(
|
||||
self.logger_name,
|
||||
False,
|
||||
|
||||
@@ -34,6 +34,7 @@ class Sheerka(Concept):
|
||||
|
||||
CONCEPTS_ENTRY = "All_Concepts" # to store all the concepts
|
||||
CONCEPTS_BY_ID_ENTRY = "Concepts_By_ID"
|
||||
CONCEPTS_BY_HASH_ENTRY = "Concepts_By_Hash" # store hash of concepts definitions (not values)
|
||||
CONCEPTS_DEFINITIONS_ENTRY = "Concepts_Definitions" # to store definitions (bnf) of concepts
|
||||
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts" # sequential key for builtin concepts
|
||||
USER_CONCEPTS_KEYS = "User_Concepts" # sequential key for user defined concepts
|
||||
@@ -482,6 +483,7 @@ class Sheerka(Concept):
|
||||
# otherwise, create another instance
|
||||
concept = self.builtin_cache[key]() if key in self.builtin_cache else Concept()
|
||||
concept.update_from(template)
|
||||
concept.freeze_definition_hash()
|
||||
|
||||
if len(kwargs) == 0:
|
||||
return concept
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, date
|
||||
import hashlib
|
||||
import json
|
||||
@@ -206,12 +207,32 @@ class State:
|
||||
def modify_in_list(self, entry, key, obj, obj_key, obj_origin, load_ref_if_needed, save_ref_if_needed):
|
||||
found = False
|
||||
to_remove = None
|
||||
new_digest = None
|
||||
|
||||
def _get_item_origin(o):
|
||||
if hasattr(o, Serializer.ORIGIN):
|
||||
return getattr(o, Serializer.ORIGIN)
|
||||
|
||||
if isinstance(o, dict) and Serializer.ORIGIN in o:
|
||||
return o[Serializer.ORIGIN]
|
||||
|
||||
if hasattr(o, "get_digest"):
|
||||
return o.get_digest()
|
||||
|
||||
if isinstance(o, str):
|
||||
return o
|
||||
|
||||
return None
|
||||
|
||||
for i in range(len(self.data[entry][key])):
|
||||
item, is_ref = load_ref_if_needed(self.data[entry][key][i])
|
||||
if not hasattr(item, "get_digest"):
|
||||
item_origin = _get_item_origin(item)
|
||||
if item_origin is None:
|
||||
continue
|
||||
if item.get_digest() == obj_origin:
|
||||
if item_origin == obj_origin:
|
||||
obj = save_ref_if_needed(is_ref, obj)
|
||||
if is_ref:
|
||||
new_digest = obj[len(SheerkaDataProvider.REF_PREFIX):]
|
||||
if obj_key == key:
|
||||
self.data[entry][key][i] = obj
|
||||
else:
|
||||
@@ -226,6 +247,8 @@ class State:
|
||||
if to_remove is not None:
|
||||
del self.data[entry][key][to_remove]
|
||||
|
||||
return new_digest
|
||||
|
||||
def remove(self, entry, filter):
|
||||
if filter is None:
|
||||
del (self.data[entry])
|
||||
@@ -282,6 +305,28 @@ class SheerkaDataProviderDuplicateKeyError(Exception):
|
||||
self.obj = obj
|
||||
|
||||
|
||||
@dataclass
|
||||
class SheerkaDataProviderResult:
|
||||
obj: object
|
||||
entry: str
|
||||
key: str
|
||||
digest: str
|
||||
already_exists: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class SheerkaDataProviderRef:
|
||||
key: str
|
||||
target: str
|
||||
original_target: str = None
|
||||
|
||||
def get_digest(self):
|
||||
return self.original_target
|
||||
|
||||
def get_key(self):
|
||||
return self.key
|
||||
|
||||
|
||||
class SheerkaDataProvider:
|
||||
"""Manages the state of the system"""
|
||||
|
||||
@@ -305,7 +350,6 @@ class SheerkaDataProvider:
|
||||
|
||||
self.serializer = Serializer()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_obj_key(obj):
|
||||
"""
|
||||
@@ -344,6 +388,9 @@ class SheerkaDataProvider:
|
||||
if hasattr(obj, Serializer.ORIGIN):
|
||||
return getattr(obj, Serializer.ORIGIN)
|
||||
|
||||
if isinstance(obj, SheerkaDataProviderRef):
|
||||
return obj.original_target
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -359,7 +406,7 @@ class SheerkaDataProvider:
|
||||
def is_reference(obj):
|
||||
return isinstance(obj, str) and obj.startswith(SheerkaDataProvider.REF_PREFIX)
|
||||
|
||||
def add(self, event_digest: str, entry, obj, allow_multiple=True, use_ref=False, is_ref=False):
|
||||
def add(self, event_digest: str, entry, obj, allow_multiple=True, use_ref=False):
|
||||
"""
|
||||
Adds obj to the entry 'entry'
|
||||
:param event_digest: digest of the event that triggers the modification of the state
|
||||
@@ -372,11 +419,7 @@ class SheerkaDataProvider:
|
||||
:return: (entry, key) to retrieve the object
|
||||
"""
|
||||
|
||||
if use_ref and is_ref:
|
||||
raise SheerkaDataProviderError("Cannot use use_ref and is_ref at the same time", None)
|
||||
|
||||
if is_ref and not isinstance(obj, dict):
|
||||
raise SheerkaDataProviderError("is_ref can only be used with dictionaries", obj)
|
||||
original_obj = obj.copy() if isinstance(obj, dict) else obj
|
||||
|
||||
snapshot = self.get_snapshot(SheerkaDataProvider.HeadFile)
|
||||
state = self.load_state(snapshot)
|
||||
@@ -406,15 +449,11 @@ class SheerkaDataProvider:
|
||||
obj.set_digest(self.save_obj(obj.obj))
|
||||
obj.obj = self.REF_PREFIX + obj.get_digest()
|
||||
|
||||
if is_ref:
|
||||
for k, v in obj.obj.items():
|
||||
obj.obj[k] = self.REF_PREFIX + v
|
||||
|
||||
state.update(entry, obj)
|
||||
|
||||
new_snapshot = self.save_state(state)
|
||||
self.set_snapshot(SheerkaDataProvider.HeadFile, new_snapshot)
|
||||
return entry, key
|
||||
return SheerkaDataProviderResult(original_obj, entry, obj.get_key(), obj.get_digest())
|
||||
|
||||
def add_with_auto_key(self, event_digest: str, entry, obj):
|
||||
"""
|
||||
@@ -424,14 +463,20 @@ class SheerkaDataProvider:
|
||||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
|
||||
original_obj = obj.copy() if isinstance(obj, dict) else obj
|
||||
|
||||
next_key = self.get_next_key(entry)
|
||||
if hasattr(obj, "set_key"):
|
||||
obj.set_key(next_key)
|
||||
self.add(event_digest, entry, ObjToUpdate(obj, next_key))
|
||||
return entry, next_key
|
||||
res = self.add(event_digest, entry, ObjToUpdate(obj, next_key))
|
||||
return SheerkaDataProviderResult(original_obj, res.entry, res.key, res.digest)
|
||||
|
||||
def add_unique(self, event_digest: str, entry, obj):
|
||||
"""Add an entry and make sure it's unique"""
|
||||
|
||||
original_obj = obj.copy() if isinstance(obj, dict) else obj
|
||||
|
||||
snapshot = self.get_snapshot(SheerkaDataProvider.HeadFile)
|
||||
state = self.load_state(snapshot)
|
||||
|
||||
@@ -448,7 +493,12 @@ class SheerkaDataProvider:
|
||||
|
||||
new_snapshot = self.save_state(state)
|
||||
self.set_snapshot(SheerkaDataProvider.HeadFile, new_snapshot)
|
||||
return (None if already_exist else entry), None
|
||||
return SheerkaDataProviderResult(
|
||||
original_obj,
|
||||
entry,
|
||||
None,
|
||||
None,
|
||||
already_exist)
|
||||
|
||||
def set(self, event_digest, entry, obj, use_ref=False, is_ref=False):
|
||||
"""
|
||||
@@ -462,6 +512,8 @@ class SheerkaDataProvider:
|
||||
:return:
|
||||
"""
|
||||
|
||||
original_obj = obj.copy() if isinstance(obj, dict) else obj
|
||||
|
||||
if use_ref and is_ref:
|
||||
raise SheerkaDataProviderError("Cannot use use_ref and is_ref at the same time", None)
|
||||
|
||||
@@ -486,7 +538,7 @@ class SheerkaDataProvider:
|
||||
|
||||
new_snapshot = self.save_state(state)
|
||||
self.set_snapshot(SheerkaDataProvider.HeadFile, new_snapshot)
|
||||
return entry, key
|
||||
return SheerkaDataProviderResult(original_obj, entry, key, self.get_obj_digest(obj))
|
||||
|
||||
def modify(self, event_digest, entry, key, obj):
|
||||
"""
|
||||
@@ -499,6 +551,8 @@ class SheerkaDataProvider:
|
||||
:return:
|
||||
"""
|
||||
|
||||
original_obj = obj.copy() if isinstance(obj, dict) else obj
|
||||
|
||||
if key is None:
|
||||
raise SheerkaDataProviderError("Key is mandatory.", None)
|
||||
|
||||
@@ -517,21 +571,33 @@ class SheerkaDataProvider:
|
||||
|
||||
# Gets obj original key, it will help to know if the key has changed
|
||||
obj_key = self.get_obj_key(obj) or key
|
||||
digest = None
|
||||
|
||||
if isinstance(state.data[entry][key], list):
|
||||
obj_origin = self.get_obj_origin(obj)
|
||||
if obj_origin is None:
|
||||
raise (SheerkaDataProviderError(f"Multiple entries under '{entry}.{key}'", obj))
|
||||
|
||||
state.modify_in_list(entry, key, obj, obj_key, obj_origin, self.load_ref_if_needed, self.save_ref_if_needed)
|
||||
digest = state.modify_in_list(
|
||||
entry,
|
||||
key,
|
||||
obj,
|
||||
obj_key,
|
||||
obj_origin,
|
||||
self.load_ref_if_needed,
|
||||
self.save_ref_if_needed)
|
||||
|
||||
else:
|
||||
obj = self.save_ref_if_needed(self.is_reference(state.data[entry][key]), obj)
|
||||
was_saved_as_reference = self.is_reference(state.data[entry][key])
|
||||
if was_saved_as_reference:
|
||||
obj = self.save_ref_if_needed(True, obj)
|
||||
digest = self.get_obj_digest(obj)
|
||||
|
||||
state.modify(entry, key, obj, obj_key)
|
||||
|
||||
new_snapshot = self.save_state(state)
|
||||
self.set_snapshot(SheerkaDataProvider.HeadFile, new_snapshot)
|
||||
return entry, obj_key
|
||||
return SheerkaDataProviderResult(original_obj, entry, obj_key, digest)
|
||||
|
||||
def list(self, entry, filter=None):
|
||||
"""
|
||||
@@ -814,16 +880,15 @@ class SheerkaDataProvider:
|
||||
return obj
|
||||
|
||||
def load_ref_if_needed(self, obj, load_origin=True):
|
||||
if not isinstance(obj, str):
|
||||
return obj, False
|
||||
if not obj.startswith(SheerkaDataProvider.REF_PREFIX):
|
||||
if isinstance(obj, SheerkaDataProviderRef):
|
||||
resolved = self.load_obj(obj.target, load_origin)
|
||||
return resolved, False
|
||||
|
||||
if not isinstance(obj, str) or not obj.startswith(SheerkaDataProvider.REF_PREFIX):
|
||||
return obj, False
|
||||
|
||||
resolved = self.load_obj(obj[len(SheerkaDataProvider.REF_PREFIX):], load_origin)
|
||||
if resolved is None:
|
||||
return obj, False
|
||||
|
||||
return resolved, True
|
||||
return (obj, False) if resolved is None else (resolved, True)
|
||||
|
||||
def save_ref_if_needed(self, save_ref, obj):
|
||||
if not save_ref:
|
||||
|
||||
@@ -5,7 +5,8 @@ from sheerkapickle import tags, utils, handlers
|
||||
|
||||
|
||||
def decode(sheerka, obj):
|
||||
return SheerkaUnpickler(sheerka).restore(json.loads(obj))
|
||||
decoded = SheerkaUnpickler(sheerka).restore(json.loads(obj))
|
||||
return decoded
|
||||
|
||||
|
||||
class SheerkaUnpickler:
|
||||
|
||||
@@ -74,6 +74,7 @@ class ConceptHandler(BaseHandler):
|
||||
# get value
|
||||
instance.set_metadata_value(ConceptParts(key), resolved_value)
|
||||
|
||||
instance.freeze_definition_hash()
|
||||
return instance
|
||||
|
||||
|
||||
@@ -92,6 +93,7 @@ class UserInputHandler(ConceptHandler):
|
||||
instance.__init__(data["text"], data["user_name"])
|
||||
instance.metadata.key = data[CONCEPT_ID][0]
|
||||
instance.metadata.id = data[CONCEPT_ID][1]
|
||||
instance.freeze_definition_hash()
|
||||
return instance
|
||||
|
||||
|
||||
@@ -122,6 +124,7 @@ class ReturnValueHandler(BaseHandler):
|
||||
|
||||
instance.metadata.key = data[CONCEPT_ID][0]
|
||||
instance.metadata.id = data[CONCEPT_ID][1]
|
||||
instance.freeze_definition_hash()
|
||||
return instance
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user