galaxy-cli 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. galaxy_cli-1.0.0/LICENSE +22 -0
  2. galaxy_cli-1.0.0/PKG-INFO +90 -0
  3. galaxy_cli-1.0.0/README.md +71 -0
  4. galaxy_cli-1.0.0/cli_anything/galaxy/__init__.py +3 -0
  5. galaxy_cli-1.0.0/cli_anything/galaxy/__main__.py +5 -0
  6. galaxy_cli-1.0.0/cli_anything/galaxy/core/__init__.py +0 -0
  7. galaxy_cli-1.0.0/cli_anything/galaxy/core/config.py +39 -0
  8. galaxy_cli-1.0.0/cli_anything/galaxy/core/dataset.py +100 -0
  9. galaxy_cli-1.0.0/cli_anything/galaxy/core/history.py +98 -0
  10. galaxy_cli-1.0.0/cli_anything/galaxy/core/invocation.py +79 -0
  11. galaxy_cli-1.0.0/cli_anything/galaxy/core/job.py +88 -0
  12. galaxy_cli-1.0.0/cli_anything/galaxy/core/library.py +63 -0
  13. galaxy_cli-1.0.0/cli_anything/galaxy/core/session.py +103 -0
  14. galaxy_cli-1.0.0/cli_anything/galaxy/core/tool.py +176 -0
  15. galaxy_cli-1.0.0/cli_anything/galaxy/core/workflow.py +131 -0
  16. galaxy_cli-1.0.0/cli_anything/galaxy/galaxy_cli.py +882 -0
  17. galaxy_cli-1.0.0/cli_anything/galaxy/skills/SKILL.md +165 -0
  18. galaxy_cli-1.0.0/cli_anything/galaxy/tests/__init__.py +0 -0
  19. galaxy_cli-1.0.0/cli_anything/galaxy/tests/test_core.py +712 -0
  20. galaxy_cli-1.0.0/cli_anything/galaxy/tests/test_full_e2e.py +313 -0
  21. galaxy_cli-1.0.0/cli_anything/galaxy/utils/__init__.py +0 -0
  22. galaxy_cli-1.0.0/cli_anything/galaxy/utils/galaxy_backend.py +266 -0
  23. galaxy_cli-1.0.0/cli_anything/galaxy/utils/repl_skin.py +521 -0
  24. galaxy_cli-1.0.0/galaxy_cli.egg-info/PKG-INFO +90 -0
  25. galaxy_cli-1.0.0/galaxy_cli.egg-info/SOURCES.txt +30 -0
  26. galaxy_cli-1.0.0/galaxy_cli.egg-info/dependency_links.txt +1 -0
  27. galaxy_cli-1.0.0/galaxy_cli.egg-info/entry_points.txt +2 -0
  28. galaxy_cli-1.0.0/galaxy_cli.egg-info/requires.txt +3 -0
  29. galaxy_cli-1.0.0/galaxy_cli.egg-info/top_level.txt +1 -0
  30. galaxy_cli-1.0.0/pyproject.toml +42 -0
  31. galaxy_cli-1.0.0/setup.cfg +4 -0
  32. galaxy_cli-1.0.0/setup.py +4 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cli-anything
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: galaxy-cli
3
+ Version: 1.0.0
4
+ Summary: CLI harness for the Galaxy bioinformatics platform
5
+ Author: cli-anything
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Environment :: Console
11
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: click>=8.0.0
16
+ Requires-Dist: prompt-toolkit>=3.0.0
17
+ Requires-Dist: requests>=2.28.0
18
+ Dynamic: license-file
19
+
20
+ # galaxy-cli
21
+
22
+ CLI harness for the [Galaxy](https://galaxyproject.org/) bioinformatics platform.
23
+ It wraps Galaxy's REST API to provide command-line and REPL access to histories,
24
+ datasets, tools, workflows, jobs, and libraries.
25
+
26
+ This project was initially generated with `cli-anything` and then refined into
27
+ the standalone `galaxy-cli` package.
28
+
29
+ ## Prerequisites
30
+
31
+ - Python 3.10+
32
+ - A running Galaxy server
33
+ - A Galaxy API key from your target instance
34
+
35
+ ## Installation
36
+
37
+ Install from PyPI with `uv` or `pip`:
38
+
39
+ ```bash
40
+ uv tool install galaxy-cli
41
+ ```
42
+
43
+ ```bash
44
+ python3 -m pip install galaxy-cli
45
+ ```
46
+
47
+ For local development from this repository:
48
+
49
+ ```bash
50
+ python3 -m pip install .
51
+ ```
52
+
53
+ Verify installation:
54
+
55
+ ```bash
56
+ which galaxy-cli
57
+ galaxy-cli --version
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ Set your Galaxy server URL and API key:
63
+
64
+ ```bash
65
+ export GALAXY_URL=https://usegalaxy.org
66
+ export GALAXY_API_KEY=your-api-key
67
+
68
+ galaxy-cli config test
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ```bash
74
+ galaxy-cli history list
75
+ galaxy-cli history create "My Analysis"
76
+ galaxy-cli tool search "bowtie"
77
+ galaxy-cli --json workflow list
78
+ ```
79
+
80
+ Run the REPL:
81
+
82
+ ```bash
83
+ galaxy-cli
84
+ ```
85
+
86
+ ## Tests
87
+
88
+ ```bash
89
+ python3 -m pytest cli_anything/galaxy/tests/ -v
90
+ ```
@@ -0,0 +1,71 @@
1
+ # galaxy-cli
2
+
3
+ CLI harness for the [Galaxy](https://galaxyproject.org/) bioinformatics platform.
4
+ It wraps Galaxy's REST API to provide command-line and REPL access to histories,
5
+ datasets, tools, workflows, jobs, and libraries.
6
+
7
+ This project was initially generated with `cli-anything` and then refined into
8
+ the standalone `galaxy-cli` package.
9
+
10
+ ## Prerequisites
11
+
12
+ - Python 3.10+
13
+ - A running Galaxy server
14
+ - A Galaxy API key from your target instance
15
+
16
+ ## Installation
17
+
18
+ Install from PyPI with `uv` or `pip`:
19
+
20
+ ```bash
21
+ uv tool install galaxy-cli
22
+ ```
23
+
24
+ ```bash
25
+ python3 -m pip install galaxy-cli
26
+ ```
27
+
28
+ For local development from this repository:
29
+
30
+ ```bash
31
+ python3 -m pip install .
32
+ ```
33
+
34
+ Verify installation:
35
+
36
+ ```bash
37
+ which galaxy-cli
38
+ galaxy-cli --version
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ Set your Galaxy server URL and API key:
44
+
45
+ ```bash
46
+ export GALAXY_URL=https://usegalaxy.org
47
+ export GALAXY_API_KEY=your-api-key
48
+
49
+ galaxy-cli config test
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```bash
55
+ galaxy-cli history list
56
+ galaxy-cli history create "My Analysis"
57
+ galaxy-cli tool search "bowtie"
58
+ galaxy-cli --json workflow list
59
+ ```
60
+
61
+ Run the REPL:
62
+
63
+ ```bash
64
+ galaxy-cli
65
+ ```
66
+
67
+ ## Tests
68
+
69
+ ```bash
70
+ python3 -m pytest cli_anything/galaxy/tests/ -v
71
+ ```
@@ -0,0 +1,3 @@
1
+ """galaxy-cli: CLI harness for Galaxy bioinformatics platform."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as: python -m cli_anything.galaxy"""
2
+
3
+ from cli_anything.galaxy.galaxy_cli import main
4
+
5
+ main()
File without changes
@@ -0,0 +1,39 @@
1
+ """Configuration management for Galaxy CLI."""
2
+
3
+ from cli_anything.galaxy.utils.galaxy_backend import GalaxyClient
4
+
5
+
6
+ def set_url(url):
7
+ """Save the Galaxy server URL."""
8
+ GalaxyClient.save_config("url", url.rstrip("/"))
9
+ return {"url": url.rstrip("/"), "status": "saved"}
10
+
11
+
12
+ def set_key(api_key):
13
+ """Save the Galaxy API key."""
14
+ GalaxyClient.save_config("api_key", api_key)
15
+ return {"status": "saved"}
16
+
17
+
18
+ def show_config():
19
+ """Show current configuration."""
20
+ cfg = GalaxyClient.load_config()
21
+ result = {"url": cfg.get("url", "(not set)"), "api_key": "(not set)"}
22
+ key = cfg.get("api_key")
23
+ if key:
24
+ result["api_key"] = key[:8] + "..." + key[-4:] if len(key) > 12 else "***"
25
+ return result
26
+
27
+
28
+ def test_connection(client):
29
+ """Test the connection to Galaxy server."""
30
+ version = client.get_version()
31
+ user = client.whoami()
32
+ return {
33
+ "status": "connected",
34
+ "galaxy_version": version.get("version_major", "unknown"),
35
+ "extra": version.get("version_minor", ""),
36
+ "user": user.get("username", "unknown"),
37
+ "email": user.get("email", "unknown"),
38
+ "user_id": user.get("id", "unknown"),
39
+ }
@@ -0,0 +1,100 @@
1
+ """Dataset management — upload, download, show, delete, peek at datasets."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def upload_dataset(client, history_id, file_path, file_type="auto", dbkey="?"):
7
+ """Upload a local file to a Galaxy history."""
8
+ result = client.upload_file(file_path, history_id, file_type=file_type, dbkey=dbkey)
9
+ outputs = result.get("outputs", [])
10
+ if outputs:
11
+ ds = outputs[0]
12
+ return {
13
+ "id": ds.get("id", ""),
14
+ "name": ds.get("name", ""),
15
+ "state": ds.get("state", ""),
16
+ "history_id": history_id,
17
+ "file_type": ds.get("extension", file_type),
18
+ }
19
+ return {"status": "uploaded", "history_id": history_id, "raw": result}
20
+
21
+
22
+ def show_dataset(client, dataset_id, history_id=None):
23
+ """Show details of a dataset."""
24
+ if history_id:
25
+ info = client.get(f"histories/{history_id}/contents/{dataset_id}")
26
+ else:
27
+ info = client.get(f"datasets/{dataset_id}")
28
+ return {
29
+ "id": info.get("id", ""),
30
+ "name": info.get("name", ""),
31
+ "state": info.get("state", ""),
32
+ "extension": info.get("extension", ""),
33
+ "file_size": info.get("file_size", 0),
34
+ "genome_build": info.get("genome_build", "?"),
35
+ "data_type": info.get("data_type", ""),
36
+ "create_time": info.get("create_time", ""),
37
+ "update_time": info.get("update_time", ""),
38
+ "deleted": info.get("deleted", False),
39
+ "visible": info.get("visible", True),
40
+ "metadata": info.get("metadata", {}),
41
+ "history_id": info.get("history_id", history_id or ""),
42
+ "history_content_type": info.get("history_content_type", ""),
43
+ "misc_info": info.get("misc_info", ""),
44
+ "misc_blurb": info.get("misc_blurb", ""),
45
+ }
46
+
47
+
48
+ def download_dataset(client, dataset_id, output_path):
49
+ """Download a dataset to a local file."""
50
+ return client.download_dataset(dataset_id, output_path)
51
+
52
+
53
+ def peek_dataset(client, dataset_id, lines=10):
54
+ """Get a preview of dataset contents."""
55
+ info = client.get(f"datasets/{dataset_id}", params={"data_type": "raw_data", "provider": "base"})
56
+ # The peek is typically included in the dataset info
57
+ if "peek" in info:
58
+ raw_peek = info["peek"]
59
+ peek_lines = raw_peek.strip().split("\n")[:lines]
60
+ return {"id": dataset_id, "lines": peek_lines, "total_shown": len(peek_lines)}
61
+ # Fallback: try getting raw content directly
62
+ try:
63
+ preview = client.get(f"datasets/{dataset_id}/display", params={"offset": 0, "limit": lines})
64
+ if isinstance(preview, dict) and "raw" in preview:
65
+ peek_lines = preview["raw"].strip().split("\n")[:lines]
66
+ return {"id": dataset_id, "lines": peek_lines, "total_shown": len(peek_lines)}
67
+ except Exception:
68
+ pass
69
+ return {"id": dataset_id, "lines": [], "total_shown": 0, "note": "Preview not available"}
70
+
71
+
72
+ def delete_dataset(client, dataset_id, history_id, purge=False):
73
+ """Delete a dataset from a history."""
74
+ payload = {"deleted": True}
75
+ if purge:
76
+ payload["purged"] = True
77
+ client.put(f"histories/{history_id}/contents/{dataset_id}", json_data=payload)
78
+ return {"id": dataset_id, "history_id": history_id, "status": "deleted", "purged": purge}
79
+
80
+
81
+ def list_datasets(client, history_id, limit=50, offset=0, deleted=False):
82
+ """List datasets in a history."""
83
+ params = {"limit": limit, "offset": offset}
84
+ if deleted:
85
+ params["deleted"] = True
86
+ items = client.get(f"histories/{history_id}/contents", params=params)
87
+ return [
88
+ {
89
+ "id": item["id"],
90
+ "name": item.get("name", ""),
91
+ "type": item.get("type", ""),
92
+ "state": item.get("state", ""),
93
+ "extension": item.get("extension", ""),
94
+ "file_size": item.get("file_size", 0),
95
+ "deleted": item.get("deleted", False),
96
+ "visible": item.get("visible", True),
97
+ }
98
+ for item in items
99
+ if item.get("type") in ("file", "dataset")
100
+ ]
@@ -0,0 +1,98 @@
1
+ """History management — create, list, show, delete, export Galaxy histories."""
2
+
3
+
4
+ def list_histories(client, deleted=False, limit=50, offset=0):
5
+ """List histories for the current user."""
6
+ params = {"limit": limit, "offset": offset}
7
+ if deleted:
8
+ params["deleted"] = True
9
+ histories = client.get("histories", params=params)
10
+ return [
11
+ {
12
+ "id": h["id"],
13
+ "name": h.get("name", ""),
14
+ "state": h.get("state", ""),
15
+ "size": h.get("size", 0),
16
+ "update_time": h.get("update_time", ""),
17
+ "deleted": h.get("deleted", False),
18
+ "dataset_count": h.get("count", 0),
19
+ }
20
+ for h in histories
21
+ ]
22
+
23
+
24
+ def create_history(client, name="Unnamed history"):
25
+ """Create a new history."""
26
+ result = client.post("histories", json_data={"name": name})
27
+ return {
28
+ "id": result["id"],
29
+ "name": result.get("name", name),
30
+ "state": result.get("state", ""),
31
+ "create_time": result.get("create_time", ""),
32
+ }
33
+
34
+
35
+ def show_history(client, history_id, contents=False):
36
+ """Show details of a history."""
37
+ info = client.get(f"histories/{history_id}")
38
+ result = {
39
+ "id": info["id"],
40
+ "name": info.get("name", ""),
41
+ "state": info.get("state", ""),
42
+ "size": info.get("size", 0),
43
+ "create_time": info.get("create_time", ""),
44
+ "update_time": info.get("update_time", ""),
45
+ "annotation": info.get("annotation", ""),
46
+ "tags": info.get("tags", []),
47
+ "deleted": info.get("deleted", False),
48
+ "importable": info.get("importable", False),
49
+ "published": info.get("published", False),
50
+ "state_details": info.get("state_details", {}),
51
+ }
52
+ if contents:
53
+ items = client.get(f"histories/{history_id}/contents")
54
+ result["contents"] = [
55
+ {
56
+ "id": item["id"],
57
+ "name": item.get("name", ""),
58
+ "type": item.get("type", ""),
59
+ "state": item.get("state", ""),
60
+ "extension": item.get("extension", ""),
61
+ "deleted": item.get("deleted", False),
62
+ "visible": item.get("visible", True),
63
+ }
64
+ for item in items
65
+ ]
66
+ return result
67
+
68
+
69
+ def delete_history(client, history_id, purge=False):
70
+ """Delete a history."""
71
+ payload = {}
72
+ if purge:
73
+ payload["purge"] = True
74
+ client.delete(f"histories/{history_id}", json_data=payload)
75
+ return {"id": history_id, "status": "deleted", "purged": purge}
76
+
77
+
78
+ def update_history(client, history_id, name=None, annotation=None, tags=None):
79
+ """Update a history's metadata."""
80
+ payload = {}
81
+ if name is not None:
82
+ payload["name"] = name
83
+ if annotation is not None:
84
+ payload["annotation"] = annotation
85
+ if tags is not None:
86
+ payload["tags"] = tags
87
+ result = client.put(f"histories/{history_id}", json_data=payload)
88
+ return {"id": history_id, "updated": list(payload.keys()), "name": result.get("name", "")}
89
+
90
+
91
+ def export_history(client, history_id):
92
+ """Start a history export (archive creation)."""
93
+ result = client.put(f"histories/{history_id}/exports")
94
+ return {
95
+ "id": history_id,
96
+ "status": "export_started",
97
+ "download_url": result.get("download_url", ""),
98
+ }
@@ -0,0 +1,79 @@
1
+ """Workflow invocation management — list, show, cancel invocations."""
2
+
3
+ import time
4
+
5
+
6
+ def list_invocations(client, workflow_id=None, history_id=None, limit=50):
7
+ """List workflow invocations."""
8
+ params = {"limit": limit}
9
+ if workflow_id:
10
+ params["workflow_id"] = workflow_id
11
+ if history_id:
12
+ params["history_id"] = history_id
13
+ invocations = client.get("invocations", params=params)
14
+ return [
15
+ {
16
+ "id": inv.get("id", ""),
17
+ "workflow_id": inv.get("workflow_id", ""),
18
+ "history_id": inv.get("history_id", ""),
19
+ "state": inv.get("state", ""),
20
+ "create_time": inv.get("create_time", ""),
21
+ "update_time": inv.get("update_time", ""),
22
+ }
23
+ for inv in invocations
24
+ ]
25
+
26
+
27
+ def show_invocation(client, invocation_id):
28
+ """Show details of a workflow invocation."""
29
+ info = client.get(f"invocations/{invocation_id}")
30
+ steps = []
31
+ for step in info.get("steps", []):
32
+ steps.append({
33
+ "id": step.get("id", ""),
34
+ "order_index": step.get("order_index", 0),
35
+ "state": step.get("state", ""),
36
+ "job_id": step.get("job_id"),
37
+ "update_time": step.get("update_time", ""),
38
+ "action": step.get("action"),
39
+ })
40
+ return {
41
+ "id": info.get("id", invocation_id),
42
+ "workflow_id": info.get("workflow_id", ""),
43
+ "history_id": info.get("history_id", ""),
44
+ "state": info.get("state", ""),
45
+ "create_time": info.get("create_time", ""),
46
+ "update_time": info.get("update_time", ""),
47
+ "steps": steps,
48
+ "inputs": info.get("inputs", {}),
49
+ "outputs": info.get("outputs", {}),
50
+ }
51
+
52
+
53
+ def cancel_invocation(client, invocation_id):
54
+ """Cancel a running invocation."""
55
+ client.delete(f"invocations/{invocation_id}")
56
+ return {"id": invocation_id, "status": "cancelled"}
57
+
58
+
59
+ def wait_for_invocation(client, invocation_id, max_wait=1800, poll_interval=10):
60
+ """Wait for a workflow invocation to complete."""
61
+ terminal_states = {"scheduled", "cancelled", "failed", "error"}
62
+ elapsed = 0
63
+ while elapsed < max_wait:
64
+ info = client.get(f"invocations/{invocation_id}")
65
+ state = info.get("state", "unknown")
66
+ # "scheduled" means all steps are scheduled (essentially done from invocation perspective)
67
+ if state in terminal_states:
68
+ return {
69
+ "id": invocation_id,
70
+ "state": state,
71
+ "waited_seconds": elapsed,
72
+ }
73
+ time.sleep(poll_interval)
74
+ elapsed += poll_interval
75
+ return {
76
+ "id": invocation_id,
77
+ "state": "timeout",
78
+ "waited_seconds": elapsed,
79
+ }
@@ -0,0 +1,88 @@
1
+ """Job management — list, show, cancel, rerun Galaxy jobs."""
2
+
3
+ import time
4
+
5
+
6
+ def list_jobs(client, history_id=None, state=None, tool_id=None, limit=50, offset=0):
7
+ """List jobs, optionally filtered."""
8
+ params = {"limit": limit, "offset": offset, "order_by": "update_time"}
9
+ if history_id:
10
+ params["history_id"] = history_id
11
+ if state:
12
+ params["state"] = state
13
+ if tool_id:
14
+ params["tool_id"] = tool_id
15
+ jobs = client.get("jobs", params=params)
16
+ return [
17
+ {
18
+ "id": j.get("id", ""),
19
+ "tool_id": j.get("tool_id", ""),
20
+ "state": j.get("state", ""),
21
+ "create_time": j.get("create_time", ""),
22
+ "update_time": j.get("update_time", ""),
23
+ "exit_code": j.get("exit_code"),
24
+ }
25
+ for j in jobs
26
+ ]
27
+
28
+
29
+ def show_job(client, job_id, full=False):
30
+ """Show details of a job."""
31
+ params = {"full": True} if full else {}
32
+ info = client.get(f"jobs/{job_id}", params=params)
33
+ result = {
34
+ "id": info.get("id", ""),
35
+ "tool_id": info.get("tool_id", ""),
36
+ "state": info.get("state", ""),
37
+ "create_time": info.get("create_time", ""),
38
+ "update_time": info.get("update_time", ""),
39
+ "exit_code": info.get("exit_code"),
40
+ "history_id": info.get("history_id", ""),
41
+ "command_line": info.get("command_line", ""),
42
+ "stdout": info.get("tool_stdout", ""),
43
+ "stderr": info.get("tool_stderr", ""),
44
+ }
45
+ if full:
46
+ result["inputs"] = info.get("inputs", {})
47
+ result["outputs"] = info.get("outputs", {})
48
+ result["params"] = info.get("params", {})
49
+ return result
50
+
51
+
52
+ def cancel_job(client, job_id):
53
+ """Cancel a running job."""
54
+ client.delete(f"jobs/{job_id}")
55
+ return {"id": job_id, "status": "cancelled"}
56
+
57
+
58
+ def rerun_job(client, job_id):
59
+ """Get parameters to rerun a job."""
60
+ info = client.get(f"jobs/{job_id}/build_for_rerun")
61
+ return {
62
+ "id": job_id,
63
+ "tool_id": info.get("id", ""),
64
+ "state_inputs": info.get("state_inputs", {}),
65
+ }
66
+
67
+
68
+ def wait_for_job(client, job_id, max_wait=600, poll_interval=5):
69
+ """Wait for a job to complete. Returns final job state."""
70
+ terminal_states = {"ok", "error", "deleted", "paused"}
71
+ elapsed = 0
72
+ while elapsed < max_wait:
73
+ info = client.get(f"jobs/{job_id}")
74
+ state = info.get("state", "unknown")
75
+ if state in terminal_states:
76
+ return {
77
+ "id": job_id,
78
+ "state": state,
79
+ "exit_code": info.get("exit_code"),
80
+ "waited_seconds": elapsed,
81
+ }
82
+ time.sleep(poll_interval)
83
+ elapsed += poll_interval
84
+ return {
85
+ "id": job_id,
86
+ "state": "timeout",
87
+ "waited_seconds": elapsed,
88
+ }
@@ -0,0 +1,63 @@
1
+ """Library management — create, list, show, manage shared data libraries."""
2
+
3
+
4
+ def list_libraries(client, deleted=False):
5
+ """List data libraries."""
6
+ params = {}
7
+ if deleted:
8
+ params["deleted"] = True
9
+ libraries = client.get("libraries", params=params)
10
+ return [
11
+ {
12
+ "id": lib.get("id", ""),
13
+ "name": lib.get("name", ""),
14
+ "description": lib.get("description", ""),
15
+ "deleted": lib.get("deleted", False),
16
+ "create_time": lib.get("create_time", ""),
17
+ }
18
+ for lib in libraries
19
+ ]
20
+
21
+
22
+ def create_library(client, name, description="", synopsis=""):
23
+ """Create a new data library."""
24
+ payload = {"name": name, "description": description, "synopsis": synopsis}
25
+ result = client.post("libraries", json_data=payload)
26
+ return {
27
+ "id": result.get("id", ""),
28
+ "name": result.get("name", name),
29
+ "status": "created",
30
+ }
31
+
32
+
33
+ def show_library(client, library_id):
34
+ """Show details of a library."""
35
+ info = client.get(f"libraries/{library_id}")
36
+ return {
37
+ "id": info.get("id", library_id),
38
+ "name": info.get("name", ""),
39
+ "description": info.get("description", ""),
40
+ "synopsis": info.get("synopsis", ""),
41
+ "deleted": info.get("deleted", False),
42
+ "create_time": info.get("create_time", ""),
43
+ }
44
+
45
+
46
+ def list_library_contents(client, library_id):
47
+ """List contents of a library."""
48
+ contents = client.get(f"libraries/{library_id}/contents")
49
+ return [
50
+ {
51
+ "id": item.get("id", ""),
52
+ "name": item.get("name", ""),
53
+ "type": item.get("type", ""),
54
+ "url": item.get("url", ""),
55
+ }
56
+ for item in contents
57
+ ]
58
+
59
+
60
+ def delete_library(client, library_id):
61
+ """Delete a library."""
62
+ client.delete(f"libraries/{library_id}")
63
+ return {"id": library_id, "status": "deleted"}