Skip to content

Commit 048c543

Browse files
Leverage str, enum capability of Python for any generated enums (#133)
* str_enum: Change generation of S2 specification to include the subclass of an enum. * str_enum: Exclude generated files from pyright check. * str_enum: ombc classes should be explicitely exported in init. * str_enum: add missing Any to dict type in S2Parser.
1 parent 51fa849 commit 048c543

6 files changed

Lines changed: 56 additions & 74 deletions

File tree

ci/generate_s2.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33

44
. .venv/bin/activate
5-
datamodel-codegen --input specification/openapi.yml --input-file-type openapi --output-model-type pydantic_v2.BaseModel --output src/s2python/generated/gen_s2.py --use-one-literal-as-default
5+
datamodel-codegen --input specification/openapi.yml --input-file-type openapi --output-model-type pydantic_v2.BaseModel --output src/s2python/generated/gen_s2.py --use-one-literal-as-default --use-subclass-enum --output-datetime-class AwareDatetime --use-double-quotes

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,6 @@ docs = [
6464

6565
[project.scripts]
6666
s2python = "s2python.tools.cli:s2python_cmd"
67+
68+
[tool.black]
69+
line-length=100

pyrightconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
"tests"
55
],
66

7+
"exclude": [
8+
"src/s2python/generated/"
9+
],
10+
711
"defineConstant": {
812
"DEBUG": true
913
}

src/s2python/generated/gen_s2.py

Lines changed: 35 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,21 @@
11
# generated by datamodel-codegen:
22
# filename: openapi.yml
3-
# timestamp: 2024-07-29T10:18:52+00:00
3+
# timestamp: 2025-07-29T10:12:16+00:00
44

55
from __future__ import annotations
66

77
from enum import Enum
8-
from typing import List, Optional
8+
from typing import List, Literal, Optional
99

10-
from pydantic import (
11-
AwareDatetime,
12-
BaseModel,
13-
ConfigDict,
14-
Field,
15-
RootModel,
16-
conint,
17-
constr,
18-
)
19-
from typing_extensions import Literal
10+
from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel, conint, constr
2011

2112

2213
class Duration(RootModel[conint(ge=0)]):
23-
root: conint(ge=0) = Field( # pyright: ignore[reportInvalidTypeForm]
24-
..., description="Duration in milliseconds"
25-
)
14+
root: conint(ge=0) = Field(..., description="Duration in milliseconds")
2615

2716

2817
class ID(RootModel[constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}")]):
29-
root: constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}") = ( # pyright: ignore[reportInvalidTypeForm]
30-
Field(..., description="UUID")
31-
)
18+
root: constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}") = Field(..., description="UUID")
3219

3320

3421
class Currency(Enum):
@@ -134,12 +121,12 @@ class Currency(Enum):
134121
ZWL = "ZWL"
135122

136123

137-
class SessionRequestType(Enum):
124+
class SessionRequestType(str, Enum):
138125
RECONNECT = "RECONNECT"
139126
TERMINATE = "TERMINATE"
140127

141128

142-
class RevokableObjects(Enum):
129+
class RevokableObjects(str, Enum):
143130
PEBC_PowerConstraints = "PEBC.PowerConstraints"
144131
PEBC_EnergyConstraint = "PEBC.EnergyConstraint"
145132
PEBC_Instruction = "PEBC.Instruction"
@@ -155,12 +142,12 @@ class RevokableObjects(Enum):
155142
DDBC_Instruction = "DDBC.Instruction"
156143

157144

158-
class EnergyManagementRole(Enum):
145+
class EnergyManagementRole(str, Enum):
159146
CEM = "CEM"
160147
RM = "RM"
161148

162149

163-
class ReceptionStatusValues(Enum):
150+
class ReceptionStatusValues(str, Enum):
164151
INVALID_DATA = "INVALID_DATA"
165152
INVALID_MESSAGE = "INVALID_MESSAGE"
166153
INVALID_CONTENT = "INVALID_CONTENT"
@@ -234,8 +221,7 @@ class Timer(BaseModel):
234221
description="Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.",
235222
)
236223
duration: Duration = Field(
237-
...,
238-
description="The time it takes for the Timer to finish after it has been started",
224+
..., description="The time it takes for the Timer to finish after it has been started"
239225
)
240226

241227

@@ -381,20 +367,20 @@ class DDBCAverageDemandRateForecastElement(BaseModel):
381367
)
382368

383369

384-
class RoleType(Enum):
370+
class RoleType(str, Enum):
385371
ENERGY_PRODUCER = "ENERGY_PRODUCER"
386372
ENERGY_CONSUMER = "ENERGY_CONSUMER"
387373
ENERGY_STORAGE = "ENERGY_STORAGE"
388374

389375

390-
class Commodity(Enum):
376+
class Commodity(str, Enum):
391377
GAS = "GAS"
392378
HEAT = "HEAT"
393379
ELECTRICITY = "ELECTRICITY"
394380
OIL = "OIL"
395381

396382

397-
class CommodityQuantity(Enum):
383+
class CommodityQuantity(str, Enum):
398384
ELECTRIC_POWER_L1 = "ELECTRIC.POWER.L1"
399385
ELECTRIC_POWER_L2 = "ELECTRIC.POWER.L2"
400386
ELECTRIC_POWER_L3 = "ELECTRIC.POWER.L3"
@@ -407,7 +393,7 @@ class CommodityQuantity(Enum):
407393
OIL_FLOW_RATE = "OIL.FLOW_RATE"
408394

409395

410-
class InstructionStatus(Enum):
396+
class InstructionStatus(str, Enum):
411397
NEW = "NEW"
412398
ACCEPTED = "ACCEPTED"
413399
REJECTED = "REJECTED"
@@ -417,7 +403,7 @@ class InstructionStatus(Enum):
417403
ABORTED = "ABORTED"
418404

419405

420-
class ControlType(Enum):
406+
class ControlType(str, Enum):
421407
POWER_ENVELOPE_BASED_CONTROL = "POWER_ENVELOPE_BASED_CONTROL"
422408
POWER_PROFILE_BASED_CONTROL = "POWER_PROFILE_BASED_CONTROL"
423409
OPERATION_MODE_BASED_CONTROL = "OPERATION_MODE_BASED_CONTROL"
@@ -427,17 +413,17 @@ class ControlType(Enum):
427413
NO_SELECTION = "NO_SELECTION"
428414

429415

430-
class PEBCPowerEnvelopeLimitType(Enum):
416+
class PEBCPowerEnvelopeLimitType(str, Enum):
431417
UPPER_LIMIT = "UPPER_LIMIT"
432418
LOWER_LIMIT = "LOWER_LIMIT"
433419

434420

435-
class PEBCPowerEnvelopeConsequenceType(Enum):
421+
class PEBCPowerEnvelopeConsequenceType(str, Enum):
436422
VANISH = "VANISH"
437423
DEFER = "DEFER"
438424

439425

440-
class PPBCPowerSequenceStatus(Enum):
426+
class PPBCPowerSequenceStatus(str, Enum):
441427
NOT_SCHEDULED = "NOT_SCHEDULED"
442428
SCHEDULED = "SCHEDULED"
443429
EXECUTING = "EXECUTING"
@@ -507,8 +493,7 @@ class SessionRequest(BaseModel):
507493
message_id: ID
508494
request: SessionRequestType = Field(..., description="The type of request")
509495
diagnostic_label: Optional[str] = Field(
510-
None,
511-
description="Optional field for a human readible descirption for debugging purposes",
496+
None, description="Optional field for a human readible descirption for debugging purposes"
512497
)
513498

514499

@@ -586,12 +571,10 @@ class PEBCEnergyConstraint(BaseModel):
586571
description="Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.",
587572
)
588573
valid_from: AwareDatetime = Field(
589-
...,
590-
description="Moment this PEBC.EnergyConstraints information starts to be valid",
574+
..., description="Moment this PEBC.EnergyConstraints information starts to be valid"
591575
)
592576
valid_until: AwareDatetime = Field(
593-
...,
594-
description="Moment until this PEBC.EnergyConstraints information is valid.",
577+
..., description="Moment until this PEBC.EnergyConstraints information is valid."
595578
)
596579
upper_average_power: float = Field(
597580
...,
@@ -634,8 +617,7 @@ class PPBCScheduleInstruction(BaseModel):
634617
description="Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.",
635618
)
636619
abnormal_condition: bool = Field(
637-
...,
638-
description="Indicates if this is an instruction during an abnormal condition",
620+
..., description="Indicates if this is an instruction during an abnormal condition"
639621
)
640622

