-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: remove pydantic validation from dto classes #740
base: main
Are you sure you want to change the base?
Changes from all commits
5fce454
d2631ed
21df895
5371ecd
8937692
92f87cb
56c0263
1c3d201
5058974
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,28 @@ | ||
from typing import Literal | ||
|
||
from pydantic import Field | ||
|
||
from libecalc.application.energy.energy_component import EnergyComponent | ||
from libecalc.common.component_type import ComponentType | ||
from libecalc.common.string.string_utils import generate_id | ||
from libecalc.domain.infrastructure.energy_components.base.component_dto import Component | ||
from libecalc.domain.infrastructure.energy_components.installation.installation import Installation | ||
from libecalc.dto.component_graph import ComponentGraph | ||
from libecalc.dto.utils.validators import ComponentNameStr | ||
|
||
|
||
class Asset(Component, EnergyComponent): | ||
def __init__( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Component is no longer necessary, but we would have to make sure not to use |
||
self, | ||
name: str, | ||
installations: list[Installation], | ||
component_type: Literal[ComponentType.ASSET] = ComponentType.ASSET, | ||
): | ||
self.name = name | ||
self.installations = installations | ||
self.component_type = component_type | ||
|
||
@property | ||
def id(self): | ||
return generate_id(self.name) | ||
|
||
name: ComponentNameStr | ||
|
||
installations: list[Installation] = Field(default_factory=list) | ||
component_type: Literal[ComponentType.ASSET] = ComponentType.ASSET | ||
|
||
def is_fuel_consumer(self) -> bool: | ||
return True | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,31 @@ | ||
from abc import ABC, abstractmethod | ||
from typing import Optional | ||
from __future__ import annotations | ||
|
||
from pydantic import ConfigDict, Field, field_validator | ||
from pydantic_core.core_schema import ValidationInfo | ||
from abc import ABC, abstractmethod | ||
from typing import Optional, Union | ||
|
||
from libecalc.application.energy.emitter import Emitter | ||
from libecalc.application.energy.energy_component import EnergyComponent | ||
from libecalc.common.component_type import ComponentType | ||
from libecalc.common.consumption_type import ConsumptionType | ||
from libecalc.common.string.string_utils import generate_id | ||
from libecalc.common.time_utils import Period | ||
from libecalc.common.units import Unit | ||
from libecalc.common.utils.rates import RateType | ||
from libecalc.domain.infrastructure.energy_components.component_validation_error import ( | ||
ComponentValidationException, | ||
ModelValidationError, | ||
) | ||
from libecalc.domain.infrastructure.energy_components.utils import _convert_keys_in_dictionary_from_str_to_periods | ||
from libecalc.dto.base import EcalcBaseModel | ||
from libecalc.dto.fuel_type import FuelType | ||
from libecalc.dto.types import ConsumerUserDefinedCategoryType | ||
from libecalc.dto.utils.validators import ( | ||
ComponentNameStr, | ||
ExpressionType, | ||
validate_temporal_model, | ||
) | ||
from libecalc.expression import Expression | ||
|
||
|
||
class Component(EcalcBaseModel, ABC): | ||
class Component(ABC): | ||
component_type: ComponentType | ||
|
||
@property | ||
|
@@ -31,13 +34,11 @@ def id(self) -> str: ... | |
|
||
|
||
class BaseComponent(Component, ABC): | ||
name: ComponentNameStr | ||
def __init__(self, name: str, regularity: dict[Period, Expression]): | ||
self.name = name | ||
self.regularity = self.check_regularity(regularity) | ||
validate_temporal_model(self.regularity) | ||
|
||
regularity: dict[Period, Expression] | ||
|
||
_validate_base_temporal_model = field_validator("regularity")(validate_temporal_model) | ||
|
||
@field_validator("regularity", mode="before") | ||
@classmethod | ||
def check_regularity(cls, regularity): | ||
if isinstance(regularity, dict) and len(regularity.values()) > 0: | ||
|
@@ -46,61 +47,116 @@ def check_regularity(cls, regularity): | |
|
||
|
||
class BaseEquipment(BaseComponent, ABC): | ||
user_defined_category: dict[Period, ConsumerUserDefinedCategoryType] = Field(..., validate_default=True) | ||
def __init__( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should look into removing BaseEquipment and BaseComponent. Instead we could create objects that contains several of these values if we think it makes sense to group them. Or just duplicate the init in each energy component. |
||
self, | ||
name: str, | ||
regularity: dict[Period, Expression], | ||
user_defined_category: dict[Period, ConsumerUserDefinedCategoryType], | ||
component_type: ComponentType, | ||
energy_usage_model: Optional[dict[Period, Expression]] = None, | ||
fuel: Optional[dict[Period, FuelType]] = None, | ||
generator_set_model: Optional[dict[Period, Union[BaseEquipment, Emitter, EnergyComponent]]] = None, | ||
consumers: Optional[list[BaseEquipment]] = None, | ||
cable_loss: Optional[Expression] = None, | ||
max_usage_from_shore: Optional[Expression] = None, | ||
): | ||
super().__init__(name, regularity) | ||
self.user_defined_category = self.check_user_defined_category(user_defined_category, name) | ||
self.energy_usage_model = energy_usage_model | ||
self.component_type = component_type | ||
self.fuel = fuel | ||
self.generator_set_model = generator_set_model | ||
self.consumers = consumers | ||
self.cable_loss = cable_loss | ||
self.max_usage_from_shore = max_usage_from_shore | ||
Comment on lines
+68
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move the generator specific stuff to generator set? |
||
|
||
@property | ||
def id(self) -> str: | ||
return generate_id(self.name) | ||
|
||
@field_validator("user_defined_category", mode="before") | ||
def check_user_defined_category(cls, user_defined_category, info: ValidationInfo): | ||
@classmethod | ||
def check_user_defined_category( | ||
cls, user_defined_category, name: str | ||
): # TODO: Check if this is needed. Should be handled in yaml validation | ||
"""Provide which value and context to make it easier for user to correct wrt mandatory changes.""" | ||
|
||
if isinstance(user_defined_category, dict) and len(user_defined_category.values()) > 0: | ||
user_defined_category = _convert_keys_in_dictionary_from_str_to_periods(user_defined_category) | ||
for user_category in user_defined_category.values(): | ||
if user_category not in list(ConsumerUserDefinedCategoryType): | ||
name_context_str = "" | ||
if (name := info.data.get("name")) is not None: | ||
name_context_str = f"with the name {name}" | ||
|
||
raise ValueError( | ||
f"CATEGORY: {user_category} is not allowed for {cls.__name__} {name_context_str}. Valid categories are: {[(consumer_user_defined_category.value) for consumer_user_defined_category in ConsumerUserDefinedCategoryType]}" | ||
msg = ( | ||
f"CATEGORY: {user_category} is not allowed for {cls.__name__}. Valid categories are: " | ||
f"{[(consumer_user_defined_category.value) for consumer_user_defined_category in ConsumerUserDefinedCategoryType]}" | ||
) | ||
|
||
raise ComponentValidationException( | ||
errors=[ | ||
ModelValidationError( | ||
name=name, | ||
message=str(msg), | ||
) | ||
] | ||
) | ||
return user_defined_category | ||
|
||
|
||
class BaseConsumer(BaseEquipment, ABC): | ||
"""Base class for all consumers.""" | ||
|
||
consumes: ConsumptionType | ||
fuel: Optional[dict[Period, FuelType]] = None | ||
def __init__( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also remove BaseConsumer I think. We don't want to use inheritance without being sure the abstraction makes sense, here it's just used to avoid having to specify the same params. |
||
self, | ||
name: str, | ||
regularity: dict[Period, Expression], | ||
consumes: ConsumptionType, | ||
user_defined_category: dict[Period, ConsumerUserDefinedCategoryType], | ||
component_type: ComponentType, | ||
energy_usage_model: Optional[dict[Period, Expression]] = None, | ||
fuel: Optional[dict[Period, FuelType]] = None, | ||
): | ||
super().__init__(name, regularity, user_defined_category, component_type, energy_usage_model, fuel) | ||
|
||
self.fuel = self.validate_fuel_exist(name=self.name, fuel=fuel, consumes=consumes) | ||
self.consumes = consumes | ||
|
||
@field_validator("fuel", mode="before") | ||
@classmethod | ||
def validate_fuel_exist(cls, fuel, info: ValidationInfo): | ||
def validate_fuel_exist(cls, name: str, fuel: Optional[dict[Period, FuelType]], consumes: ConsumptionType): | ||
""" | ||
Make sure fuel is set if consumption type is FUEL. | ||
""" | ||
if isinstance(fuel, dict) and len(fuel.values()) > 0: | ||
fuel = _convert_keys_in_dictionary_from_str_to_periods(fuel) | ||
if info.data.get("consumes") == ConsumptionType.FUEL and (fuel is None or len(fuel) < 1): | ||
msg = f"Missing fuel for fuel consumer '{info.data.get('name')}'" | ||
raise ValueError(msg) | ||
if consumes == ConsumptionType.FUEL and (fuel is None or len(fuel) < 1): | ||
msg = "Missing fuel for fuel consumer" | ||
raise ComponentValidationException( | ||
errors=[ | ||
ModelValidationError( | ||
name=name, | ||
message=str(msg), | ||
) | ||
], | ||
) | ||
return fuel | ||
|
||
|
||
class ExpressionTimeSeries(EcalcBaseModel): | ||
value: ExpressionType | ||
unit: Unit | ||
type: Optional[RateType] = None | ||
class ExpressionTimeSeries: | ||
def __init__(self, value: ExpressionType, unit: Unit, type: Optional[RateType] = None): | ||
self.value = value | ||
self.unit = unit | ||
self.type = type | ||
|
||
|
||
class ExpressionStreamConditions(EcalcBaseModel): | ||
rate: Optional[ExpressionTimeSeries] = None | ||
pressure: Optional[ExpressionTimeSeries] = None | ||
temperature: Optional[ExpressionTimeSeries] = None | ||
fluid_density: Optional[ExpressionTimeSeries] = None | ||
class ExpressionStreamConditions: | ||
def __init__( | ||
self, | ||
rate: Optional[ExpressionTimeSeries] = None, | ||
pressure: Optional[ExpressionTimeSeries] = None, | ||
temperature: Optional[ExpressionTimeSeries] = None, | ||
fluid_density: Optional[ExpressionTimeSeries] = None, | ||
): | ||
self.rate = rate | ||
self.pressure = pressure | ||
self.temperature = temperature | ||
self.fluid_density = fluid_density | ||
|
||
|
||
ConsumerID = str | ||
|
@@ -110,13 +166,13 @@ class ExpressionStreamConditions(EcalcBaseModel): | |
SystemStreamConditions = dict[ConsumerID, dict[StreamID, ExpressionStreamConditions]] | ||
|
||
|
||
class Crossover(EcalcBaseModel): | ||
model_config = ConfigDict(populate_by_name=True) | ||
|
||
stream_name: Optional[str] = Field(None) | ||
from_component_id: str | ||
to_component_id: str | ||
class Crossover: | ||
def __init__(self, from_component_id: str, to_component_id: str, stream_name: Optional[str] = None): | ||
self.stream_name = stream_name | ||
self.from_component_id = from_component_id | ||
self.to_component_id = to_component_id | ||
|
||
|
||
class SystemComponentConditions(EcalcBaseModel): | ||
crossover: list[Crossover] | ||
class SystemComponentConditions: | ||
def __init__(self, crossover: list[Crossover]): | ||
self.crossover = crossover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should consider using
asarray
orasfarray
? asarray won't copy when the input already is an array. asfarray will create a floating point ndarray, I'm not sure what that actually is array with dtype=float or something else.