import hashlib from enum import Enum import logging from core.tokenizer import Tokenizer, TokenKind log = logging.getLogger(__name__) class ConceptParts(Enum): """ Helper class, Note quite sure that is it that useful I guess, I was learning nums with Python... """ WHERE = "where" PRE = "pre" POST = "post" BODY = "body" class Concept: """ Default concept object A concept is a the base object of our universe Everything is a concept """ props_to_serialize = ("id", "is_builtin", "key", "name", "where", "pre", "post", "body", "desc", "obj") props_for_digest = ("is_builtin", "key", "name", "where", "pre", "post", "body", "desc") concept_parts = set(item.value for item in ConceptParts) PROPERTY_PREFIX = "__var__" def __init__(self, name=None, is_builtin=False, is_unique=False, key=None, where=None, pre=None, post=None, body=None, desc=None, obj=None): self.name = str(name) if name else None self.is_builtin = is_builtin self.is_unique = is_unique self.key = str(key) if key else None # name od the concept, where prop are replaced. to ease search self.where = where # condition to recognize variables in name self.pre = pre # list of pre conditions before calling the main function self.post = post # list of post conditions after calling the main function self.body = body # main method, can also be the value of the concept self.desc = desc self.id = None # unique identifier for a concept. The id will never be modified self.obj = obj # main of principal property of the concept self.props = {} # list of Property for this concept self.functions = {} # list of helper functions self.codes = {} # cached ast for the where, pre, post and body parts def __repr__(self): return f"({self.id}){self.name}" def __eq__(self, other): if not isinstance(other, Concept): return False # check the attributes for prop in self.props_to_serialize: if getattr(self, prop) != getattr(other, prop): print(prop) return False # check the props (Concept variables) for var_name, p in self.props.items(): if p != other.props[var_name]: return False return True def __hash__(self): return hash(self.name) def get_key(self): return self.key def init_key(self, tokens=None): """ Create the key for this concept. Must be called only when the concept if fully initialized The method is not called set_key to make sure that no other class set the key by mistake :param tokens: :return: """ if self.key is not None: return self if tokens is None: tokens = iter(Tokenizer(self.name)) variables = list(self.props.keys()) key = "" first = True for token in tokens: if token.type == TokenKind.EOF: break if token.type == TokenKind.WHITESPACE: continue if not first: key += " " if variables is not None and token.value in variables: key += self.PROPERTY_PREFIX + str(variables.index(token.value)) else: key += token.value[1:-1] if token.type == TokenKind.STRING else token.value first = False self.key = key return self def add_codes(self, codes): """ Gets the ASTs for 'where', 'pre', 'post' and 'body' There ASTs are know when the concept is freshly parsed. So the values are kept in cache. For concepts loaded from sdp, these ASTs must be created again TODO : Seems to be a service method. Can be put somewhere else :param codes: :return: """ possibles_codes = self.concept_parts if codes is None: return for key in codes: if key in possibles_codes: self.codes[ConceptParts(key)] = codes[key] return self def get_digest(self): """ Returns the digest of the event :return: hexa form of the sha256 """ return hashlib.sha256(f"Concept:{self.to_dict(self.props_for_digest)}".encode("utf-8")).hexdigest() def to_dict(self, props_to_use=None): """ Returns a dict representing 'self' :return: """ props_to_use = props_to_use or self.props_to_serialize props_as_dict = dict((prop, getattr(self, prop)) for prop in props_to_use) props_as_dict["props"] = [(p, self.props[p].value) for p in self.props] return props_as_dict def from_dict(self, as_dict): """ Initializes 'self' from a dict :param as_dict: :return: """ for prop in self.props_to_serialize: if prop in as_dict: setattr(self, prop, as_dict[prop]) if "props" in as_dict: for n, v in as_dict["props"]: self.set_prop(n, v) return self def update_from(self, other): """ Update self using the properties of another concept This method is to mimic the class to instance pattern 'other' is the class, the template, and 'self' is a new instance :param other: :return: """ if other is None: return self self.from_dict(other.to_dict()) # for prop in self.props_to_serialize: # setattr(self, prop, getattr(other, prop)) return self def set_prop(self, prop_name, prop_value=None): self.props[prop_name] = Property(prop_name, prop_value) return self def set_prop_by_index(self, index, prop_value): prop_name = list(self.props.keys())[index] self.props[prop_name] = Property(prop_name, prop_value) return self class Property: """ Defines the variables of a concept It as its specific class, because from experience, property management is more complex than a key/value pair """ def __init__(self, name, value): self.name = name self.value = value def __repr__(self): return f"{self.name}={self.value}" def __eq__(self, other): if not isinstance(other, Property): return False return self.name == other.name and self.value == other.value def __hash__(self): return hash((self.name, self.value))