From 50f41abf59ccb37783b3765f556dc9f51f755bbd Mon Sep 17 00:00:00 2001 From: Jostein Solaas Date: Fri, 23 Aug 2024 08:05:30 +0200 Subject: [PATCH] refactor: yaml model class and reader Make YamlModel reusable without having to use files on disk --- README.md | 8 +- examples/simple_python_model.ipynb | 122 ++++++++++++--- examples/simple_yaml_model.ipynb | 40 +++-- src/ecalc_cli/commands/run.py | 10 +- src/libecalc/fixtures/case_types.py | 8 +- src/libecalc/fixtures/case_utils.py | 8 - .../ltp_export/loading_storage_ltp_yaml.py | 5 +- .../ltp_export/ltp_power_from_shore_yaml.py | 8 +- .../venting_emitters/venting_emitter_yaml.py | 8 +- .../yaml/mappers/component_mapper.py | 6 +- .../variables_mapper/variables_mapper.py | 1 + src/libecalc/presentation/yaml/model.py | 142 ++++++++++++------ src/libecalc/presentation/yaml/parse_input.py | 5 +- .../yaml/yaml_models/pyyaml_yaml_model.py | 17 ++- .../yaml/yaml_models/ruamel_yaml_model.py | 14 +- .../yaml/yaml_models/yaml_model.py | 56 +++++-- .../input/mappers/test_model_mapper.py | 26 +++- src/tests/libecalc/input/test_file_io.py | 12 +- src/tests/libecalc/input/test_model.py | 9 -- src/tests/libecalc/input/test_parse_input.py | 59 -------- .../libecalc/input/test_yaml_configuration.py | 5 +- src/tests/libecalc/input/test_yaml_model.py | 49 ++++++ .../yaml_models/test_yaml_model_errors.py | 8 +- 23 files changed, 406 insertions(+), 220 deletions(-) delete mode 100644 src/tests/libecalc/input/test_model.py delete mode 100644 src/tests/libecalc/input/test_parse_input.py create mode 100644 src/tests/libecalc/input/test_yaml_model.py diff --git a/README.md b/README.md index 757a323f7e..59889afb6e 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,16 @@ dependencies. ```bash pip install libecalc[notebooks] -``` +``` In the examples you will find examples using both the YAML specifications and Python models. See /examples +Run jupyter: + +```bash +jupyter notebook examples +``` + ## Documentation The documentation can be found at https://equinor.github.io/ecalc diff --git a/examples/simple_python_model.ipynb b/examples/simple_python_model.ipynb index bc7d248996..8af4dce799 100644 --- a/examples/simple_python_model.ipynb +++ b/examples/simple_python_model.ipynb @@ -3,7 +3,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "# Simple Model Example\n", @@ -39,7 +42,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "### Python Model\n", @@ -65,7 +71,10 @@ "end_time": "2023-04-26T14:25:23.596846Z", "start_time": "2023-04-26T14:25:23.572924Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -107,6 +116,9 @@ "start_time": "2023-04-26T13:28:02.924970Z" }, "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "is_executing": true } @@ -305,7 +317,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Then we create fuel and emissions that will be used by the model." @@ -320,6 +335,9 @@ "start_time": "2023-04-26T13:28:02.932398Z" }, "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "is_executing": true } @@ -337,7 +355,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Next we create the Flare" @@ -352,6 +373,9 @@ "start_time": "2023-04-26T13:28:02.934113Z" }, "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "is_executing": true } @@ -382,7 +406,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Then we create a gas export compressor" @@ -397,6 +424,9 @@ "start_time": "2023-04-26T13:28:02.939094Z" }, "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "is_executing": true } @@ -439,7 +469,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Then we create the base production load" @@ -453,7 +486,10 @@ "end_time": "2023-04-26T14:25:24.117379Z", "start_time": "2023-04-26T14:25:24.115645Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -480,7 +516,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Then we create a gas injection compressor" @@ -494,7 +533,10 @@ "end_time": "2023-04-26T14:25:24.120878Z", "start_time": "2023-04-26T14:25:24.119794Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -550,7 +592,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Then we create a Produced Water Re-injection Pump" @@ -564,7 +609,10 @@ "end_time": "2023-04-26T14:25:24.124429Z", "start_time": "2023-04-26T14:25:24.123353Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -609,7 +657,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "The we create a Seawater Injection Pump" @@ -623,7 +674,10 @@ "end_time": "2023-04-26T14:25:24.127468Z", "start_time": "2023-04-26T14:25:24.126420Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -661,7 +715,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "Then we collect the electricity consumers inside a GeneratorSet" @@ -675,7 +732,10 @@ "end_time": "2023-04-26T14:25:24.130979Z", "start_time": "2023-04-26T14:25:24.129956Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -709,7 +769,10 @@ "end_time": "2023-04-26T14:25:24.134692Z", "start_time": "2023-04-26T14:25:24.133559Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -741,7 +804,10 @@ "end_time": "2023-04-26T14:25:24.148885Z", "start_time": "2023-04-26T14:25:24.147857Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -768,7 +834,10 @@ "end_time": "2023-04-26T14:25:24.638557Z", "start_time": "2023-04-26T14:25:24.153021Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -797,7 +866,10 @@ "end_time": "2023-04-26T14:25:24.640337Z", "start_time": "2023-04-26T14:25:24.639622Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -809,23 +881,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.8.16" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 4 } diff --git a/examples/simple_yaml_model.ipynb b/examples/simple_yaml_model.ipynb index 45ffc26448..28ba7c39c6 100644 --- a/examples/simple_yaml_model.ipynb +++ b/examples/simple_yaml_model.ipynb @@ -3,7 +3,10 @@ { "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "source": [ "# Simple Model Example\n", @@ -52,7 +55,10 @@ "end_time": "2023-04-26T14:25:25.542990Z", "start_time": "2023-04-26T14:25:25.462190Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -60,12 +66,18 @@ "from libecalc.application.graph_result import GraphResult\n", "from libecalc.application.energy_calculator import EnergyCalculator\n", "from libecalc.common.time_utils import Frequency\n", - "from libecalc.presentation.yaml.model import YamlModel\n", + "from libecalc.presentation.yaml.model import FileConfigurationService, FileResourceService, YamlModel\n", "from libecalc.examples import simple\n", "\n", "\n", "model_path = Path(simple.__file__).parent / \"model.yaml\"\n", - "yaml_model = YamlModel(path=model_path, output_frequency=Frequency.NONE)\n", + "configuration_service = FileConfigurationService(configuration_path=model_path)\n", + "resource_service = FileResourceService(working_directory=model_path.parent)\n", + "yaml_model = YamlModel(\n", + " configuration_service=configuration_service,\n", + " resource_service=resource_service,\n", + " output_frequency=Frequency.NONE,\n", + ")\n", "\n", "model = EnergyCalculator(graph=yaml_model.graph)\n", "consumer_results = model.evaluate_energy_usage(yaml_model.variables)\n", @@ -89,7 +101,10 @@ "end_time": "2023-04-26T14:25:26.099329Z", "start_time": "2023-04-26T14:25:25.559549Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -118,7 +133,10 @@ "end_time": "2023-04-26T14:25:26.101180Z", "start_time": "2023-04-26T14:25:26.100018Z" }, - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -130,23 +148,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.8.16" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 4 } diff --git a/src/ecalc_cli/commands/run.py b/src/ecalc_cli/commands/run.py index 0cfc23036c..15f058af52 100644 --- a/src/ecalc_cli/commands/run.py +++ b/src/ecalc_cli/commands/run.py @@ -22,7 +22,7 @@ from libecalc.common.run_info import RunInfo from libecalc.infrastructure.file_utils import OutputFormat, get_result_output from libecalc.presentation.json_result.mapper import get_asset_result -from libecalc.presentation.yaml.model import YamlModel +from libecalc.presentation.yaml.model import FileConfigurationService, FileResourceService, YamlModel def run( @@ -109,7 +109,13 @@ def run( logger.info(f"eCalcâ„¢ simulation starting. Running {run_info}") validate_arguments(model_file=model_file, output_folder=output_folder) - model = YamlModel(path=model_file, output_frequency=output_frequency) + configuration_service = FileConfigurationService(configuration_path=model_file) + resource_service = FileResourceService(working_directory=model_file.parent) + model = YamlModel( + configuration_service=configuration_service, + resource_service=resource_service, + output_frequency=output_frequency, + ) if (flow_diagram or ltp_export) and (model.start is None or model.end is None): logger.warning( diff --git a/src/libecalc/fixtures/case_types.py b/src/libecalc/fixtures/case_types.py index f6dbf92447..db33d6e98f 100644 --- a/src/libecalc/fixtures/case_types.py +++ b/src/libecalc/fixtures/case_types.py @@ -1,3 +1,4 @@ +import io from dataclasses import dataclass from pathlib import Path from typing import Dict, NamedTuple, TextIO @@ -9,9 +10,14 @@ @dataclass class YamlCase: resources: Dict[str, Resource] - main_file: TextIO main_file_path: Path + @property + def main_file(self) -> TextIO: + with open(self.main_file_path) as f: + lines = f.read() + return io.StringIO(lines) + class DTOCase(NamedTuple): ecalc_model: dto.Asset diff --git a/src/libecalc/fixtures/case_utils.py b/src/libecalc/fixtures/case_utils.py index dc7d806d40..18365461a5 100644 --- a/src/libecalc/fixtures/case_utils.py +++ b/src/libecalc/fixtures/case_utils.py @@ -1,4 +1,3 @@ -import io from pathlib import Path from typing import Dict, List @@ -7,12 +6,6 @@ from libecalc.presentation.yaml.yaml_entities import Resource -def _read_main_file(main_file_path: Path) -> io.StringIO: - with open(main_file_path) as f: - lines = f.read() - return io.StringIO(lines) - - def _read_resources(directory: Path, resource_names: List[str]) -> Dict[str, Resource]: resources = {} for resource_name in resource_names: @@ -27,6 +20,5 @@ def load(case_path: Path, main_file: str, resource_names: List[str]): main_file_path = case_data_path / main_file return YamlCase( main_file_path=main_file_path, - main_file=_read_main_file(main_file_path), resources=_read_resources(directory=main_file_path.parent, resource_names=resource_names), ) diff --git a/src/libecalc/fixtures/cases/ltp_export/loading_storage_ltp_yaml.py b/src/libecalc/fixtures/cases/ltp_export/loading_storage_ltp_yaml.py index 73db2df3af..f074e58ca4 100644 --- a/src/libecalc/fixtures/cases/ltp_export/loading_storage_ltp_yaml.py +++ b/src/libecalc/fixtures/cases/ltp_export/loading_storage_ltp_yaml.py @@ -4,8 +4,8 @@ from libecalc.common.utils.rates import RateType from libecalc.dto import Asset -from libecalc.presentation.yaml.model import PyYamlYamlModel from libecalc.presentation.yaml.parse_input import map_yaml_to_dto +from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel def ltp_oil_loaded_yaml_factory( @@ -40,9 +40,10 @@ def ltp_oil_loaded_yaml_factory( yaml_text = yaml.safe_load(input_text) configuration = PyYamlYamlModel( internal_datamodel=yaml_text, + name="test", instantiated_through_read=True, ) - yaml_model = map_yaml_to_dto(configuration=configuration, resources={}, name="test") + yaml_model = map_yaml_to_dto(configuration=configuration, resources={}) return yaml_model diff --git a/src/libecalc/fixtures/cases/ltp_export/ltp_power_from_shore_yaml.py b/src/libecalc/fixtures/cases/ltp_export/ltp_power_from_shore_yaml.py index ad9eca6bde..fcf1abd17f 100644 --- a/src/libecalc/fixtures/cases/ltp_export/ltp_power_from_shore_yaml.py +++ b/src/libecalc/fixtures/cases/ltp_export/ltp_power_from_shore_yaml.py @@ -8,8 +8,9 @@ from libecalc.expression.expression import ExpressionType from libecalc.fixtures.case_types import DTOCase from libecalc.presentation.yaml.mappers.variables_mapper import map_yaml_to_variables -from libecalc.presentation.yaml.model import PyYamlYamlModel, YamlModel +from libecalc.presentation.yaml.model import FileResourceService from libecalc.presentation.yaml.parse_input import map_yaml_to_dto +from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel @pytest.fixture @@ -80,12 +81,13 @@ def _ltp_pfs_yaml_factory( yaml_text = yaml.safe_load(input_text) configuration = PyYamlYamlModel( internal_datamodel=yaml_text, + name="ltp_export", instantiated_through_read=True, ) path = path - resources = YamlModel._read_resources(yaml_configuration=configuration, working_directory=path) + resources = FileResourceService._read_resources(configuration=configuration, working_directory=path) variables = map_yaml_to_variables( configuration, resources=resources, @@ -95,7 +97,7 @@ def _ltp_pfs_yaml_factory( output_frequency=Frequency.YEAR, ), ) - yaml_model = map_yaml_to_dto(configuration=configuration, resources=resources, name="ltp_export") + yaml_model = map_yaml_to_dto(configuration=configuration, resources=resources) return DTOCase(ecalc_model=yaml_model, variables=variables) return _ltp_pfs_yaml_factory diff --git a/src/libecalc/fixtures/cases/venting_emitters/venting_emitter_yaml.py b/src/libecalc/fixtures/cases/venting_emitters/venting_emitter_yaml.py index 2e460fdbb0..865b89c501 100644 --- a/src/libecalc/fixtures/cases/venting_emitters/venting_emitter_yaml.py +++ b/src/libecalc/fixtures/cases/venting_emitters/venting_emitter_yaml.py @@ -9,8 +9,9 @@ from libecalc.dto import ResultOptions from libecalc.fixtures.case_types import DTOCase from libecalc.presentation.yaml.mappers.variables_mapper import map_yaml_to_variables -from libecalc.presentation.yaml.model import PyYamlYamlModel, YamlModel +from libecalc.presentation.yaml.model import FileResourceService from libecalc.presentation.yaml.parse_input import map_yaml_to_dto +from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel from libecalc.presentation.yaml.yaml_types.emitters.yaml_venting_emitter import ( YamlVentingType, ) @@ -85,9 +86,10 @@ def venting_emitter_yaml_factory( yaml_text = yaml.safe_load(input_text) configuration = PyYamlYamlModel( internal_datamodel=yaml_text, + name="venting_emitters", instantiated_through_read=True, ) - resources = YamlModel._read_resources(yaml_configuration=configuration, working_directory=path) + resources = FileResourceService._read_resources(configuration=configuration, working_directory=path) variables = map_yaml_to_variables( configuration, resources=resources, @@ -98,7 +100,7 @@ def venting_emitter_yaml_factory( ), ) - yaml_model = map_yaml_to_dto(configuration=configuration, resources=resources, name="venting_emitters") + yaml_model = map_yaml_to_dto(configuration=configuration, resources=resources) return DTOCase(ecalc_model=yaml_model, variables=variables) diff --git a/src/libecalc/presentation/yaml/mappers/component_mapper.py b/src/libecalc/presentation/yaml/mappers/component_mapper.py index 077b6742e8..ec3a18fc50 100644 --- a/src/libecalc/presentation/yaml/mappers/component_mapper.py +++ b/src/libecalc/presentation/yaml/mappers/component_mapper.py @@ -20,7 +20,7 @@ ) from libecalc.presentation.yaml.yaml_entities import References 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 YamlValidator from libecalc.presentation.yaml.yaml_types.components.system.yaml_consumer_system import ( YamlConsumerSystem, ) @@ -310,10 +310,10 @@ def __init__( self.__references = references self.__installation_mapper = InstallationMapper(references=references, target_period=target_period) - def from_yaml_to_dto(self, configuration: PyYamlYamlModel, name: str) -> dto.Asset: + def from_yaml_to_dto(self, configuration: YamlValidator) -> dto.Asset: try: ecalc_model = dto.Asset( - name=name, + name=configuration.name, installations=[ self.__installation_mapper.from_yaml_to_dto(installation) for installation in configuration.installations diff --git a/src/libecalc/presentation/yaml/mappers/variables_mapper/variables_mapper.py b/src/libecalc/presentation/yaml/mappers/variables_mapper/variables_mapper.py index 87161071e2..505d05ee91 100644 --- a/src/libecalc/presentation/yaml/mappers/variables_mapper/variables_mapper.py +++ b/src/libecalc/presentation/yaml/mappers/variables_mapper/variables_mapper.py @@ -111,6 +111,7 @@ def map_yaml_to_variables( resources: Resources, result_options: dto.ResultOptions, ) -> dto.VariablesMap: + # TODO: Replace configuration type with YamlValidator timeseries_collections = [ TimeSeriesCollectionMapper(resources).from_yaml_to_dto(timeseries.model_dump(by_alias=True)) for timeseries in configuration.time_series_raise_if_invalid diff --git a/src/libecalc/presentation/yaml/model.py b/src/libecalc/presentation/yaml/model.py index 8ecc6b7561..34709df817 100644 --- a/src/libecalc/presentation/yaml/model.py +++ b/src/libecalc/presentation/yaml/model.py @@ -1,6 +1,7 @@ +import abc from datetime import datetime from pathlib import Path -from typing import Callable, Dict +from typing import Callable, Dict, Optional, Protocol from libecalc.common.errors.exceptions import EcalcError, InvalidResourceHeaderException from libecalc.common.logger import logger @@ -17,55 +18,22 @@ Resource, ResourceStream, ) -from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel +from libecalc.presentation.yaml.yaml_models.yaml_model import ReaderType, YamlConfiguration, YamlValidator -class YamlModel: - def __init__(self, path: Path, output_frequency: Frequency) -> None: - self._model_path = path - self._output_frequency = output_frequency - self._yaml_configuration = YamlModel._create_yaml_configuration(path) - self.resources = YamlModel._read_resources(self._yaml_configuration, working_directory=path.parent) - self.dto = map_yaml_to_dto(configuration=self._yaml_configuration, resources=self.resources, name=path.stem) +class ResourceService(Protocol): + @abc.abstractmethod + def get_resources(self, configuration: YamlValidator) -> Dict[str, Resource]: ... - @property - def start(self) -> datetime: - return self._yaml_configuration.start - @property - def end(self) -> datetime: - return self._yaml_configuration.end +class ConfigurationService(Protocol): + @abc.abstractmethod + def get_configuration(self) -> YamlValidator: ... - @property - def variables(self) -> VariablesMap: - return map_yaml_to_variables( - configuration=self._yaml_configuration, resources=self.resources, result_options=self.result_options - ) - @property - def result_options(self) -> ResultOptions: - return ResultOptions( - start=self._yaml_configuration.start, - end=self._yaml_configuration.end, - output_frequency=self._output_frequency, - ) - - @property - def graph(self) -> ComponentGraph: - return self.dto.get_graph() - - @staticmethod - def _create_yaml_configuration(main_yaml_path: Path) -> PyYamlYamlModel: - with open(main_yaml_path) as model_file: - main_resource = ResourceStream( - name=main_yaml_path.name, - stream=model_file, - ) - - main_yaml_model: PyYamlYamlModel = PyYamlYamlModel.read( - main_yaml=main_resource, enable_include=True, base_dir=main_yaml_path.parent - ) - return main_yaml_model +class FileResourceService(ResourceService): + def __init__(self, working_directory: Path): + self._working_directory = working_directory @staticmethod def _read_resource(resource_name: Path, *args, read_func: Callable[..., Resource]): @@ -75,19 +43,93 @@ def _read_resource(resource_name: Path, *args, read_func: Callable[..., Resource logger.error(str(exc)) raise EcalcError("Failed to read resource", f"Failed to read {resource_name.name}: {str(exc)}") from exc - @staticmethod - def _read_resources(yaml_configuration: PyYamlYamlModel, working_directory: Path) -> Dict[str, Resource]: + @classmethod + def _read_resources(cls, configuration: YamlValidator, working_directory: Path) -> Dict[str, Resource]: resources: Dict[str, Resource] = {} - for timeseries_resource in yaml_configuration.timeseries_resources: - resources[timeseries_resource.name] = YamlModel._read_resource( + for timeseries_resource in configuration.timeseries_resources: + resources[timeseries_resource.name] = cls._read_resource( working_directory / timeseries_resource.name, timeseries_resource.typ, read_func=read_timeseries_resource, ) - for facility_resource_name in yaml_configuration.facility_resource_names: - resources[facility_resource_name] = YamlModel._read_resource( + for facility_resource_name in configuration.facility_resource_names: + resources[facility_resource_name] = cls._read_resource( working_directory / facility_resource_name, read_func=read_facility_resource, ) return resources + + def get_resources(self, configuration: YamlValidator) -> Dict[str, Resource]: + return self._read_resources(configuration=configuration, working_directory=self._working_directory) + + +class FileConfigurationService(ConfigurationService): + def __init__(self, configuration_path: Path): + self._configuration_path = configuration_path + + def get_configuration(self) -> YamlValidator: + with open(self._configuration_path) as configuration_file: + main_resource = ResourceStream( + name=self._configuration_path.stem, + stream=configuration_file, + ) + + main_yaml_model = YamlConfiguration.Builder.get_yaml_reader(ReaderType.PYYAML).get_validator( + main_yaml=main_resource, enable_include=True, base_dir=self._configuration_path.parent + ) + return main_yaml_model + + +class YamlModel: + """ + Class representing both the yaml and the resources. + + We haven't defined a difference in naming between the YamlModel representing only the yaml file and this class, + which also have information about the referenced resources. + + Maybe we could use 'configuration' for the single yaml file, that naming is already used a lot for the instantiation + of that YamlModel class. + + configuration: the model configuration + resources: the model 'input', kind of + model: configuration + resources (input) + """ + + def __init__( + self, + configuration_service: ConfigurationService, + resource_service: ResourceService, + output_frequency: Frequency, + ) -> None: + self._output_frequency = output_frequency + configuration = configuration_service.get_configuration() + self.resources = resource_service.get_resources(configuration) + self.dto = map_yaml_to_dto(configuration=configuration, resources=self.resources) + self._configuration = configuration + + @property + def start(self) -> Optional[datetime]: + return self._configuration.start + + @property + def end(self) -> Optional[datetime]: + return self._configuration.end + + @property + def variables(self) -> VariablesMap: + return map_yaml_to_variables( + configuration=self._configuration, resources=self.resources, result_options=self.result_options + ) + + @property + def result_options(self) -> ResultOptions: + return ResultOptions( + start=self._configuration.start, + end=self._configuration.end, + output_frequency=self._output_frequency, + ) + + @property + def graph(self) -> ComponentGraph: + return self.dto.get_graph() diff --git a/src/libecalc/presentation/yaml/parse_input.py b/src/libecalc/presentation/yaml/parse_input.py index fc8698e82d..d4a3161fd4 100644 --- a/src/libecalc/presentation/yaml/parse_input.py +++ b/src/libecalc/presentation/yaml/parse_input.py @@ -10,7 +10,8 @@ DEFAULT_START_TIME = datetime(1900, 1, 1) -def map_yaml_to_dto(configuration: PyYamlYamlModel, resources: Resources, name: str) -> dto.Asset: +def map_yaml_to_dto(configuration: PyYamlYamlModel, resources: Resources) -> dto.Asset: + # TODO: Replace configuration type with YamlValidator references = create_references(configuration, resources) target_period = Period( start=configuration.start or DEFAULT_START_TIME, @@ -20,4 +21,4 @@ def map_yaml_to_dto(configuration: PyYamlYamlModel, resources: Resources, name: references=references, target_period=target_period, ) - return model_mapper.from_yaml_to_dto(configuration=configuration, name=name) + return model_mapper.from_yaml_to_dto(configuration=configuration) diff --git a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py index 51425ab34f..1930c3746d 100644 --- a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py @@ -35,7 +35,7 @@ FileContext, YamlError, ) -from libecalc.presentation.yaml.yaml_models.yaml_model import YamlModel, YamlValidator +from libecalc.presentation.yaml.yaml_models.yaml_model import YamlConfiguration, YamlValidator from libecalc.presentation.yaml.yaml_types.components.yaml_asset import YamlAsset from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import ( YamlFacilityModel, @@ -54,19 +54,23 @@ ) -class PyYamlYamlModel(YamlValidator, YamlModel): +class PyYamlYamlModel(YamlValidator, YamlConfiguration): """Implementation of yaml model using PyYaml library Keeping comments and horizontal lists on loading currently not supported! """ - def __init__(self, internal_datamodel: Dict[str, Any], instantiated_through_read: bool = False): + @classmethod + def get_validator(cls, *args, **kwargs) -> "YamlValidator": + return cls.read(*args, **kwargs) + + def __init__(self, internal_datamodel: Dict[str, Any], name: str, instantiated_through_read: bool = False): """To avoid mistakes, make sure that this is only instantiated through read method/named constructor :param instantiated_through_read: set to True to allow to use constructor. """ if not instantiated_through_read: raise ProgrammingError(f"{self.__class__} can only be instantiated through read() method/named constructor") - super().__init__(internal_datamodel=internal_datamodel) + super().__init__(internal_datamodel=internal_datamodel, name=name) def dump(self) -> str: if self._internal_datamodel is None: @@ -85,7 +89,7 @@ def read( internal_datamodel = PyYamlYamlModel.read_yaml( main_yaml=main_yaml, resources=resources, base_dir=base_dir, enable_include=enable_include ) - self = cls(internal_datamodel=internal_datamodel, instantiated_through_read=True) + self = cls(internal_datamodel=internal_datamodel, name=main_yaml.name, instantiated_through_read=True) return self class SafeLineLoader(SafeLoader): @@ -257,6 +261,9 @@ def read_yaml( ) from e # start of validation/parsing methods + @property + def name(self): + return self._name @property def facility_resource_names(self) -> List[str]: diff --git a/src/libecalc/presentation/yaml/yaml_models/ruamel_yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/ruamel_yaml_model.py index 93d7917d95..33fa13c531 100644 --- a/src/libecalc/presentation/yaml/yaml_models/ruamel_yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/ruamel_yaml_model.py @@ -10,22 +10,22 @@ from libecalc.presentation.yaml.file_context import FileContext, FileMark from libecalc.presentation.yaml.yaml_entities import ResourceStream from libecalc.presentation.yaml.yaml_models.exceptions import YamlError -from libecalc.presentation.yaml.yaml_models.yaml_model import YamlModel +from libecalc.presentation.yaml.yaml_models.yaml_model import YamlConfiguration -class RuamelYamlModel(YamlModel): +class RuamelYamlModel(YamlConfiguration): """Implementation of yaml model using Ruamel library Validation has currently not been implemented. """ - def __init__(self, internal_datamodel: Dict[str, Any], instantiated_through_read: bool = False): + def __init__(self, internal_datamodel: Dict[str, Any], name: str, instantiated_through_read: bool = False): """To avoid mistakes, make sure that this is only instantiated through read method/named constructor :param instantiated_through_read: set to True to allow to use constructor. """ if not instantiated_through_read: raise ProgrammingError(f"{self.__class__} can only be instantiated through read() method/named constructor") - super().__init__(internal_datamodel=internal_datamodel) + super().__init__(internal_datamodel=internal_datamodel, name=name) def dump(self) -> str: """Dumps the model to a string buffer and returns it @@ -107,7 +107,7 @@ def read( internal_datamodel = RuamelYamlModel._load( yaml_file=main_yaml, resources=resources, enable_include=enable_include, base_dir=base_dir ) - self = cls(internal_datamodel=internal_datamodel, instantiated_through_read=True) + self = cls(internal_datamodel=internal_datamodel, name=main_yaml.name, instantiated_through_read=True) return self @staticmethod @@ -154,3 +154,7 @@ def _load( raise YamlError( problem="We are not able to load the yaml due to an error: " + str(e), ) from e + + @property + def name(self): + return self._name diff --git a/src/libecalc/presentation/yaml/yaml_models/yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/yaml_model.py index 7dda65be90..bcff0354c0 100644 --- a/src/libecalc/presentation/yaml/yaml_models/yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/yaml_model.py @@ -2,7 +2,7 @@ import datetime import enum from pathlib import Path -from typing import Any, Dict, List, Optional, TextIO, Type +from typing import Any, Dict, Iterable, List, Optional, TextIO, Type from libecalc.common.logger import logger from libecalc.presentation.yaml.yaml_entities import ( @@ -25,14 +25,21 @@ class YamlValidator(abc.ABC): gets details of the model. Currently only PyYaml implementation. """ + @property + @abc.abstractmethod + def name(self): ... + + @property @abc.abstractmethod def facility_resource_names(self) -> List[str]: pass + @property @abc.abstractmethod def timeseries_resources(self) -> List[YamlTimeseriesResource]: pass + @property @abc.abstractmethod def all_resource_names(self) -> List[str]: pass @@ -61,18 +68,22 @@ def models(self): def fuel_types(self): pass + @property @abc.abstractmethod - def installations(self): + def installations(self) -> Iterable: pass + @property @abc.abstractmethod def start(self) -> Optional[datetime.datetime]: pass + @property @abc.abstractmethod def end(self) -> Optional[datetime.datetime]: pass + @property @abc.abstractmethod def dates(self): pass @@ -90,7 +101,7 @@ def read( base_dir: Optional[Path] = None, resources: Optional[Dict[str, TextIO]] = None, enable_include: bool = False, - ) -> "YamlModel": + ) -> "YamlConfiguration": """Named constructor for the yaml model, the way to instantiate the yaml model. We currently only allow a yaml model to be constructed by reading a yaml file. @@ -107,6 +118,20 @@ def read( """ pass + @classmethod + @abc.abstractmethod + def get_validator( + cls, + main_yaml: ResourceStream, + base_dir: Optional[Path] = None, + resources: Optional[Dict[str, TextIO]] = None, + enable_include: bool = False, + ) -> "YamlValidator": ... + + """ + Get yaml validator + """ + class YamlDumper(abc.ABC): @abc.abstractmethod @@ -120,7 +145,7 @@ def dump(self) -> str: pass -class YamlModelType(str, enum.Enum): +class ReaderType(str, enum.Enum): """Which yaml model to use. User should in general define capabilities, and get an appropriate yaml model, but for now we define implementation. """ @@ -129,7 +154,7 @@ class YamlModelType(str, enum.Enum): PYYAML = "PYYAML" # Support for validation, does not conserve comments and makes vertical lists -class YamlModel(YamlReader, YamlDumper, metaclass=abc.ABCMeta): +class YamlConfiguration(YamlReader, YamlDumper, metaclass=abc.ABCMeta): """Default yaml model specification, that a yaml model implementation MUST HAVE reader/loader and dumper/representer behaviour. @@ -144,35 +169,36 @@ class YamlModel(YamlReader, YamlDumper, metaclass=abc.ABCMeta): None # to temporary store a loaded yaml model. Format is defined by implementation ) - def __init__(self, internal_datamodel: Dict[str, Any]): + def __init__(self, internal_datamodel: Dict[str, Any], name: str): self._internal_datamodel = internal_datamodel + self._name = name class Builder: """Inner class to build yaml models.""" @staticmethod - def get_yaml_model(yaml_model_type: YamlModelType) -> Type["YamlModel"]: + def get_yaml_reader(reader_type: ReaderType) -> Type["YamlReader"]: """Note! Returns the type of the YamlModel, and hence NOT an instantiation. That must be done later through that type/class's way to do that. (in general through read()). - :param yaml_model_type: + :param reader_type: :return: """ - if yaml_model_type == YamlModelType.RUAMEL: + if reader_type == ReaderType.RUAMEL: # Imported here to avoid circular dependency. The __init__/central module trick didn't work from libecalc.presentation.yaml.yaml_models.ruamel_yaml_model import ( RuamelYamlModel, ) return RuamelYamlModel - elif yaml_model_type == YamlModelType.PYYAML: + elif reader_type == ReaderType.PYYAML: from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import ( PyYamlYamlModel, ) return PyYamlYamlModel - raise NotImplementedError(f"Unknown yaml model implementation provided: {str(yaml_model_type)}") + raise NotImplementedError(f"Unknown yaml model implementation provided: {str(reader_type)}") class UpdateStatus(enum.Enum): """Update status for updating resource files when loading yaml and attempting to match @@ -190,7 +216,7 @@ def update_resource_names(self, mappings: Dict[str, str]) -> Dict[str, UpdateSta old_name: new_name :return: """ - update_statuses: Dict[str, YamlModel.UpdateStatus] = {} + update_statuses: Dict[str, YamlConfiguration.UpdateStatus] = {} for old_name, new_name in mappings.items(): update_statuses[old_name] = self.update_resource_name(old_name, new_name) @@ -221,12 +247,12 @@ def update_resource_name(self, old_name: str, new_name: str) -> UpdateStatus: if names_updated == 0: logger.warning(f"No resource was found with name: {old_name}") - return YamlModel.UpdateStatus.ZERO_UPDATES + return YamlConfiguration.UpdateStatus.ZERO_UPDATES elif names_updated > 1: logger.warning(f"More than one resource was updated ({old_name} found {names_updated} times)") - return YamlModel.UpdateStatus.MANY_UPDATES + return YamlConfiguration.UpdateStatus.MANY_UPDATES else: - return YamlModel.UpdateStatus.ONE_UPDATE + return YamlConfiguration.UpdateStatus.ONE_UPDATE def __update_resource(self, resource_type: str, field: str, old_value: any, new_value: any) -> int: """Update a nested dict object in the yaml config data. diff --git a/src/tests/libecalc/input/mappers/test_model_mapper.py b/src/tests/libecalc/input/mappers/test_model_mapper.py index 06362bf03c..97d94e870c 100644 --- a/src/tests/libecalc/input/mappers/test_model_mapper.py +++ b/src/tests/libecalc/input/mappers/test_model_mapper.py @@ -5,13 +5,14 @@ import pytest from libecalc import dto -from libecalc.common.time_utils import Period -from libecalc.presentation.yaml.mappers.component_mapper import EcalcModelMapper -from libecalc.presentation.yaml.mappers.create_references import create_references +from libecalc.common.time_utils import Frequency, Period from libecalc.presentation.yaml.mappers.model import ModelMapper +from libecalc.presentation.yaml.model import ConfigurationService, YamlModel from libecalc.presentation.yaml.yaml_entities import Resource, 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 YamlValidator +from tests.libecalc.input.test_yaml_model import DirectResourceService class TestModelMapper: @@ -288,6 +289,15 @@ def dated_model_data(dated_model_source: str) -> Dict[str, Any]: ) +class DictConfigurationService(ConfigurationService): + def __init__(self, data: Dict, name: str = "test"): + self._data = data + self._name = name + + def get_configuration(self) -> YamlValidator: + return PyYamlYamlModel(internal_datamodel=self._data, name=self._name, instantiated_through_read=True) + + def parse_model(model_data, start: datetime, end: datetime) -> dto.Asset: period = Period( start=start, @@ -296,10 +306,12 @@ def parse_model(model_data, start: datetime, end: datetime) -> dto.Asset: model_data[EcalcYamlKeywords.start] = period.start model_data[EcalcYamlKeywords.end] = period.end - configuration = PyYamlYamlModel(internal_datamodel=model_data, instantiated_through_read=True) - references = create_references(configuration, resources={}) - model_mapper = EcalcModelMapper(references=references, target_period=period) - return model_mapper.from_yaml_to_dto(configuration, name="test") + configuration_service = DictConfigurationService(model_data) + resource_service = DirectResourceService(resources={}) + model = YamlModel( + configuration_service=configuration_service, resource_service=resource_service, output_frequency=Frequency.NONE + ) + return model.dto class TestDatedModelFilter: diff --git a/src/tests/libecalc/input/test_file_io.py b/src/tests/libecalc/input/test_file_io.py index 0007aef733..58c3b1f823 100644 --- a/src/tests/libecalc/input/test_file_io.py +++ b/src/tests/libecalc/input/test_file_io.py @@ -10,7 +10,7 @@ from libecalc.fixtures.cases import input_file_examples from libecalc.infrastructure import file_io from libecalc.presentation.yaml import yaml_entities -from libecalc.presentation.yaml.model import YamlModel +from libecalc.presentation.yaml.model import FileResourceService from libecalc.presentation.yaml.yaml_entities import YamlTimeseriesType from libecalc.presentation.yaml.yaml_models.exceptions import DuplicateKeyError from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel @@ -307,10 +307,13 @@ def test_time_series_missing_headers(self): test = PyYamlYamlModel( internal_datamodel=time_series_yaml_text, + name="test", instantiated_through_read=True, ) with pytest.raises(EcalcError) as e: - YamlModel._read_resources(yaml_configuration=test, working_directory=Path(input_file_examples.__path__[0])) + FileResourceService._read_resources( + configuration=test, working_directory=Path(input_file_examples.__path__[0]) + ) assert str(e.value) == ( "Failed to read resource: Failed to read base_profile_missing_header_oil_prod.csv: " @@ -331,10 +334,13 @@ def test_facility_input_missing_headers(self): test = PyYamlYamlModel( internal_datamodel=time_series_yaml_text, + name="test", instantiated_through_read=True, ) with pytest.raises(EcalcError) as e: - YamlModel._read_resources(yaml_configuration=test, working_directory=Path(input_file_examples.__path__[0])) + FileResourceService._read_resources( + configuration=test, working_directory=Path(input_file_examples.__path__[0]) + ) assert str(e.value) == ( "Failed to read resource: Failed to read tabular_missing_header_fuel.csv: " diff --git a/src/tests/libecalc/input/test_model.py b/src/tests/libecalc/input/test_model.py deleted file mode 100644 index 1d9d8abada..0000000000 --- a/src/tests/libecalc/input/test_model.py +++ /dev/null @@ -1,9 +0,0 @@ -from libecalc.common.time_utils import Frequency -from libecalc.fixtures import YamlCase -from libecalc.presentation.yaml.model import YamlModel - - -class TestModel: - def test_parse_ecalc_model(self, all_energy_usage_models_yaml: YamlCase): - model = YamlModel(path=all_energy_usage_models_yaml.main_file_path, output_frequency=Frequency.NONE) - assert model diff --git a/src/tests/libecalc/input/test_parse_input.py b/src/tests/libecalc/input/test_parse_input.py deleted file mode 100644 index 8f94b8204a..0000000000 --- a/src/tests/libecalc/input/test_parse_input.py +++ /dev/null @@ -1,59 +0,0 @@ -from pathlib import Path -from typing import Optional - -from libecalc import dto -from libecalc.dto import VariablesMap -from libecalc.fixtures import DTOCase, YamlCase -from libecalc.presentation.yaml.mappers.variables_mapper import map_yaml_to_variables -from libecalc.presentation.yaml.parse_input import map_yaml_to_dto -from libecalc.presentation.yaml.yaml_entities import Resources, ResourceStream -from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel - - -def parse_input( - main_yaml: ResourceStream, - resources: Resources = None, - base_dir: Optional[Path] = None, -) -> (dto.Asset, Optional[VariablesMap]): - """The stream on top here does NOT expect timeseries as a part of the yaml, only - facility_types, models, installations, start_time, end_time. Timeseries are handled - separately. - - NOTE: This function is a "work-in-progress", and currently only used in order to - bootstrap tests and test current functionality in common. - :param base_dir: - :param main_yaml: - :param resources: - :return: - """ - configuration: PyYamlYamlModel = PyYamlYamlModel.read(main_yaml=main_yaml, enable_include=True, base_dir=base_dir) - model_dto = map_yaml_to_dto(configuration, resources, name=Path(main_yaml.name).stem) - - variables = map_yaml_to_variables(configuration, resources, result_options=dto.ResultOptions()) - - return ( - model_dto, - variables, - ) - - -class TestParseYaml: - def test_parse_input_with_all_energy_usage_models( - self, all_energy_usage_models_yaml: YamlCase, all_energy_usage_models_dto: DTOCase - ): - model_dto, variables = parse_input( - ResourceStream(stream=all_energy_usage_models_yaml.main_file, name="all_energy_usage_models.yaml"), - resources=all_energy_usage_models_yaml.resources, - ) - - assert model_dto.model_dump() == all_energy_usage_models_dto.ecalc_model.model_dump() - assert variables == all_energy_usage_models_dto.variables - - def test_parse_input_with_consumer_system_v2(self, consumer_system_v2_yaml, consumer_system_v2_dto_fixture): - model_dto, variables = parse_input( - ResourceStream(stream=consumer_system_v2_yaml.main_file, name="consumer_system_v2.yaml"), - resources=consumer_system_v2_yaml.resources, - ) - - assert model_dto.model_dump() == consumer_system_v2_dto_fixture.ecalc_model.model_dump() - assert variables == consumer_system_v2_dto_fixture.variables diff --git a/src/tests/libecalc/input/test_yaml_configuration.py b/src/tests/libecalc/input/test_yaml_configuration.py index 5aeeac5b4a..314bc55ef4 100644 --- a/src/tests/libecalc/input/test_yaml_configuration.py +++ b/src/tests/libecalc/input/test_yaml_configuration.py @@ -15,6 +15,7 @@ def test_invalid_timeseries_type(self): {EcalcYamlKeywords.name: "filepath.csv", EcalcYamlKeywords.type: "INVALID"} ] }, + name="test_case", instantiated_through_read=True, ) with pytest.raises(DataValidationError) as exc_info: @@ -35,7 +36,7 @@ def test_read_dates_from_dict(self): "FUELCONSUMERS": [{datetime(2020, 2, 17): "CONSUMER3"}], } } - configuration = PyYamlYamlModel(internal_datamodel=yaml_dict, instantiated_through_read=True) + configuration = PyYamlYamlModel(internal_datamodel=yaml_dict, name="test_case", instantiated_through_read=True) dates = configuration.dates assert len(dates) == 3 @@ -59,7 +60,7 @@ def test_read_chart_curve_files_from_models(self): ], } - configuration = PyYamlYamlModel(internal_datamodel=yaml_dict, instantiated_through_read=True) + configuration = PyYamlYamlModel(internal_datamodel=yaml_dict, name="test_case", instantiated_through_read=True) assert [ resource in configuration.facility_resource_names diff --git a/src/tests/libecalc/input/test_yaml_model.py b/src/tests/libecalc/input/test_yaml_model.py new file mode 100644 index 0000000000..094137b3fc --- /dev/null +++ b/src/tests/libecalc/input/test_yaml_model.py @@ -0,0 +1,49 @@ +from typing import Dict + +from libecalc.common.time_utils import Frequency +from libecalc.fixtures import DTOCase, YamlCase +from libecalc.presentation.yaml.model import FileConfigurationService, ResourceService, YamlModel +from libecalc.presentation.yaml.yaml_entities import Resource +from libecalc.presentation.yaml.yaml_models.yaml_model import YamlValidator + + +class DirectResourceService(ResourceService): + def __init__(self, resources: Dict[str, Resource]): + self._resources = resources + + def get_resources(self, configuration: YamlValidator) -> Dict[str, Resource]: + return self._resources + + +class TestParseYaml: + def test_parse_input_with_all_energy_usage_models( + self, all_energy_usage_models_yaml: YamlCase, all_energy_usage_models_dto: DTOCase + ): + """ + Make sure yaml and dto is consistent for all_energy_usage_models + """ + configuration_service = FileConfigurationService(configuration_path=all_energy_usage_models_yaml.main_file_path) + resource_service = DirectResourceService(resources=all_energy_usage_models_yaml.resources) + model = YamlModel( + configuration_service=configuration_service, + resource_service=resource_service, + output_frequency=Frequency.NONE, + ) + + assert model.dto.model_dump() == all_energy_usage_models_dto.ecalc_model.model_dump() + assert model.variables == all_energy_usage_models_dto.variables + + def test_parse_input_with_consumer_system_v2(self, consumer_system_v2_yaml, consumer_system_v2_dto_fixture): + """ + Make sure yaml and dto is consistent for consumer_system_v2 + """ + configuration_service = FileConfigurationService(configuration_path=consumer_system_v2_yaml.main_file_path) + resource_service = DirectResourceService(resources=consumer_system_v2_yaml.resources) + model = YamlModel( + configuration_service=configuration_service, + resource_service=resource_service, + output_frequency=Frequency.NONE, + ) + + assert model.dto.model_dump() == consumer_system_v2_dto_fixture.ecalc_model.model_dump() + assert model.variables == consumer_system_v2_dto_fixture.variables diff --git a/src/tests/libecalc/presentation/yaml/yaml_models/test_yaml_model_errors.py b/src/tests/libecalc/presentation/yaml/yaml_models/test_yaml_model_errors.py index 243aecbca0..ff89b382bb 100644 --- a/src/tests/libecalc/presentation/yaml/yaml_models/test_yaml_model_errors.py +++ b/src/tests/libecalc/presentation/yaml/yaml_models/test_yaml_model_errors.py @@ -4,7 +4,7 @@ from libecalc.presentation.yaml.yaml_entities import ResourceStream from libecalc.presentation.yaml.yaml_models.exceptions import YamlError -from libecalc.presentation.yaml.yaml_models.yaml_model import YamlModel, YamlModelType +from libecalc.presentation.yaml.yaml_models.yaml_model import ReaderType, YamlConfiguration invalid_models = [ ( @@ -31,9 +31,9 @@ @pytest.mark.parametrize("invalid_model,description", invalid_models) -@pytest.mark.parametrize("yaml_model_type", YamlModelType) -def test_invalid_models(invalid_model: str, description: str, yaml_model_type: YamlModelType): - yaml_reader = YamlModel.Builder.get_yaml_model(yaml_model_type) +@pytest.mark.parametrize("yaml_model_type", ReaderType) +def test_invalid_models(invalid_model: str, description: str, yaml_model_type: ReaderType): + yaml_reader = YamlConfiguration.Builder.get_yaml_reader(yaml_model_type) with pytest.raises(YamlError) as exc_info: yaml_reader.read(ResourceStream(name=description, stream=StringIO(invalid_model)))