diff --git a/src/caosadvancedtools/models/parser.py b/src/caosadvancedtools/models/parser.py index 5e1532e03690e753b8926b87b01db4e3a89f2c4c..6d028a28f5c17897adb861c171d53cfc65368b2e 100644 --- a/src/caosadvancedtools/models/parser.py +++ b/src/caosadvancedtools/models/parser.py @@ -21,6 +21,7 @@ import sys import caosdb as db import yaml +from caosdb.common.datatype import is_reference from .data_model import DataModel @@ -43,8 +44,23 @@ KEYWORDS_IGNORED = [ ] +def _get_listdatatype(dtype): + """matches a string to check whehter the type definition is a list + + returns the type within the list or None, if it cannot be matched with a + list definition + """ + match = re.match(r"^LIST[(](?P<dt>.*)[)]$", dtype) + + if match is None: + return None + else: + return match.group("dt") + # Taken from https://stackoverflow.com/a/53647080, CC-BY-SA, 2018 by # https://stackoverflow.com/users/2572431/augurar + + class SafeLineLoader(yaml.SafeLoader): """Load a line and keep meta-information. @@ -56,6 +72,7 @@ class SafeLineLoader(yaml.SafeLoader): mapping = super().construct_mapping(node, deep=deep) # Add 1 so line numbering starts at 1 mapping['__line__'] = node.start_mark.line + 1 + return mapping # End of https://stackoverflow.com/a/53647080 @@ -76,12 +93,14 @@ class YamlDefinitionError(RuntimeError): def parse_model_from_yaml(filename): """Shortcut if the Parser object is not needed.""" parser = Parser() + return parser.parse_model_from_yaml(filename) def parse_model_from_string(string): """Shortcut if the Parser object is not needed.""" parser = Parser() + return parser.parse_model_from_string(string) @@ -105,6 +124,7 @@ class Parser(object): """ with open(filename, 'r') as outfile: ymlmodel = yaml.load(outfile, Loader=SafeLineLoader) + return self._create_model_from_dict(ymlmodel) def parse_model_from_string(self, string): @@ -121,6 +141,7 @@ class Parser(object): The created DataModel """ ymlmodel = yaml.load(string, Loader=SafeLineLoader) + return self._create_model_from_dict(ymlmodel) def _create_model_from_dict(self, ymlmodel): @@ -148,6 +169,7 @@ class Parser(object): # a record type with the name of the element. # The retrieved entity will be added to the model. # If no entity with that name is found an exception is raised. + if "extern" not in ymlmodel: ymlmodel["extern"] = [] @@ -196,11 +218,14 @@ class Parser(object): out : str If `name` was a string, return it. Else return str(`name`). """ + if name is None: print("WARNING: Name of this context is None: {}".format(context), file=sys.stderr) + if not isinstance(name, str): name = str(name) + return name def _add_entity_to_model(self, name, definition): @@ -208,9 +233,11 @@ class Parser(object): Properties are also initialized. """ + if name == "__line__": return name = self._stringify(name) + if name not in self.model: self.model[name] = None @@ -222,7 +249,7 @@ class Parser(object): # is it a property and "datatype" in definition # but not a list - and not definition["datatype"].startswith("LIST")): + and not is_reference(db.Property(datatype=definition["datatype"]))): # and create the new property self.model[name] = db.Property(name=name, @@ -235,6 +262,7 @@ class Parser(object): if prop_type in definition: # Empty property mapping should be allowed. + if definition[prop_type] is None: definition[prop_type] = {} try: @@ -245,6 +273,7 @@ class Parser(object): except AttributeError as ate: if ate.args[0].endswith("'items'"): line = definition["__line__"] + if isinstance(definition[prop_type], list): line = definition[prop_type][0]["__line__"] raise YamlDefinitionError(line) from None @@ -252,26 +281,24 @@ class Parser(object): def _add_to_recordtype(self, ent_name, props, importance): """Add properties to a RecordType.""" + for n, e in props.items(): if n in KEYWORDS: if n in KEYWORDS_IGNORED: continue raise YamlDefinitionError("Unexpected keyword in line {}: {}".format( props["__line__"], n)) + if n == "__line__": continue n = self._stringify(n) - if isinstance(e, dict) and "datatype" in e and e["datatype"].startswith("LIST"): - match = re.match(r"LIST[(](.*)[)]", e["datatype"]) - - if match is None: - raise ValueError("List datatype definition is wrong") - dt = db.LIST(match.group(1)) - self.model[ent_name].add_property(name=n, - importance=importance, - datatype=dt - ) + if (isinstance(e, dict) and "datatype" in e + and (_get_listdatatype(e["datatype"]) is not None)): + self.model[ent_name].add_property( + name=n, + importance=importance, + datatype=_get_listdatatype(e["datatype"])) else: self.model[ent_name].add_property(name=n, importance=importance) @@ -288,6 +315,7 @@ class Parser(object): def _treat_entity(self, name, definition, line=None): """Parse the definition and the information to the entity.""" + if name == "__line__": return name = self._stringify(name) @@ -366,7 +394,11 @@ class Parser(object): else: # get the datatype try: - value.datatype = db.__getattribute__(value.datatype) + if _get_listdatatype(value.datatype) is None: + value.datatype = db.__getattribute__(value.datatype) + else: + value.datatype = db.__getattribute__( + _get_listdatatype(value.datatype)) except AttributeError: raise ValueError("Unknown Datatype.")