Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Primitives, enum and CIM_datatype #39

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bc81346
Added identification of CIMDatatypes and Primitive classes
HUG0-D Nov 15, 2024
6db0aad
Decomposition of the run_template function, template files are now de…
HUG0-D Nov 15, 2024
5713723
Added Primitive class and template
HUG0-D Nov 15, 2024
e2cba25
Adding CIMDatatype classes and template
HUG0-D Nov 15, 2024
7ab6fe7
Adding attribute dataclass in json_extra
HUG0-D Nov 15, 2024
e13cdc8
Adding Enum classes
HUG0-D Nov 15, 2024
a0a3624
Fixing template formating
HUG0-D Nov 15, 2024
d5469ad
Fixing template formating
HUG0-D Nov 15, 2024
5d675a3
Corrected header in template (unspecifying the CGMES version)
HUG0-D Nov 18, 2024
900cdd9
Simplified _set_instance by replacing text before exception and added…
HUG0-D Nov 18, 2024
1ca6bb1
Imports sorted by alphabetical order
HUG0-D Nov 18, 2024
08834d9
Rename class and attribute property "is_a_primitive"-->"is_a_primitiv…
HUG0-D Nov 18, 2024
2c3d74c
Adapted terminology of variable and function name: cim_data_type as d…
HUG0-D Nov 18, 2024
b9f262c
Adapted terminology of variable and function name: cim_data_type as a…
HUG0-D Nov 18, 2024
58e0c21
Renaming mustache template files and variables to be more consistent
HUG0-D Nov 19, 2024
e681a4f
Enum class generation using chevron
HUG0-D Nov 19, 2024
a931467
removing pycgmes from base, and chevron_writer imports
HUG0-D Nov 19, 2024
dd5e0ee
Removed need of setAttributeClass by handling attribute_class direclt…
HUG0-D Nov 20, 2024
6c6c674
Fix sonar issue
HUG0-D Nov 20, 2024
29833d0
Fix datatype attributes for cgmes_v2 and simplifed imports
HUG0-D Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ __pycache__/
.vscode/*
*/.DS_Store
.DS_Store
tests/
102 changes: 76 additions & 26 deletions cimgen/languages/modernpython/lang_pack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import os
import re
from distutils.dir_util import copy_tree
from pathlib import Path
Expand Down Expand Up @@ -35,9 +34,12 @@ def location(version): # NOSONAR
base = {"base_class": "Base", "class_location": location}

# These are the files that are used to generate the python files.
template_files = [{"filename": "cimpy_class_template.mustache", "ext": ".py"}]
constants_template_files = [{"filename": "cimpy_constants_template.mustache", "ext": ".py"}]
profile_template_files = [{"filename": "cimpy_cgmesProfile_template.mustache", "ext": ".py"}]
class_template_file = {"filename": "class_template.mustache", "ext": ".py"}
constants_template_file = {"filename": "constants_template.mustache", "ext": ".py"}
profile_template_file = {"filename": "profile_template.mustache", "ext": ".py"}
enum_template_file = {"filename": "enum_template.mustache", "ext": ".py"}
primitive_template_file = {"filename": "primitive_template.mustache", "ext": ".py"}
datatype_template_file = {"filename": "datatype_template.mustache", "ext": ".py"}


def get_class_location(class_name, class_map, version): # NOSONAR
Expand Down Expand Up @@ -76,23 +78,73 @@ def _get_type_and_default(text, render) -> tuple[str, str]:
return ("str", 'default=""')


def _get_python_type(datatype):
if datatype.lower() == "integer":
return "int"
if datatype.lower() == "boolean":
return "bool"
if datatype.lower() == "datetime":
return "datetime"
if datatype.lower() == "date":
return "date"
if datatype.lower() == "time":
return "time"
if datatype.lower() == "monthday":
return "str" # TO BE FIXED? I could not find a datatype in python that holds only month and day.
if datatype.lower() == "string":
return "str"
else:
# everything else should be a float
return "float"


def _set_imports(attributes):
import_set = set()
for attribute in attributes:
if attribute["is_datatype_attribute"] or attribute["is_primitive_attribute"]:
import_set.add(attribute["attribute_class"])
return sorted(import_set)


def _set_datatype_attributes(attributes) -> dict:
datatype_attributes = {}
datatype_attributes["python_type"] = "None"
import_set = set()
for attribute in attributes:
if "value" in attribute.get("about", "") and "attribute_class" in attribute:
datatype_attributes["python_type"] = _get_python_type(attribute["attribute_class"])
if "isFixed" in attribute:
import_set.add(attribute["attribute_class"])
datatype_attributes["isFixed_imports"] = sorted(import_set)
return datatype_attributes


def run_template(output_path, class_details):
if class_details["is_a_primitive_class"] or class_details["is_a_datatype_class"]:
return
for template_info in template_files:
resource_file = Path(
os.path.join(
output_path,
"resources",
class_details["class_name"] + template_info["ext"],
)
)
if not resource_file.exists():
if not (parent := resource_file.parent).exists():
parent.mkdir()
if class_details["is_a_primitive_class"]:
# Primitives are never used in the in memory representation but only for
# the schema
template = primitive_template_file
class_details["python_type"] = _get_python_type(class_details["class_name"])
elif class_details["is_a_datatype_class"]:
# Datatypes based on primitives are never used in the in memory
# representation but only for the schema
template = datatype_template_file
class_details.update(_set_datatype_attributes(class_details["attributes"]))
elif class_details["is_an_enum_class"]:
template = enum_template_file
else:
template = class_template_file
class_details["setDefault"] = _set_default
class_details["setType"] = _set_type
_write_templated_file(resource_file, class_details, template_info["filename"])
class_details["imports"] = _set_imports(class_details["attributes"])
resource_file = _create_file(output_path, class_details, template)
_write_templated_file(resource_file, class_details, template["filename"])


