From 3ae6b0321c0359c340ae0c55c8399bb08a110163 Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Tue, 10 Feb 2026 15:16:10 +0000 Subject: [PATCH 1/3] Allow importing local file in custom file --- packaging/build_exe.py | 7 ++++++- packaging/hooks/hook-matlab.py | 3 --- rascal2/ui/model.py | 17 ++++++++++++++++- tests/ui/test_model.py | 30 +++++++++++++----------------- tests/ui/test_presenter.py | 5 ++++- 5 files changed, 39 insertions(+), 23 deletions(-) delete mode 100644 packaging/hooks/hook-matlab.py diff --git a/packaging/build_exe.py b/packaging/build_exe.py index 9b29b298..3bdd560b 100644 --- a/packaging/build_exe.py +++ b/packaging/build_exe.py @@ -49,7 +49,6 @@ "PyQt6.QtXml", "PyQt6.QtXmlPatterns", "sphinx", - "numpy.array_api", "pkg_resources", ] IS_WINDOWS = sys.platform.startswith("win") @@ -80,6 +79,12 @@ def build_exe(): str(PACKAGING_PATH / "hooks"), "--log-level", "ERROR", + "--collect-submodules", + "numpy", + "--collect-submodules", + "scipy", + "--collect-all", + "matlab", str(main_path), ] diff --git a/packaging/hooks/hook-matlab.py b/packaging/hooks/hook-matlab.py deleted file mode 100644 index 56c0462a..00000000 --- a/packaging/hooks/hook-matlab.py +++ /dev/null @@ -1,3 +0,0 @@ -from PyInstaller.utils.hooks import collect_all - -datas, binaries, hiddenimports = collect_all("matlab") diff --git a/rascal2/ui/model.py b/rascal2/ui/model.py index ff2cbd3a..40423c68 100644 --- a/rascal2/ui/model.py +++ b/rascal2/ui/model.py @@ -1,4 +1,6 @@ +import os import shutil +import sys from json import JSONDecodeError from pathlib import Path @@ -63,7 +65,19 @@ def __init__(self): self.result_log = "" self.controls = None - self.save_path = "" + self.__save_path = "" + + @property + def save_path(self): + return self.__save_path + + @save_path.setter + def save_path(self, value): + if self.__save_path in sys.path: + sys.path.remove(self.__save_path) + self.__save_path = value + os.chdir(value) + sys.path.append(value) def create_project(self, name: str, save_path: str): """Create a new RAT project and controls object. @@ -125,6 +139,7 @@ def save_project(self, save_path): if self.results: self.results.save(Path(save_path, "results.json")) self.save_path = save_path + os.chdir(save_path) def is_project_example(self): return Path(self.save_path).is_relative_to(EXAMPLES_TEMP_PATH) diff --git a/tests/ui/test_model.py b/tests/ui/test_model.py index 67246ca6..da38cf4b 100644 --- a/tests/ui/test_model.py +++ b/tests/ui/test_model.py @@ -38,9 +38,14 @@ def empty_results(): return empty_results -def test_create_project(): +@pytest.fixture +def model(): + with patch("rascal2.ui.model.os.chdir", autospec=True): + yield MainWindowModel() + + +def test_create_project(model): """The project should be set up with the desired name and default objets when a new project is created.""" - model = MainWindowModel() assert model.project is None assert model.controls is None assert model.results is None @@ -53,8 +58,7 @@ def test_create_project(): assert model.save_path == "C:/test" -def test_save_project(empty_results): - model = MainWindowModel() +def test_save_project(empty_results, model): model.project = Project(calculation="domains", name="test project") model.controls = Controls(procedure="dream", resampleMinAngle=0.5) model.results = empty_results @@ -73,9 +77,8 @@ def test_save_project(empty_results): assert '"fitParams": []' in results -def test_load_project(empty_results): +def test_load_project(empty_results, model): """The load function should load the correct controls object from JSON.""" - model = MainWindowModel() project = Project(name="test project", calculation="domains") with TemporaryDirectory() as tmpdir: @@ -91,9 +94,8 @@ def test_load_project(empty_results): @patch("ratapi.utils.convert.r1_to_project") -def test_load_r1_project(mock_r1_class): +def test_load_r1_project(mock_r1_class, model): """load_r1_project should call the conversion function and set the path correctly.""" - model = MainWindowModel() model.load_r1_project("test_path/r1project.mat") mock_r1_class.assert_called_once() @@ -101,10 +103,8 @@ def test_load_r1_project(mock_r1_class): @pytest.mark.parametrize("bad_json", ['{"field1":3', '{"procedure":"fry eggs"}']) -def test_load_controls_error(bad_json): +def test_load_controls_error(bad_json, model): """The project load function should raise an error if the controls JSON is invalid or the parameters are invalid.""" - model = MainWindowModel() - with pytest.raises( # noqa (for nested with's: pytest.raises breaks if not by itself) ValueError, match="The controls.json file for this project is not valid.\n" @@ -116,10 +116,8 @@ def test_load_controls_error(bad_json): @pytest.mark.parametrize("bad_json", ['{"calculation":"Do}', '{i"m not a good project file']) -def test_load_project_decode_error(bad_json): +def test_load_project_decode_error(bad_json, model): """The project load function should raise an error if the project JSON is invalid JSON.""" - model = MainWindowModel() - with pytest.raises( # noqa (for nested with's: pytest.raises breaks if not by itself) ValueError, match="The project.json file for this project contains invalid JSON." ): @@ -132,10 +130,8 @@ def test_load_project_decode_error(bad_json): @pytest.mark.parametrize( "bad_json", ['{"calculation":"guessing"}', '{"parameters":[{"name":"parameter 1","thickness":0.51}]}'] ) -def test_load_project_value_error(bad_json): +def test_load_project_value_error(bad_json, model): """The project load function should raise an error if the values are not valid.""" - model = MainWindowModel() - with pytest.raises( # noqa (for nested with's: pytest.raises breaks if not by itself) ValueError, match="The project.json file for this project is not valid." ): diff --git a/tests/ui/test_presenter.py b/tests/ui/test_presenter.py index 114c7916..156a0373 100644 --- a/tests/ui/test_presenter.py +++ b/tests/ui/test_presenter.py @@ -47,7 +47,10 @@ def __init__(self): @pytest.fixture def presenter(): - with patch("rascal2.ui.presenter.LOGGER", autospec=True) as mock_log: + with ( + patch("rascal2.ui.presenter.LOGGER", autospec=True) as mock_log, + patch("rascal2.ui.model.os.chdir", autospec=True), + ): pr = MainWindowPresenter(MockWindowView()) pr.runner = MagicMock() pr.model.controls = Controls() From 5a2c43751a9503e5aa530e6c6b958eea3cd62b4e Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Tue, 10 Feb 2026 15:40:56 +0000 Subject: [PATCH 2/3] Reverse the order of examples --- rascal2/dialogs/startup_dialog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rascal2/dialogs/startup_dialog.py b/rascal2/dialogs/startup_dialog.py index ab4fd9e1..763029d7 100644 --- a/rascal2/dialogs/startup_dialog.py +++ b/rascal2/dialogs/startup_dialog.py @@ -10,13 +10,13 @@ # global variable for required project files PROJECT_FILES = ["controls.json", "project.json"] EXAMPLES = { - "absorption": "Shows absorption (imaginary SLD) effect usually seen below the critical edge", - "domains_custom_layers": "Incoherent summing ('domains') from custom layer model", - "domains_custom_XY": "Incoherent summing ('domains') from custom XY model", - "domains_standard_layers": "Incoherent summing ('domains') from standard layer model", - "DSPC_custom_layers": "Reflectivity analysis of a floating bilayer of DSPC using custom layer model", - "DSPC_custom_XY": "Reflectivity analysis of a floating bilayer of DSPC using custom XY model", "DSPC_standard_layers": "Reflectivity analysis of a floating bilayer of DSPC using standard layer model", + "DSPC_custom_XY": "Reflectivity analysis of a floating bilayer of DSPC using custom XY model", + "DSPC_custom_layers": "Reflectivity analysis of a floating bilayer of DSPC using custom layer model", + "domains_standard_layers": "Incoherent summing ('domains') from standard layer model", + "domains_custom_XY": "Incoherent summing ('domains') from custom XY model", + "domains_custom_layers": "Incoherent summing ('domains') from custom layer model", + "absorption": "Shows absorption (imaginary SLD) effect usually seen below the critical edge", } From 09072d0734fbde4aa62ccbc65ee960ec6c7cfe45 Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Wed, 11 Feb 2026 09:34:44 +0000 Subject: [PATCH 3/3] Address review comment --- rascal2/dialogs/startup_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal2/dialogs/startup_dialog.py b/rascal2/dialogs/startup_dialog.py index 763029d7..d61d3a11 100644 --- a/rascal2/dialogs/startup_dialog.py +++ b/rascal2/dialogs/startup_dialog.py @@ -11,11 +11,11 @@ PROJECT_FILES = ["controls.json", "project.json"] EXAMPLES = { "DSPC_standard_layers": "Reflectivity analysis of a floating bilayer of DSPC using standard layer model", - "DSPC_custom_XY": "Reflectivity analysis of a floating bilayer of DSPC using custom XY model", "DSPC_custom_layers": "Reflectivity analysis of a floating bilayer of DSPC using custom layer model", + "DSPC_custom_XY": "Reflectivity analysis of a floating bilayer of DSPC using custom XY model", "domains_standard_layers": "Incoherent summing ('domains') from standard layer model", - "domains_custom_XY": "Incoherent summing ('domains') from custom XY model", "domains_custom_layers": "Incoherent summing ('domains') from custom layer model", + "domains_custom_XY": "Incoherent summing ('domains') from custom XY model", "absorption": "Shows absorption (imaginary SLD) effect usually seen below the critical edge", }