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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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