def _create_file(output_path, class_details, template) -> str:
resource_file = Path(output_path) / "resources" / (class_details["class_name"] + template["ext"])
resource_file.parent.mkdir(exist_ok=True)
return str(resource_file)


def _write_templated_file(class_file, class_details, template_filename):
Expand All @@ -109,17 +161,15 @@ def _write_templated_file(class_file, class_details, template_filename):


def _create_constants(output_path: str, cim_namespace: str):
for template_info in constants_template_files:
class_file = os.path.join(output_path, "utils", "constants" + template_info["ext"])
class_details = {"cim_namespace": cim_namespace}
_write_templated_file(class_file, class_details, template_info["filename"])
class_file = Path(output_path) / "utils" / ("constants" + constants_template_file["ext"])
class_details = {"cim_namespace": cim_namespace}
_write_templated_file(class_file, class_details, constants_template_file["filename"])


def _create_cgmes_profile(output_path: str, profile_details: list):
for template_info in profile_template_files:
class_file = os.path.join(output_path, "utils", "profile" + template_info["ext"])
class_details = {"profiles": profile_details}
_write_templated_file(class_file, class_details, template_info["filename"])
class_file = Path(output_path) / "utils" / ("profile" + profile_template_file["ext"])
class_details = {"profiles": profile_details}
_write_templated_file(class_file, class_details, profile_template_file["filename"])


def resolve_headers(path: str, version: str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ from pydantic.dataclasses import dataclass

from ..utils.profile import BaseProfile, Profile
from {{class_location}} import {{sub_class_of}}
{{#imports}}
from .{{.}} import {{.}}
{{/imports}}


@dataclass
Expand Down Expand Up @@ -37,6 +40,12 @@ class {{class_name}}({{sub_class_of}}):
"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}},
{{#is_datatype_attribute}}
"attribute_class": {{attribute_class}},
{{/is_datatype_attribute}}
{{#is_primitive_attribute}}
"attribute_class": {{attribute_class}},
{{/is_primitive_attribute}}
},
)

Expand Down
27 changes: 27 additions & 0 deletions cimgen/languages/modernpython/templates/datatype_template.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen
"""

from ..utils.datatypes import CIMDatatype
from ..utils.profile import Profile
{{#isFixed_imports}}
from .{{.}} import {{.}}
{{/isFixed_imports}}


{{class_name}} = CIMDatatype(
name="{{class_name}}",
type={{python_type}},
{{#attributes}}
{{#isFixed}}
{{label}}={{attribute_class}}.{{isFixed}},
{{/isFixed}}
{{/attributes}}
profiles=[{{#class_origin}}
Profile.{{origin}},{{/class_origin}}
],
)

"""
{{{wrapped_class_comment}}}
"""
15 changes: 15 additions & 0 deletions cimgen/languages/modernpython/templates/enum_template.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen
"""

from enum import Enum


class {{class_name}}(str, Enum):
"""
{{{class_comment}}} # noqa: E501
"""

{{#enum_instances}}
{{label}} = "{{label}}"{{#comment}} # {{comment}}{{/comment}} # noqa: E501
{{/enum_instances}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen
"""

from datetime import date, datetime, time
from ..utils.datatypes import Primitive
from ..utils.profile import Profile

{{class_name}} = Primitive(
name="{{class_name}}",
type={{python_type}},
profiles=[{{#class_origin}}
Profile.{{origin}},{{/class_origin}}
],
)

"""
{{{wrapped_class_comment}}}
"""
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class BaseProfile(str, Enum):
"""
Profile parent. Use it if you need your own profiles.

All pycgmes objects requiring a Profile are actually asking for a `BaseProfile`. As
All CGMES objects requiring a Profile are actually asking for a `BaseProfile`. As
Enum with fields cannot be inherited or composed, just create your own CustomProfile without
trying to extend Profile. It will work.
"""
Expand Down
4 changes: 2 additions & 2 deletions cimgen/languages/modernpython/utils/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

from pydantic.dataclasses import dataclass

from pycgmes.utils.constants import NAMESPACES
from .constants import NAMESPACES

from ..utils.config import cgmes_resource_config
from .config import cgmes_resource_config
from .profile import BaseProfile


Expand Down
6 changes: 3 additions & 3 deletions cimgen/languages/modernpython/utils/chevron_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import chevron

from pycgmes.utils.base import Base
from pycgmes.utils.constants import NAMESPACES
from pycgmes.utils.profile import BaseProfile, Profile
from .base import Base
from .constants import NAMESPACES
from .profile import BaseProfile, Profile


class ChevronWriter:
Expand Down
29 changes: 29 additions & 0 deletions cimgen/languages/modernpython/utils/datatypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pydantic import Field
from typing import List, Optional, Union

from .constants import NAMESPACES
from pydantic.dataclasses import dataclass

from .config import cgmes_resource_config
from .profile import BaseProfile
from ..resources.UnitMultiplier import UnitMultiplier
from ..resources.UnitSymbol import UnitSymbol
from ..resources.Currency import Currency


@dataclass(config=cgmes_resource_config)
class Primitive:

name: str = Field(frozen=True)
type: object = Field(frozen=True)
namespace: str = Field(frozen=True, default=NAMESPACES["cim"])
profiles: List[BaseProfile] = Field(frozen=True)


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

multiplier: Optional[UnitMultiplier] = Field(default=None, frozen=True)
unit: Optional[Union[UnitSymbol, Currency]] = Field(default=None, frozen=True)
denominatorMultiplier: Optional[UnitMultiplier] = Field(default=None, frozen=True)
denominatorUnit: Optional[UnitSymbol] = Field(default=None, frozen=True)