Skip to content

Commit 565ec2b

Browse files
authored
Merge pull request #912 from simvue-io/feature/find-nearest-config
Find nearest Simvue config in nested directories
2 parents 32d6dbe + 396c466 commit 565ec2b

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## Unreleased
4+
5+
- Added search for Simvue configuration within the parent file hierarchy of the current directory.
6+
37
## [v2.3.6](https://github.com/simvue-io/python-api/releases/tag/v2.3.6) - 2026-02-27
48

59
- Fixed retry capability allowing metrics to be re-sent if specific server errors are returned.

simvue/utilities.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def find_first_instance_of_file(
3333
3434
Parameters
3535
----------
36+
file_names: list[str] | str
3637
candidate names of file to locate
3738
check_user_space: bool, optional
3839
check the users home area if current working directory is not
@@ -47,9 +48,16 @@ def find_first_instance_of_file(
4748
file_names = [file_names]
4849

4950
for file_name in file_names:
50-
_user_file = pathlib.Path.cwd().joinpath(file_name)
51-
if _user_file.exists():
52-
return _user_file
51+
_current_path = pathlib.Path.cwd()
52+
53+
while os.access(_current_path, os.R_OK):
54+
_user_file = _current_path.joinpath(file_name)
55+
if _user_file.exists():
56+
return _user_file
57+
_parent_path = _current_path.parent
58+
if _parent_path == _current_path: # parent of root dir is root dir
59+
break
60+
_current_path = _parent_path
5361

5462
# If the user is running on different mounted volume or outside
5563
# of their user space then the above will not return the file

tests/functional/test_utilities.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import pytest
22
import tempfile
33
import os.path
4+
import pathlib
5+
import stat
6+
7+
from pytest_mock import MockerFixture
48

59
import simvue.utilities as sv_util
610

@@ -19,3 +23,63 @@ def test_calculate_hash(is_file: bool, hash: str) -> None:
1923
assert sv_util.calculate_sha256(filename=out_file, is_file=is_file) == hash
2024
else:
2125
assert sv_util.calculate_sha256(filename="temp.txt", is_file=is_file) == hash
26+
27+
@pytest.mark.config
28+
@pytest.mark.parametrize(
29+
"user_area", (True, False),
30+
ids=("permitted_dir", "out_of_user_area")
31+
)
32+
def test_find_first_file_search(user_area: bool, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None:
33+
# Deactivate the server checks for this test
34+
monkeypatch.setenv("SIMVUE_NO_SERVER_CHECK", "True")
35+
monkeypatch.delenv("SIMVUE_TOKEN", False)
36+
monkeypatch.delenv("SIMVUE_URL", False)
37+
38+
with tempfile.TemporaryDirectory() as temp_d:
39+
_path = pathlib.Path(temp_d)
40+
_path_sub = _path.joinpath("level_0")
41+
_path_sub.mkdir()
42+
43+
for i in range(1, 5):
44+
_path_sub = _path_sub.joinpath(f"level_{i}")
45+
_path_sub.mkdir()
46+
mocker.patch("pathlib.Path.cwd", lambda *_: _path_sub)
47+
48+
if user_area:
49+
_path.joinpath("level_0").joinpath("simvue.toml").touch()
50+
_path.chmod(stat.S_IXUSR)
51+
_result = sv_util.find_first_instance_of_file("simvue.toml", check_user_space=False)
52+
else:
53+
_path.chmod(stat.S_IXUSR)
54+
_result = sv_util.find_first_instance_of_file("simvue.toml", check_user_space=False) is None
55+
56+
_path.chmod(stat.S_IRWXU)
57+
assert _result
58+
59+
@pytest.mark.config
60+
def test_find_first_file_at_root(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None:
61+
# Deactivate the server checks for this test
62+
monkeypatch.setenv("SIMVUE_NO_SERVER_CHECK", "True")
63+
monkeypatch.delenv("SIMVUE_TOKEN", False)
64+
monkeypatch.delenv("SIMVUE_URL", False)
65+
66+
@property
67+
def _returns_self(self):
68+
return self
69+
70+
71+
with tempfile.TemporaryDirectory() as temp_d:
72+
_path = pathlib.Path(temp_d)
73+
_path_sub = _path.joinpath("level_0")
74+
_path_sub.mkdir()
75+
_path.joinpath("level_0").joinpath("simvue.toml").touch()
76+
77+
for i in range(1, 5):
78+
_path_sub = _path_sub.joinpath(f"level_{i}")
79+
_path_sub.mkdir()
80+
mocker.patch("pathlib.Path.parent", _returns_self)
81+
mocker.patch("pathlib.Path.cwd", lambda *_: _path_sub)
82+
83+
assert not sv_util.find_first_instance_of_file("simvue.toml", check_user_space=False)
84+
85+

0 commit comments

Comments
 (0)