Skip to content

Commit

Permalink
generate primitives and datatypes instances
Browse files Browse the repository at this point in the history
Signed-off-by: Federico M. Facca <federico.facca@zaphiro.ch>
  • Loading branch information
chicco785 committed Nov 27, 2023
1 parent 8dfe5bd commit 4d47385
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 68 deletions.
3 changes: 3 additions & 0 deletions modernpython/cimdatatype_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ..utils.datatypes import CIMDatatype
from ..utils.profile import Profile
from .enum import *
159 changes: 99 additions & 60 deletions modernpython/langPack.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def location(version):
base = {"base_class": "Base", "class_location": location}

template_files = [{"filename": "cimpy_class_template.mustache", "ext": ".py"}]
enum_template_files = [{"filename": "pydantic_enum_template.mustache", "ext": ".py"}]

required_profiles = ["EQ", "GL"] #temporary
enum_template_files = [{"filename": "enum_class_template.mustache", "ext": ".py"}]
primitive_template_files = [{"filename": "primitive_template.mustache", "ext": ".py"}]
cimdatatype_template_files = [{"filename": "cimdatatype_template.mustache", "ext": ".py"}]

def get_class_location(class_name, class_map, version):
return f".{class_map[class_name].superClass()}"
Expand All @@ -54,6 +54,43 @@ def get_class_location(class_name, class_map, version):

partials = {}

def _primitive_to_dataType(datatype):
if datatype.lower() == "integer":
return "int"
if datatype.lower() == "boolean":
return "bool"
if datatype.lower() == "string":
return "str"
if datatype.lower() == "datetime":
return "datetime"
if datatype.lower() == "monthday":
return "str" # TO BE FIXED?
if datatype.lower() == "date":
return "date"
if datatype.lower() == "time":
return "time"
if datatype.lower() == "float":
return "float"
if datatype.lower() == "string":
return "str"
else:
# this actually never happens
return "float"

def _compute_cim_data_type(attributes) -> tuple[str, str, str]:
type = None
unit = None
multiplier = None
for attribute in attributes:
if 'about' in attribute and attribute['about'] and "value" in attribute['about'] and 'class_name' in attribute:
type = _primitive_to_dataType(attribute['class_name'])
if 'about' in attribute and attribute['about'] and "multiplier" in attribute['about'] and 'isFixed' in attribute:
multiplier = "UnitMultiplier."+attribute['isFixed']
if 'about' in attribute and attribute['about'] and "unit" in attribute['about'] and 'isFixed' in attribute:
unit = "UnitSymbol."+attribute['isFixed']
return (type, unit, multiplier)


# computes the data type
def _compute_data_type(attribute):
if "label" in attribute and attribute["label"] == "mRID":
Expand All @@ -64,28 +101,7 @@ def _compute_data_type(attribute):
elif "dataType" in attribute and "class_name" in attribute:
# for whatever weird reason String is not created as class from CIMgen
if is_primitive_class(attribute["class_name"]) or attribute["class_name"] == "String":
datatype = attribute["dataType"].split("#")[1].lower()
if datatype == "integer":
return "int"
if datatype == "boolean":
return "bool"
if datatype == "string":
return "str"
if datatype == "datetime":
return "datetime"
if datatype == "monthday":
return "str" # TO BE FIXED?
if datatype == "date":
return "str" # TO BE FIXED?
if datatype == "time":
return "time"
if datatype == "float":
return "float"
if datatype == "string":
return "str"
else:
# this actually never happens
return "float"
return _primitive_to_dataType(attribute["dataType"].split("#")[1])
# the assumption is that cim data type e.g. Voltage, ActivePower, always
# maps to a float
elif is_cim_data_type_class(attribute["class_name"]):
Expand Down Expand Up @@ -263,24 +279,19 @@ def has_unit_attribute(attributes):
return True
return False

