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.
Files changed (30) hide show
  1. robovast_client-2.1.0/PKG-INFO +97 -0
  2. robovast_client-2.1.0/README.md +80 -0
  3. robovast_client-2.1.0/pyproject.toml +51 -0
  4. robovast_client-2.1.0/robovast/client/__init__.py +17 -0
  5. robovast_client-2.1.0/robovast/client/app_version.py +65 -0
  6. robovast_client-2.1.0/robovast/client/campaign_cli.py +804 -0
  7. robovast_client-2.1.0/robovast/client/cli.py +1256 -0
  8. robovast_client-2.1.0/robovast/client/cluster_cli.py +94 -0
  9. robovast_client-2.1.0/robovast/client/container_cli.py +110 -0
  10. robovast_client-2.1.0/robovast/client/doctor.py +792 -0
  11. robovast_client-2.1.0/robovast/client/errors.py +38 -0
  12. robovast_client-2.1.0/robovast/client/file_address.py +166 -0
  13. robovast_client-2.1.0/robovast/client/lazy_group.py +65 -0
  14. robovast_client-2.1.0/robovast/client/logging_config.py +158 -0
  15. robovast_client-2.1.0/robovast/client/login.py +261 -0
  16. robovast_client-2.1.0/robovast/client/progress.py +175 -0
  17. robovast_client-2.1.0/robovast/client/safe_path.py +107 -0
  18. robovast_client-2.1.0/robovast/client/scene_markers.py +116 -0
  19. robovast_client-2.1.0/robovast/client/service_cli.py +393 -0
  20. robovast_client-2.1.0/robovast/client/service_target.py +148 -0
  21. robovast_client-2.1.0/robovast/client/status.py +764 -0
  22. robovast_client-2.1.0/robovast/client/tail.py +54 -0
  23. robovast_client-2.1.0/robovast/client/workspaces.py +75 -0
  24. robovast_client-2.1.0/robovast/execution/campaign_wait.py +144 -0
  25. robovast_client-2.1.0/robovast/execution/image_build_wait.py +129 -0
  26. robovast_client-2.1.0/robovast/execution/poll_health.py +116 -0
  27. robovast_client-2.1.0/robovast/service/auth.py +271 -0
  28. robovast_client-2.1.0/robovast/service/http_client.py +716 -0
  29. robovast_client-2.1.0/robovast/service/interface.py +3557 -0
  30. 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 ""