diff --git a/src/r2x/parser/plexos.py b/src/r2x/parser/plexos.py index eeee647a..3bb58b21 100644 --- a/src/r2x/parser/plexos.py +++ b/src/r2x/parser/plexos.py @@ -56,6 +56,7 @@ reconcile_timeseries, ) from .plexos_utils import ( + find_xml, DATAFILE_COLUMNS, PLEXOS_ACTION_MAP, filter_property_dates, @@ -168,20 +169,20 @@ def __init__(self, *args, xml_file: str | None = None, **kwargs) -> None: # TODO(pesap): Rename exceptions to include R2X # https://github.com/NREL/R2X/issues/5 # R2X needs at least one of this maps defined to correctly work. - if ( - not self.fuel_map - and not self.device_map - and not self.device_match_string - and not self.category_map - ): - msg = ( - "Neither `plexos_fuel_map` or `plexos_device_map` or `device_match_string` was provided. " - "To fix, provide any of the mappings." - ) + one_required = ["fuel_map", "device_map", "device_match_string", "category_map"] + if all(getattr(self, one_req, {}) == {} for one_req in one_required): + msg = f'At least one of {", or ".join(one_required)} is required to initialize PlexosParser' raise ParserError(msg) # Populate databse from XML file. - xml_file = xml_file or self.run_folder / self.config.fmap["xml_file"]["fname"] + # If xml file is not specified, check user_dict["fmap"]["xml_file"] or use + # only xml file in project directory + if xml_file is None: + xml_file = self.config.fmap.get("xml_file", {}).get("fname", None) + xml_file = xml_file or str(find_xml(self.run_folder)) + + xml_file = str(self.run_folder / xml_file) + self.db = PlexosSQLite(xml_fname=xml_file) # Extract scenario data diff --git a/src/r2x/parser/plexos_utils.py b/src/r2x/parser/plexos_utils.py index 142f684f..f49e25bb 100644 --- a/src/r2x/parser/plexos_utils.py +++ b/src/r2x/parser/plexos_utils.py @@ -6,6 +6,8 @@ from enum import Enum from typing import Any from collections.abc import Sequence +from os import PathLike +from pathlib import Path from numpy._typing import NDArray import pint @@ -465,3 +467,42 @@ def time_slice_handler( raise NotImplementedError return month_datetime_series + + +def find_xml(directory: PathLike): + """ + Parameters + ---------- + + directory: Pathlike + directory to search in for an xml file + + Raises + ------ + + FileNotFoundError: + if no xml file is in + OR more than one xml files are in + + NotADirectoryError: + if is not a directory + + Returns + ------- + ret: Path + Path to a single xml file + + """ + directory = Path(directory) + if not directory.is_dir(): + raise NotADirectoryError(f"Can't search {directory}, not a directory") + + xml_files = list(directory.glob("*.xml")) + + if len(xml_files) == 0: + raise FileNotFoundError(f"No xml file in {directory}") + + if len(xml_files) > 1: + raise FileNotFoundError(f"More than one xml file in {directory}") + + return xml_files[0] diff --git a/tests/test_parser_utils.py b/tests/test_parser_utils.py index 91b34308..7e80e74a 100644 --- a/tests/test_parser_utils.py +++ b/tests/test_parser_utils.py @@ -4,6 +4,7 @@ from polars.testing import assert_frame_equal from tempfile import NamedTemporaryFile from r2x.parser.handler import csv_handler +from r2x.parser.plexos_utils import find_xml @pytest.fixture @@ -33,3 +34,35 @@ def test_csv_handler_basic(temp_csv_file): with pytest.raises(FileNotFoundError): _ = csv_handler(Path("non_existent_file.csv")) + + +@pytest.fixture +def basic_xml(): + data = """ + + 1 + 1 + 0 + - + + """ + return data + + +def test_find_xml(tmp_path: Path, basic_xml: str): + # Case 1: no xml files in tmp_path + with pytest.raises(FileNotFoundError): + find_xml(tmp_path) + + # Case 2: 1 xml file in tmp_path + xml_file_1 = tmp_path / "file1.xml" + xml_file_1.write_text(basic_xml) + + assert xml_file_1 == find_xml(tmp_path) + + # Case 3: 2 xml files in tmp_path + xml_file_2 = tmp_path / "file2.xml" + xml_file_2.write_text(basic_xml) + + with pytest.raises(FileNotFoundError): + find_xml(tmp_path) diff --git a/tests/test_plexos_parser.py b/tests/test_plexos_parser.py index 20087235..35add137 100644 --- a/tests/test_plexos_parser.py +++ b/tests/test_plexos_parser.py @@ -3,9 +3,9 @@ from plexosdb import XMLHandler from r2x.api import System from r2x.config import Scenario -from r2x.exceptions import ParserError from r2x.parser.handler import get_parser_data from r2x.parser.plexos import PlexosParser +from r2x.exceptions import ParserError DB_NAME = "2-bus_example.xml" MODEL_NAME = "main_model" @@ -58,30 +58,19 @@ def test_build_system(plexos_parser_instance): assert isinstance(system, System) -def test_raise_if_no_map_provided(tmp_path, data_folder): - scenario = Scenario.from_kwargs( - name="plexos_test", - input_model="plexos", - run_folder=data_folder, - output_folder=tmp_path, - solve_year=2035, - model=MODEL_NAME, - weather_year=2012, - fmap={"xml_file": {"fname": DB_NAME}}, - ) - with pytest.raises(ParserError): - _ = get_parser_data(scenario, parser_class=PlexosParser) - - def test_parser_system(pjm_scenario): plexos_category_map = { "thermal": {"fuel": "GAS", "type": "CC"}, "solar": {"fuel": "SUN", "type": "WT"}, "wind": {"fuel": "WIND", "type": "PV"}, } - pjm_scenario.defaults["plexos_category_map"] = plexos_category_map pjm_scenario.model = "model_2012" + with pytest.raises(ParserError): + parser = get_parser_data(pjm_scenario, parser_class=PlexosParser) + + pjm_scenario.defaults["plexos_category_map"] = plexos_category_map + parser = get_parser_data(pjm_scenario, parser_class=PlexosParser) system = parser.build_system() assert isinstance(system, System)