diff --git a/docs/docs/changelog/next.md b/docs/docs/changelog/next.md index 4cb2be0581..3c25690abc 100644 --- a/docs/docs/changelog/next.md +++ b/docs/docs/changelog/next.md @@ -38,4 +38,6 @@ sidebar_position: -1002 - Economics has been removed. TAX, PRICE and QUOTA keywords will now give an error. - Misplaced keywords will now cause an error instead of being ignored. -- H2O is no longer supported in composition. Use 'water' instead. \ No newline at end of file +- H2O is no longer supported in composition. Use 'water' instead. + +- The CLI command `ecalc show results` has been removed. The reason being additional maintenance for something that did not seem to be used a lot. \ No newline at end of file diff --git a/src/ecalc_cli/commands/run.py b/src/ecalc_cli/commands/run.py index 01faadd41e..5173402080 100644 --- a/src/ecalc_cli/commands/run.py +++ b/src/ecalc_cli/commands/run.py @@ -7,7 +7,6 @@ import libecalc.version from ecalc_cli.errors import EcalcCLIError from ecalc_cli.infrastructure.file_resource_service import FileResourceService -from ecalc_cli.io.cache import Cache from ecalc_cli.io.output import ( write_flow_diagram, write_json, @@ -148,13 +147,6 @@ def run( run_info.end = datetime.now() - cache = Cache(user_specified_output_path=output_folder) - cache.write_run_info(run_info) - cache.write_results( - results=results_core, - component_dto=model.dto, - ) - output_prefix: Path = output_folder / name_prefix results_dto = get_asset_result(results_core) diff --git a/src/ecalc_cli/commands/show.py b/src/ecalc_cli/commands/show.py index 92900a0c2b..24bf9202f0 100644 --- a/src/ecalc_cli/commands/show.py +++ b/src/ecalc_cli/commands/show.py @@ -2,17 +2,7 @@ import typer -import libecalc.common.time_utils -import libecalc.version -from ecalc_cli.io.cache import Cache from ecalc_cli.io.output import write_output -from ecalc_cli.logger import logger -from ecalc_cli.types import DateFormat, Frequency -from libecalc.infrastructure.file_utils import ( - OutputFormat, - get_component_output, - get_result_output, -) from libecalc.presentation.yaml.yaml_entities import ResourceStream from libecalc.presentation.yaml.yaml_models.pyyaml_yaml_model import PyYamlYamlModel @@ -38,83 +28,3 @@ def show_yaml( ResourceStream(name=model_file.name, stream=model_file), base_dir=model_filepath.parent ) write_output(read_model, output_file) - - -@app.command("results") -def show_results( - component_name: str = typer.Option( - None, - "-n", - "--name", - help="Filter the results to only show the component with this name", - ), - output_format: OutputFormat = typer.Option( - OutputFormat.JSON.value, - "--output-format", - help="Show the data in this format.", - ), - output_file: Path = typer.Option( - None, - "--file", - help="Write the data to a file with the specified name.", - ), - output_folder: Path = typer.Option( - Path.cwd(), - "--output-folder", - help="Output folder. Defaults to current working directory", - show_default=False, - ), - detailed_output: bool = typer.Option( - False, - "--detailed-output", - help="Output detailed output." " When False you will get basic energy usage and emissions results", - ), - date_format_option: DateFormat = typer.Option( - DateFormat.ISO_8601.value, - "--date-format-option", - help='Date format option. 0: "YYYY-MM-DD HH:MM:SS" (Accepted variant of ISO8601), 1: "YYYYMMDD HH:MM:SS" (ISO8601), 2: "DD.MM.YYYY HH:MM:SS". Default 0 (ISO 8601)', - ), - output_frequency: Frequency = typer.Option( - libecalc.common.time_utils.Frequency.NONE.name, - "--output-frequency", - "-f", - help="Frequency of output. Options are DAY, MONTH, YEAR. If not specified, it will give" - " time steps equal to the union of all input given with INFLUENCE_TIME_VECTOR set to True." - " Down-sampling the result may lead to loss of data, and rates such as MW may not add up to cumulative values", - ), -): - """Show results. You need to run eCalcâ„¢ before this will be available.""" - cache = Cache(user_specified_output_path=output_folder) - results = cache.load_results() - run_info = cache.load_run_info() - - if run_info.version != libecalc.version.current_version(): - logger.warning( - f"Your version of eCalcâ„¢ '{libecalc.version.current_version()}' is different to the one used to create the results '{run_info.version}'." - ) - - if output_frequency != Frequency.NONE: - results_resampled = results.resample(libecalc.common.time_utils.Frequency[output_frequency.name]) - else: - results_resampled = results.model_copy() - - component_name = component_name - - if component_name is None: - # Default to full result if no specified component. - text = get_result_output( - results_resampled, - output_format=output_format, - simple_output=not detailed_output, - date_format_option=int(date_format_option.value), - ) - else: - text = get_component_output( - results_resampled, - component_name, - output_format=output_format, - simple_output=not detailed_output, - date_format_option=int(date_format_option.value), - ) - - write_output(text, output_file) diff --git a/src/ecalc_cli/io/cache.py b/src/ecalc_cli/io/cache.py deleted file mode 100644 index 4ec440f260..0000000000 --- a/src/ecalc_cli/io/cache.py +++ /dev/null @@ -1,107 +0,0 @@ -from dataclasses import dataclass, field -from pathlib import Path - -from ecalc_cli.logger import logger -from libecalc.application.graph_result import EnergyCalculatorResult, GraphResult -from libecalc.common.run_info import RunInfo -from libecalc.dto import Asset -from libecalc.dto.base import EcalcBaseModel -from libecalc.presentation.json_result.mapper import get_asset_result -from libecalc.presentation.json_result.result import EcalcModelResult - - -class CacheData(EcalcBaseModel): - """Data model for content in cache.""" - - component_dto: Asset - results: EnergyCalculatorResult - - -@dataclass -class Cache: - """Data class for CLI cache, storing model, results and run info.""" - - user_specified_output_path: Path - cache_path: Path = field(init=False) - results_path: Path = field(init=False) - run_info_path: Path = field(init=False) - - def __post_init__(self): - self.cache_path = self.user_specified_output_path / ".ecalc" - self.cache_path.mkdir(mode=0o770, exist_ok=True) - self.results_path = self.cache_path / "results.json" - self.run_info_path = self.cache_path / "run_info.json" - - def write_results(self, results: GraphResult, component_dto: Asset): - """Write results to cache. - - Args: - results: Model results - component_dto: Model used - - Returns: - - """ - logger.info(f"Writing results to cache '{self.cache_path}'.") - self.results_path.touch(mode=0o660, exist_ok=True) - cache_data = CacheData( - results=results.get_results(), - component_dto=component_dto, - ) - self.results_path.write_text(cache_data.model_dump_json()) - - def write_run_info(self, run_info: RunInfo): - """Write meta information about the run to the cache. - - Args: - run_info: A data model containing meta data of a eCalc run - - Returns: - - """ - logger.info(f"Writing run info to cache '{self.cache_path}'.") - self.run_info_path.touch(mode=0o660, exist_ok=True) - self.run_info_path.write_text(run_info.model_dump_json()) - - def load_run_info(self) -> RunInfo: - """Load metadata about run from cache. - - Returns: - Cached metadata about a eCalc run - - Raises: - ValueError: If the run info filepath is not set. - - """ - if not self.run_info_path.is_file(): - msg = "Could not find run info in this directory. Run the model again to generate results." - logger.error(msg) - raise ValueError(msg) - - return RunInfo.model_validate_json(self.run_info_path.read_text()) - - def load_results(self) -> EcalcModelResult: - """Load cached results from an ecalc run. - - Returns: - Ecalc run results for a model - - Raises: - ValueError: If the results path is not set - - """ - if not self.results_path.is_file(): - msg = "Could not find results in this directory, make sure you run 'ecalc show' from the output directory of 'ecalc run' (or specify --outputfolder). Run the model again if no output directory exists." - raise ValueError(msg) - cached_text = self.results_path.read_text() - cache_data = CacheData.model_validate_json(cached_text) - graph = cache_data.component_dto.get_graph() - - return get_asset_result( - GraphResult( - graph=graph, - emission_results=cache_data.results.emission_results, - consumer_results=cache_data.results.consumer_results, - variables_map=cache_data.results.variables_map, - ) - ) diff --git a/src/libecalc/infrastructure/file_utils.py b/src/libecalc/infrastructure/file_utils.py index 633e7975f7..8e576e7225 100644 --- a/src/libecalc/infrastructure/file_utils.py +++ b/src/libecalc/infrastructure/file_utils.py @@ -1,5 +1,4 @@ import enum -import sys from datetime import datetime from typing import Any, Callable, Optional, Union @@ -129,54 +128,3 @@ def get_result_output( raise ValueError( f"Invalid output format. Expected {OutputFormat.CSV} or {OutputFormat.JSON}, got '{output_format}'" ) - - -def get_component_output( - results: EcalcModelResult, - component_name: str, - output_format: OutputFormat, - simple_output: bool, - date_format_option: int, -) -> str: - """Get eCalc output for a single component by name - - Args: - results: Complete from eCalc model - component_name: Name of component to output results from - output_format: Format of output file, CSV and JSON is currently supported - simple_output: If true, will provide a simplified output format. Only supported for json format - date_format_option: - - Returns: - - """ - components = [component for component in results.components if component.name == component_name] - - if len(components) == 0: - msg = f"Unable to find component with name '{component_name}'" - logger.error(msg) - raise ValueError(msg) - elif len(components) == 1: - component = components[0] - else: - print("Several components match this name\n") - format_str = "{:<5} {:<18} {:<10}" - print(format_str.format("index", "type", "name")) - for index, component in enumerate(components): - print(format_str.format(index, component.componentType.value, component.name)) - print() - selected_component_index = input("Enter the index of the component you want to select (q to quit): ") - if selected_component_index == "q": - sys.exit(0) - - component = components[int(selected_component_index)] - - if output_format == OutputFormat.JSON: - return to_json(component, simple_output=simple_output, date_format_option=date_format_option) - elif output_format == OutputFormat.CSV: - df = component.to_dataframe() - return dataframe_to_csv(df, date_format=DateTimeFormats.get_format(date_format_option)) - else: - raise ValueError( - f"Invalid output format. Expected {OutputFormat.CSV} or {OutputFormat.JSON}, got '{output_format}'" - ) diff --git a/src/tests/ecalc_cli/test_app.py b/src/tests/ecalc_cli/test_app.py index b394a6e16a..e56db56185 100644 --- a/src/tests/ecalc_cli/test_app.py +++ b/src/tests/ecalc_cli/test_app.py @@ -1,4 +1,3 @@ -import csv import json from datetime import date from io import StringIO @@ -12,7 +11,6 @@ from typer.testing import CliRunner from ecalc_cli import main -from ecalc_cli.commands import show from libecalc.common.errors.exceptions import EcalcError from libecalc.common.run_info import RunInfo from libecalc.dto.utils.validators import COMPONENT_NAME_ALLOWED_CHARS @@ -431,229 +429,6 @@ def test_save_logs(self, simple_yaml_path, tmp_path, snapshot): assert getsize(log_file_actual_path) > 0 -class TestShowResultsCommand: - @pytest.mark.snapshot - def test_component_name_json(self, simple_run, monkeypatch, snapshot, tmp_path): - monkeypatch.chdir(tmp_path) - - runner.invoke( - show.app, - [ - "results", - "--name", - "Sea water injection pump", - "--output-format", - "json", - "--detailed-output", - "--output-folder", - str(simple_run.output_folder), - "--file", - "./waterinj.json", - ], - catch_exceptions=False, - ) - - with open("./waterinj.json") as waterinj_result: - waterinj_data = json.load(waterinj_result) - snapshot.assert_match(json.dumps(waterinj_data, sort_keys=True, indent=4), snapshot_name="waterinj.json") - - @pytest.mark.snapshot - def test_component_name_json_stdout(self, simple_run, monkeypatch, snapshot): - result = runner.invoke( - show.app, - [ - "results", - "--name", - "Sea water injection pump", - "--output-format", - "json", - "--detailed-output", - "--output-folder", - str(simple_run.output_folder), - ], - catch_exceptions=False, - ) - output_text = result.stdout - waterinj_data = json.loads(output_text) - snapshot.assert_match(json.dumps(waterinj_data, sort_keys=True, indent=4), snapshot_name="waterinj.json") - - @pytest.mark.snapshot - def test_component_name_csv(self, simple_run, monkeypatch, snapshot, tmp_path): - monkeypatch.chdir(tmp_path) - - runner.invoke( - show.app, - [ - "results", - "--name", - "Sea water injection pump", - "--output-format", - "csv", - "--detailed-output", - "--output-folder", - str(simple_run.output_folder), - "--file", - "./waterinj.csv", - ], - catch_exceptions=False, - ) - - with open("./waterinj.csv") as waterinj_result: - waterinj_data = waterinj_result.read() - snapshot.assert_match(waterinj_data, snapshot_name="waterinj.csv") - - @pytest.mark.snapshot - def test_full_csv(self, simple_run, monkeypatch, snapshot): - result = runner.invoke( - show.app, - [ - "results", - "--output-format", - "csv", - "--output-folder", - str(simple_run.output_folder), - ], - catch_exceptions=False, - ) - - output_text = result.stdout - snapshot.assert_match(output_text, snapshot_name="results.csv") - - @pytest.mark.snapshot - def test_csv_resampled(self, simple_run, monkeypatch, snapshot): - result = runner.invoke( - show.app, - [ - "results", - "--output-format", - "csv", - "--output-folder", - str(simple_run.output_folder), - "--output-frequency", - "YEAR", - ], - catch_exceptions=False, - ) - - output_text = result.stdout - snapshot.assert_match(output_text, snapshot_name="results_resampled.csv") - - @pytest.mark.snapshot - def test_json_resampled(self, simple_run, monkeypatch, snapshot): - """ - TEST REASON and SCOPE: That resampled json follows json schema - - Testing the resample json from a representative model in order to make - sure that the resampled json is correctly changing the json according to - our schema. Not testing this may easily lead to json that violates the schema - when e.g. new data types are introduced. This test should make sure we have - control of that. This was added after a bug in resampling was found, where - resampling was creating different json than without resampling, and that - the json was invalid. - - Args: - simple_run: - monkeypatch: - snapshot: - - Returns: - - """ - result = runner.invoke( - show.app, - [ - "results", - "--output-format", - "json", - "--output-folder", - str(simple_run.output_folder), - "--output-frequency", - "YEAR", - ], - catch_exceptions=False, - ) - - output_text = result.stdout - snapshot.assert_match(output_text, snapshot_name="results_resampled.json") - - def test_json_custom_date_format(self, simple_run, monkeypatch, snapshot): - result = runner.invoke( - show.app, - [ - "results", - "--output-format", - "json", - "--detailed-output", - "--output-folder", - str(simple_run.output_folder), - "--date-format-option", - "2", - ], - catch_exceptions=False, - ) - - output_text = result.stdout - data = json.loads(output_text) - assert data["component_result"]["timesteps"] == [ - "01.01.2020 00:00:00", - "01.01.2021 00:00:00", - "01.01.2022 00:00:00", - "01.01.2023 00:00:00", - "01.01.2024 00:00:00", - "01.12.2024 00:00:00", - "01.01.2026 00:00:00", - "01.01.2027 00:00:00", - "01.01.2028 00:00:00", - "01.01.2029 00:00:00", - "01.01.2030 00:00:00", - "01.01.2031 00:00:00", - ] - - def test_csv_custom_date_format(self, simple_run, monkeypatch, snapshot): - result = runner.invoke( - show.app, - [ - "results", - "--output-format", - "csv", - "--output-folder", - str(simple_run.output_folder), - "--date-format-option", - "2", - ], - catch_exceptions=False, - ) - - output_text = result.stdout - data = csv.reader(StringIO(output_text), delimiter=",") - timesteps = [row[0] for row in list(data)[1:]] - assert timesteps == [ - "01.01.2020 00:00:00", - "01.01.2021 00:00:00", - "01.01.2022 00:00:00", - "01.01.2023 00:00:00", - "01.01.2024 00:00:00", - "01.12.2024 00:00:00", - "01.01.2026 00:00:00", - "01.01.2027 00:00:00", - "01.01.2028 00:00:00", - "01.01.2029 00:00:00", - "01.01.2030 00:00:00", - "01.01.2031 00:00:00", - ] - - @pytest.mark.snapshot - def test_full_simplified_json(self, simple_run, monkeypatch, snapshot): - result = runner.invoke( - show.app, - ["results", "--output-format", "json", "--output-folder", str(simple_run.output_folder)], - catch_exceptions=False, - ) - - output_text = result.stdout - snapshot.assert_match(output_text, snapshot_name="results.json") - - class TestYamlFile: def test_yaml_file_error(self): """ diff --git a/src/tests/ecalc_cli/test_cache.py b/src/tests/ecalc_cli/test_cache.py deleted file mode 100644 index d1f2dd68d9..0000000000 --- a/src/tests/ecalc_cli/test_cache.py +++ /dev/null @@ -1,39 +0,0 @@ -import json -from pathlib import Path - -import pytest - -from ecalc_cli.io.cache import Cache, CacheData -from libecalc.presentation.json_result.result import EcalcModelResult - - -@pytest.fixture -def all_energy_usage_models_cache_fixture(request, all_energy_usage_models_dto, tmp_path): - results_path = ( - Path(request.path).parent.parent - / "libecalc" - / "integration" - / "snapshots" - / "test_all_energy_usage_models" - / "test_all_results" - / "all_energy_usage_models_v3.json" - ) - with results_path.open() as source: - cache_data = CacheData( - results=json.load(source), - component_dto=all_energy_usage_models_dto.ecalc_model, - ) - cache = Cache(user_specified_output_path=tmp_path) - cache.results_path.touch(mode=0o660, exist_ok=True) - text = cache_data.model_dump_json() - cache_data.model_dump() - cache.results_path.write_text(text) - return cache - - -class TestLoadResults: - def test_load_all_energy_usage_models_snapshot(self, all_energy_usage_models_cache_fixture): - """ - Test created to make sure get_asset_result works as expected - """ - assert isinstance(all_energy_usage_models_cache_fixture.load_results(), EcalcModelResult)