runta-sdk 0.0.4__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.
- runta_sdk-0.0.4/PKG-INFO +136 -0
- runta_sdk-0.0.4/README.md +123 -0
- runta_sdk-0.0.4/pyproject.toml +31 -0
- runta_sdk-0.0.4/setup.cfg +4 -0
- runta_sdk-0.0.4/src/asyncrunta/__init__.py +45 -0
- runta_sdk-0.0.4/src/asyncrunta/_config.py +69 -0
- runta_sdk-0.0.4/src/asyncrunta/_http.py +345 -0
- runta_sdk-0.0.4/src/asyncrunta/client.py +524 -0
- runta_sdk-0.0.4/src/asyncrunta/errors.py +45 -0
- runta_sdk-0.0.4/src/asyncrunta/models.py +271 -0
- runta_sdk-0.0.4/src/asyncrunta/py.typed +1 -0
- runta_sdk-0.0.4/src/runta/__init__.py +49 -0
- runta_sdk-0.0.4/src/runta/_http.py +345 -0
- runta_sdk-0.0.4/src/runta/client.py +541 -0
- runta_sdk-0.0.4/src/runta/py.typed +1 -0
- runta_sdk-0.0.4/src/runta_sdk.egg-info/PKG-INFO +136 -0
- runta_sdk-0.0.4/src/runta_sdk.egg-info/SOURCES.txt +23 -0
- runta_sdk-0.0.4/src/runta_sdk.egg-info/dependency_links.txt +1 -0
- runta_sdk-0.0.4/src/runta_sdk.egg-info/requires.txt +5 -0
- runta_sdk-0.0.4/src/runta_sdk.egg-info/top_level.txt +2 -0
- runta_sdk-0.0.4/tests/test_config_and_helpers.py +141 -0
- runta_sdk-0.0.4/tests/test_harbor_integration.py +156 -0
- runta_sdk-0.0.4/tests/test_live.py +702 -0
- runta_sdk-0.0.4/tests/test_openapi_contract.py +233 -0
- runta_sdk-0.0.4/tests/test_rest_deployment.py +283 -0
runta_sdk-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runta-sdk
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Python SDK for Runta runtime management
|
|
5
|
+
Author: Runta
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# Runta Python SDK
|
|
15
|
+
|
|
16
|
+
This package provides matching async and sync SDKs. Both call the public
|
|
17
|
+
`runta-api` HTTP/JSON service; they do not speak control-plane gRPC directly.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install runta-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For user-facing installation, configuration, and workflow examples, see
|
|
26
|
+
[`doc.md`](doc.md).
|
|
27
|
+
|
|
28
|
+
For a full API reference with every public method, parameter, return value, and
|
|
29
|
+
model field, see [`manual.md`](manual.md).
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
|
|
34
|
+
from runta import AsyncRunta
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
async with AsyncRunta(endpoint="http://127.0.0.1:8080") as runta:
|
|
39
|
+
runtime = await runta.runtimes.create("example")
|
|
40
|
+
result = await runtime.exec("echo hello")
|
|
41
|
+
print(result.stdout_text)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
asyncio.run(main())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from runta import Runta
|
|
49
|
+
|
|
50
|
+
runta = Runta(endpoint="http://127.0.0.1:8080")
|
|
51
|
+
runtime = runta.runtimes.create("example")
|
|
52
|
+
result = runtime.exec("echo hello")
|
|
53
|
+
print(result.stdout_text)
|
|
54
|
+
runta.close()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Authentication uses `Runta(token="rt_...")` or `RUNTA_TOKEN`. The endpoint uses
|
|
58
|
+
`Runta(endpoint="...")`, `RUNTA_ENDPOINT`, or `endpoint` from the optional
|
|
59
|
+
Runta config file.
|
|
60
|
+
|
|
61
|
+
## Public REST endpoint lifecycle test
|
|
62
|
+
|
|
63
|
+
You can test the SDK against the hosted REST API by pointing the client at
|
|
64
|
+
`https://rest.runta.me` and providing a Runta token:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
export RUNTA_TOKEN="rt_..."
|
|
68
|
+
uv run python scripts/runta-lifecycle.py
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The lifecycle script creates a runtime, starts it, runs a command, writes and
|
|
72
|
+
reads a remote file, uploads a local file, downloads it back, pauses/resumes the
|
|
73
|
+
runtime, then stops and deletes it. To pass credentials explicitly:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv run python scripts/runta-lifecycle.py \
|
|
77
|
+
--endpoint https://rest.runta.me \
|
|
78
|
+
--token "$RUNTA_TOKEN"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `--keep` to leave the runtime behind for inspection.
|
|
82
|
+
|
|
83
|
+
The SDK is aligned with the current `crates/runta-api` REST routes. The OpenAPI
|
|
84
|
+
contract exposes single-file raw byte routes at
|
|
85
|
+
`/v1/runtimes/{runtime_id}/files` for `runtime.files.read`,
|
|
86
|
+
`runtime.files.write`, `runtime.files.upload`, and `runtime.files.download`.
|
|
87
|
+
SDK `sessions` are SDK-owned handles because persistent server-side session
|
|
88
|
+
routes are not exposed.
|
|
89
|
+
|
|
90
|
+
## REST contract
|
|
91
|
+
|
|
92
|
+
`contracts/runta-api.openapi.yaml` is copied from
|
|
93
|
+
`runta/crates/runta-api/openapi.yaml` and is used by the SDK contract tests.
|
|
94
|
+
CI refreshes this file from the latest successful `runta` OpenAPI contract
|
|
95
|
+
artifact before running tests. If the artifact is unavailable, it falls back to
|
|
96
|
+
the `dev` branch raw YAML and then to the checked-in contract.
|
|
97
|
+
|
|
98
|
+
To refresh it locally after changing `runta-api`:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cd ~/runta
|
|
102
|
+
cargo make openapi
|
|
103
|
+
cp crates/runta-api/openapi.yaml ~/RuntaPythonSDK/contracts/runta-api.openapi.yaml
|
|
104
|
+
cd ~/RuntaPythonSDK
|
|
105
|
+
uv run pytest tests/test_openapi_contract.py
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
To fetch the latest published contract directly:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
scripts/fetch-runta-openapi.py
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Live tests
|
|
115
|
+
|
|
116
|
+
The live test module runs mocked SDK workflow tests by default:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python -m unittest tests/test_live.py -v
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Pass `--live` to run the real runtime tests. They create real runtimes and use
|
|
123
|
+
external egress, so run them only with `scripts/dev-api.sh` already running:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run pytest --live
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Local pytest loads `.env` automatically. The default local file uses:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
RUNTA_ENDPOINT=http://127.0.0.1:8080
|
|
133
|
+
RUNTA_TOKEN_FILE=../runta/.dev/cli_token_dev-a
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Copy or edit `.env.example` if your checkout paths differ.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Runta Python SDK
|
|
2
|
+
|
|
3
|
+
This package provides matching async and sync SDKs. Both call the public
|
|
4
|
+
`runta-api` HTTP/JSON service; they do not speak control-plane gRPC directly.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install runta-sdk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
For user-facing installation, configuration, and workflow examples, see
|
|
13
|
+
[`doc.md`](doc.md).
|
|
14
|
+
|
|
15
|
+
For a full API reference with every public method, parameter, return value, and
|
|
16
|
+
model field, see [`manual.md`](manual.md).
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
from runta import AsyncRunta
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
async with AsyncRunta(endpoint="http://127.0.0.1:8080") as runta:
|
|
26
|
+
runtime = await runta.runtimes.create("example")
|
|
27
|
+
result = await runtime.exec("echo hello")
|
|
28
|
+
print(result.stdout_text)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from runta import Runta
|
|
36
|
+
|
|
37
|
+
runta = Runta(endpoint="http://127.0.0.1:8080")
|
|
38
|
+
runtime = runta.runtimes.create("example")
|
|
39
|
+
result = runtime.exec("echo hello")
|
|
40
|
+
print(result.stdout_text)
|
|
41
|
+
runta.close()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Authentication uses `Runta(token="rt_...")` or `RUNTA_TOKEN`. The endpoint uses
|
|
45
|
+
`Runta(endpoint="...")`, `RUNTA_ENDPOINT`, or `endpoint` from the optional
|
|
46
|
+
Runta config file.
|
|
47
|
+
|
|
48
|
+
## Public REST endpoint lifecycle test
|
|
49
|
+
|
|
50
|
+
You can test the SDK against the hosted REST API by pointing the client at
|
|
51
|
+
`https://rest.runta.me` and providing a Runta token:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
export RUNTA_TOKEN="rt_..."
|
|
55
|
+
uv run python scripts/runta-lifecycle.py
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The lifecycle script creates a runtime, starts it, runs a command, writes and
|
|
59
|
+
reads a remote file, uploads a local file, downloads it back, pauses/resumes the
|
|
60
|
+
runtime, then stops and deletes it. To pass credentials explicitly:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv run python scripts/runta-lifecycle.py \
|
|
64
|
+
--endpoint https://rest.runta.me \
|
|
65
|
+
--token "$RUNTA_TOKEN"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use `--keep` to leave the runtime behind for inspection.
|
|
69
|
+
|
|
70
|
+
The SDK is aligned with the current `crates/runta-api` REST routes. The OpenAPI
|
|
71
|
+
contract exposes single-file raw byte routes at
|
|
72
|
+
`/v1/runtimes/{runtime_id}/files` for `runtime.files.read`,
|
|
73
|
+
`runtime.files.write`, `runtime.files.upload`, and `runtime.files.download`.
|
|
74
|
+
SDK `sessions` are SDK-owned handles because persistent server-side session
|
|
75
|
+
routes are not exposed.
|
|
76
|
+
|
|
77
|
+
## REST contract
|
|
78
|
+
|
|
79
|
+
`contracts/runta-api.openapi.yaml` is copied from
|
|
80
|
+
`runta/crates/runta-api/openapi.yaml` and is used by the SDK contract tests.
|
|
81
|
+
CI refreshes this file from the latest successful `runta` OpenAPI contract
|
|
82
|
+
artifact before running tests. If the artifact is unavailable, it falls back to
|
|
83
|
+
the `dev` branch raw YAML and then to the checked-in contract.
|
|
84
|
+
|
|
85
|
+
To refresh it locally after changing `runta-api`:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd ~/runta
|
|
89
|
+
cargo make openapi
|
|
90
|
+
cp crates/runta-api/openapi.yaml ~/RuntaPythonSDK/contracts/runta-api.openapi.yaml
|
|
91
|
+
cd ~/RuntaPythonSDK
|
|
92
|
+
uv run pytest tests/test_openapi_contract.py
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
To fetch the latest published contract directly:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
scripts/fetch-runta-openapi.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Live tests
|
|
102
|
+
|
|
103
|
+
The live test module runs mocked SDK workflow tests by default:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python -m unittest tests/test_live.py -v
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Pass `--live` to run the real runtime tests. They create real runtimes and use
|
|
110
|
+
external egress, so run them only with `scripts/dev-api.sh` already running:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv run pytest --live
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Local pytest loads `.env` automatically. The default local file uses:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
RUNTA_ENDPOINT=http://127.0.0.1:8080
|
|
120
|
+
RUNTA_TOKEN_FILE=../runta/.dev/cli_token_dev-a
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Copy or edit `.env.example` if your checkout paths differ.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runta-sdk"
|
|
7
|
+
version = "0.0.4"
|
|
8
|
+
description = "Python SDK for Runta runtime management"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Runta" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"httpx>=0.27",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0",
|
|
20
|
+
"pytest-asyncio>=0.23",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
asyncrunta = ["py.typed"]
|
|
28
|
+
runta = ["py.typed"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Async Runta Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .client import AsyncRunta, Runtime, Session
|
|
4
|
+
from .errors import ApiError, CommandError, ConfigError, RuntaError
|
|
5
|
+
from .models import (
|
|
6
|
+
CommandResult,
|
|
7
|
+
CpuArch,
|
|
8
|
+
EgressAuditEvent,
|
|
9
|
+
EgressPolicy,
|
|
10
|
+
Injection,
|
|
11
|
+
IngressSpec,
|
|
12
|
+
RuntimeInfo,
|
|
13
|
+
RuntimeStatus,
|
|
14
|
+
SecretInfo,
|
|
15
|
+
SecretInjectionRuleInfo,
|
|
16
|
+
SecretRuleInfo,
|
|
17
|
+
SnapshotInfo,
|
|
18
|
+
SnapshotState,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
Runta = AsyncRunta
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ApiError",
|
|
25
|
+
"AsyncRunta",
|
|
26
|
+
"CommandError",
|
|
27
|
+
"CommandResult",
|
|
28
|
+
"ConfigError",
|
|
29
|
+
"CpuArch",
|
|
30
|
+
"EgressAuditEvent",
|
|
31
|
+
"EgressPolicy",
|
|
32
|
+
"Injection",
|
|
33
|
+
"IngressSpec",
|
|
34
|
+
"Runta",
|
|
35
|
+
"RuntaError",
|
|
36
|
+
"Runtime",
|
|
37
|
+
"RuntimeInfo",
|
|
38
|
+
"RuntimeStatus",
|
|
39
|
+
"SecretInfo",
|
|
40
|
+
"SecretInjectionRuleInfo",
|
|
41
|
+
"SecretRuleInfo",
|
|
42
|
+
"Session",
|
|
43
|
+
"SnapshotInfo",
|
|
44
|
+
"SnapshotState",
|
|
45
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Configuration resolution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import tomllib
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .errors import ConfigError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def resolve_config(
|
|
13
|
+
*,
|
|
14
|
+
endpoint: str | None,
|
|
15
|
+
token: str | None,
|
|
16
|
+
config_path: str | Path | None,
|
|
17
|
+
) -> tuple[str, str]:
|
|
18
|
+
file_config = _read_config(config_path)
|
|
19
|
+
resolved_endpoint = _clean(endpoint) or _clean(os.getenv("RUNTA_ENDPOINT")) or file_config[0]
|
|
20
|
+
resolved_token = (
|
|
21
|
+
_clean(token)
|
|
22
|
+
or _clean(os.getenv("RUNTA_TOKEN"))
|
|
23
|
+
or _read_token_file(os.getenv("RUNTA_TOKEN_FILE"))
|
|
24
|
+
or file_config[1]
|
|
25
|
+
)
|
|
26
|
+
if not resolved_endpoint:
|
|
27
|
+
raise ConfigError(
|
|
28
|
+
"missing endpoint: pass endpoint=..., set RUNTA_ENDPOINT, or set endpoint in config"
|
|
29
|
+
)
|
|
30
|
+
if not resolved_token:
|
|
31
|
+
raise ConfigError("missing token: pass token=... or set RUNTA_TOKEN")
|
|
32
|
+
return resolved_endpoint, resolved_token
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _read_config(config_path: str | Path | None) -> tuple[str | None, str | None]:
|
|
36
|
+
path = (
|
|
37
|
+
Path(config_path).expanduser()
|
|
38
|
+
if config_path is not None
|
|
39
|
+
else Path(os.getenv("RUNTA_CONFIG", "~/.config/runta/config.toml")).expanduser()
|
|
40
|
+
)
|
|
41
|
+
if not path.exists():
|
|
42
|
+
return None, None
|
|
43
|
+
try:
|
|
44
|
+
data = tomllib.loads(path.read_text())
|
|
45
|
+
except OSError as exc:
|
|
46
|
+
raise ConfigError(f"failed to read config file {path}: {exc}") from exc
|
|
47
|
+
except tomllib.TOMLDecodeError as exc:
|
|
48
|
+
raise ConfigError(f"failed to parse config file {path}: {exc}") from exc
|
|
49
|
+
return _clean(data.get("endpoint")), _clean(data.get("token"))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _read_token_file(token_file: str | None) -> str | None:
|
|
53
|
+
token_file = _clean(token_file)
|
|
54
|
+
if token_file is None:
|
|
55
|
+
return None
|
|
56
|
+
path = Path(token_file).expanduser()
|
|
57
|
+
if not path.exists():
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
return _clean(path.read_text())
|
|
61
|
+
except OSError as exc:
|
|
62
|
+
raise ConfigError(f"failed to read token file {path}: {exc}") from exc
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _clean(value: object) -> str | None:
|
|
66
|
+
if not isinstance(value, str):
|
|
67
|
+
return None
|
|
68
|
+
value = value.strip()
|
|
69
|
+
return value or None
|