Files
Sheerka-Old/core/concept.py
T

268 lines
7.6 KiB
Python

import hashlib
from dataclasses import dataclass
from enum import Enum
import logging
from core.tokenizer import Tokenizer, TokenKind
log = logging.getLogger(__name__)
PROPERTIES_FOR_DIGEST = ("name", "key",
"definition", "definition_type",
"is_builtin", "is_unique",
"where", "pre", "post", "body",
"desc")
PROPERTIES_TO_SERIALIZE = PROPERTIES_FOR_DIGEST + tuple(["id"])
VARIABLE_PREFIX = "__var__"
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"
@staticmethod
def get_parts():
return set(item.value for item in ConceptParts)
@dataclass
class ConceptMetadata:
name: str
is_builtin: bool
is_unique: bool
key: str # name od the concept, where prop are replaced. to ease search
body: str # main method, can also be the value of the concept
where: str # condition to recognize variables in name
pre: str # list of pre conditions before calling the main function
post: str # list of post conditions after calling the main function
definition: str # regex used to define the concept
definition_type: str # definition can be done with something else than regex
desc: str # possible description for the concept
id: str # unique identifier for a concept. The id will never be modified (but the key can)
class Concept:
"""
Default concept object
A concept is a the base object of our universe
Everything is a concept
"""
def __init__(self, name=None,
is_builtin=False,
is_unique=False,
key=None,
body=None,
where=None,
pre=None,
post=None,
definition=None,
definition_type=None,
desc=None,
id=None):
metadata = ConceptMetadata(
str(name) if name else None,
is_builtin,
is_unique,
str(key) if key else None,
body,
where,
pre,
post,
definition,
definition_type,
desc,
id
)
self.metadata = metadata
self.props = {} # list of Property for this concept
self.cached_asts = {} # cached ast for the where, pre, post and body parts
def __repr__(self):
return f"({self.metadata.id}){self.metadata.name}"
def __eq__(self, other):
if not isinstance(other, Concept):
return False
# check the attributes
for prop in PROPERTIES_TO_SERIALIZE:
if getattr(self.metadata, prop) != getattr(other.metadata, prop):
# print(prop) # use full to know which id does not match
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.metadata.name)
@property
def name(self):
return self.metadata.name
@property
def id(self):
return self.metadata.id
@property
def key(self):
return self.metadata.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.metadata.key is not None:
return self
if tokens is None:
tokens = iter(Tokenizer(self.metadata.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 += VARIABLE_PREFIX + str(variables.index(token.value))
else:
key += token.value[1:-1] if token.type == TokenKind.STRING else token.value
first = False
self.metadata.key = key
return self
@property
def body(self):
return self.metadata.body
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 = ConceptParts.get_parts()
if codes is None:
return
for key in codes:
if key in possibles_codes:
self.cached_asts[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(PROPERTIES_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 PROPERTIES_TO_SERIALIZE
props_as_dict = dict((prop, getattr(self.metadata, 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 PROPERTIES_TO_SERIALIZE:
if prop in as_dict:
setattr(self.metadata, 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: str, prop_value=None):
self.props[prop_name] = Property(prop_name, prop_value)
return self
def set_prop_by_index(self, index: int, prop_value):
prop_name = list(self.props.keys())[index]
self.props[prop_name] = Property(prop_name, prop_value)
return self
def get_prop(self, prop_name: str):
return self.props[prop_name].value
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))