From 78e15d17f46966173e8a7f9717872fe2236d6f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Wed, 28 Jan 2026 15:15:30 +0000 Subject: [PATCH 1/5] Find nearest Simvue config in nested directories --- simvue/utilities.py | 10 +++++--- tests/functional/test_utilities.py | 38 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/simvue/utilities.py b/simvue/utilities.py index e890698e..bd2c1ccb 100644 --- a/simvue/utilities.py +++ b/simvue/utilities.py @@ -47,9 +47,13 @@ def find_first_instance_of_file( file_names = [file_names] for file_name in file_names: - _user_file = pathlib.Path.cwd().joinpath(file_name) - if _user_file.exists(): - return _user_file + _current_path = pathlib.Path.cwd() + + while os.access(_current_path, os.R_OK): + _user_file = _current_path.joinpath(file_name) + if _user_file.exists(): + return _user_file + _current_path = _current_path.parent # If the user is running on different mounted volume or outside # of their user space then the above will not return the file diff --git a/tests/functional/test_utilities.py b/tests/functional/test_utilities.py index 8f953be4..a8ddc01d 100644 --- a/tests/functional/test_utilities.py +++ b/tests/functional/test_utilities.py @@ -1,6 +1,10 @@ import pytest import tempfile import os.path +import pathlib +import stat + +from pytest_mock import MockerFixture import simvue.utilities as sv_util @@ -19,3 +23,37 @@ def test_calculate_hash(is_file: bool, hash: str) -> None: assert sv_util.calculate_sha256(filename=out_file, is_file=is_file) == hash else: assert sv_util.calculate_sha256(filename="temp.txt", is_file=is_file) == hash + +@pytest.mark.config +@pytest.mark.parametrize( + "user_area", (True, False), + ids=("permitted_dir", "out_of_user_area") +) +def test_find_first_file(user_area: bool, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: + # Deactivate the server checks for this test + monkeypatch.setenv("SIMVUE_NO_SERVER_CHECK", "True") + monkeypatch.delenv("SIMVUE_TOKEN", False) + monkeypatch.delenv("SIMVUE_URL", False) + + + with tempfile.TemporaryDirectory() as temp_d: + _path = pathlib.Path(temp_d) + _path_sub = _path.joinpath("level_0") + _path_sub.mkdir() + + for i in range(1, 5): + _path_sub = _path_sub.joinpath(f"level_{i}") + _path_sub.mkdir() + mocker.patch("pathlib.Path.cwd", lambda *_: _path_sub) + + if user_area: + _path.joinpath("level_0").joinpath("simvue.toml").touch() + _path.chmod(stat.S_IXUSR) + _result = sv_util.find_first_instance_of_file("simvue.toml", check_user_space=False) + else: + _path.chmod(stat.S_IXUSR) + _result = sv_util.find_first_instance_of_file("simvue.toml", check_user_space=False) is None + + _path.chmod(stat.S_IRWXU) + assert _result + From d7fdcdbbd695ed65d6d58e7be9e5839abdf66c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Wed, 28 Jan 2026 22:07:45 +0000 Subject: [PATCH 2/5] [skip ci] Updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b05308ab..3ef6e842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## Unreleased + +- Added search for Simvue configuration within the parent file hierarchy of the current directory. + ## [v2.3.0](https://github.com/simvue-io/client/releases/tag/v2.3.0) - 2025-12-11 - Refactored sender functionality introducing new `Sender` class. From b0bf2dab7cd34849666af08e5615e9382b85c85c Mon Sep 17 00:00:00 2001 From: James Panayis Date: Thu, 29 Jan 2026 07:52:49 +0000 Subject: [PATCH 3/5] Make search fore simvue.toml file in parent directories stop at root --- simvue/utilities.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simvue/utilities.py b/simvue/utilities.py index bd2c1ccb..d7877220 100644 --- a/simvue/utilities.py +++ b/simvue/utilities.py @@ -53,7 +53,10 @@ def find_first_instance_of_file( _user_file = _current_path.joinpath(file_name) if _user_file.exists(): return _user_file - _current_path = _current_path.parent + _parent_path = _current_path.parent + if _parent_path == _current_path: # parent of root dir is root dir + break + _current_path = _parent_path # If the user is running on different mounted volume or outside # of their user space then the above will not return the file From 2e24ac028eb548dd4e590e30fb2aa48d81180053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= <64790965+kzscisoft@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:08:32 +0000 Subject: [PATCH 4/5] Update simvue/utilities.py Co-authored-by: James Panayis --- simvue/utilities.py | 1 + 1 file changed, 1 insertion(+) diff --git a/simvue/utilities.py b/simvue/utilities.py index d7877220..faa567ec 100644 --- a/simvue/utilities.py +++ b/simvue/utilities.py @@ -33,6 +33,7 @@ def find_first_instance_of_file( Parameters ---------- + file_names: list[str] | str candidate names of file to locate check_user_space: bool, optional check the users home area if current working directory is not From 9638221b417739093e5ee98346aeeba1ab7e0f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Thu, 29 Jan 2026 13:07:04 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=A7=AA=20Add=20test=20against=20recur?= =?UTF-8?q?sion=20when=20config=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/functional/test_utilities.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/functional/test_utilities.py b/tests/functional/test_utilities.py index a8ddc01d..1d8e0abd 100644 --- a/tests/functional/test_utilities.py +++ b/tests/functional/test_utilities.py @@ -29,13 +29,12 @@ def test_calculate_hash(is_file: bool, hash: str) -> None: "user_area", (True, False), ids=("permitted_dir", "out_of_user_area") ) -def test_find_first_file(user_area: bool, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: +def test_find_first_file_search(user_area: bool, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: # Deactivate the server checks for this test monkeypatch.setenv("SIMVUE_NO_SERVER_CHECK", "True") monkeypatch.delenv("SIMVUE_TOKEN", False) monkeypatch.delenv("SIMVUE_URL", False) - with tempfile.TemporaryDirectory() as temp_d: _path = pathlib.Path(temp_d) _path_sub = _path.joinpath("level_0") @@ -57,3 +56,30 @@ def test_find_first_file(user_area: bool, monkeypatch: pytest.MonkeyPatch, mocke _path.chmod(stat.S_IRWXU) assert _result +@pytest.mark.config +def test_find_first_file_at_root(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: + # Deactivate the server checks for this test + monkeypatch.setenv("SIMVUE_NO_SERVER_CHECK", "True") + monkeypatch.delenv("SIMVUE_TOKEN", False) + monkeypatch.delenv("SIMVUE_URL", False) + + @property + def _returns_self(self): + return self + + + with tempfile.TemporaryDirectory() as temp_d: + _path = pathlib.Path(temp_d) + _path_sub = _path.joinpath("level_0") + _path_sub.mkdir() + _path.joinpath("level_0").joinpath("simvue.toml").touch() + + for i in range(1, 5): + _path_sub = _path_sub.joinpath(f"level_{i}") + _path_sub.mkdir() + mocker.patch("pathlib.Path.parent", _returns_self) + mocker.patch("pathlib.Path.cwd", lambda *_: _path_sub) + + assert not sv_util.find_first_instance_of_file("simvue.toml", check_user_space=False) + +