boxd 0.1.0.dev2__py3-none-any.whl

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.
boxd/types.py ADDED
@@ -0,0 +1,142 @@
1
+ """Data types for the boxd SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+
8
+ @dataclass
9
+ class BoxConfig:
10
+ vcpu: int = 0
11
+ memory: str = ""
12
+ disk: str = ""
13
+ env: dict[str, str] | None = None
14
+ cmd: list[str] | None = None
15
+ restart_policy: str = "always"
16
+ lifecycle: LifecycleConfig | None = None
17
+ network: NetworkConfig | None = None
18
+ volumes: list[VolumeMount] | None = None
19
+
20
+
21
+ @dataclass
22
+ class LifecycleConfig:
23
+ auto_suspend_timeout: int = 0
24
+ auto_destroy_timeout: int = 0
25
+
26
+
27
+ @dataclass
28
+ class NetworkConfig:
29
+ ssh: bool = True
30
+ proxies: list[ProxyEntry] | None = None
31
+
32
+
33
+ @dataclass
34
+ class ProxyEntry:
35
+ name: str
36
+ port: int = 0
37
+
38
+
39
+ @dataclass
40
+ class VolumeMount:
41
+ disk_id: str
42
+ mount_path: str
43
+ read_only: bool = False
44
+
45
+
46
+ @dataclass
47
+ class Proxy:
48
+ name: str
49
+ vm_name: str
50
+ domain: str
51
+ port: int
52
+ is_default: bool
53
+
54
+
55
+ @dataclass
56
+ class Template:
57
+ id: str
58
+ name: str
59
+ image: str
60
+ status: str
61
+ vcpu: int
62
+ memory_bytes: int
63
+
64
+
65
+ @dataclass
66
+ class Disk:
67
+ id: str
68
+ name: str
69
+ size_bytes: int
70
+ status: str
71
+ attachments: list[DiskAttachment] = field(default_factory=list)
72
+
73
+
74
+ @dataclass
75
+ class DiskAttachment:
76
+ vm_id: str
77
+ vm_name: str
78
+ mount_path: str
79
+ mount_mode: str
80
+
81
+
82
+ @dataclass
83
+ class SuspendResult:
84
+ suspend_us: int
85
+
86
+
87
+ @dataclass
88
+ class ResumeResult:
89
+ resume_us: int
90
+
91
+
92
+ @dataclass
93
+ class PtyInfo:
94
+ rows: int = 24
95
+ cols: int = 80
96
+
97
+
98
+ @dataclass
99
+ class WhoamiResult:
100
+ user_id: str
101
+ fingerprints: list[str]
102
+ default_network_id: str
103
+
104
+
105
+ @dataclass
106
+ class ConfigResult:
107
+ default_image: str
108
+ zone: str
109
+
110
+
111
+ @dataclass
112
+ class Domain:
113
+ """An external domain bound to a VM."""
114
+
115
+ domain: str
116
+ vm_id: str
117
+
118
+
119
+ @dataclass
120
+ class Network:
121
+ """A user network."""
122
+
123
+ id: str
124
+ subnet: str
125
+ status: str
126
+
127
+
128
+ @dataclass
129
+ class Token:
130
+ """A freshly-issued JWT. The raw token is only available at creation time."""
131
+
132
+ token: str
133
+ expires_at: int
134
+
135
+
136
+ @dataclass
137
+ class TokenInfo:
138
+ """A previously-issued token (from list)."""
139
+
140
+ jti: str
141
+ created_at: int
142
+ expires_at: int
@@ -0,0 +1,329 @@
1
+ Metadata-Version: 2.4
2
+ Name: boxd
3
+ Version: 0.1.0.dev2
4
+ Summary: Python SDK for the boxd cloud VM platform
5
+ Author: Azin
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://boxd.sh
8
+ Project-URL: Repository, https://github.com/azin-tech/boxd
9
+ Project-URL: Issues, https://github.com/azin-tech/boxd/issues
10
+ Keywords: boxd,vm,microvm,sandbox,compute,grpc,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: System :: Distributed Computing
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: grpcio>=1.60
24
+ Requires-Dist: protobuf>=4.25
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: dev
27
+ Requires-Dist: grpcio-tools>=1.60; extra == "dev"
28
+ Requires-Dist: pytest>=8; extra == "dev"
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
30
+ Requires-Dist: build>=1.0; extra == "dev"
31
+ Requires-Dist: twine>=4.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # boxd Python SDK
35
+
36
+ Python SDK for the [boxd](https://boxd.sh) cloud VM platform. Sync-first API with full async support.
37
+
38
+ Requires Python 3.10+.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install boxd
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```python
49
+ from boxd import Compute
50
+
51
+ with Compute(api_key="bxk_...") as c:
52
+ box = c.box.create(name="my-vm")
53
+ result = box.exec("echo", "hello")
54
+ print(result.stdout)
55
+ box.destroy()
56
+ ```
57
+
58
+ ## Authentication
59
+
60
+ ```python
61
+ Compute(api_key="bxk_...") # API key (recommended)
62
+ Compute(token="eyJ...") # direct JWT
63
+ Compute() # reads BOXD_API_KEY or BOXD_TOKEN
64
+ ```
65
+
66
+ ## Configuration
67
+
68
+ The SDK reads its endpoint configuration from constructor arguments or env vars:
69
+
70
+ ```python
71
+ Compute(
72
+ api_key="bxk_...",
73
+ api_url="http://boxd.sh:9443", # default
74
+ exchange_url="https://boxd.sh/api/v1/auth/token", # default
75
+ )
76
+ ```
77
+
78
+ Equivalent env vars: `BOXD_API_KEY`, `BOXD_TOKEN`, `BOXD_API_URL`, `BOXD_EXCHANGE_URL`.
79
+
80
+ `api_url` accepts an optional URL scheme that controls TLS:
81
+
82
+ | `api_url` value | Transport |
83
+ |---|---|
84
+ | `http://host:port` | plaintext (scheme stripped before connecting) |
85
+ | `https://host:port` | TLS (scheme stripped before connecting) |
86
+ | bare `host:port` | TLS, except `localhost` / `127.*` which stay plaintext |
87
+
88
+ The default `http://boxd.sh:9443` matches production. Self-hosted clusters can pass `api_url="http://my-cluster:9443"` to opt into plaintext.
89
+
90
+ ## VM Lifecycle
91
+
92
+ ```python
93
+ box = c.box.create(name="my-vm")
94
+ boxes = c.box.list()
95
+ found = c.box.get("my-vm") # by name or id
96
+ forked = c.box.fork("my-vm", name="f1")
97
+
98
+ box.start()
99
+ box.stop()
100
+ box.reboot()
101
+ box.destroy()
102
+ s = box.suspend() # SuspendResult
103
+ r = box.resume() # ResumeResult
104
+ ```
105
+
106
+ `Box` exposes the server-returned fields: `id`, `name`, `image`, `public_ip`, `status`, `url`, `boot_time_ms`. Forked VMs additionally carry `forked_from`. VMs returned by `c.box.get(...)` also expose `restart_policy`, `disk_bytes`, and `auto_suspend_timeout_secs`.
107
+
108
+ ## Exec
109
+
110
+ ```python
111
+ # Simple — collect all output
112
+ r = box.exec("python", "script.py")
113
+ r.stdout # str
114
+ r.stderr # str
115
+ r.exit_code # int
116
+ r.success # bool
117
+
118
+ # With env vars and timeout
119
+ box.exec("sh", "-c", "echo $FOO", env={"FOO": "bar"}, timeout=30)
120
+
121
+ # Streaming
122
+ proc = box.exec("tail", "-f", "/var/log/syslog", stream=True)
123
+ for chunk in proc.iter_stdout():
124
+ print(chunk.decode(), end="")
125
+ exit_code = proc.wait()
126
+
127
+ # Interactive (PTY + stdin)
128
+ sh = box.exec("bash", interactive=True) # interactive implies pty
129
+ ```
130
+
131
+ ## Files
132
+
133
+ ```python
134
+ from pathlib import Path
135
+
136
+ box.write_file(b"binary content", "/app/file.bin")
137
+ box.write_file("text content", "/app/file.txt")
138
+ box.write_file(Path("local/file.py"), "/app/file.py")
139
+ data = box.read_file("/app/output.json") # bytes
140
+ ```
141
+
142
+ ## Proxies
143
+
144
+ ```python
145
+ box.proxies() # list[Proxy]
146
+ proxy = box.create_proxy("api", port=3001) # api.<vm>.boxd.sh -> port 3001
147
+ box.set_proxy_port(port=3000) # change default proxy port
148
+ box.set_proxy_port(port=3001, name="api") # change a named proxy
149
+ box.delete_proxy("api")
150
+ ```
151
+
152
+ ## Logs
153
+
154
+ ```python
155
+ # Snapshot of available console output
156
+ for chunk in box.stream_logs():
157
+ print(chunk.decode(errors="replace"), end="")
158
+
159
+ # Follow (keeps the stream open for new chunks)
160
+ for chunk in box.stream_logs(follow=True):
161
+ print(chunk.decode(errors="replace"), end="")
162
+ ```
163
+
164
+ ## Templates
165
+
166
+ ```python
167
+ from boxd import BoxConfig
168
+
169
+ t = c.template.create(
170
+ name="t1",
171
+ image="ghcr.io/org/img:tag",
172
+ config=BoxConfig(vcpu=2, memory="4G"),
173
+ )
174
+ c.template.list()
175
+ box = c.template.create_vm(template=t, name="from-t")
176
+ c.template.delete(t.id)
177
+ ```
178
+
179
+ ## Disks
180
+
181
+ ```python
182
+ d = c.disk.create("data", size="10G")
183
+ d.attach(box, mount_path="/mnt/data")
184
+ d.attach(box, mount_path="/mnt/data", read_only=True)
185
+ d.detach(box)
186
+ d.destroy()
187
+ ```
188
+
189
+ ## Domains
190
+
191
+ Bind an external domain (DNS must already point at the boxd proxy).
192
+
193
+ ```python
194
+ c.domain.bind("app.example.com", box) # accepts a Box, name, or id
195
+ c.domain.bind("app.example.com", "my-vm")
196
+ for d in c.domain.list():
197
+ print(d.domain, "->", d.vm_id)
198
+ c.domain.unbind("app.example.com")
199
+ ```
200
+
201
+ ## Networks
202
+
203
+ ```python
204
+ n = c.network.create() # server assigns id
205
+ named = c.network.create(name="staging")
206
+ for net in c.network.list():
207
+ print(net.id, net.subnet, net.status)
208
+ ```
209
+
210
+ ## Tokens
211
+
212
+ Issue scoped JWTs for delegated access. The raw token string is only returned at creation — store it then.
213
+
214
+ ```python
215
+ t = c.token.create(expires_in=3600) # 0 = server default
216
+ t.token # "eyJ..." — save this; list() will not return it
217
+ t.expires_at # unix seconds
218
+
219
+ for info in c.token.list():
220
+ print(info.jti, info.created_at, info.expires_at)
221
+ c.token.revoke(info.jti)
222
+
223
+ # Use the token to authenticate a new client
224
+ c2 = Compute(token=t.token)
225
+ ```
226
+
227
+ ## Identity
228
+
229
+ ```python
230
+ me = c.whoami()
231
+ me.user_id # "gh-username"
232
+ me.fingerprints # ["SHA256:..."]
233
+ me.default_network_id # "net-..."
234
+
235
+ cfg = c.config()
236
+ cfg.default_image # "ubuntu:latest"
237
+ cfg.zone # "boxd.sh"
238
+ ```
239
+
240
+ ## Errors
241
+
242
+ ```python
243
+ from boxd import (
244
+ BoxdError, # base class
245
+ AuthenticationError,
246
+ NotFoundError,
247
+ QuotaExceededError,
248
+ InvalidArgumentError,
249
+ TimeoutError,
250
+ ConnectionError,
251
+ InternalError,
252
+ )
253
+
254
+ try:
255
+ box = c.box.get("nope")
256
+ except NotFoundError:
257
+ ...
258
+ ```
259
+
260
+ | Class | gRPC status |
261
+ |---|---|
262
+ | `AuthenticationError` | `UNAUTHENTICATED`, `PERMISSION_DENIED` |
263
+ | `NotFoundError` | `NOT_FOUND` |
264
+ | `QuotaExceededError` | `RESOURCE_EXHAUSTED` |
265
+ | `InvalidArgumentError` | `INVALID_ARGUMENT`, `ALREADY_EXISTS` |
266
+ | `TimeoutError` | `DEADLINE_EXCEEDED` |
267
+ | `ConnectionError` | `UNAVAILABLE` |
268
+ | `InternalError` | `INTERNAL`, `UNKNOWN` |
269
+
270
+ Each error carries the underlying `grpc_code` for finer-grained handling.
271
+
272
+ ## Sync vs Async
273
+
274
+ The default import is the **sync API**, which wraps the async implementation using a dedicated event loop:
275
+
276
+ ```python
277
+ from boxd import Compute # sync — recommended for scripts and notebooks
278
+ ```
279
+
280
+ For async code, import from `boxd.aio`:
281
+
282
+ ```python
283
+ from boxd.aio import Compute
284
+
285
+ async with Compute(api_key="bxk_...") as c:
286
+ box = await c.box.create(name="my-vm")
287
+ result = await box.exec("echo", "hello")
288
+ ```
289
+
290
+ The two APIs are surface-equivalent — only the call style (sync vs `await`) differs.
291
+
292
+ ## Development
293
+
294
+ ```bash
295
+ cd sdk/python
296
+ python -m venv .venv
297
+ source .venv/bin/activate
298
+ pip install -e ".[dev]"
299
+
300
+ pytest tests/ # unit tests (e2e marker excluded by default)
301
+ pytest tests/ -m e2e # e2e tests (creates/destroys VMs)
302
+ pytest tests/ -m "" # everything
303
+ bash scripts/compile_proto.sh # regenerate _generated/ after changing api.proto
304
+ ```
305
+
306
+ ## Architecture
307
+
308
+ ```
309
+ sdk/python/
310
+ ├── src/boxd/
311
+ │ ├── __init__.py # public sync API exports (default import)
312
+ │ ├── aio.py # public async API exports
313
+ │ ├── _sync.py # sync wrappers (run_until_complete)
314
+ │ ├── client.py # async Compute (entry point) + auth/transport
315
+ │ ├── auth.py # API key → JWT exchange + refresh
316
+ │ ├── boxes.py # async BoxService (create/list/get/fork)
317
+ │ ├── box.py # async Box (lifecycle/exec/files/proxies/logs)
318
+ │ ├── exec.py # ExecResult, ExecProcess, stream readers/writers
319
+ │ ├── templates.py # async TemplateService
320
+ │ ├── disks.py # async DiskService + DiskHandle
321
+ │ ├── domains.py # async DomainService
322
+ │ ├── networks.py # async NetworkService
323
+ │ ├── tokens.py # async TokenService
324
+ │ ├── types.py # public dataclasses (BoxConfig, Proxy, etc.)
325
+ │ ├── errors.py # BoxdError hierarchy + gRPC mapping
326
+ │ ├── _utils.py # GrpcCaller mixin, parse_size, resolve_endpoint
327
+ │ └── _generated/ # protoc-grpc-python output (committed)
328
+ └── tests/ # pytest unit + gated e2e
329
+ ```
@@ -0,0 +1,24 @@
1
+ boxd/__init__.py,sha256=2ef6pCn3mAVYXIpDYLdBifaCjMapYK64gQV9yJ0qyHA,2221
2
+ boxd/_sync.py,sha256=iQdtUkEAoykoFihiVDvoO2yTEJDcR7y8YUrYBxlUPW8,14482
3
+ boxd/_utils.py,sha256=Dh9pdq6ZM_7Yw0wsSP679USb1tRj74WQaIFflchIFyY,2287
4
+ boxd/aio.py,sha256=Ojl0NqnpZjILz18APNUbQoTYWHEnqFxdyvVUTcM_JHM,1950
5
+ boxd/auth.py,sha256=RXXrnZcJEt9Uho2a5CggfvY-PKoZ-epGCXyzk7Qbi1g,4353
6
+ boxd/box.py,sha256=CKGsqF3ovLBZodg5MXI1iieNlVgT4BU1TsK7o4Ky-_g,11046
7
+ boxd/boxes.py,sha256=8pFe0Fb2gUsN2WW4RLo5VJrOmj7NPqXI4Bl5RdhecdM,5342
8
+ boxd/client.py,sha256=OIXy3GgstbOJyx9HBYGlQaPBs7nSNoJN69-TnT21Cr8,4327
9
+ boxd/disks.py,sha256=L7IzyNdwKKSAWjoBdO3iHi7dHzfEWfU8XMeJw_AZ_5U,3110
10
+ boxd/domains.py,sha256=tSoXpgBZILsfZz6owjBRa_a2oTEFCc8jZDqIxpfNnzA,1695
11
+ boxd/errors.py,sha256=nXEsq6BJNEz2WienaHvY4fTi9NvZ7Ifl_jIZ65U6NB0,1659
12
+ boxd/exec.py,sha256=Bu451UtanSx5Rssmg3gR-ZQQGOQCXBhfS2Qz2y1ggTU,3409
13
+ boxd/networks.py,sha256=T5SZ6kVWJZVTcOl7VNajYuxmiNntZ7NgYmGo-chvyaI,1191
14
+ boxd/templates.py,sha256=B7F8andarXwLwnp3Bk6T6rAib9xun3HlRf-MoiJCTBQ,3369
15
+ boxd/tokens.py,sha256=9whIjTyBHBwBci_oSShHteqJFGBhBZniEAbqmv7nsxY,1621
16
+ boxd/types.py,sha256=XEYxDAN4yYO-w9AEIslCQewDYMP8Zhqmfgzb7Qp-BWE,2120
17
+ boxd/_generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ boxd/_generated/api_pb2.py,sha256=eC6-gyBTOAammi1xDKDdCf1IyESv9a-yMhCFFxcSgdQ,25061
19
+ boxd/_generated/api_pb2_grpc.py,sha256=gx4ztNqCCShdwEUruDQ95HXrfjDVG83t02gXere5UVg,68255
20
+ boxd-0.1.0.dev2.dist-info/licenses/LICENSE,sha256=rD8_32iKSf_QarUkwB8tTujEORtC3uYs4KIggm6cFKg,1061
21
+ boxd-0.1.0.dev2.dist-info/METADATA,sha256=pKObr2Uxd8ro8rejkpRqlKL0qX2JVmJemQDtWNPK-P0,9417
22
+ boxd-0.1.0.dev2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
23
+ boxd-0.1.0.dev2.dist-info/top_level.txt,sha256=lJFbtvnDUx23dMMfX5hqRWPjtUhgrLpNJ9qdCPwON30,5
24
+ boxd-0.1.0.dev2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Azin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ boxd