Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: remove pydantic validation from dto classes #740

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

frodehk
Copy link
Contributor

@frodehk frodehk commented Dec 17, 2024

equinor/ecalc-internal#291

Why is this pull request needed?

Pydantic validation is taken care of in the Yaml classes now. It is not needed in the dto classes, here we need more flexibility.

What does this pull request change?

Remove inheritance from EcalcBaseModel (Pydantic) in Component base class. Remove: field_validators, Field definitions, ConfigDict import and usage. Remove Pydantic specific imports and replace with standard Python types if necessary. In libecalc/domain/infracstructure/energy_components/ this is done for:

  • base/component_dto.py
  • consumer_system/consumer_system_dto.py
  • electricity_consumer/electricity_consumer.py
  • generator_set_dto.py
  • installation/installation.py
  • asset/asset.py
  • legacy_consumer: All files in directory
  • Remove some redundant validation from dto layer (in GeneratorSet and Installation)
  • Update affected tests: Use yaml classes instead of dtos, as validation is done here only (after removing redundant validation in dto)

@frodehk frodehk self-assigned this Dec 17, 2024
@frodehk frodehk requested a review from a team as a code owner December 17, 2024 12:13
Copy link
Contributor

@jsolaas jsolaas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. Looks like a step in the right direction to me :)

The last comment about ValueError is something we should look into I think.

@@ -288,6 +292,10 @@ def evaluate_rate_ps_pd_density(
:param discharge_pressures:
:param fluid_density:
"""
# Ensure rate is a NumPy array
rate = np.array(rate, dtype=np.float64)
fluid_density = np.array(fluid_density, dtype=np.float64)
Copy link
Contributor

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 or asfarray? 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.



class Asset(Component, EnergyComponent):
def __init__(
Copy link
Contributor

Choose a reason for hiding this comment

The 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 component_type directly when removing it. EnergyComponent has the same information as Component.

Comment on lines +64 to +67
self.generator_set_model = generator_set_model
self.consumers = consumers
self.cable_loss = cable_loss
self.max_usage_from_shore = max_usage_from_shore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the generator specific stuff to generator set?

@@ -46,61 +43,96 @@ def check_regularity(cls, regularity):


class BaseEquipment(BaseComponent, ABC):
user_defined_category: dict[Period, ConsumerUserDefinedCategoryType] = Field(..., validate_default=True)
def __init__(
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

return user_defined_category


class BaseConsumer(BaseEquipment, ABC):
"""Base class for all consumers."""

consumes: ConsumptionType
fuel: Optional[dict[Period, FuelType]] = None
def __init__(
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

@@ -226,6 +228,7 @@ def from_yaml_to_dto(
user_defined_category=user_defined_category,
cable_loss=cable_loss,
max_usage_from_shore=max_usage_from_shore,
component_type=ComponentType.GENERATOR_SET, # TODO: Check if this is correct. Why isn´t component_type set for YamlGeneratorSet?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean why data.component_type can't be used instead? We don't have types here in the yaml, it's based on context, i.e. the key under which this data is put makes it a generator set.

Instead of specifying component_type here, we could just hardcode it in the constructor though. The reason we don't do that already was pydantic related I think. component_type and class type means the same in this case.

@@ -114,7 +135,7 @@ def to_dto(
regularity=regularity,
consumes=consumes,
component_conditions=component_conditions,
stream_conditions_priorities=TypeAdapter(YamlPriorities).dump_python(
stream_conditions_priorities=self.convert_yaml_priorities(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably map this similar to v1/the other components, in the mapper (removing to_dto). But we'll see what we end up doing with v2, seems unnecessary to maintain if we have no plan to make users migrate.

@@ -13,7 +13,7 @@

class TestElectricityConsumer:
def test_invalid_energy_usage(self):
with pytest.raises(ValidationError) as e:
with pytest.raises(ValueError) as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is probably something we need to handle. Previously pydantic would collect all value errors and create a validation error with everything (making the webapp display all errors). Now we probably don't even handle the single ValueError correctly? Or I might have missed a change in this PR, but we should still decide what to do with validation errors. Collecting several ValueErrors is probably a good idea?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants