robovast-client 2.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.
- robovast_client-2.1.0/PKG-INFO +97 -0
- robovast_client-2.1.0/README.md +80 -0
- robovast_client-2.1.0/pyproject.toml +51 -0
- robovast_client-2.1.0/robovast/client/__init__.py +17 -0
- robovast_client-2.1.0/robovast/client/app_version.py +65 -0
- robovast_client-2.1.0/robovast/client/campaign_cli.py +804 -0
- robovast_client-2.1.0/robovast/client/cli.py +1256 -0
- robovast_client-2.1.0/robovast/client/cluster_cli.py +94 -0
- robovast_client-2.1.0/robovast/client/container_cli.py +110 -0
- robovast_client-2.1.0/robovast/client/doctor.py +792 -0
- robovast_client-2.1.0/robovast/client/errors.py +38 -0
- robovast_client-2.1.0/robovast/client/file_address.py +166 -0
- robovast_client-2.1.0/robovast/client/lazy_group.py +65 -0
- robovast_client-2.1.0/robovast/client/logging_config.py +158 -0
- robovast_client-2.1.0/robovast/client/login.py +261 -0
- robovast_client-2.1.0/robovast/client/progress.py +175 -0
- robovast_client-2.1.0/robovast/client/safe_path.py +107 -0
- robovast_client-2.1.0/robovast/client/scene_markers.py +116 -0
- robovast_client-2.1.0/robovast/client/service_cli.py +393 -0
- robovast_client-2.1.0/robovast/client/service_target.py +148 -0
- robovast_client-2.1.0/robovast/client/status.py +764 -0
- robovast_client-2.1.0/robovast/client/tail.py +54 -0
- robovast_client-2.1.0/robovast/client/workspaces.py +75 -0
- robovast_client-2.1.0/robovast/execution/campaign_wait.py +144 -0
- robovast_client-2.1.0/robovast/execution/image_build_wait.py +129 -0
- robovast_client-2.1.0/robovast/execution/poll_health.py +116 -0
- robovast_client-2.1.0/robovast/service/auth.py +271 -0
- robovast_client-2.1.0/robovast/service/http_client.py +716 -0
- robovast_client-2.1.0/robovast/service/interface.py +3557 -0
- robovast_client-2.1.0/robovast/service/project_push.py +479 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: robovast-client
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Talk to a RoboVAST service: upload projects, launch campaigns, wait for them, fetch results.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Author: Frederik Pasch
|
|
7
|
+
Author-email: fred-labs@mailbox.org
|
|
8
|
+
Requires-Python: >=3.12,<3.14
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: click (>=8.1,<9.0)
|
|
13
|
+
Requires-Dist: pydantic (>=2.12,<3.0)
|
|
14
|
+
Requires-Dist: requests (>=2.31,<3.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# robovast-client
|
|
18
|
+
|
|
19
|
+
Talk to a running [RoboVAST](https://github.com/cps-test-lab/robovast) service — upload a
|
|
20
|
+
project, launch a campaign, wait for it, fetch the results — without installing RoboVAST.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install robovast-client
|
|
24
|
+
vast login https://robovast.example.org
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What it is for
|
|
28
|
+
|
|
29
|
+
RoboVAST runs simulation campaigns: a scenario, a sweep of configurations, repeated runs,
|
|
30
|
+
recorded provenance. That work happens **on the service**, on a Docker host or a
|
|
31
|
+
Kubernetes cluster. If you are the person *driving* it rather than the person *hosting*
|
|
32
|
+
it, this is all you need.
|
|
33
|
+
|
|
34
|
+
The full `robovast` distribution pulls in a simulator stack, an array library and a
|
|
35
|
+
dataframe library — 88 packages, around 290 MB — because it can execute campaigns itself.
|
|
36
|
+
This one is three dependencies (`pydantic`, `click`, `requests`) and about 30 MB, because
|
|
37
|
+
it only talks to something that can.
|
|
38
|
+
|
|
39
|
+
> **Not yet on PyPI.** Install it from a checkout with `pip install src/robovast_client`.
|
|
40
|
+
|
|
41
|
+
## What you get
|
|
42
|
+
|
|
43
|
+
`vast` grows commands as capability is installed; with only the client, it is:
|
|
44
|
+
|
|
45
|
+
| Command | Does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `vast login <url>` / `vast logout` | store or forget the service credentials |
|
|
48
|
+
| **`vast workspace run`** | **launch a campaign** — pushes the project and starts it; `--wait-and-download` blocks and fetches the results |
|
|
49
|
+
| `vast campaign stop/stop-job/log` | stop a campaign, kill one wedged job, read its infrastructure log |
|
|
50
|
+
| `vast workspace init/update/list/delete` | push a project directory to the service |
|
|
51
|
+
| `vast files get/put` | move a single file by address |
|
|
52
|
+
| `vast image build/wait/status/log` | have the service build a project's derived images |
|
|
53
|
+
| `vast campaign wait <campaign-id>` | block until a campaign is genuinely over |
|
|
54
|
+
| `vast doctor` | check the login, the service and your PATH |
|
|
55
|
+
|
|
56
|
+
`vast cluster` and `vast service` are partly here on purpose: a subcommand exists exactly
|
|
57
|
+
when something that can perform it is installed. The verbs above only drive a service, so
|
|
58
|
+
they are complete here. `vast cluster setup/cleanup/jobs-cleanup/monitor` and `vast service
|
|
59
|
+
upgrade/token` need a kubeconfig and come with `robovast-cluster`. So `vast cluster --help`
|
|
60
|
+
lists `store-cleanup` and not `setup` — nothing is stubbed and nothing fails on use.
|
|
61
|
+
|
|
62
|
+
Both groups spanning two distributions is the design: a group is named after the **object**
|
|
63
|
+
it acts on, not after what you installed.
|
|
64
|
+
|
|
65
|
+
There is no ambient project. Every command names its own input, and a campaign runs a
|
|
66
|
+
*workspace's* project — `vast workspace run <workspace> [vast]`.
|
|
67
|
+
|
|
68
|
+
An LLM agent reaches the same service through its MCP endpoint, which needs nothing
|
|
69
|
+
installed at all. `vast login` prints the `claude mcp add` line that registers it. The
|
|
70
|
+
control verbs are deliberately on both sides; each side additionally owns what only it can
|
|
71
|
+
do — bulk bytes and long waits here, results queries and diff-based authoring there.
|
|
72
|
+
|
|
73
|
+
## Waiting for a campaign
|
|
74
|
+
|
|
75
|
+
A campaign can run for days, so nothing blocks a request on one. `vast campaign wait` polls
|
|
76
|
+
the service and exits when the campaign is genuinely finished — past postprocessing, not
|
|
77
|
+
merely past its last run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
vast campaign wait basic-nav-2026-08-16-101500
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Its **exit code is the answer**: `0` finished, `1` failed, `2` you interrupted the wait,
|
|
84
|
+
and a distinct code for "no such campaign" so a typo cannot be mistaken for a failed run.
|
|
85
|
+
Run it as the whole command — chaining anything after it makes the shell report the
|
|
86
|
+
wrapper's status instead, which turns a failed campaign into a reported success.
|
|
87
|
+
|
|
88
|
+
## What it is not
|
|
89
|
+
|
|
90
|
+
No simulator, no Kubernetes client, no Docker, no MuJoCo, no ROS. It cannot execute a
|
|
91
|
+
campaign or build an image itself, only ask a service to. If you need to *run* campaigns on your own machine,
|
|
92
|
+
install `robovast`; to host them on a cluster, add `robovast-cluster`.
|
|
93
|
+
|
|
94
|
+
## Licence
|
|
95
|
+
|
|
96
|
+
Apache-2.0.
|
|
97
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# robovast-client
|
|
2
|
+
|
|
3
|
+
Talk to a running [RoboVAST](https://github.com/cps-test-lab/robovast) service — upload a
|
|
4
|
+
project, launch a campaign, wait for it, fetch the results — without installing RoboVAST.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install robovast-client
|
|
8
|
+
vast login https://robovast.example.org
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it is for
|
|
12
|
+
|
|
13
|
+
RoboVAST runs simulation campaigns: a scenario, a sweep of configurations, repeated runs,
|
|
14
|
+
recorded provenance. That work happens **on the service**, on a Docker host or a
|
|
15
|
+
Kubernetes cluster. If you are the person *driving* it rather than the person *hosting*
|
|
16
|
+
it, this is all you need.
|
|
17
|
+
|
|
18
|
+
The full `robovast` distribution pulls in a simulator stack, an array library and a
|
|
19
|
+
dataframe library — 88 packages, around 290 MB — because it can execute campaigns itself.
|
|
20
|
+
This one is three dependencies (`pydantic`, `click`, `requests`) and about 30 MB, because
|
|
21
|
+
it only talks to something that can.
|
|
22
|
+
|
|
23
|
+
> **Not yet on PyPI.** Install it from a checkout with `pip install src/robovast_client`.
|
|
24
|
+
|
|
25
|
+
## What you get
|
|
26
|
+
|
|
27
|
+
`vast` grows commands as capability is installed; with only the client, it is:
|
|
28
|
+
|
|
29
|
+
| Command | Does |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `vast login <url>` / `vast logout` | store or forget the service credentials |
|
|
32
|
+
| **`vast workspace run`** | **launch a campaign** — pushes the project and starts it; `--wait-and-download` blocks and fetches the results |
|
|
33
|
+
| `vast campaign stop/stop-job/log` | stop a campaign, kill one wedged job, read its infrastructure log |
|
|
34
|
+
| `vast workspace init/update/list/delete` | push a project directory to the service |
|
|
35
|
+
| `vast files get/put` | move a single file by address |
|
|
36
|
+
| `vast image build/wait/status/log` | have the service build a project's derived images |
|
|
37
|
+
| `vast campaign wait <campaign-id>` | block until a campaign is genuinely over |
|
|
38
|
+
| `vast doctor` | check the login, the service and your PATH |
|
|
39
|
+
|
|
40
|
+
`vast cluster` and `vast service` are partly here on purpose: a subcommand exists exactly
|
|
41
|
+
when something that can perform it is installed. The verbs above only drive a service, so
|
|
42
|
+
they are complete here. `vast cluster setup/cleanup/jobs-cleanup/monitor` and `vast service
|
|
43
|
+
upgrade/token` need a kubeconfig and come with `robovast-cluster`. So `vast cluster --help`
|
|
44
|
+
lists `store-cleanup` and not `setup` — nothing is stubbed and nothing fails on use.
|
|
45
|
+
|
|
46
|
+
Both groups spanning two distributions is the design: a group is named after the **object**
|
|
47
|
+
it acts on, not after what you installed.
|
|
48
|
+
|
|
49
|
+
There is no ambient project. Every command names its own input, and a campaign runs a
|
|
50
|
+
*workspace's* project — `vast workspace run <workspace> [vast]`.
|
|
51
|
+
|
|
52
|
+
An LLM agent reaches the same service through its MCP endpoint, which needs nothing
|
|
53
|
+
installed at all. `vast login` prints the `claude mcp add` line that registers it. The
|
|
54
|
+
control verbs are deliberately on both sides; each side additionally owns what only it can
|
|
55
|
+
do — bulk bytes and long waits here, results queries and diff-based authoring there.
|
|
56
|
+
|
|
57
|
+
## Waiting for a campaign
|
|
58
|
+
|
|
59
|
+
A campaign can run for days, so nothing blocks a request on one. `vast campaign wait` polls
|
|
60
|
+
the service and exits when the campaign is genuinely finished — past postprocessing, not
|
|
61
|
+
merely past its last run:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
vast campaign wait basic-nav-2026-08-16-101500
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Its **exit code is the answer**: `0` finished, `1` failed, `2` you interrupted the wait,
|
|
68
|
+
and a distinct code for "no such campaign" so a typo cannot be mistaken for a failed run.
|
|
69
|
+
Run it as the whole command — chaining anything after it makes the shell report the
|
|
70
|
+
wrapper's status instead, which turns a failed campaign into a reported success.
|
|
71
|
+
|
|
72
|
+
## What it is not
|
|
73
|
+
|
|
74
|
+
No simulator, no Kubernetes client, no Docker, no MuJoCo, no ROS. It cannot execute a
|
|
75
|
+
campaign or build an image itself, only ask a service to. If you need to *run* campaigns on your own machine,
|
|
76
|
+
install `robovast`; to host them on a cluster, add `robovast-cluster`.
|
|
77
|
+
|
|
78
|
+
## Licence
|
|
79
|
+
|
|
80
|
+
Apache-2.0.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "robovast-client"
|
|
3
|
+
version = "2.1.0"
|
|
4
|
+
description = "Talk to a RoboVAST service: upload projects, launch campaigns, wait for them, fetch results."
|
|
5
|
+
authors = ["Frederik Pasch <fred-labs@mailbox.org>"]
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
# Ships into the `robovast` namespace rather than a name of its own, so no import path
|
|
9
|
+
# changes: `robovast/`, `robovast/service/` and `robovast/execution/` carry no
|
|
10
|
+
# `__init__.py` in any distribution, which makes them PEP 420 namespace packages and lets
|
|
11
|
+
# the trees merge. `robovast/client/` is a regular package because only this
|
|
12
|
+
# distribution ever provides it.
|
|
13
|
+
packages = [{include = "robovast"}]
|
|
14
|
+
|
|
15
|
+
[tool.poetry.dependencies]
|
|
16
|
+
python = ">=3.12,<3.14"
|
|
17
|
+
# The whole point of this distribution. `pip install robovast` pulls 88 distributions and
|
|
18
|
+
# ~290 MB -- pandas and numpy alone are 152 MB of it -- to run four HTTP verbs. These
|
|
19
|
+
# three are ~30 MB, and nothing here may add a fourth without a reason that survives
|
|
20
|
+
# "could the service do this instead?".
|
|
21
|
+
#
|
|
22
|
+
# Deliberately NOT depending on `robovast`. The dependency runs the other way: robovast is
|
|
23
|
+
# built on this, and an edge back would be a cycle.
|
|
24
|
+
pydantic = "^2.12"
|
|
25
|
+
click = "^8.1"
|
|
26
|
+
requests = "^2.31"
|
|
27
|
+
|
|
28
|
+
# The `vast` command itself. It lives here because every audience has one: the core, the
|
|
29
|
+
# execution lanes and the operator commands all attach their verbs to this group through
|
|
30
|
+
# `robovast.cli_plugins`.
|
|
31
|
+
[tool.poetry.scripts]
|
|
32
|
+
vast = "robovast.client.cli:main"
|
|
33
|
+
|
|
34
|
+
# The groups are named after what they act on, and each ships wherever its verbs can run.
|
|
35
|
+
# `campaign` and `container` only ever drive a service, so they are wholly the client's.
|
|
36
|
+
# `service` and `cluster` span two distributions on purpose: the group is defined by its
|
|
37
|
+
# object, not by what you installed, so `service restart` (a URL and a token) is here while
|
|
38
|
+
# `service upgrade` (an API server) attaches from robovast-cluster.
|
|
39
|
+
#
|
|
40
|
+
# Verbs defined in the same module as their group stay ordinary `@group.command()`s rather
|
|
41
|
+
# than entry points: metadata is for crossing distributions, and a distribution's own verbs
|
|
42
|
+
# must not vanish on a pyproject edit that was not followed by a reinstall.
|
|
43
|
+
[tool.poetry.plugins."robovast.cli_plugins"]
|
|
44
|
+
campaign = "robovast.client.campaign_cli:campaign"
|
|
45
|
+
service = "robovast.client.service_cli:service"
|
|
46
|
+
cluster = "robovast.client.cluster_cli:cluster"
|
|
47
|
+
container = "robovast.client.container_cli:container"
|
|
48
|
+
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["poetry-core"]
|
|
51
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright (C) 2026 Frederik Pasch
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""What a client needs to talk to a robovast-service, and nothing else.
|
|
5
|
+
|
|
6
|
+
These modules were under :mod:`robovast.common`, which cannot host them once the client
|
|
7
|
+
ships as its own distribution: ``common/__init__.py`` carries the lazy re-export map, so
|
|
8
|
+
the package is a regular one and its contents cannot be split across two distributions.
|
|
9
|
+
``robovast.client`` is a package of its own for exactly that reason.
|
|
10
|
+
|
|
11
|
+
The rule for what belongs here is the install it has to survive in: ``pydantic``,
|
|
12
|
+
``click`` and ``requests``, with no simulator, no Kubernetes client and no array library.
|
|
13
|
+
Anything reaching further belongs in the core.
|
|
14
|
+
|
|
15
|
+
Deliberately kept empty of re-exports. The parent it came from cost 528 modules to touch
|
|
16
|
+
because its ``__init__`` imported eagerly; repeating that here would undo the point.
|
|
17
|
+
"""
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright (C) 2026 Frederik Pasch
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""The version of the code *this process is running*.
|
|
5
|
+
|
|
6
|
+
Lives here rather than beside the in-process service because both sides of the version
|
|
7
|
+
handshake need it: the HTTP client reports its own version, the service reports the one it
|
|
8
|
+
loaded. Keeping it in ``local_transport`` meant a client could not ask "what am I running?"
|
|
9
|
+
without importing the whole in-process server.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from importlib.metadata import PackageNotFoundError
|
|
13
|
+
from importlib.metadata import version as _pkg_version
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def running_version() -> str:
|
|
17
|
+
"""The running code's version: git revision if available, else package metadata.
|
|
18
|
+
|
|
19
|
+
The preference is the point. A service is long-lived and loads its code once, so a
|
|
20
|
+
client needs to tell "the fix I just made is loaded" from "this process predates it".
|
|
21
|
+
The packaged version alone cannot — it stays ``2.0.0`` across every edit.
|
|
22
|
+
|
|
23
|
+
Never raises. The git-revision path needs ``robovast.common.execution``, which a
|
|
24
|
+
client-only install does not have, so the import is inside the ``try`` with everything
|
|
25
|
+
else: a version string is diagnostic, and failing to produce one must not break the
|
|
26
|
+
handshake it is part of.
|
|
27
|
+
|
|
28
|
+
The distributions are tried in order of how much they say about the running code:
|
|
29
|
+
``robovast`` when it is installed, then ``robovast-client``, which is installed in
|
|
30
|
+
*every* case and so is the last answer that is still a fact. ``0.0.0+unknown`` remains
|
|
31
|
+
only for a source tree with no metadata at all — reported by a perfectly good
|
|
32
|
+
client-only install, it would read as a broken install rather than a small one.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
from robovast.common.execution import \
|
|
36
|
+
get_app_version # pylint: disable=import-outside-toplevel
|
|
37
|
+
return get_app_version()
|
|
38
|
+
except Exception: # noqa: BLE001 - version reporting must never break the handshake
|
|
39
|
+
for dist in ("robovast", "robovast-client"):
|
|
40
|
+
try:
|
|
41
|
+
return _pkg_version(dist)
|
|
42
|
+
except PackageNotFoundError:
|
|
43
|
+
continue
|
|
44
|
+
return "0.0.0+unknown" # a source tree with no metadata
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def running_revision() -> str:
|
|
48
|
+
"""The running code's git revision, or ``""`` when it cannot be determined.
|
|
49
|
+
|
|
50
|
+
The strict half of :func:`running_version`, and the reason both exist: a *version* is
|
|
51
|
+
allowed to fall back to package metadata, because "which robovast am I?" always has an
|
|
52
|
+
answer worth printing. A *revision* is not, because the only question it is asked is
|
|
53
|
+
"is this the same code as over there?" -- and a semver that stays ``2.0.0`` across every
|
|
54
|
+
edit answers that with a confident wrong yes.
|
|
55
|
+
|
|
56
|
+
``""`` therefore means "no comparison is possible here", which a caller can act on by
|
|
57
|
+
saying so. Two cases produce it and neither is an error: a client-only install (no
|
|
58
|
+
``robovast.common.execution`` to ask) and a non-git install of the full package.
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
from robovast.common.execution import \
|
|
62
|
+
code_revision # pylint: disable=import-outside-toplevel
|
|
63
|
+
return code_revision()
|
|
64
|
+
except Exception: # noqa: BLE001 - a diagnostic must not raise; "" is the honest answer
|
|
65
|
+
return ""
|