diff --git a/examples/ready-to-run/processing_example.py b/examples/ready-to-run/processing_example.py index 8661a60..1d78f87 100644 --- a/examples/ready-to-run/processing_example.py +++ b/examples/ready-to-run/processing_example.py @@ -19,42 +19,48 @@ def processing_of_monthly_data(simulation: api.Simulation): # modify label for the y axis ax.set_ylabel("Power [kW]") - # make label fit inside your plot + + # This is also done by api.save_plot, but if you want to see your plot before saving it. + # For example with _plt.show(). Calling this function is required, to make the plot look as expected. _plt.tight_layout() # create plots folder inside simulation directory and save plot in configured formats - api.save_plot(fig, simulation.path, "monthly-bar-chart") + api.export_plots_in_configured_formats( + fig, simulation.path, "monthly-bar-chart" + ) def processing_of_hourly_data(simulation: api.Simulation): # create line plot using hourly data fig, ax = api.line_plot(simulation.hourly, ["QSrc1TIn", "QSrc1TOut"]) - # modify label for the y axis + # Here you can modify anything to do with your plot, according to the matplotlib.pyplot standard ax.set_ylabel("Temperature [°C]") # make label fit inside your plot _plt.tight_layout() # create plots folder inside simulation directory and save plot in configured formats - api.save_plot(fig, simulation.path, "hourly-line-plot") + api.export_plots_in_configured_formats( + fig, simulation.path, "hourly-line-plot" + ) def processing_for_histogram(simulation: api.Simulation): # create histogram using hourly data fig, _ = api.histogram(simulation.hourly, ["QSrc1TIn"]) - # create plots folder inside simulation directory and save plot in configured formats - api.save_plot(fig, simulation.path, "histogram") + # if you don't want to export your plot, you can also just show it by calling this function + _plt.show() if __name__ == "__main__": # This is the entry point to your script. - # In here you can decide how you would like to run your processing steps. + # Here, you can decide how you would like to run your processing steps. # In the example below we run both processing steps on a whole result set. # Or a single processing step on a single simulation. # Make sure to remove the lines you do not want to run. - # bundle the scenarios into a list + # bundle the steps into a list processing_scenarios = [ processing_of_monthly_data, processing_of_hourly_data, diff --git a/pytrnsys_process/api.py b/pytrnsys_process/api.py index 5732fec..5f55c86 100644 --- a/pytrnsys_process/api.py +++ b/pytrnsys_process/api.py @@ -21,9 +21,13 @@ ) from pytrnsys_process.process_sim.process_sim import Simulation +# ============================================================ # this lives here, because it needs to be available everywhere from pytrnsys_process.settings import settings -from pytrnsys_process.utils import save_plot + +# ============================================================ + +from pytrnsys_process.utils import export_plots_in_configured_formats __all__ = [ "line_plot", @@ -35,6 +39,6 @@ "process_single_simulation", "process_whole_result_set", "Simulation", - "save_plot", + "export_plots_in_configured_formats", "settings", ] diff --git a/pytrnsys_process/converter.py b/pytrnsys_process/file_converter.py similarity index 98% rename from pytrnsys_process/converter.py rename to pytrnsys_process/file_converter.py index c9e971c..597f19e 100644 --- a/pytrnsys_process/converter.py +++ b/pytrnsys_process/file_converter.py @@ -55,7 +55,7 @@ def convert_sim_results_to_csv( ) for input_file in input_files: - if not input_file.is_file(): + if input_file.is_dir(): continue if ftd.has_pattern(input_file, const.FileType.MONTHLY): @@ -130,12 +130,12 @@ def _refactor_filename( filename: str, patterns: list[str], prefix: str ) -> str: """Process filename by removing patterns and adding appropriate prefix. - + Args: filename: The original filename to process patterns: List of regex patterns to remove from filename prefix: Prefix to add to the processed filename - + Returns: The processed filename with patterns removed and prefix added """ diff --git a/pytrnsys_process/logger.py b/pytrnsys_process/logger.py index 8a4431a..49a4506 100644 --- a/pytrnsys_process/logger.py +++ b/pytrnsys_process/logger.py @@ -1,3 +1,18 @@ +""" +Configures logging for the pytrnsys_process package with three outputs: +1. Console output (INFO level) - Shows basic messages without stacktrace +2. Regular log file (INFO level) - Logs to pytrnsys_process.log without stacktrace +3. Debug log file (DEBUG level) - Logs to pytrnsys_process_debug.log with full stacktrace + +The logging setup includes custom formatting for each handler and uses a TracebackInfoFilter +to control stacktrace visibility in different outputs. The main logger is configured at DEBUG +level to capture all logging events, while individual handlers control what gets displayed +in each output. + +All handlers use the same log record. +Once the log record is modified and anything removed from it, will not be available in the other handlers. +""" + import logging import sys diff --git a/pytrnsys_process/plotting/plot_wrappers.py b/pytrnsys_process/plotting/plot_wrappers.py index 6b9d505..0c8d4ef 100644 --- a/pytrnsys_process/plotting/plot_wrappers.py +++ b/pytrnsys_process/plotting/plot_wrappers.py @@ -32,12 +32,19 @@ def line_plot( >>> >>> def create_line_plot(simulation: api.Simulation): >>> fig, ax = api.line_plot(simulation.hourly, columns=['var1', 'var2']) - Customize the plot using the returned axes object: + >>> # Customize the plot using the returned axes object: >>> ax.set_xlabel('Time') >>> ax.set_ylabel('Value') >>> ax.set_title('My Plot') >>> ax.grid(True) >>> _plt.show() + >>> + >>> # run the single scenario on a single simulation + >>> api.process_single_simulation( + >>> _pl.Path("data/results/complete-0-SnkScale0.6000-StoreScale8"), + >>> create_line_plot, + >>> ) + For additional customization options, refer to: - Matplotlib documentation: https://matplotlib.org/stable/api/ - Pandas plotting: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html @@ -70,12 +77,19 @@ def bar_chart( Example: >>> from pytrnsys_process import api - >>> fig, ax = api.bar_chart(simulation.monthly, columns=['var1', 'var2']) - Customize the plot using the returned axes object: - >>> ax.set_xlabel('Time') - >>> ax.set_ylabel('Value') - >>> ax.set_title('My Plot') - >>> ax.grid(True) + >>> def create_bar_chart(simulation: api.Simulation): + >>> fig, ax = api.bar_chart(simulation.monthly, columns=['var1', 'var2']) + >>> # Customize the plot using the returned axes object: + >>> ax.set_xlabel('Time') + >>> ax.set_ylabel('Value') + >>> ax.set_title('My Plot') + >>> ax.grid(True) + >>> + >>> # run the single scenario on a single simulation + >>> api.process_single_simulation( + >>> _pl.Path("data/results/complete-0-SnkScale0.6000-StoreScale8"), + >>> create_bar_chart, + >>> ) For additional customization options, refer to: - Matplotlib documentation: https://matplotlib.org/stable/api/ @@ -109,12 +123,19 @@ def stacked_bar_chart( Example: >>> from pytrnsys_process import api - >>> fig, ax = api.stacked_bar_chart(simulation.monthly, columns=['var1', 'var2', 'var3']) - Customize the plot using the returned axes object: - >>> ax.set_xlabel('Time') - >>> ax.set_ylabel('Value') - >>> ax.set_title('My Plot') - >>> ax.grid(True) + >>> def create_stacked_bar_chart(simulation: api.Simulation): + >>> fig, ax = api.stacked_bar_chart(simulation.monthly, columns=['var1', 'var2', 'var3']) + >>> # Customize the plot using the returned axes object: + >>> ax.set_xlabel('Time') + >>> ax.set_ylabel('Value') + >>> ax.set_title('My Plot') + >>> ax.grid(True) + >>> + >>> # run the single scenario on a single simulation + >>> api.process_single_simulation( + >>> _pl.Path("data/results/complete-0-SnkScale0.6000-StoreScale8"), + >>> create_stacked_bar_chart, + >>> ) For additional customization options, refer to: - Matplotlib documentation: https://matplotlib.org/stable/api/ @@ -148,12 +169,19 @@ def histogram( Example: >>> from pytrnsys_process import api - >>> fig, ax = api.histogram(simulation.hourly, columns=['var1', 'var2']) - Customize the plot using the returned axes object: - >>> ax.set_xlabel('Value') - >>> ax.set_ylabel('Frequency') - >>> ax.set_title('My Histogram') - >>> ax.grid(True) + >>> def create_histogram(simulation: api.Simulation): + >>> fig, ax = api.histogram(simulation.hourly, columns=['var1', 'var2']) + >>> # Customize the plot using the returned axes object: + >>> ax.set_xlabel('Value') + >>> ax.set_ylabel('Frequency') + >>> ax.set_title('My Histogram') + >>> ax.grid(True) + >>> + >>> # run the single scenario on a single simulation + >>> api.process_single_simulation( + >>> _pl.Path("data/results/complete-0-SnkScale0.6000-StoreScale8"), + >>> create_histogram, + >>> ) For additional customization options, refer to: - Matplotlib documentation: https://matplotlib.org/stable/api/ @@ -190,17 +218,24 @@ def scatter_plot( Example: >>> from pytrnsys_process import api - >>> fig, ax = api.scatter_plot( - ... simulation.hourly, - ... columns=["var1", "var2", "var3"], - ... x_column="var1", - ... y_column="var2", - ... ) - Customize the plot using the returned axes object: - >>> ax.set_xlabel('Time') - >>> ax.set_ylabel('Value') - >>> ax.set_title('My Scatter Plot') - >>> ax.grid(True) + >>> def create_scatter_plot(simulation: api.Simulation): + >>> fig, ax = api.scatter_plot( + ... simulation.hourly, + ... columns=["var1", "var2", "var3"], + ... x_column="var1", + ... y_column="var2", + ... ) + >>> # Customize the plot using the returned axes object: + >>> ax.set_xlabel('Time') + >>> ax.set_ylabel('Value') + >>> ax.set_title('My Scatter Plot') + >>> ax.grid(True) + >>> + >>> # run the single scenario on a single simulation + >>> api.process_single_simulation( + >>> _pl.Path("data/results/complete-0-SnkScale0.6000-StoreScale8"), + >>> create_scatter_plot, + >>> ) For additional customization options, refer to: - Matplotlib documentation: https://matplotlib.org/stable/api/ diff --git a/pytrnsys_process/plotting/plotters.py b/pytrnsys_process/plotting/plotters.py index 50b7bc1..cd209ed 100644 --- a/pytrnsys_process/plotting/plotters.py +++ b/pytrnsys_process/plotting/plotters.py @@ -15,6 +15,7 @@ # TODO: deal with legends (curve names, fonts, colors, linestyles) # pylint: disable=fixme # TODO: clean up old stuff by refactoring # pylint: disable=fixme # TODO: make issue for docstrings of plotting # pylint: disable=fixme +# TODO: Add colormap support # pylint: disable=fixme @dataclass @@ -113,32 +114,6 @@ def _do_plot( return fig, ax - # TODO: Add colormap support # pylint: disable=fixme - # def _do_plot( - # self, - # df: _pd.DataFrame, - # columns: list[str], - # use_legend: bool = True, - # size: tuple[float, float] = const.PlotSizes.A4.value, - # **kwargs: _tp.Any, - # ) -> _plt.Figure: - # """The matplot date formatter does not work when using df.plot func. - # This is an example to plot a stacked bar chart without df.plot""" - # fig, ax = _plt.subplots(figsize=size) - # x = _np.arange(len(df.index)) - # bottom = _np.zeros(len(df.index)) - # for col in columns: - # ax.bar(x, df[col], label=col, bottom=bottom, width=0.35) - # bottom += df[col] - # if use_legend: - # ax.legend() - # ax.set_xticks(x) - # ax.set_xticklabels( - # _pd.to_datetime(df.index).strftime(self.DATE_FORMAT) - # ) - # self.configure(ax) - # return fig - # TODO Idea for what an energy balance plot method could look like # pylint: disable=fixme @staticmethod def create_energy_balance_monthly( @@ -237,6 +212,8 @@ def _do_plot( ) -> tuple[_plt.Figure, _plt.Axes]: fig, ax = _plt.subplots(figsize=size) # TODO: cleanup the other Plotters to remove the stringy dictionary. - df[columns].plot.scatter(colormap=self.COLOR_MAP, legend=use_legend, ax=ax, **kwargs) + df[columns].plot.scatter( + colormap=self.COLOR_MAP, legend=use_legend, ax=ax, **kwargs + ) ax = self.configure(ax) return fig, ax diff --git a/pytrnsys_process/process_sim/process_sim.py b/pytrnsys_process/process_sim/process_sim.py index 50a1295..17926df 100644 --- a/pytrnsys_process/process_sim/process_sim.py +++ b/pytrnsys_process/process_sim/process_sim.py @@ -43,6 +43,8 @@ class Simulation: def process_sim( sim_files: _abc.Sequence[_pl.Path], sim_folder: _pl.Path ) -> Simulation: + # Used to store the array of dataframes for each file type. + # Later used to concatenate all into one dataframe and saving as Sim object simulation_data_collector = _SimulationDataCollector() for sim_file in sim_files: try: @@ -192,25 +194,16 @@ def _process_file( def _merge_dataframes_into_simulation( simulation_data_collector: _SimulationDataCollector, sim_folder: _pl.Path ) -> Simulation: - monthly_df = ( - handle_duplicate_columns( - _pd.concat(simulation_data_collector.monthly, axis=1) - ) - if simulation_data_collector.monthly - else _pd.DataFrame() - ) - hourly_df = ( - handle_duplicate_columns( - _pd.concat(simulation_data_collector.hourly, axis=1) - ) - if simulation_data_collector.hourly - else _pd.DataFrame() - ) - timestep_df = ( - handle_duplicate_columns( - _pd.concat(simulation_data_collector.step, axis=1) - ) - if simulation_data_collector.step - else _pd.DataFrame() - ) + + monthly_df = get_df_without_duplicates(simulation_data_collector.monthly) + hourly_df = get_df_without_duplicates(simulation_data_collector.hourly) + timestep_df = get_df_without_duplicates(simulation_data_collector.step) + return Simulation(sim_folder, monthly_df, hourly_df, timestep_df) + + +def get_df_without_duplicates(dfs: _abc.Sequence[_pd.DataFrame]): + if len(dfs) > 0: + return handle_duplicate_columns(_pd.concat(dfs, axis=1)) + + return _pd.DataFrame() diff --git a/pytrnsys_process/settings.py b/pytrnsys_process/settings.py index 22987bf..75cf8a7 100644 --- a/pytrnsys_process/settings.py +++ b/pytrnsys_process/settings.py @@ -28,7 +28,7 @@ class Plot: @dataclass class Reader: - folder_name_for_printer_files_loc: str = "temp" + folder_name_for_printer_files: str = "temp" read_step_files: bool = True diff --git a/pytrnsys_process/utils.py b/pytrnsys_process/utils.py index 16f5719..db430b7 100644 --- a/pytrnsys_process/utils.py +++ b/pytrnsys_process/utils.py @@ -19,14 +19,14 @@ def get_sim_folders(path_to_results: _pl.Path) -> _abc.Sequence[_pl.Path]: def get_files( sim_folders: _abc.Sequence[_pl.Path], - results_folder_name: str = _set.settings.reader.folder_name_for_printer_files_loc, + results_folder_name: str = _set.settings.reader.folder_name_for_printer_files, get_mfr_and_t: bool = _set.settings.reader.read_step_files, ) -> _abc.Sequence[_pl.Path]: sim_files: list[_pl.Path] = [] for sim_folder in sim_folders: if get_mfr_and_t: sim_files.extend(sim_folder.glob("*[_T,_Mfr].prt")) - for sim_file in (sim_folder / results_folder_name).glob("**/*"): + for sim_file in (sim_folder / results_folder_name).glob("*"): sim_files.append(sim_file) return [x for x in sim_files if x.is_file()] @@ -34,9 +34,46 @@ def get_files( # TODO add docstring #pylint: disable=fixme -def save_plot( + +def export_plots_in_configured_formats( fig: _plt.Figure, path_to_directory: _pl.Path, plot_name: str ) -> None: + """Save a matplotlib figure in multiple formats and sizes. + + Saves the figure in all configured formats (png, pdf, emf) and sizes (A4, A4_HALF) + as specified in the plot settings (api.settings.plot). + For EMF format, the figure is first saved as SVG and then converted using Inkscape. + + Args: + fig: The matplotlib Figure object to save. + path_to_directory: Directory path where the plots should be saved. + plot_name: Base name for the plot file (will be appended with size and format). + + Returns: + None + + Note: + - Creates a 'plots' subdirectory if it doesn't exist + - For EMF files, requires Inkscape to be installed at the configured path + - File naming format: {plot_name}-{size_name}.{format} + + Example: + >>> from pytrnsys_process import api + >>> def processing_of_monthly_data(simulation: api.Simulation): + >>> monthly_df = simulation.monthly + >>> columns_to_plot = ["QSnk60P", "QSnk60PauxCondSwitch_kW"] + >>> fig, ax = api.bar_chart(monthly_df, columns_to_plot) + >>> + >>> # Save the plot in multiple formats + >>> api.export_plots_in_configured_formats(fig, simulation.path, "monthly-bar-chart") + >>> # Creates files like: + >>> # results/simulation1/plots/monthly-bar-chart-A4.png + >>> # results/simulation1/plots/monthly-bar-chart-A4.pdf + >>> # results/simulation1/plots/monthly-bar-chart-A4.emf + >>> # results/simulation1/plots/monthly-bar-chart-A4_HALF.png + >>> # etc. + + """ plot_settings = _set.settings.plot plots_folder = path_to_directory / "plots" plots_folder.mkdir(exist_ok=True) @@ -49,17 +86,20 @@ def save_plot( if fmt == ".emf": path_to_svg = file_no_suffix.with_suffix(".svg") fig.savefig(path_to_svg) - convert_svg_to_emf(path_to_svg) + convert_svg_to_emf(file_no_suffix) + if ".svg" not in plot_settings.file_formats: + os.remove(path_to_svg) else: fig.savefig(file_no_suffix.with_suffix(fmt)) -def convert_svg_to_emf(path_to_svg: _pl.Path) -> None: +def convert_svg_to_emf(file_no_suffix: _pl.Path) -> None: try: inkscape_path = _set.settings.plot.inkscape_path if not _pl.Path(inkscape_path).exists(): raise OSError(f"Inkscape executable not found at: {inkscape_path}") - emf_filepath = path_to_svg.parent / f"{path_to_svg.stem}.emf" + emf_filepath = file_no_suffix.with_suffix(".emf") + path_to_svg = file_no_suffix.with_suffix(".svg") subprocess.run( [ @@ -72,7 +112,7 @@ def convert_svg_to_emf(path_to_svg: _pl.Path) -> None: capture_output=True, text=True, ) - os.remove(path_to_svg) + except subprocess.CalledProcessError as e: logger.error( "Inkscape conversion failed: %s\nOutput: %s", diff --git a/tests/pytrnsys_process/data/plots/bar-chart/expected.png b/tests/pytrnsys_process/data/plots/bar-chart/expected.png index 7257cb7..05f61aa 100644 Binary files a/tests/pytrnsys_process/data/plots/bar-chart/expected.png and b/tests/pytrnsys_process/data/plots/bar-chart/expected.png differ diff --git a/tests/pytrnsys_process/data/plots/line-plot/expected.png b/tests/pytrnsys_process/data/plots/line-plot/expected.png index 076726f..620ba2b 100644 Binary files a/tests/pytrnsys_process/data/plots/line-plot/expected.png and b/tests/pytrnsys_process/data/plots/line-plot/expected.png differ diff --git a/tests/pytrnsys_process/data/plots/scatter-plot/expected.png b/tests/pytrnsys_process/data/plots/scatter-plot/expected.png index 9ab8f43..fb18a2b 100644 Binary files a/tests/pytrnsys_process/data/plots/scatter-plot/expected.png and b/tests/pytrnsys_process/data/plots/scatter-plot/expected.png differ diff --git a/tests/pytrnsys_process/data/plots/stacked-bar-chart/expected.png b/tests/pytrnsys_process/data/plots/stacked-bar-chart/expected.png index 45a1835..d94735b 100644 Binary files a/tests/pytrnsys_process/data/plots/stacked-bar-chart/expected.png and b/tests/pytrnsys_process/data/plots/stacked-bar-chart/expected.png differ diff --git a/tests/pytrnsys_process/data/results/sim-1/temp/don-not-process.xlsx b/tests/pytrnsys_process/data/results/sim-1/temp/don-not-process.xlsx new file mode 100644 index 0000000..e69de29 diff --git a/tests/pytrnsys_process/test_converter.py b/tests/pytrnsys_process/test_converter.py index 1d11b44..2ddd082 100644 --- a/tests/pytrnsys_process/test_converter.py +++ b/tests/pytrnsys_process/test_converter.py @@ -1,7 +1,7 @@ import pytest as _pt from pytrnsys_process import constants as const -from pytrnsys_process import converter +from pytrnsys_process import file_converter from tests.pytrnsys_process import constants as test_const @@ -18,7 +18,7 @@ def test_convert_sim_results_to_csv(self): input_dir = test_const.DATA_FOLDER / "conversion/prt" output_dir = test_const.DATA_FOLDER / "conversion/csv" - converter.CsvConverter().convert_sim_results_to_csv( + file_converter.CsvConverter().convert_sim_results_to_csv( input_dir, output_dir ) @@ -65,7 +65,9 @@ def test_rename_file_with_prefix( test_file = tmp_path / "test.txt" test_file.write_text("test content") - converter.CsvConverter().rename_file_with_prefix(test_file, file_type) + file_converter.CsvConverter().rename_file_with_prefix( + test_file, file_type + ) assert not test_file.exists() assert (tmp_path / f"{expected_prefix}test.txt").exists() @@ -75,6 +77,6 @@ def test_rename_nonexistent_file(self, tmp_path): nonexistent_file = tmp_path / "doesnotexist.txt" with _pt.raises(FileNotFoundError): - converter.CsvConverter().rename_file_with_prefix( + file_converter.CsvConverter().rename_file_with_prefix( nonexistent_file, const.FileType.MONTHLY ) diff --git a/tests/pytrnsys_process/test_plotters.py b/tests/pytrnsys_process/test_plotters.py index e52116a..5840f0c 100644 --- a/tests/pytrnsys_process/test_plotters.py +++ b/tests/pytrnsys_process/test_plotters.py @@ -12,7 +12,7 @@ class TestPlotters: SKIP_PLOT_COMPARISON = ( - True # Toggle this to enable/disable plot comparison + False # Toggle this to enable/disable plot comparison ) @pytest.fixture diff --git a/tests/pytrnsys_process/test_process_sim.py b/tests/pytrnsys_process/test_process_sim.py index 00725af..b445780 100644 --- a/tests/pytrnsys_process/test_process_sim.py +++ b/tests/pytrnsys_process/test_process_sim.py @@ -1,8 +1,10 @@ +import unittest as _ut +import unittest.mock as _mock + import pandas as _pd import pytest as _pt import tests.pytrnsys_process.constants as const -from pytrnsys_process import settings as sett from pytrnsys_process import utils from pytrnsys_process.process_sim import process_file as pf from pytrnsys_process.process_sim import process_sim as ps @@ -10,14 +12,18 @@ PATH_TO_RESULTS = const.DATA_FOLDER / "results/sim-1" -class TestProcessSim: +class TestProcessSim(_ut.TestCase): def test_process_sim_prt(self): sim_files = utils.get_files([PATH_TO_RESULTS]) - simulation = ps.process_sim(sim_files, PATH_TO_RESULTS) - - self.do_assert(simulation) + with self.assertLogs("pytrnsys_process", level="ERROR") as log_context: + simulation = ps.process_sim(sim_files, PATH_TO_RESULTS) + assert ( + "don-not-process.xlsx: No columns to parse from file" + in log_context.output[0] + ) + self.do_assert(simulation) def test_process_sim_csv(self): sim_files = utils.get_files( @@ -32,9 +38,10 @@ def test_process_sim_csv(self): def test_process_sim_ignore_step(self): sim_files = utils.get_files([PATH_TO_RESULTS]) - sett.settings.reader.read_step_files = False - - simulation = ps.process_sim(sim_files, PATH_TO_RESULTS) + with _mock.patch( + "pytrnsys_process.settings.settings.reader.read_step_files", False + ): + simulation = ps.process_sim(sim_files, PATH_TO_RESULTS) assert simulation.step.shape == (0, 0) @@ -135,10 +142,14 @@ def test_handle_with_conflicting_none_duplicates(self): class TestBenchmarkProcessSim: def test_process_per_sim_prt(self, benchmark): - benchmark(ps.process_sim, PATH_TO_RESULTS) + sim_files = utils.get_files([PATH_TO_RESULTS]) + + benchmark(lambda: ps.process_sim(sim_files, PATH_TO_RESULTS)) def test_process_per_sim_csv(self, benchmark): - benchmark(ps.process_sim, PATH_TO_RESULTS) + sim_files = utils.get_files([PATH_TO_RESULTS]) + + benchmark(lambda: ps.process_sim(sim_files, PATH_TO_RESULTS)) def test_process_per_file_using_file_content(self, benchmark): benchmark( diff --git a/tests/pytrnsys_process/test_utils.py b/tests/pytrnsys_process/test_utils.py index 2169ac5..5e07ee3 100644 --- a/tests/pytrnsys_process/test_utils.py +++ b/tests/pytrnsys_process/test_utils.py @@ -10,9 +10,12 @@ def test_save_plot_for_default_settings(tmp_path): fig = Mock(spec=plt.Figure) - with patch("pytrnsys_process.utils.convert_svg_to_emf") as mock_convert: + with ( + patch("pytrnsys_process.utils.convert_svg_to_emf") as mock_convert, + patch("os.remove") as mock_remove, + ): # Call save_plot - utils.save_plot(fig, tmp_path, "test_plot") + utils.export_plots_in_configured_formats(fig, tmp_path, "test_plot") # Verify plots directory was created plots_dir = tmp_path / "plots" @@ -35,10 +38,11 @@ def test_save_plot_for_default_settings(tmp_path): # Verify convert_svg_to_emf was called for each size expected_convert_calls = [ - call(plots_dir / "test_plot-A4.svg"), - call(plots_dir / "test_plot-A4_HALF.svg"), + call(plots_dir / "test_plot-A4"), + call(plots_dir / "test_plot-A4_HALF"), ] assert mock_convert.call_args_list == expected_convert_calls + assert mock_remove.call_count == 2 def test_convert_svg_to_emf(tmp_path): @@ -49,9 +53,10 @@ def test_convert_svg_to_emf(tmp_path): ): # Create test SVG path svg_path = tmp_path / "test.svg" + file_no_suffix = tmp_path / "test" # Call convert_svg_to_emf - utils.convert_svg_to_emf(svg_path) + utils.convert_svg_to_emf(file_no_suffix) # Verify subprocess.run was called correctly mock_run.assert_called_once_with( @@ -67,7 +72,7 @@ def test_convert_svg_to_emf(tmp_path): ) # Verify original SVG was removed - mock_remove.assert_called_once_with(svg_path) + assert not mock_remove.called def test_convert_svg_to_emf_inkscape_not_found(tmp_path): @@ -103,3 +108,30 @@ def test_convert_svg_to_emf_subprocess_error(tmp_path): assert ( "Inkscape conversion failed" in mock_logger.error.call_args[0][0] ) + + +def test_get_files_works_as_expected(tmp_path): + # Create test directory structure + sim_folder = tmp_path / "sim1" + results_folder = sim_folder / "temp" + nested_folder = results_folder / "nested" + + # Create directories + for folder in [sim_folder, results_folder, nested_folder]: + folder.mkdir(parents=True) + + # Create some test files + test_file1 = results_folder / "test1.prt" + test_file2 = nested_folder / "test2.prt" + test_file1.touch() + test_file2.touch() + + # Run the function + files = utils.get_files([sim_folder]) + + # Verify results + assert len(files) == 1 + assert all(f.is_file() for f in files) + assert nested_folder not in files + assert results_folder not in files + assert set(files) == {test_file1}