641623

@@ -665,8 +647,7 @@ class PPBCStartInterruptionInstruction(BaseModel):
665647
description="Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.",
666648
)
667649
abnormal_condition: bool = Field(
668-
...,
669-
description="Indicates if this is an instruction during an abnormal condition",
650+
..., description="Indicates if this is an instruction during an abnormal condition"
670651
)
671652

672653

@@ -697,8 +678,7 @@ class PPBCEndInterruptionInstruction(BaseModel):
697678
description="Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.",
698679
)
699680
abnormal_condition: bool = Field(
700-
...,
701-
description="Indicates if this is an instruction during an abnormal condition",
681+
..., description="Indicates if this is an instruction during an abnormal condition"
702682
)
703683

704684

@@ -745,8 +725,7 @@ class OMBCInstruction(BaseModel):
745725
description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.",
746726
)
747727
abnormal_condition: bool = Field(
748-
...,
749-
description="Indicates if this is an instruction during an abnormal condition",
728+
..., description="Indicates if this is an instruction during an abnormal condition"
750729
)
751730

752731

@@ -824,8 +803,7 @@ class FRBCInstruction(BaseModel):
824803
description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.",
825804
)
826805
abnormal_condition: bool = Field(
827-
...,
828-
description="Indicates if this is an instruction during an abnormal condition.",
806+
..., description="Indicates if this is an instruction during an abnormal condition."
829807
)
830808

831809

@@ -871,8 +849,7 @@ class DDBCActuatorStatus(BaseModel):
871849
message_id: ID
872850
actuator_id: ID = Field(..., description="ID of the actuator this messages refers to")
873851
active_operation_mode_id: ID = Field(
874-
...,
875-
description="The operation mode that is presently active for this actuator.",
852+
..., description="The operation mode that is presently active for this actuator."
876853
)
877854
operation_mode_factor: float = Field(
878855
...,
@@ -903,8 +880,7 @@ class DDBCInstruction(BaseModel):
903880
description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.",
904881
)
905882
abnormal_condition: bool = Field(
906-
...,
907-
description="Indicates if this is an instruction during an abnormal condition",
883+
..., description="Indicates if this is an instruction during an abnormal condition"
908884
)
909885
actuator_id: ID = Field(..., description="ID of the actuator this Instruction belongs to.")
910886
operation_mode_id: ID = Field(..., description="ID of the DDBC.OperationMode")
@@ -937,8 +913,7 @@ class PowerValue(BaseModel):
937913
..., description="The power quantity the value refers to"
938914
)
939915
value: float = Field(
940-
...,
941-
description="Power value expressed in the unit associated with the CommodityQuantity",
916+
..., description="Power value expressed in the unit associated with the CommodityQuantity"
942917
)
943918

944919

@@ -1020,8 +995,7 @@ class PEBCAllowedLimitRange(BaseModel):
1020995
..., description="Type of power quantity this PEBC.AllowedLimitRange applies to"
1021996
)
1022997
limit_type: PEBCPowerEnvelopeLimitType = Field(
1023-
...,
1024-
description="Indicates if this ranges applies to the upper limit or the lower limit",
998+
..., description="Indicates if this ranges applies to the upper limit or the lower limit"
1025999
)
10261000
range_boundary: NumberRange = Field(
10271001
...,
@@ -1193,8 +1167,7 @@ class ResourceManagerDetails(BaseModel):
11931167
)
11941168
manufacturer: Optional[str] = Field(None, description="Name of Manufacturer")
11951169
model: Optional[str] = Field(
1196-
None,
1197-
description="Name of the model of the device (provided by the manufacturer)",
1170+
None, description="Name of the model of the device (provided by the manufacturer)"
11981171
)
11991172
serial_number: Optional[str] = Field(
12001173
None, description="Serial number of the device (provided by the manufacturer)"
@@ -1218,8 +1191,7 @@ class ResourceManagerDetails(BaseModel):
12181191
description="Currency to be used for all information regarding costs. Mandatory if cost information is published.",
12191192
)
12201193
provides_forecast: bool = Field(
1221-
...,
1222-
description="Indicates whether the ResourceManager is able to provide PowerForecasts",
1194+
..., description="Indicates whether the ResourceManager is able to provide PowerForecasts"
12231195
)
12241196
provides_power_measurement_types: List[CommodityQuantity] = Field(
12251197
...,
@@ -1306,8 +1278,7 @@ class PEBCInstruction(BaseModel):
13061278
description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.",
13071279
)
13081280
abnormal_condition: bool = Field(
1309-
...,
1310-
description="Indicates if this is an instruction during an abnormal condition.",
1281+
..., description="Indicates if this is an instruction during an abnormal condition."
13111282
)
13121283
power_constraints_id: ID = Field(
13131284
...,
@@ -1380,8 +1351,7 @@ class PPBCPowerSequence(BaseModel):
13801351
min_length=1,
13811352
)
13821353
is_interruptible: bool = Field(
1383-
...,
1384-
description="Indicates whether the option of pausing a sequence is available.",
1354+
..., description="Indicates whether the option of pausing a sequence is available."
13851355
)
13861356
max_pause_before: Optional[Duration] = Field(
13871357
None,
@@ -1509,10 +1479,7 @@ class FRBCActuatorDescription(BaseModel):
15091479
description="Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.",
15101480
)
15111481
supported_commodities: List[Commodity] = Field(
1512-
...,
1513-
description="List of all supported Commodities.",
1514-
max_length=4,
1515-
min_length=1,
1482+
..., description="List of all supported Commodities.", max_length=4, min_length=1
15161483
)
15171484
operation_modes: List[FRBCOperationMode] = Field(
15181485
...,

src/s2python/ombc/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@
33
from s2python.ombc.ombc_status import OMBCStatus
44
from s2python.ombc.ombc_system_description import OMBCSystemDescription
55
from s2python.ombc.ombc_timer_status import OMBCTimerStatus
6+
7+
__all__ = [
8+
"OMBCInstruction",
9+
"OMBCOperationMode",
10+
"OMBCStatus",
11+
"OMBCSystemDescription",
12+
"OMBCTimerStatus",
13+
]

src/s2python/s2_parser.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
import logging
3-
from typing import Optional, TypeVar, Union, Type, Dict
3+
from typing import Optional, TypeVar, Union, Type, Dict, Any
44

55
from s2python.common import (
66
Handshake,
@@ -67,13 +67,13 @@
6767

6868
class S2Parser:
6969
@staticmethod
70-
def _parse_json_if_required(unparsed_message: Union[dict, str, bytes]) -> dict:
70+
def _parse_json_if_required(unparsed_message: Union[dict[Any, Any], str, bytes]) -> dict:
7171
if isinstance(unparsed_message, (str, bytes)):
7272
return json.loads(unparsed_message)
7373
return unparsed_message
7474

7575
@staticmethod
76-
def parse_as_any_message(unparsed_message: Union[dict, str, bytes]) -> S2Message:
76+
def parse_as_any_message(unparsed_message: Union[dict[Any, Any], str, bytes]) -> S2Message:
7777
"""Parse the message as any S2 python message regardless of message type.
7878
7979
:param unparsed_message: The message as a JSON-formatted string or as a json-parsed dictionary.
@@ -94,7 +94,7 @@ def parse_as_any_message(unparsed_message: Union[dict, str, bytes]) -> S2Message
9494

9595
@staticmethod
9696
def parse_as_message(
97-
unparsed_message: Union[dict, str, bytes], as_message: Type[M]
97+
unparsed_message: Union[dict[Any, Any], str, bytes], as_message: Type[M]
9898
) -> M:
9999
"""Parse the message to a specific S2 python message.
100100
@@ -108,7 +108,7 @@ def parse_as_message(
108108

109109
@staticmethod
110110
def parse_message_type(
111-
unparsed_message: Union[dict, str, bytes],
111+
unparsed_message: Union[dict[Any, Any], str, bytes],
112112
) -> Optional[S2MessageType]:
113113
"""Parse only the message type from the unparsed message.
114114

0 commit comments

Comments
 (0)