def is_required_profile(class_origin):
for origin in class_origin:
if origin["origin"] in required_profiles:
return True
return False

def run_template(version_path, class_details):
if (
if class_details["class_name"] == 'PositionPoint':
#this class is created manually to support types conversions
return
elif class_details["is_a_primitive"] is True:
# Primitives are never used in the in memory representation but only for
# the schema
class_details["is_a_primitive"] is True
run_template_primitive(version_path, class_details, primitive_template_files)
elif class_details["is_a_cim_data_type"] is True:
# Datatypes based on primitives are never used in the in memory
# representation but only for the schema
or class_details["is_a_cim_data_type"] == True
or class_details["class_name"] == 'PositionPoint'
):
return
elif class_details["has_instances"] == True:
run_template_cimdatatype(version_path, class_details, cimdatatype_template_files)
elif class_details["has_instances"] is True:
run_template_enum(version_path, class_details, enum_template_files)
else:
run_template_schema(version_path, class_details, template_files)
Expand Down Expand Up @@ -337,6 +348,56 @@ def run_template_schema(version_path, class_details, templates):
output = chevron.render(**args)
file.write(output)

def run_template_primitive(version_path, class_details, templates):
for template_info in templates:
class_file =Path(version_path, "resources", "primitives" + template_info["ext"])
if not os.path.exists(class_file):
if not (parent:=class_file.parent).exists():
parent.mkdir()
with open(class_file, "w", encoding="utf-8") as file:
schema_file_path = os.path.join(
os.getcwd(), "modernpython", "primitive_header.py"
)
schema_file = open(schema_file_path, "r")
file.write(schema_file.read())
with open(class_file, "a", encoding="utf-8") as file:
template_path = os.path.join(os.getcwd(), "modernpython/templates", template_info["filename"])
class_details["data_type"] = _primitive_to_dataType(class_details["class_name"])
with open(template_path, encoding="utf-8") as f:
args = {
"data": class_details,
"template": f,
"partials_dict": partials,
}
output = chevron.render(**args)
file.write(output)

def run_template_cimdatatype(version_path, class_details, templates):
for template_info in templates:
class_file =Path(version_path, "resources", "cimdatatype" + template_info["ext"])
if not os.path.exists(class_file):
if not (parent:=class_file.parent).exists():
parent.mkdir()
with open(class_file, "w", encoding="utf-8") as file:
schema_file_path = os.path.join(
os.getcwd(), "modernpython", "cimdatatype_header.py"
)
schema_file = open(schema_file_path, "r")
file.write(schema_file.read())
with open(class_file, "a", encoding="utf-8") as file:
template_path = os.path.join(os.getcwd(), "modernpython/templates", template_info["filename"])
class_details["data_type"] = _compute_cim_data_type(class_details["attributes"])[0]
class_details["unit"] = _compute_cim_data_type(class_details["attributes"])[1]
class_details["multiplier"] = _compute_cim_data_type(class_details["attributes"])[2]
with open(template_path, encoding="utf-8") as f:
args = {
"data": class_details,
"template": f,
"partials_dict": partials,
}
output = chevron.render(**args)
file.write(output)

def resolve_headers(dest: str, version: str):
"""Add all classes in __init__.py"""

Expand All @@ -349,25 +410,3 @@ def resolve_headers(dest: str, version: str):
with open(dest / "__init__.py", "a", encoding="utf-8") as header_file:
header_file.write("# pylint: disable=too-many-lines,missing-module-docstring\n")
header_file.write(f"CGMES_VERSION='{version_number}'\n")

# # Under this, add all imports in init. Disabled becasue loading 600 unneeded classes is slow.
# _all = ["CGMES_VERSION"]

# for include_name in sorted(dest.glob("*.py")):
# stem = include_name.stem
# if stem in[ "__init__", "Base"]:
# continue
# _all.append(stem)
# header_file.write(f"from .{stem} import {stem}\n")

