From 7944f6e75955f2f2a75a4c8e1892e1c52bb83ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sat, 12 Oct 2024 13:54:34 +0200 Subject: [PATCH 01/12] Fix some attribute types in classes DiagramObjectGluePoint, VCompIEEEType2, WindContPType3IEC for cpp and modernpython MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 67813dd1..155c6a39 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -8,6 +8,7 @@ import xmltodict from bs4 import BeautifulSoup +logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) @@ -249,6 +250,7 @@ def subClasses(self): def setSubClasses(self, classes): self.subclasses = classes + @staticmethod def _simple_float_attribute(attr): if "dataType" in attr: return attr["label"] == "value" and attr["dataType"] == "#Float" @@ -856,7 +858,7 @@ def _get_attribute_type(attribute: dict, is_an_enum_class: bool) -> str: attribute_type = "primitive" elif is_an_enum_class: attribute_type = "enum" - elif attribute.get("multiplicity") in ("M:0..n", "M:1..n"): + elif attribute.get("multiplicity") in ("M:0..n", "M:1..n", "M:2..n"): attribute_type = "list" return attribute_type From 3b6e9d2260909e0fc3f65a328a07faec8559bc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sat, 12 Oct 2024 14:19:50 +0200 Subject: [PATCH 02/12] Add class property "is_a_primitive_class" and attribute property "is_primitive_float_attribute" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 9 +++++++-- cimgen/languages/cpp/lang_pack.py | 6 +----- cimgen/languages/java/lang_pack.py | 6 +----- cimgen/languages/javascript/lang_pack.py | 2 ++ cimgen/languages/modernpython/lang_pack.py | 2 ++ cimgen/languages/python/lang_pack.py | 2 ++ 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 155c6a39..6d32e9bb 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -211,6 +211,7 @@ def __init__(self, rdfsEntry): self.origin_list = [] self.super = rdfsEntry.subClassOf() self.subclasses = [] + self.stereotype = rdfsEntry.stereotype() def attributes(self): return self.attribute_list @@ -282,6 +283,9 @@ def is_a_float_class(self): return False return True + def is_a_primitive_class(self): + return self.stereotype == "Primitive" + def get_profile_name(descriptions): for list_elem in descriptions: @@ -382,8 +386,7 @@ def _add_class(classes_map, rdfs_entry): """ if rdfs_entry.label() in classes_map: logger.error("Class {} already exists".format(rdfs_entry.label())) - if rdfs_entry.label() != "String": - classes_map[rdfs_entry.label()] = CIMComponentDefinition(rdfs_entry) + classes_map[rdfs_entry.label()] = CIMComponentDefinition(rdfs_entry) def _add_profile_to_packages(profile_name, short_profile_name, profile_uri_list): @@ -481,6 +484,7 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): "enum_instances": elem_dict[class_name].enum_instances(), "is_an_enum_class": elem_dict[class_name].is_an_enum_class(), "is_a_float_class": elem_dict[class_name].is_a_float_class(), + "is_a_primitive_class": elem_dict[class_name].is_a_primitive_class(), "langPack": lang_pack, "sub_class_of": elem_dict[class_name].superClass(), "sub_classes": elem_dict[class_name].subClasses(), @@ -514,6 +518,7 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): attribute["is_enum_attribute"] = _get_bool_string(attribute_type == "enum") attribute["is_list_attribute"] = _get_bool_string(attribute_type == "list") attribute["is_primitive_attribute"] = _get_bool_string(attribute_type == "primitive") + attribute["is_primitive_float_attribute"] = _get_bool_string(elem_dict[attribute_class].is_a_float_class()) attribute["attribute_class"] = attribute_class class_details["attributes"].sort(key=lambda d: d["label"]) diff --git a/cimgen/languages/cpp/lang_pack.py b/cimgen/languages/cpp/lang_pack.py index 09d73a97..1c548adc 100644 --- a/cimgen/languages/cpp/lang_pack.py +++ b/cimgen/languages/cpp/lang_pack.py @@ -63,11 +63,7 @@ def run_template(output_path, class_details): else: templates = template_files - if ( - class_details["class_name"] == "Integer" - or class_details["class_name"] == "Boolean" - or class_details["class_name"] == "Date" - ): + if class_details["class_name"] in ("String", "Integer", "Boolean", "Date"): # These classes are defined already # We have to implement operators for them return diff --git a/cimgen/languages/java/lang_pack.py b/cimgen/languages/java/lang_pack.py index fd671f0e..fa47d303 100644 --- a/cimgen/languages/java/lang_pack.py +++ b/cimgen/languages/java/lang_pack.py @@ -57,11 +57,7 @@ def run_template(output_path, class_details): else: templates = template_files - if ( - class_details["class_name"] == "Integer" - or class_details["class_name"] == "Boolean" - or class_details["class_name"] == "Date" - ): + if class_details["class_name"] in ("String", "Integer", "Boolean", "Date"): # These classes are defined already # We have to implement operators for them return diff --git a/cimgen/languages/javascript/lang_pack.py b/cimgen/languages/javascript/lang_pack.py index f216816a..a0e15968 100644 --- a/cimgen/languages/javascript/lang_pack.py +++ b/cimgen/languages/javascript/lang_pack.py @@ -102,6 +102,8 @@ def select_primitive_render_function(class_details): # This is the function that runs the template. def run_template(output_path, class_details): + if class_details["class_name"] == "String": + return for index, attribute in enumerate(class_details["attributes"]): if not attribute["is_used"]: diff --git a/cimgen/languages/modernpython/lang_pack.py b/cimgen/languages/modernpython/lang_pack.py index c4f9e836..1f321f0a 100644 --- a/cimgen/languages/modernpython/lang_pack.py +++ b/cimgen/languages/modernpython/lang_pack.py @@ -82,6 +82,8 @@ def _get_type_and_default(text, renderer) -> tuple[str, str]: def run_template(output_path, class_details): + if class_details["class_name"] == "String": + return for template_info in template_files: resource_file = Path( os.path.join( diff --git a/cimgen/languages/python/lang_pack.py b/cimgen/languages/python/lang_pack.py index 08167e01..a5af5ab5 100644 --- a/cimgen/languages/python/lang_pack.py +++ b/cimgen/languages/python/lang_pack.py @@ -74,6 +74,8 @@ def _set_default(text, render): def run_template(output_path, class_details): + if class_details["class_name"] == "String": + return for template_info in template_files: class_file = os.path.join(output_path, class_details["class_name"] + template_info["ext"]) _write_templated_file(class_file, class_details, template_info["filename"]) From bded89c6db19288406819eedcc005ca1bfbcb4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 13 Oct 2024 12:48:33 +0200 Subject: [PATCH 03/12] Fix type and default of some attributes for modernpython (for enums and attribute types Date, DateTime, MonthDay, Status, StreetAddress, StreetDetail, TownDetail) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 20 +++--------- cimgen/languages/modernpython/lang_pack.py | 31 +++++++------------ .../templates/cimpy_class_template.mustache | 4 +-- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 6d32e9bb..afa64950 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -512,8 +512,7 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): subsequent_indent=" " * 6, ) attribute_class = _get_attribute_class(attribute) - is_an_enum_class = attribute_class in elem_dict and elem_dict[attribute_class].is_an_enum_class() - attribute_type = _get_attribute_type(attribute, is_an_enum_class) + attribute_type = _get_attribute_type(attribute, elem_dict[attribute_class]) attribute["is_class_attribute"] = _get_bool_string(attribute_type == "class") attribute["is_enum_attribute"] = _get_bool_string(attribute_type == "enum") attribute["is_list_attribute"] = _get_bool_string(attribute_type == "list") @@ -842,26 +841,17 @@ def _get_attribute_class(attribute: dict) -> str: return _get_rid_of_hash(name) -def _get_attribute_type(attribute: dict, is_an_enum_class: bool) -> str: +def _get_attribute_type(attribute: dict, class_infos: CIMComponentDefinition) -> str: """Get the type of an attribute: "class", "enum", "list", or "primitive". :param attribute: Dictionary with information about an attribute of a class. - :param is_an_enum_class: Is this attribute an enumation? + :param class_infos: Information about the attribute class. :return: Type of the attribute. """ - so_far_not_primitive = _get_attribute_class(attribute) in ( - "Date", - "DateTime", - "MonthDay", - "Status", - "StreetAddress", - "StreetDetail", - "TownDetail", - ) attribute_type = "class" - if "dataType" in attribute and not so_far_not_primitive: + if class_infos.is_a_primitive_class() or class_infos.is_a_float_class(): attribute_type = "primitive" - elif is_an_enum_class: + elif class_infos.is_an_enum_class(): attribute_type = "enum" elif attribute.get("multiplicity") in ("M:0..n", "M:1..n", "M:2..n"): attribute_type = "list" diff --git a/cimgen/languages/modernpython/lang_pack.py b/cimgen/languages/modernpython/lang_pack.py index 1f321f0a..52f8eef7 100644 --- a/cimgen/languages/modernpython/lang_pack.py +++ b/cimgen/languages/modernpython/lang_pack.py @@ -47,7 +47,7 @@ def get_class_location(class_name, class_map, version): # NOSONAR partials = {} -# called by chevron, text contains the label {{dataType}}, which is evaluated by the renderer (see class template) +# called by chevron, text contains the attribute infos, which are evaluated by the renderer (see class template) def _set_default(text, render): return _get_type_and_default(text, render)[1] @@ -56,29 +56,22 @@ def _set_type(text, render): return _get_type_and_default(text, render)[0] -def _get_type_and_default(text, renderer) -> tuple[str, str]: - result = renderer(text) - # the field {{dataType}} either contains the multiplicity of an attribute if it is a reference or otherwise the - # datatype of the attribute. If no datatype is set and there is also no multiplicity entry for an attribute, the - # default value is set to None. The multiplicity is set for all attributes, but the datatype is only set for basic - # data types. If the data type entry for an attribute is missing, the attribute contains a reference and therefore - # the default value is either None or [] depending on the multiplicity. See also write_python_files - # The default will be copied as-is, hence the possibility to have default or default_factory. - if result in ["M:1", "M:0..1", "M:1..1", ""]: +def _get_type_and_default(text, render) -> tuple[str, str]: + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if attribute_json["is_class_attribute"]: return ("Optional[str]", "default=None") - elif result in ["M:0..n", "M:1..n"] or "M:" in result: + elif attribute_json["is_list_attribute"]: return ("list", "default_factory=list") - - result = result.split("#")[1] - if result in ["integer", "Integer"]: + elif attribute_json["is_primitive_float_attribute"]: + return ("float", "default=0.0") + elif attribute_json["attribute_class"] == "Integer": return ("int", "default=0") - elif result in ["String", "DateTime", "Date"]: - return ("str", 'default=""') - elif result == "Boolean": + elif attribute_json["attribute_class"] == "Boolean": return ("bool", "default=False") else: - # everything else should be a float - return ("float", "default=0.0") + # everything else should be a string + return ("str", 'default=""') def run_template(output_path, class_details): diff --git a/cimgen/languages/modernpython/templates/cimpy_class_template.mustache b/cimgen/languages/modernpython/templates/cimpy_class_template.mustache index 08e125d9..e6e94911 100644 --- a/cimgen/languages/modernpython/templates/cimpy_class_template.mustache +++ b/cimgen/languages/modernpython/templates/cimpy_class_template.mustache @@ -23,8 +23,8 @@ class {{class_name}}({{sub_class_of}}): """ {{#attributes}} - {{label}}: {{#setType}}{{dataType}}{{/setType}} = Field( - {{#setDefault}}{{dataType}}{{/setDefault}}, + {{label}}: {{#setType}}{{.}}{{/setType}} = Field( + {{#setDefault}}{{.}}{{/setDefault}}, json_schema_extra={ "in_profiles": [ {{#attr_origin}} From 037dc1c45b9d3218025a67ca5835d361a297ca7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 13 Oct 2024 13:15:36 +0200 Subject: [PATCH 04/12] Fix handling of Decimal as float class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index afa64950..97bbd016 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -258,7 +258,7 @@ def _simple_float_attribute(attr): return False def is_a_float_class(self): - if self.about == "Float": + if self.about in ("Float", "Decimal"): return True simple_float = False for attr in self.attribute_list: From 08dfa2e720854cca4356fa2dfed0b70d99b18420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 13 Oct 2024 13:33:41 +0200 Subject: [PATCH 05/12] Remove generation of unused classes for modernpython MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/languages/modernpython/lang_pack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cimgen/languages/modernpython/lang_pack.py b/cimgen/languages/modernpython/lang_pack.py index 52f8eef7..d25f903c 100644 --- a/cimgen/languages/modernpython/lang_pack.py +++ b/cimgen/languages/modernpython/lang_pack.py @@ -75,7 +75,7 @@ def _get_type_and_default(text, render) -> tuple[str, str]: def run_template(output_path, class_details): - if class_details["class_name"] == "String": + if class_details["is_a_primitive_class"] or class_details["is_a_float_class"]: return for template_info in template_files: resource_file = Path( From 4ad48d7223f9bf3f0b015b3f1a9ea63b2cfc4bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 13 Oct 2024 18:40:24 +0200 Subject: [PATCH 06/12] Improve setting of long profile names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 77 +++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 97bbd016..55b3b411 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -287,22 +287,6 @@ def is_a_primitive_class(self): return self.stereotype == "Primitive" -def get_profile_name(descriptions): - for list_elem in descriptions: - # only for CGMES-Standard - rdfsEntry = RDFSEntry(list_elem) - if rdfsEntry.stereotype() == "Entsoe": - return rdfsEntry.about() - - -def get_short_profile_name(descriptions): - for list_elem in descriptions: - # only for CGMES-Standard - rdfsEntry = RDFSEntry(list_elem) - if rdfsEntry.label() == "shortName": - return rdfsEntry.fixed() - - def wrap_and_clean(txt: str, width: int = 120, initial_indent="", subsequent_indent=" ") -> str: """ Used for comments: make them fit within character, including indentation. @@ -325,9 +309,9 @@ def wrap_and_clean(txt: str, width: int = 120, initial_indent="", subsequent_ind short_package_name = {} +long_profile_names = {} package_listed_by_short_name = {} cim_namespace = "" -profiles = {} def _rdfs_entry_types(rdfs_entry: RDFSEntry, version) -> list: @@ -389,23 +373,21 @@ def _add_class(classes_map, rdfs_entry): classes_map[rdfs_entry.label()] = CIMComponentDefinition(rdfs_entry) -def _add_profile_to_packages(profile_name, short_profile_name, profile_uri_list): +def _add_profile_to_packages(profile_name: str, short_profile_name: str, profile_uri_list: list[str]) -> None: """ - Add profile_uris + Add profile_uris and set long profile_name. """ - if profile_name not in profiles and profile_uri_list: - profiles[profile_name] = profile_uri_list - else: - profiles[profile_name].extend(profile_uri_list) - if short_profile_name not in package_listed_by_short_name and profile_uri_list: - package_listed_by_short_name[short_profile_name] = profile_uri_list - else: - package_listed_by_short_name[short_profile_name].extend(profile_uri_list) + uri_list = package_listed_by_short_name.setdefault(short_profile_name, []) + for uri in profile_uri_list: + if uri not in uri_list: + uri_list.append(uri) + long_profile_names[short_profile_name] = profile_name.removesuffix("Version").removesuffix("Profile") def _parse_rdf(input_dic, version): # NOSONAR classes_map = {} profile_name = "" + short_profile_name = "" profile_uri_list = [] attributes = [] enum_instances = [] @@ -429,22 +411,24 @@ def _parse_rdf(input_dic, version): # NOSONAR attributes.append(object_dic) if "rest_non_class_category" in rdfs_entry_types: enum_instances.append(object_dic) - if "profile_name_v2_4" in rdfs_entry_types: - profile_name = rdfsEntry.about() - if "profile_name_v3" in rdfs_entry_types: - profile_name = rdfsEntry.label() - if "short_profile_name_v2_4" in rdfs_entry_types and rdfsEntry.fixed(): - short_profile_name = rdfsEntry.fixed() - if "short_profile_name_v3" in rdfs_entry_types: - short_profile_name = rdfsEntry.keyword() + if not profile_name: + if "profile_name_v2_4" in rdfs_entry_types: + profile_name = rdfsEntry.about() + if "profile_name_v3" in rdfs_entry_types: + profile_name = rdfsEntry.label() + if not short_profile_name: + if "short_profile_name_v2_4" in rdfs_entry_types and rdfsEntry.fixed(): + short_profile_name = rdfsEntry.fixed() + if "short_profile_name_v3" in rdfs_entry_types: + short_profile_name = rdfsEntry.keyword() if "profile_iri_v2_4" in rdfs_entry_types and rdfsEntry.fixed(): profile_uri_list.append(rdfsEntry.fixed()) if "profile_iri_v3" in rdfs_entry_types: profile_uri_list.append(rdfsEntry.version_iri()) short_package_name[profile_name] = short_profile_name - package_listed_by_short_name[short_profile_name] = [] _add_profile_to_packages(profile_name, short_profile_name, profile_uri_list) + # Add attributes to corresponding class for attribute in attributes: clarse = attribute["domain"] @@ -746,32 +730,13 @@ def _get_profile_details(cgmes_profile_uris): profile_info = { "index": index, "short_name": profile, - "long_name": _extract_profile_long_name(cgmes_profile_uris[profile]), + "long_name": long_profile_names[profile], "uris": [{"uri": uri} for uri in cgmes_profile_uris[profile]], } profile_details.append(profile_info) return profile_details -def _extract_profile_long_name(profile_uris): - # Extract name from uri, e.g. "Topology" from "http://iec.ch/TC57/2013/61970-456/Topology/4" - # Examples of other possible uris: "http://entsoe.eu/CIM/Topology/4/1", "http://iec.ch/TC57/ns/CIM/Topology-EU/3.0" - # If more than one uri given, extract common part (e.g. "Equipment" from "EquipmentCore" and "EquipmentOperation") - long_name = "" - for uri in profile_uris: - match = re.search(r"/([^/-]*)(-[^/]*)?(/\d+)?/[\d.]+?$", uri) - if match: - name = match.group(1) - if long_name: - for idx in range(1, len(long_name)): - if idx >= len(name) or long_name[idx] != name[idx]: - long_name = long_name[:idx] - break - else: - long_name = name - return long_name - - def _get_sorted_profile_keys(profile_key_list): """Sort profiles alphabetically, but "EQ" to the first place. From ac487b34a008e7395da4438ad900c56b98c385c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 13 Oct 2024 18:44:31 +0200 Subject: [PATCH 07/12] Refactor _merge_profiles and _merge_classes in cimgen.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 134 ++++++++++++++++------------------------------- 1 file changed, 44 insertions(+), 90 deletions(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 55b3b411..0855b821 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -216,7 +216,7 @@ def __init__(self, rdfsEntry): def attributes(self): return self.attribute_list - def addAttribute(self, attribute): + def add_attribute(self, attribute): self.attribute_list.append(attribute) def is_an_enum_class(self): @@ -229,10 +229,6 @@ def add_enum_instance(self, instance): instance["index"] = len(self.enum_instance_list) self.enum_instance_list.append(instance) - def addAttributes(self, attributes): - for attribute in attributes: - self.attribute_list.append(attribute) - def origins(self): return self.origin_list @@ -308,7 +304,6 @@ def wrap_and_clean(txt: str, width: int = 120, initial_indent="", subsequent_ind ) -short_package_name = {} long_profile_names = {} package_listed_by_short_name = {} cim_namespace = "" @@ -426,14 +421,13 @@ def _parse_rdf(input_dic, version): # NOSONAR if "profile_iri_v3" in rdfs_entry_types: profile_uri_list.append(rdfsEntry.version_iri()) - short_package_name[profile_name] = short_profile_name _add_profile_to_packages(profile_name, short_profile_name, profile_uri_list) # Add attributes to corresponding class for attribute in attributes: clarse = attribute["domain"] if clarse and classes_map[clarse]: - classes_map[clarse].addAttribute(attribute) + classes_map[clarse].add_attribute(attribute) else: logger.info("Class {} for attribute {} not found.".format(clarse, attribute)) @@ -461,7 +455,7 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): for class_name in elem_dict.keys(): class_details = { - "attributes": _find_multiple_attributes(elem_dict[class_name].attributes()), + "attributes": elem_dict[class_name].attributes(), "class_location": lang_pack.get_class_location(class_name, elem_dict, version), "class_name": class_name, "class_origin": elem_dict[class_name].origins(), @@ -541,20 +535,6 @@ def _write_files(class_details, output_path, version): class_details["langPack"].run_template(output_path, class_details) -# Find multiple entries for the same attribute -def _find_multiple_attributes(attributes_array): - merged_attributes = [] - for elem in attributes_array: - found = False - for i in range(len(merged_attributes)): - if elem["label"] == merged_attributes[i]["label"]: - found = True - break - if found is False: - merged_attributes.append(elem) - return merged_attributes - - # If multiple CGMES schema files for one profile are read, e.g. Equipment Core and Equipment Core Short Circuit # this function merges these into one profile, e.g. Equipment, after this function only one dictionary entry for each # profile exists. The profiles_array contains one entry for each CGMES schema file which was read. @@ -563,21 +543,21 @@ def _merge_profiles(profiles_array): # Iterate through array elements for elem_dict in profiles_array: # Iterate over profile names - for profile_key in elem_dict.keys(): - if profile_key in profiles_dict.keys(): - # Iterate over classes and check for multiple class definitions - for class_key in elem_dict[profile_key]: - if class_key in profiles_dict[profile_key].keys(): - # If class already exists in packageDict add attributes to attributes array - if len(elem_dict[profile_key][class_key].attributes()) > 0: - attributes_array = elem_dict[profile_key][class_key].attributes() - profiles_dict[profile_key][class_key].addAttributes(attributes_array) - # If class is not in packageDict, create entry - else: - profiles_dict[profile_key][class_key] = elem_dict[profile_key][class_key] - # If package name not in packageDict create entry - else: - profiles_dict[profile_key] = elem_dict[profile_key] + for profile_key, new_class_dict in elem_dict.items(): + class_dict = profiles_dict.setdefault(profile_key, {}) + # Iterate over classes and check for multiple class definitions + for class_key, new_class_infos in new_class_dict.items(): + if class_key in class_dict: + class_infos = class_dict[class_key] + for new_attr in new_class_infos.attributes(): + # Iterate over attributes and check for multiple attribute definitions + for attr in class_infos.attributes(): + if new_attr["label"] == attr["label"]: + break + else: + class_infos.add_attribute(new_attr) + else: + class_dict[class_key] = new_class_infos return profiles_dict @@ -586,63 +566,37 @@ def _merge_profiles(profiles_array): # the possibleProfileList used for the serialization. def _merge_classes(profiles_dict): class_dict = {} - # Iterate over profiles - for package_key in profiles_dict.keys(): - # get short name of the profile - short_name = "" - if package_key in short_package_name: - short_name = short_package_name[package_key] - else: - short_name = package_key - + for profile_key, new_class_dict in profiles_dict.items(): + origin = {"origin": profile_key} # iterate over classes in the current profile - for class_key in profiles_dict[package_key]: - # class already defined? - if class_key not in class_dict: - # store class and class origin - class_dict[class_key] = profiles_dict[package_key][class_key] - class_dict[class_key].addOrigin({"origin": short_name}) - for attr in class_dict[class_key].attributes(): - # store origin of the attributes - attr["attr_origin"] = [{"origin": short_name}] - else: + for class_key, new_class_infos in new_class_dict.items(): + if class_key in class_dict: + class_infos = class_dict[class_key] # some inheritance information is stored only in one of the packages. Therefore it has to be checked # if the subClassOf attribute is set. See for example TopologicalNode definitions in SV and TP. - if not class_dict[class_key].superClass(): - if profiles_dict[package_key][class_key].superClass(): - class_dict[class_key].super = profiles_dict[package_key][class_key].superClass() - - # check if profile is already stored in class origin list - multiple_origin = False - for origin in class_dict[class_key].origins(): - if short_name == origin["origin"]: - # origin already stored - multiple_origin = True - break - if not multiple_origin: - class_dict[class_key].addOrigin({"origin": short_name}) - - for attr in profiles_dict[package_key][class_key].attributes(): - # check if attribute is already in attributes list - multiple_attr = False - for attr_set in class_dict[class_key].attributes(): - if attr["label"] == attr_set["label"]: + if not class_infos.superClass(): + class_infos.super = new_class_infos.superClass() + if origin not in class_infos.origins(): + class_infos.addOrigin(origin) + for new_attr in new_class_infos.attributes(): + for attr in class_infos.attributes(): + if attr["label"] == new_attr["label"]: # attribute already in attributes list, check if origin is new - multiple_attr = True - for origin in attr_set["attr_origin"]: - multiple_attr_origin = False - if origin["origin"] == short_name: - multiple_attr_origin = True - break - if not multiple_attr_origin: - # new origin - attr_set["attr_origin"].append({"origin": short_name}) + origin_list = attr["attr_origin"] + if origin not in origin_list: + origin_list.append(origin) break - if not multiple_attr: + else: # new attribute - attr["attr_origin"] = [{"origin": short_name}] - class_dict[class_key].addAttribute(attr) + new_attr["attr_origin"] = [origin] + class_infos.add_attribute(new_attr) + else: + # store new class and origin + new_class_infos.addOrigin(origin) + for attr in new_class_infos.attributes(): + attr["attr_origin"] = [origin] + class_dict[class_key] = new_class_infos return class_dict @@ -710,7 +664,7 @@ def cim_generate(directory, output_path, version, lang_pack): superClass = class_dict_with_origins[superClassName] superClass.addSubClass(className) else: - print("No match for superClass in dict: :", superClassName) + logger.error("No match for superClass in dict: %s", superClassName) # recursively add the subclasses of subclasses addSubClassesOfSubClasses(class_dict_with_origins) @@ -777,7 +731,7 @@ def _get_recommended_class_profiles(elem_dict): profile_count_map = {} name = class_name while name: - for attribute in _find_multiple_attributes(elem_dict[name].attributes()): + for attribute in elem_dict[name].attributes(): profiles = [origin["origin"] for origin in attribute["attr_origin"]] ambiguous_profile = len(profiles) > 1 for profile in profiles: From 4ce0983962d17b31cc9c3ac9ed26ed791bdf6964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 13 Oct 2024 18:46:03 +0200 Subject: [PATCH 08/12] Fix attribute type of TieFlow in class Terminal for cpp and modernpython MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 0855b821..49e77952 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -772,7 +772,7 @@ def _get_attribute_type(attribute: dict, class_infos: CIMComponentDefinition) -> attribute_type = "primitive" elif class_infos.is_an_enum_class(): attribute_type = "enum" - elif attribute.get("multiplicity") in ("M:0..n", "M:1..n", "M:2..n"): + elif attribute.get("multiplicity") in ("M:0..n", "M:0..2", "M:1..n", "M:2..n"): attribute_type = "list" return attribute_type From e3b05ac5ae89d0e4213e46596f708ac8d1e34572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Mon, 28 Oct 2024 00:01:56 +0100 Subject: [PATCH 09/12] Fix unused import (pre-commit flake8: cimgen/cimgen.py:5:1: F401 're' imported but unused) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 49e77952..37c6336e 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -2,7 +2,6 @@ import os import textwrap import warnings -import re from time import time import xmltodict From 569cd0f6a01267030e9b15c2c1144e9654560e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Tue, 12 Nov 2024 03:25:23 +0100 Subject: [PATCH 10/12] Add dependency to setuptools to fix ModuleNotFoundError for distutils in modernpython/lang_pack.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c4e0177..1f2f8c73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,8 @@ dependencies = [ "xmltodict >= 0.13.0, < 1", "chevron >= 0.14.0, < 1", "pydantic < 2", - "beautifulsoup4 >= 4.12.2, < 5" + "beautifulsoup4 >= 4.12.2, < 5", + "setuptools" ] requires-python = ">=3.11" From e68a0be5d4ee1d78d834af32d8bef8d2997657ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Fri, 15 Nov 2024 02:32:41 +0100 Subject: [PATCH 11/12] Add "is_a_datatype_class" and "is_datatype_attribute" (stereotype == "CIMDatatype"), use these instead of "is_a_float_class" and "is_primitive_float_attribute" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are now 4 disjoint class types: - is_a_datatype_class (= float) - is_an_enum_class - is_a_primitive_class (Integer, Boolean, Float/Decimal = float, String/Date/DateTime/MonthDay = string) - all others are normal classes and 5 disjoint attribute types: - is_class_attribute - is_enum_attribute - is_datatype_attribute - is_list_attribute - is_primitive_attribute Signed-off-by: Thomas Günther --- cimgen/cimgen.py | 45 ++++--------------- cimgen/languages/cpp/lang_pack.py | 22 ++++++--- cimgen/languages/java/lang_pack.py | 14 +++--- cimgen/languages/javascript/lang_pack.py | 10 ++--- cimgen/languages/modernpython/lang_pack.py | 6 ++- .../templates/cimpy_class_template.mustache | 1 + .../utils/export_template.mustache | 6 +++ cimgen/languages/modernpython/utils/writer.py | 1 + 8 files changed, 49 insertions(+), 56 deletions(-) diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 37c6336e..dfea746a 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -246,41 +246,12 @@ def subClasses(self): def setSubClasses(self, classes): self.subclasses = classes - @staticmethod - def _simple_float_attribute(attr): - if "dataType" in attr: - return attr["label"] == "value" and attr["dataType"] == "#Float" - return False - - def is_a_float_class(self): - if self.about in ("Float", "Decimal"): - return True - simple_float = False - for attr in self.attribute_list: - if CIMComponentDefinition._simple_float_attribute(attr): - simple_float = True - for attr in self.attribute_list: - if not CIMComponentDefinition._simple_float_attribute(attr): - simple_float = False - if simple_float: - return True - - candidate_array = {"value": False, "unit": False, "multiplier": False} - optional_attributes = ["denominatorUnit", "denominatorMultiplier"] - for attr in self.attribute_list: - key = attr["label"] - if key in candidate_array: - candidate_array[key] = True - elif key not in optional_attributes: - return False - for key in candidate_array: - if not candidate_array[key]: - return False - return True - def is_a_primitive_class(self): return self.stereotype == "Primitive" + def is_a_datatype_class(self): + return self.stereotype == "CIMDatatype" + def wrap_and_clean(txt: str, width: int = 120, initial_indent="", subsequent_indent=" ") -> str: """ @@ -460,8 +431,8 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): "class_origin": elem_dict[class_name].origins(), "enum_instances": elem_dict[class_name].enum_instances(), "is_an_enum_class": elem_dict[class_name].is_an_enum_class(), - "is_a_float_class": elem_dict[class_name].is_a_float_class(), "is_a_primitive_class": elem_dict[class_name].is_a_primitive_class(), + "is_a_datatype_class": elem_dict[class_name].is_a_datatype_class(), "langPack": lang_pack, "sub_class_of": elem_dict[class_name].superClass(), "sub_classes": elem_dict[class_name].subClasses(), @@ -494,7 +465,7 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): attribute["is_enum_attribute"] = _get_bool_string(attribute_type == "enum") attribute["is_list_attribute"] = _get_bool_string(attribute_type == "list") attribute["is_primitive_attribute"] = _get_bool_string(attribute_type == "primitive") - attribute["is_primitive_float_attribute"] = _get_bool_string(elem_dict[attribute_class].is_a_float_class()) + attribute["is_datatype_attribute"] = _get_bool_string(attribute_type == "datatype") attribute["attribute_class"] = attribute_class class_details["attributes"].sort(key=lambda d: d["label"]) @@ -760,14 +731,16 @@ def _get_attribute_class(attribute: dict) -> str: def _get_attribute_type(attribute: dict, class_infos: CIMComponentDefinition) -> str: - """Get the type of an attribute: "class", "enum", "list", or "primitive". + """Get the type of an attribute: "class", "datatype", "enum", "list", or "primitive". :param attribute: Dictionary with information about an attribute of a class. :param class_infos: Information about the attribute class. :return: Type of the attribute. """ attribute_type = "class" - if class_infos.is_a_primitive_class() or class_infos.is_a_float_class(): + if class_infos.is_a_datatype_class(): + attribute_type = "datatype" + elif class_infos.is_a_primitive_class(): attribute_type = "primitive" elif class_infos.is_an_enum_class(): attribute_type = "enum" diff --git a/cimgen/languages/cpp/lang_pack.py b/cimgen/languages/cpp/lang_pack.py index 1c548adc..6c914b3e 100644 --- a/cimgen/languages/cpp/lang_pack.py +++ b/cimgen/languages/cpp/lang_pack.py @@ -56,7 +56,7 @@ def get_class_location(class_name, class_map, version): # NOSONAR # This is the function that runs the template. def run_template(output_path, class_details): - if class_details["is_a_float_class"]: + if class_details["is_a_datatype_class"] or class_details["class_name"] in ("Float", "Decimal"): templates = float_template_files elif class_details["is_an_enum_class"]: templates = enum_template_files @@ -104,7 +104,7 @@ def label(text, render): def insert_assign_fn(text, render): attribute_txt = render(text) attribute_json = eval(attribute_txt) - if not (attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]): + if not _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label = attribute_json["label"] class_name = attribute_json["domain"] @@ -124,7 +124,7 @@ def insert_assign_fn(text, render): def insert_class_assign_fn(text, render): attribute_txt = render(text) attribute_json = eval(attribute_txt) - if attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label = attribute_json["label"] class_name = attribute_json["domain"] @@ -165,7 +165,7 @@ def create_class_assign(text, render): attribute_json = eval(attribute_txt) assign = "" attribute_class = attribute_json["attribute_class"] - if attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" if attribute_json["is_list_attribute"]: assign = ( @@ -229,7 +229,7 @@ def create_assign(text, render): attribute_json = eval(attribute_txt) assign = "" _class = attribute_json["attribute_class"] - if not (attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]): + if not _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label_without_keyword = attribute_json["label"] if label_without_keyword == "switch": @@ -282,7 +282,7 @@ def attribute_decl(text, render): def _attribute_decl(attribute): _class = attribute["attribute_class"] - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): return "CIMPP::" + _class if attribute["is_list_attribute"]: return "std::list" @@ -299,7 +299,7 @@ def _create_attribute_includes(text, render): if jsonStringNoHtmlEsc is not None and jsonStringNoHtmlEsc != "": attributes = json.loads(jsonStringNoHtmlEsc) for attribute in attributes: - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): unique[attribute["attribute_class"]] = True for clarse in unique: include_string += '\n#include "' + clarse + '.hpp"' @@ -349,6 +349,14 @@ def set_default(dataType): return "nullptr" +def _attribute_is_primitive_or_datatype_or_enum(attribute: dict) -> bool: + return _attribute_is_primitive_or_datatype(attribute) or attribute["is_enum_attribute"] + + +def _attribute_is_primitive_or_datatype(attribute: dict) -> bool: + return attribute["is_primitive_attribute"] or attribute["is_datatype_attribute"] + + # The code below this line is used after the main cim_generate phase to generate # two include files. They are called CIMClassList.hpp and IEC61970.hpp, and # contain the list of the class files and the list of define functions that add diff --git a/cimgen/languages/java/lang_pack.py b/cimgen/languages/java/lang_pack.py index fa47d303..2e088a6e 100644 --- a/cimgen/languages/java/lang_pack.py +++ b/cimgen/languages/java/lang_pack.py @@ -47,10 +47,10 @@ def run_template(output_path, class_details): class_details["primitives"] = [] for attr in class_details["attributes"]: - if attr["is_primitive_attribute"] or attr["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attr): class_details["primitives"].append(attr) - if class_details["is_a_float_class"]: + if class_details["is_a_datatype_class"] or class_details["class_name"] in ("Float", "Decimal"): templates = float_template_files elif class_details["is_an_enum_class"]: templates = enum_template_files @@ -103,7 +103,7 @@ def create_assign(text, render): attribute_json = eval(attribute_txt) assign = "" _class = attribute_json["attribute_class"] - if not (attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]): + if not _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label_without_keyword = attribute_json["label"] if label_without_keyword == "switch": @@ -140,7 +140,7 @@ def attribute_decl(text, render): def _attribute_decl(attribute): _class = attribute["attribute_class"] - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): return _class if attribute["is_list_attribute"]: return "List<" + _class + ">" @@ -157,7 +157,7 @@ def _create_attribute_includes(text, render): if jsonStringNoHtmlEsc is not None and jsonStringNoHtmlEsc != "": attributes = json.loads(jsonStringNoHtmlEsc) for attribute in attributes: - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): unique[attribute["attribute_class"]] = True for clarse in unique: if clarse != "String": @@ -206,6 +206,10 @@ def set_default(dataType): return "nullptr" +def _attribute_is_primitive_or_datatype_or_enum(attribute: dict) -> bool: + return attribute["is_primitive_attribute"] or attribute["is_datatype_attribute"] or attribute["is_enum_attribute"] + + # The code below this line is used after the main cim_generate phase to generate # two include files. They are called CIMClassList.hpp and IEC61970.hpp, and # contain the list of the class files and the list of define functions that add diff --git a/cimgen/languages/javascript/lang_pack.py b/cimgen/languages/javascript/lang_pack.py index a0e15968..6a00d230 100644 --- a/cimgen/languages/javascript/lang_pack.py +++ b/cimgen/languages/javascript/lang_pack.py @@ -76,7 +76,9 @@ def get_class_location(class_name, class_map, version): # NOSONAR def select_primitive_render_function(class_details): class_name = class_details["class_name"] render = "" - if class_details["is_a_float_class"]: + if class_details["is_a_datatype_class"]: + render = aggregateRenderer["renderFloat"] + elif class_name in ("Float", "Decimal"): render = aggregateRenderer["renderFloat"] elif class_name == "String": render = aggregateRenderer["renderString"] @@ -88,9 +90,6 @@ def select_primitive_render_function(class_details): elif class_name == "DateTime": # TODO: Implementation Required! render = aggregateRenderer["renderString"] - elif class_name == "Decimal": - # TODO: Implementation Required! - render = aggregateRenderer["renderString"] elif class_name == "Integer": # TODO: Implementation Required! render = aggregateRenderer["renderString"] @@ -158,8 +157,7 @@ def _create_cgmes_profile(output_path: str, profile_details: list, cim_namespace def _get_class_type(class_details): - class_name = class_details["class_name"] - if class_details["is_a_float_class"] or class_name in ("String", "Boolean", "Integer"): + if class_details["is_a_primitive_class"] or class_details["is_a_datatype_class"]: return "primitive" if class_details["is_an_enum_class"]: return "enum" diff --git a/cimgen/languages/modernpython/lang_pack.py b/cimgen/languages/modernpython/lang_pack.py index d25f903c..99685bf8 100644 --- a/cimgen/languages/modernpython/lang_pack.py +++ b/cimgen/languages/modernpython/lang_pack.py @@ -63,7 +63,9 @@ def _get_type_and_default(text, render) -> tuple[str, str]: return ("Optional[str]", "default=None") elif attribute_json["is_list_attribute"]: return ("list", "default_factory=list") - elif attribute_json["is_primitive_float_attribute"]: + elif attribute_json["is_datatype_attribute"]: + return ("float", "default=0.0") + elif attribute_json["attribute_class"] in ("Float", "Decimal"): return ("float", "default=0.0") elif attribute_json["attribute_class"] == "Integer": return ("int", "default=0") @@ -75,7 +77,7 @@ def _get_type_and_default(text, render) -> tuple[str, str]: def run_template(output_path, class_details): - if class_details["is_a_primitive_class"] or class_details["is_a_float_class"]: + if class_details["is_a_primitive_class"] or class_details["is_a_datatype_class"]: return for template_info in template_files: resource_file = Path( diff --git a/cimgen/languages/modernpython/templates/cimpy_class_template.mustache b/cimgen/languages/modernpython/templates/cimpy_class_template.mustache index e6e94911..58ba391d 100644 --- a/cimgen/languages/modernpython/templates/cimpy_class_template.mustache +++ b/cimgen/languages/modernpython/templates/cimpy_class_template.mustache @@ -33,6 +33,7 @@ class {{class_name}}({{sub_class_of}}): ], "is_used": {{#is_used}}True{{/is_used}}{{^is_used}}False{{/is_used}}, "is_class_attribute": {{#is_class_attribute}}True{{/is_class_attribute}}{{^is_class_attribute}}False{{/is_class_attribute}}, + "is_datatype_attribute": {{#is_datatype_attribute}}True{{/is_datatype_attribute}}{{^is_datatype_attribute}}False{{/is_datatype_attribute}}, "is_enum_attribute": {{#is_enum_attribute}}True{{/is_enum_attribute}}{{^is_enum_attribute}}False{{/is_enum_attribute}}, "is_list_attribute": {{#is_list_attribute}}True{{/is_list_attribute}}{{^is_list_attribute}}False{{/is_list_attribute}}, "is_primitive_attribute": {{#is_primitive_attribute}}True{{/is_primitive_attribute}}{{^is_primitive_attribute}}False{{/is_primitive_attribute}}, diff --git a/cimgen/languages/modernpython/utils/export_template.mustache b/cimgen/languages/modernpython/utils/export_template.mustache index 4055d9ef..410a266e 100644 --- a/cimgen/languages/modernpython/utils/export_template.mustache +++ b/cimgen/languages/modernpython/utils/export_template.mustache @@ -13,6 +13,9 @@ {{#is_class_attribute}} {{/is_class_attribute}} + {{#is_datatype_attribute}} + {{value}} + {{/is_datatype_attribute}} {{#is_enum_attribute}} {{/is_enum_attribute}} @@ -31,6 +34,9 @@ {{#is_class_attribute}} {{/is_class_attribute}} + {{#is_datatype_attribute}} + {{value}} + {{/is_datatype_attribute}} {{#is_enum_attribute}} {{/is_enum_attribute}} diff --git a/cimgen/languages/modernpython/utils/writer.py b/cimgen/languages/modernpython/utils/writer.py index 89abb2e7..729a3d13 100644 --- a/cimgen/languages/modernpython/utils/writer.py +++ b/cimgen/languages/modernpython/utils/writer.py @@ -190,6 +190,7 @@ def get_attribute_infos(obj: Base) -> dict[str, dict[str, object]]: "namespace": extra.get("namespace", obj.namespace), "value": getattr(obj, attr), "is_class_attribute": extra.get("is_class_attribute"), + "is_datatype_attribute": extra.get("is_datatype_attribute"), "is_enum_attribute": extra.get("is_enum_attribute"), "is_list_attribute": extra.get("is_list_attribute"), "is_primitive_attribute": extra.get("is_primitive_attribute"), From b2376a82e721b8a443f874315ea728ffc3aba026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sat, 16 Nov 2024 15:34:29 +0100 Subject: [PATCH 12/12] Rename writer.py to chevron_writer.py for modernpython (to prevent conflicts with the new writer in https://github.com/zaphiro-technologies/cimgen/tree/xml-generation-and-parsing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Günther --- .../utils/{writer.py => chevron_writer.py} | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) rename cimgen/languages/modernpython/utils/{writer.py => chevron_writer.py} (94%) diff --git a/cimgen/languages/modernpython/utils/writer.py b/cimgen/languages/modernpython/utils/chevron_writer.py similarity index 94% rename from cimgen/languages/modernpython/utils/writer.py rename to cimgen/languages/modernpython/utils/chevron_writer.py index 729a3d13..acd7b48f 100644 --- a/cimgen/languages/modernpython/utils/writer.py +++ b/cimgen/languages/modernpython/utils/chevron_writer.py @@ -8,7 +8,7 @@ from pycgmes.utils.profile import BaseProfile, Profile -class Writer: +class ChevronWriter: """Class for writing CIM RDF/XML files.""" def __init__(self, objects: dict[str, Base]): @@ -96,14 +96,15 @@ def sort_attributes_to_profile(self, profile: BaseProfile, class_profile_map: di about = [] for rdfid, obj in self.objects.items(): typ = obj.apparent_name() - if typ in class_profile_map and Writer.is_class_matching_profile(obj, profile): + if typ in class_profile_map and ChevronWriter.is_class_matching_profile(obj, profile): class_profile = class_profile_map[typ] main_entry_of_object = class_profile == profile attributes = [] - for attr, attr_infos in Writer.get_attribute_infos(obj).items(): + for attr, attr_infos in ChevronWriter.get_attribute_infos(obj).items(): value = attr_infos["value"] - if value and attr != "mRID" and Writer.get_attribute_profile(obj, attr, class_profile) == profile: + attribute_profile = ChevronWriter.get_attribute_profile(obj, attr, class_profile) + if value and attr != "mRID" and attribute_profile == profile: if isinstance(value, (list, tuple)): attributes.extend(attr_infos | {"value": v} for v in value) else: @@ -150,7 +151,7 @@ def get_class_profile_map(obj_list: list[Base]) -> dict[str, BaseProfile]: :param obj_list: List of CIM objects. :return: Mapping of CIM type to profile. """ - return {obj.apparent_name(): Writer.get_class_profile(obj) for obj in obj_list} + return {obj.apparent_name(): ChevronWriter.get_class_profile(obj) for obj in obj_list} @staticmethod def get_attribute_profile(obj: Base, attr: str, class_profile: BaseProfile) -> BaseProfile | None: