From 0dded91d9c3fc65da949988cc0b6b8d9dbc0d2b2 Mon Sep 17 00:00:00 2001 From: Sebastiaan la Fleur Date: Mon, 29 Jul 2024 12:52:48 +0200 Subject: [PATCH 1/3] 10: Update to pydantic v2. --- ci/generate_s2.sh | 2 +- dev-requirements.txt | 8 +- setup.cfg | 2 +- src/s2python/common/duration.py | 6 +- src/s2python/common/handshake.py | 6 +- src/s2python/common/handshake_response.py | 6 +- .../common/instruction_status_update.py | 14 +- src/s2python/common/number_range.py | 25 +- src/s2python/common/power_forecast.py | 10 +- src/s2python/common/power_forecast_element.py | 10 +- src/s2python/common/power_forecast_value.py | 4 +- src/s2python/common/power_measurement.py | 8 +- src/s2python/common/power_range.py | 20 +- src/s2python/common/power_value.py | 4 +- src/s2python/common/reception_status.py | 8 +- .../common/resource_manager_details.py | 18 +- src/s2python/common/revoke_object.py | 8 +- src/s2python/common/role.py | 4 +- src/s2python/common/select_control_type.py | 6 +- src/s2python/common/session_request.py | 6 +- src/s2python/common/timer.py | 8 +- src/s2python/common/transition.py | 20 +- .../frbc/frbc_actuator_description.py | 108 ++- src/s2python/frbc/frbc_actuator_status.py | 16 +- .../frbc/frbc_fill_level_target_profile.py | 18 +- .../frbc_fill_level_target_profile_element.py | 12 +- src/s2python/frbc/frbc_instruction.py | 12 +- src/s2python/frbc/frbc_leakage_behaviour.py | 10 +- .../frbc/frbc_leakage_behaviour_element.py | 12 +- src/s2python/frbc/frbc_operation_mode.py | 33 +- .../frbc/frbc_operation_mode_element.py | 22 +- src/s2python/frbc/frbc_storage_description.py | 12 +- src/s2python/frbc/frbc_storage_status.py | 6 +- src/s2python/frbc/frbc_system_description.py | 18 +- src/s2python/frbc/frbc_timer_status.py | 10 +- src/s2python/frbc/frbc_usage_forecast.py | 10 +- .../frbc/frbc_usage_forecast_element.py | 10 +- src/s2python/generated/gen_s2.py | 671 +++++++++--------- src/s2python/s2_parser.py | 6 +- src/s2python/s2_validation_error.py | 9 +- src/s2python/validate_values_mixin.py | 62 +- tests/unit/common/duration_test.py | 4 +- tests/unit/common/transition_test.py | 34 +- tests/unit/frbc/frbc_actuator_status_test.py | 8 +- .../frbc_fill_level_target_profile_test.py | 8 +- .../unit/frbc/frbc_leakage_behaviour_test.py | 4 +- .../unit/frbc/frbc_system_description_test.py | 20 +- 47 files changed, 631 insertions(+), 707 deletions(-) diff --git a/ci/generate_s2.sh b/ci/generate_s2.sh index 0971e1b..f1ee694 100755 --- a/ci/generate_s2.sh +++ b/ci/generate_s2.sh @@ -2,4 +2,4 @@ . .venv/bin/activate -datamodel-codegen --input specification/openapi.yml --input-file-type openapi --output src/s2python/generated/gen_s2.py +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 diff --git a/dev-requirements.txt b/dev-requirements.txt index 1ab4ec7..70c1bc9 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,6 +6,8 @@ # alabaster==0.7.13 # via sphinx +annotated-types==0.7.0 + # via pydantic argcomplete==3.4.0 # via datamodel-code-generator astroid==3.2.4 @@ -121,10 +123,12 @@ pluggy==1.5.0 # tox pre-commit==3.5.0 # via s2-python (setup.cfg) -pydantic[email]==1.10.17 +pydantic[email]==2.8.2 # via # datamodel-code-generator # s2-python (setup.cfg) +pydantic-core==2.20.1 + # via pydantic pygments==2.18.0 # via # sphinx @@ -218,10 +222,12 @@ types-pytz==2024.1.0.20240417 # via s2-python (setup.cfg) typing-extensions==4.12.2 # via + # annotated-types # astroid # black # mypy # pydantic + # pydantic-core # pylint urllib3==2.2.2 # via requests diff --git a/setup.cfg b/setup.cfg index d861495..55e2cdb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,7 @@ python_requires >= 3.8, <= 3.11 # new major versions. This works if the required packages follow Semantic Versioning. # For more information, check out https://semver.org/. install_requires = - pydantic~=1.10.7 + pydantic~=2.8.2 pytz click diff --git a/src/s2python/common/duration.py b/src/s2python/common/duration.py index 461a690..08ead98 100644 --- a/src/s2python/common/duration.py +++ b/src/s2python/common/duration.py @@ -11,12 +11,12 @@ @catch_and_convert_exceptions class Duration(GenDuration, S2Message["Duration"]): def to_timedelta(self) -> timedelta: - return timedelta(milliseconds=self.__root__) + return timedelta(milliseconds=self.root) @staticmethod def from_timedelta(duration: timedelta) -> "Duration": - return Duration(__root__=math.ceil(duration.total_seconds() * 1000)) + return Duration(math.ceil(duration.total_seconds() * 1000)) @staticmethod def from_milliseconds(milliseconds: int) -> "Duration": - return Duration(__root__=milliseconds) + return Duration(milliseconds) diff --git a/src/s2python/common/handshake.py b/src/s2python/common/handshake.py index 7c8f5a0..c068150 100644 --- a/src/s2python/common/handshake.py +++ b/src/s2python/common/handshake.py @@ -9,7 +9,7 @@ @catch_and_convert_exceptions class Handshake(GenHandshake, S2Message["Handshake"]): - class Config(GenHandshake.Config): - validate_assignment = True + model_config = GenHandshake.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenHandshake.__fields__["message_id"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenHandshake.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/common/handshake_response.py b/src/s2python/common/handshake_response.py index 31340ff..fcc2eb5 100644 --- a/src/s2python/common/handshake_response.py +++ b/src/s2python/common/handshake_response.py @@ -9,7 +9,7 @@ @catch_and_convert_exceptions class HandshakeResponse(GenHandshakeResponse, S2Message["HandshakeResponse"]): - class Config(GenHandshakeResponse.Config): - validate_assignment = True + model_config = GenHandshakeResponse.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenHandshakeResponse.__fields__["message_id"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenHandshakeResponse.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/common/instruction_status_update.py b/src/s2python/common/instruction_status_update.py index bd39531..5a8c45f 100644 --- a/src/s2python/common/instruction_status_update.py +++ b/src/s2python/common/instruction_status_update.py @@ -10,13 +10,9 @@ @catch_and_convert_exceptions -class InstructionStatusUpdate( - GenInstructionStatusUpdate, S2Message["InstructionStatusUpdate"] -): - class Config(GenInstructionStatusUpdate.Config): - validate_assignment = True +class InstructionStatusUpdate(GenInstructionStatusUpdate, S2Message["InstructionStatusUpdate"]): + model_config = GenInstructionStatusUpdate.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenInstructionStatusUpdate.__fields__["message_id"].field_info # type: ignore[assignment] - instruction_id: uuid.UUID = GenInstructionStatusUpdate.__fields__[ - "instruction_id" - ].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenInstructionStatusUpdate.model_fields["message_id"] # type: ignore[assignment] + instruction_id: uuid.UUID = GenInstructionStatusUpdate.model_fields["instruction_id"] # type: ignore[assignment] diff --git a/src/s2python/common/number_range.py b/src/s2python/common/number_range.py index 9af00ec..65e9b81 100644 --- a/src/s2python/common/number_range.py +++ b/src/s2python/common/number_range.py @@ -1,6 +1,6 @@ from typing import Any, Dict -from pydantic import root_validator +from pydantic import model_validator from s2python.validate_values_mixin import ( S2Message, @@ -11,29 +11,22 @@ @catch_and_convert_exceptions class NumberRange(GenNumberRange, S2Message["NumberRange"]): - class Config(GenNumberRange.Config): - validate_assignment = True + model_config = GenNumberRange.model_config + model_config["validate_assignment"] = True - @root_validator(pre=False) + @model_validator(mode="after") @classmethod - def validate_start_end_order( # pylint: disable=duplicate-code - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: - if values.get("start_of_range", 0.0) > values.get("end_of_range", 0.0): - raise ValueError( - cls, "start_of_range should not be higher than end_of_range" - ) + def validate_start_end_order(cls, number_range: "NumberRange") -> "NumberRange": # pylint: disable=duplicate-code + if number_range.start_of_range > number_range.end_of_range: + raise ValueError(cls, "start_of_range should not be higher than end_of_range") - return values + return number_range def __hash__(self) -> int: return hash(f"{self.start_of_range}|{self.end_of_range}") def __eq__(self, other: Any) -> bool: if isinstance(other, NumberRange): - return ( - self.start_of_range == other.start_of_range - and self.end_of_range == other.end_of_range - ) + return self.start_of_range == other.start_of_range and self.end_of_range == other.end_of_range return False diff --git a/src/s2python/common/power_forecast.py b/src/s2python/common/power_forecast.py index c31d7ed..31c595d 100644 --- a/src/s2python/common/power_forecast.py +++ b/src/s2python/common/power_forecast.py @@ -11,10 +11,8 @@ @catch_and_convert_exceptions class PowerForecast(GenPowerForecast, S2Message["PowerForecast"]): - class Config(GenPowerForecast.Config): - validate_assignment = True + model_config = GenPowerForecast.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenPowerForecast.__fields__["message_id"].field_info # type: ignore[assignment] - elements: List[PowerForecastElement] = GenPowerForecast.__fields__[ - "elements" - ].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenPowerForecast.model_fields["message_id"] # type: ignore[assignment] + elements: List[PowerForecastElement] = GenPowerForecast.model_fields["elements"] # type: ignore[assignment] diff --git a/src/s2python/common/power_forecast_element.py b/src/s2python/common/power_forecast_element.py index 3749e0e..10460f7 100644 --- a/src/s2python/common/power_forecast_element.py +++ b/src/s2python/common/power_forecast_element.py @@ -11,10 +11,10 @@ @catch_and_convert_exceptions class PowerForecastElement(GenPowerForecastElement, S2Message["PowerForecastElement"]): - class Config(GenPowerForecastElement.Config): - validate_assignment = True + model_config = GenPowerForecastElement.model_config + model_config["validate_assignment"] = True - duration: Duration = GenPowerForecastElement.__fields__["duration"].field_info # type: ignore[assignment] - power_values: List[PowerForecastValue] = GenPowerForecastElement.__fields__[ + duration: Duration = GenPowerForecastElement.model_fields["duration"] # type: ignore[assignment] + power_values: List[PowerForecastValue] = GenPowerForecastElement.model_fields[ "power_values" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] diff --git a/src/s2python/common/power_forecast_value.py b/src/s2python/common/power_forecast_value.py index 8b7b9c8..3ee2cc3 100644 --- a/src/s2python/common/power_forecast_value.py +++ b/src/s2python/common/power_forecast_value.py @@ -7,5 +7,5 @@ @catch_and_convert_exceptions class PowerForecastValue(GenPowerForecastValue, S2Message["PowerForecastValue"]): - class Config(GenPowerForecastValue.Config): - validate_assignment = True + model_config = GenPowerForecastValue.model_config + model_config["validate_assignment"] = True diff --git a/src/s2python/common/power_measurement.py b/src/s2python/common/power_measurement.py index 013e85d..27896c9 100644 --- a/src/s2python/common/power_measurement.py +++ b/src/s2python/common/power_measurement.py @@ -11,8 +11,8 @@ @catch_and_convert_exceptions class PowerMeasurement(GenPowerMeasurement, S2Message["PowerMeasurement"]): - class Config(GenPowerMeasurement.Config): - validate_assignment = True + model_config = GenPowerMeasurement.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenPowerMeasurement.__fields__["message_id"].field_info # type: ignore[assignment] - values: List[PowerValue] = GenPowerMeasurement.__fields__["values"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenPowerMeasurement.model_fields["message_id"] # type: ignore[assignment] + values: List[PowerValue] = GenPowerMeasurement.model_fields["values"] # type: ignore[assignment] diff --git a/src/s2python/common/power_range.py b/src/s2python/common/power_range.py index 5b3b4ed..a8cd293 100644 --- a/src/s2python/common/power_range.py +++ b/src/s2python/common/power_range.py @@ -1,6 +1,6 @@ from typing import Any, Dict -from pydantic import root_validator +from pydantic import model_validator from s2python.generated.gen_s2 import PowerRange as GenPowerRange from s2python.validate_values_mixin import ( @@ -11,17 +11,13 @@ @catch_and_convert_exceptions class PowerRange(GenPowerRange, S2Message["PowerRange"]): - class Config(GenPowerRange.Config): - validate_assignment = True + model_config = GenPowerRange.model_config + model_config["validate_assignment"] = True - @root_validator(pre=False) + @model_validator(mode="after") @classmethod - def validate_start_end_order( - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: # pylint: disable=duplicate-code - if values.get("start_of_range", 0.0) > values.get("end_of_range", 0.0): - raise ValueError( - cls, "start_of_range should not be higher than end_of_range" - ) + def validate_start_end_order(cls, power_range: "PowerRange") -> "PowerRange": # pylint: disable=duplicate-code + if power_range.start_of_range > power_range.end_of_range: + raise ValueError(cls, "start_of_range should not be higher than end_of_range") - return values + return power_range diff --git a/src/s2python/common/power_value.py b/src/s2python/common/power_value.py index c00583e..c623627 100644 --- a/src/s2python/common/power_value.py +++ b/src/s2python/common/power_value.py @@ -7,5 +7,5 @@ @catch_and_convert_exceptions class PowerValue(GenPowerValue, S2Message["PowerValue"]): - class Config(GenPowerValue.Config): - validate_assignment = True + model_config = GenPowerValue.model_config + model_config["validate_assignment"] = True diff --git a/src/s2python/common/reception_status.py b/src/s2python/common/reception_status.py index 69760d7..a759897 100644 --- a/src/s2python/common/reception_status.py +++ b/src/s2python/common/reception_status.py @@ -9,9 +9,7 @@ @catch_and_convert_exceptions class ReceptionStatus(GenReceptionStatus, S2Message["ReceptionStatus"]): - class Config(GenReceptionStatus.Config): - validate_assignment = True + model_config = GenReceptionStatus.model_config + model_config["validate_assignment"] = True - subject_message_id: uuid.UUID = GenReceptionStatus.__fields__[ - "subject_message_id" - ].field_info # type: ignore[assignment] + subject_message_id: uuid.UUID = GenReceptionStatus.model_fields["subject_message_id"] # type: ignore[assignment] diff --git a/src/s2python/common/resource_manager_details.py b/src/s2python/common/resource_manager_details.py index d261e2d..82ce844 100644 --- a/src/s2python/common/resource_manager_details.py +++ b/src/s2python/common/resource_manager_details.py @@ -13,15 +13,13 @@ @catch_and_convert_exceptions -class ResourceManagerDetails( - GenResourceManagerDetails, S2Message["ResourceManagerDetails"] -): - class Config(GenResourceManagerDetails.Config): - validate_assignment = True +class ResourceManagerDetails(GenResourceManagerDetails, S2Message["ResourceManagerDetails"]): + model_config = GenResourceManagerDetails.model_config + model_config["validate_assignment"] = True - instruction_processing_delay: Duration = GenResourceManagerDetails.__fields__[ + instruction_processing_delay: Duration = GenResourceManagerDetails.model_fields[ "instruction_processing_delay" - ].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenResourceManagerDetails.__fields__["message_id"].field_info # type: ignore[assignment] - resource_id: uuid.UUID = GenResourceManagerDetails.__fields__["resource_id"].field_info # type: ignore[assignment] - roles: List[Role] = GenResourceManagerDetails.__fields__["roles"].field_info # type: ignore[assignment] + ] # type: ignore[assignment] + message_id: uuid.UUID = GenResourceManagerDetails.model_fields["message_id"] # type: ignore[assignment] + resource_id: uuid.UUID = GenResourceManagerDetails.model_fields["resource_id"] # type: ignore[assignment] + roles: List[Role] = GenResourceManagerDetails.model_fields["roles"] # type: ignore[assignment] diff --git a/src/s2python/common/revoke_object.py b/src/s2python/common/revoke_object.py index 4d1a579..d133c79 100644 --- a/src/s2python/common/revoke_object.py +++ b/src/s2python/common/revoke_object.py @@ -9,8 +9,8 @@ @catch_and_convert_exceptions class RevokeObject(GenRevokeObject, S2Message["RevokeObject"]): - class Config(GenRevokeObject.Config): - validate_assignment = True + model_config = GenRevokeObject.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenRevokeObject.__fields__["message_id"].field_info # type: ignore[assignment] - object_id: uuid.UUID = GenRevokeObject.__fields__["object_id"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenRevokeObject.model_fields["message_id"] # type: ignore[assignment] + object_id: uuid.UUID = GenRevokeObject.model_fields["object_id"] # type: ignore[assignment] diff --git a/src/s2python/common/role.py b/src/s2python/common/role.py index bac2732..4a3d3ef 100644 --- a/src/s2python/common/role.py +++ b/src/s2python/common/role.py @@ -7,5 +7,5 @@ @catch_and_convert_exceptions class Role(GenRole, S2Message["Role"]): - class Config(GenRole.Config): - validate_assignment = True + model_config = GenRole.model_config + model_config["validate_assignment"] = True diff --git a/src/s2python/common/select_control_type.py b/src/s2python/common/select_control_type.py index 6096f99..5f02954 100644 --- a/src/s2python/common/select_control_type.py +++ b/src/s2python/common/select_control_type.py @@ -9,7 +9,7 @@ @catch_and_convert_exceptions class SelectControlType(GenSelectControlType, S2Message["SelectControlType"]): - class Config(GenSelectControlType.Config): - validate_assignment = True + model_config = GenSelectControlType.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenSelectControlType.__fields__["message_id"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenSelectControlType.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/common/session_request.py b/src/s2python/common/session_request.py index 5606569..f962427 100644 --- a/src/s2python/common/session_request.py +++ b/src/s2python/common/session_request.py @@ -9,7 +9,7 @@ @catch_and_convert_exceptions class SessionRequest(GenSessionRequest, S2Message["SessionRequest"]): - class Config(GenSessionRequest.Config): - validate_assignment = True + model_config = GenSessionRequest.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenSessionRequest.__fields__["message_id"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenSessionRequest.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/common/timer.py b/src/s2python/common/timer.py index 94f1bae..3811082 100644 --- a/src/s2python/common/timer.py +++ b/src/s2python/common/timer.py @@ -10,8 +10,8 @@ @catch_and_convert_exceptions class Timer(GenTimer, S2Message["Timer"]): - class Config(GenTimer.Config): - validate_assignment = True + model_config = GenTimer.model_config + model_config["validate_assignment"] = True - id: uuid.UUID = GenTimer.__fields__["id"].field_info # type: ignore[assignment] - duration: Duration = GenTimer.__fields__["duration"].field_info # type: ignore[assignment] + id: uuid.UUID = GenTimer.model_fields["id"] # type: ignore[assignment] + duration: Duration = GenTimer.model_fields["duration"] # type: ignore[assignment] diff --git a/src/s2python/common/transition.py b/src/s2python/common/transition.py index af657b6..e1e1a25 100644 --- a/src/s2python/common/transition.py +++ b/src/s2python/common/transition.py @@ -11,16 +11,14 @@ @catch_and_convert_exceptions class Transition(GenTransition, S2Message["Transition"]): - class Config(GenTransition.Config): - validate_assignment = True + model_config = GenTransition.model_config + model_config["validate_assignment"] = True - id: uuid.UUID = GenTransition.__fields__["id"].field_info # type: ignore[assignment] - from_: uuid.UUID = GenTransition.__fields__["from_"].field_info # type: ignore[assignment] - to: uuid.UUID = GenTransition.__fields__["to"].field_info # type: ignore[assignment] - start_timers: List[uuid.UUID] = GenTransition.__fields__["start_timers"].field_info # type: ignore[assignment] - blocking_timers: List[uuid.UUID] = GenTransition.__fields__[ - "blocking_timers" - ].field_info # type: ignore[assignment] - transition_duration: Optional[Duration] = GenTransition.__fields__[ + id: uuid.UUID = GenTransition.model_fields["id"] # type: ignore[assignment] + from_: uuid.UUID = GenTransition.model_fields["from_"] # type: ignore[assignment] + to: uuid.UUID = GenTransition.model_fields["to"] # type: ignore[assignment] + start_timers: List[uuid.UUID] = GenTransition.model_fields["start_timers"] # type: ignore[assignment] + blocking_timers: List[uuid.UUID] = GenTransition.model_fields["blocking_timers"] # type: ignore[assignment] + transition_duration: Optional[Duration] = GenTransition.model_fields[ "transition_duration" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_actuator_description.py b/src/s2python/frbc/frbc_actuator_description.py index f45083e..d832f8c 100644 --- a/src/s2python/frbc/frbc_actuator_description.py +++ b/src/s2python/frbc/frbc_actuator_description.py @@ -1,15 +1,14 @@ import uuid -from typing import List, Any, Dict +from typing import List -from pydantic import root_validator +from pydantic import model_validator from s2python.common import Transition, Timer, Commodity from s2python.common.support import commodity_has_quantity from s2python.frbc.frbc_operation_mode import FRBCOperationMode from s2python.generated.gen_s2 import ( FRBCActuatorDescription as GenFRBCActuatorDescription, - CommodityQuantity, ) from s2python.validate_values_mixin import ( S2Message, @@ -18,30 +17,28 @@ @catch_and_convert_exceptions -class FRBCActuatorDescription( - GenFRBCActuatorDescription, S2Message["FRBCActuatorDescription"] -): - class Config(GenFRBCActuatorDescription.Config): - validate_assignment = True - - id: uuid.UUID = GenFRBCActuatorDescription.__fields__["id"].field_info # type: ignore[assignment] - operation_modes: List[FRBCOperationMode] = GenFRBCActuatorDescription.__fields__[ +class FRBCActuatorDescription(GenFRBCActuatorDescription, S2Message["FRBCActuatorDescription"]): + model_config = GenFRBCActuatorDescription.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenFRBCActuatorDescription.model_fields["id"] # type: ignore[assignment] + operation_modes: List[FRBCOperationMode] = GenFRBCActuatorDescription.model_fields[ "operation_modes" - ].field_info # type: ignore[assignment] - transitions: List[Transition] = GenFRBCActuatorDescription.__fields__[ - "transitions" - ].field_info # type: ignore[assignment] - timers: List[Timer] = GenFRBCActuatorDescription.__fields__["timers"].field_info # type: ignore[assignment] - supported_commodities: List[Commodity] = GenFRBCActuatorDescription.__fields__[ + ] # type: ignore[assignment] + transitions: List[Transition] = GenFRBCActuatorDescription.model_fields["transitions"] # type: ignore[assignment] + timers: List[Timer] = GenFRBCActuatorDescription.model_fields["timers"] # type: ignore[assignment] + supported_commodities: List[Commodity] = GenFRBCActuatorDescription.model_fields[ "supported_commodities" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] - @root_validator(pre=False) + @model_validator(mode="after") @classmethod - def validate_timers_in_transitions(cls, values: Dict[str, Any]) -> Dict[str, Any]: - timers_by_id = {timer.id: timer for timer in values.get("timers", {})} + def validate_timers_in_transitions( + cls, frbc_actuator_description: "FRBCActuatorDescription" + ) -> "FRBCActuatorDescription": + timers_by_id = {timer.id: timer for timer in frbc_actuator_description.timers} transition: Transition - for transition in values.get("transitions", []): + for transition in frbc_actuator_description.transitions: for start_timer_id in transition.start_timers: if start_timer_id not in timers_by_id: raise ValueError( @@ -58,33 +55,32 @@ def validate_timers_in_transitions(cls, values: Dict[str, Any]) -> Dict[str, Any f"{transition.id} but was not defined in 'timers'.", ) - return values + return frbc_actuator_description - @root_validator(pre=False) + @model_validator(mode="after") @classmethod - def validate_timers_unique_ids(cls, values: Dict[str, Any]) -> Dict[str, Any]: + def validate_timers_unique_ids( + cls, frbc_actuator_description: "FRBCActuatorDescription" + ) -> "FRBCActuatorDescription": ids = [] timer: Timer - for timer in values.get("timers", []): + for timer in frbc_actuator_description.timers: if timer.id in ids: - raise ValueError( - cls, f"Id {timer.id} was found multiple times in 'timers'." - ) + raise ValueError(cls, f"Id {timer.id} was found multiple times in 'timers'.") ids.append(timer.id) - return values + return frbc_actuator_description - @root_validator(pre=False) + @model_validator(mode="after") @classmethod def validate_operation_modes_in_transitions( - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: + cls, frbc_actuator_description: "FRBCActuatorDescription" + ) -> "FRBCActuatorDescription": operation_mode_by_id = { - operation_mode.id: operation_mode - for operation_mode in values.get("operation_modes", []) + operation_mode.id: operation_mode for operation_mode in frbc_actuator_description.operation_modes } transition: Transition - for transition in values.get("transitions", []): + for transition in frbc_actuator_description.transitions: if transition.from_ not in operation_mode_by_id: raise ValueError( cls, @@ -99,16 +95,16 @@ def validate_operation_modes_in_transitions( f"{transition.id} but was not defined in 'operation_modes'.", ) - return values + return frbc_actuator_description - @root_validator(pre=False) + @model_validator(mode="after") @classmethod def validate_operation_modes_unique_ids( - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: + cls, frbc_actuator_description: "FRBCActuatorDescription" + ) -> "FRBCActuatorDescription": ids = [] operation_mode: FRBCOperationMode - for operation_mode in values.get("operation_modes", []): + for operation_mode in frbc_actuator_description.operation_modes: if operation_mode.id in ids: raise ValueError( cls, @@ -116,24 +112,22 @@ def validate_operation_modes_unique_ids( ) ids.append(operation_mode.id) - return values + return frbc_actuator_description - @root_validator(pre=False) + @model_validator(mode="after") @classmethod def validate_operation_mode_elements_have_all_supported_commodities( - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: - supported_commodities = values.get("supported_commodities", []) + cls, frbc_actuator_description: "FRBCActuatorDescription" + ) -> "FRBCActuatorDescription": + supported_commodities = frbc_actuator_description.supported_commodities operation_mode: FRBCOperationMode - for operation_mode in values.get("operation_modes", []): + for operation_mode in frbc_actuator_description.operation_modes: for operation_mode_element in operation_mode.elements: for commodity in supported_commodities: power_ranges_for_commodity = [ power_range for power_range in operation_mode_element.power_ranges - if commodity_has_quantity( - commodity, power_range.commodity_quantity - ) + if commodity_has_quantity(commodity, power_range.commodity_quantity) ] if len(power_ranges_for_commodity) > 1: @@ -150,16 +144,14 @@ def validate_operation_mode_elements_have_all_supported_commodities( f"mode {operation_mode.id} and element with fill_level_range " f"{operation_mode_element.fill_level_range}", ) - return values + return frbc_actuator_description - @root_validator(pre=False) + @model_validator(mode="after") @classmethod def validate_unique_supported_commodities( - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: - supported_commodities: List[CommodityQuantity] = values.get( - "supported_commodities", [] - ) + cls, frbc_actuator_description: "FRBCActuatorDescription" + ) -> "FRBCActuatorDescription": + supported_commodities: List[Commodity] = frbc_actuator_description.supported_commodities for supported_commodity in supported_commodities: if supported_commodities.count(supported_commodity) > 1: @@ -167,4 +159,4 @@ def validate_unique_supported_commodities( cls, f"Found duplicate {supported_commodity} commodity in 'supported_commodities'", ) - return values + return frbc_actuator_description diff --git a/src/s2python/frbc/frbc_actuator_status.py b/src/s2python/frbc/frbc_actuator_status.py index db3cb58..585a23d 100644 --- a/src/s2python/frbc/frbc_actuator_status.py +++ b/src/s2python/frbc/frbc_actuator_status.py @@ -10,14 +10,14 @@ @catch_and_convert_exceptions class FRBCActuatorStatus(GenFRBCActuatorStatus, S2Message["FRBCActuatorStatus"]): - class Config(GenFRBCActuatorStatus.Config): - validate_assignment = True + model_config = GenFRBCActuatorStatus.model_config + model_config["validate_assignment"] = True - active_operation_mode_id: uuid.UUID = GenFRBCActuatorStatus.__fields__[ + active_operation_mode_id: uuid.UUID = GenFRBCActuatorStatus.model_fields[ "active_operation_mode_id" - ].field_info # type: ignore[assignment] - actuator_id: uuid.UUID = GenFRBCActuatorStatus.__fields__["actuator_id"].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCActuatorStatus.__fields__["message_id"].field_info # type: ignore[assignment] - previous_operation_mode_id: Optional[uuid.UUID] = GenFRBCActuatorStatus.__fields__[ + ] # type: ignore[assignment] + actuator_id: uuid.UUID = GenFRBCActuatorStatus.model_fields["actuator_id"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCActuatorStatus.model_fields["message_id"] # type: ignore[assignment] + previous_operation_mode_id: Optional[uuid.UUID] = GenFRBCActuatorStatus.model_fields[ "previous_operation_mode_id" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_fill_level_target_profile.py b/src/s2python/frbc/frbc_fill_level_target_profile.py index 20fdc4a..38ef83b 100644 --- a/src/s2python/frbc/frbc_fill_level_target_profile.py +++ b/src/s2python/frbc/frbc_fill_level_target_profile.py @@ -14,17 +14,11 @@ @catch_and_convert_exceptions -class FRBCFillLevelTargetProfile( - GenFRBCFillLevelTargetProfile, S2Message["FRBCFillLevelTargetProfile"] -): - class Config(GenFRBCFillLevelTargetProfile.Config): - validate_assignment = True +class FRBCFillLevelTargetProfile(GenFRBCFillLevelTargetProfile, S2Message["FRBCFillLevelTargetProfile"]): + model_config = GenFRBCFillLevelTargetProfile.model_config + model_config["validate_assignment"] = True - elements: List[ - FRBCFillLevelTargetProfileElement - ] = GenFRBCFillLevelTargetProfile.__fields__[ + elements: List[FRBCFillLevelTargetProfileElement] = GenFRBCFillLevelTargetProfile.model_fields[ "elements" - ].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCFillLevelTargetProfile.__fields__[ - "message_id" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCFillLevelTargetProfile.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_fill_level_target_profile_element.py b/src/s2python/frbc/frbc_fill_level_target_profile_element.py index c6d2e22..bd2fea7 100644 --- a/src/s2python/frbc/frbc_fill_level_target_profile_element.py +++ b/src/s2python/frbc/frbc_fill_level_target_profile_element.py @@ -14,12 +14,10 @@ class FRBCFillLevelTargetProfileElement( GenFRBCFillLevelTargetProfileElement, S2Message["FRBCFillLevelTargetProfileElement"], ): - class Config(GenFRBCFillLevelTargetProfileElement.Config): - validate_assignment = True + model_config = GenFRBCFillLevelTargetProfileElement.model_config + model_config["validate_assignment"] = True - duration: Duration = GenFRBCFillLevelTargetProfileElement.__fields__[ - "duration" - ].field_info # type: ignore[assignment] - fill_level_range: NumberRange = GenFRBCFillLevelTargetProfileElement.__fields__[ + duration: Duration = GenFRBCFillLevelTargetProfileElement.model_fields["duration"] # type: ignore[assignment] + fill_level_range: NumberRange = GenFRBCFillLevelTargetProfileElement.model_fields[ "fill_level_range" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_instruction.py b/src/s2python/frbc/frbc_instruction.py index 6bd173d..584cfba 100644 --- a/src/s2python/frbc/frbc_instruction.py +++ b/src/s2python/frbc/frbc_instruction.py @@ -9,10 +9,10 @@ @catch_and_convert_exceptions class FRBCInstruction(GenFRBCInstruction, S2Message["FRBCInstruction"]): - class Config(GenFRBCInstruction.Config): - validate_assignment = True + model_config = GenFRBCInstruction.model_config + model_config["validate_assignment"] = True - actuator_id: uuid.UUID = GenFRBCInstruction.__fields__["actuator_id"].field_info # type: ignore[assignment] - id: uuid.UUID = GenFRBCInstruction.__fields__["id"].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCInstruction.__fields__["message_id"].field_info # type: ignore[assignment] - operation_mode: uuid.UUID = GenFRBCInstruction.__fields__["operation_mode"].field_info # type: ignore[assignment] + actuator_id: uuid.UUID = GenFRBCInstruction.model_fields["actuator_id"] # type: ignore[assignment] + id: uuid.UUID = GenFRBCInstruction.model_fields["id"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCInstruction.model_fields["message_id"] # type: ignore[assignment] + operation_mode: uuid.UUID = GenFRBCInstruction.model_fields["operation_mode"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_leakage_behaviour.py b/src/s2python/frbc/frbc_leakage_behaviour.py index 5543b6d..fda7d3b 100644 --- a/src/s2python/frbc/frbc_leakage_behaviour.py +++ b/src/s2python/frbc/frbc_leakage_behaviour.py @@ -11,10 +11,10 @@ @catch_and_convert_exceptions class FRBCLeakageBehaviour(GenFRBCLeakageBehaviour, S2Message["FRBCLeakageBehaviour"]): - class Config(GenFRBCLeakageBehaviour.Config): - validate_assignment = True + model_config = GenFRBCLeakageBehaviour.model_config + model_config["validate_assignment"] = True - elements: List[FRBCLeakageBehaviourElement] = GenFRBCLeakageBehaviour.__fields__[ + elements: List[FRBCLeakageBehaviourElement] = GenFRBCLeakageBehaviour.model_fields[ "elements" - ].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCLeakageBehaviour.__fields__["message_id"].field_info # type: ignore[assignment] + ] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCLeakageBehaviour.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_leakage_behaviour_element.py b/src/s2python/frbc/frbc_leakage_behaviour_element.py index 4866045..7e718ee 100644 --- a/src/s2python/frbc/frbc_leakage_behaviour_element.py +++ b/src/s2python/frbc/frbc_leakage_behaviour_element.py @@ -9,12 +9,10 @@ @catch_and_convert_exceptions -class FRBCLeakageBehaviourElement( - GenFRBCLeakageBehaviourElement, S2Message["FRBCLeakageBehaviourElement"] -): - class Config(GenFRBCLeakageBehaviourElement.Config): - validate_assignment = True +class FRBCLeakageBehaviourElement(GenFRBCLeakageBehaviourElement, S2Message["FRBCLeakageBehaviourElement"]): + model_config = GenFRBCLeakageBehaviourElement.model_config + model_config["validate_assignment"] = True - fill_level_range: NumberRange = GenFRBCLeakageBehaviourElement.__fields__[ + fill_level_range: NumberRange = GenFRBCLeakageBehaviourElement.model_fields[ "fill_level_range" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_operation_mode.py b/src/s2python/frbc/frbc_operation_mode.py index 5713763..839b3d0 100644 --- a/src/s2python/frbc/frbc_operation_mode.py +++ b/src/s2python/frbc/frbc_operation_mode.py @@ -2,7 +2,7 @@ import uuid from typing import List, Dict, Any -from pydantic import root_validator +from pydantic import model_validator from s2python.common import NumberRange from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement @@ -16,38 +16,29 @@ @catch_and_convert_exceptions class FRBCOperationMode(GenFRBCOperationMode, S2Message["FRBCOperationMode"]): - class Config(GenFRBCOperationMode.Config): - validate_assignment = True + model_config = GenFRBCOperationMode.model_config + model_config["validate_assignment"] = True - id: uuid.UUID = GenFRBCOperationMode.__fields__["id"].field_info # type: ignore[assignment] - elements: List[FRBCOperationModeElement] = GenFRBCOperationMode.__fields__[ - "elements" - ].field_info # type: ignore[assignment] + id: uuid.UUID = GenFRBCOperationMode.model_fields["id"] # type: ignore[assignment] + elements: List[FRBCOperationModeElement] = GenFRBCOperationMode.model_fields["elements"] # type: ignore[assignment] - @root_validator(pre=False) + @model_validator(mode="after") @classmethod def validate_contiguous_fill_levels_operation_mode_elements( - cls, values: Dict[str, Any] - ) -> Dict[str, Any]: + cls, frbc_operation_mode: "FRBCOperationMode" + ) -> "FRBCOperationMode": elements_by_fill_level_range: Dict[NumberRange, FRBCOperationModeElement] - elements_by_fill_level_range = { - element.fill_level_range: element for element in values.get("elements", []) - } + elements_by_fill_level_range = {element.fill_level_range: element for element in frbc_operation_mode.elements} sorted_fill_level_ranges: List[NumberRange] sorted_fill_level_ranges = list(elements_by_fill_level_range.keys()) sorted_fill_level_ranges.sort(key=lambda r: r.start_of_range) - for current_fill_level_range, next_fill_level_range in pairwise( - sorted_fill_level_ranges - ): - if ( - current_fill_level_range.end_of_range - != next_fill_level_range.start_of_range - ): + for current_fill_level_range, next_fill_level_range in pairwise(sorted_fill_level_ranges): + if current_fill_level_range.end_of_range != next_fill_level_range.start_of_range: raise ValueError( cls, f"Elements with fill level ranges {current_fill_level_range} and " f"{next_fill_level_range} are closest match to each other but not contiguous.", ) - return values + return frbc_operation_mode diff --git a/src/s2python/frbc/frbc_operation_mode_element.py b/src/s2python/frbc/frbc_operation_mode_element.py index f4d657e..054bd73 100644 --- a/src/s2python/frbc/frbc_operation_mode_element.py +++ b/src/s2python/frbc/frbc_operation_mode_element.py @@ -11,19 +11,15 @@ @catch_and_convert_exceptions -class FRBCOperationModeElement( - GenFRBCOperationModeElement, S2Message["FRBCOperationModeElement"] -): - class Config(GenFRBCOperationModeElement.Config): - validate_assignment = True +class FRBCOperationModeElement(GenFRBCOperationModeElement, S2Message["FRBCOperationModeElement"]): + model_config = GenFRBCOperationModeElement.model_config + model_config["validate_assignment"] = True - fill_level_range: NumberRange = GenFRBCOperationModeElement.__fields__[ + fill_level_range: NumberRange = GenFRBCOperationModeElement.model_fields[ "fill_level_range" - ].field_info # type: ignore[assignment] - fill_rate: NumberRange = GenFRBCOperationModeElement.__fields__["fill_rate"].field_info # type: ignore[assignment] - power_ranges: List[PowerRange] = GenFRBCOperationModeElement.__fields__[ - "power_ranges" - ].field_info # type: ignore[assignment] - running_costs: Optional[NumberRange] = GenFRBCOperationModeElement.__fields__[ + ] # type: ignore[assignment] + fill_rate: NumberRange = GenFRBCOperationModeElement.model_fields["fill_rate"] # type: ignore[assignment] + power_ranges: List[PowerRange] = GenFRBCOperationModeElement.model_fields["power_ranges"] # type: ignore[assignment] + running_costs: Optional[NumberRange] = GenFRBCOperationModeElement.model_fields[ "running_costs" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_storage_description.py b/src/s2python/frbc/frbc_storage_description.py index 77f413b..b279b70 100644 --- a/src/s2python/frbc/frbc_storage_description.py +++ b/src/s2python/frbc/frbc_storage_description.py @@ -9,12 +9,8 @@ @catch_and_convert_exceptions -class FRBCStorageDescription( - GenFRBCStorageDescription, S2Message["FRBCStorageDescription"] -): - class Config(GenFRBCStorageDescription.Config): - validate_assignment = True +class FRBCStorageDescription(GenFRBCStorageDescription, S2Message["FRBCStorageDescription"]): + model_config = GenFRBCStorageDescription.model_config + model_config["validate_assignment"] = True - fill_level_range: NumberRange = GenFRBCStorageDescription.__fields__[ - "fill_level_range" - ].field_info # type: ignore[assignment] + fill_level_range: NumberRange = GenFRBCStorageDescription.model_fields["fill_level_range"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_storage_status.py b/src/s2python/frbc/frbc_storage_status.py index 2feca7d..7940b79 100644 --- a/src/s2python/frbc/frbc_storage_status.py +++ b/src/s2python/frbc/frbc_storage_status.py @@ -9,7 +9,7 @@ @catch_and_convert_exceptions class FRBCStorageStatus(GenFRBCStorageStatus, S2Message["FRBCStorageStatus"]): - class Config(GenFRBCStorageStatus.Config): - validate_assignment = True + model_config = GenFRBCStorageStatus.model_config + model_config["validate_assignment"] = True - message_id: uuid.UUID = GenFRBCStorageStatus.__fields__["message_id"].field_info # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCStorageStatus.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_system_description.py b/src/s2python/frbc/frbc_system_description.py index 7b1a8ac..2eb5899 100644 --- a/src/s2python/frbc/frbc_system_description.py +++ b/src/s2python/frbc/frbc_system_description.py @@ -11,16 +11,12 @@ @catch_and_convert_exceptions -class FRBCSystemDescription( - GenFRBCSystemDescription, S2Message["FRBCSystemDescription"] -): - class Config(GenFRBCSystemDescription.Config): - validate_assignment = True +class FRBCSystemDescription(GenFRBCSystemDescription, S2Message["FRBCSystemDescription"]): + model_config = GenFRBCSystemDescription.model_config + model_config["validate_assignment"] = True - actuators: List[FRBCActuatorDescription] = GenFRBCSystemDescription.__fields__[ + actuators: List[FRBCActuatorDescription] = GenFRBCSystemDescription.model_fields[ "actuators" - ].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCSystemDescription.__fields__["message_id"].field_info # type: ignore[assignment] - storage: FRBCStorageDescription = GenFRBCSystemDescription.__fields__[ - "storage" - ].field_info # type: ignore[assignment] + ] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCSystemDescription.model_fields["message_id"] # type: ignore[assignment] + storage: FRBCStorageDescription = GenFRBCSystemDescription.model_fields["storage"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_timer_status.py b/src/s2python/frbc/frbc_timer_status.py index dc06787..80c86d6 100644 --- a/src/s2python/frbc/frbc_timer_status.py +++ b/src/s2python/frbc/frbc_timer_status.py @@ -9,9 +9,9 @@ @catch_and_convert_exceptions class FRBCTimerStatus(GenFRBCTimerStatus, S2Message["FRBCTimerStatus"]): - class Config(GenFRBCTimerStatus.Config): - validate_assignment = True + model_config = GenFRBCTimerStatus.model_config + model_config["validate_assignment"] = True - actuator_id: uuid.UUID = GenFRBCTimerStatus.__fields__["actuator_id"].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCTimerStatus.__fields__["message_id"].field_info # type: ignore[assignment] - timer_id: uuid.UUID = GenFRBCTimerStatus.__fields__["timer_id"].field_info # type: ignore[assignment] + actuator_id: uuid.UUID = GenFRBCTimerStatus.model_fields["actuator_id"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCTimerStatus.model_fields["message_id"] # type: ignore[assignment] + timer_id: uuid.UUID = GenFRBCTimerStatus.model_fields["timer_id"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_usage_forecast.py b/src/s2python/frbc/frbc_usage_forecast.py index 618ac53..f71fda4 100644 --- a/src/s2python/frbc/frbc_usage_forecast.py +++ b/src/s2python/frbc/frbc_usage_forecast.py @@ -11,10 +11,8 @@ @catch_and_convert_exceptions class FRBCUsageForecast(GenFRBCUsageForecast, S2Message["FRBCUsageForecast"]): - class Config(GenFRBCUsageForecast.Config): - validate_assignment = True + model_config = GenFRBCUsageForecast.model_config + model_config["validate_assignment"] = True - elements: List[FRBCUsageForecastElement] = GenFRBCUsageForecast.__fields__[ - "elements" - ].field_info # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCUsageForecast.__fields__["message_id"].field_info # type: ignore[assignment] + elements: List[FRBCUsageForecastElement] = GenFRBCUsageForecast.model_fields["elements"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCUsageForecast.model_fields["message_id"] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_usage_forecast_element.py b/src/s2python/frbc/frbc_usage_forecast_element.py index c4337eb..370c04e 100644 --- a/src/s2python/frbc/frbc_usage_forecast_element.py +++ b/src/s2python/frbc/frbc_usage_forecast_element.py @@ -10,10 +10,8 @@ @catch_and_convert_exceptions -class FRBCUsageForecastElement( - GenFRBCUsageForecastElement, S2Message["FRBCUsageForecastElement"] -): - class Config(GenFRBCUsageForecastElement.Config): - validate_assignment = True +class FRBCUsageForecastElement(GenFRBCUsageForecastElement, S2Message["FRBCUsageForecastElement"]): + model_config = GenFRBCUsageForecastElement.model_config + model_config["validate_assignment"] = True - duration: Duration = GenFRBCUsageForecastElement.__fields__["duration"].field_info # type: ignore[assignment] + duration: Duration = GenFRBCUsageForecastElement.model_fields["duration"] # type: ignore[assignment] diff --git a/src/s2python/generated/gen_s2.py b/src/s2python/generated/gen_s2.py index 55da95f..c7febd6 100644 --- a/src/s2python/generated/gen_s2.py +++ b/src/s2python/generated/gen_s2.py @@ -1,22 +1,30 @@ # generated by datamodel-codegen: # filename: openapi.yml -# timestamp: 2024-07-29T08:53:39+00:00 +# timestamp: 2024-07-29T10:18:52+00:00 from __future__ import annotations -from datetime import datetime from enum import Enum from typing import List, Optional -from pydantic import BaseModel, Extra, Field, conint, constr +from pydantic import ( + AwareDatetime, + BaseModel, + ConfigDict, + Field, + RootModel, + conint, + constr, +) +from typing_extensions import Literal -class Duration(BaseModel): - __root__: conint(ge=0) = Field(..., description='Duration in milliseconds') +class Duration(RootModel[conint(ge=0)]): + root: conint(ge=0) = Field(..., description='Duration in milliseconds') -class ID(BaseModel): - __root__: constr(regex=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') +class ID(RootModel[constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}')]): + root: constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') class Currency(Enum): @@ -158,9 +166,9 @@ class ReceptionStatusValues(Enum): class NumberRange(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) start_of_range: float = Field( ..., description='Number that defines the start of the range' ) @@ -170,9 +178,9 @@ class Config: class Transition(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', @@ -189,14 +197,14 @@ class Config: start_timers: List[ID] = Field( ..., description='List of IDs of Timers that will be (re)started when this transition is initiated', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) blocking_timers: List[ID] = Field( ..., description='List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) transition_costs: Optional[float] = Field( None, @@ -213,9 +221,9 @@ class Config: class Timer(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', @@ -231,9 +239,9 @@ class Config: class PEBCPowerEnvelopeElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) duration: Duration = Field(..., description='The duration of the element') upper_limit: float = Field( ..., @@ -246,9 +254,9 @@ class Config: class FRBCStorageDescription(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) diagnostic_label: Optional[str] = Field( None, description='Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.', @@ -276,9 +284,9 @@ class Config: class FRBCLeakageBehaviourElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) fill_level_range: NumberRange = Field( ..., description='The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.', @@ -290,9 +298,9 @@ class Config: class FRBCUsageForecastElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) duration: Duration = Field( ..., description='Indicator for how long the given usage_rate is valid.' ) @@ -327,9 +335,9 @@ class Config: class FRBCFillLevelTargetProfileElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) duration: Duration = Field(..., description='The duration of the element.') fill_level_range: NumberRange = Field( ..., @@ -338,9 +346,9 @@ class Config: class DDBCAverageDemandRateForecastElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) duration: Duration = Field(..., description='Duration of the element') demand_rate_upper_limit: Optional[float] = Field( None, @@ -438,55 +446,55 @@ class PPBCPowerSequenceStatus(Enum): class OMBCTimerStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('OMBC.TimerStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.TimerStatus'] = 'OMBC.TimerStatus' message_id: ID timer_id: ID = Field(..., description='The ID of the timer this message refers to') - finished_at: datetime = Field( + finished_at: AwareDatetime = Field( ..., description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', ) class FRBCTimerStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.TimerStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.TimerStatus'] = 'FRBC.TimerStatus' message_id: ID timer_id: ID = Field(..., description='The ID of the timer this message refers to') actuator_id: ID = Field( ..., description='The ID of the actuator the timer belongs to' ) - finished_at: datetime = Field( + finished_at: AwareDatetime = Field( ..., description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', ) class DDBCTimerStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('DDBC.TimerStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.TimerStatus'] = 'DDBC.TimerStatus' message_id: ID timer_id: ID = Field(..., description='The ID of the timer this message refers to') actuator_id: ID = Field( ..., description='The ID of the actuator the timer belongs to' ) - finished_at: datetime = Field( + finished_at: AwareDatetime = Field( ..., description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', ) class SelectControlType(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('SelectControlType', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['SelectControlType'] = 'SelectControlType' message_id: ID control_type: ControlType = Field( ..., @@ -495,10 +503,10 @@ class Config: class SessionRequest(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('SessionRequest', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['SessionRequest'] = 'SessionRequest' message_id: ID request: SessionRequestType = Field(..., description='The type of request') diagnostic_label: Optional[str] = Field( @@ -508,10 +516,10 @@ class Config: class RevokeObject(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('RevokeObject', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['RevokeObject'] = 'RevokeObject' message_id: ID object_type: RevokableObjects = Field( ..., description='The type of object that needs to be revoked' @@ -520,10 +528,10 @@ class Config: class Handshake(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('Handshake', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['Handshake'] = 'Handshake' message_id: ID role: EnergyManagementRole = Field( ..., description='The role of the sender of this message' @@ -531,15 +539,15 @@ class Config: supported_protocol_versions: Optional[List[str]] = Field( None, description='Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.', - min_items=1, + min_length=1, ) class HandshakeResponse(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('HandshakeResponse', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['HandshakeResponse'] = 'HandshakeResponse' message_id: ID selected_protocol_version: str = Field( ..., description='The protocol version the CEM selected for this session' @@ -547,10 +555,10 @@ class Config: class ReceptionStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('ReceptionStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['ReceptionStatus'] = 'ReceptionStatus' subject_message_id: ID = Field( ..., description='The message this ReceptionStatus refers to' ) @@ -564,10 +572,10 @@ class Config: class InstructionStatusUpdate(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('InstructionStatusUpdate', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['InstructionStatusUpdate'] = 'InstructionStatusUpdate' message_id: ID instruction_id: ID = Field( ..., description='ID of this instruction (as provided by the CEM) ' @@ -575,26 +583,26 @@ class Config: status_type: InstructionStatus = Field( ..., description='Present status of this instruction.' ) - timestamp: datetime = Field( + timestamp: AwareDatetime = Field( ..., description='Timestamp when status_type has changed the last time.' ) class PEBCEnergyConstraint(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PEBC.EnergyConstraint', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PEBC.EnergyConstraint'] = 'PEBC.EnergyConstraint' message_id: ID id: ID = Field( ..., 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.', ) - valid_from: datetime = Field( + valid_from: AwareDatetime = Field( ..., description='Moment this PEBC.EnergyConstraints information starts to be valid', ) - valid_until: datetime = Field( + valid_until: AwareDatetime = Field( ..., description='Moment until this PEBC.EnergyConstraints information is valid.', ) @@ -613,10 +621,10 @@ class Config: class PPBCScheduleInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PPBC.ScheduleInstruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.ScheduleInstruction'] = 'PPBC.ScheduleInstruction' message_id: ID id: ID = Field( ..., @@ -634,7 +642,7 @@ class Config: ..., description='ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.', ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -645,10 +653,12 @@ class Config: class PPBCStartInterruptionInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PPBC.StartInterruptionInstruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.StartInterruptionInstruction'] = ( + 'PPBC.StartInterruptionInstruction' + ) message_id: ID id: ID = Field( ..., @@ -665,7 +675,7 @@ class Config: power_sequence_id: ID = Field( ..., description='ID of the PPBC.PowerSequence that the CEM wants to interrupt.' ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -676,10 +686,12 @@ class Config: class PPBCEndInterruptionInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PPBC.EndInterruptionInstruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.EndInterruptionInstruction'] = ( + 'PPBC.EndInterruptionInstruction' + ) message_id: ID id: ID = Field( ..., @@ -697,7 +709,7 @@ class Config: ..., description='ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.', ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -708,10 +720,10 @@ class Config: class OMBCStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('OMBC.Status', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.Status'] = 'OMBC.Status' message_id: ID active_operation_mode_id: ID = Field( ..., description='ID of the active OMBC.OperationMode.' @@ -724,23 +736,23 @@ class Config: None, description='ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', ) - transition_timestamp: Optional[datetime] = Field( + transition_timestamp: Optional[AwareDatetime] = Field( None, description='Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', ) class OMBCInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('OMBC.Instruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.Instruction'] = 'OMBC.Instruction' message_id: ID id: ID = Field( ..., description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -758,10 +770,10 @@ class Config: class FRBCActuatorStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.ActuatorStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.ActuatorStatus'] = 'FRBC.ActuatorStatus' message_id: ID actuator_id: ID = Field( ..., description='ID of the actuator this messages refers to' @@ -777,17 +789,17 @@ class Config: None, description='ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', ) - transition_timestamp: Optional[datetime] = Field( + transition_timestamp: Optional[AwareDatetime] = Field( None, description='Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', ) class FRBCStorageStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.StorageStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.StorageStatus'] = 'FRBC.StorageStatus' message_id: ID present_fill_level: float = Field( ..., description='Present fill level of the Storage' @@ -795,28 +807,28 @@ class Config: class FRBCLeakageBehaviour(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.LeakageBehaviour', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.LeakageBehaviour'] = 'FRBC.LeakageBehaviour' message_id: ID - valid_from: datetime = Field( + valid_from: AwareDatetime = Field( ..., description='Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.', ) elements: List[FRBCLeakageBehaviourElement] = Field( ..., description='List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class FRBCInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.Instruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.Instruction'] = 'FRBC.Instruction' message_id: ID id: ID = Field( ..., @@ -832,7 +844,7 @@ class Config: ..., description='The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -843,44 +855,44 @@ class Config: class FRBCUsageForecast(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.UsageForecast', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.UsageForecast'] = 'FRBC.UsageForecast' message_id: ID - start_time: datetime = Field( + start_time: AwareDatetime = Field( ..., description='Time at which the FRBC.UsageForecast starts.' ) elements: List[FRBCUsageForecastElement] = Field( ..., description='Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class FRBCFillLevelTargetProfile(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.FillLevelTargetProfile', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.FillLevelTargetProfile'] = 'FRBC.FillLevelTargetProfile' message_id: ID - start_time: datetime = Field( + start_time: AwareDatetime = Field( ..., description='Time at which the FRBC.FillLevelTargetProfile starts.' ) elements: List[FRBCFillLevelTargetProfileElement] = Field( ..., description='List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class DDBCActuatorStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('DDBC.ActuatorStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.ActuatorStatus'] = 'DDBC.ActuatorStatus' message_id: ID actuator_id: ID = Field( ..., description='ID of the actuator this messages refers to' @@ -897,23 +909,23 @@ class Config: None, description='ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', ) - transition_timestamp: Optional[datetime] = Field( + transition_timestamp: Optional[AwareDatetime] = Field( None, description='Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', ) class DDBCInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('DDBC.Instruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.Instruction'] = 'DDBC.Instruction' message_id: ID id: ID = Field( ..., description='Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -932,24 +944,26 @@ class Config: class DDBCAverageDemandRateForecast(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('DDBC.AverageDemandRateForecast', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.AverageDemandRateForecast'] = ( + 'DDBC.AverageDemandRateForecast' + ) message_id: ID - start_time: datetime = Field(..., description='Start time of the profile.') + start_time: AwareDatetime = Field(..., description='Start time of the profile.') elements: List[DDBCAverageDemandRateForecastElement] = Field( ..., description='Elements of the profile. Elements must be placed in chronological order.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class PowerValue(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) commodity_quantity: CommodityQuantity = Field( ..., description='The power quantity the value refers to' ) @@ -960,9 +974,9 @@ class Config: class PowerForecastValue(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) value_upper_limit: Optional[float] = Field( None, description='The upper boundary of the range with 100 % certainty the power value is in it', @@ -994,9 +1008,9 @@ class Config: class PowerRange(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) start_of_range: float = Field( ..., description='Power value that defines the start of the range.' ) @@ -1009,9 +1023,9 @@ class Config: class Role(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) role: RoleType = Field( ..., description='Role type of the Resource Manager for the given commodity' ) @@ -1019,22 +1033,22 @@ class Config: class PowerForecastElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) duration: Duration = Field(..., description='Duration of the PowerForecastElement') power_values: List[PowerForecastValue] = Field( ..., description='The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) class PEBCAllowedLimitRange(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) commodity_quantity: CommodityQuantity = Field( ..., description='Type of power quantity this PEBC.AllowedLimitRange applies to' ) @@ -1053,9 +1067,9 @@ class Config: class PEBCPowerEnvelope(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', @@ -1066,30 +1080,30 @@ class Config: power_envelope_elements: List[PEBCPowerEnvelopeElement] = Field( ..., description='The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class PPBCPowerSequenceElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) duration: Duration = Field( ..., description='Duration of the PPBC.PowerSequenceElement.' ) power_values: List[PowerForecastValue] = Field( ..., description='The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) class PPBCPowerSequenceContainerStatus(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) power_profile_id: ID = Field( ..., description='ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ', @@ -1112,9 +1126,9 @@ class Config: class OMBCOperationMode(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', @@ -1126,8 +1140,8 @@ class Config: power_ranges: List[PowerRange] = Field( ..., description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) running_costs: Optional[NumberRange] = Field( None, @@ -1140,9 +1154,9 @@ class Config: class FRBCOperationModeElement(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) fill_level_range: NumberRange = Field( ..., description='The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.', @@ -1154,8 +1168,8 @@ class Config: power_ranges: List[PowerRange] = Field( ..., description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) running_costs: Optional[NumberRange] = Field( None, @@ -1164,9 +1178,9 @@ class Config: class DDBCOperationMode(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) Id: ID = Field( ..., description='ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.', @@ -1178,8 +1192,8 @@ class Config: power_ranges: List[PowerRange] = Field( ..., description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) supply_range: NumberRange = Field( ..., @@ -1196,10 +1210,10 @@ class Config: class ResourceManagerDetails(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('ResourceManagerDetails', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['ResourceManagerDetails'] = 'ResourceManagerDetails' message_id: ID resource_id: ID = Field( ..., @@ -1209,8 +1223,8 @@ class Config: roles: List[Role] = Field( ..., description='Each Resource Manager provides one or more energy Roles', - max_items=3, - min_items=1, + max_length=3, + min_length=1, ) manufacturer: Optional[str] = Field(None, description='Name of Manufacturer') model: Optional[str] = Field( @@ -1231,8 +1245,8 @@ class Config: available_control_types: List[ControlType] = Field( ..., description='The control types supported by this Resource Manager.', - max_items=5, - min_items=1, + max_length=5, + min_length=1, ) currency: Optional[Currency] = Field( None, @@ -1245,59 +1259,59 @@ class Config: provides_power_measurement_types: List[CommodityQuantity] = Field( ..., description='Array of all CommodityQuantities that this Resource Manager can provide measurements for. ', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) class PowerMeasurement(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PowerMeasurement', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PowerMeasurement'] = 'PowerMeasurement' message_id: ID - measurement_timestamp: datetime = Field( + measurement_timestamp: AwareDatetime = Field( ..., description='Timestamp when PowerValues were measured.' ) values: List[PowerValue] = Field( ..., description='Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) class PowerForecast(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PowerForecast', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PowerForecast'] = 'PowerForecast' message_id: ID - start_time: datetime = Field( + start_time: AwareDatetime = Field( ..., description='Start time of time period that is covered by the profile.' ) elements: List[PowerForecastElement] = Field( ..., description='Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class PEBCPowerConstraints(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PEBC.PowerConstraints', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PEBC.PowerConstraints'] = 'PEBC.PowerConstraints' message_id: ID id: ID = Field( ..., description='Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) - valid_from: datetime = Field( + valid_from: AwareDatetime = Field( ..., description='Moment this PEBC.PowerConstraints start to be valid' ) - valid_until: Optional[datetime] = Field( + valid_until: Optional[AwareDatetime] = Field( None, description='Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.', ) @@ -1307,22 +1321,22 @@ class Config: allowed_limit_ranges: List[PEBCAllowedLimitRange] = Field( ..., description='The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.', - max_items=100, - min_items=2, + max_length=100, + min_length=2, ) class PEBCInstruction(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PEBC.Instruction', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PEBC.Instruction'] = 'PEBC.Instruction' message_id: ID id: ID = Field( ..., description='Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) - execution_time: datetime = Field( + execution_time: AwareDatetime = Field( ..., 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.', ) @@ -1337,59 +1351,59 @@ class Config: power_envelopes: List[PEBCPowerEnvelope] = Field( ..., description='The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) class PPBCPowerProfileStatus(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PPBC.PowerProfileStatus', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.PowerProfileStatus'] = 'PPBC.PowerProfileStatus' message_id: ID sequence_container_status: List[PPBCPowerSequenceContainerStatus] = Field( ..., description='Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.', - max_items=1000, - min_items=1, + max_length=1000, + min_length=1, ) class OMBCSystemDescription(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('OMBC.SystemDescription', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.SystemDescription'] = 'OMBC.SystemDescription' message_id: ID - valid_from: datetime = Field( + valid_from: AwareDatetime = Field( ..., description='Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', ) operation_modes: List[OMBCOperationMode] = Field( ..., description='OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.', - max_items=100, - min_items=1, + max_length=100, + min_length=1, ) transitions: List[Transition] = Field( ..., description='Possible transitions to switch from one OMBC.OperationMode to another.', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) timers: List[Timer] = Field( ..., description='Timers that control when certain transitions can be made.', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) class PPBCPowerSequence(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.', @@ -1397,8 +1411,8 @@ class Config: elements: List[PPBCPowerSequenceElement] = Field( ..., description='List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) is_interruptible: bool = Field( ..., @@ -1415,9 +1429,9 @@ class Config: class FRBCOperationMode(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.', @@ -1429,8 +1443,8 @@ class Config: elements: List[FRBCOperationModeElement] = Field( ..., description='List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.', - max_items=100, - min_items=1, + max_length=100, + min_length=1, ) abnormal_condition_only: bool = Field( ..., @@ -1439,9 +1453,9 @@ class Config: class DDBCActuatorDescription(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', @@ -1453,44 +1467,44 @@ class Config: supported_commodites: List[Commodity] = Field( ..., description='Commodities supported by the operation modes of this actuator. There shall be at least one commodity', - max_items=4, - min_items=1, + max_length=4, + min_length=1, ) operation_modes: List[DDBCOperationMode] = Field( ..., description='List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.', - max_items=100, - min_items=1, + max_length=100, + min_length=1, ) transitions: List[Transition] = Field( ..., description='List of Transitions between Operation Modes. Shall contain at least one Transition.', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) timers: List[Timer] = Field( ..., description='List of Timers associated with Transitions for this Actuator. Can be empty.', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) class DDBCSystemDescription(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('DDBC.SystemDescription', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.SystemDescription'] = 'DDBC.SystemDescription' message_id: ID - valid_from: datetime = Field( + valid_from: AwareDatetime = Field( ..., description='Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', ) actuators: List[DDBCActuatorDescription] = Field( ..., description='List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.', - max_items=10, - min_items=1, + max_length=10, + min_length=1, ) present_demand_rate: NumberRange = Field( ..., description='Present demand rate that needs to be satisfied by the system' @@ -1502,9 +1516,9 @@ class Config: class PPBCPowerSequenceContainer(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.', @@ -1512,15 +1526,15 @@ class Config: power_sequences: List[PPBCPowerSequence] = Field( ..., description='List of alternative Sequences where one could be chosen by the CEM', - max_items=288, - min_items=1, + max_length=288, + min_length=1, ) class FRBCActuatorDescription(BaseModel): - class Config: - extra = Extra.forbid - + model_config = ConfigDict( + extra='forbid', + ) id: ID = Field( ..., description='ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', @@ -1530,65 +1544,68 @@ class Config: description='Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', ) supported_commodities: List[Commodity] = Field( - ..., description='List of all supported Commodities.', max_items=4, min_items=1 + ..., + description='List of all supported Commodities.', + max_length=4, + min_length=1, ) operation_modes: List[FRBCOperationMode] = Field( ..., description='Provided FRBC.OperationModes associated with this actuator', - max_items=100, - min_items=1, + max_length=100, + min_length=1, ) transitions: List[Transition] = Field( ..., description='Possible transitions between FRBC.OperationModes associated with this actuator.', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) timers: List[Timer] = Field( ..., description='List of Timers associated with this actuator', - max_items=1000, - min_items=0, + max_length=1000, + min_length=0, ) class PPBCPowerProfileDefinition(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('PPBC.PowerProfileDefinition', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.PowerProfileDefinition'] = 'PPBC.PowerProfileDefinition' message_id: ID id: ID = Field( ..., description='ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) - start_time: datetime = Field( + start_time: AwareDatetime = Field( ..., description='Indicates the first possible time the first PPBC.PowerSequence could start', ) - end_time: datetime = Field( + end_time: AwareDatetime = Field( ..., description='Indicates when the last PPBC.PowerSequence shall be finished at the latest', ) power_sequences_containers: List[PPBCPowerSequenceContainer] = Field( ..., description='The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.', - max_items=1000, - min_items=1, + max_length=1000, + min_length=1, ) class FRBCSystemDescription(BaseModel): - class Config: - extra = Extra.forbid - - message_type: str = Field('FRBC.SystemDescription', const=True) + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.SystemDescription'] = 'FRBC.SystemDescription' message_id: ID - valid_from: datetime = Field( + valid_from: AwareDatetime = Field( ..., description='Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', ) actuators: List[FRBCActuatorDescription] = Field( - ..., description='Details of all Actuators.', max_items=10, min_items=1 + ..., description='Details of all Actuators.', max_length=10, min_length=1 ) storage: FRBCStorageDescription = Field(..., description='Details of the storage.') diff --git a/src/s2python/s2_parser.py b/src/s2python/s2_parser.py index 399d045..44b1c34 100644 --- a/src/s2python/s2_parser.py +++ b/src/s2python/s2_parser.py @@ -81,7 +81,7 @@ def parse_as_any_message(unparsed_message: Union[dict, str]) -> S2Message: f"Unable to parse {message_type} as an S2 message. Type unknown.", ) - return TYPE_TO_MESSAGE_CLASS[message_type].parse_obj(message_json) + return TYPE_TO_MESSAGE_CLASS[message_type].model_validate(message_json) @staticmethod def parse_as_message(unparsed_message: Union[dict, str], as_message: Type[M]) -> M: @@ -96,9 +96,7 @@ def parse_as_message(unparsed_message: Union[dict, str], as_message: Type[M]) -> return as_message.from_dict(message_json) @staticmethod - def parse_message_type( - unparsed_message: Union[dict, str] - ) -> Optional[S2MessageType]: + def parse_message_type(unparsed_message: Union[dict, str]) -> Optional[S2MessageType]: """Parse only the message type from the unparsed message. This is useful to call before `parse_as_message` to retrieve the message type and allows for strictly-typed diff --git a/src/s2python/s2_validation_error.py b/src/s2python/s2_validation_error.py index 0a498ea..60d255d 100644 --- a/src/s2python/s2_validation_error.py +++ b/src/s2python/s2_validation_error.py @@ -1,13 +1,12 @@ -from typing import Union +from dataclasses import dataclass +from typing import Union, Type, Optional from pydantic import ValidationError +@dataclass class S2ValidationError(Exception): + class_: Optional[Type] obj: object msg: str pydantic_validation_error: Union[ValidationError, TypeError, None] - - def __init__(self, obj: object, msg: str): - self.obj = obj - self.msg = msg diff --git a/src/s2python/validate_values_mixin.py b/src/s2python/validate_values_mixin.py index 14e305a..f3225ae 100644 --- a/src/s2python/validate_values_mixin.py +++ b/src/s2python/validate_values_mixin.py @@ -1,3 +1,4 @@ +import inspect from typing import ( TypeVar, Generic, @@ -15,11 +16,11 @@ from pydantic import ( # pylint: disable=no-name-in-module BaseModel, - StrBytes, - Protocol as PydanticProtocol, ValidationError, + RootModel, ) -from pydantic.error_wrappers import display_errors # pylint: disable=no-name-in-module +from pydantic.deprecated.parse import Protocol as PydanticProtocol +from pydantic.v1.error_wrappers import display_errors # pylint: disable=no-name-in-module from s2python.s2_validation_error import S2ValidationError @@ -32,19 +33,15 @@ class SupportsValidation(Protocol[B_co]): # ValidateValuesMixin methods - def to_json(self) -> str: - ... + def to_json(self) -> str: ... - def to_dict(self) -> Dict: - ... + def to_dict(self) -> Dict: ... @classmethod - def from_json(cls, json_str: str) -> B_co: - ... + def from_json(cls, json_str: str) -> B_co: ... @classmethod - def from_dict(cls, json_dict: Dict) -> B_co: - ... + def from_dict(cls, json_dict: Dict) -> B_co: ... # Pydantic methods def json( # pylint: disable=too-many-arguments @@ -60,8 +57,7 @@ def json( # pylint: disable=too-many-arguments encoder: Optional[Callable[[Any], Any]] = None, models_as_dict: bool = True, **dumps_kwargs: Any, - ) -> str: - ... + ) -> str: ... def dict( # pylint: disable=too-many-arguments self, @@ -73,24 +69,21 @@ def dict( # pylint: disable=too-many-arguments exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - ) -> Dict[str, Any]: - ... + ) -> Dict[str, Any]: ... @classmethod - def parse_raw( # pylint: disable=too-many-arguments + def model_validate_json( # pylint: disable=too-many-arguments cls, - b: StrBytes, + b: Union[str, bytes], *, - content_type: str = ..., + content_type: Optional[str] = ..., encoding: str = ..., proto: PydanticProtocol = ..., allow_pickle: bool = ..., - ) -> B_co: - ... + ) -> B_co: ... @classmethod - def parse_obj(cls, obj: Any) -> "B_co": - ... + def model_validate(cls, obj: Any) -> "B_co": ... C = TypeVar("C", bound="SupportsValidation") @@ -99,27 +92,25 @@ def parse_obj(cls, obj: Any) -> "B_co": class ValidateValuesMixin(Generic[C]): def to_json(self: C) -> str: try: - return self.json(by_alias=True, exclude_none=True) + return self.model_dump_json(by_alias=True, exclude_none=True) except (ValidationError, TypeError) as e: - raise S2ValidationError( - self, "Pydantic raised a format validation error." - ) from e + raise S2ValidationError(type(self), self, "Pydantic raised a format validation error.", e) from e def to_dict(self: C) -> dict: return self.dict() @classmethod def from_json(cls: Type[C], json_str: str) -> C: - gen_model: C = cls.parse_raw(json_str) + gen_model: C = cls.model_validate_json(json_str) return gen_model @classmethod def from_dict(cls: Type[C], json_dict: dict) -> C: - gen_model: C = cls.parse_obj(json_dict) + gen_model: C = cls.model_validate(json_dict) return gen_model -class S2Message(Generic[C], ValidateValuesMixin[C], BaseModel): +class S2Message(Generic[C], ValidateValuesMixin[C]): pass @@ -128,9 +119,15 @@ def inner(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: try: return f(*args, **kwargs) except ValidationError as e: - raise S2ValidationError(args, display_errors(e.errors())) from e + if isinstance(args[0], BaseModel): + class_type = type(args[0]) + args = args[1:] + else: + class_type = None + + raise S2ValidationError(class_type, args, display_errors(e.errors()), e) from e except TypeError as e: - raise S2ValidationError(args, str(e)) from e + raise S2ValidationError(None, args, str(e), e) from e inner.__doc__ = f.__doc__ inner.__annotations__ = f.__annotations__ @@ -143,6 +140,7 @@ def catch_and_convert_exceptions( ) -> Type[SupportsValidation[B_co]]: input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign] input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign] - input_class.parse_raw = convert_to_s2exception(input_class.parse_raw) # type: ignore[method-assign] + input_class.model_validate_json = convert_to_s2exception(input_class.model_validate_json) # type: ignore[method-assign] + input_class.model_validate = convert_to_s2exception(input_class.model_validate) # type: ignore[method-assign] return input_class diff --git a/tests/unit/common/duration_test.py b/tests/unit/common/duration_test.py index 33c5ca7..87b6f36 100644 --- a/tests/unit/common/duration_test.py +++ b/tests/unit/common/duration_test.py @@ -13,11 +13,11 @@ def test__from_timedelta__happy_path(self): duration = Duration.from_timedelta(duration_timedelta) # Assert - self.assertEqual(duration.__root__, 10_000) + self.assertEqual(duration.root, 10_000) def test__to_timedelta__happy_path(self): # Arrange - duration = Duration(__root__=20_000) + duration = Duration(20_000) # Act duration_timedelta = duration.to_timedelta() diff --git a/tests/unit/common/transition_test.py b/tests/unit/common/transition_test.py index d3ecebe..765239e 100644 --- a/tests/unit/common/transition_test.py +++ b/tests/unit/common/transition_test.py @@ -25,15 +25,9 @@ def test__from_json__happy_path_full(self): transition: Transition = Transition.from_json(json_str) # Assert - self.assertEqual( - transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3") - ) - self.assertEqual( - transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2") - ) - self.assertEqual( - transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1") - ) + self.assertEqual(transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3")) + self.assertEqual(transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2")) + self.assertEqual(transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1")) self.assertEqual( transition.start_timers, [ @@ -47,9 +41,7 @@ def test__from_json__happy_path_full(self): ) self.assertEqual(transition.transition_costs, 4.3) assert transition.transition_duration is not None - self.assertEqual( - transition.transition_duration.to_timedelta(), timedelta(seconds=1.5) - ) + self.assertEqual(transition.transition_duration.to_timedelta(), timedelta(seconds=1.5)) self.assertEqual(transition.abnormal_condition_only, False) def test__from_json__happy_path_min(self): @@ -67,15 +59,9 @@ def test__from_json__happy_path_min(self): transition: Transition = Transition.from_json(json_str) # Assert - self.assertEqual( - transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3") - ) - self.assertEqual( - transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2") - ) - self.assertEqual( - transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1") - ) + self.assertEqual(transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3")) + self.assertEqual(transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2")) + self.assertEqual(transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1")) self.assertEqual(transition.start_timers, []) self.assertEqual(transition.blocking_timers, []) self.assertEqual(transition.transition_costs, None) @@ -120,9 +106,7 @@ def test__to_json__happy_path(self): "to": uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1"), "start_timers": [], "blocking_timers": [], - "transition_duration": Duration.from_timedelta( - timedelta(minutes=1, seconds=1) - ), + "transition_duration": Duration.from_timedelta(timedelta(minutes=1, seconds=1)), "abnormal_condition_only": False, } ) @@ -151,6 +135,6 @@ def test__to_json__value_validation_error_neg_duration(self): to=uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1"), start_timers=[], blocking_timers=[], - transition_duration=Duration(__root__=-5000), + transition_duration=Duration(-5000), abnormal_condition_only=False, ) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py index 6fe04d3..b3bb5c8 100644 --- a/tests/unit/frbc/frbc_actuator_status_test.py +++ b/tests/unit/frbc/frbc_actuator_status_test.py @@ -18,7 +18,7 @@ def test__from_json__happy_path_full(self): "message_type": "FRBC.ActuatorStatus", "operation_mode_factor": 6919.960475850124, "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", - "transition_timestamp": "2020-01-02T07:56:46+00:00" + "transition_timestamp": "2020-01-02T07:56:46Z" } """ @@ -65,9 +65,7 @@ def test__to_json__happy_path_full(self): message_id=uuid.UUID("07f3d559-63c5-4369-a9e0-deed4195f651"), message_type="FRBC.ActuatorStatus", operation_mode_factor=6919.960475850124, - previous_operation_mode_id=uuid.UUID( - "2ed8f7de-cbaa-4cab-9d25-6792317aa284" - ), + previous_operation_mode_id=uuid.UUID("2ed8f7de-cbaa-4cab-9d25-6792317aa284"), transition_timestamp=datetime( year=2020, month=1, @@ -90,6 +88,6 @@ def test__to_json__happy_path_full(self): "message_type": "FRBC.ActuatorStatus", "operation_mode_factor": 6919.960475850124, "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", - "transition_timestamp": "2020-01-02T07:56:46+00:00", + "transition_timestamp": "2020-01-02T07:56:46Z", } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_fill_level_target_profile_test.py b/tests/unit/frbc/frbc_fill_level_target_profile_test.py index 5a3ef0e..046a0bc 100644 --- a/tests/unit/frbc/frbc_fill_level_target_profile_test.py +++ b/tests/unit/frbc/frbc_fill_level_target_profile_test.py @@ -23,7 +23,7 @@ def test__from_json__happy_path_full(self): ], "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", "message_type": "FRBC.FillLevelTargetProfile", - "start_time": "2021-04-17T00:19:20+00:00" + "start_time": "2021-04-17T00:19:20Z" } """ @@ -47,9 +47,7 @@ def test__from_json__happy_path_full(self): frbc_fill_level_target_profile.message_id, uuid.UUID("04a6c8af-ca8d-420c-9c11-e96a70fe82b1"), ) - self.assertEqual( - frbc_fill_level_target_profile.message_type, "FRBC.FillLevelTargetProfile" - ) + self.assertEqual(frbc_fill_level_target_profile.message_type, "FRBC.FillLevelTargetProfile") self.assertEqual( frbc_fill_level_target_profile.start_time, datetime( @@ -104,6 +102,6 @@ def test__to_json__happy_path_full(self): ], "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", "message_type": "FRBC.FillLevelTargetProfile", - "start_time": "2021-04-17T00:19:20+00:00", + "start_time": "2021-04-17T00:19:20Z", } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_test.py b/tests/unit/frbc/frbc_leakage_behaviour_test.py index c245df5..ad290d1 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_test.py @@ -23,7 +23,7 @@ def test__from_json__happy_path_full(self): ], "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", "message_type": "FRBC.LeakageBehaviour", - "valid_from": "2022-05-26T15:02:32+00:00" + "valid_from": "2022-05-26T15:02:32Z" } """ @@ -102,6 +102,6 @@ def test__to_json__happy_path_full(self): ], "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", "message_type": "FRBC.LeakageBehaviour", - "valid_from": "2022-05-26T15:02:32+00:00", + "valid_from": "2022-05-26T15:02:32Z", } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py index 5b5dc36..9950ea7 100644 --- a/tests/unit/frbc/frbc_system_description_test.py +++ b/tests/unit/frbc/frbc_system_description_test.py @@ -87,7 +87,7 @@ def test__from_json__happy_path_full(self): "provides_leakage_behaviour": true, "provides_usage_forecast": false }, - "valid_from": "2020-10-07T06:30:55+00:00" + "valid_from": "2020-10-07T06:30:55Z" } """ @@ -151,9 +151,7 @@ def test__from_json__happy_path_full(self): timers=[ Timer( diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta( - timedelta(milliseconds=14099) - ), + duration=Duration.from_timedelta(timedelta(milliseconds=14099)), id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), ) ], @@ -171,9 +169,7 @@ def test__from_json__happy_path_full(self): FRBCStorageDescription( diagnostic_label="some-test-string8418", fill_level_label="some-test-string9512", - fill_level_range=NumberRange( - end_of_range=20876.752745956997, start_of_range=18324.0229135081 - ), + fill_level_range=NumberRange(end_of_range=20876.752745956997, start_of_range=18324.0229135081), provides_fill_level_target_profile=False, provides_leakage_behaviour=True, provides_usage_forecast=False, @@ -248,9 +244,7 @@ def test__to_json__happy_path_full(self): timers=[ Timer( diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta( - timedelta(milliseconds=14099) - ), + duration=Duration.from_timedelta(timedelta(milliseconds=14099)), id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), ) ], @@ -262,9 +256,7 @@ def test__to_json__happy_path_full(self): storage=FRBCStorageDescription( diagnostic_label="some-test-string8418", fill_level_label="some-test-string9512", - fill_level_range=NumberRange( - end_of_range=20876.752745956997, start_of_range=18324.0229135081 - ), + fill_level_range=NumberRange(end_of_range=20876.752745956997, start_of_range=18324.0229135081), provides_fill_level_target_profile=False, provides_leakage_behaviour=True, provides_usage_forecast=False, @@ -354,6 +346,6 @@ def test__to_json__happy_path_full(self): "provides_leakage_behaviour": True, "provides_usage_forecast": False, }, - "valid_from": "2020-10-07T06:30:55+00:00", + "valid_from": "2020-10-07T06:30:55Z", } self.assertEqual(json.loads(json_str), expected_json) From a3d0f914dd2040801ae1eb6d265bbaa70cc01a4c Mon Sep 17 00:00:00 2001 From: Sebastiaan la Fleur Date: Mon, 29 Jul 2024 14:10:41 +0200 Subject: [PATCH 2/3] 10: Solve linting issues. --- src/s2python/common/number_range.py | 2 +- src/s2python/common/power_range.py | 2 -- src/s2python/frbc/frbc_operation_mode.py | 2 +- src/s2python/frbc/frbc_operation_mode_element.py | 4 +++- src/s2python/frbc/frbc_storage_description.py | 4 +++- src/s2python/s2_parser.py | 3 +-- src/s2python/validate_values_mixin.py | 4 +--- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/s2python/common/number_range.py b/src/s2python/common/number_range.py index 65e9b81..c0583b3 100644 --- a/src/s2python/common/number_range.py +++ b/src/s2python/common/number_range.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from pydantic import model_validator diff --git a/src/s2python/common/power_range.py b/src/s2python/common/power_range.py index a8cd293..7f4700f 100644 --- a/src/s2python/common/power_range.py +++ b/src/s2python/common/power_range.py @@ -1,5 +1,3 @@ -from typing import Any, Dict - from pydantic import model_validator from s2python.generated.gen_s2 import PowerRange as GenPowerRange diff --git a/src/s2python/frbc/frbc_operation_mode.py b/src/s2python/frbc/frbc_operation_mode.py index 839b3d0..9399f61 100644 --- a/src/s2python/frbc/frbc_operation_mode.py +++ b/src/s2python/frbc/frbc_operation_mode.py @@ -1,6 +1,6 @@ # from itertools import pairwise import uuid -from typing import List, Dict, Any +from typing import List, Dict from pydantic import model_validator diff --git a/src/s2python/frbc/frbc_operation_mode_element.py b/src/s2python/frbc/frbc_operation_mode_element.py index 054bd73..d154d11 100644 --- a/src/s2python/frbc/frbc_operation_mode_element.py +++ b/src/s2python/frbc/frbc_operation_mode_element.py @@ -19,7 +19,9 @@ class FRBCOperationModeElement(GenFRBCOperationModeElement, S2Message["FRBCOpera "fill_level_range" ] # type: ignore[assignment] fill_rate: NumberRange = GenFRBCOperationModeElement.model_fields["fill_rate"] # type: ignore[assignment] - power_ranges: List[PowerRange] = GenFRBCOperationModeElement.model_fields["power_ranges"] # type: ignore[assignment] + power_ranges: List[PowerRange] = GenFRBCOperationModeElement.model_fields[ + "power_ranges" + ] # type: ignore[assignment] running_costs: Optional[NumberRange] = GenFRBCOperationModeElement.model_fields[ "running_costs" ] # type: ignore[assignment] diff --git a/src/s2python/frbc/frbc_storage_description.py b/src/s2python/frbc/frbc_storage_description.py index b279b70..eb141b8 100644 --- a/src/s2python/frbc/frbc_storage_description.py +++ b/src/s2python/frbc/frbc_storage_description.py @@ -13,4 +13,6 @@ class FRBCStorageDescription(GenFRBCStorageDescription, S2Message["FRBCStorageDe model_config = GenFRBCStorageDescription.model_config model_config["validate_assignment"] = True - fill_level_range: NumberRange = GenFRBCStorageDescription.model_fields["fill_level_range"] # type: ignore[assignment] + fill_level_range: NumberRange = GenFRBCStorageDescription.model_fields[ + "fill_level_range" + ] # type: ignore[assignment] diff --git a/src/s2python/s2_parser.py b/src/s2python/s2_parser.py index 44b1c34..a7b4d53 100644 --- a/src/s2python/s2_parser.py +++ b/src/s2python/s2_parser.py @@ -77,8 +77,7 @@ def parse_as_any_message(unparsed_message: Union[dict, str]) -> S2Message: if message_type not in TYPE_TO_MESSAGE_CLASS: raise S2ValidationError( - message_json, - f"Unable to parse {message_type} as an S2 message. Type unknown.", + None, message_json, f"Unable to parse {message_type} as an S2 message. Type unknown.", None ) return TYPE_TO_MESSAGE_CLASS[message_type].model_validate(message_json) diff --git a/src/s2python/validate_values_mixin.py b/src/s2python/validate_values_mixin.py index f3225ae..7cea034 100644 --- a/src/s2python/validate_values_mixin.py +++ b/src/s2python/validate_values_mixin.py @@ -1,4 +1,3 @@ -import inspect from typing import ( TypeVar, Generic, @@ -17,7 +16,6 @@ from pydantic import ( # pylint: disable=no-name-in-module BaseModel, ValidationError, - RootModel, ) from pydantic.deprecated.parse import Protocol as PydanticProtocol from pydantic.v1.error_wrappers import display_errors # pylint: disable=no-name-in-module @@ -140,7 +138,7 @@ def catch_and_convert_exceptions( ) -> Type[SupportsValidation[B_co]]: input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign] input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign] - input_class.model_validate_json = convert_to_s2exception(input_class.model_validate_json) # type: ignore[method-assign] + input_class.model_validate_json = convert_to_s2exception(input_class.model_validate_json) input_class.model_validate = convert_to_s2exception(input_class.model_validate) # type: ignore[method-assign] return input_class From 01e9676a1fff9f79e9b30e99e31106fa1432ca28 Mon Sep 17 00:00:00 2001 From: Sebastiaan la Fleur Date: Mon, 29 Jul 2024 17:05:10 +0200 Subject: [PATCH 3/3] 10: Fix all linting and typing issues. --- .pylintrc | 2 +- src/s2python/common/duration.py | 4 +- src/s2python/common/number_range.py | 10 +-- src/s2python/common/power_range.py | 11 +-- .../frbc/frbc_actuator_description.py | 81 +++++++----------- src/s2python/frbc/frbc_operation_mode.py | 12 ++- src/s2python/s2_parser.py | 8 +- src/s2python/s2_validation_error.py | 3 +- src/s2python/validate_values_mixin.py | 84 +++++++++++-------- tests/unit/common/duration_test.py | 2 +- tests/unit/common/transition_test.py | 2 +- 11 files changed, 105 insertions(+), 114 deletions(-) diff --git a/.pylintrc b/.pylintrc index 3760a09..b0bfeed 100644 --- a/.pylintrc +++ b/.pylintrc @@ -10,7 +10,7 @@ ignore-paths=src/s2python/generated/ # avoid hangs. jobs=1 -disable=missing-class-docstring,missing-module-docstring,too-few-public-methods,missing-function-docstring +disable=missing-class-docstring,missing-module-docstring,too-few-public-methods,missing-function-docstring,no-member [Format] max-line-length=120 diff --git a/src/s2python/common/duration.py b/src/s2python/common/duration.py index 08ead98..65663c0 100644 --- a/src/s2python/common/duration.py +++ b/src/s2python/common/duration.py @@ -15,8 +15,8 @@ def to_timedelta(self) -> timedelta: @staticmethod def from_timedelta(duration: timedelta) -> "Duration": - return Duration(math.ceil(duration.total_seconds() * 1000)) + return Duration(root=math.ceil(duration.total_seconds() * 1000)) @staticmethod def from_milliseconds(milliseconds: int) -> "Duration": - return Duration(milliseconds) + return Duration(root=milliseconds) diff --git a/src/s2python/common/number_range.py b/src/s2python/common/number_range.py index c0583b3..3cc7013 100644 --- a/src/s2python/common/number_range.py +++ b/src/s2python/common/number_range.py @@ -1,4 +1,5 @@ from typing import Any +from typing_extensions import Self from pydantic import model_validator @@ -15,12 +16,11 @@ class NumberRange(GenNumberRange, S2Message["NumberRange"]): model_config["validate_assignment"] = True @model_validator(mode="after") - @classmethod - def validate_start_end_order(cls, number_range: "NumberRange") -> "NumberRange": # pylint: disable=duplicate-code - if number_range.start_of_range > number_range.end_of_range: - raise ValueError(cls, "start_of_range should not be higher than end_of_range") + def validate_start_end_order(self) -> Self: # pylint: disable=duplicate-code + if self.start_of_range > self.end_of_range: + raise ValueError(self, "start_of_range should not be higher than end_of_range") - return number_range + return self def __hash__(self) -> int: return hash(f"{self.start_of_range}|{self.end_of_range}") diff --git a/src/s2python/common/power_range.py b/src/s2python/common/power_range.py index 7f4700f..4ca1ec8 100644 --- a/src/s2python/common/power_range.py +++ b/src/s2python/common/power_range.py @@ -1,3 +1,5 @@ +from typing_extensions import Self + from pydantic import model_validator from s2python.generated.gen_s2 import PowerRange as GenPowerRange @@ -13,9 +15,8 @@ class PowerRange(GenPowerRange, S2Message["PowerRange"]): model_config["validate_assignment"] = True @model_validator(mode="after") - @classmethod - def validate_start_end_order(cls, power_range: "PowerRange") -> "PowerRange": # pylint: disable=duplicate-code - if power_range.start_of_range > power_range.end_of_range: - raise ValueError(cls, "start_of_range should not be higher than end_of_range") + def validate_start_end_order(self) -> Self: + if self.start_of_range > self.end_of_range: + raise ValueError(self, "start_of_range should not be higher than end_of_range") - return power_range + return self diff --git a/src/s2python/frbc/frbc_actuator_description.py b/src/s2python/frbc/frbc_actuator_description.py index d832f8c..08afce6 100644 --- a/src/s2python/frbc/frbc_actuator_description.py +++ b/src/s2python/frbc/frbc_actuator_description.py @@ -1,6 +1,7 @@ import uuid from typing import List +from typing_extensions import Self from pydantic import model_validator @@ -32,17 +33,14 @@ class FRBCActuatorDescription(GenFRBCActuatorDescription, S2Message["FRBCActuato ] # type: ignore[assignment] @model_validator(mode="after") - @classmethod - def validate_timers_in_transitions( - cls, frbc_actuator_description: "FRBCActuatorDescription" - ) -> "FRBCActuatorDescription": - timers_by_id = {timer.id: timer for timer in frbc_actuator_description.timers} + def validate_timers_in_transitions(self) -> Self: + timers_by_id = {timer.id: timer for timer in self.timers} transition: Transition - for transition in frbc_actuator_description.transitions: + for transition in self.transitions: for start_timer_id in transition.start_timers: if start_timer_id not in timers_by_id: raise ValueError( - cls, + self, f"{start_timer_id} was referenced as start timer in transition " f"{transition.id} but was not defined in 'timers'.", ) @@ -50,78 +48,64 @@ def validate_timers_in_transitions( for blocking_timer_id in transition.blocking_timers: if blocking_timer_id not in timers_by_id: raise ValueError( - cls, + self, f"{blocking_timer_id} was referenced as blocking timer in transition " f"{transition.id} but was not defined in 'timers'.", ) - return frbc_actuator_description + return self @model_validator(mode="after") - @classmethod - def validate_timers_unique_ids( - cls, frbc_actuator_description: "FRBCActuatorDescription" - ) -> "FRBCActuatorDescription": + def validate_timers_unique_ids(self) -> Self: ids = [] timer: Timer - for timer in frbc_actuator_description.timers: + for timer in self.timers: if timer.id in ids: - raise ValueError(cls, f"Id {timer.id} was found multiple times in 'timers'.") + raise ValueError(self, f"Id {timer.id} was found multiple times in 'timers'.") ids.append(timer.id) - return frbc_actuator_description + return self @model_validator(mode="after") - @classmethod - def validate_operation_modes_in_transitions( - cls, frbc_actuator_description: "FRBCActuatorDescription" - ) -> "FRBCActuatorDescription": - operation_mode_by_id = { - operation_mode.id: operation_mode for operation_mode in frbc_actuator_description.operation_modes - } + def validate_operation_modes_in_transitions(self) -> Self: + operation_mode_by_id = {operation_mode.id: operation_mode for operation_mode in self.operation_modes} transition: Transition - for transition in frbc_actuator_description.transitions: + for transition in self.transitions: if transition.from_ not in operation_mode_by_id: raise ValueError( - cls, + self, f"Operation mode {transition.from_} was referenced as 'from' in transition " f"{transition.id} but was not defined in 'operation_modes'.", ) if transition.to not in operation_mode_by_id: raise ValueError( - cls, + self, f"Operation mode {transition.to} was referenced as 'to' in transition " f"{transition.id} but was not defined in 'operation_modes'.", ) - return frbc_actuator_description + return self @model_validator(mode="after") - @classmethod - def validate_operation_modes_unique_ids( - cls, frbc_actuator_description: "FRBCActuatorDescription" - ) -> "FRBCActuatorDescription": + def validate_operation_modes_unique_ids(self) -> Self: ids = [] operation_mode: FRBCOperationMode - for operation_mode in frbc_actuator_description.operation_modes: + for operation_mode in self.operation_modes: if operation_mode.id in ids: raise ValueError( - cls, + self, f"Id {operation_mode.id} was found multiple times in 'operation_modes'.", ) ids.append(operation_mode.id) - return frbc_actuator_description + return self @model_validator(mode="after") - @classmethod - def validate_operation_mode_elements_have_all_supported_commodities( - cls, frbc_actuator_description: "FRBCActuatorDescription" - ) -> "FRBCActuatorDescription": - supported_commodities = frbc_actuator_description.supported_commodities + def validate_operation_mode_elements_have_all_supported_commodities(self) -> Self: + supported_commodities = self.supported_commodities operation_mode: FRBCOperationMode - for operation_mode in frbc_actuator_description.operation_modes: + for operation_mode in self.operation_modes: for operation_mode_element in operation_mode.elements: for commodity in supported_commodities: power_ranges_for_commodity = [ @@ -132,31 +116,28 @@ def validate_operation_mode_elements_have_all_supported_commodities( if len(power_ranges_for_commodity) > 1: raise ValueError( - cls, + self, f"Multiple power ranges defined for commodity {commodity} in operation " f"mode {operation_mode.id} and element with fill_level_range " f"{operation_mode_element.fill_level_range}", ) if not power_ranges_for_commodity: raise ValueError( - cls, + self, f"No power ranges defined for commodity {commodity} in operation " f"mode {operation_mode.id} and element with fill_level_range " f"{operation_mode_element.fill_level_range}", ) - return frbc_actuator_description + return self @model_validator(mode="after") - @classmethod - def validate_unique_supported_commodities( - cls, frbc_actuator_description: "FRBCActuatorDescription" - ) -> "FRBCActuatorDescription": - supported_commodities: List[Commodity] = frbc_actuator_description.supported_commodities + def validate_unique_supported_commodities(self) -> Self: + supported_commodities: List[Commodity] = self.supported_commodities for supported_commodity in supported_commodities: if supported_commodities.count(supported_commodity) > 1: raise ValueError( - cls, + self, f"Found duplicate {supported_commodity} commodity in 'supported_commodities'", ) - return frbc_actuator_description + return self diff --git a/src/s2python/frbc/frbc_operation_mode.py b/src/s2python/frbc/frbc_operation_mode.py index 9399f61..c6758ad 100644 --- a/src/s2python/frbc/frbc_operation_mode.py +++ b/src/s2python/frbc/frbc_operation_mode.py @@ -1,6 +1,7 @@ # from itertools import pairwise import uuid from typing import List, Dict +from typing_extensions import Self from pydantic import model_validator @@ -23,12 +24,9 @@ class FRBCOperationMode(GenFRBCOperationMode, S2Message["FRBCOperationMode"]): elements: List[FRBCOperationModeElement] = GenFRBCOperationMode.model_fields["elements"] # type: ignore[assignment] @model_validator(mode="after") - @classmethod - def validate_contiguous_fill_levels_operation_mode_elements( - cls, frbc_operation_mode: "FRBCOperationMode" - ) -> "FRBCOperationMode": + def validate_contiguous_fill_levels_operation_mode_elements(self) -> Self: elements_by_fill_level_range: Dict[NumberRange, FRBCOperationModeElement] - elements_by_fill_level_range = {element.fill_level_range: element for element in frbc_operation_mode.elements} + elements_by_fill_level_range = {element.fill_level_range: element for element in self.elements} sorted_fill_level_ranges: List[NumberRange] sorted_fill_level_ranges = list(elements_by_fill_level_range.keys()) @@ -37,8 +35,8 @@ def validate_contiguous_fill_levels_operation_mode_elements( for current_fill_level_range, next_fill_level_range in pairwise(sorted_fill_level_ranges): if current_fill_level_range.end_of_range != next_fill_level_range.start_of_range: raise ValueError( - cls, + self, f"Elements with fill level ranges {current_fill_level_range} and " f"{next_fill_level_range} are closest match to each other but not contiguous.", ) - return frbc_operation_mode + return self diff --git a/src/s2python/s2_parser.py b/src/s2python/s2_parser.py index a7b4d53..99433b6 100644 --- a/src/s2python/s2_parser.py +++ b/src/s2python/s2_parser.py @@ -24,18 +24,18 @@ FRBCTimerStatus, FRBCUsageForecast, ) -from s2python.validate_values_mixin import S2Message +from s2python.validate_values_mixin import SupportsValidation from s2python.s2_validation_error import S2ValidationError LOGGER = logging.getLogger(__name__) S2MessageType = str -M = TypeVar("M", bound=S2Message) +M = TypeVar("M", bound=SupportsValidation) # May be generated with development_utilities/generate_s2_message_type_to_class.py -TYPE_TO_MESSAGE_CLASS: Dict[str, Type[S2Message]] = { +TYPE_TO_MESSAGE_CLASS: Dict[str, Type[SupportsValidation]] = { "FRBC.ActuatorStatus": FRBCActuatorStatus, "FRBC.FillLevelTargetProfile": FRBCFillLevelTargetProfile, "FRBC.Instruction": FRBCInstruction, @@ -65,7 +65,7 @@ def _parse_json_if_required(unparsed_message: Union[dict, str]) -> dict: return unparsed_message @staticmethod - def parse_as_any_message(unparsed_message: Union[dict, str]) -> S2Message: + def parse_as_any_message(unparsed_message: Union[dict, str]) -> SupportsValidation: """Parse the message as any S2 python message regardless of message type. :param unparsed_message: The message as a JSON-formatted string or as a json-parsed dictionary. diff --git a/src/s2python/s2_validation_error.py b/src/s2python/s2_validation_error.py index 60d255d..8ab7664 100644 --- a/src/s2python/s2_validation_error.py +++ b/src/s2python/s2_validation_error.py @@ -2,6 +2,7 @@ from typing import Union, Type, Optional from pydantic import ValidationError +from pydantic.v1.error_wrappers import ValidationError as ValidationErrorV1 @dataclass @@ -9,4 +10,4 @@ class S2ValidationError(Exception): class_: Optional[Type] obj: object msg: str - pydantic_validation_error: Union[ValidationError, TypeError, None] + pydantic_validation_error: Union[ValidationErrorV1, ValidationError, TypeError, None] diff --git a/src/s2python/validate_values_mixin.py b/src/s2python/validate_values_mixin.py index 7cea034..c59cc7f 100644 --- a/src/s2python/validate_values_mixin.py +++ b/src/s2python/validate_values_mixin.py @@ -11,13 +11,15 @@ Mapping, List, Dict, + Literal, ) +from typing_extensions import Self from pydantic import ( # pylint: disable=no-name-in-module BaseModel, ValidationError, ) -from pydantic.deprecated.parse import Protocol as PydanticProtocol +from pydantic.main import IncEx from pydantic.v1.error_wrappers import display_errors # pylint: disable=no-name-in-module from s2python.s2_validation_error import S2ValidationError @@ -42,60 +44,70 @@ def from_json(cls, json_str: str) -> B_co: ... def from_dict(cls, json_dict: Dict) -> B_co: ... # Pydantic methods - def json( # pylint: disable=too-many-arguments + @classmethod + def model_validate_json( + cls, + json_data: Union[str, bytes, bytearray], + *, + strict: Optional[bool] = None, + context: Optional[Any] = None, + ) -> Self: ... + + @classmethod + def model_validate( + cls, + obj: Any, + *, + strict: Optional[bool] = None, + from_attributes: Optional[bool] = None, + context: Optional[Any] = None, + ) -> Self: ... + + def model_dump( # pylint: disable=too-many-arguments self, *, - include: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None, - exclude: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None, + mode: Union[Literal["json", "python"], str] = "python", + include: IncEx = None, + exclude: IncEx = None, + context: Optional[Any] = None, by_alias: bool = False, - skip_defaults: Optional[bool] = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - encoder: Optional[Callable[[Any], Any]] = None, - models_as_dict: bool = True, - **dumps_kwargs: Any, - ) -> str: ... + round_trip: bool = False, + warnings: Union[bool, Literal["none", "warn", "error"]] = True, + serialize_as_any: bool = False, + ) -> Dict[str, Any]: ... - def dict( # pylint: disable=too-many-arguments + def model_dump_json( # pylint: disable=too-many-arguments self, *, - include: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None, - exclude: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None, + indent: Optional[int] = None, + include: IncEx = None, + exclude: IncEx = None, + context: Optional[Any] = None, by_alias: bool = False, - skip_defaults: Optional[bool] = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - ) -> Dict[str, Any]: ... - - @classmethod - def model_validate_json( # pylint: disable=too-many-arguments - cls, - b: Union[str, bytes], - *, - content_type: Optional[str] = ..., - encoding: str = ..., - proto: PydanticProtocol = ..., - allow_pickle: bool = ..., - ) -> B_co: ... - - @classmethod - def model_validate(cls, obj: Any) -> "B_co": ... + round_trip: bool = False, + warnings: Union[bool, Literal["none", "warn", "error"]] = True, + serialize_as_any: bool = False, + ) -> str: ... C = TypeVar("C", bound="SupportsValidation") -class ValidateValuesMixin(Generic[C]): +class S2Message(Generic[C]): def to_json(self: C) -> str: try: return self.model_dump_json(by_alias=True, exclude_none=True) except (ValidationError, TypeError) as e: raise S2ValidationError(type(self), self, "Pydantic raised a format validation error.", e) from e - def to_dict(self: C) -> dict: - return self.dict() + def to_dict(self: C) -> Dict: + return self.model_dump() @classmethod def from_json(cls: Type[C], json_str: str) -> C: @@ -108,10 +120,6 @@ def from_dict(cls: Type[C], json_dict: dict) -> C: return gen_model -class S2Message(Generic[C], ValidateValuesMixin[C]): - pass - - def convert_to_s2exception(f: Callable) -> Callable: def inner(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: try: @@ -123,7 +131,7 @@ def inner(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: else: class_type = None - raise S2ValidationError(class_type, args, display_errors(e.errors()), e) from e + raise S2ValidationError(class_type, args, display_errors(e.errors()), e) from e # type: ignore[arg-type] except TypeError as e: raise S2ValidationError(None, args, str(e), e) from e @@ -138,7 +146,9 @@ def catch_and_convert_exceptions( ) -> Type[SupportsValidation[B_co]]: input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign] input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign] - input_class.model_validate_json = convert_to_s2exception(input_class.model_validate_json) + input_class.model_validate_json = convert_to_s2exception( # type: ignore[method-assign] + input_class.model_validate_json + ) input_class.model_validate = convert_to_s2exception(input_class.model_validate) # type: ignore[method-assign] return input_class diff --git a/tests/unit/common/duration_test.py b/tests/unit/common/duration_test.py index 87b6f36..a256bf7 100644 --- a/tests/unit/common/duration_test.py +++ b/tests/unit/common/duration_test.py @@ -17,7 +17,7 @@ def test__from_timedelta__happy_path(self): def test__to_timedelta__happy_path(self): # Arrange - duration = Duration(20_000) + duration = Duration(root=20_000) # Act duration_timedelta = duration.to_timedelta() diff --git a/tests/unit/common/transition_test.py b/tests/unit/common/transition_test.py index 765239e..0c9dc9e 100644 --- a/tests/unit/common/transition_test.py +++ b/tests/unit/common/transition_test.py @@ -135,6 +135,6 @@ def test__to_json__value_validation_error_neg_duration(self): to=uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1"), start_timers=[], blocking_timers=[], - transition_duration=Duration(-5000), + transition_duration=Duration(root=-5000), abnormal_condition_only=False, )