envloop-cli 1.16.0__tar.gz → 1.23.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.
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/.gitignore +1 -0
- envloop_cli-1.16.0/README.pypi.md → envloop_cli-1.23.0/PKG-INFO +74 -8
- envloop_cli-1.16.0/PKG-INFO → envloop_cli-1.23.0/README.pypi.md +54 -22
- envloop_cli-1.23.0/envloop/__init__.py +13 -0
- envloop_cli-1.23.0/envloop/_optional.py +185 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/auth_cli.py +16 -16
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/auth_login.py +12 -8
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/auth_workspace.py +17 -15
- envloop_cli-1.23.0/envloop/cli.py +322 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/cli_errors.py +98 -20
- envloop_cli-1.23.0/envloop/cli_output.py +105 -0
- envloop_cli-1.23.0/envloop/client.py +86 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/config.py +74 -40
- envloop_cli-1.23.0/envloop/downloads.py +71 -0
- envloop_cli-1.23.0/envloop/http.py +355 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/lifecycle_cli.py +5 -3
- envloop_cli-1.23.0/envloop/navigation_resources.py +141 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/profile_secrets.py +2 -1
- envloop_cli-1.23.0/envloop/resource_cli.py +180 -0
- envloop_cli-1.23.0/envloop/resources.py +71 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/terminal.py +6 -6
- envloop_cli-1.23.0/pyproject.toml +82 -0
- envloop_cli-1.16.0/envloop/__init__.py +0 -6
- envloop_cli-1.16.0/envloop/agent_runs.py +0 -248
- envloop_cli-1.16.0/envloop/cli.py +0 -681
- envloop_cli-1.16.0/envloop/cli_output.py +0 -238
- envloop_cli-1.16.0/envloop/client.py +0 -712
- envloop_cli-1.16.0/envloop/decision.py +0 -19
- envloop_cli-1.16.0/envloop/http.py +0 -397
- envloop_cli-1.16.0/envloop/inference.py +0 -35
- envloop_cli-1.16.0/envloop/job_logs.py +0 -20
- envloop_cli-1.16.0/envloop/job_spec_cli.py +0 -138
- envloop_cli-1.16.0/envloop/navigation_resources.py +0 -348
- envloop_cli-1.16.0/envloop/profile_updates.py +0 -76
- envloop_cli-1.16.0/envloop/resource_cli.py +0 -166
- envloop_cli-1.16.0/envloop/resources.py +0 -208
- envloop_cli-1.16.0/envloop/rsi_cli.py +0 -60
- envloop_cli-1.16.0/envloop/sandbox.py +0 -27
- envloop_cli-1.16.0/envloop/train.py +0 -96
- envloop_cli-1.16.0/envloop/train_log_cli.py +0 -128
- envloop_cli-1.16.0/envloop/train_progress.py +0 -51
- envloop_cli-1.16.0/envloop/train_runner_cli.py +0 -64
- envloop_cli-1.16.0/envloop/training/__init__.py +0 -4
- envloop_cli-1.16.0/pyproject.toml +0 -56
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/utils/__init__.py +0 -0
- {envloop_cli-1.16.0 → envloop_cli-1.23.0}/envloop/utils/logging.py +0 -0
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: envloop-cli
|
|
3
|
+
Version: 1.23.0
|
|
4
|
+
Summary: EnvLoop CLI and Python SDK for jobs, training, checkpoints and inference
|
|
5
|
+
Project-URL: Homepage, https://envloop.ai
|
|
6
|
+
Project-URL: Documentation, https://docs.envloop.ai
|
|
7
|
+
Project-URL: Repository, https://github.com/EnvLoop/envloop-cli
|
|
8
|
+
Project-URL: Issues, https://github.com/EnvLoop/envloop-cli/issues
|
|
9
|
+
Project-URL: Agent instructions, https://docs.envloop.ai/skill.md
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: envloop-cli-experiment==1.23.0
|
|
12
|
+
Provides-Extra: data
|
|
13
|
+
Requires-Dist: envloop-cli-data==1.23.0; extra == 'data'
|
|
14
|
+
Provides-Extra: default
|
|
15
|
+
Provides-Extra: experiment
|
|
16
|
+
Requires-Dist: envloop-cli-experiment==1.23.0; extra == 'experiment'
|
|
17
|
+
Provides-Extra: rsi-loop
|
|
18
|
+
Requires-Dist: envloop-cli-rsi-loop==1.23.0; extra == 'rsi-loop'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
1
21
|
# envloop-cli: EnvLoop CLI and Python SDK
|
|
2
22
|
|
|
3
23
|
Install the public client to run agent Jobs, inspect Trials, manage training and
|
|
@@ -17,14 +37,25 @@ installation alone does not establish those prerequisites. See the
|
|
|
17
37
|
|
|
18
38
|
## Install
|
|
19
39
|
|
|
20
|
-
|
|
40
|
+
envloop-cli requires Python 3.12 or later; an existing compatible interpreter can
|
|
41
|
+
be reused. The following [uv](https://docs.astral.sh/uv/) example uses Python 3.12:
|
|
21
42
|
|
|
22
43
|
```bash
|
|
23
|
-
|
|
44
|
+
uv tool install --python 3.12 envloop-cli
|
|
45
|
+
el --version # upgrade later with: uv tool upgrade --python 3.12 envloop-cli
|
|
24
46
|
el --help
|
|
25
47
|
```
|
|
26
48
|
|
|
27
|
-
|
|
49
|
+
To use the Python SDK, install the package into a Python 3.12+ virtual environment:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv venv --python 3.12
|
|
53
|
+
uv pip install --upgrade envloop-cli
|
|
54
|
+
# or, with an existing Python 3.12 interpreter:
|
|
55
|
+
python3.12 -m pip install --upgrade envloop-cli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
In this source candidate the bare installation and `default` include experiment: Jobs, Trials, training, logs and inference calls. Data resources require `data`; RSI Loop controls require `rsi-loop`. This source change is not a claim that the modular packages have been published.
|
|
28
59
|
Check that the SDK can be imported:
|
|
29
60
|
|
|
30
61
|
```bash
|
|
@@ -37,6 +68,19 @@ follow the [managed training guide](https://docs.envloop.ai/training/cli-hello-w
|
|
|
37
68
|
|
|
38
69
|
Install the client in your Python environment; the platform provides the execution resources.
|
|
39
70
|
|
|
71
|
+
## Optional features (source candidate)
|
|
72
|
+
|
|
73
|
+
The current source candidate uses independent feature wheels at version 1.23.0. Matching modular distributions and public ANS core must be released before using this contract from a public index:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m pip install "envloop-cli[data]" # Dataset/Data/Runner/Checkpoint/Profile/Recipe
|
|
77
|
+
python -m pip install envloop-cli # Includes Job/Trial/logs/inference
|
|
78
|
+
python -m pip install "envloop-cli[experiment]" # Explicit spelling of the default installation
|
|
79
|
+
python -m pip install "envloop-cli[rsi-loop]" # Existing job run paths; includes experiment
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`envloop-cli[default]` equals the bare installation: base plus `envloop-cli-experiment==1.23.0`, delivered as separate wheels. Only base owns the `el`/`envloop` scripts. Feature resources load on access; absent optional features report installation guidance. The default installation uses standard-library verified TLS and system CA configuration, without EnvHub or certifi. The examples below require their corresponding feature packages and an authorized matching platform.
|
|
83
|
+
|
|
40
84
|
## Connect
|
|
41
85
|
|
|
42
86
|
Use an authorized EnvLoop account and project. Browser authorization:
|
|
@@ -112,7 +156,7 @@ cleaning up temporary resources.
|
|
|
112
156
|
|
|
113
157
|
## Sandbox jobs
|
|
114
158
|
|
|
115
|
-
With a matching platform and a published `
|
|
159
|
+
With a matching platform and a published `envloop/base` ANS Runner, submit a
|
|
116
160
|
Sandbox Job without a Dataset:
|
|
117
161
|
|
|
118
162
|
```bash
|
|
@@ -122,7 +166,7 @@ el job list --type sandbox
|
|
|
122
166
|
el job log JOB_ID --follow
|
|
123
167
|
```
|
|
124
168
|
|
|
125
|
-
The platform resolves `base` to the registered `
|
|
169
|
+
The platform resolves `base` to the registered `envloop/base@main` source ref and
|
|
126
170
|
freezes its immutable Revision. The Sandbox image defaults to `python:3.11`;
|
|
127
171
|
`--image` accepts an image name, tag or digest. The execution provider is configured
|
|
128
172
|
by the server. Job logs retain the actual captured output and collection metadata.
|
|
@@ -178,8 +222,8 @@ Choose a published Runner readable in your project. Save this request as `gpu-ru
|
|
|
178
222
|
{
|
|
179
223
|
"request_id": "gpu-run-001",
|
|
180
224
|
"trials": [
|
|
181
|
-
{"name": "first", "spec": {"runner": "
|
|
182
|
-
{"name": "second", "spec": {"runner": "
|
|
225
|
+
{"name": "first", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_sec": 600}},
|
|
226
|
+
{"name": "second", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_sec": 600}}
|
|
183
227
|
]
|
|
184
228
|
}
|
|
185
229
|
```
|
|
@@ -191,7 +235,7 @@ el job log JOB_ID --follow
|
|
|
191
235
|
el job result JOB_ID
|
|
192
236
|
```
|
|
193
237
|
|
|
194
|
-
Replace `JOB_ID` with the returned value. The example has two independent Trials, each requesting four H100 GPUs: concurrent execution needs eight GPUs. Capacity and budget determine actual concurrency. Startup and execution are billable; check the Runner requirements, project `training.run` permission and
|
|
238
|
+
Replace `JOB_ID` with the returned value. The example has two independent Trials, each requesting four H100 GPUs: concurrent execution needs eight GPUs. Capacity and budget determine actual concurrency. Startup and execution are billable; check the Runner requirements, project `training.run` permission, and applicable prices and balance in your authorized Console account first. Reduce the Trial or GPU count when your program supports a smaller first run. The [usage guide](https://docs.envloop.ai/manage/usage) explains resource observations.
|
|
195
239
|
|
|
196
240
|
The request file does not depend on GPU shortcut flags. If `el job submit --help` lists both `--trails` and `--gput-type`, you can instead use the shortcuts in [GPU Runner training](https://docs.envloop.ai/training/runner). Choose one submission method to avoid duplicate runs. The SDK accepts the same dictionary through `client.training_jobs.submit(request)`.
|
|
197
241
|
|
|
@@ -226,3 +270,25 @@ with Client.remote() as client:
|
|
|
226
270
|
Use the exact model returned by the Job. The SDK forwards the payload to the
|
|
227
271
|
Job's `/Inference/decision` API; the approved plugin validates its business fields.
|
|
228
272
|
Availability requires matching platform support and approved content in your project.
|
|
273
|
+
|
|
274
|
+
## ANS data and verified downloads
|
|
275
|
+
|
|
276
|
+
`client.data.download_directory(uri, destination)` and `el data download URI --output ./content`
|
|
277
|
+
download a complete registered ANS object directory through EnvHub. Datasets require
|
|
278
|
+
`split="lite"` / `--split lite`; the Dataset version and filenames are preserved. After API
|
|
279
|
+
authorization the SDK uses GET URLs valid for up to five minutes, verifies file size/SHA-256,
|
|
280
|
+
and installs the directory atomically. Matching platform support is required.
|
|
281
|
+
|
|
282
|
+
A compatible server and an explicitly granted project Key with `envloop:data:read` are required; writes additionally need `envloop:data:write`. Default and older Keys do not gain these permissions. The current login selects the base URL, workspace and project; there is no `--env` flag. After configuring the Key using `el auth config`:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
el data list --type dataset --json
|
|
286
|
+
el data get DATA_ID --json
|
|
287
|
+
el data download DATA_ID --file FILE_ID --output ./dataset.tgz --resume --json
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Unified ANS CRUD supports Dataset, Runner, Recipe and HF model Checkpoint registrations through `el data` and `client.data`. Dataset/Runner/Recipe creation requires a native immutable `--ref`, `--manifest` and exact `--directory`. HF metadata registration uses `el data create --type checkpoint --model namespace/model --revision FULL_COMMIT`; SDK uses `create(kind="checkpoint", model=..., revision=...)`. HF weights are not downloaded, and artifact imports do not establish Model or Job checkpoint provenance.
|
|
291
|
+
|
|
292
|
+
Success follows native storage and original catalog readback and returns an `ans_` identity. Update publishes an explicit new revision. Delete checks active and unknown dependencies; Runner deletion covers all revisions. Historical bytes and quota usage remain. Commands default to the current login base URL/workspace/project, with no `--env`. Both Data grants and the corresponding resource capabilities are required.
|
|
293
|
+
|
|
294
|
+
Read methods include `list/get/files/open/download`; `client.recipes.list/get` queries the original Recipe catalog. Downloads verify size/SHA-256 and refuse existing destinations, symlinks and storage redirects. Large payloads use multipart with stable idempotency keys. Staging `pub_` IDs and failure phases support retry; they are not native success. Matching client/server support is required. See the [Data reference](https://docs.envloop.ai/reference/cli#data-commands).
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: envloop-cli
|
|
3
|
-
Version: 1.16.0
|
|
4
|
-
Summary: EnvLoop CLI and Python SDK for jobs, training, checkpoints and inference
|
|
5
|
-
Project-URL: Homepage, https://envloop.ai
|
|
6
|
-
Project-URL: Documentation, https://docs.envloop.ai
|
|
7
|
-
Project-URL: Repository, https://github.com/EnvLoop/envloop-cli
|
|
8
|
-
Project-URL: Issues, https://github.com/EnvLoop/envloop-cli/issues
|
|
9
|
-
Project-URL: Agent instructions, https://docs.envloop.ai/skill.md
|
|
10
|
-
Requires-Python: >=3.11
|
|
11
|
-
Requires-Dist: certifi>=2026.10.19
|
|
12
|
-
Requires-Dist: tinker==0.34.0
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
1
|
# envloop-cli: EnvLoop CLI and Python SDK
|
|
16
2
|
|
|
17
3
|
Install the public client to run agent Jobs, inspect Trials, manage training and
|
|
@@ -31,14 +17,25 @@ installation alone does not establish those prerequisites. See the
|
|
|
31
17
|
|
|
32
18
|
## Install
|
|
33
19
|
|
|
34
|
-
|
|
20
|
+
envloop-cli requires Python 3.12 or later; an existing compatible interpreter can
|
|
21
|
+
be reused. The following [uv](https://docs.astral.sh/uv/) example uses Python 3.12:
|
|
35
22
|
|
|
36
23
|
```bash
|
|
37
|
-
|
|
24
|
+
uv tool install --python 3.12 envloop-cli
|
|
25
|
+
el --version # upgrade later with: uv tool upgrade --python 3.12 envloop-cli
|
|
38
26
|
el --help
|
|
39
27
|
```
|
|
40
28
|
|
|
41
|
-
|
|
29
|
+
To use the Python SDK, install the package into a Python 3.12+ virtual environment:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv venv --python 3.12
|
|
33
|
+
uv pip install --upgrade envloop-cli
|
|
34
|
+
# or, with an existing Python 3.12 interpreter:
|
|
35
|
+
python3.12 -m pip install --upgrade envloop-cli
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
In this source candidate the bare installation and `default` include experiment: Jobs, Trials, training, logs and inference calls. Data resources require `data`; RSI Loop controls require `rsi-loop`. This source change is not a claim that the modular packages have been published.
|
|
42
39
|
Check that the SDK can be imported:
|
|
43
40
|
|
|
44
41
|
```bash
|
|
@@ -51,6 +48,19 @@ follow the [managed training guide](https://docs.envloop.ai/training/cli-hello-w
|
|
|
51
48
|
|
|
52
49
|
Install the client in your Python environment; the platform provides the execution resources.
|
|
53
50
|
|
|
51
|
+
## Optional features (source candidate)
|
|
52
|
+
|
|
53
|
+
The current source candidate uses independent feature wheels at version 1.23.0. Matching modular distributions and public ANS core must be released before using this contract from a public index:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m pip install "envloop-cli[data]" # Dataset/Data/Runner/Checkpoint/Profile/Recipe
|
|
57
|
+
python -m pip install envloop-cli # Includes Job/Trial/logs/inference
|
|
58
|
+
python -m pip install "envloop-cli[experiment]" # Explicit spelling of the default installation
|
|
59
|
+
python -m pip install "envloop-cli[rsi-loop]" # Existing job run paths; includes experiment
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`envloop-cli[default]` equals the bare installation: base plus `envloop-cli-experiment==1.23.0`, delivered as separate wheels. Only base owns the `el`/`envloop` scripts. Feature resources load on access; absent optional features report installation guidance. The default installation uses standard-library verified TLS and system CA configuration, without EnvHub or certifi. The examples below require their corresponding feature packages and an authorized matching platform.
|
|
63
|
+
|
|
54
64
|
## Connect
|
|
55
65
|
|
|
56
66
|
Use an authorized EnvLoop account and project. Browser authorization:
|
|
@@ -126,7 +136,7 @@ cleaning up temporary resources.
|
|
|
126
136
|
|
|
127
137
|
## Sandbox jobs
|
|
128
138
|
|
|
129
|
-
With a matching platform and a published `
|
|
139
|
+
With a matching platform and a published `envloop/base` ANS Runner, submit a
|
|
130
140
|
Sandbox Job without a Dataset:
|
|
131
141
|
|
|
132
142
|
```bash
|
|
@@ -136,7 +146,7 @@ el job list --type sandbox
|
|
|
136
146
|
el job log JOB_ID --follow
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
The platform resolves `base` to the registered `
|
|
149
|
+
The platform resolves `base` to the registered `envloop/base@main` source ref and
|
|
140
150
|
freezes its immutable Revision. The Sandbox image defaults to `python:3.11`;
|
|
141
151
|
`--image` accepts an image name, tag or digest. The execution provider is configured
|
|
142
152
|
by the server. Job logs retain the actual captured output and collection metadata.
|
|
@@ -192,8 +202,8 @@ Choose a published Runner readable in your project. Save this request as `gpu-ru
|
|
|
192
202
|
{
|
|
193
203
|
"request_id": "gpu-run-001",
|
|
194
204
|
"trials": [
|
|
195
|
-
{"name": "first", "spec": {"runner": "
|
|
196
|
-
{"name": "second", "spec": {"runner": "
|
|
205
|
+
{"name": "first", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_sec": 600}},
|
|
206
|
+
{"name": "second", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_sec": 600}}
|
|
197
207
|
]
|
|
198
208
|
}
|
|
199
209
|
```
|
|
@@ -205,7 +215,7 @@ el job log JOB_ID --follow
|
|
|
205
215
|
el job result JOB_ID
|
|
206
216
|
```
|
|
207
217
|
|
|
208
|
-
Replace `JOB_ID` with the returned value. The example has two independent Trials, each requesting four H100 GPUs: concurrent execution needs eight GPUs. Capacity and budget determine actual concurrency. Startup and execution are billable; check the Runner requirements, project `training.run` permission and
|
|
218
|
+
Replace `JOB_ID` with the returned value. The example has two independent Trials, each requesting four H100 GPUs: concurrent execution needs eight GPUs. Capacity and budget determine actual concurrency. Startup and execution are billable; check the Runner requirements, project `training.run` permission, and applicable prices and balance in your authorized Console account first. Reduce the Trial or GPU count when your program supports a smaller first run. The [usage guide](https://docs.envloop.ai/manage/usage) explains resource observations.
|
|
209
219
|
|
|
210
220
|
The request file does not depend on GPU shortcut flags. If `el job submit --help` lists both `--trails` and `--gput-type`, you can instead use the shortcuts in [GPU Runner training](https://docs.envloop.ai/training/runner). Choose one submission method to avoid duplicate runs. The SDK accepts the same dictionary through `client.training_jobs.submit(request)`.
|
|
211
221
|
|
|
@@ -240,3 +250,25 @@ with Client.remote() as client:
|
|
|
240
250
|
Use the exact model returned by the Job. The SDK forwards the payload to the
|
|
241
251
|
Job's `/Inference/decision` API; the approved plugin validates its business fields.
|
|
242
252
|
Availability requires matching platform support and approved content in your project.
|
|
253
|
+
|
|
254
|
+
## ANS data and verified downloads
|
|
255
|
+
|
|
256
|
+
`client.data.download_directory(uri, destination)` and `el data download URI --output ./content`
|
|
257
|
+
download a complete registered ANS object directory through EnvHub. Datasets require
|
|
258
|
+
`split="lite"` / `--split lite`; the Dataset version and filenames are preserved. After API
|
|
259
|
+
authorization the SDK uses GET URLs valid for up to five minutes, verifies file size/SHA-256,
|
|
260
|
+
and installs the directory atomically. Matching platform support is required.
|
|
261
|
+
|
|
262
|
+
A compatible server and an explicitly granted project Key with `envloop:data:read` are required; writes additionally need `envloop:data:write`. Default and older Keys do not gain these permissions. The current login selects the base URL, workspace and project; there is no `--env` flag. After configuring the Key using `el auth config`:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
el data list --type dataset --json
|
|
266
|
+
el data get DATA_ID --json
|
|
267
|
+
el data download DATA_ID --file FILE_ID --output ./dataset.tgz --resume --json
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Unified ANS CRUD supports Dataset, Runner, Recipe and HF model Checkpoint registrations through `el data` and `client.data`. Dataset/Runner/Recipe creation requires a native immutable `--ref`, `--manifest` and exact `--directory`. HF metadata registration uses `el data create --type checkpoint --model namespace/model --revision FULL_COMMIT`; SDK uses `create(kind="checkpoint", model=..., revision=...)`. HF weights are not downloaded, and artifact imports do not establish Model or Job checkpoint provenance.
|
|
271
|
+
|
|
272
|
+
Success follows native storage and original catalog readback and returns an `ans_` identity. Update publishes an explicit new revision. Delete checks active and unknown dependencies; Runner deletion covers all revisions. Historical bytes and quota usage remain. Commands default to the current login base URL/workspace/project, with no `--env`. Both Data grants and the corresponding resource capabilities are required.
|
|
273
|
+
|
|
274
|
+
Read methods include `list/get/files/open/download`; `client.recipes.list/get` queries the original Recipe catalog. Downloads verify size/SHA-256 and refuse existing destinations, symlinks and storage redirects. Large payloads use multipart with stable idempotency keys. Staging `pub_` IDs and failure phases support retry; they are not native success. Matching client/server support is required. See the [Data reference](https://docs.envloop.ai/reference/cli#data-commands).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""EnvLoop Python client, with optional functionality loaded on demand."""
|
|
2
|
+
from ._optional import install_legacy_imports
|
|
3
|
+
from .client import Client
|
|
4
|
+
|
|
5
|
+
install_legacy_imports()
|
|
6
|
+
__all__ = ['Client', 'DecisionClient']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def __getattr__(name: str):
|
|
10
|
+
if name == 'DecisionClient':
|
|
11
|
+
from ._optional import feature_attribute
|
|
12
|
+
return feature_attribute('experiment', 'decision', name)
|
|
13
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Lazy, static adapters for independently installed client features.
|
|
2
|
+
|
|
3
|
+
No feature code is imported when the base client is imported or constructed.
|
|
4
|
+
Legacy module names resolve to their single feature owner on explicit import.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import importlib
|
|
8
|
+
import importlib.abc
|
|
9
|
+
import importlib.util
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from importlib.machinery import ModuleSpec
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
from typing import Any, override
|
|
15
|
+
|
|
16
|
+
FEATURES = {'data': 'envloop_data', 'experiment': 'envloop_experiment', 'rsi-loop': 'envloop_rsi_loop'}
|
|
17
|
+
FEATURE_COMMANDS = {
|
|
18
|
+
'data': {
|
|
19
|
+
'dataset': 'Discover datasets, revisions and tasks',
|
|
20
|
+
'data': 'Manage versioned Data resources and verified file downloads',
|
|
21
|
+
'runner': 'Discover available Runner revisions',
|
|
22
|
+
'checkpoint': 'List and download saved training checkpoints',
|
|
23
|
+
'profile': 'Manage platform runtime profiles',
|
|
24
|
+
},
|
|
25
|
+
'experiment': {
|
|
26
|
+
'job': 'Submit and manage Agent, Sandbox, Train and Inference Jobs',
|
|
27
|
+
'trail': 'Inspect and control existing trials',
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
MODULES: dict[str, str] = {
|
|
32
|
+
**dict.fromkeys('data data_publications data_upload data_cli profile_updates recipes'.split(), 'data'),
|
|
33
|
+
**dict.fromkeys('train train_progress train_runner_cli train_log_cli job_logs job_cost inference decision sandbox runtime_params job_spec_cli training'.split(), 'experiment'),
|
|
34
|
+
**dict.fromkeys('agent_runs rsi_cli'.split(), 'rsi-loop'),
|
|
35
|
+
}
|
|
36
|
+
CLIENTS = {
|
|
37
|
+
**{name: ('data', 'client') for name in ('ProfileService', 'RunnerService', 'Profiles', 'Runners')},
|
|
38
|
+
**{name: ('experiment', 'client') for name in ('TaskService', 'RpcService', 'load_task', 'validate_command', 'dataset_selector', 'Tasks', 'runner_selector', 'job_budget', 'Jobs')},
|
|
39
|
+
**{name: ('base', 'downloads') for name in ('ArtifactError', 'artifact_path')},
|
|
40
|
+
}
|
|
41
|
+
DATA_ENDPOINTS = set('open_download resolve_dataset list_runners profile_path put_profile list_profiles get_profile resolve_profile set_default_profile archive_profile revoke_profile_secret export_profile import_profile open_checkpoint_archive'.split())
|
|
42
|
+
EXPERIMENT_ENDPOINTS = set('submit_job get_job get_job_result get_job_logs list_jobs job_action job_events stop_job list_trials get_task_run get_trial_detail get_trial_training list_trial_metrics list_trial_checkpoints get_task_result get_task_snapshot stop_task list_trial_artifacts open_trial_artifact'.split())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MissingFeatureError(ImportError):
|
|
46
|
+
"""Static installation instructions, without reflecting user input."""
|
|
47
|
+
code = 'FEATURE_NOT_INSTALLED'
|
|
48
|
+
|
|
49
|
+
def __init__(self, feature: str):
|
|
50
|
+
self.feature = feature
|
|
51
|
+
super().__init__(f'Install optional functionality with: pip install "envloop-cli[{feature}]"')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CommandExit(Exception):
|
|
55
|
+
def __init__(self, status: int):
|
|
56
|
+
self.status = status
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def available(feature: str):
|
|
60
|
+
return importlib.util.find_spec(FEATURES[feature]) is not None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def feature_module(feature: str, module: str):
|
|
64
|
+
if not available(feature):
|
|
65
|
+
raise MissingFeatureError(feature)
|
|
66
|
+
return importlib.import_module(f'{FEATURES[feature]}.{module}')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def feature_attribute(feature: str, module: str, name: str):
|
|
70
|
+
if name.startswith('__'):
|
|
71
|
+
raise AttributeError(name)
|
|
72
|
+
return getattr(feature_module(feature, module), name)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def client_attribute(name: str):
|
|
76
|
+
if name not in CLIENTS:
|
|
77
|
+
raise AttributeError(name)
|
|
78
|
+
feature, module = CLIENTS[name]
|
|
79
|
+
if feature == 'base':
|
|
80
|
+
return getattr(importlib.import_module(f'envloop.{module}'), name)
|
|
81
|
+
return feature_attribute(feature, module, name)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def endpoint(name: str):
|
|
85
|
+
if name in DATA_ENDPOINTS:
|
|
86
|
+
feature = 'data'
|
|
87
|
+
elif name in EXPERIMENT_ENDPOINTS:
|
|
88
|
+
feature = 'experiment'
|
|
89
|
+
else:
|
|
90
|
+
raise AttributeError(name)
|
|
91
|
+
# Inspect the class dictionary: static methods retain their descriptor contract.
|
|
92
|
+
return feature_module(feature, 'http').Endpoints.__dict__[name]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def navigation(name: str):
|
|
96
|
+
if name in {'Datasets', 'Checkpoints', '_checkpoint_matches', '_checkpoint_target', '_verified_checkpoint_file'}:
|
|
97
|
+
return feature_attribute('data', 'navigation_resources', name)
|
|
98
|
+
if name in {'Inference', 'InferenceResource'}:
|
|
99
|
+
return feature_attribute('experiment', 'navigation_resources', name)
|
|
100
|
+
raise AttributeError(name)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def client_resource(client: Any, name: str):
|
|
104
|
+
server = client._transport
|
|
105
|
+
if name in {'billing', 'project', 'operations'}:
|
|
106
|
+
module, cls = ('resources', 'Operations') if name == 'operations' else ('navigation_resources', name.capitalize())
|
|
107
|
+
return getattr(importlib.import_module(f'envloop.{module}'), cls)(server)
|
|
108
|
+
if name == 'dashboard':
|
|
109
|
+
if available('experiment'):
|
|
110
|
+
return feature_attribute('experiment', 'navigation_resources', 'Dashboard')(client.project, client.jobs, client.train)
|
|
111
|
+
from .navigation_resources import Dashboard
|
|
112
|
+
return Dashboard(client.project)
|
|
113
|
+
if name in {'profiles', 'runners'}:
|
|
114
|
+
return feature_attribute('data', 'client', name.capitalize())(server)
|
|
115
|
+
if name in {'data', 'recipes'}:
|
|
116
|
+
return feature_attribute('data', name, name.capitalize())(server)
|
|
117
|
+
if name == 'datasets':
|
|
118
|
+
return navigation('Datasets')(server)
|
|
119
|
+
if name == 'checkpoints':
|
|
120
|
+
return feature_attribute('data', 'navigation_resources', 'Checkpoints')(server)
|
|
121
|
+
if name == 'jobs':
|
|
122
|
+
return feature_attribute('experiment', 'client', 'Jobs')(server)
|
|
123
|
+
if name == 'trials':
|
|
124
|
+
return client.tasks
|
|
125
|
+
if name == 'tasks':
|
|
126
|
+
return feature_attribute('experiment', 'client', 'Tasks')(server)
|
|
127
|
+
if name == 'training_jobs':
|
|
128
|
+
return client.train
|
|
129
|
+
if name == 'train':
|
|
130
|
+
return feature_attribute('experiment', 'train', 'TrainJobs')(server)
|
|
131
|
+
if name == 'decision':
|
|
132
|
+
return feature_attribute('experiment', 'decision', 'DecisionClient')(server)
|
|
133
|
+
if name == 'agent_runs':
|
|
134
|
+
return feature_attribute('rsi-loop', 'agent_runs', 'AgentRuns')(client.train)
|
|
135
|
+
if name in {'endpoints', 'adapters'}:
|
|
136
|
+
return feature_attribute('experiment', 'resources', name.capitalize())(server)
|
|
137
|
+
if name == 'inference':
|
|
138
|
+
return navigation('Inference')(server)
|
|
139
|
+
raise AttributeError(name)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def execute_command(args: argparse.Namespace, client: Any, cli: Any):
|
|
143
|
+
if args.group in {'job', 'trail'}:
|
|
144
|
+
return getattr(feature_module('experiment', 'cli'), 'execute_' + args.group)(args, client, cli)
|
|
145
|
+
if args.group == 'profile':
|
|
146
|
+
return feature_module('data', 'cli').execute_profile(args, client, cli)
|
|
147
|
+
if args.group in {'data', 'dataset', 'runner', 'checkpoint'}:
|
|
148
|
+
return feature_module('data', 'resource_cli').execute(args, client)
|
|
149
|
+
from .resource_cli import execute
|
|
150
|
+
return execute(args, client)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class _LegacyFeatureLoader(importlib.abc.Loader):
|
|
154
|
+
def __init__(self, feature: str, module: str):
|
|
155
|
+
self.feature, self.module = feature, module
|
|
156
|
+
|
|
157
|
+
@override
|
|
158
|
+
def create_module(self, spec: ModuleSpec):
|
|
159
|
+
module = feature_module(self.feature, self.module)
|
|
160
|
+
self.spec = module.__spec__
|
|
161
|
+
return module
|
|
162
|
+
|
|
163
|
+
@override
|
|
164
|
+
def exec_module(self, module: ModuleType):
|
|
165
|
+
# importlib rebinds __spec__ to the legacy alias; restore the feature module's own
|
|
166
|
+
# spec so its relative imports keep resolving inside the feature package.
|
|
167
|
+
module.__spec__ = self.spec
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class _LegacyFeatureFinder(importlib.abc.MetaPathFinder):
|
|
171
|
+
@override
|
|
172
|
+
def find_spec(self, fullname: str, path: Sequence[str] | None = None, target: ModuleType | None = None):
|
|
173
|
+
if not fullname.startswith('envloop.'):
|
|
174
|
+
return None
|
|
175
|
+
module = fullname.removeprefix('envloop.')
|
|
176
|
+
feature = MODULES.get(module)
|
|
177
|
+
if feature is None:
|
|
178
|
+
return None
|
|
179
|
+
loader = _LegacyFeatureLoader(feature, module)
|
|
180
|
+
return importlib.util.spec_from_loader(fullname, loader, is_package=module == 'training')
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def install_legacy_imports():
|
|
184
|
+
if not any(isinstance(finder, _LegacyFeatureFinder) for finder in sys.meta_path):
|
|
185
|
+
sys.meta_path.append(_LegacyFeatureFinder())
|
|
@@ -3,13 +3,14 @@ import argparse
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
from typing import Any, cast
|
|
6
7
|
|
|
7
8
|
from .config import ConfigError, ConfigStore, normalize_url, validate_key
|
|
8
9
|
|
|
9
10
|
DEFAULT_BASE_URL = "https://api.envloop.ai"
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
def add_auth_commands(groups):
|
|
13
|
+
def add_auth_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
|
|
13
14
|
auth = groups.add_parser("auth", help="Configure credentials, inspect status, or remove a saved key")
|
|
14
15
|
commands = auth.add_subparsers(dest="auth_command", required=True)
|
|
15
16
|
login = commands.add_parser("login", help="Open the Clerk sign-in page and authorize CLI/SDK access")
|
|
@@ -47,7 +48,7 @@ def add_auth_commands(groups):
|
|
|
47
48
|
workspace.add_argument("--stdin", action="store_true", help="Read the selected workspace's API key from stdin")
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def _key_from_input(stdin=False):
|
|
51
|
+
def _key_from_input(stdin: bool = False) -> str:
|
|
51
52
|
if stdin:
|
|
52
53
|
value = sys.stdin.read(515)
|
|
53
54
|
if value.endswith("\r\n"):
|
|
@@ -72,7 +73,7 @@ def _masked_key():
|
|
|
72
73
|
masked[3] &= ~(termios.ECHO | termios.ICANON | termios.ISIG | termios.IEXTEN)
|
|
73
74
|
masked[6][termios.VMIN] = 1
|
|
74
75
|
masked[6][termios.VTIME] = 0
|
|
75
|
-
characters = []
|
|
76
|
+
characters: list[str] = []
|
|
76
77
|
try:
|
|
77
78
|
termios.tcsetattr(descriptor, termios.TCSAFLUSH, masked)
|
|
78
79
|
sys.stderr.write("API key: ")
|
|
@@ -102,7 +103,7 @@ def _masked_key():
|
|
|
102
103
|
sys.stderr.flush()
|
|
103
104
|
|
|
104
105
|
|
|
105
|
-
def _report(args, store):
|
|
106
|
+
def _report(args: argparse.Namespace, store: ConfigStore):
|
|
106
107
|
saved, configured = store.inspect()
|
|
107
108
|
from .cli import resolve_cli_connection
|
|
108
109
|
|
|
@@ -116,7 +117,7 @@ def _report(args, store):
|
|
|
116
117
|
}, connection
|
|
117
118
|
|
|
118
119
|
|
|
119
|
-
def _verify_key(url, key, workspace_id=None, project_id=None):
|
|
120
|
+
def _verify_key(url: str, key: str, workspace_id: str | None = None, project_id: str | None = None):
|
|
120
121
|
from .http import HttpService, RemoteError
|
|
121
122
|
|
|
122
123
|
validate_key(key)
|
|
@@ -131,10 +132,10 @@ def _verify_key(url, key, workspace_id=None, project_id=None):
|
|
|
131
132
|
environment=False).request("GET", "/v1/me")
|
|
132
133
|
except RemoteError as error:
|
|
133
134
|
code = "EDGE_REJECTED" if error.code == "EDGE_REJECTED" else {
|
|
134
|
-
401: "AUTHENTICATION_REJECTED", 403: "AUTHORIZATION_DENIED"}.get(error.status_code)
|
|
135
|
+
401: "AUTHENTICATION_REJECTED", 403: "AUTHORIZATION_DENIED"}.get(cast(int, error.status_code))
|
|
135
136
|
if code is None:
|
|
136
137
|
code = error.code if error.status_code is None and error.code in {
|
|
137
|
-
"NETWORK_ERROR", "INVALID_RESPONSE", "RESPONSE_TOO_LARGE",
|
|
138
|
+
"NETWORK_ERROR", "REQUEST_TIMEOUT", "RESPONSE_INTERRUPTED", "INVALID_RESPONSE", "RESPONSE_TOO_LARGE",
|
|
138
139
|
} else "HTTP_ERROR"
|
|
139
140
|
raise ConfigError(code) from None
|
|
140
141
|
if not isinstance(response, dict) or response.get("auth_method") != "api_key":
|
|
@@ -145,7 +146,7 @@ def _verify_key(url, key, workspace_id=None, project_id=None):
|
|
|
145
146
|
return _scope(response, project=project_id)
|
|
146
147
|
|
|
147
148
|
|
|
148
|
-
def _print(report, as_json=False):
|
|
149
|
+
def _print(report: dict[str, Any], as_json: bool = False) -> None:
|
|
149
150
|
if as_json:
|
|
150
151
|
print(json.dumps(report, ensure_ascii=False))
|
|
151
152
|
return
|
|
@@ -160,7 +161,7 @@ def _print(report, as_json=False):
|
|
|
160
161
|
print(f"Check: {report['check']}")
|
|
161
162
|
|
|
162
163
|
|
|
163
|
-
def run_auth(args, parser):
|
|
164
|
+
def run_auth(args: argparse.Namespace, parser: argparse.ArgumentParser):
|
|
164
165
|
store = ConfigStore()
|
|
165
166
|
command = args.auth_command
|
|
166
167
|
if command == "login":
|
|
@@ -198,20 +199,19 @@ def run_auth(args, parser):
|
|
|
198
199
|
raise ConfigError("API_URL_REQUIRED")
|
|
199
200
|
if not no_check:
|
|
200
201
|
scope = _verify_key(url, value, saved.get("workspace_id"), saved.get("project_id"))
|
|
202
|
+
if scope is not None:
|
|
203
|
+
store.configure(url, value, expected_saved=saved, expected_credential=credential, scope=scope)
|
|
204
|
+
else:
|
|
205
|
+
store.update(args.field, value, expected_url=url, expected_saved=saved, expected_credential=credential)
|
|
201
206
|
elif args.field == "api-url":
|
|
202
207
|
if args.stdin:
|
|
203
208
|
parser.error("this field does not support --stdin")
|
|
204
209
|
value = args.value if args.value is not None else DEFAULT_BASE_URL
|
|
210
|
+
store.update(args.field, value)
|
|
205
211
|
else:
|
|
206
212
|
if args.value is None or args.stdin:
|
|
207
213
|
parser.error("this field requires a value and does not support --stdin")
|
|
208
214
|
value = args.value
|
|
209
|
-
if args.field == "api-key":
|
|
210
|
-
if scope is not None:
|
|
211
|
-
store.configure(url, value, expected_saved=saved, expected_credential=credential, scope=scope)
|
|
212
|
-
else:
|
|
213
|
-
store.update(args.field, value, expected_url=url, expected_saved=saved, expected_credential=credential)
|
|
214
|
-
else:
|
|
215
215
|
store.update(args.field, value)
|
|
216
216
|
if args.field == "api-key":
|
|
217
217
|
print("Saved. API key check: " + ("not_checked (offline)" if no_check else "authentication_confirmed"))
|
|
@@ -282,7 +282,7 @@ def run_auth(args, parser):
|
|
|
282
282
|
|
|
283
283
|
report["check"] = "edge_rejected" if getattr(error, "code", None) == "EDGE_REJECTED" else {
|
|
284
284
|
401: "authentication_rejected", 403: "authorization_denied"}.get(
|
|
285
|
-
getattr(error, "status_code", None), safe_error_code(error))
|
|
285
|
+
cast(int, getattr(error, "status_code", None)), safe_error_code(error))
|
|
286
286
|
_print(report, args.json)
|
|
287
287
|
print(format_error(error), file=sys.stderr)
|
|
288
288
|
return 1
|