# header_file.write(
# "\n".join(
# [
# "# This is not needed per se, but by referencing all imports",
# "# this prevents a potential autoflake from cleaning up the whole file.",
# "# FYA, if __all__ is present, only what's in there will be import with a import *",
# "",
# ]
# )
# )
# header_file.write(f"__all__={_all}")
4 changes: 4 additions & 0 deletions modernpython/primitive_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from datetime import date, datetime, time
from ..utils.datatypes import Primitive
from ..utils.profile import Profile
from .enum import *
11 changes: 11 additions & 0 deletions modernpython/templates/cimdatatype_template.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

"""
Generated from the CGMES 3 files via cimgen: https://github.com/sogno-platform/cimgen
"""

{{class_name}} = CIMDatatype("{{class_name}}", {{data_type}}, {{unit}}, {{multiplier}}, [{{#class_origin}}Profile.{{origin}},{{/class_origin}}])

"""
{{{wrapped_class_comment}}}
"""

11 changes: 11 additions & 0 deletions modernpython/templates/primitive_template.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

"""
Generated from the CGMES 3 files via cimgen: https://github.com/sogno-platform/cimgen
"""

{{class_name}} = Primitive("{{class_name}}",{{data_type}}, [{{#class_origin}}Profile.{{origin}}, {{/class_origin}}])

"""
{{{wrapped_class_comment}}}
"""

10 changes: 2 additions & 8 deletions modernpython/utils/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,10 @@ def to_dict(self, with_class: bool = True) -> dict[str, "CgmesAttributeTypes"]:
"""
attrs = {f.name: getattr(self, f.name) for f in fields(self)}
attrs["__class__"] = self.apparent_name()
if with_class:
attrs["__class__"] = self.resource_name
attrs["__class__"] = self.apparent_name()
return attrs

@cached_property
def resource_name(self) -> str:
"""Returns the resource type."""
return self.__class__.__name__

@cached_property
def namespace(self) -> str:
"""Returns the namespace. By default, the namespace is the cim namespace for all resources.
Expand All @@ -71,7 +65,7 @@ def apparent_name(cls) -> str:

def cgmes_attribute_names_in_profile(self, profile: BaseProfile | None) -> set[Field]:
"""
Returns all fields accross the parent tree which are in the profile in parameter.
Returns all fields across the parent tree which are in the profile in parameter.
Mostly useful during export to find all the attributes relevant to one profile only.
Expand Down
51 changes: 51 additions & 0 deletions modernpython/utils/datatypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import importlib
from dataclasses import Field, fields
from functools import cached_property
from typing import Any, TypeAlias, TypedDict

from .constants import NAMESPACES
from pydantic.dataclasses import dataclass

from .dataclassconfig import DataclassConfig
from .profile import BaseProfile
from ..resources.enum import UnitMultiplier, UnitSymbol

@dataclass(config=DataclassConfig)
class Primitive:

def __init__(self, name: str, type, profiles: set[BaseProfile]):
self.name = name
self.type = type
self.profiles = profiles

def getName(self) -> str:
return self.name

def getType(self):
return self.type

@cached_property
def namespace(self) -> str:
"""Returns the namespace. By default, the namespace is the cim namespace for all resources.
Custom resources can override this.
"""
return NAMESPACES["cim"]

@cached_property
def getProfiles(self) -> set[BaseProfile]:
return self.profiles

@dataclass(config=DataclassConfig)
class CIMDatatype(Primitive):

def __init__(self,
name: str, type, symbol: UnitSymbol, multiplier: UnitMultiplier, profiles: set[BaseProfile]):
super().__init__(name, type, profiles)
self.multiplier = multiplier
self.symbol = symbol

def getMultiplier(self) -> UnitMultiplier:
return self.multiplier

def getSymbole(self) -> UnitSymbol:
return self.symbol

0 comments on commit 4d47385

Please sign in to comment.