Refactored Concept class for better separation of metadata, compiled and values

This commit is contained in:
2020-01-17 17:27:54 +01:00
parent 3789ef25d1
commit a7b239c167
27 changed files with 614 additions and 349 deletions
+126 -29
View File
@@ -1,5 +1,6 @@
import hashlib
from dataclasses import dataclass
from collections import namedtuple
from dataclasses import dataclass, field
from enum import Enum
from core.sheerka_logger import get_logger
@@ -10,7 +11,7 @@ PROPERTIES_FOR_DIGEST = ("name", "key",
"definition", "definition_type",
"is_builtin", "is_unique",
"where", "pre", "post", "body",
"desc")
"desc", "props")
PROPERTIES_TO_SERIALIZE = PROPERTIES_FOR_DIGEST + tuple(["id"])
PROPERTIES_FOR_NEW = ("where", "pre", "post", "body", "desc")
VARIABLE_PREFIX = "__var__"
@@ -44,9 +45,13 @@ class ConceptMetadata:
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)
props: list # list properties, with their default values
is_evaluated: bool = False # True is the concept is evaluated by sheerka.eval_concept()
simplec = namedtuple("concept", "name body") # for simple concept (tests purposes only)
class Concept:
"""
Default concept object
@@ -65,7 +70,8 @@ class Concept:
definition=None,
definition_type=None,
desc=None,
id=None):
id=None,
props=None):
metadata = ConceptMetadata(
str(name) if name else None,
@@ -79,12 +85,14 @@ class Concept:
definition,
definition_type,
desc,
id
id,
props or []
)
self.metadata = metadata
self.props = {} # list of Property for this concept
self.cached_asts = {} # cached ast for the where, pre, post and body parts
self.compiled = {} # cached ast for the where, pre, post and body parts
self.values = {} # values of metadata once resolved
self.props = {} # resolved properties of this concept
self.bnf = None
self.log = get_logger("core." + self.__class__.__name__)
self.init_log = get_logger("init.core." + self.__class__.__name__)
@@ -93,10 +101,17 @@ class Concept:
return f"({self.metadata.id}){self.metadata.name}"
def __eq__(self, other):
if isinstance(other, simplec):
return self.name == other.name and self.body == other.body
if id(self) == id(other):
return True
if not isinstance(other, Concept):
return False
# check the attributes
# check the metadata
for prop in PROPERTIES_TO_SERIALIZE:
# print(prop) # use full to know which id does not match
my_value = getattr(self.metadata, prop)
@@ -119,9 +134,19 @@ class Concept:
if my_value != other_value:
return False
# check the props (Concept variables)
for var_name, p in self.props.items():
if p != other.props[var_name]:
# checks the values
if len(self.values) != len(other.values):
return False
for metadata in self.values:
if self.get_metadata_value(metadata) != other.get_metadata_value(metadata):
return False
if len(self.props) != len(other.props):
return False
for prop in self.props:
if self.get_prop(prop) != other.get_prop(prop):
return False
return True
@@ -138,6 +163,33 @@ class Concept:
name = self.name if 'metadata' in vars(self) else 'Concept'
raise AttributeError(f"'{name}' concept has no attribute '{item}'")
def def_prop(self, prop_name: str, default_value=None):
"""
Adds a property to the metadata
:param prop_name:
:param default_value:
:return:
"""
assert default_value is None or isinstance(default_value, str) # default properties will have to be evaluated
self.metadata.props.append((prop_name, default_value))
self.props[prop_name] = Property(prop_name, None) # do not set the default value
# why not setting props to the default values ?
# Because it may not be the real values, as metadata.props need to be evaluated
return self
def def_prop_by_index(self, index: int, value):
"""
Re-assign a value to a property (mainly used by ExactConceptParser)
:param index:
:param value:
:return:
"""
assert value is None or isinstance(value, str) # default properties will have to be evaluated
prop = self.metadata.props[index]
self.metadata.props[index] = (prop[0], value)
return self
@property
def name(self):
return self.metadata.name
@@ -165,7 +217,7 @@ class Concept:
if tokens is None:
tokens = list(Tokenizer(self.metadata.name))
variables = list(self.props.keys()) if len(core.utils.strip_tokens(tokens, True)) > 1 else []
variables = [p[0] for p in self.metadata.props] if len(core.utils.strip_tokens(tokens, True)) > 1 else []
key = ""
first = True
@@ -175,8 +227,8 @@ class Concept:
if token.type == TokenKind.WHITESPACE:
continue
if not first:
key += " " # spaces are normalized
if variables is not None and token.value in variables:
key += " " # spaces are normalized
if 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
@@ -187,7 +239,7 @@ class Concept:
@property
def body(self):
return self.metadata.body
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
def add_codes(self, codes):
"""
@@ -204,7 +256,7 @@ class Concept:
return
for key in codes:
self.cached_asts[key] = codes[key]
self.compiled[key] = codes[key]
return self
@@ -224,7 +276,6 @@ class Concept:
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):
@@ -235,10 +286,11 @@ class Concept:
"""
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)
if prop == "props":
for name, value in as_dict[prop]:
self.def_prop(name, value)
else:
setattr(self.metadata, prop, as_dict[prop])
return self
def update_from(self, other):
@@ -252,24 +304,69 @@ class Concept:
if other is None:
return self
if id(other) == id(self):
return self
# update metadata
self.from_dict(other.to_dict())
# for prop in self.props_to_serialize:
# setattr(self, prop, getattr(other, prop))
# update values
for k, v in other.values.items():
self.values[k] = v
# update properties
for k, v in other.props.items():
self.set_prop(k, v.value)
return self
def set_prop(self, prop_name: str, prop_value=None):
self.props[prop_name] = Property(prop_name, prop_value) # Python 3.x order is kept in dictionaries
return self
def set_prop_by_index(self, index: int, prop_value):
prop_name = list(self.props.keys())[index]
def set_prop(self, prop_name: str, prop_value):
"""Directly sets a value to a property"""
self.props[prop_name] = Property(prop_name, prop_value)
return self
def get_prop(self, prop_name: str):
return self.props[prop_name].value
def set_metadata_value(self, metadata: ConceptParts, value):
"""
Set the resolved value of a metadata (not the metadata itself)
:param metadata:
:param value:
:return:
"""
self.values[metadata] = value
def get_metadata_value(self, metadata: ConceptParts):
"""
Gets the resolved value of a metadata
:param metadata:
:return:
"""
return self.values[metadata]
def auto_init(self):
"""
Sometimes (for tests purposes)
You don't need the full process of evaluation to to get the values of the concept
Directly use the values of the metadata
:return:
"""
if self.metadata.is_evaluated:
return self
for metadata in ConceptParts:
value = getattr(self.metadata, metadata.value)
if value is not None:
self.values[metadata] = value
for prop, value in self.metadata.props:
self.set_prop(prop, value)
self.metadata.is_evaluated = True
return self
class Property:
"""
@@ -303,6 +400,6 @@ class DoNotResolve:
For example, if you want to set a value to the BODY that will not change when
when the concept will be evaluated,
set concept.cached_asts[BODY] to DoNotResolve(value)
set concept.compiled[BODY] to DoNotResolve(value)
"""
value: object