Skip to content

Commit 862d7a3

Browse files
committed
support single file generation for enum and schema
1 parent 9b68a96 commit 862d7a3

File tree

6 files changed

+138
-50
lines changed

6 files changed

+138
-50
lines changed

modernpython/Base.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,26 @@ class DataclassConfig: # pylint: disable=too-few-public-methods
7979

8080
# By default with pydantic extra arguments given to a dataclass are silently ignored.
8181
# This matches the default behaviour by failing noisily.
82-
extra = "ignore"
82+
extra = "forbid"
8383
populate_by_name = True
8484
defer_build = True
8585
from_attributes = True
8686

87+
class GeoDataclassConfig: # pylint: disable=too-few-public-methods
88+
"""
89+
Used to configure pydantic dataclasses.
90+
91+
See doc at
92+
https://docs.pydantic.dev/latest/usage/model_config/#options
93+
"""
94+
95+
# By default with pydantic extra arguments given to a dataclass are silently ignored.
96+
# This matches the default behaviour by failing noisily.
97+
extra = "ignore"
98+
populate_by_name = True
99+
defer_build = True
100+
from_attributes = True
101+
arbitrary_types_allowed=True
87102

88103
# Default namespaces used by CGMES.
89104
NAMESPACES = {

modernpython/enum_header.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from enum import Enum, IntEnum
2+

modernpython/langPack.py

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def location(version):
3333
template_files = [{"filename": "cimpy_class_template.mustache", "ext": ".py"}]
3434
enum_template_files = [{"filename": "pydantic_enum_template.mustache", "ext": ".py"}]
3535

36+
required_profiles = ["EQ", "GL"] #temporary
37+
3638
def get_class_location(class_name, class_map, version):
3739
# Check if the current class has a parent class
3840
if class_map[class_name].superClass():
@@ -53,7 +55,7 @@ def _compute_data_type(attribute):
5355

5456
if "range" in attribute:
5557
# return "'"+attribute["range"].split("#")[1]+"'"
56-
return "'"+attribute["range"].split("#")[1]+"'"
58+
return attribute["range"].split("#")[1]
5759
if "dataType" in attribute and "class_name" in attribute:
5860
# for whatever weird reason String is not created as class from CIMgen
5961
if is_primitive_class(attribute["class_name"]) or attribute["class_name"] == "String":
@@ -84,7 +86,7 @@ def _compute_data_type(attribute):
8486
if is_cim_data_type_class(attribute["class_name"]):
8587
return "float"
8688
# this is for example the case for 'StreetAddress.streetDetail'
87-
return "'"+attribute["dataType"].split("#")[1]+"'"
89+
return attribute["dataType"].split("#")[1]
8890

8991
def _ends_with_s(attribute_name):
9092
return attribute_name.endswith("s")
@@ -252,6 +254,12 @@ def has_unit_attribute(attributes):
252254
return True
253255
return False
254256

257+
def is_required_profile(class_origin):
258+
for origin in class_origin:
259+
if origin["origin"] in required_profiles:
260+
return True
261+
return False
262+
255263
def run_template(version_path, class_details):
256264
if (
257265
# Primitives are never used in the in memory representation but only for
@@ -260,6 +268,7 @@ def run_template(version_path, class_details):
260268
# Datatypes based on primitives are never used in the in memory
261269
# representation but only for the schema
262270
or class_details["is_a_cim_data_type"] == True
271+
or class_details["class_name"] == 'PositionPoint'
263272
):
264273
return
265274
elif class_details["has_instances"] == True:
@@ -269,47 +278,59 @@ def run_template(version_path, class_details):
269278

270279
def run_template_enum(version_path, class_details, templates):
271280
for template_info in templates:
272-
class_file = os.path.join(version_path, class_details["class_name"] + template_info["ext"])
281+
class_file = os.path.join(version_path, "enum" + template_info["ext"])
273282
if not os.path.exists(class_file):
274283
with open(class_file, "w", encoding="utf-8") as file:
275-
template_path = os.path.join(os.getcwd(), "modernpython/templates", template_info["filename"])
276-
class_details["setInstances"] = _set_instances
277-
with open(template_path, encoding="utf-8") as f:
278-
args = {
279-
"data": class_details,
280-
"template": f,
281-
"partials_dict": partials,
282-
}
283-
output = chevron.render(**args)
284-
file.write(output)
284+
header_file_path = os.path.join(
285+
os.getcwd(), "modernpython", "enum_header.py"
286+
)
287+
header_file = open(header_file_path, "r")
288+
file.write(header_file.read())
289+
with open(class_file, "a", encoding="utf-8") as file:
290+
template_path = os.path.join(os.getcwd(), "modernpython/templates", template_info["filename"])
291+
class_details["setInstances"] = _set_instances
292+
with open(template_path, encoding="utf-8") as f:
293+
args = {
294+
"data": class_details,
295+
"template": f,
296+
"partials_dict": partials,
297+
}
298+
output = chevron.render(**args)
299+
file.write(output)
285300

286301
def run_template_schema(version_path, class_details, templates):
287302
for template_info in templates:
288-
class_file = os.path.join(version_path, class_details["class_name"] + template_info["ext"])
303+
class_file = os.path.join(version_path, "schema" + template_info["ext"])
289304
if not os.path.exists(class_file):
290305
with open(class_file, "w", encoding="utf-8") as file:
291-
template_path = os.path.join(os.getcwd(), "modernpython/templates", template_info["filename"])
292-
class_details["setDefault"] = _set_default
293-
class_details["setType"] = _set_type
294-
class_details["setImports"] = _set_imports
295-
class_details["setValidator"] = _set_validator
296-
class_details["setNormalizedName"] = _set_normalized_name
297-
with open(template_path, encoding="utf-8") as f:
298-
args = {
299-
"data": class_details,
300-
"template": f,
301-
"partials_dict": partials,
302-
}
303-
output = chevron.render(**args)
304-
file.write(output)
306+
schema_file_path = os.path.join(
307+
os.getcwd(), "modernpython", "schema_header.py"
308+
)
309+
schema_file = open(schema_file_path, "r")
310+
file.write(schema_file.read())
311+
with open(class_file, "a", encoding="utf-8") as file:
312+
template_path = os.path.join(os.getcwd(), "modernpython/templates", template_info["filename"])
313+
class_details["setDefault"] = _set_default
314+
class_details["setType"] = _set_type
315+
class_details["setImports"] = _set_imports
316+
class_details["setValidator"] = _set_validator
317+
class_details["setNormalizedName"] = _set_normalized_name
318+
with open(template_path, encoding="utf-8") as f:
319+
args = {
320+
"data": class_details,
321+
"template": f,
322+
"partials_dict": partials,
323+
}
324+
output = chevron.render(**args)
325+
file.write(output)
305326

306327

307328
def _create_init(path):
308329
init_file = path + "/__init__.py"
309330

310331
with open(init_file, "w", encoding="utf-8") as init:
311-
init.write("# pylint: disable=too-many-lines,missing-module-docstring\n")
312-
332+
#init.write("# pylint: disable=too-many-lines,missing-module-docstring\n")
333+
pass
313334

314335
# creates the Base class file, all classes inherit from this class
315336
def _create_base(path):

modernpython/schema_header.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import annotations
2+
import uuid
3+
from functools import cached_property
4+
from pydantic import ConfigDict, Field, field_validator, computed_field
5+
from geoalchemy2.shape import to_shape
6+
from geoalchemy2.elements import WKBElement
7+
from shapely.geometry import Point
8+
from datetime import date, datetime, time
9+
from typing import Optional, Iterator, List
10+
from pydantic.dataclasses import dataclass
11+
from .Base import DataclassConfig, GeoDataclassConfig, Profile, Base
12+
from .util import cyclic_references_validator
13+
from .enum import *
14+
15+
@dataclass(config=GeoDataclassConfig)
16+
class PositionPoint(Base):
17+
"""
18+
Set of spatial coordinates that determine a point, defined in the coordinate system specified in 'Location.CoordinateSystem'. Use a single position point instance to desribe a point-oriented location. Use a sequence of position points to describe a line-oriented object (physical location of non-point oriented objects like cables or lines), or area of an object (like a substation or a geographical zone - in this case, have first and last position point with the same values).
19+
20+
:Location: Location described by this position point.
21+
:sequenceNumber: Zero-relative sequence number of this point within a series of points.
22+
:xPosition: X axis position.
23+
:yPosition: Y axis position.
24+
:zPosition: (if applicable) Z axis position.
25+
"""
26+
27+
location: "Location" = Field(alias="Location", in_profiles = [Profile.GL, ])
28+
sequenceNumber: Optional[int] = Field(default=None, in_profiles = [Profile.GL, ])
29+
point: Point = Field(
30+
repr=False, in_profiles = [Profile.GL, ]
31+
) # we introduce this field compared to CIM definition because we want to store a proper geometry "point" in the database
32+
33+
@computed_field
34+
@property
35+
def xPosition(self) -> str:
36+
return str(self.point.x)
37+
38+
@computed_field
39+
@property
40+
def yPosition(self) -> str:
41+
return str(self.point.y)
42+
43+
@computed_field
44+
@property
45+
def zPosition(self) -> str:
46+
return str(self.point.z)
47+
48+
@cached_property
49+
def possible_profiles(self)->set[Profile]:
50+
"""
51+
A resource can be used by multiple profiles. This is the set of profiles
52+
where this element can be found.
53+
"""
54+
return { Profile.GL, }
55+
56+
# Pydantic needs help to map GeoAlchemy classes to Shapely
57+
@field_validator("point", mode="before")
58+
def validate_point_format(cls, v):
59+
if isinstance(v, Point):
60+
return v
61+
elif isinstance(v, WKBElement):
62+
point = to_shape(v)
63+
if point.geom_type != "Point":
64+
raise ValueError("must be a Point")
65+
return Point(point)
66+
else:
67+
raise ValueError("must be a Point or a WKBElement")
Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
1+
12
"""
23
Generated from the CGMES 3 files via cimgen: https://github.com/sogno-platform/cimgen
34
"""
45

5-
from __future__ import annotations
6-
from functools import cached_property
7-
from typing import Optional, List
8-
from pydantic import Field, field_validator
9-
import uuid
10-
from datetime import date, datetime, time
11-
from pydantic.dataclasses import dataclass, rebuild_dataclass
12-
from .Base import DataclassConfig, Profile
13-
from .util import cyclic_references_validator
14-
from .{{sub_class_of}} import {{sub_class_of}}
15-
166
@dataclass(config=DataclassConfig)
177
class {{class_name}}({{sub_class_of}}):
188
"""
@@ -37,16 +27,10 @@ class {{class_name}}({{sub_class_of}}):
3727
{{^attributes}}
3828
# No attributes defined for this class.
3929
{{/attributes}}
40-
41-
4230
@cached_property
4331
def possible_profiles(self)->set[Profile]:
4432
"""
4533
A resource can be used by multiple profiles. This is the set of profiles
4634
where this element can be found.
4735
"""
4836
return { {{#class_origin}}Profile.{{origin}}, {{/class_origin}} }
49-
50-
{{#setImports}}{{attributes}}{{/setImports}}
51-
52-
rebuild_dataclass({{class_name}})

modernpython/templates/pydantic_enum_template.mustache

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
12
"""
23
Generated from the CGMES 3 files via cimgen: https://github.com/sogno-platform/cimgen
34
"""
45

5-
from enum import Enum
6-
76
class {{class_name}}(str,Enum):
87

98
'''

0 commit comments

Comments
 (0)