diff --git a/packages/python/openproblems/CHANGELOG.md b/packages/python/openproblems/CHANGELOG.md index d87b1ff..4354b69 100644 --- a/packages/python/openproblems/CHANGELOG.md +++ b/packages/python/openproblems/CHANGELOG.md @@ -33,6 +33,10 @@ * `render_component_spec`: Include non-file arguments (e.g. `--seed`) in the arguments table, and fall back to an argument's `description` when it has no `summary`. +* `resolve_path`: Resolve a path starting with a `/` relative to the project root, the way + Viash does. `os.path.join()` treats such a path as absolute and silently dropped the project + root, so a config containing e.g. `__merge__: /src/api/file_dataset.yaml` failed to read. + # openproblems core Python v0.1.1 ## NEW FUNCTIONALITY diff --git a/packages/python/openproblems/src/openproblems/project/resolve_path.py b/packages/python/openproblems/src/openproblems/project/resolve_path.py index 20b1e12..6ec2a71 100644 --- a/packages/python/openproblems/src/openproblems/project/resolve_path.py +++ b/packages/python/openproblems/src/openproblems/project/resolve_path.py @@ -1,4 +1,7 @@ -def resolve_path(path: str, project_path: str, parent_path: str) -> str: +from __future__ import annotations + + +def resolve_path(path: str, project_path: str | None, parent_path: str) -> str: """ Resolve a path relative to a parent path or project path @@ -32,6 +35,12 @@ def resolve_path(path: str, project_path: str, parent_path: str) -> str: import os if path.startswith("/"): - return os.path.join(project_path, path) + if project_path is None: + raise ValueError( + f"Cannot resolve '{path}' relative to the project root: " + "no project root (_viash.yaml) was found" + ) + # note: os.path.join() discards project_path if path starts with a "/" + return os.path.abspath(os.path.join(project_path, path.lstrip("/"))) else: return os.path.abspath(os.path.join(parent_path, path)) diff --git a/packages/python/openproblems/tests/data/example_project/api/comp_method.yaml b/packages/python/openproblems/tests/data/example_project/api/comp_method.yaml index 3a93846..958e17a 100644 --- a/packages/python/openproblems/tests/data/example_project/api/comp_method.yaml +++ b/packages/python/openproblems/tests/data/example_project/api/comp_method.yaml @@ -8,7 +8,8 @@ info: A method to predict the task effects. arguments: - name: --input_train - __merge__: file_train.yaml + # project-absolute, i.e. relative to the _viash.yaml, unlike the merges below + __merge__: /api/file_train.yaml required: true direction: input - name: "--input_test" diff --git a/packages/python/openproblems/tests/test_project_resolve_path.py b/packages/python/openproblems/tests/test_project_resolve_path.py new file mode 100644 index 0000000..256a831 --- /dev/null +++ b/packages/python/openproblems/tests/test_project_resolve_path.py @@ -0,0 +1,27 @@ +import pytest +from openproblems.project.resolve_path import resolve_path + +PROJECT = "/path/to/project" +PARENT = "/path/to/project/src/api" + + +def test_resolve_path_relative_to_parent(): + assert resolve_path("file.yaml", PROJECT, PARENT) == PARENT + "/file.yaml" + assert resolve_path("./file.yaml", PROJECT, PARENT) == PARENT + "/file.yaml" + assert ( + resolve_path("../file.yaml", PROJECT, PARENT) + == "/path/to/project/src/file.yaml" + ) + + +def test_resolve_path_absolute_is_relative_to_project(): + # a leading slash means "relative to the _viash.yaml", not to the filesystem + assert ( + resolve_path("/src/api/file_dataset.yaml", PROJECT, PARENT) + == PROJECT + "/src/api/file_dataset.yaml" + ) + + +def test_resolve_path_absolute_without_project_root(): + with pytest.raises(ValueError, match="no project root"): + resolve_path("/src/api/file_dataset.yaml", None, PARENT)