From 6935d2788f7a4da52e21ba1ed7849912b91106fc Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 13 Nov 2024 12:58:34 +0100 Subject: [PATCH] Change creation of matlab class names Build class names from file paths instead of the type IRI Add exception for class name AnatomicalEntity of v2.0 Fix bug preventing creating of ParameterSetting for v1.0 --- build.py | 7 +++++-- pipeline/translator.py | 42 +++++++++++++++++++++++++++++++----------- pipeline/utils.py | 23 ++++++++++++++++++++++- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/build.py b/build.py index 4a0e9081..05080711 100644 --- a/build.py +++ b/build.py @@ -2,7 +2,7 @@ import shutil from pipeline.translator import MATLABSchemaBuilder -from pipeline.utils import clone_sources, SchemaLoader, initialise_jinja_templates, save_resource_files, save_enumeration_classes +from pipeline.utils import clone_sources, SchemaLoader, initialise_jinja_templates, save_resource_files, save_enumeration_classes, get_class_name_map print("***************************************") print(f"Triggering the generation of MATLAB-Classes for openMINDS") @@ -23,11 +23,14 @@ schemas_file_paths = schema_loader.find_schemas(schema_version) # schemas_file_paths = [path for path in schemas_file_paths if "person" in path] # testing + class_name_map = get_class_name_map(schema_loader, schema_version) + for schema_file_path in schemas_file_paths: # Step 4 - translate and build each openMINDS schema as MATLAB class schema_root_path = schema_loader.schemas_sources + try: - MATLABSchemaBuilder(schema_file_path, schema_root_path, jinja_templates).build() + MATLABSchemaBuilder(schema_file_path, schema_root_path, class_name_map, jinja_templates).build() except Exception as e: print(f"Error while building schema {schema_file_path}: {e}") diff --git a/pipeline/translator.py b/pipeline/translator.py index bc4affbc..e943c95d 100644 --- a/pipeline/translator.py +++ b/pipeline/translator.py @@ -46,9 +46,10 @@ class MATLABSchemaBuilder(object): """ Class for building MATLAB schema classes """ - def __init__(self, schema_file_path:str, root_path:str, jinja_templates:Dict[str, Template]): + def __init__(self, schema_file_path:str, root_path:str, class_name_map:Dict[str, str], jinja_templates:Dict[str, Template]): self._parse_source_file_path(schema_file_path, root_path) + self._class_name_map = class_name_map with open(schema_file_path, "r") as schema_file: self._schema_payload = json.load(schema_file) @@ -128,12 +129,12 @@ def _extract_template_variables(self): # Resolve property class name in matlab if has_linked_type: possible_types = [ - f'{_generate_class_name(iri)}' + f'{_generate_class_name(iri, self._class_name_map)}' for iri in property_info[SCHEMA_PROPERTY_LINKED_TYPES] ] elif has_embedded_type: possible_types = [ - f'{_generate_class_name(iri)}' + f'{_generate_class_name(iri, self._class_name_map)}' for iri in property_info[SCHEMA_PROPERTY_EMBEDDED_TYPES] ] # todo: handle minItems maxItems, e.g. for axesOrigin elif "_formats" in property_info: @@ -141,9 +142,13 @@ def _extract_template_variables(self): possible_types = sorted(set([format_map[item] for item in property_info["_formats"]])) elif property_info.get("type") == "array": possible_types = [type_name_map[property_info["items"]["type"]]] + elif isinstance( property_info.get("type"), list ): + possible_types = [] else: possible_types = [type_name_map[property_info["type"]]] + + # Resolve property dimension in matlab if allow_multiple: size_attribute = "(1,:)" @@ -173,7 +178,12 @@ def _extract_template_variables(self): mixed_types_list = sorted(possible_types) - if len(possible_types) == 1: + if len(possible_types) == 0: + # Exception: ParameterSetting from v1. Uses validator instead of type restriction + possible_types = '' + possible_types_str = '' + possible_types_docstr = ", ".join(property_info.get("type")) + elif len(possible_types) == 1: possible_types = possible_types[0] possible_types_str = f'"{possible_types}"' possible_types_docstr = possible_types_docstr[0] @@ -181,7 +191,7 @@ def _extract_template_variables(self): possible_types_str = _list_to_string_array(possible_types, do_sort=True) possible_types_docstr = ", ".join(sorted(possible_types_docstr)) - class_name = _generate_class_name(schema[SCHEMA_PROPERTY_TYPE]).split(".")[-1] + class_name = _generate_class_name(schema[SCHEMA_PROPERTY_TYPE], self._class_name_map).split(".")[-1] possible_types = _create_mixedtype_full_class_name(class_name, property_name) template_property_attributes = { @@ -207,7 +217,7 @@ def _extract_template_variables(self): embedded_types = [ {'name':prop["name"],'types':prop["type_list"]} for prop in props if prop["is_embedded"] ] # Some schemas had the wrong type in older model versions, so this is unreliable - #class_name = _generate_class_name(schema[SCHEMA_PROPERTY_TYPE]).split(".")[-1] + #class_name = _generate_class_name(schema[SCHEMA_PROPERTY_TYPE], self._class_name_map).split(".")[-1] class_name = self._schema_class_name display_label_method_expression = _get_display_label_method_expression(class_name, schema["properties"].keys()) @@ -292,7 +302,7 @@ def _create_matlab_name(json_name): """Remove the openMINDS prefix from a name""" return json_name.split('/')[-1] -def _generate_class_name(iri): +def _generate_class_name(iri, class_name_map): """ Generate a class name from an IRI. E.g https://openminds.ebrains.eu/core/Subject -> openminds.core.Subject @@ -302,9 +312,16 @@ def _generate_class_name(iri): else: # v4 and higher parts = iri.split(':') - for i in range(len(parts) - 1): - parts[i] = parts[i].lower() - return "openminds." + ".".join(parts) + type_name = parts[-1] + + # Ensure first letter of type_name is capitalized + type_name = type_name[0].upper() + type_name[1:] + + return class_name_map[type_name] + + #for i in range(len(parts) - 1): + # parts[i] = parts[i].lower() + # return "openminds." + ".".join(parts) def _to_label(class_name_list): @@ -477,6 +494,9 @@ def _create_property_validator_functions(name, property_info): elif "time" in property_info.get("_formats") == "time": validation_functions += [f"mustBeValidTime({property_name})"] + if isinstance( property_info.get("type"), list ): + validation_functions += [f'mustBeA({property_name}, ["numeric", "string"])'] + if has_linked_type or has_embedded_type: if not allow_multiple: validation_functions += [f'mustBeSpecifiedLength({property_name}, 0, 1)'] @@ -546,4 +566,4 @@ def _parse_schema_type(type_specifier): if __name__ == "__main__": - error('Not implemented yet') + raise NotImplementedError diff --git a/pipeline/utils.py b/pipeline/utils.py index 1a6ac608..6dcfc60c 100644 --- a/pipeline/utils.py +++ b/pipeline/utils.py @@ -136,6 +136,27 @@ def initialise_jinja_templates(autoescape:bool=None): return jinja_templates +def get_class_name_map(schema_loader, version): + # Extract all schema files + root_path = schema_loader.schemas_sources + schema_files = schema_loader.find_schemas(version) + schema_files.sort() + + # Build a list for all the enumeration members + class_name_map = {} + + for schema_file in schema_files: + schema_info = _parse_source_file_path(schema_file, root_path) + class_name_map[schema_info['type_name']] = _get_matlab_class_name(schema_info) + + # Add some exceptions + if version == "v2.0": + print(class_name_map["CustomAnatomicalEntity"]) + class_name_map["AnatomicalEntity"] = class_name_map["CustomAnatomicalEntity"] + + + return class_name_map + def camel_case(text_string: str): return text_string[0].lower() + text_string[1:] @@ -292,4 +313,4 @@ def _get_template_variables(enum_type, schema_files, root_path): return {'types': template_variable_list } elif enum_type == "Models": - return {'models': sorted(set(template_variable_list)) } \ No newline at end of file + return {'models': sorted(set(template_variable_list)) }