tyto.run 0.11.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.
- tyto_run-0.11.0/.gitignore +7 -0
- tyto_run-0.11.0/PKG-INFO +136 -0
- tyto_run-0.11.0/README.md +124 -0
- tyto_run-0.11.0/pyproject.toml +25 -0
- tyto_run-0.11.0/src/tyto/__init__.py +62 -0
- tyto_run-0.11.0/src/tyto/_http.py +82 -0
- tyto_run-0.11.0/src/tyto/client.py +51 -0
- tyto_run-0.11.0/src/tyto/config.py +25 -0
- tyto_run-0.11.0/src/tyto/errors.py +13 -0
- tyto_run-0.11.0/src/tyto/models.py +250 -0
- tyto_run-0.11.0/src/tyto/resources/__init__.py +0 -0
- tyto_run-0.11.0/src/tyto/resources/auth.py +26 -0
- tyto_run-0.11.0/src/tyto/resources/files.py +26 -0
- tyto_run-0.11.0/src/tyto/resources/holds.py +53 -0
- tyto_run-0.11.0/src/tyto/resources/nests.py +285 -0
- tyto_run-0.11.0/src/tyto/resources/previews.py +40 -0
- tyto_run-0.11.0/src/tyto/resources/sessions.py +112 -0
- tyto_run-0.11.0/src/tyto/resources/snapshots.py +64 -0
- tyto_run-0.11.0/src/tyto/ws.py +10 -0
tyto_run-0.11.0/PKG-INFO
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tyto.run
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: Official Python SDK for the Tyto API
|
|
5
|
+
Project-URL: Homepage, https://tyto.run
|
|
6
|
+
Author: Bonya
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: httpx>=0.27.0
|
|
10
|
+
Requires-Dist: websockets>=13.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# tyto
|
|
14
|
+
|
|
15
|
+
Python SDK for the [Tyto API](https://api.tyto.run) — manage nests, sessions, files, previews, snapshots, and keepalive holds.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install tyto
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Auth
|
|
24
|
+
|
|
25
|
+
Export your API key:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export TYTO_API_KEY=your_key_here
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or pass it directly:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from tyto import Tyto
|
|
35
|
+
tyto = Tyto(api_key="your_key_here")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import os
|
|
42
|
+
from tyto import Tyto
|
|
43
|
+
|
|
44
|
+
tyto = Tyto(api_key=os.environ["TYTO_API_KEY"])
|
|
45
|
+
|
|
46
|
+
# Who am I?
|
|
47
|
+
me = tyto.me()
|
|
48
|
+
print(me.email)
|
|
49
|
+
|
|
50
|
+
# Create a nest
|
|
51
|
+
nest = tyto.create(name="my-nest", template="ubuntu-24-dev")
|
|
52
|
+
|
|
53
|
+
# Upload / download a file or directory
|
|
54
|
+
nest.put("./hello.txt", "hello.txt")
|
|
55
|
+
nest.get("hello.txt", "./hello.downloaded.txt")
|
|
56
|
+
|
|
57
|
+
# Create a session and attach over WebSocket
|
|
58
|
+
session = nest.create_session(argv=["bash", "-lc", "echo hi"], tty=False)
|
|
59
|
+
with session.attach() as ws:
|
|
60
|
+
for message in ws:
|
|
61
|
+
print(message)
|
|
62
|
+
|
|
63
|
+
# Open raw WebSocket connections
|
|
64
|
+
with nest.exec() as ws: ...
|
|
65
|
+
with nest.console() as ws: ...
|
|
66
|
+
|
|
67
|
+
# Create a preview
|
|
68
|
+
preview = nest.create_preview(port=3000, auth="private")
|
|
69
|
+
print(preview.url)
|
|
70
|
+
|
|
71
|
+
# Snapshot, fork, restore
|
|
72
|
+
snap = nest.create_snapshot(name="v1")
|
|
73
|
+
fork = nest.fork(name="my-fork")
|
|
74
|
+
nest.delete_snapshot(snap.id)
|
|
75
|
+
# nest.restore(snap.id)
|
|
76
|
+
|
|
77
|
+
# Keepalive hold
|
|
78
|
+
nest.holds.put("ci", ttl="30m", reason="CI job")
|
|
79
|
+
nest.holds.heartbeat("ci", ttl="30m")
|
|
80
|
+
nest.holds.delete("ci")
|
|
81
|
+
|
|
82
|
+
# Lifecycle
|
|
83
|
+
nest.stop()
|
|
84
|
+
nest.start()
|
|
85
|
+
nest.wake(reason="wakeup")
|
|
86
|
+
nest.delete()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Resources
|
|
90
|
+
|
|
91
|
+
| Resource | Description |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `tyto.create()` | Create a nest |
|
|
94
|
+
| `tyto.nests` | Create / list / get nests |
|
|
95
|
+
| `tyto.previews` | Inspect / revoke previews by ID |
|
|
96
|
+
| `tyto.auth` | CLI browser auth flow |
|
|
97
|
+
| `nest.put(local, remote)` | Upload a file or directory |
|
|
98
|
+
| `nest.get(remote, local)` | Download a file or directory |
|
|
99
|
+
| `nest.create_session()` | Create a managed session |
|
|
100
|
+
| `nest.create_preview()` | Create a preview URL |
|
|
101
|
+
| `nest.create_snapshot()` | Create a snapshot |
|
|
102
|
+
| `nest.delete_snapshot()` | Delete a snapshot |
|
|
103
|
+
| `nest.fs` | Low-level file upload / download |
|
|
104
|
+
| `nest.sessions` | Managed sessions (create / list) |
|
|
105
|
+
| `nest.previews` | Previews scoped to the nest |
|
|
106
|
+
| `nest.snapshots` | Snapshots + fork/restore |
|
|
107
|
+
| `nest.holds` | Keepalive holds |
|
|
108
|
+
| `session.attach()` | WebSocket stream for a session |
|
|
109
|
+
| `nest.console()` | Interactive shell WebSocket |
|
|
110
|
+
| `nest.exec()` | Command WebSocket |
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
| Parameter | Env var | Default |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `api_key` | `TYTO_API_KEY` | — (required) |
|
|
117
|
+
| `api_url` | `TYTO_API_URL` | `https://api.tyto.run` |
|
|
118
|
+
|
|
119
|
+
## Examples
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
python examples/quickstart.py
|
|
123
|
+
python examples/files.py
|
|
124
|
+
python examples/preview.py
|
|
125
|
+
python examples/snapshot_fork.py
|
|
126
|
+
python examples/websocket_exec.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Context manager
|
|
130
|
+
|
|
131
|
+
The `Tyto` client can be used as a context manager to ensure the underlying HTTP connection pool is closed:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
with Tyto() as tyto:
|
|
135
|
+
nests = tyto.nests.list()
|
|
136
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# tyto
|
|
2
|
+
|
|
3
|
+
Python SDK for the [Tyto API](https://api.tyto.run) — manage nests, sessions, files, previews, snapshots, and keepalive holds.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install tyto
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Auth
|
|
12
|
+
|
|
13
|
+
Export your API key:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export TYTO_API_KEY=your_key_here
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or pass it directly:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from tyto import Tyto
|
|
23
|
+
tyto = Tyto(api_key="your_key_here")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import os
|
|
30
|
+
from tyto import Tyto
|
|
31
|
+
|
|
32
|
+
tyto = Tyto(api_key=os.environ["TYTO_API_KEY"])
|
|
33
|
+
|
|
34
|
+
# Who am I?
|
|
35
|
+
me = tyto.me()
|
|
36
|
+
print(me.email)
|
|
37
|
+
|
|
38
|
+
# Create a nest
|
|
39
|
+
nest = tyto.create(name="my-nest", template="ubuntu-24-dev")
|
|
40
|
+
|
|
41
|
+
# Upload / download a file or directory
|
|
42
|
+
nest.put("./hello.txt", "hello.txt")
|
|
43
|
+
nest.get("hello.txt", "./hello.downloaded.txt")
|
|
44
|
+
|
|
45
|
+
# Create a session and attach over WebSocket
|
|
46
|
+
session = nest.create_session(argv=["bash", "-lc", "echo hi"], tty=False)
|
|
47
|
+
with session.attach() as ws:
|
|
48
|
+
for message in ws:
|
|
49
|
+
print(message)
|
|
50
|
+
|
|
51
|
+
# Open raw WebSocket connections
|
|
52
|
+
with nest.exec() as ws: ...
|
|
53
|
+
with nest.console() as ws: ...
|
|
54
|
+
|
|
55
|
+
# Create a preview
|
|
56
|
+
preview = nest.create_preview(port=3000, auth="private")
|
|
57
|
+
print(preview.url)
|
|
58
|
+
|
|
59
|
+
# Snapshot, fork, restore
|
|
60
|
+
snap = nest.create_snapshot(name="v1")
|
|
61
|
+
fork = nest.fork(name="my-fork")
|
|
62
|
+
nest.delete_snapshot(snap.id)
|
|
63
|
+
# nest.restore(snap.id)
|
|
64
|
+
|
|
65
|
+
# Keepalive hold
|
|
66
|
+
nest.holds.put("ci", ttl="30m", reason="CI job")
|
|
67
|
+
nest.holds.heartbeat("ci", ttl="30m")
|
|
68
|
+
nest.holds.delete("ci")
|
|
69
|
+
|
|
70
|
+
# Lifecycle
|
|
71
|
+
nest.stop()
|
|
72
|
+
nest.start()
|
|
73
|
+
nest.wake(reason="wakeup")
|
|
74
|
+
nest.delete()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Resources
|
|
78
|
+
|
|
79
|
+
| Resource | Description |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `tyto.create()` | Create a nest |
|
|
82
|
+
| `tyto.nests` | Create / list / get nests |
|
|
83
|
+
| `tyto.previews` | Inspect / revoke previews by ID |
|
|
84
|
+
| `tyto.auth` | CLI browser auth flow |
|
|
85
|
+
| `nest.put(local, remote)` | Upload a file or directory |
|
|
86
|
+
| `nest.get(remote, local)` | Download a file or directory |
|
|
87
|
+
| `nest.create_session()` | Create a managed session |
|
|
88
|
+
| `nest.create_preview()` | Create a preview URL |
|
|
89
|
+
| `nest.create_snapshot()` | Create a snapshot |
|
|
90
|
+
| `nest.delete_snapshot()` | Delete a snapshot |
|
|
91
|
+
| `nest.fs` | Low-level file upload / download |
|
|
92
|
+
| `nest.sessions` | Managed sessions (create / list) |
|
|
93
|
+
| `nest.previews` | Previews scoped to the nest |
|
|
94
|
+
| `nest.snapshots` | Snapshots + fork/restore |
|
|
95
|
+
| `nest.holds` | Keepalive holds |
|
|
96
|
+
| `session.attach()` | WebSocket stream for a session |
|
|
97
|
+
| `nest.console()` | Interactive shell WebSocket |
|
|
98
|
+
| `nest.exec()` | Command WebSocket |
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
| Parameter | Env var | Default |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `api_key` | `TYTO_API_KEY` | — (required) |
|
|
105
|
+
| `api_url` | `TYTO_API_URL` | `https://api.tyto.run` |
|
|
106
|
+
|
|
107
|
+
## Examples
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python examples/quickstart.py
|
|
111
|
+
python examples/files.py
|
|
112
|
+
python examples/preview.py
|
|
113
|
+
python examples/snapshot_fork.py
|
|
114
|
+
python examples/websocket_exec.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Context manager
|
|
118
|
+
|
|
119
|
+
The `Tyto` client can be used as a context manager to ensure the underlying HTTP connection pool is closed:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
with Tyto() as tyto:
|
|
123
|
+
nests = tyto.nests.list()
|
|
124
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tyto.run"
|
|
7
|
+
version = "0.11.0"
|
|
8
|
+
description = "Official Python SDK for the Tyto API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Bonya" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"httpx>=0.27.0",
|
|
15
|
+
"websockets>=13.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://tyto.run"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/tyto"]
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.sdist]
|
|
25
|
+
include = ["src/tyto", "README.md", "pyproject.toml"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from .client import Tyto
|
|
2
|
+
from .config import TytoConfig
|
|
3
|
+
from .errors import TytoAPIError, TytoError
|
|
4
|
+
from .models import (
|
|
5
|
+
AuthPollResponse,
|
|
6
|
+
AuthStartResponse,
|
|
7
|
+
DeleteSnapshotResponse,
|
|
8
|
+
ForkResponse,
|
|
9
|
+
ForkStorage,
|
|
10
|
+
KeepaliveHoldData,
|
|
11
|
+
NestData,
|
|
12
|
+
NestLifecycle,
|
|
13
|
+
PreviewData,
|
|
14
|
+
ReadFileResult,
|
|
15
|
+
RestoreResponse,
|
|
16
|
+
SessionData,
|
|
17
|
+
SnapshotData,
|
|
18
|
+
SnapshotList,
|
|
19
|
+
User,
|
|
20
|
+
WakeResponse,
|
|
21
|
+
)
|
|
22
|
+
from .resources.auth import AuthResource
|
|
23
|
+
from .resources.files import FileSystem
|
|
24
|
+
from .resources.holds import HoldsResource
|
|
25
|
+
from .resources.nests import Nest, NestsResource
|
|
26
|
+
from .resources.previews import PreviewsResource, TopLevelPreviewsResource
|
|
27
|
+
from .resources.sessions import Session, SessionsResource
|
|
28
|
+
from .resources.snapshots import SnapshotsResource, TopLevelSnapshotsResource
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Tyto",
|
|
32
|
+
"TytoConfig",
|
|
33
|
+
"TytoError",
|
|
34
|
+
"TytoAPIError",
|
|
35
|
+
"User",
|
|
36
|
+
"AuthStartResponse",
|
|
37
|
+
"AuthPollResponse",
|
|
38
|
+
"NestData",
|
|
39
|
+
"NestLifecycle",
|
|
40
|
+
"WakeResponse",
|
|
41
|
+
"SessionData",
|
|
42
|
+
"PreviewData",
|
|
43
|
+
"SnapshotData",
|
|
44
|
+
"SnapshotList",
|
|
45
|
+
"RestoreResponse",
|
|
46
|
+
"ForkResponse",
|
|
47
|
+
"ForkStorage",
|
|
48
|
+
"DeleteSnapshotResponse",
|
|
49
|
+
"KeepaliveHoldData",
|
|
50
|
+
"ReadFileResult",
|
|
51
|
+
"Nest",
|
|
52
|
+
"NestsResource",
|
|
53
|
+
"Session",
|
|
54
|
+
"SessionsResource",
|
|
55
|
+
"FileSystem",
|
|
56
|
+
"PreviewsResource",
|
|
57
|
+
"TopLevelPreviewsResource",
|
|
58
|
+
"SnapshotsResource",
|
|
59
|
+
"TopLevelSnapshotsResource",
|
|
60
|
+
"HoldsResource",
|
|
61
|
+
"AuthResource",
|
|
62
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .config import TytoConfig
|
|
8
|
+
from .errors import TytoAPIError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HttpClient:
|
|
12
|
+
def __init__(self, config: TytoConfig) -> None:
|
|
13
|
+
self._config = config
|
|
14
|
+
self._client = httpx.Client(
|
|
15
|
+
base_url=config.api_url,
|
|
16
|
+
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
17
|
+
timeout=30.0,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def _raise_for_status(self, response: httpx.Response) -> None:
|
|
21
|
+
if response.is_success:
|
|
22
|
+
return
|
|
23
|
+
try:
|
|
24
|
+
body = response.json()
|
|
25
|
+
code = body.get("error")
|
|
26
|
+
message = body.get("message") or f"HTTP {response.status_code}"
|
|
27
|
+
except Exception:
|
|
28
|
+
code = None
|
|
29
|
+
message = f"HTTP {response.status_code}"
|
|
30
|
+
raise TytoAPIError(response.status_code, code, message)
|
|
31
|
+
|
|
32
|
+
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
33
|
+
resp = self._client.get(path, params={k: v for k, v in (params or {}).items() if v is not None})
|
|
34
|
+
self._raise_for_status(resp)
|
|
35
|
+
if resp.status_code == 204:
|
|
36
|
+
return None
|
|
37
|
+
return resp.json()
|
|
38
|
+
|
|
39
|
+
def post(self, path: str, json: Any = None, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
40
|
+
resp = self._client.post(path, json=json, params={k: v for k, v in (params or {}).items() if v is not None})
|
|
41
|
+
self._raise_for_status(resp)
|
|
42
|
+
if resp.status_code == 204:
|
|
43
|
+
return None
|
|
44
|
+
return resp.json()
|
|
45
|
+
|
|
46
|
+
def put(self, path: str, json: Any = None) -> Any:
|
|
47
|
+
resp = self._client.put(path, json=json)
|
|
48
|
+
self._raise_for_status(resp)
|
|
49
|
+
if resp.status_code == 204:
|
|
50
|
+
return None
|
|
51
|
+
return resp.json()
|
|
52
|
+
|
|
53
|
+
def delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
54
|
+
resp = self._client.delete(path, params={k: v for k, v in (params or {}).items() if v is not None})
|
|
55
|
+
self._raise_for_status(resp)
|
|
56
|
+
if resp.status_code == 204:
|
|
57
|
+
return None
|
|
58
|
+
return resp.json()
|
|
59
|
+
|
|
60
|
+
def put_binary(self, path: str, data: bytes, content_type: str, params: Dict[str, Any]) -> None:
|
|
61
|
+
resp = self._client.put(
|
|
62
|
+
path,
|
|
63
|
+
content=data,
|
|
64
|
+
headers={"Content-Type": content_type},
|
|
65
|
+
params={k: v for k, v in params.items() if v is not None},
|
|
66
|
+
)
|
|
67
|
+
self._raise_for_status(resp)
|
|
68
|
+
|
|
69
|
+
def get_binary(self, path: str, params: Optional[Dict[str, Any]] = None) -> tuple[bytes, str]:
|
|
70
|
+
resp = self._client.get(path, params={k: v for k, v in (params or {}).items() if v is not None})
|
|
71
|
+
self._raise_for_status(resp)
|
|
72
|
+
kind = resp.headers.get("X-Tyto-FS-Kind", "file")
|
|
73
|
+
return resp.content, kind
|
|
74
|
+
|
|
75
|
+
def close(self) -> None:
|
|
76
|
+
self._client.close()
|
|
77
|
+
|
|
78
|
+
def __enter__(self) -> "HttpClient":
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def __exit__(self, *args: Any) -> None:
|
|
82
|
+
self.close()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from ._http import HttpClient
|
|
6
|
+
from .config import TytoConfig, resolve_config
|
|
7
|
+
from .models import User
|
|
8
|
+
from .resources.auth import AuthResource
|
|
9
|
+
from .resources.nests import Nest, NestsResource
|
|
10
|
+
from .resources.previews import TopLevelPreviewsResource
|
|
11
|
+
from .resources.snapshots import TopLevelSnapshotsResource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Tyto:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
api_key: Optional[str] = None,
|
|
18
|
+
api_url: Optional[str] = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
self._config = resolve_config(api_key=api_key, api_url=api_url)
|
|
21
|
+
self._http = HttpClient(self._config)
|
|
22
|
+
self.auth = AuthResource(self._http)
|
|
23
|
+
self.nests = NestsResource(self._http, self._config)
|
|
24
|
+
self.previews = TopLevelPreviewsResource(self._http)
|
|
25
|
+
self.snapshots = TopLevelSnapshotsResource(self._http)
|
|
26
|
+
|
|
27
|
+
def create(
|
|
28
|
+
self,
|
|
29
|
+
name: str,
|
|
30
|
+
template: str = "ubuntu-24-dev",
|
|
31
|
+
repo_url: Optional[str] = None,
|
|
32
|
+
) -> Nest:
|
|
33
|
+
return self.nests.create(name=name, template=template, repo_url=repo_url)
|
|
34
|
+
|
|
35
|
+
def health(self) -> dict:
|
|
36
|
+
return self._http.get("/healthz")
|
|
37
|
+
|
|
38
|
+
def ready(self) -> dict:
|
|
39
|
+
return self._http.get("/readyz")
|
|
40
|
+
|
|
41
|
+
def me(self) -> User:
|
|
42
|
+
return User.from_dict(self._http.get("/me"))
|
|
43
|
+
|
|
44
|
+
def close(self) -> None:
|
|
45
|
+
self._http.close()
|
|
46
|
+
|
|
47
|
+
def __enter__(self) -> "Tyto":
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def __exit__(self, *args) -> None:
|
|
51
|
+
self.close()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .errors import TytoError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TytoConfig:
|
|
11
|
+
api_key: str = ""
|
|
12
|
+
api_url: str = "https://api.tyto.run"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def resolve_config(
|
|
16
|
+
api_key: str | None = None,
|
|
17
|
+
api_url: str | None = None,
|
|
18
|
+
) -> TytoConfig:
|
|
19
|
+
key = api_key or os.environ.get("TYTO_API_KEY", "")
|
|
20
|
+
if not key:
|
|
21
|
+
raise TytoError(
|
|
22
|
+
"api_key is required. Pass it as an argument or set the TYTO_API_KEY environment variable."
|
|
23
|
+
)
|
|
24
|
+
url = (api_url or os.environ.get("TYTO_API_URL", "https://api.tyto.run")).rstrip("/")
|
|
25
|
+
return TytoConfig(api_key=key, api_url=url)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class TytoError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TytoAPIError(TytoError):
|
|
6
|
+
def __init__(self, status: int, code: str | None, message: str) -> None:
|
|
7
|
+
super().__init__(message)
|
|
8
|
+
self.status = status
|
|
9
|
+
self.code = code
|
|
10
|
+
self.message = message
|
|
11
|
+
|
|
12
|
+
def __repr__(self) -> str:
|
|
13
|
+
return f"TytoAPIError(status={self.status}, code={self.code!r}, message={self.message!r})"
|