adagio-cli 0.1.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.
- adagio_cli-0.1.0/LICENSE +21 -0
- adagio_cli-0.1.0/PKG-INFO +136 -0
- adagio_cli-0.1.0/README.md +105 -0
- adagio_cli-0.1.0/pyproject.toml +91 -0
- adagio_cli-0.1.0/src/adagio/__init__.py +15 -0
- adagio_cli-0.1.0/src/adagio/app/parsers/pipeline.py +89 -0
- adagio_cli-0.1.0/src/adagio/check.py +16 -0
- adagio_cli-0.1.0/src/adagio/cli/__init__.py +1 -0
- adagio_cli-0.1.0/src/adagio/cli/args.py +62 -0
- adagio_cli-0.1.0/src/adagio/cli/cache.py +82 -0
- adagio_cli-0.1.0/src/adagio/cli/config.py +183 -0
- adagio_cli-0.1.0/src/adagio/cli/dynamic.py +656 -0
- adagio_cli-0.1.0/src/adagio/cli/main.py +313 -0
- adagio_cli-0.1.0/src/adagio/cli/pipeline.py +49 -0
- adagio_cli-0.1.0/src/adagio/cli/pipeline_sources.py +322 -0
- adagio_cli-0.1.0/src/adagio/cli/qapi.py +300 -0
- adagio_cli-0.1.0/src/adagio/cli/runner.py +310 -0
- adagio_cli-0.1.0/src/adagio/cli/runtime.py +530 -0
- adagio_cli-0.1.0/src/adagio/cli/task_exec.py +474 -0
- adagio_cli-0.1.0/src/adagio/convert.py +0 -0
- adagio_cli-0.1.0/src/adagio/describe.py +487 -0
- adagio_cli-0.1.0/src/adagio/executors/__init__.py +34 -0
- adagio_cli-0.1.0/src/adagio/executors/apptainer.py +275 -0
- adagio_cli-0.1.0/src/adagio/executors/base.py +92 -0
- adagio_cli-0.1.0/src/adagio/executors/cache_support.py +82 -0
- adagio_cli-0.1.0/src/adagio/executors/common.py +124 -0
- adagio_cli-0.1.0/src/adagio/executors/conda.py +314 -0
- adagio_cli-0.1.0/src/adagio/executors/container_support.py +260 -0
- adagio_cli-0.1.0/src/adagio/executors/defaults.py +99 -0
- adagio_cli-0.1.0/src/adagio/executors/docker.py +404 -0
- adagio_cli-0.1.0/src/adagio/executors/path_utils.py +60 -0
- adagio_cli-0.1.0/src/adagio/executors/serial_runner.py +318 -0
- adagio_cli-0.1.0/src/adagio/executors/signature.py +140 -0
- adagio_cli-0.1.0/src/adagio/executors/task_contract.py +125 -0
- adagio_cli-0.1.0/src/adagio/executors/task_environments.py +760 -0
- adagio_cli-0.1.0/src/adagio/model/__init__.py +0 -0
- adagio_cli-0.1.0/src/adagio/model/arguments.py +43 -0
- adagio_cli-0.1.0/src/adagio/model/ast.py +67 -0
- adagio_cli-0.1.0/src/adagio/model/pipeline.py +85 -0
- adagio_cli-0.1.0/src/adagio/model/task.py +149 -0
- adagio_cli-0.1.0/src/adagio/monitor/__init__.py +0 -0
- adagio_cli-0.1.0/src/adagio/monitor/api.py +102 -0
- adagio_cli-0.1.0/src/adagio/monitor/base.py +0 -0
- adagio_cli-0.1.0/src/adagio/monitor/composite.py +99 -0
- adagio_cli-0.1.0/src/adagio/monitor/connected.py +212 -0
- adagio_cli-0.1.0/src/adagio/monitor/log.py +83 -0
- adagio_cli-0.1.0/src/adagio/monitor/tty.py +301 -0
- adagio_cli-0.1.0/src/adagio/protocol/__init__.py +0 -0
- adagio_cli-0.1.0/src/adagio/protocol/base.py +0 -0
- adagio_cli-0.1.0/src/adagio/protocol/file.py +0 -0
- adagio_cli-0.1.0/src/adagio/protocol/http.py +0 -0
- adagio_cli-0.1.0/src/adagio/protocol/s3.py +0 -0
- adagio_cli-0.1.0/src/adagio/qapi/__init__.py +13 -0
- adagio_cli-0.1.0/src/adagio/qapi/build.py +418 -0
- adagio_cli-0.1.0/src/adagio/qapi/client.py +56 -0
- adagio_cli-0.1.0/src/adagio/type_format.py +149 -0
adagio_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cymis
|
|
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.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adagio-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line runner for Adagio pipeline files.
|
|
5
|
+
Keywords: adagio,bioinformatics,cli,pipelines,qiime2,workflow
|
|
6
|
+
Author: Cymis
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Dist: cyclopts>=4.5.3
|
|
22
|
+
Requires-Dist: pydantic>=2.12.5
|
|
23
|
+
Requires-Dist: rich>=14.1.0
|
|
24
|
+
Requires-Dist: tomli>=2.2.1 ; python_full_version < '3.11'
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Project-URL: Changelog, https://github.com/cymis/adagio-cli/blob/dev/CHANGELOG.md
|
|
27
|
+
Project-URL: Homepage, https://github.com/cymis/adagio-cli
|
|
28
|
+
Project-URL: Issues, https://github.com/cymis/adagio-cli/issues
|
|
29
|
+
Project-URL: Repository, https://github.com/cymis/adagio-cli
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Adagio CLI
|
|
33
|
+
|
|
34
|
+
`adagio-cli` is the Python command-line interface for Adagio pipeline execution.
|
|
35
|
+
|
|
36
|
+
For user-facing documentation and product guides, please reference the docs:
|
|
37
|
+
|
|
38
|
+
- [Adagio Docs](https://docs.adagiodata.com)
|
|
39
|
+
|
|
40
|
+
The adagio frontend is used to build pipelines that can be run with this package on the command line
|
|
41
|
+
It can be found here:
|
|
42
|
+
|
|
43
|
+
- [Adagio](https://adagio.run)
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
Set up the project and run the test suite with:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv sync --group dev
|
|
51
|
+
uv run pytest
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Runtime environments
|
|
55
|
+
|
|
56
|
+
Every plugin action requires an explicit execution environment. A runtime config
|
|
57
|
+
passed with `--config` can define one default and override it per plugin or task;
|
|
58
|
+
the CLI never guesses an image from a plugin name.
|
|
59
|
+
|
|
60
|
+
Conda environments are supported with `kind = "conda"`:
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
version = 1
|
|
64
|
+
|
|
65
|
+
[defaults]
|
|
66
|
+
kind = "conda"
|
|
67
|
+
prefix = "/opt/conda/envs/qiime2-2026.1"
|
|
68
|
+
|
|
69
|
+
[plugins]
|
|
70
|
+
dada2 = { kind = "conda", prefix = "/opt/conda/envs/q2-dada2" }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The environment must already exist and contain QIIME 2 plus the plugins needed
|
|
74
|
+
by the pipeline. Adagio enters it with `conda run`; it does not create or manage
|
|
75
|
+
the environment.
|
|
76
|
+
|
|
77
|
+
## Task resource requests
|
|
78
|
+
|
|
79
|
+
A runtime config can also declare the requested shape of each task execution:
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
[resources.tasks."denoise-node"]
|
|
83
|
+
cpus = 4
|
|
84
|
+
memory = "8 GiB"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`cpus` is a positive whole number and `memory` is a positive, unit-bearing
|
|
88
|
+
quantity. Each entry describes one independently schedulable execution of that
|
|
89
|
+
task, not an aggregate pipeline allocation. If a task is later expanded into
|
|
90
|
+
parallel subtasks, each subtask requests this shape and the scheduler determines
|
|
91
|
+
the total concurrent allocation.
|
|
92
|
+
|
|
93
|
+
The current serial executor parses and validates these fields but intentionally
|
|
94
|
+
does not apply them yet. Omitting either field leaves that resource at the
|
|
95
|
+
executor's default.
|
|
96
|
+
|
|
97
|
+
## Plugin submission defaults
|
|
98
|
+
|
|
99
|
+
QAPI submissions can persist the environment that Adagio should use for the
|
|
100
|
+
submitted plugins:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
adagio qapi build --plugin my-plugin \
|
|
104
|
+
--default-conda-prefix /opt/conda/envs/my-plugin
|
|
105
|
+
|
|
106
|
+
adagio qapi build --plugin my-plugin \
|
|
107
|
+
--default-docker-image registry.example.org/my-plugin:2026.1
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
These options are explicit and mutually exclusive. Omitting both leaves the
|
|
111
|
+
plugin without a default; the Adagio app will flag that plugin until an
|
|
112
|
+
environment is selected for a run.
|
|
113
|
+
|
|
114
|
+
## Catalog pipelines
|
|
115
|
+
|
|
116
|
+
Run a pipeline from the Adagio pipeline catalog:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
adagio pipeline show @adagio/microbial-diversity
|
|
120
|
+
adagio run @adagio/microbial-diversity --cache-dir /path/to/cache --arguments run-arguments.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`@adagio/<slug>` first resolves against a nearby local `adagio-pipelines`
|
|
124
|
+
checkout when one is available. If no local catalog is found, Adagio fetches
|
|
125
|
+
`pipeline.adg` from `cymis/adagio-pipelines` on GitHub, checking `official`
|
|
126
|
+
before `community`.
|
|
127
|
+
|
|
128
|
+
During `adagio run`, remote catalog pipelines are downloaded under the selected
|
|
129
|
+
`--cache-dir` and reused by source name and slug on later runs. `adagio pipeline
|
|
130
|
+
show` uses a temporary download when it fetches from GitHub because it does not
|
|
131
|
+
take a cache directory.
|
|
132
|
+
|
|
133
|
+
Private GitHub access is explicit: set `GITHUB_TOKEN` or `GH_TOKEN` to a token
|
|
134
|
+
that can read `cymis/adagio-pipelines`; with a token, the CLI fetches through
|
|
135
|
+
the GitHub contents API. The CLI does not read browser, git, or `gh` credentials
|
|
136
|
+
automatically.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Adagio CLI
|
|
2
|
+
|
|
3
|
+
`adagio-cli` is the Python command-line interface for Adagio pipeline execution.
|
|
4
|
+
|
|
5
|
+
For user-facing documentation and product guides, please reference the docs:
|
|
6
|
+
|
|
7
|
+
- [Adagio Docs](https://docs.adagiodata.com)
|
|
8
|
+
|
|
9
|
+
The adagio frontend is used to build pipelines that can be run with this package on the command line
|
|
10
|
+
It can be found here:
|
|
11
|
+
|
|
12
|
+
- [Adagio](https://adagio.run)
|
|
13
|
+
|
|
14
|
+
## Development
|
|
15
|
+
|
|
16
|
+
Set up the project and run the test suite with:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv sync --group dev
|
|
20
|
+
uv run pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Runtime environments
|
|
24
|
+
|
|
25
|
+
Every plugin action requires an explicit execution environment. A runtime config
|
|
26
|
+
passed with `--config` can define one default and override it per plugin or task;
|
|
27
|
+
the CLI never guesses an image from a plugin name.
|
|
28
|
+
|
|
29
|
+
Conda environments are supported with `kind = "conda"`:
|
|
30
|
+
|
|
31
|
+
```toml
|
|
32
|
+
version = 1
|
|
33
|
+
|
|
34
|
+
[defaults]
|
|
35
|
+
kind = "conda"
|
|
36
|
+
prefix = "/opt/conda/envs/qiime2-2026.1"
|
|
37
|
+
|
|
38
|
+
[plugins]
|
|
39
|
+
dada2 = { kind = "conda", prefix = "/opt/conda/envs/q2-dada2" }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The environment must already exist and contain QIIME 2 plus the plugins needed
|
|
43
|
+
by the pipeline. Adagio enters it with `conda run`; it does not create or manage
|
|
44
|
+
the environment.
|
|
45
|
+
|
|
46
|
+
## Task resource requests
|
|
47
|
+
|
|
48
|
+
A runtime config can also declare the requested shape of each task execution:
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
[resources.tasks."denoise-node"]
|
|
52
|
+
cpus = 4
|
|
53
|
+
memory = "8 GiB"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`cpus` is a positive whole number and `memory` is a positive, unit-bearing
|
|
57
|
+
quantity. Each entry describes one independently schedulable execution of that
|
|
58
|
+
task, not an aggregate pipeline allocation. If a task is later expanded into
|
|
59
|
+
parallel subtasks, each subtask requests this shape and the scheduler determines
|
|
60
|
+
the total concurrent allocation.
|
|
61
|
+
|
|
62
|
+
The current serial executor parses and validates these fields but intentionally
|
|
63
|
+
does not apply them yet. Omitting either field leaves that resource at the
|
|
64
|
+
executor's default.
|
|
65
|
+
|
|
66
|
+
## Plugin submission defaults
|
|
67
|
+
|
|
68
|
+
QAPI submissions can persist the environment that Adagio should use for the
|
|
69
|
+
submitted plugins:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
adagio qapi build --plugin my-plugin \
|
|
73
|
+
--default-conda-prefix /opt/conda/envs/my-plugin
|
|
74
|
+
|
|
75
|
+
adagio qapi build --plugin my-plugin \
|
|
76
|
+
--default-docker-image registry.example.org/my-plugin:2026.1
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
These options are explicit and mutually exclusive. Omitting both leaves the
|
|
80
|
+
plugin without a default; the Adagio app will flag that plugin until an
|
|
81
|
+
environment is selected for a run.
|
|
82
|
+
|
|
83
|
+
## Catalog pipelines
|
|
84
|
+
|
|
85
|
+
Run a pipeline from the Adagio pipeline catalog:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
adagio pipeline show @adagio/microbial-diversity
|
|
89
|
+
adagio run @adagio/microbial-diversity --cache-dir /path/to/cache --arguments run-arguments.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`@adagio/<slug>` first resolves against a nearby local `adagio-pipelines`
|
|
93
|
+
checkout when one is available. If no local catalog is found, Adagio fetches
|
|
94
|
+
`pipeline.adg` from `cymis/adagio-pipelines` on GitHub, checking `official`
|
|
95
|
+
before `community`.
|
|
96
|
+
|
|
97
|
+
During `adagio run`, remote catalog pipelines are downloaded under the selected
|
|
98
|
+
`--cache-dir` and reused by source name and slug on later runs. `adagio pipeline
|
|
99
|
+
show` uses a temporary download when it fetches from GitHub because it does not
|
|
100
|
+
take a cache directory.
|
|
101
|
+
|
|
102
|
+
Private GitHub access is explicit: set `GITHUB_TOKEN` or `GH_TOKEN` to a token
|
|
103
|
+
that can read `cymis/adagio-pipelines`; with a token, the CLI fetches through
|
|
104
|
+
the GitHub contents API. The CLI does not read browser, git, or `gh` credentials
|
|
105
|
+
automatically.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "adagio-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Command-line runner for Adagio pipeline files."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
authors = [{ name = "Cymis" }]
|
|
8
|
+
license = "MIT"
|
|
9
|
+
license-files = ["LICENSE"]
|
|
10
|
+
keywords = [
|
|
11
|
+
"adagio",
|
|
12
|
+
"bioinformatics",
|
|
13
|
+
"cli",
|
|
14
|
+
"pipelines",
|
|
15
|
+
"qiime2",
|
|
16
|
+
"workflow",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: Science/Research",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"cyclopts>=4.5.3",
|
|
34
|
+
"pydantic>=2.12.5",
|
|
35
|
+
"rich>=14.1.0",
|
|
36
|
+
"tomli>=2.2.1; python_version < '3.11'",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/cymis/adagio-cli"
|
|
41
|
+
Repository = "https://github.com/cymis/adagio-cli"
|
|
42
|
+
Issues = "https://github.com/cymis/adagio-cli/issues"
|
|
43
|
+
Changelog = "https://github.com/cymis/adagio-cli/blob/dev/CHANGELOG.md"
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = [
|
|
47
|
+
"build>=1.2.2",
|
|
48
|
+
"nox>=2024.4.15,<2025.0.0",
|
|
49
|
+
"pytest>=8.4.0",
|
|
50
|
+
"pytest-cov>=6.0.0",
|
|
51
|
+
"ruff>=0.13.0",
|
|
52
|
+
"twine>=6.2.0",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.scripts]
|
|
56
|
+
adagio = "adagio.cli.main:main"
|
|
57
|
+
|
|
58
|
+
[build-system]
|
|
59
|
+
requires = ["uv_build>=0.8.17,<0.9.0"]
|
|
60
|
+
build-backend = "uv_build"
|
|
61
|
+
|
|
62
|
+
[tool.uv.build-backend]
|
|
63
|
+
module-name = "adagio"
|
|
64
|
+
|
|
65
|
+
[tool.ruff.format]
|
|
66
|
+
quote-style = "double"
|
|
67
|
+
indent-style = "space"
|
|
68
|
+
docstring-code-format = true
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
known-first-party = ["adagio"]
|
|
74
|
+
section-order = [
|
|
75
|
+
"future",
|
|
76
|
+
"standard-library",
|
|
77
|
+
"third-party",
|
|
78
|
+
"first-party",
|
|
79
|
+
"local-folder",
|
|
80
|
+
]
|
|
81
|
+
[tool.mypy]
|
|
82
|
+
plugins = ['pydantic.mypy']
|
|
83
|
+
|
|
84
|
+
[[tool.mypy.overrides]]
|
|
85
|
+
|
|
86
|
+
module = ["qiime2.*"]
|
|
87
|
+
ignore_missing_imports = true
|
|
88
|
+
|
|
89
|
+
[tool.pytest.ini_options]
|
|
90
|
+
addopts = "-ra"
|
|
91
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _resolve_version() -> str:
|
|
5
|
+
for dist_name in ("adagio-cli", "adagio"):
|
|
6
|
+
try:
|
|
7
|
+
return version(dist_name)
|
|
8
|
+
except PackageNotFoundError:
|
|
9
|
+
continue
|
|
10
|
+
return "0.0.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__version__ = _resolve_version()
|
|
14
|
+
|
|
15
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Helpers for pulling promoted parameter specs from pipeline JSON."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Parameter(BaseModel):
|
|
10
|
+
id: UUID
|
|
11
|
+
name: str
|
|
12
|
+
required: bool
|
|
13
|
+
default: Optional[Any] = None
|
|
14
|
+
type: str
|
|
15
|
+
description: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Input(BaseModel):
|
|
19
|
+
id: UUID
|
|
20
|
+
name: str
|
|
21
|
+
required: bool
|
|
22
|
+
type: str
|
|
23
|
+
description: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Output(BaseModel):
|
|
27
|
+
id: UUID
|
|
28
|
+
name: str
|
|
29
|
+
type: str
|
|
30
|
+
description: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _extract_signature(data: Any) -> dict[str, Any]:
|
|
34
|
+
signature = (
|
|
35
|
+
data.get("spec", {}).get("signature")
|
|
36
|
+
if isinstance(data, dict)
|
|
37
|
+
else None
|
|
38
|
+
) or (data.get("signature") if isinstance(data, dict) else None)
|
|
39
|
+
|
|
40
|
+
if not isinstance(signature, dict):
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"Invalid pipeline: missing 'signature' section in pipeline JSON."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return signature
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_parameters(data: Any) -> List[Parameter]:
|
|
49
|
+
"""Parse promoted parameters from supported pipeline JSON layouts.
|
|
50
|
+
|
|
51
|
+
We currently accept either:
|
|
52
|
+
- {"spec": {"signature": {"parameters": [...]}}}
|
|
53
|
+
- {"signature": {"parameters": [...]} }
|
|
54
|
+
"""
|
|
55
|
+
signature = _extract_signature(data)
|
|
56
|
+
|
|
57
|
+
raw_parameters = signature.get("parameters")
|
|
58
|
+
if not isinstance(raw_parameters, list):
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"Invalid pipeline: missing 'signature.parameters' list in pipeline JSON."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return [Parameter(**param) for param in raw_parameters]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_inputs(data: Any) -> List[Input]:
|
|
67
|
+
"""Parse promoted inputs from supported pipeline JSON layouts."""
|
|
68
|
+
signature = _extract_signature(data)
|
|
69
|
+
|
|
70
|
+
raw_inputs = signature.get("inputs")
|
|
71
|
+
if not isinstance(raw_inputs, list):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
"Invalid pipeline: missing 'signature.inputs' list in pipeline JSON."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return [Input(**input_item) for input_item in raw_inputs]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def parse_outputs(data: Any) -> List[Output]:
|
|
80
|
+
"""Parse pipeline outputs from supported pipeline JSON layouts."""
|
|
81
|
+
signature = _extract_signature(data)
|
|
82
|
+
|
|
83
|
+
raw_outputs = signature.get("outputs")
|
|
84
|
+
if not isinstance(raw_outputs, list):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"Invalid pipeline: missing 'signature.outputs' list in pipeline JSON."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return [Output(**output_item) for output_item in raw_outputs]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
except ImportError: # pragma: no cover - Python < 3.11
|
|
7
|
+
class StrEnum(str, Enum):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ParamType(StrEnum):
|
|
12
|
+
INPUT = "input"
|
|
13
|
+
PARAM = "param"
|
|
14
|
+
OUTPUT = "output"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ShowParamsMode(StrEnum):
|
|
18
|
+
ALL = "all"
|
|
19
|
+
MISSING = "missing"
|
|
20
|
+
REQUIRED = "required"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def promote_positional_pipeline(argv: list[str]) -> tuple[list[str], str | None]:
|
|
24
|
+
"""Allow `adagio run <pipeline>` by rewriting it to `--pipeline <pipeline>`."""
|
|
25
|
+
if len(argv) < 2 or argv[0] != "run":
|
|
26
|
+
return argv, None
|
|
27
|
+
|
|
28
|
+
candidate = argv[1]
|
|
29
|
+
if candidate.startswith("-"):
|
|
30
|
+
return argv, None
|
|
31
|
+
|
|
32
|
+
rewritten = ["run", "--pipeline", candidate, *argv[2:]]
|
|
33
|
+
return rewritten, candidate
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def extract_flag_value(argv: list[str], *flags: str) -> str | None:
|
|
37
|
+
"""Supports: --flag value, -f value, --flag=value."""
|
|
38
|
+
flag_set = set(flags)
|
|
39
|
+
for i, tok in enumerate(argv):
|
|
40
|
+
if tok in flag_set:
|
|
41
|
+
return argv[i + 1] if i + 1 < len(argv) else None
|
|
42
|
+
for flag in flags:
|
|
43
|
+
if tok.startswith(flag + "="):
|
|
44
|
+
return tok.split("=", 1)[1]
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def to_identifier(name: str, prefix: str | None = None) -> str:
|
|
49
|
+
"""Turn arbitrary names into valid Python identifiers for kwargs keys."""
|
|
50
|
+
clean = (name or "").strip()
|
|
51
|
+
clean = re.sub(r"[^0-9a-zA-Z_]+", "_", clean)
|
|
52
|
+
if not clean:
|
|
53
|
+
raise ValueError("Empty parameter name in pipeline file.")
|
|
54
|
+
if clean[0].isdigit():
|
|
55
|
+
clean = "_" + clean
|
|
56
|
+
if prefix:
|
|
57
|
+
return f"{prefix}_{clean}"
|
|
58
|
+
return clean
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def dynamic_opt(name: str, param_type: ParamType) -> str:
|
|
62
|
+
return f"--{param_type}-{name.replace('_', '-')}"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
from cyclopts import App, Group, Parameter
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from ..executors.cache_support import CACHE_DIR_HELP, resolve_cache_dir_path
|
|
10
|
+
|
|
11
|
+
QIIME_CACHE_CONTENTS = {"VERSION", "data", "keys", "pools", "processes"}
|
|
12
|
+
QIIME_CACHE_LINE_RE = re.compile(r"cache: v?\d+\Z")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_cache(argv: list[str], *, console: Console) -> None:
|
|
16
|
+
app = App(
|
|
17
|
+
name="adagio cache",
|
|
18
|
+
help="Manage Adagio's shared QIIME cache directory.",
|
|
19
|
+
)
|
|
20
|
+
command_group = Group("Command Options", sort_key=0)
|
|
21
|
+
|
|
22
|
+
@app.command
|
|
23
|
+
def clear(
|
|
24
|
+
*,
|
|
25
|
+
cache_dir: Annotated[
|
|
26
|
+
Path,
|
|
27
|
+
Parameter(
|
|
28
|
+
name=("--cache-dir",),
|
|
29
|
+
group=command_group,
|
|
30
|
+
help=CACHE_DIR_HELP,
|
|
31
|
+
),
|
|
32
|
+
],
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Delete an existing QIIME cache directory."""
|
|
35
|
+
resolved_cache_dir = resolve_cache_dir_path(
|
|
36
|
+
cwd=Path.cwd().resolve(),
|
|
37
|
+
raw_value=str(cache_dir),
|
|
38
|
+
)
|
|
39
|
+
_clear_cache(cache_dir=resolved_cache_dir, console=console)
|
|
40
|
+
|
|
41
|
+
app(argv)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _clear_cache(*, cache_dir: Path, console: Console) -> None:
|
|
45
|
+
_require_qiime_cache(cache_dir)
|
|
46
|
+
shutil.rmtree(cache_dir)
|
|
47
|
+
console.print(f"Cleared cache directory: {cache_dir}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _require_qiime_cache(cache_dir: Path) -> None:
|
|
51
|
+
if not cache_dir.exists():
|
|
52
|
+
raise SystemExit(f"Cache directory does not exist: {cache_dir}")
|
|
53
|
+
if not cache_dir.is_dir():
|
|
54
|
+
raise SystemExit(f"Cache path is not a directory: {cache_dir}")
|
|
55
|
+
|
|
56
|
+
contents = set(item.name for item in cache_dir.iterdir())
|
|
57
|
+
if not contents.issuperset(QIIME_CACHE_CONTENTS):
|
|
58
|
+
raise SystemExit(f"Path is not a QIIME cache: {cache_dir}")
|
|
59
|
+
|
|
60
|
+
version_file = cache_dir / "VERSION"
|
|
61
|
+
try:
|
|
62
|
+
version_text = version_file.read_text(encoding="utf-8").strip()
|
|
63
|
+
except OSError as exc:
|
|
64
|
+
raise SystemExit(f"Could not read cache version file: {version_file}") from exc
|
|
65
|
+
|
|
66
|
+
if not _looks_like_qiime_cache_version(version_text):
|
|
67
|
+
raise SystemExit(f"Path is not a QIIME cache: {cache_dir}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _looks_like_qiime_cache_version(version_text: str) -> bool:
|
|
71
|
+
lines = version_text.splitlines()
|
|
72
|
+
if len(lines) != 3:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
if lines[0] != "QIIME 2":
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
if not QIIME_CACHE_LINE_RE.fullmatch(lines[1]):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
framework_prefix = "framework: "
|
|
82
|
+
return lines[2].startswith(framework_prefix) and bool(lines[2][len(framework_prefix) :].strip())
|