From 95cfe1a17f60a2ed43125b2d3541a8e1345acf2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 18:05:28 +0000 Subject: [PATCH 1/2] Initial plan From b29924aa851a847a69ef21d97d1d1a5d5aff6447 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 18:08:03 +0000 Subject: [PATCH 2/2] Normalize linker directory inputs and add focused path tests Agent-Logs-Url: https://github.com/NSLS2/cms-workflows/sessions/5377da0e-fd91-44b7-aaf0-39636cc60e17 Co-authored-by: swu4bnl <205365658+swu4bnl@users.noreply.github.com> --- linker.py | 9 ++++++-- test_linker_paths.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 test_linker_paths.py diff --git a/linker.py b/linker.py index c4ec657..2231b9b 100644 --- a/linker.py +++ b/linker.py @@ -25,6 +25,10 @@ def chmod_and_chown(path, *, uid=None, gid=None, mode=0o775): #if uid is not None and gid is not None: # os.chown(path, uid, gid) +def make_relative_path(path_value): + path = Path(path_value) + return Path(*path.parts[1:]) if path.is_absolute() else path + @task(retries=2, retry_delay_seconds=10) def create_symlinks(ref, api_key=None, dry_run=False): """ @@ -56,13 +60,14 @@ def create_symlinks(ref, api_key=None, dry_run=False): if path_expr_alias := doc.get("experiment_alias_directory"): path_proposal = Path(f"/nsls2/data/cms/proposals/{doc['cycle']}/{doc['data_session']}") # stats = path_proposal.stat() - path_expr = path_proposal / "experiments" # experiments directory + experiments_dir = make_relative_path(doc.get("experiments_directory", "experiments")) + path_expr = path_proposal / experiments_dir if dry_run: logger.info(f"Dry run: mkdir {path_expr}") else: path_expr.mkdir(exist_ok=True, parents=True) #chmod_and_chown(path_expr, uid=stats.st_uid, gid=stats.st_gid) - path_expr_alias = path_expr / path_expr_alias + path_expr_alias = path_expr / make_relative_path(path_expr_alias) if dry_run: logger.info(f"Dry run: mkdir {path_expr_alias}") else: diff --git a/test_linker_paths.py b/test_linker_paths.py new file mode 100644 index 0000000..f23c738 --- /dev/null +++ b/test_linker_paths.py @@ -0,0 +1,52 @@ +import sys +import types +import unittest +from pathlib import Path + + +prefect_stub = types.ModuleType("prefect") + +def task(*args, **kwargs): + def decorator(func): + return func + + return decorator + + +def get_run_logger(): + return None + + +prefect_stub.task = task +prefect_stub.get_run_logger = get_run_logger +sys.modules.setdefault("prefect", prefect_stub) + + +data_validation_stub = types.ModuleType("data_validation") +data_validation_stub.get_run = lambda *args, **kwargs: None +sys.modules.setdefault("data_validation", data_validation_stub) + + +import linker # noqa: E402 + + +class MakeRelativePathTests(unittest.TestCase): + def test_relative_path_is_unchanged(self): + self.assertEqual(linker.make_relative_path("experiments"), Path("experiments")) + + def test_absolute_path_becomes_relative(self): + self.assertEqual(linker.make_relative_path("/experiments"), Path("experiments")) + + def test_root_path_becomes_current_directory(self): + self.assertEqual(linker.make_relative_path("/"), Path(".")) + + def test_joined_path_never_overrides_proposal_path(self): + proposal = Path("/nsls2/data/cms/proposals/2026-1/pass-12345") + self.assertEqual( + proposal / linker.make_relative_path("/experiments"), + proposal / "experiments", + ) + + +if __name__ == "__main__": + unittest.main()