From 5dcbb6897878468ce7a653d809a27ce11f567c22 Mon Sep 17 00:00:00 2001 From: Victor Morand Date: Mon, 20 Apr 2026 17:36:07 +0200 Subject: [PATCH 1/2] fix: translate xp path if using SSHStateProvider --- src/experimaestro/scheduler/remote/client.py | 12 ++-- src/experimaestro/scheduler/state_provider.py | 13 +++- .../tests/test_path_translation.py | 63 +++++++++++++++++++ src/experimaestro/tui/widgets/experiments.py | 2 +- 4 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/experimaestro/tests/test_path_translation.py diff --git a/src/experimaestro/scheduler/remote/client.py b/src/experimaestro/scheduler/remote/client.py index bf4e6501..0ffd8f1b 100644 --- a/src/experimaestro/scheduler/remote/client.py +++ b/src/experimaestro/scheduler/remote/client.py @@ -1533,21 +1533,19 @@ def is_remote(self) -> bool: """This is a remote provider""" return True - def get_display_path(self, job: "BaseJob") -> str: - """Get the remote path to display/copy for a job - - Translates the local cache path back to the remote path. + def translate_path(self, path: "Path") -> str: + """Translate the local cache path back to the remote path. Args: - job: Job to get display path for + path: Local path to translate Returns: Remote path string """ - if not job.path: + if not path: return "" - path_str = str(job.path) + path_str = str(path) local_cache_str = str(self.local_cache_dir) # If path is in local cache, translate back to remote path diff --git a/src/experimaestro/scheduler/state_provider.py b/src/experimaestro/scheduler/state_provider.py index dbf14988..a4a8784b 100644 --- a/src/experimaestro/scheduler/state_provider.py +++ b/src/experimaestro/scheduler/state_provider.py @@ -599,6 +599,17 @@ def is_remote(self) -> bool: """ return False + def translate_path(self, path: Path) -> str: + """Translate a local path to a display path (e.g. remote path) + + Args: + path: Local path to translate + + Returns: + Path string suitable for display and copying + """ + return str(path) + def get_display_path(self, job: BaseJob) -> str: """Get the path to display/copy for a job @@ -611,7 +622,7 @@ def get_display_path(self, job: BaseJob) -> str: Returns: Path string suitable for display and copying """ - return str(job.path) if job.path else "" + return self.translate_path(job.path) if job.path else "" # ============================================================================= diff --git a/src/experimaestro/tests/test_path_translation.py b/src/experimaestro/tests/test_path_translation.py new file mode 100644 index 00000000..a5962c49 --- /dev/null +++ b/src/experimaestro/tests/test_path_translation.py @@ -0,0 +1,63 @@ + +import pytest +from pathlib import Path +from experimaestro.scheduler.state_provider import StateProvider +from experimaestro.scheduler.remote.client import SSHStateProviderClient +from experimaestro.scheduler.interfaces import BaseJob, BaseExperiment + +class MockJob(BaseJob): + def __init__(self, path): + self.path = path + +class MockExperiment(BaseExperiment): + def __init__(self, workdir): + self.workdir = workdir + +class MockStateProvider(StateProvider): + def clean_job(self, *args, **kwargs): pass + def close(self, *args, **kwargs): pass + def get_all_jobs(self, *args, **kwargs): return [] + def get_current_run(self, *args, **kwargs): return None + def get_dependencies_map(self, *args, **kwargs): return {} + def get_experiment(self, *args, **kwargs): return None + def get_experiment_job_info(self, *args, **kwargs): return None + def get_experiment_runs(self, *args, **kwargs): return [] + def get_experiments(self, *args, **kwargs): return [] + def get_job(self, *args, **kwargs): return None + def get_jobs(self, *args, **kwargs): return [] + def get_services(self, *args, **kwargs): return [] + def get_tags_map(self, *args, **kwargs): return {} + def kill_job(self, *args, **kwargs): pass + +def test_state_provider_translate_path(): + provider = MockStateProvider() + path = Path("/some/local/path") + assert provider.translate_path(path) == str(path) + + job = MockJob(path) + assert provider.get_display_path(job) == str(path) + +def test_ssh_state_provider_client_translate_path(): + client = SSHStateProviderClient( + host="remote-host", + remote_workspace="/remote/workspace" + ) + # Set local_cache_dir which is normally set during connect/init + client.local_cache_dir = Path("/tmp/local/cache") + + # Path in local cache should be translated + local_path = Path("/tmp/local/cache/experiments/exp1/run1") + expected_remote = "/remote/workspace/experiments/exp1/run1" + assert client.translate_path(local_path) == expected_remote + + # Path NOT in local cache should be returned as-is + other_path = Path("/some/other/path") + assert client.translate_path(other_path) == str(other_path) + + # Test get_display_path (which now uses translate_path) + job = MockJob(local_path) + assert client.get_display_path(job) == expected_remote + + # Test for experiments (direct use of translate_path as in experiments.py) + exp = MockExperiment(local_path) + assert client.translate_path(exp.workdir) == expected_remote diff --git a/src/experimaestro/tui/widgets/experiments.py b/src/experimaestro/tui/widgets/experiments.py index 66b163a2..531454b9 100644 --- a/src/experimaestro/tui/widgets/experiments.py +++ b/src/experimaestro/tui/widgets/experiments.py @@ -97,7 +97,7 @@ def action_copy_path(self) -> None: None, ) if exp_info and exp_info.workdir: - path_str = str(exp_info.workdir) + path_str = self.state_provider.translate_path(exp_info.workdir) if copy(path_str): self.notify(f"Path copied: {path_str}", severity="information") else: From 56b887449b6f934d57a4cd73ddfe2b899ac2490c Mon Sep 17 00:00:00 2001 From: Victor Morand Date: Mon, 20 Apr 2026 17:59:46 +0200 Subject: [PATCH 2/2] fix: ruff --- src/experimaestro/tests/test_path_translation.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/experimaestro/tests/test_path_translation.py b/src/experimaestro/tests/test_path_translation.py index a5962c49..2d15e723 100644 --- a/src/experimaestro/tests/test_path_translation.py +++ b/src/experimaestro/tests/test_path_translation.py @@ -1,5 +1,4 @@ -import pytest from pathlib import Path from experimaestro.scheduler.state_provider import StateProvider from experimaestro.scheduler.remote.client import SSHStateProviderClient @@ -33,7 +32,7 @@ def test_state_provider_translate_path(): provider = MockStateProvider() path = Path("/some/local/path") assert provider.translate_path(path) == str(path) - + job = MockJob(path) assert provider.get_display_path(job) == str(path) @@ -44,20 +43,20 @@ def test_ssh_state_provider_client_translate_path(): ) # Set local_cache_dir which is normally set during connect/init client.local_cache_dir = Path("/tmp/local/cache") - + # Path in local cache should be translated local_path = Path("/tmp/local/cache/experiments/exp1/run1") expected_remote = "/remote/workspace/experiments/exp1/run1" assert client.translate_path(local_path) == expected_remote - + # Path NOT in local cache should be returned as-is other_path = Path("/some/other/path") assert client.translate_path(other_path) == str(other_path) - + # Test get_display_path (which now uses translate_path) job = MockJob(local_path) assert client.get_display_path(job) == expected_remote - + # Test for experiments (direct use of translate_path as in experiments.py) exp = MockExperiment(local_path) assert client.translate_path(exp.workdir) == expected_remote