Skip to content

Commit 4ec08d2

Browse files
authored
Refactors the MatlabWrapper to load Matlab in the background (#41)
1 parent 0ea292b commit 4ec08d2

File tree

6 files changed

+46
-29
lines changed

6 files changed

+46
-29
lines changed

.github/workflows/run_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ jobs:
4949
- name: Install and Test with pytest
5050
run: |
5151
python -m pip install -e .[Dev]
52-
pytest tests/ --cov=${{ github.event.repository.name }} --cov-report=html:htmlcov --cov-report=term
52+
pytest tests/ --cov=RATpy --cov-report=term

RATpy/wrappers.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pathlib
2+
from contextlib import suppress
23
from typing import Callable, Tuple
34

45
import numpy as np
@@ -7,6 +8,24 @@
78
import RATpy.rat_core
89

910

11+
def start_matlab():
12+
"""Starts MATLAB asynchronously and returns a future to retrieve the engine later
13+
14+
Returns
15+
-------
16+
future : matlab.engine.futureresult.FutureResult
17+
A future used to get the actual matlab engine
18+
19+
"""
20+
future = None
21+
with suppress(ImportError):
22+
import matlab.engine
23+
24+
future = matlab.engine.start_matlab(background=True)
25+
26+
return future
27+
28+
1029
class MatlabWrapper:
1130
"""Creates a python callback for a MATLAB function.
1231
@@ -17,21 +36,17 @@ class MatlabWrapper:
1736
1837
"""
1938

39+
loader = start_matlab()
40+
2041
def __init__(self, filename: str) -> None:
21-
self.engine = None
22-
try:
23-
import matlab.engine
24-
except ImportError:
42+
if self.loader is None:
2543
raise ImportError("matlabengine is required to use MatlabWrapper") from None
26-
self.engine = matlab.engine.start_matlab()
44+
45+
self.engine = self.loader.result()
2746
path = pathlib.Path(filename)
2847
self.engine.cd(str(path.parent), nargout=0)
2948
self.function_name = path.stem
3049

31-
def __del__(self):
32-
if self.engine is not None:
33-
self.engine.quit()
34-
3550
def getHandle(self) -> Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], Tuple[ArrayLike, float]]:
3651
"""Returns a wrapper for the custom MATLAB function
3752

requirements-dev.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ pytest >= 7.4.0
66
pytest-cov >= 4.1.0
77
matplotlib >= 3.8.3
88
StrEnum >= 0.4.15; python_version < '3.11'
9+
ruff >= 0.4.10

tests/test_inputs.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -618,13 +618,14 @@ def test_make_input(test_project, test_problem, test_cells, test_limits, test_pr
618618
"domainRatio",
619619
]
620620

621-
mocked_matlab_module = mock.MagicMock()
621+
mocked_matlab_future = mock.MagicMock()
622622
mocked_engine = mock.MagicMock()
623-
mocked_matlab_module.engine.start_matlab.return_value = mocked_engine
623+
mocked_matlab_future.result.return_value = mocked_engine
624624

625-
with mock.patch.dict(
626-
"sys.modules",
627-
{"matlab": mocked_matlab_module, "matlab.engine": mocked_matlab_module.engine},
625+
with mock.patch.object(
626+
RATpy.wrappers.MatlabWrapper,
627+
"loader",
628+
mocked_matlab_future,
628629
), mock.patch.object(RATpy.rat_core, "DylibEngine", mock.MagicMock()), mock.patch.object(
629630
RATpy.inputs,
630631
"get_python_handle",
@@ -757,13 +758,13 @@ def test_make_cells(test_project, test_cells, request) -> None:
757758
test_project = request.getfixturevalue(test_project)
758759
test_cells = request.getfixturevalue(test_cells)
759760

760-
mocked_matlab_module = mock.MagicMock()
761-
mocked_matlab_engine = mock.MagicMock()
762-
mocked_matlab_module.engine.start_matlab.return_value = mocked_matlab_engine
763-
764-
with mock.patch.dict(
765-
"sys.modules",
766-
{"matlab": mocked_matlab_module, "matlab.engine": mocked_matlab_module.engine},
761+
mocked_matlab_future = mock.MagicMock()
762+
mocked_engine = mock.MagicMock()
763+
mocked_matlab_future.result.return_value = mocked_engine
764+
with mock.patch.object(
765+
RATpy.wrappers.MatlabWrapper,
766+
"loader",
767+
mocked_matlab_future,
767768
), mock.patch.object(RATpy.rat_core, "DylibEngine", mock.MagicMock()), mock.patch.object(
768769
RATpy.inputs,
769770
"get_python_handle",

tests/test_wrappers.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77

88

99
def test_matlab_wrapper() -> None:
10-
with mock.patch.dict("sys.modules", {"matlab": mock.MagicMock(side_effect=ImportError)}), pytest.raises(
11-
ImportError,
10+
with (
11+
mock.patch.object(RATpy.wrappers.MatlabWrapper, "loader", None),
12+
pytest.raises(ImportError),
1213
):
1314
RATpy.wrappers.MatlabWrapper("demo.m")
14-
mocked_matlab_module = mock.MagicMock()
15-
mocked_engine = mock.MagicMock()
16-
mocked_matlab_module.engine.start_matlab.return_value = mocked_engine
1715

18-
with mock.patch.dict("sys.modules", {"matlab": mocked_matlab_module, "matlab.engine": mocked_matlab_module.engine}):
16+
mocked_matlab_future = mock.MagicMock()
17+
mocked_engine = mock.MagicMock()
18+
mocked_matlab_future.result.return_value = mocked_engine
19+
with mock.patch.object(RATpy.wrappers.MatlabWrapper, "loader", mocked_matlab_future):
1920
wrapper = RATpy.wrappers.MatlabWrapper("demo.m")
2021
assert wrapper.function_name == "demo"
2122
mocked_engine.cd.assert_called_once()

0 commit comments

Comments
 (0)