From a19c0bdba462df08a5e6e4deb53795527b6d91cc Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:55:59 +0100 Subject: [PATCH 1/3] chore: check setup factory for test data --- .../presentation/yaml/yaml_types/models/models_test_setup.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py diff --git a/src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py b/src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py new file mode 100644 index 0000000000..e69de29bb2 From 3658e06e2f906deb7c9ac1aedae86422ee81f35b Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:04:49 +0100 Subject: [PATCH 2/3] chore: check setup factory for test data --- .../yaml/yaml_types/models/conftest.py | 220 +++++++++++++++++- .../yaml_types/models/models_test_setup.py | 159 +++++++++++++ 2 files changed, 378 insertions(+), 1 deletion(-) diff --git a/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py b/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py index 3fee09d22c..7ceac11b42 100644 --- a/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py +++ b/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py @@ -1,10 +1,61 @@ -from typing import Dict, Optional, cast +from io import StringIO +from pathlib import Path +from typing import Dict, Generic, List, Optional, TypeVar, Union, cast +import pytest +from models_test_setup import ( + ChartSingleSpeed, + ChartVariableSpeed, + CompressorTrainBase, + Curve, + FluidModel, + Models, + SimplifiedVariableSpeedTrainKnownStagesModel, + SimplifiedVariableSpeedTrainUnknownStagesModel, + SingleSpeedTrainModel, + Stage, + Stages, + VariableSpeedTrainModel, +) +from polyfactory import Require +from polyfactory.decorators import post_generated +from polyfactory.factories.pydantic_factory import ModelFactory + +from ecalc_cli.infrastructure.file_resource_service import FileResourceService +from libecalc.common.time_utils import Frequency +from libecalc.dto import GenericChartFromDesignPoint, GenericChartFromInput from libecalc.presentation.yaml.configuration_service import ConfigurationService +from libecalc.presentation.yaml.model import YamlModel +from libecalc.presentation.yaml.validation_errors import ValidationError from libecalc.presentation.yaml.yaml_entities import ResourceStream +from libecalc.presentation.yaml.yaml_keywords import EcalcYamlKeywords +from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel from libecalc.presentation.yaml.yaml_models.yaml_model import ReaderType, YamlConfiguration, YamlValidator +def remove_null_none_empty(ob): + cleaned = {} + for k, v in ob.items(): + if isinstance(v, dict): + x = remove_null_none_empty(v) + if len(x.keys()) > 0: + cleaned[k] = x + + elif isinstance(v, list): + p = [] + for c in v: + if isinstance(c, dict): + x = remove_null_none_empty(c) + if len(x.keys()) > 0: + p.append(x) + elif c is not None and c != "": + p.append(c) + cleaned[k] = p + elif v is not None and v != "": + cleaned[k] = v + return cleaned + + class OverridableStreamConfigurationService(ConfigurationService): def __init__(self, stream: ResourceStream, overrides: Optional[Dict] = None): self._overrides = overrides @@ -21,6 +72,173 @@ def get_configuration(self) -> YamlValidator: return cast(YamlValidator, main_yaml_model) +class FluidModelFactory(ModelFactory[FluidModel]): + __use_defaults__ = True + + # Required parameters when creating instance of fluid model: + fluid_model_type = Require() # PREDEFINED or COMPOSITION + eos_model = Require() # SRK, PR, GERG_SRK or GERG_PR + + +TChart = TypeVar( + "TChart", bound=Union[ChartSingleSpeed, ChartVariableSpeed, GenericChartFromInput, GenericChartFromDesignPoint] +) + + +class CustomChartFactory(Generic[TChart], ModelFactory[TChart]): + __is_base_factory__ = True + __use_defaults__ = True + + +class SingleSpeedChartFactory(CustomChartFactory[ChartSingleSpeed]): ... + + +class VariableSpeedChartFactory(CustomChartFactory[ChartVariableSpeed]): ... + + +class GenericInputChartFactory(CustomChartFactory[GenericChartFromInput]): ... + + +class GenericInputDesignPointFactory(CustomChartFactory[GenericChartFromDesignPoint]): ... + + +TCompTrain = TypeVar( + "TCompTrain", + bound=Union[ + SingleSpeedTrainModel, + VariableSpeedTrainModel, + SimplifiedVariableSpeedTrainUnknownStagesModel, + SimplifiedVariableSpeedTrainKnownStagesModel, + ], +) + + +class CustomTrainFactory(Generic[TCompTrain], ModelFactory[TCompTrain]): + __is_base_factory__ = True + __use_defaults__ = True + + # pressure_control = Require() + fluid_model = Require() + compressor_train = Require() + + +class SingleSpeedTrainFactory(CustomTrainFactory[SingleSpeedTrainModel]): ... + + +class VariableSpeedTrainFactory(CustomTrainFactory[VariableSpeedTrainModel]): ... + + +class SimplifiedVariableSpeedTrainKnownStagesFactory( + CustomTrainFactory[SimplifiedVariableSpeedTrainUnknownStagesModel] +): ... + + +class SimplifiedVariableSpeedTrainUnknownStagesFactory( + CustomTrainFactory[SimplifiedVariableSpeedTrainUnknownStagesModel] +): ... + + +class StageFactory(ModelFactory[Stage]): + __use_defaults__ = True + compressor_chart = Require() + + +class CurveFactory(ModelFactory[Curve]): + nr_samples = 4 + __use_defaults__ = True + __randomize_collection_length__ = True + __min_collection_length__ = nr_samples + __max_collection_length__ = nr_samples + + @post_generated + @classmethod + def speed(cls, _min_speed: int, _max_speed: int) -> int: + speed = cls.__random__.randint(a=_min_speed, b=_max_speed) + return speed + + @post_generated + @classmethod + def rate(cls, _min_rate: int, _max_rate: int) -> List[int]: + rate = [] + for _i in range(cls.nr_samples): + rate_sample = cls.__random__.randint(a=_min_rate, b=_max_rate) + rate.append(rate_sample) + return sorted(rate) + + @post_generated + @classmethod + def head(cls, _min_head: int, _max_head: int) -> List[int]: + head = [] + for _i in range(cls.nr_samples): + head_sample = cls.__random__.randint(a=_min_head, b=_max_head) + head.append(head_sample) + return sorted(head, reverse=True) + + @post_generated + @classmethod + def efficiency(cls, _min_efficiency: float, _max_efficiency: float) -> List[float]: + efficiency = [] + for _i in range(cls.nr_samples): + efficiency_sample = cls.__random__.uniform(a=_min_efficiency, b=_max_efficiency) + efficiency.append(efficiency_sample) + return efficiency + + +def test_single_speed_compressor_train() -> None: + curve_instance = CurveFactory.build( + _min_speed=3000, + _max_speed=6500, + _min_rate=3500, + _max_rate=7500, + _min_head=5000, + _max_head=9500, + _min_efficiency=0.7, + _max_efficiency=0.8, + ) + chart_instance = SingleSpeedChartFactory.build(name="single_speed_chart", curve=curve_instance) + stage_instance = StageFactory.build( + compressor_chart=chart_instance.name, control_margin=0, control_margin_unit="FRACTION" + ) + fluid_model_instance = FluidModelFactory.build( + name="fluid_model", fluid_model_type="PREDEFINED", gas_type="DRY", eos_model="SRK" + ) + compressor_instance = SingleSpeedTrainFactory.build( + name="single_speed_compressor_train", + compressor_chart=chart_instance.name, + compressor_train=Stages(stages=[stage_instance]), + fluid_model=fluid_model_instance.name, + ) + models = Models( + models=[ + fluid_model_instance, + chart_instance, + compressor_instance, + ] + ) + yaml_dict = models.model_dump(by_alias=True) + cleaned_data = remove_null_none_empty(yaml_dict) + yaml_string = PyYamlYamlModel.dump_yaml(yaml_dict=cleaned_data) + + configuration_service = OverridableStreamConfigurationService( + stream=ResourceStream(name="", stream=StringIO(yaml_string)) + ) + resource_service = FileResourceService(working_directory=Path("")) + + model = YamlModel( + configuration_service=configuration_service, + resource_service=resource_service, + output_frequency=Frequency.YEAR, + ) + + with pytest.raises(ValidationError) as exc_info: + model.validate_for_run() + + assert f"{EcalcYamlKeywords.fuel_types}\n" f"\tMessage: This keyword is missing, it is required" in str( + exc_info.value + ) + assert isinstance(compressor_instance, CompressorTrainBase) + + yaml_fluid_model = """ - NAME: fluid_model TYPE: FLUID diff --git a/src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py b/src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py index e69de29bb2..5591998a87 100644 --- a/src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py +++ b/src/tests/libecalc/presentation/yaml/yaml_types/models/models_test_setup.py @@ -0,0 +1,159 @@ +from typing import Any, List, Literal, Optional, Union + +from pydantic import BaseModel, Field, model_validator +from typing_extensions import Annotated + +from libecalc.presentation.yaml.yaml_types.models.yaml_enums import YamlChartType, YamlModelType + + +class Units(BaseModel): + rate: Annotated[Optional[Literal["AM3_PER_HOUR"]], Field(serialization_alias="RATE")] = "AM3_PER_HOUR" + head: Annotated[Optional[Literal["M", "KJ_PER_KG", "JOULE_PER_KG"]], Field(serialization_alias="HEAD")] = "M" + efficiency: Annotated[Literal["FRACTION", "PERCENTAGE"], Field(serialization_alias="EFFICIENCY")] = "FRACTION" + + +class Models(BaseModel): + models: Annotated[List[Any], Field(serialization_alias="MODELS")] + + +class Curve(BaseModel): + # model_config = ConfigDict(extra='allow') + _min_speed: Annotated[int, Field(1000, init=True)] + _max_speed: Annotated[int, Field(6000, init=True)] + _min_rate: Annotated[int, Field(4000, init=True)] + _max_rate: Annotated[int, Field(7000, init=True)] + _min_head: Annotated[int, Field(default=6000, init=True)] + _max_head: Annotated[int, Field(default=9000, init=True)] + _min_efficiency: Annotated[float, Field(default=0.7, init=True)] + _max_efficiency: Annotated[float, Field(default=0.8, init=True)] + + speed: Annotated[int, Field(init=False, serialization_alias="SPEED")] + rate: Annotated[List[int], Field(init=False, serialization_alias="RATE")] + head: Annotated[List[int], Field(init=False, serialization_alias="HEAD")] + efficiency: Annotated[List[float], Field(init=False, serialization_alias="EFFICIENCY")] + + +class Composition(BaseModel): + water: Annotated[float, Field(ge=0, le=1)] = 0 + nitrogen: Annotated[float, Field(ge=0, le=1)] = 0 + CO2: Annotated[float, Field(ge=0, le=1)] = 0 + methane: Annotated[float, Field(ge=0, le=1)] = 0 + ethane: Annotated[float, Field(ge=0, le=1)] = 0 + propane: Annotated[float, Field(ge=0, le=1)] = 0 + i_butane: Annotated[float, Field(ge=0, le=1)] = 0 + n_butane: Annotated[float, Field(ge=0, le=1)] = 0 + i_pentane: Annotated[float, Field(ge=0, le=1)] = 0 + n_pentane: Annotated[float, Field(ge=0, le=1)] = 0 + n_hexane: Annotated[float, Field(ge=0, le=1)] = 0 + + +class FluidModel(BaseModel): + name: Annotated[str, Field(serialization_alias="NAME")] + type: Annotated[str, Field(serialization_alias="TYPE")] = YamlModelType.FLUID.value + fluid_model_type: Annotated[Literal["PREDEFINED", "COMPOSITION"], Field(serialization_alias="FLUID_MODEL_TYPE")] + eos_model: Annotated[Literal["SRK", "PR", "GERG_SRK", "GERG_PR"], Field(serialization_alias="EOS_MODEL")] + gas_type: Annotated[ + Optional[Literal["ULTRA_DRY", "DRY", "MEDIUM", "RICH", "ULTRA_RICH"]], Field(serialization_alias="GAS_TYPE") + ] = None + composition: Annotated[Optional[Composition], Field(serialization_alias="COMPOSITION")] = None + + @model_validator(mode="after") + def check_model_type(self): + if self.fluid_model_type == "PREDEFINED": + self.composition = None + if self.gas_type is None: + raise ValueError("Gas type must be specified for PREDEFINED fluid model") + if self.fluid_model_type == "COMPOSITION": + self.gas_type = None + if self.composition is None: + raise ValueError("Composition must be specified for COMPOSITION fluid model") + return self + + +class CompressorChart(BaseModel): + name: Annotated[str, Field(serialization_alias="NAME")] + type: Annotated[str, Field(serialization_alias="TYPE")] = YamlModelType.COMPRESSOR_CHART.value + units: Annotated[Units, Field(serialization_alias="UNITS")] + + +class ChartSingleSpeed(CompressorChart): + curve: Annotated[Curve, Field(serialization_alias="CURVE")] + chart_type: Annotated[str, Field(serialization_alias="CHART_TYPE")] = YamlChartType.SINGLE_SPEED.value + + +class ChartVariableSpeed(CompressorChart): + curves: List[Curve] + chart_type: str = YamlChartType.VARIABLE_SPEED.value + + +class ChartGenericFromInput(CompressorChart): + polytropic_efficiency: float + chart_type: str = YamlChartType.GENERIC_FROM_INPUT.value + + +class ChartGenericDesignPoint(ChartGenericFromInput): + design_rate: float + design_head: float + chart_type: str = YamlChartType.GENERIC_FROM_DESIGN_POINT.value + + +class Stage(BaseModel): + inlet_temperature: Annotated[float, Field(ge=10, le=60, serialization_alias="INLET_TEMPERATURE")] + compressor_chart: Annotated[str, Field(serialization_alias="COMPRESSOR_CHART")] + pressure_drop_ahead_of_stage: Annotated[ + Optional[float], Field(serialization_alias="PRESSURE_DROP_AHEAD_OF_STAGE") + ] = None + control_margin: Annotated[Optional[float], Field(serialization_alias="CONTROL_MARGIN", ge=0)] + control_margin_unit: Annotated[Optional[str], Field(serialization_alias="CONTROL_MARGIN_UNIT")] + + +class Stages(BaseModel): + stages: Annotated[Union[List[Stage], Stage], Field(serialization_alias="STAGES")] + + +class TrainUnknownStages(BaseModel): + inlet_temperature: float + compressor_chart: str + maximum_pressure_ratio_per_stage: float + + +class CompressorTrainBase(BaseModel): + name: Annotated[str, Field(serialization_alias="NAME")] + fluid_model: Annotated[str, Field(serialization_alias="FLUID_MODEL")] + pressure_control: Annotated[ + Literal["DOWNSTREAM_CHOKE", "UPSTREAM_CHOKE", "INDIVIDUAL_ASV_PRESSURE", "INDIVIDUAL_ASV_RATE", "COMMON_ASV"], + Field(serialization_alias="PRESSURE_CONTROL"), + ] + power_adjustment_constant: Annotated[Optional[float], Field(serialization_alias="POWER_ADJUSTMENT_CONSTANT")] = None + maximum_power: Annotated[Optional[float], Field(serialization_alias="MAXIMUM_POWER", ge=0)] = None + calculate_max_rate: Annotated[Optional[bool], Field(serialization_alias="CALCULATE_MAX_RATE")] = None + + +class SingleSpeedTrainModel(CompressorTrainBase): + type: Annotated[str, Field(serialization_alias="TYPE")] = YamlModelType.SINGLE_SPEED_COMPRESSOR_TRAIN.value + compressor_train: Annotated[Stages, Field(serialization_alias="COMPRESSOR_TRAIN")] + maximum_discharge_pressure: Annotated[Optional[float], Field(serialization_alias="MAXIMUM_DISCHARGE_PRESSURE")] = ( + None + ) + + +class VariableSpeedTrainModel(CompressorTrainBase): + type: Annotated[str, Field(serialization_alias="TYPE")] = YamlModelType.VARIABLE_SPEED_COMPRESSOR_TRAIN.value + compressor_train: Annotated[Stages, Field(serialization_alias="COMPRESSOR_TRAIN")] + maximum_discharge_pressure: Annotated[Optional[float], Field(serialization_alias="MAXIMUM_DISCHARGE_PRESSURE")] = ( + None + ) + + +class SimplifiedVariableSpeedTrainUnknownStagesModel(CompressorTrainBase): + type: Annotated[str, Field(serialization_alias="TYPE")] = ( + YamlModelType.SIMPLIFIED_VARIABLE_SPEED_COMPRESSOR_TRAIN.value + ) + compressor_train: Annotated[TrainUnknownStages, Field(serialization_alias="COMPRESSOR_TRAIN")] + + +class SimplifiedVariableSpeedTrainKnownStagesModel(CompressorTrainBase): + type: Annotated[str, Field(serialization_alias="TYPE")] = ( + YamlModelType.SIMPLIFIED_VARIABLE_SPEED_COMPRESSOR_TRAIN.value + ) + compressor_train: Annotated[Stages, Field(serialization_alias="COMPRESSOR_TRAIN")] From f90c3e044f7cacc005361862dc8d39cf8e8cdd02 Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:24:31 +0100 Subject: [PATCH 3/3] chore: split test in separate file --- .../yaml/yaml_types/models/conftest.py | 67 --------------- .../models/test_compressor_factories.py | 84 +++++++++++++++++++ 2 files changed, 84 insertions(+), 67 deletions(-) create mode 100644 src/tests/libecalc/presentation/yaml/yaml_types/models/test_compressor_factories.py diff --git a/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py b/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py index 7ceac11b42..a68609b07e 100644 --- a/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py +++ b/src/tests/libecalc/presentation/yaml/yaml_types/models/conftest.py @@ -1,35 +1,23 @@ -from io import StringIO -from pathlib import Path from typing import Dict, Generic, List, Optional, TypeVar, Union, cast -import pytest from models_test_setup import ( ChartSingleSpeed, ChartVariableSpeed, - CompressorTrainBase, Curve, FluidModel, - Models, SimplifiedVariableSpeedTrainKnownStagesModel, SimplifiedVariableSpeedTrainUnknownStagesModel, SingleSpeedTrainModel, Stage, - Stages, VariableSpeedTrainModel, ) from polyfactory import Require from polyfactory.decorators import post_generated from polyfactory.factories.pydantic_factory import ModelFactory -from ecalc_cli.infrastructure.file_resource_service import FileResourceService -from libecalc.common.time_utils import Frequency from libecalc.dto import GenericChartFromDesignPoint, GenericChartFromInput from libecalc.presentation.yaml.configuration_service import ConfigurationService -from libecalc.presentation.yaml.model import YamlModel -from libecalc.presentation.yaml.validation_errors import ValidationError from libecalc.presentation.yaml.yaml_entities import ResourceStream -from libecalc.presentation.yaml.yaml_keywords import EcalcYamlKeywords -from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel from libecalc.presentation.yaml.yaml_models.yaml_model import ReaderType, YamlConfiguration, YamlValidator @@ -184,61 +172,6 @@ def efficiency(cls, _min_efficiency: float, _max_efficiency: float) -> List[floa return efficiency -def test_single_speed_compressor_train() -> None: - curve_instance = CurveFactory.build( - _min_speed=3000, - _max_speed=6500, - _min_rate=3500, - _max_rate=7500, - _min_head=5000, - _max_head=9500, - _min_efficiency=0.7, - _max_efficiency=0.8, - ) - chart_instance = SingleSpeedChartFactory.build(name="single_speed_chart", curve=curve_instance) - stage_instance = StageFactory.build( - compressor_chart=chart_instance.name, control_margin=0, control_margin_unit="FRACTION" - ) - fluid_model_instance = FluidModelFactory.build( - name="fluid_model", fluid_model_type="PREDEFINED", gas_type="DRY", eos_model="SRK" - ) - compressor_instance = SingleSpeedTrainFactory.build( - name="single_speed_compressor_train", - compressor_chart=chart_instance.name, - compressor_train=Stages(stages=[stage_instance]), - fluid_model=fluid_model_instance.name, - ) - models = Models( - models=[ - fluid_model_instance, - chart_instance, - compressor_instance, - ] - ) - yaml_dict = models.model_dump(by_alias=True) - cleaned_data = remove_null_none_empty(yaml_dict) - yaml_string = PyYamlYamlModel.dump_yaml(yaml_dict=cleaned_data) - - configuration_service = OverridableStreamConfigurationService( - stream=ResourceStream(name="", stream=StringIO(yaml_string)) - ) - resource_service = FileResourceService(working_directory=Path("")) - - model = YamlModel( - configuration_service=configuration_service, - resource_service=resource_service, - output_frequency=Frequency.YEAR, - ) - - with pytest.raises(ValidationError) as exc_info: - model.validate_for_run() - - assert f"{EcalcYamlKeywords.fuel_types}\n" f"\tMessage: This keyword is missing, it is required" in str( - exc_info.value - ) - assert isinstance(compressor_instance, CompressorTrainBase) - - yaml_fluid_model = """ - NAME: fluid_model TYPE: FLUID diff --git a/src/tests/libecalc/presentation/yaml/yaml_types/models/test_compressor_factories.py b/src/tests/libecalc/presentation/yaml/yaml_types/models/test_compressor_factories.py new file mode 100644 index 0000000000..bb850b498f --- /dev/null +++ b/src/tests/libecalc/presentation/yaml/yaml_types/models/test_compressor_factories.py @@ -0,0 +1,84 @@ +from io import StringIO +from pathlib import Path + +import pytest +from models_test_setup import CompressorTrainBase, Models, Stages + +from conftest import ( + CurveFactory, + FluidModelFactory, + OverridableStreamConfigurationService, + SingleSpeedChartFactory, + SingleSpeedTrainFactory, + StageFactory, + remove_null_none_empty, +) +from ecalc_cli.infrastructure.file_resource_service import FileResourceService +from libecalc.common.time_utils import Frequency +from libecalc.presentation.yaml.model import YamlModel +from libecalc.presentation.yaml.validation_errors import ValidationError +from libecalc.presentation.yaml.yaml_entities import ResourceStream +from libecalc.presentation.yaml.yaml_keywords import EcalcYamlKeywords +from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel + + +def test_single_speed_compressor_train() -> None: + # Build objects for curve, chart, stage, fluid model and compressor + curve_instance = CurveFactory.build( + _min_speed=3000, + _max_speed=6500, + _min_rate=3500, + _max_rate=7500, + _min_head=5000, + _max_head=9500, + _min_efficiency=0.7, + _max_efficiency=0.8, + ) + chart_instance = SingleSpeedChartFactory.build(name="single_speed_chart", curve=curve_instance) + stage_instance = StageFactory.build( + compressor_chart=chart_instance.name, control_margin=0, control_margin_unit="FRACTION" + ) + fluid_model_instance = FluidModelFactory.build( + name="fluid_model", fluid_model_type="PREDEFINED", gas_type="DRY", eos_model="SRK" + ) + compressor_instance = SingleSpeedTrainFactory.build( + name="single_speed_compressor_train", + compressor_chart=chart_instance.name, + compressor_train=Stages(stages=[stage_instance]), + fluid_model=fluid_model_instance.name, + ) + + # Combine objects into MODELS + models = Models( + models=[ + fluid_model_instance, + chart_instance, + compressor_instance, + ] + ) + + # Serialize pydantic model, clean None-data, generate yaml string + yaml_dict = models.model_dump(by_alias=True) + cleaned_data = remove_null_none_empty(yaml_dict) + yaml_string = PyYamlYamlModel.dump_yaml(yaml_dict=cleaned_data) + + # Set up yaml model + configuration_service = OverridableStreamConfigurationService( + stream=ResourceStream(name="", stream=StringIO(yaml_string)) + ) + resource_service = FileResourceService(working_directory=Path("")) + + model = YamlModel( + configuration_service=configuration_service, + resource_service=resource_service, + output_frequency=Frequency.YEAR, + ) + + # Validate yaml model + with pytest.raises(ValidationError) as exc_info: + model.validate_for_run() + + assert f"{EcalcYamlKeywords.fuel_types}\n" f"\tMessage: This keyword is missing, it is required" in str( + exc_info.value + ) + assert isinstance(compressor_instance, CompressorTrainBase)