diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 7a8f9acb..d78c985b 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -49,4 +49,4 @@ jobs: - name: Install and Test with pytest run: | python -m pip install -e .[Dev] - pytest tests/ --cov=${{ github.event.repository.name }} --cov-report=html:htmlcov --cov-report=term + pytest tests/ --cov=RATpy --cov-report=term diff --git a/RATpy/wrappers.py b/RATpy/wrappers.py index 1b90d2ec..58a8e639 100644 --- a/RATpy/wrappers.py +++ b/RATpy/wrappers.py @@ -1,4 +1,5 @@ import pathlib +from contextlib import suppress from typing import Callable, Tuple import numpy as np @@ -7,6 +8,24 @@ import RATpy.rat_core +def start_matlab(): + """Starts MATLAB asynchronously and returns a future to retrieve the engine later + + Returns + ------- + future : matlab.engine.futureresult.FutureResult + A future used to get the actual matlab engine + + """ + future = None + with suppress(ImportError): + import matlab.engine + + future = matlab.engine.start_matlab(background=True) + + return future + + class MatlabWrapper: """Creates a python callback for a MATLAB function. @@ -17,21 +36,17 @@ class MatlabWrapper: """ + loader = start_matlab() + def __init__(self, filename: str) -> None: - self.engine = None - try: - import matlab.engine - except ImportError: + if self.loader is None: raise ImportError("matlabengine is required to use MatlabWrapper") from None - self.engine = matlab.engine.start_matlab() + + self.engine = self.loader.result() path = pathlib.Path(filename) self.engine.cd(str(path.parent), nargout=0) self.function_name = path.stem - def __del__(self): - if self.engine is not None: - self.engine.quit() - def getHandle(self) -> Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], Tuple[ArrayLike, float]]: """Returns a wrapper for the custom MATLAB function diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 4899358b..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ -ruff >= 0.4.10 diff --git a/requirements.txt b/requirements.txt index f1f3dca6..83340710 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ pytest >= 7.4.0 pytest-cov >= 4.1.0 matplotlib >= 3.8.3 StrEnum >= 0.4.15; python_version < '3.11' +ruff >= 0.4.10 diff --git a/tests/test_inputs.py b/tests/test_inputs.py index dd415f26..188ffa81 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -618,13 +618,14 @@ def test_make_input(test_project, test_problem, test_cells, test_limits, test_pr "domainRatio", ] - mocked_matlab_module = mock.MagicMock() + mocked_matlab_future = mock.MagicMock() mocked_engine = mock.MagicMock() - mocked_matlab_module.engine.start_matlab.return_value = mocked_engine + mocked_matlab_future.result.return_value = mocked_engine - with mock.patch.dict( - "sys.modules", - {"matlab": mocked_matlab_module, "matlab.engine": mocked_matlab_module.engine}, + with mock.patch.object( + RATpy.wrappers.MatlabWrapper, + "loader", + mocked_matlab_future, ), mock.patch.object(RATpy.rat_core, "DylibEngine", mock.MagicMock()), mock.patch.object( RATpy.inputs, "get_python_handle", @@ -757,13 +758,13 @@ def test_make_cells(test_project, test_cells, request) -> None: test_project = request.getfixturevalue(test_project) test_cells = request.getfixturevalue(test_cells) - mocked_matlab_module = mock.MagicMock() - mocked_matlab_engine = mock.MagicMock() - mocked_matlab_module.engine.start_matlab.return_value = mocked_matlab_engine - - with mock.patch.dict( - "sys.modules", - {"matlab": mocked_matlab_module, "matlab.engine": mocked_matlab_module.engine}, + mocked_matlab_future = mock.MagicMock() + mocked_engine = mock.MagicMock() + mocked_matlab_future.result.return_value = mocked_engine + with mock.patch.object( + RATpy.wrappers.MatlabWrapper, + "loader", + mocked_matlab_future, ), mock.patch.object(RATpy.rat_core, "DylibEngine", mock.MagicMock()), mock.patch.object( RATpy.inputs, "get_python_handle", diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index db8f7f38..88e598bc 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -7,15 +7,16 @@ def test_matlab_wrapper() -> None: - with mock.patch.dict("sys.modules", {"matlab": mock.MagicMock(side_effect=ImportError)}), pytest.raises( - ImportError, + with ( + mock.patch.object(RATpy.wrappers.MatlabWrapper, "loader", None), + pytest.raises(ImportError), ): RATpy.wrappers.MatlabWrapper("demo.m") - mocked_matlab_module = mock.MagicMock() - mocked_engine = mock.MagicMock() - mocked_matlab_module.engine.start_matlab.return_value = mocked_engine - with mock.patch.dict("sys.modules", {"matlab": mocked_matlab_module, "matlab.engine": mocked_matlab_module.engine}): + mocked_matlab_future = mock.MagicMock() + mocked_engine = mock.MagicMock() + mocked_matlab_future.result.return_value = mocked_engine + with mock.patch.object(RATpy.wrappers.MatlabWrapper, "loader", mocked_matlab_future): wrapper = RATpy.wrappers.MatlabWrapper("demo.m") assert wrapper.function_name == "demo" mocked_engine.cd.assert_called_once()