envloop-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.
- envloop_cli-0.1.0/.gitignore +5 -0
- envloop_cli-0.1.0/PKG-INFO +89 -0
- envloop_cli-0.1.0/README.md +81 -0
- envloop_cli-0.1.0/pyproject.toml +35 -0
- envloop_cli-0.1.0/src/el_cli/__init__.py +1 -0
- envloop_cli-0.1.0/src/el_cli/cli.py +257 -0
- envloop_cli-0.1.0/src/el_cli/client.py +335 -0
- envloop_cli-0.1.0/src/el_cli/http.py +115 -0
- envloop_cli-0.1.0/src/el_cli/train.py +53 -0
- envloop_cli-0.1.0/src/el_cli/utils/__init__.py +1 -0
- envloop_cli-0.1.0/src/el_cli/utils/logging.py +30 -0
- envloop_cli-0.1.0/tests/test_bearer.py +117 -0
- envloop_cli-0.1.0/tests/test_cli_help.py +98 -0
- envloop_cli-0.1.0/tests/test_job_submit_cli.py +50 -0
- envloop_cli-0.1.0/tests/test_jobs_http.py +183 -0
- envloop_cli-0.1.0/tests/test_train_cli.py +42 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: envloop-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Standalone el command-line client for EnvLoop
|
|
5
|
+
Project-URL: Repository, https://github.com/EnvLoop/envloop
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# envloop-cli
|
|
10
|
+
|
|
11
|
+
Independent Python 3.11+ project providing the existing `el` CLI, version 0.1.0.
|
|
12
|
+
It builds and installs without the envloop SDK checkout or any third-party runtime
|
|
13
|
+
dependencies. The distribution is `envloop-cli`; its private implementation package
|
|
14
|
+
is `el_cli`, so it does not overwrite the `envloop` SDK namespace.
|
|
15
|
+
|
|
16
|
+
After publication:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install envloop-cli==0.1.0
|
|
20
|
+
el --help
|
|
21
|
+
el train --help
|
|
22
|
+
el --api-url https://api.envloop.ai --workspace-id YOUR_WORKSPACE train job list
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Set `ENVLOOP_API_KEY` privately in your environment. Existing command names,
|
|
26
|
+
arguments, JSON output, error codes and `envloop/0.1` User-Agent are preserved.
|
|
27
|
+
Local Task/Profile commands still require a separately installed compatible
|
|
28
|
+
EnvPlatform, just as before; this package does not bundle an execution backend.
|
|
29
|
+
No Tinker SDK, GPU, Docker or EnvHub dependencies are needed for remote queries.
|
|
30
|
+
|
|
31
|
+
## Boundary and layout
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
el-cli/
|
|
35
|
+
pyproject.toml
|
|
36
|
+
src/el_cli/
|
|
37
|
+
__init__.py
|
|
38
|
+
cli.py
|
|
39
|
+
client.py
|
|
40
|
+
http.py
|
|
41
|
+
train.py
|
|
42
|
+
utils/{__init__,logging}.py
|
|
43
|
+
tests/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The actual source has `envloop/cli.py` and `envloop/train.py`, rather than the
|
|
47
|
+
`cli/commands/train.py` layout in the brief. `cli.py` imports Client and Train;
|
|
48
|
+
Client uses the logging helper and lazily loads HttpService and Train. All use
|
|
49
|
+
the standard library. Client also lazily imports EnvPlatform for local execution.
|
|
50
|
+
Retaining Client preserves existing task/job/profile behavior rather than shipping
|
|
51
|
+
a train-only CLI. These modules were extracted from the training-read CLI at
|
|
52
|
+
`b546830`, based on main `11a5803`. Shared SDK/transport fixes should be applied
|
|
53
|
+
to both packages while their copied implementations remain separate.
|
|
54
|
+
|
|
55
|
+
The SDK keeps its Python APIs and legacy `python -m envloop.cli` interface, but
|
|
56
|
+
no longer registers `el`; install this project for that executable.
|
|
57
|
+
|
|
58
|
+
## Build, verify and publish
|
|
59
|
+
|
|
60
|
+
From this directory:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv sync --locked
|
|
64
|
+
uv run --locked pytest -q --ignore=tests/test_jobs_http.py
|
|
65
|
+
uv run --locked ruff check .
|
|
66
|
+
uv build
|
|
67
|
+
python -m venv /tmp/el-cli-clean
|
|
68
|
+
/tmp/el-cli-clean/bin/python -m pip install dist/envloop_cli-0.1.0-py3-none-any.whl
|
|
69
|
+
/tmp/el-cli-clean/bin/el --help
|
|
70
|
+
/tmp/el-cli-clean/bin/el train --help
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The real JobMaster integration cases in `tests/test_jobs_http.py` also require
|
|
74
|
+
a separately installed EnvPlatform and its dependencies. Run the complete suite
|
|
75
|
+
with that backend installed; the other cases need only the dev group.
|
|
76
|
+
|
|
77
|
+
The workstation's Aliyun index is a download mirror, not an upload endpoint.
|
|
78
|
+
No existing private upload repository or publishing credentials were found on
|
|
79
|
+
2026-10-08. The intended public target is PyPI, upload endpoint
|
|
80
|
+
`https://upload.pypi.org/legacy/`. Release remains unpublished until an authorized
|
|
81
|
+
PyPI token (or configured Trusted Publisher) is available. Never put credentials
|
|
82
|
+
in this project. With a token supplied privately as `UV_PUBLISH_TOKEN`:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv publish --publish-url https://upload.pypi.org/legacy/ dist/*
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Building a wheel does not establish that the PyPI name is reserved or that a
|
|
89
|
+
release is available. Verify index installation after a successful upload.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# envloop-cli
|
|
2
|
+
|
|
3
|
+
Independent Python 3.11+ project providing the existing `el` CLI, version 0.1.0.
|
|
4
|
+
It builds and installs without the envloop SDK checkout or any third-party runtime
|
|
5
|
+
dependencies. The distribution is `envloop-cli`; its private implementation package
|
|
6
|
+
is `el_cli`, so it does not overwrite the `envloop` SDK namespace.
|
|
7
|
+
|
|
8
|
+
After publication:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install envloop-cli==0.1.0
|
|
12
|
+
el --help
|
|
13
|
+
el train --help
|
|
14
|
+
el --api-url https://api.envloop.ai --workspace-id YOUR_WORKSPACE train job list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Set `ENVLOOP_API_KEY` privately in your environment. Existing command names,
|
|
18
|
+
arguments, JSON output, error codes and `envloop/0.1` User-Agent are preserved.
|
|
19
|
+
Local Task/Profile commands still require a separately installed compatible
|
|
20
|
+
EnvPlatform, just as before; this package does not bundle an execution backend.
|
|
21
|
+
No Tinker SDK, GPU, Docker or EnvHub dependencies are needed for remote queries.
|
|
22
|
+
|
|
23
|
+
## Boundary and layout
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
el-cli/
|
|
27
|
+
pyproject.toml
|
|
28
|
+
src/el_cli/
|
|
29
|
+
__init__.py
|
|
30
|
+
cli.py
|
|
31
|
+
client.py
|
|
32
|
+
http.py
|
|
33
|
+
train.py
|
|
34
|
+
utils/{__init__,logging}.py
|
|
35
|
+
tests/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The actual source has `envloop/cli.py` and `envloop/train.py`, rather than the
|
|
39
|
+
`cli/commands/train.py` layout in the brief. `cli.py` imports Client and Train;
|
|
40
|
+
Client uses the logging helper and lazily loads HttpService and Train. All use
|
|
41
|
+
the standard library. Client also lazily imports EnvPlatform for local execution.
|
|
42
|
+
Retaining Client preserves existing task/job/profile behavior rather than shipping
|
|
43
|
+
a train-only CLI. These modules were extracted from the training-read CLI at
|
|
44
|
+
`b546830`, based on main `11a5803`. Shared SDK/transport fixes should be applied
|
|
45
|
+
to both packages while their copied implementations remain separate.
|
|
46
|
+
|
|
47
|
+
The SDK keeps its Python APIs and legacy `python -m envloop.cli` interface, but
|
|
48
|
+
no longer registers `el`; install this project for that executable.
|
|
49
|
+
|
|
50
|
+
## Build, verify and publish
|
|
51
|
+
|
|
52
|
+
From this directory:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv sync --locked
|
|
56
|
+
uv run --locked pytest -q --ignore=tests/test_jobs_http.py
|
|
57
|
+
uv run --locked ruff check .
|
|
58
|
+
uv build
|
|
59
|
+
python -m venv /tmp/el-cli-clean
|
|
60
|
+
/tmp/el-cli-clean/bin/python -m pip install dist/envloop_cli-0.1.0-py3-none-any.whl
|
|
61
|
+
/tmp/el-cli-clean/bin/el --help
|
|
62
|
+
/tmp/el-cli-clean/bin/el train --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The real JobMaster integration cases in `tests/test_jobs_http.py` also require
|
|
66
|
+
a separately installed EnvPlatform and its dependencies. Run the complete suite
|
|
67
|
+
with that backend installed; the other cases need only the dev group.
|
|
68
|
+
|
|
69
|
+
The workstation's Aliyun index is a download mirror, not an upload endpoint.
|
|
70
|
+
No existing private upload repository or publishing credentials were found on
|
|
71
|
+
2026-10-08. The intended public target is PyPI, upload endpoint
|
|
72
|
+
`https://upload.pypi.org/legacy/`. Release remains unpublished until an authorized
|
|
73
|
+
PyPI token (or configured Trusted Publisher) is available. Never put credentials
|
|
74
|
+
in this project. With a token supplied privately as `UV_PUBLISH_TOKEN`:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv publish --publish-url https://upload.pypi.org/legacy/ dist/*
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Building a wheel does not establish that the PyPI name is reserved or that a
|
|
81
|
+
release is available. Verify index installation after a successful upload.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "envloop-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Standalone el command-line client for EnvLoop"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.scripts]
|
|
10
|
+
el = "el_cli.cli:main"
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Repository = "https://github.com/EnvLoop/envloop"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["src/el_cli"]
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.sdist]
|
|
23
|
+
include = ["src", "tests", "README.md", "pyproject.toml"]
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = ["pytest>=8", "ruff==0.16.10"]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = ["tests"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
target-version = "py311"
|
|
33
|
+
|
|
34
|
+
[tool.ruff.lint]
|
|
35
|
+
select = ["I"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Standalone EnvLoop command-line client."""
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""el: local Profile and Task operations built entirely on the SDK."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import shlex
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import train
|
|
11
|
+
from .client import Client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HelpArgumentParser(argparse.ArgumentParser):
|
|
15
|
+
"""Show full command help for syntax and usage errors."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
super().__init__(*args, **kwargs)
|
|
19
|
+
self.set_defaults(_help_parser=self)
|
|
20
|
+
self._help_parser = self
|
|
21
|
+
|
|
22
|
+
def parse_known_args(self, args=None, namespace=None):
|
|
23
|
+
self._help_parser = self
|
|
24
|
+
parsed, remaining = super().parse_known_args(args, namespace)
|
|
25
|
+
# Subparsers propagate their defaults; keep the deepest selected command
|
|
26
|
+
# for unknown arguments and usage checks performed after parsing.
|
|
27
|
+
self._help_parser = parsed._help_parser
|
|
28
|
+
return parsed, remaining
|
|
29
|
+
|
|
30
|
+
def error(self, message):
|
|
31
|
+
command = self._help_parser
|
|
32
|
+
command.print_help(sys.stderr)
|
|
33
|
+
command.exit(2, f"\n{command.prog}: error: {message}\n")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def dataset_reference(value):
|
|
37
|
+
name, separator, version = value.rpartition("@")
|
|
38
|
+
if (not value.strip() or (separator and (not name.strip() or not version.strip()))):
|
|
39
|
+
raise argparse.ArgumentTypeError(
|
|
40
|
+
"expected name or name@version with a non-empty name and version "
|
|
41
|
+
"(e.g. org/dataset@1.0)"
|
|
42
|
+
)
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def submit_arguments(submit):
|
|
47
|
+
submit.add_argument("source", type=Path)
|
|
48
|
+
submit.add_argument(
|
|
49
|
+
"--profile", help="name@version, or name with an explicit default; latest is literal",
|
|
50
|
+
)
|
|
51
|
+
submit.add_argument("--profile-sha256", help="Expected canonical ProfileSpec SHA-256")
|
|
52
|
+
submit.add_argument("--request-id")
|
|
53
|
+
submit.add_argument("--seed", type=int)
|
|
54
|
+
submit.add_argument("--timeout-sec", type=int)
|
|
55
|
+
submit.add_argument("--executor", choices=["local", "docker", "sandbox"])
|
|
56
|
+
submit.add_argument("--provider", choices=["e2b", "e2b-local"])
|
|
57
|
+
submit.add_argument("--wait", action="store_true")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parser():
|
|
61
|
+
cli = HelpArgumentParser(prog="el")
|
|
62
|
+
cli.add_argument("--repository-root", type=Path)
|
|
63
|
+
cli.add_argument("--repository-commit")
|
|
64
|
+
cli.add_argument("--work-dir", type=Path)
|
|
65
|
+
cli.add_argument("--api-url", default=os.environ.get("ENVLOOP_BASE_URL", os.environ.get("ENVPLATFORM_API_URL")), help="HTTPS apiserver or loopback URL; defaults to ENVLOOP_BASE_URL; Bearer key from ENVLOOP_API_KEY")
|
|
66
|
+
cli.add_argument("--workspace-id", default=os.environ.get("ENVLOOP_WORKSPACE_ID"), help="Optional workspace override; defaults to the user's authorized workspace")
|
|
67
|
+
groups = cli.add_subparsers(dest="group", required=True)
|
|
68
|
+
train.register(groups)
|
|
69
|
+
profiles = groups.add_parser("profile").add_subparsers(dest="action", required=True)
|
|
70
|
+
profiles.add_parser("init")
|
|
71
|
+
put = profiles.add_parser("put")
|
|
72
|
+
put.add_argument("name")
|
|
73
|
+
put.add_argument("version")
|
|
74
|
+
put.add_argument("--file", type=Path, required=True, help="ProfileSpec JSON file")
|
|
75
|
+
get = profiles.add_parser("get")
|
|
76
|
+
get.add_argument("name")
|
|
77
|
+
get.add_argument("--version")
|
|
78
|
+
listing = profiles.add_parser("list")
|
|
79
|
+
listing.add_argument("--name")
|
|
80
|
+
listing.add_argument("--include-archived", action="store_true")
|
|
81
|
+
listing.add_argument("--limit", type=int, default=50)
|
|
82
|
+
listing.add_argument("--cursor")
|
|
83
|
+
default = profiles.add_parser("set-default")
|
|
84
|
+
default.add_argument("name")
|
|
85
|
+
default.add_argument("version")
|
|
86
|
+
default.add_argument("--expected-version", help="Omit only when no default exists")
|
|
87
|
+
for command in ("archive", "export"):
|
|
88
|
+
item = profiles.add_parser(command)
|
|
89
|
+
item.add_argument("name")
|
|
90
|
+
item.add_argument("version")
|
|
91
|
+
tasks = groups.add_parser("task").add_subparsers(dest="action", required=True)
|
|
92
|
+
submit = tasks.add_parser("submit")
|
|
93
|
+
submit_arguments(submit)
|
|
94
|
+
submissions = groups.add_parser("submit").add_subparsers(dest="action", required=True)
|
|
95
|
+
submit_arguments(submissions.add_parser("task"))
|
|
96
|
+
snapshot = tasks.add_parser("snapshot")
|
|
97
|
+
snapshot.add_argument("trial_id")
|
|
98
|
+
for action in ("status", "stop", "result"):
|
|
99
|
+
tasks.add_parser(action).add_argument("trial_id")
|
|
100
|
+
jobs = groups.add_parser("job").add_subparsers(dest="action", required=True)
|
|
101
|
+
job_submit = jobs.add_parser("submit")
|
|
102
|
+
job_submit.add_argument(
|
|
103
|
+
"dataset", nargs="?", type=dataset_reference, metavar="NAME[@VERSION]",
|
|
104
|
+
help="dataset name; optional author version (legacy positional form)",
|
|
105
|
+
)
|
|
106
|
+
job_submit.add_argument("--dataset", dest="dataset_option", type=dataset_reference, metavar="NAME[@VERSION]",
|
|
107
|
+
help="dataset name; default selection uses the platform's pinned snapshot")
|
|
108
|
+
job_submit.add_argument("--namespace", help="explicit namespace for a short dataset name")
|
|
109
|
+
job_submit.add_argument("--config", help="dataset subset; uses its declared default when omitted")
|
|
110
|
+
job_submit.add_argument("--split", help="one complete split name; omitted selects all splits")
|
|
111
|
+
job_submit.add_argument("--revision", help="Git branch/tag/commit; must match the platform's pinned snapshot")
|
|
112
|
+
job_submit.add_argument("--attempts", type=int, default=1)
|
|
113
|
+
job_submit.add_argument("--request-id")
|
|
114
|
+
job_submit.add_argument("--profile")
|
|
115
|
+
job_submit.add_argument("--seed", type=int)
|
|
116
|
+
job_submit.add_argument("--timeout-sec", type=int)
|
|
117
|
+
job_submit.add_argument("--executor", choices=["local", "docker", "sandbox"])
|
|
118
|
+
job_submit.add_argument("--provider", choices=["e2b", "e2b-local"])
|
|
119
|
+
job_submit.add_argument("--wait", action="store_true")
|
|
120
|
+
for action in ("status", "stop", "result"):
|
|
121
|
+
jobs.add_parser(action).add_argument("job_id")
|
|
122
|
+
listing = jobs.add_parser("list")
|
|
123
|
+
listing.add_argument("--cursor")
|
|
124
|
+
listing.add_argument("--limit", type=int, default=25)
|
|
125
|
+
return cli
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def submission_options(args):
|
|
129
|
+
options = {}
|
|
130
|
+
if args.profile:
|
|
131
|
+
if "@" not in args.profile:
|
|
132
|
+
name, version = args.profile, None
|
|
133
|
+
elif args.group == "job":
|
|
134
|
+
name, version = args.profile.rsplit("@", 1)
|
|
135
|
+
else:
|
|
136
|
+
name, version = args.profile.split("@", 1)
|
|
137
|
+
options["profile_name"] = name
|
|
138
|
+
if version is not None:
|
|
139
|
+
options["profile_version"] = version
|
|
140
|
+
if args.seed is not None:
|
|
141
|
+
options["seed"] = args.seed
|
|
142
|
+
execution = {key: value for key, value in {"timeout_sec": args.timeout_sec, "executor": args.executor, "provider": args.provider}.items() if value is not None}
|
|
143
|
+
if execution:
|
|
144
|
+
options["execution"] = execution
|
|
145
|
+
return options
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def local_api_url():
|
|
149
|
+
"""Read only the CLI connection setting; never import task secrets."""
|
|
150
|
+
path = Path.cwd() / ".env.local"
|
|
151
|
+
if not path.is_file():
|
|
152
|
+
return None
|
|
153
|
+
values = {}
|
|
154
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
155
|
+
key, separator, raw = line.strip().removeprefix("export ").partition("=")
|
|
156
|
+
if separator and key.strip() in {"ENVLOOP_BASE_URL", "ENVPLATFORM_API_URL"}:
|
|
157
|
+
values[key.strip()] = raw
|
|
158
|
+
raw = values.get("ENVLOOP_BASE_URL", values.get("ENVPLATFORM_API_URL"))
|
|
159
|
+
if raw is None:
|
|
160
|
+
return None
|
|
161
|
+
parts = shlex.split(raw, comments=True)
|
|
162
|
+
if len(parts) > 1:
|
|
163
|
+
raise ValueError("invalid API URL configuration")
|
|
164
|
+
return parts[0] if parts else ""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def main(argv=None):
|
|
168
|
+
cli = parser()
|
|
169
|
+
args = cli.parse_args(argv)
|
|
170
|
+
if args.group == "job" and args.action == "submit":
|
|
171
|
+
if (args.dataset is None) == (args.dataset_option is None):
|
|
172
|
+
cli.error("provide exactly one positional dataset or --dataset")
|
|
173
|
+
args.dataset = args.dataset if args.dataset is not None else args.dataset_option
|
|
174
|
+
if args.api_url is not None and (args.repository_root or args.repository_commit or args.work_dir or args.group == "profile"):
|
|
175
|
+
cli.error("--api-url cannot be combined with local repository/profile options")
|
|
176
|
+
try:
|
|
177
|
+
if args.api_url is None and not (
|
|
178
|
+
args.repository_root or args.repository_commit or args.work_dir
|
|
179
|
+
or args.group == "profile"
|
|
180
|
+
):
|
|
181
|
+
args.api_url = local_api_url()
|
|
182
|
+
if args.group == "profile" and args.action == "init":
|
|
183
|
+
if args.repository_root is None:
|
|
184
|
+
cli.error("--repository-root is required")
|
|
185
|
+
Client.init_repository(args.repository_root)
|
|
186
|
+
print(json.dumps({"initialized": True}))
|
|
187
|
+
return 0
|
|
188
|
+
if args.group == "train" and args.api_url is None:
|
|
189
|
+
cli.error("train requires --api-url or ENVLOOP_BASE_URL")
|
|
190
|
+
connection = Client.remote(args.api_url, workspace_id=args.workspace_id) if args.api_url is not None else Client.local(
|
|
191
|
+
repository_root=args.repository_root,
|
|
192
|
+
repository_commit=args.repository_commit,
|
|
193
|
+
work_dir=args.work_dir,
|
|
194
|
+
)
|
|
195
|
+
with connection as client:
|
|
196
|
+
if args.group == "train":
|
|
197
|
+
result = train.execute(client.train, args)
|
|
198
|
+
elif args.group == "profile":
|
|
199
|
+
if args.action == "put":
|
|
200
|
+
result = client.profiles.import_spec(
|
|
201
|
+
args.file.read_bytes(), name=args.name, version=args.version
|
|
202
|
+
)
|
|
203
|
+
elif args.action == "get":
|
|
204
|
+
result = client.profiles.get(args.name, args.version)
|
|
205
|
+
elif args.action == "list":
|
|
206
|
+
result = client.profiles.list(
|
|
207
|
+
name=args.name,
|
|
208
|
+
include_archived=args.include_archived,
|
|
209
|
+
limit=args.limit,
|
|
210
|
+
cursor=args.cursor,
|
|
211
|
+
)
|
|
212
|
+
elif args.action == "set-default":
|
|
213
|
+
result = client.profiles.set_default(
|
|
214
|
+
args.name, args.version, expected_version=args.expected_version
|
|
215
|
+
)
|
|
216
|
+
elif args.action == "archive":
|
|
217
|
+
result = client.profiles.archive(args.name, args.version)
|
|
218
|
+
else:
|
|
219
|
+
sys.stdout.buffer.write(
|
|
220
|
+
client.profiles.export(args.name, args.version) + b"\n"
|
|
221
|
+
)
|
|
222
|
+
return 0
|
|
223
|
+
elif args.group == "job":
|
|
224
|
+
if args.action == "submit":
|
|
225
|
+
result = client.jobs.submit(args.dataset, attempts=args.attempts, request_id=args.request_id,
|
|
226
|
+
namespace=args.namespace, config=args.config, split=args.split,
|
|
227
|
+
revision=args.revision, **submission_options(args))
|
|
228
|
+
if not args.api_url or args.wait:
|
|
229
|
+
result = client.jobs.wait(result["job_id"])
|
|
230
|
+
elif args.action == "list":
|
|
231
|
+
result = client.jobs.list(cursor=args.cursor, limit=args.limit)
|
|
232
|
+
else:
|
|
233
|
+
result = getattr(client.jobs, args.action)(args.job_id)
|
|
234
|
+
elif args.action in {"snapshot", "status", "stop", "result"}:
|
|
235
|
+
result = getattr(client.tasks, args.action)(args.trial_id)
|
|
236
|
+
else:
|
|
237
|
+
trial = client.tasks.submit(
|
|
238
|
+
args.source,
|
|
239
|
+
request_id=args.request_id,
|
|
240
|
+
profile_sha256=args.profile_sha256,
|
|
241
|
+
**submission_options(args),
|
|
242
|
+
)
|
|
243
|
+
if not args.api_url or args.wait:
|
|
244
|
+
client.tasks.wait(trial["trial_id"], timeout=600)
|
|
245
|
+
result = client.tasks.result(trial["trial_id"])
|
|
246
|
+
else:
|
|
247
|
+
result = trial
|
|
248
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
249
|
+
return 1 if result.get("status") == "failed" else 0
|
|
250
|
+
except Exception as exc:
|
|
251
|
+
# Do not emit arbitrary exception messages, file content or credentials.
|
|
252
|
+
print(getattr(exc, "code", None) or type(exc).__name__, file=sys.stderr)
|
|
253
|
+
return 1
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
raise SystemExit(main())
|