Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
14 changes: 11 additions & 3 deletions simvue/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -47,9 +48,16 @@ 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
_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
Expand Down
64 changes: 64 additions & 0 deletions tests/functional/test_utilities.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -19,3 +23,63 @@ 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_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")
_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

@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)


Loading