From 072bc9044de6c4592bf53844930a574f05f71214 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 18 Feb 2026 14:32:13 +0100 Subject: [PATCH 1/3] resolve all paths before expressing target relative to root --- src/osekit/core_api/json_serializer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/osekit/core_api/json_serializer.py b/src/osekit/core_api/json_serializer.py index e728e261..3e34e2e6 100644 --- a/src/osekit/core_api/json_serializer.py +++ b/src/osekit/core_api/json_serializer.py @@ -27,7 +27,9 @@ def absolute_to_relative( Target path expressed relative to the root path. """ - target_path, root_path = map(Path, (target_path, root_path)) + target_path, root_path = ( + path.resolve() for path in map(Path, (target_path, root_path)) + ) return Path(os.path.relpath(path=target_path, start=root_path)) From 780ec473217ef3879a042bfc59e4deb8b1dacc5b Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 18 Feb 2026 15:38:48 +0100 Subject: [PATCH 2/3] add absolute_to_relative path tests --- tests/test_serialization.py | 64 ++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 824b4a55..49603d1b 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from pathlib import Path, PureWindowsPath import numpy as np @@ -18,7 +19,11 @@ from osekit.core_api.audio_file import AudioFile from osekit.core_api.frequency_scale import Scale, ScalePart from osekit.core_api.instrument import Instrument -from osekit.core_api.json_serializer import relative_to_absolute, set_path_reference +from osekit.core_api.json_serializer import ( + absolute_to_relative, + relative_to_absolute, + set_path_reference, +) from osekit.core_api.spectro_data import SpectroData from osekit.core_api.spectro_dataset import SpectroDataset from osekit.core_api.spectro_file import SpectroFile @@ -998,6 +1003,63 @@ def test_relative_to_absolute(target: str, root: str, expected: str) -> None: assert path.resolve() == Path(PureWindowsPath(expected)).resolve() +@pytest.mark.parametrize( + ("target", "use_alias"), + [ + pytest.param( + ".", + False, + id="same_path", + ), + pytest.param( + "../", + False, + id="parent", + ), + pytest.param( + "../milkkisses/serpentskirt", + False, + id="folder_in_parent", + ), + pytest.param( + "milkkisses/serpentskirt", + False, + id="folder_in_child", + ), + pytest.param( + "serpentskirt", + True, + id="drive_alias", + marks=pytest.mark.skipif( + os.name == "nt", + reason="Symlinks require admin or developer mode on Windows", + ), + ), + ], +) +def test_absolute_ro_relative( + tmp_path: Path, + target: str, + use_alias: bool, +) -> None: + real_root = tmp_path / "cocteau" + real_root.mkdir(parents=True, exist_ok=True) + + if use_alias: + alias_root = tmp_path / "twins" + alias_root.symlink_to(real_root) + root = alias_root + else: + root = real_root + + expected = Path(target) + + target = (real_root / target).resolve() + result = absolute_to_relative(target_path=target, root_path=root) + + assert result == expected + + def test_relative_paths_serialization(tmp_path: Path) -> None: dictionary = { "path": str(tmp_path / "user" / "cool"), From 2b76159b5b13c6dfb8320cf0c4dc35b46226c055 Mon Sep 17 00:00:00 2001 From: Gautzilla Date: Wed, 18 Feb 2026 16:01:28 +0100 Subject: [PATCH 3/3] make symlink skip condition more precise --- tests/test_serialization.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 49603d1b..210f9a09 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os from pathlib import Path, PureWindowsPath import numpy as np @@ -1003,6 +1002,16 @@ def test_relative_to_absolute(target: str, root: str, expected: str) -> None: assert path.resolve() == Path(PureWindowsPath(expected)).resolve() +def can_symlink(tmp_path: Path) -> bool: + try: + test_link = tmp_path / "link_test" + test_link.symlink_to(tmp_path) + test_link.unlink() + except OSError: + return False + return True + + @pytest.mark.parametrize( ("target", "use_alias"), [ @@ -1030,10 +1039,6 @@ def test_relative_to_absolute(target: str, root: str, expected: str) -> None: "serpentskirt", True, id="drive_alias", - marks=pytest.mark.skipif( - os.name == "nt", - reason="Symlinks require admin or developer mode on Windows", - ), ), ], ) @@ -1042,6 +1047,9 @@ def test_absolute_ro_relative( target: str, use_alias: bool, ) -> None: + if use_alias and not can_symlink(tmp_path): + pytest.skip("Symlink not permitted on this system.") + real_root = tmp_path / "cocteau" real_root.mkdir(parents=True, exist_ok=True)