-
Notifications
You must be signed in to change notification settings - Fork 81
Add Osmo Arena Experiment output downloader #933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
cvolkcvolk
wants to merge
11
commits into
main
Choose a base branch
from
cvolk/feature/download-experiment-output
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
0c3612f
Add Arena Experiment output downloader
cvolkcvolk 70a32d3
Default Experiment downloads to /eval
cvolkcvolk 9cf1b1e
Share workflow ID path-safety predicate
cvolkcvolk 27532ed
Make output base an argparse default
cvolkcvolk 0e17623
Keep output default local to argparse
cvolkcvolk 64ddcad
Allow overriding remote output base URI
cvolkcvolk 6693b43
Isolate downloader CLI exit tests
cvolkcvolk bf68579
Keep downloader CLI tests in normal phase
cvolkcvolk 4e1fb4e
Preserve argparse exit codes in CLI tests
cvolkcvolk 70f58af
Remove trailing slash from OSMO download URI
cvolkcvolk 84c28e2
Repair incomplete OSMO output downloads
cvolkcvolk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
isaaclab_arena/tests/test_osmo_download_experiment_output.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # Copyright (c) 2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | ||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Verify downloading complete Arena Experiment outputs from OSMO object storage.""" | ||
|
|
||
| from pathlib import Path | ||
| from types import SimpleNamespace | ||
|
|
||
| import pytest | ||
|
|
||
| from osmo.scripts.download_experiment_output import download_experiment_output, main | ||
| from osmo.workflows.workflow_constants import DATASETS_SWIFT_URL | ||
|
|
||
|
|
||
| def test_downloads_experiment_output_to_default_directory(monkeypatch, tmp_path): | ||
| monkeypatch.chdir(tmp_path) | ||
| captured_command = None | ||
|
|
||
| def capture_download(command): | ||
| nonlocal captured_command | ||
| captured_command = command | ||
| return SimpleNamespace(returncode=0) | ||
|
|
||
| monkeypatch.setattr("osmo.scripts.download_experiment_output.subprocess.run", capture_download) | ||
|
|
||
| return_code = main(["arena-experiment-123"]) | ||
|
|
||
| expected_output_directory = Path("arena_experiment_outputs/arena-experiment-123") | ||
| assert return_code == 0 | ||
| assert captured_command == [ | ||
| "osmo", | ||
| "data", | ||
| "download", | ||
| f"{DATASETS_SWIFT_URL}/arena-experiment-123/", | ||
| expected_output_directory.as_posix(), | ||
| ] | ||
| assert (tmp_path / expected_output_directory).is_dir() | ||
|
|
||
|
|
||
| def test_downloads_to_explicit_directory_without_shell_splitting(monkeypatch, tmp_path): | ||
| output_directory = tmp_path / "experiment output" | ||
| captured_command = None | ||
|
|
||
| def capture_download(command): | ||
| nonlocal captured_command | ||
| captured_command = command | ||
| return SimpleNamespace(returncode=0) | ||
|
|
||
| monkeypatch.setattr("osmo.scripts.download_experiment_output.subprocess.run", capture_download) | ||
|
|
||
| return_code = main([ | ||
| "arena-experiment-123", | ||
| "--output-directory", | ||
| str(output_directory), | ||
| ]) | ||
|
|
||
| assert return_code == 0 | ||
| assert captured_command[-1] == str(output_directory) | ||
| assert output_directory.is_dir() | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("workflow_id", ["", ".", "..", "workflow/name", "workflow name"]) | ||
| def test_rejects_invalid_workflow_id_before_download(monkeypatch, tmp_path, workflow_id): | ||
| def fail_if_called(command): | ||
| pytest.fail(f"Unexpected download command: {command}") | ||
|
|
||
| monkeypatch.setattr("osmo.scripts.download_experiment_output.subprocess.run", fail_if_called) | ||
|
|
||
| with pytest.raises(AssertionError, match="Invalid OSMO workflow ID"): | ||
| download_experiment_output(workflow_id, tmp_path / "output") | ||
|
|
||
|
|
||
| def test_rejects_nonempty_output_directory_before_download(monkeypatch, tmp_path): | ||
| output_directory = tmp_path / "existing-output" | ||
| output_directory.mkdir() | ||
| (output_directory / "stale-results.jsonl").write_text("stale", encoding="utf-8") | ||
|
|
||
| def fail_if_called(command): | ||
| pytest.fail(f"Unexpected download command: {command}") | ||
|
|
||
| monkeypatch.setattr("osmo.scripts.download_experiment_output.subprocess.run", fail_if_called) | ||
|
|
||
| with pytest.raises(AssertionError, match="Experiment output directory must be empty"): | ||
| download_experiment_output("arena-experiment-123", output_directory) | ||
|
|
||
|
|
||
| def test_propagates_osmo_download_failure(monkeypatch, tmp_path): | ||
| monkeypatch.setattr( | ||
| "osmo.scripts.download_experiment_output.subprocess.run", | ||
| lambda command: SimpleNamespace(returncode=23), | ||
| ) | ||
|
|
||
| return_code = download_experiment_output("arena-experiment-123", tmp_path / "output") | ||
|
|
||
| assert return_code == 23 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Copyright (c) 2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | ||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # Copyright (c) 2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | ||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Download one Arena Experiment output from OSMO object storage.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import shlex | ||
| import subprocess | ||
| from pathlib import Path | ||
|
|
||
| from osmo.workflows.utils.workflow_id import is_valid_workflow_id | ||
| from osmo.workflows.workflow_constants import DATASETS_SWIFT_URL | ||
|
|
||
| DEFAULT_OUTPUT_BASE_DIRECTORY = Path("arena_experiment_outputs") | ||
|
|
||
|
|
||
| def _workflow_id_argument(value: str) -> str: | ||
| """Parse a workflow ID that is safe to use as a remote and local path component.""" | ||
| if not is_valid_workflow_id(value) or value in {".", ".."}: | ||
| raise argparse.ArgumentTypeError(f"invalid OSMO workflow ID: {value!r}") | ||
| return value | ||
|
|
||
|
|
||
| def download_experiment_output(workflow_id: str, output_directory: Path) -> int: | ||
| """Download one complete Experiment output and return the OSMO process status. | ||
|
|
||
| Args: | ||
| workflow_id: OSMO workflow ID naming the published Experiment output. | ||
| output_directory: Exact local destination for the Experiment output. | ||
|
|
||
| Returns: | ||
| The ``osmo data download`` process status. | ||
| """ | ||
| assert is_valid_workflow_id(workflow_id) and workflow_id not in { | ||
|
cvolkcvolk marked this conversation as resolved.
Outdated
|
||
| ".", | ||
| "..", | ||
| }, f"Invalid OSMO workflow ID: {workflow_id!r}" | ||
| output_directory = output_directory.expanduser() | ||
| output_directory.mkdir(parents=True, exist_ok=True) | ||
| assert not any(output_directory.iterdir()), f"Experiment output directory must be empty: '{output_directory}'" | ||
|
cvolkcvolk marked this conversation as resolved.
cvolkcvolk marked this conversation as resolved.
|
||
| remote_uri = f"{DATASETS_SWIFT_URL}/{workflow_id}/" | ||
| command = ["osmo", "data", "download", remote_uri, output_directory.as_posix()] | ||
| print(f"$ {shlex.join(command)}", flush=True) | ||
| result = subprocess.run(command) | ||
| if result.returncode == 0: | ||
| print(f"Experiment output downloaded to '{output_directory}'.") | ||
| print(f"Open '{output_directory / 'index.html'}' to view the report.") | ||
| return result.returncode | ||
|
|
||
|
|
||
| def _create_argument_parser() -> argparse.ArgumentParser: | ||
| """Create the Experiment-output download command-line parser.""" | ||
| parser = argparse.ArgumentParser( | ||
| description=( | ||
| "Download one complete Arena Experiment output, including its report, per-Run results, JSONL outcomes, " | ||
| "and videos." | ||
| ), | ||
| formatter_class=argparse.RawDescriptionHelpFormatter, | ||
| epilog=""" | ||
| Examples: | ||
|
|
||
| python3 -m osmo.scripts.download_experiment_output arena-experiment-123 | ||
| python3 -m osmo.scripts.download_experiment_output arena-experiment-123 --output-directory ./my-output | ||
| """, | ||
| ) | ||
| parser.add_argument( | ||
| "workflow_id", | ||
| type=_workflow_id_argument, | ||
| help="OSMO workflow ID printed by the Arena Experiment submission command", | ||
| ) | ||
| parser.add_argument( | ||
| "--output-directory", | ||
| type=Path, | ||
| help="exact local destination (default: arena_experiment_outputs/<workflow-id>)", | ||
| ) | ||
| parser.allow_abbrev = False | ||
| return parser | ||
|
|
||
|
|
||
| def main(cli_args: list[str] | None = None) -> int: | ||
| """Download the Experiment output described on the command line.""" | ||
| parsed_arguments = _create_argument_parser().parse_args(cli_args) | ||
| output_directory = parsed_arguments.output_directory | ||
| if output_directory is None: | ||
| output_directory = DEFAULT_OUTPUT_BASE_DIRECTORY / parsed_arguments.workflow_id | ||
| return download_experiment_output(parsed_arguments.workflow_id, output_directory) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.