vers-sdk 0.1.1__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.
vers_sdk/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ # Generated by Sterling SDK Generator
2
+ # Orchestrator Control Plane API v0.1.0
3
+
4
+ from .client import VersSdkClient
5
+ from .errors import (
6
+ VersSDKError,
7
+ APIError,
8
+ APIConnectionError,
9
+ APIConnectionTimeoutError,
10
+ BadRequestError,
11
+ AuthenticationError,
12
+ PermissionDeniedError,
13
+ NotFoundError,
14
+ ConflictError,
15
+ UnprocessableEntityError,
16
+ RateLimitError,
17
+ InternalServerError,
18
+ )
19
+
20
+ __all__ = [
21
+ "VersSdkClient",
22
+ "VersSDKError",
23
+ "APIError",
24
+ "APIConnectionError",
25
+ "APIConnectionTimeoutError",
26
+ "BadRequestError",
27
+ "AuthenticationError",
28
+ "PermissionDeniedError",
29
+ "NotFoundError",
30
+ "ConflictError",
31
+ "UnprocessableEntityError",
32
+ "RateLimitError",
33
+ "InternalServerError",
34
+ ]
vers_sdk/client.py ADDED
@@ -0,0 +1,529 @@
1
+ # Generated by Sterling SDK Generator
2
+ # Orchestrator Control Plane API v0.1.0
3
+
4
+ import asyncio
5
+ import calendar
6
+ import email.utils
7
+ import logging
8
+ import os
9
+ import random
10
+ import time as _time
11
+ import uuid
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Optional
14
+
15
+ import httpx
16
+
17
+ from .errors import APIError, APIConnectionError
18
+ from .models import * # noqa: F403
19
+
20
+
21
+ @dataclass
22
+ class RequestOptions:
23
+ """Per-request options that override client defaults."""
24
+
25
+ #: Additional headers to send with the request.
26
+ #: Set a header to None to remove it.
27
+ headers: dict[str, str | None] | None = None
28
+
29
+ #: Request timeout in seconds. Overrides the client-level timeout.
30
+ timeout: float | None = None
31
+
32
+ logger = logging.getLogger("vers_sdk")
33
+
34
+ _LOG_LEVEL_MAP = {
35
+ "debug": logging.DEBUG,
36
+ "info": logging.INFO,
37
+ "warn": logging.WARNING,
38
+ "error": logging.ERROR,
39
+ "off": logging.CRITICAL + 1,
40
+ }
41
+
42
+
43
+ def _configure_logger() -> None:
44
+ env_level = os.environ.get("VERS_LOG", "warn").lower()
45
+ level = _LOG_LEVEL_MAP.get(env_level, logging.WARNING)
46
+ logger.setLevel(level)
47
+ if not logger.handlers:
48
+ handler = logging.StreamHandler()
49
+ handler.setFormatter(logging.Formatter("%(asctime)s [vers_sdk %(levelname)s] %(message)s"))
50
+ logger.addHandler(handler)
51
+
52
+
53
+ _configure_logger()
54
+
55
+
56
+ # Status codes that are retryable even though they are 4xx
57
+ RETRYABLE_STATUS_CODES = {408, 409, 429}
58
+
59
+
60
+ def _is_retryable_status(status: int) -> bool:
61
+ return status >= 500 or status in RETRYABLE_STATUS_CODES
62
+
63
+
64
+ def _retry_delay(attempt: int) -> float:
65
+ base = 0.5 * (2 ** attempt)
66
+ jitter = random.random() * base * 0.25
67
+ return base + jitter
68
+
69
+
70
+ def _parse_retry_after(header_value: Optional[str]) -> Optional[float]:
71
+ if header_value is None:
72
+ return None
73
+ try:
74
+ delay = float(header_value)
75
+ return min(max(delay, 0.0), 60.0)
76
+ except (ValueError, OverflowError):
77
+ pass
78
+ parsed = email.utils.parsedate(header_value)
79
+ if parsed is not None:
80
+ retry_time = calendar.timegm(parsed)
81
+ delay = retry_time - _time.time()
82
+ return min(max(delay, 0.0), 60.0)
83
+ return None
84
+
85
+
86
+ class VersSdkClient:
87
+ """Client for Orchestrator Control Plane API"""
88
+
89
+ def __init__(
90
+ self,
91
+ base_url: Optional[str] = None,
92
+ api_key: Optional[str] = None,
93
+ headers: Optional[dict[str, str | None]] = None,
94
+ max_retries: int = 2,
95
+ timeout: float = 30.0,
96
+ http_client: Optional[httpx.AsyncClient] = None,
97
+ ):
98
+ self.base_url = base_url or os.environ.get("VERS_BASE_URL", "https://api.vers.sh")
99
+ self.api_key = api_key or os.environ.get("VERS_API_KEY")
100
+ self.max_retries = max_retries
101
+ self.timeout = timeout
102
+ self._custom_headers: dict[str, str | None] = headers or {}
103
+ if http_client is not None:
104
+ self._client = http_client
105
+ else:
106
+ import platform as _platform
107
+ default_headers: dict[str, str] = {
108
+ "User-Agent": f"vers-sdk/0.1.1 python/{_platform.python_version()} {_platform.system().lower()}/{_platform.machine()}",
109
+ }
110
+ if self.api_key:
111
+ default_headers["Authorization"] = f"Bearer {self.api_key}"
112
+ merged = self._merge_headers(default_headers, self._custom_headers)
113
+ self._client = httpx.AsyncClient(
114
+ base_url=self.base_url,
115
+ headers=merged,
116
+ timeout=self.timeout,
117
+ )
118
+
119
+ @staticmethod
120
+ def _merge_headers(*sources: Optional[dict[str, str | None]]) -> dict[str, str]:
121
+ """Merge header dicts. Later sources override earlier ones (case-insensitive).
122
+ A ``None`` value removes the header."""
123
+ merged: dict[str, str] = {}
124
+ for source in sources:
125
+ if source is None:
126
+ continue
127
+ for key, value in source.items():
128
+ if value is None:
129
+ # Remove header (case-insensitive)
130
+ merged = {
131
+ k: v for k, v in merged.items()
132
+ if k.lower() != key.lower()
133
+ }
134
+ else:
135
+ merged[key] = value
136
+ return merged
137
+
138
+ def _check_response(self, response: httpx.Response) -> None:
139
+ if response.is_success:
140
+ return
141
+ body: Any = None
142
+ message: str | None = None
143
+ try:
144
+ body = response.json()
145
+ except Exception:
146
+ message = response.text
147
+ headers = dict(response.headers)
148
+ raise APIError.generate(
149
+ status=response.status_code,
150
+ body=body,
151
+ message=message,
152
+ headers=headers,
153
+ )
154
+
155
+ async def _request(self, method: str, path: str, options: RequestOptions | None = None, **kwargs: Any) -> httpx.Response:
156
+ logger.debug("request: %s %s", method, path)
157
+
158
+ req_headers: dict[str, str] = kwargs.pop("headers", {})
159
+ if method.upper() in ("POST", "PUT", "PATCH", "DELETE"):
160
+ req_headers["X-Idempotency-Key"] = str(uuid.uuid4())
161
+ # Apply per-request header overrides
162
+ if options and options.headers:
163
+ for k, v in options.headers.items():
164
+ if v is None:
165
+ req_headers.pop(k, None)
166
+ else:
167
+ req_headers[k] = v
168
+ if req_headers:
169
+ kwargs["headers"] = req_headers
170
+ effective_timeout = options.timeout if options and options.timeout is not None else None
171
+
172
+ last_exc: Optional[Exception] = None
173
+ retry_after_used = False
174
+ for attempt in range(self.max_retries + 1):
175
+ if attempt > 0 and not retry_after_used:
176
+ logger.info("retry attempt %d/%d for %s %s", attempt, self.max_retries, method, path)
177
+ await asyncio.sleep(_retry_delay(attempt - 1))
178
+ retry_after_used = False
179
+ try:
180
+ req_kwargs = dict(kwargs)
181
+ if effective_timeout is not None:
182
+ req_kwargs["timeout"] = effective_timeout
183
+ response = await self._client.request(method, path, **req_kwargs)
184
+ # Remove headers that were set to None
185
+ logger.debug("response: %d for %s %s", response.status_code, method, path)
186
+ if _is_retryable_status(response.status_code) and attempt < self.max_retries:
187
+ ra_delay = _parse_retry_after(response.headers.get("retry-after"))
188
+ if ra_delay is not None:
189
+ retry_after_used = True
190
+ logger.info("retry attempt %d/%d for %s %s (retry-after: %.1fs)", attempt + 1, self.max_retries, method, path, ra_delay)
191
+ await asyncio.sleep(ra_delay)
192
+ last_exc = Exception(f"HTTP {response.status_code}")
193
+ continue
194
+ self._check_response(response)
195
+ return response
196
+ except APIError:
197
+ raise
198
+ except (httpx.ConnectError, httpx.TimeoutException) as exc:
199
+ logger.error("connection error: %s %s - %s", method, path, exc)
200
+ if attempt == self.max_retries:
201
+ raise APIConnectionError(cause=exc) from exc
202
+ last_exc = exc
203
+ raise last_exc # type: ignore[misc]
204
+
205
+ async def resize_vm_disk(self, vm_id: str, body: dict[str, Any], params: ResizeVmDiskParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
206
+ """"""
207
+ path = f"/api/v1/vm/{vm_id}/disk"
208
+ query: dict[str, Any] = {}
209
+ if params is not None:
210
+ if params.skip_wait_boot is not None:
211
+ query["skip_wait_boot"] = params.skip_wait_boot
212
+ return await self._request("PATCH", path, options, json=body, params=query)
213
+
214
+ async def exec_vm_stream_attach(self, vm_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
215
+ """"""
216
+ path = f"/api/v1/vm/{vm_id}/exec/stream/attach"
217
+ return await self._request("POST", path, options, json=body)
218
+
219
+ async def create_new_root_vm(self, body: dict[str, Any], params: CreateNewRootVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
220
+ """"""
221
+ path = "/api/v1/vm/new_root"
222
+ query: dict[str, Any] = {}
223
+ if params is not None:
224
+ if params.wait_boot is not None:
225
+ query["wait_boot"] = params.wait_boot
226
+ return await self._request("POST", path, options, json=body, params=query)
227
+
228
+ async def vm_logs(self, vm_id: str, params: VmLogsParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
229
+ """"""
230
+ path = f"/api/v1/vm/{vm_id}/logs"
231
+ query: dict[str, Any] = {}
232
+ if params is not None:
233
+ if params.offset is not None:
234
+ query["offset"] = params.offset
235
+ if params.max_entries is not None:
236
+ query["max_entries"] = params.max_entries
237
+ if params.stream is not None:
238
+ query["stream"] = params.stream
239
+ return await self._request("GET", path, options, params=query)
240
+
241
+ async def branch_by_ref(self, repo_name: str, tag_name: str, body: dict[str, Any] | None = None, params: BranchByRefParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
242
+ """"""
243
+ path = f"/api/v1/vm/branch/by_ref/{repo_name}/{tag_name}"
244
+ query: dict[str, Any] = {}
245
+ if params is not None:
246
+ if params.count is not None:
247
+ query["count"] = params.count
248
+ return await self._request("POST", path, options, json=body, params=query)
249
+
250
+ async def list_public_commits(self, options: RequestOptions | None = None) -> httpx.Response:
251
+ """"""
252
+ path = "/api/v1/commits/public"
253
+ return await self._request("GET", path, options)
254
+
255
+ async def list_public_repo_tags(self, org_name: str, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
256
+ """"""
257
+ path = f"/api/v1/public/repositories/{org_name}/{repo_name}/tags"
258
+ return await self._request("GET", path, options)
259
+
260
+ async def branch_by_tag(self, tag_name: str, body: dict[str, Any] | None = None, params: BranchByTagParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
261
+ """"""
262
+ path = f"/api/v1/vm/branch/by_tag/{tag_name}"
263
+ query: dict[str, Any] = {}
264
+ if params is not None:
265
+ if params.count is not None:
266
+ query["count"] = params.count
267
+ return await self._request("POST", path, options, json=body, params=query)
268
+
269
+ async def branch_vm(self, vm_or_commit_id: str, body: dict[str, Any] | None = None, params: BranchVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
270
+ """"""
271
+ path = f"/api/v1/vm/{vm_or_commit_id}/branch"
272
+ query: dict[str, Any] = {}
273
+ if params is not None:
274
+ if params.keep_paused is not None:
275
+ query["keep_paused"] = params.keep_paused
276
+ if params.skip_wait_boot is not None:
277
+ query["skip_wait_boot"] = params.skip_wait_boot
278
+ if params.count is not None:
279
+ query["count"] = params.count
280
+ return await self._request("POST", path, options, json=body, params=query)
281
+
282
+ async def get_repo_tag(self, repo_name: str, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
283
+ """"""
284
+ path = f"/api/v1/repositories/{repo_name}/tags/{tag_name}"
285
+ return await self._request("GET", path, options)
286
+
287
+ async def delete_repo_tag(self, repo_name: str, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
288
+ """"""
289
+ path = f"/api/v1/repositories/{repo_name}/tags/{tag_name}"
290
+ return await self._request("DELETE", path, options)
291
+
292
+ async def update_repo_tag(self, repo_name: str, tag_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
293
+ """"""
294
+ path = f"/api/v1/repositories/{repo_name}/tags/{tag_name}"
295
+ return await self._request("PATCH", path, options, json=body)
296
+
297
+ async def get_repository(self, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
298
+ """"""
299
+ path = f"/api/v1/repositories/{repo_name}"
300
+ return await self._request("GET", path, options)
301
+
302
+ async def delete_repository(self, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
303
+ """"""
304
+ path = f"/api/v1/repositories/{repo_name}"
305
+ return await self._request("DELETE", path, options)
306
+
307
+ async def list_parent_commits(self, commit_id: str, options: RequestOptions | None = None) -> httpx.Response:
308
+ """"""
309
+ path = f"/api/v1/vm/commits/{commit_id}/parents"
310
+ return await self._request("GET", path, options)
311
+
312
+ async def update_vm_state(self, vm_id: str, body: dict[str, Any], params: UpdateVmStateParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
313
+ """"""
314
+ path = f"/api/v1/vm/{vm_id}/state"
315
+ query: dict[str, Any] = {}
316
+ if params is not None:
317
+ if params.skip_wait_boot is not None:
318
+ query["skip_wait_boot"] = params.skip_wait_boot
319
+ return await self._request("PATCH", path, options, json=body, params=query)
320
+
321
+ async def fork_repository(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
322
+ """"""
323
+ path = "/api/v1/repositories/fork"
324
+ return await self._request("POST", path, options, json=body)
325
+
326
+ async def list_repositories(self, options: RequestOptions | None = None) -> httpx.Response:
327
+ """"""
328
+ path = "/api/v1/repositories"
329
+ return await self._request("GET", path, options)
330
+
331
+ async def create_repository(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
332
+ """"""
333
+ path = "/api/v1/repositories"
334
+ return await self._request("POST", path, options, json=body)
335
+
336
+ async def restore_from_commit(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
337
+ """"""
338
+ path = "/api/v1/vm/from_commit"
339
+ return await self._request("POST", path, options, json=body)
340
+
341
+ async def get_domain(self, domain_id: str, options: RequestOptions | None = None) -> httpx.Response:
342
+ """"""
343
+ path = f"/api/v1/domains/{domain_id}"
344
+ return await self._request("GET", path, options)
345
+
346
+ async def delete_domain(self, domain_id: str, options: RequestOptions | None = None) -> httpx.Response:
347
+ """"""
348
+ path = f"/api/v1/domains/{domain_id}"
349
+ return await self._request("DELETE", path, options)
350
+
351
+ async def list_tags(self, options: RequestOptions | None = None) -> httpx.Response:
352
+ """"""
353
+ path = "/api/v1/commit_tags"
354
+ return await self._request("GET", path, options)
355
+
356
+ async def create_tag(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
357
+ """"""
358
+ path = "/api/v1/commit_tags"
359
+ return await self._request("POST", path, options, json=body)
360
+
361
+ async def list_public_repositories(self, options: RequestOptions | None = None) -> httpx.Response:
362
+ """"""
363
+ path = "/api/v1/public/repositories"
364
+ return await self._request("GET", path, options)
365
+
366
+ async def get_public_repo_tag(self, org_name: str, repo_name: str, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
367
+ """"""
368
+ path = f"/api/v1/public/repositories/{org_name}/{repo_name}/tags/{tag_name}"
369
+ return await self._request("GET", path, options)
370
+
371
+ async def get_vm_metadata(self, vm_id: str, options: RequestOptions | None = None) -> httpx.Response:
372
+ """"""
373
+ path = f"/api/v1/vm/{vm_id}/metadata"
374
+ return await self._request("GET", path, options)
375
+
376
+ async def list_env_vars(self, options: RequestOptions | None = None) -> httpx.Response:
377
+ """"""
378
+ path = "/api/v1/env_vars"
379
+ return await self._request("GET", path, options)
380
+
381
+ async def set_env_vars(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
382
+ """"""
383
+ path = "/api/v1/env_vars"
384
+ return await self._request("PUT", path, options, json=body)
385
+
386
+ async def delete_vm(self, vm_id: str, params: DeleteVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
387
+ """"""
388
+ path = f"/api/v1/vm/{vm_id}"
389
+ query: dict[str, Any] = {}
390
+ if params is not None:
391
+ if params.skip_wait_boot is not None:
392
+ query["skip_wait_boot"] = params.skip_wait_boot
393
+ return await self._request("DELETE", path, options, params=query)
394
+
395
+ async def list_commits(self, options: RequestOptions | None = None) -> httpx.Response:
396
+ """"""
397
+ path = "/api/v1/commits"
398
+ return await self._request("GET", path, options)
399
+
400
+ async def get_tag(self, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
401
+ """"""
402
+ path = f"/api/v1/commit_tags/{tag_name}"
403
+ return await self._request("GET", path, options)
404
+
405
+ async def delete_tag(self, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
406
+ """"""
407
+ path = f"/api/v1/commit_tags/{tag_name}"
408
+ return await self._request("DELETE", path, options)
409
+
410
+ async def update_tag(self, tag_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
411
+ """"""
412
+ path = f"/api/v1/commit_tags/{tag_name}"
413
+ return await self._request("PATCH", path, options, json=body)
414
+
415
+ async def branch_by_vm(self, vm_id: str, body: dict[str, Any] | None = None, params: BranchByVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
416
+ """"""
417
+ path = f"/api/v1/vm/branch/by_vm/{vm_id}"
418
+ query: dict[str, Any] = {}
419
+ if params is not None:
420
+ if params.keep_paused is not None:
421
+ query["keep_paused"] = params.keep_paused
422
+ if params.skip_wait_boot is not None:
423
+ query["skip_wait_boot"] = params.skip_wait_boot
424
+ if params.count is not None:
425
+ query["count"] = params.count
426
+ return await self._request("POST", path, options, json=body, params=query)
427
+
428
+ async def delete_env_var(self, key: str, options: RequestOptions | None = None) -> httpx.Response:
429
+ """"""
430
+ path = f"/api/v1/env_vars/{key}"
431
+ return await self._request("DELETE", path, options)
432
+
433
+ async def exec_vm_stream(self, vm_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
434
+ """"""
435
+ path = f"/api/v1/vm/{vm_id}/exec/stream"
436
+ return await self._request("POST", path, options, json=body)
437
+
438
+ async def commit_vm(self, vm_id: str, body: dict[str, Any] | None = None, params: CommitVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
439
+ """"""
440
+ path = f"/api/v1/vm/{vm_id}/commit"
441
+ query: dict[str, Any] = {}
442
+ if params is not None:
443
+ if params.keep_paused is not None:
444
+ query["keep_paused"] = params.keep_paused
445
+ if params.skip_wait_boot is not None:
446
+ query["skip_wait_boot"] = params.skip_wait_boot
447
+ return await self._request("POST", path, options, json=body, params=query)
448
+
449
+ async def delete_commit(self, commit_id: str, options: RequestOptions | None = None) -> httpx.Response:
450
+ """"""
451
+ path = f"/api/v1/commits/{commit_id}"
452
+ return await self._request("DELETE", path, options)
453
+
454
+ async def update_commit(self, commit_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
455
+ """"""
456
+ path = f"/api/v1/commits/{commit_id}"
457
+ return await self._request("PATCH", path, options, json=body)
458
+
459
+ async def list_repo_tags(self, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
460
+ """"""
461
+ path = f"/api/v1/repositories/{repo_name}/tags"
462
+ return await self._request("GET", path, options)
463
+
464
+ async def create_repo_tag(self, repo_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
465
+ """"""
466
+ path = f"/api/v1/repositories/{repo_name}/tags"
467
+ return await self._request("POST", path, options, json=body)
468
+
469
+ async def ssh_key(self, vm_id: str, options: RequestOptions | None = None) -> httpx.Response:
470
+ """"""
471
+ path = f"/api/v1/vm/{vm_id}/ssh_key"
472
+ return await self._request("GET", path, options)
473
+
474
+ async def exec_vm(self, vm_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
475
+ """"""
476
+ path = f"/api/v1/vm/{vm_id}/exec"
477
+ return await self._request("POST", path, options, json=body)
478
+
479
+ async def set_repository_visibility(self, repo_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
480
+ """"""
481
+ path = f"/api/v1/repositories/{repo_name}/visibility"
482
+ return await self._request("PATCH", path, options, json=body)
483
+
484
+ async def list_vms(self, options: RequestOptions | None = None) -> httpx.Response:
485
+ """"""
486
+ path = "/api/v1/vms"
487
+ return await self._request("GET", path, options)
488
+
489
+ async def list_domains(self, params: ListDomainsParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
490
+ """"""
491
+ path = "/api/v1/domains"
492
+ query: dict[str, Any] = {}
493
+ if params is not None:
494
+ if params.vm_id is not None:
495
+ query["vm_id"] = params.vm_id
496
+ return await self._request("GET", path, options, params=query)
497
+
498
+ async def create_domain(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
499
+ """"""
500
+ path = "/api/v1/domains"
501
+ return await self._request("POST", path, options, json=body)
502
+
503
+ async def vm_status(self, vm_id: str, options: RequestOptions | None = None) -> httpx.Response:
504
+ """"""
505
+ path = f"/api/v1/vm/{vm_id}/status"
506
+ return await self._request("GET", path, options)
507
+
508
+ async def get_public_repository(self, org_name: str, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
509
+ """"""
510
+ path = f"/api/v1/public/repositories/{org_name}/{repo_name}"
511
+ return await self._request("GET", path, options)
512
+
513
+ async def branch_by_commit(self, commit_id: str, body: dict[str, Any] | None = None, params: BranchByCommitParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
514
+ """"""
515
+ path = f"/api/v1/vm/branch/by_commit/{commit_id}"
516
+ query: dict[str, Any] = {}
517
+ if params is not None:
518
+ if params.count is not None:
519
+ query["count"] = params.count
520
+ return await self._request("POST", path, options, json=body, params=query)
521
+
522
+ async def close(self) -> None:
523
+ await self._client.aclose()
524
+
525
+ async def __aenter__(self) -> "VersSdkClient":
526
+ return self
527
+
528
+ async def __aexit__(self, *args: Any) -> None:
529
+ await self.close()
vers_sdk/errors.py ADDED
@@ -0,0 +1,132 @@
1
+ # Generated by Sterling SDK Generator
2
+ # Orchestrator Control Plane API v0.1.0
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Optional
7
+
8
+
9
+ class VersSDKError(Exception):
10
+ """Base error class for the VersSdk SDK."""
11
+ pass
12
+
13
+
14
+ class APIError(VersSDKError):
15
+ """Error returned when the API responds with a non-success status code."""
16
+
17
+ status: int | None
18
+ headers: dict[str, str] | None
19
+ body: Any
20
+
21
+ def __init__(
22
+ self,
23
+ status: int | None,
24
+ body: Any = None,
25
+ message: str | None = None,
26
+ headers: dict[str, str] | None = None,
27
+ ) -> None:
28
+ self.status = status
29
+ self.headers = headers
30
+ self.body = body
31
+ super().__init__(self._make_message(status, body, message))
32
+
33
+ @staticmethod
34
+ def _make_message(status: int | None, body: Any, message: str | None) -> str:
35
+ msg = None
36
+ if isinstance(body, dict) and "message" in body:
37
+ msg = body["message"] if isinstance(body["message"], str) else str(body["message"])
38
+ elif body is not None:
39
+ msg = str(body)
40
+ elif message is not None:
41
+ msg = message
42
+
43
+ if status and msg:
44
+ return f"{status} {msg}"
45
+ if status:
46
+ return f"{status} status code (no body)"
47
+ if msg:
48
+ return msg
49
+ return "(no status code or body)"
50
+
51
+ @classmethod
52
+ def generate(
53
+ cls,
54
+ status: int | None,
55
+ body: Any = None,
56
+ message: str | None = None,
57
+ headers: dict[str, str] | None = None,
58
+ ) -> APIError:
59
+ if status is None or headers is None:
60
+ return APIConnectionError(message=message)
61
+
62
+ error_map: dict[int, type[APIError]] = {
63
+ 400: BadRequestError,
64
+ 401: AuthenticationError,
65
+ 403: PermissionDeniedError,
66
+ 404: NotFoundError,
67
+ 409: ConflictError,
68
+ 422: UnprocessableEntityError,
69
+ 429: RateLimitError,
70
+ }
71
+
72
+ error_cls = error_map.get(status)
73
+ if error_cls is not None:
74
+ return error_cls(status=status, body=body, message=message, headers=headers)
75
+ if status >= 500:
76
+ return InternalServerError(status=status, body=body, message=message, headers=headers)
77
+ return APIError(status=status, body=body, message=message, headers=headers)
78
+
79
+
80
+ class APIConnectionError(APIError):
81
+ """Error thrown when a connection to the API cannot be established."""
82
+
83
+ def __init__(self, message: str | None = None, cause: BaseException | None = None) -> None:
84
+ super().__init__(status=None, body=None, message=message or "Connection error.")
85
+ self.__cause__ = cause
86
+
87
+
88
+ class APIConnectionTimeoutError(APIConnectionError):
89
+ """Error thrown when a request times out."""
90
+
91
+ def __init__(self, message: str | None = None) -> None:
92
+ super().__init__(message=message or "Request timed out.")
93
+
94
+
95
+ class BadRequestError(APIError):
96
+ """400 Bad Request"""
97
+ pass
98
+
99
+
100
+ class AuthenticationError(APIError):
101
+ """401 Unauthorized"""
102
+ pass
103
+
104
+
105
+ class PermissionDeniedError(APIError):
106
+ """403 Forbidden"""
107
+ pass
108
+
109
+
110
+ class NotFoundError(APIError):
111
+ """404 Not Found"""
112
+ pass
113
+
114
+
115
+ class ConflictError(APIError):
116
+ """409 Conflict"""
117
+ pass
118
+
119
+
120
+ class UnprocessableEntityError(APIError):
121
+ """422 Unprocessable Entity"""
122
+ pass
123
+
124
+
125
+ class RateLimitError(APIError):
126
+ """429 Too Many Requests"""
127
+ pass
128
+
129
+
130
+ class InternalServerError(APIError):
131
+ """5xx Internal Server Error"""
132
+ pass
vers_sdk/models.py ADDED
@@ -0,0 +1,545 @@
1
+ # Generated by Sterling SDK Generator
2
+ # Orchestrator Control Plane API v0.1.0
3
+
4
+ from __future__ import annotations
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import Any, Optional, Union
8
+
9
+ @dataclass
10
+ class ForkRepositoryResponse:
11
+ """Response body for POST /api/v1/repositories/fork"""
12
+ #: The new commit in your org (snapshot of the forked VM)
13
+ commit_id: str
14
+ #: Full reference: repo_name:tag_name
15
+ reference: str
16
+ #: The new repository name in your org
17
+ repo_name: str
18
+ #: The tag name pointing to the forked commit
19
+ tag_name: str
20
+ #: The new VM that was created from the fork
21
+ vm_id: str
22
+
23
+ @dataclass
24
+ class VmSshKeyResponse:
25
+ """Response body for GET /api/vm/{vm_id}/ssh_key"""
26
+ #: The SSH port that will be DNAT'd to the VM's netns (and, in turn, to its TAP device)
27
+ ssh_port: int
28
+ #: Private SSH key in stringified OpenSSH format
29
+ ssh_private_key: str
30
+
31
+ class VmState(str, Enum):
32
+ """The state of a VM"""
33
+ BOOTING = "booting"
34
+ RUNNING = "running"
35
+ PAUSED = "paused"
36
+ SLEEPING = "sleeping"
37
+ DEAD = "dead"
38
+
39
+ class VmUpdateStateEnum(str, Enum):
40
+ """Possible options for the state requested in PATCH /api/vm/{vm_id}/state"""
41
+ PAUSED = "Paused"
42
+ RUNNING = "Running"
43
+
44
+ @dataclass
45
+ class VmUpdateStateRequest:
46
+ """Request body for PATCH /api/vm/{vm_id}/state"""
47
+ #: The requested state for the VM
48
+ state: VmUpdateStateEnum
49
+
50
+ @dataclass
51
+ class ErrorResponse:
52
+ """"""
53
+ #: Reason of error
54
+ error: Optional[str] = None
55
+ #: Is always: false
56
+ success: Optional[bool] = None
57
+
58
+ @dataclass
59
+ class UpdateCommitRequest:
60
+ """Request body for PATCH /commits/{commit_id}"""
61
+ is_public: bool
62
+
63
+ @dataclass
64
+ class EnvVarsResponse:
65
+ """Response body for GET /env_vars and PUT /env_vars."""
66
+ #: All environment variables currently set for the authenticated user.
67
+ vars: Any
68
+
69
+ @dataclass
70
+ class CreateRepositoryResponse:
71
+ """Response body for POST /api/v1/repositories"""
72
+ #: The name of the repository
73
+ name: str
74
+ #: The ID of the newly created repository
75
+ repo_id: str
76
+
77
+ @dataclass
78
+ class CreateRepositoryRequest:
79
+ """Request body for POST /api/v1/repositories"""
80
+ #: Optional description of the repository
81
+ description: Optional[Any] = None
82
+ #: The name of the repository (alphanumeric, hyphens, underscores, dots, 1-64 chars)
83
+ name: str
84
+
85
+ @dataclass
86
+ class DomainResponse:
87
+ """Response type for domain operations."""
88
+ created_at: str
89
+ domain: str
90
+ domain_id: str
91
+ vm_id: str
92
+
93
+ @dataclass
94
+ class PublicRepositoryInfo:
95
+ """Public repository information (includes owner org name for namespacing)"""
96
+ #: When the repository was created
97
+ created_at: str
98
+ #: Optional description
99
+ description: Optional[Any] = None
100
+ #: Full reference: org_name/repo_name
101
+ full_name: str
102
+ #: The repository name
103
+ name: str
104
+ #: The owning organization's name (namespace)
105
+ org_name: str
106
+ #: The repository's unique identifier
107
+ repo_id: str
108
+
109
+ @dataclass
110
+ class ListTagsResponse:
111
+ """Response body for GET /api/v1/commit_tags"""
112
+ #: List of all tags in the user's organization
113
+ tags: list[TagInfo]
114
+
115
+ @dataclass
116
+ class CreateRepoTagRequest:
117
+ """Request body for creating a tag within a repository: POST /api/v1/repositories/{repo_name}/tags"""
118
+ #: The commit ID this tag should point to
119
+ commit_id: str
120
+ #: Optional description of what this tag represents
121
+ description: Optional[Any] = None
122
+ #: The tag name (e.g. 'latest', 'v1.0')
123
+ tag_name: str
124
+
125
+ @dataclass
126
+ class ForkRepositoryRequest:
127
+ """Request body for POST /api/v1/repositories/fork"""
128
+ #: Name for the new repository in your org (defaults to source_repo if omitted)
129
+ repo_name: Optional[Any] = None
130
+ #: The organization that owns the source public repository
131
+ source_org: str
132
+ #: The source repository name
133
+ source_repo: str
134
+ #: The tag to fork (e.g. 'latest', 'v1.0')
135
+ source_tag: str
136
+ #: Tag name in the new repo (defaults to source_tag if omitted)
137
+ tag_name: Optional[Any] = None
138
+
139
+ @dataclass
140
+ class VmCommitEntity:
141
+ """"""
142
+ created_at: str
143
+ description: Optional[Any] = None
144
+ #: The commit that this commit's parent VM was started from, if any. Intended to optimize traversing the commit tree.
145
+ grandparent_commit_id: Optional[Any] = None
146
+ id: str
147
+ #: Whether this commit is publicly accessible (readable/restorable by anyone).
148
+ is_public: bool
149
+ name: str
150
+ #: api key id.
151
+ owner_id: str
152
+ #: The VM that this commit was created from, if any.
153
+ parent_vm_id: Optional[Any] = None
154
+
155
+ @dataclass
156
+ class VmExecLogQuery:
157
+ """Query params for GET /api/vm/{vm_id}/exec/logs"""
158
+ #: Maximum number of entries to return (server applies caps).
159
+ max_entries: Optional[Any] = None
160
+ #: Byte offset into the log file to start reading from.
161
+ offset: Optional[Any] = None
162
+ #: Skip waiting for boot state (mirrors exec).
163
+ skip_wait_boot: Optional[Any] = None
164
+ stream: Optional[Any] = None
165
+
166
+ @dataclass
167
+ class TagInfo:
168
+ """Tag information returned in list and get operations"""
169
+ #: The commit ID this tag currently points to
170
+ commit_id: str
171
+ #: When the tag was created
172
+ created_at: str
173
+ #: Optional description of what this tag represents
174
+ description: Optional[Any] = None
175
+ #: The tag's unique identifier
176
+ tag_id: str
177
+ #: The name of the tag
178
+ tag_name: str
179
+ #: When the tag was last updated (moved to different commit or description changed)
180
+ updated_at: str
181
+
182
+ @dataclass
183
+ class ListCommitsResponse:
184
+ """"""
185
+ commits: list[CommitInfo]
186
+ limit: int
187
+ offset: int
188
+ total: int
189
+
190
+ @dataclass
191
+ class ListRepoTagsResponse:
192
+ """Response body for GET /api/v1/repositories/{repo_name}/tags"""
193
+ #: The repository name
194
+ repository: str
195
+ #: List of tags in this repository
196
+ tags: list[RepoTagInfo]
197
+
198
+ @dataclass
199
+ class VmExecLogEntry:
200
+ """Individual log entry describing emitted stdout/stderr chunk."""
201
+ #: Base64-encoded bytes from stdout/stderr chunk.
202
+ data_b64: str
203
+ exec_id: Optional[Any] = None
204
+ stream: VmExecLogStream
205
+ timestamp: str
206
+
207
+ class VmExecLogStream(str, Enum):
208
+ """Streams available for exec logging."""
209
+ STDOUT = "stdout"
210
+ STDERR = "stderr"
211
+
212
+ @dataclass
213
+ class VmExecResponse:
214
+ """Response body for POST /api/vm/{vm_id}/exec"""
215
+ #: Exec identifier associated with this run.
216
+ exec_id: Optional[Any] = None
217
+ #: Exit code returned by the command.
218
+ exit_code: int
219
+ #: UTF-8 decoded stderr (lossy).
220
+ stderr: str
221
+ #: UTF-8 decoded stdout (lossy).
222
+ stdout: str
223
+
224
+ @dataclass
225
+ class VmExecStreamAttachRequest:
226
+ """Request body for POST /api/vm/{vm_id}/exec/stream/attach"""
227
+ #: Optional cursor to resume from (exclusive). If omitted, the full retained backlog is replayed.
228
+ cursor: Optional[Any] = None
229
+ #: Identifier of the exec stream session to reattach to.
230
+ exec_id: str
231
+ #: Start streaming after the latest retained chunk (ignores cursor).
232
+ from_latest: Optional[Any] = None
233
+
234
+ @dataclass
235
+ class VmExecRequest:
236
+ """Request body for POST /api/vm/{vm_id}/exec"""
237
+ #: Command and arguments to execute.
238
+ command: list[str]
239
+ #: Optional environment variables to set for the process.
240
+ env: Optional[Any] = None
241
+ #: Optional exec identifier for tracking.
242
+ exec_id: Optional[Any] = None
243
+ #: Optional stdin payload passed to the command.
244
+ stdin: Optional[Any] = None
245
+ #: Timeout in seconds (0 = no timeout).
246
+ timeout_secs: Optional[Any] = None
247
+ #: Optional working directory for the command.
248
+ working_dir: Optional[Any] = None
249
+
250
+ @dataclass
251
+ class NewVmsResponse:
252
+ """"""
253
+ vms: list[NewVmResponse]
254
+
255
+ @dataclass
256
+ class SetRepositoryVisibilityRequest:
257
+ """Request body for PATCH /api/v1/repositories/{repo_name}/visibility"""
258
+ #: Whether the repository should be publicly visible
259
+ is_public: bool
260
+
261
+ @dataclass
262
+ class UpdateRepoTagRequest:
263
+ """Request body for PATCH /api/v1/repositories/{repo_name}/tags/{tag_name}"""
264
+ #: Optional new commit ID to move the tag to
265
+ commit_id: Optional[Any] = None
266
+ #: Optional new description for the tag. Send `null` to clear.
267
+ description: Optional[Any] = None
268
+
269
+ @dataclass
270
+ class NewVmResponse:
271
+ """Response body for new VM requests (new_root, from_commit, branch)"""
272
+ vm_id: str
273
+
274
+ @dataclass
275
+ class ListPublicRepositoriesResponse:
276
+ """Response body for GET /api/v1/public/repositories"""
277
+ repositories: list[PublicRepositoryInfo]
278
+
279
+ @dataclass
280
+ class CreateTagRequest:
281
+ """Request body for POST /api/v1/commit_tags"""
282
+ #: The commit ID this tag should point to
283
+ commit_id: str
284
+ #: Optional description of what this tag represents
285
+ description: Optional[Any] = None
286
+ #: The name of the tag (alphanumeric, hyphens, underscores, dots, 1-64 chars)
287
+ tag_name: str
288
+
289
+ @dataclass
290
+ class FromCommitVmRequestCommitIdVariant:
291
+ """Request body for POST /api/v1/vm/from_commit"""
292
+ commit_id: str
293
+
294
+ @dataclass
295
+ class FromCommitVmRequestTagNameVariant:
296
+ """Request body for POST /api/v1/vm/from_commit"""
297
+ tag_name: str
298
+
299
+ @dataclass
300
+ class FromCommitVmRequestRefVariant:
301
+ """Request body for POST /api/v1/vm/from_commit"""
302
+ ref: str
303
+
304
+ FromCommitVmRequest = Union[FromCommitVmRequestCommitIdVariant, FromCommitVmRequestTagNameVariant, FromCommitVmRequestRefVariant]
305
+
306
+ @dataclass
307
+ class CreateRepoTagResponse:
308
+ """Response body for POST /api/v1/repositories/{repo_name}/tags"""
309
+ #: The commit ID this tag points to
310
+ commit_id: str
311
+ #: Full reference in image_name:tag format
312
+ reference: str
313
+ #: The ID of the newly created tag
314
+ tag_id: str
315
+
316
+ @dataclass
317
+ class CommitInfo:
318
+ """"""
319
+ commit_id: str
320
+ created_at: str
321
+ description: Optional[Any] = None
322
+ grandparent_commit_id: Optional[Any] = None
323
+ is_public: bool
324
+ name: str
325
+ owner_id: str
326
+ parent_vm_id: Optional[Any] = None
327
+
328
+ @dataclass
329
+ class CreateDomainRequest:
330
+ """Request body for POST /api/v1/domains"""
331
+ domain: str
332
+ vm_id: str
333
+
334
+ @dataclass
335
+ class DeleteDomainResponse:
336
+ """Response body for DELETE /api/v1/domains/{domain_id}"""
337
+ domain_id: str
338
+
339
+ @dataclass
340
+ class ListRepositoriesResponse:
341
+ """Response body for GET /api/v1/repositories"""
342
+ #: List of all repositories in the user's organization
343
+ repositories: list[RepositoryInfo]
344
+
345
+ @dataclass
346
+ class RepositoryInfo:
347
+ """Repository information returned in list and get operations"""
348
+ #: When the repository was created
349
+ created_at: str
350
+ #: Optional description
351
+ description: Optional[Any] = None
352
+ #: Whether this repository is publicly visible
353
+ is_public: bool
354
+ #: The repository name
355
+ name: str
356
+ #: The repository's unique identifier
357
+ repo_id: str
358
+
359
+ @dataclass
360
+ class UpdateTagRequest:
361
+ """Request body for PATCH /api/v1/commit_tags/{tag_name} For `description`: - Field absent from JSON → don't change the description - Field present as `null` → clear the description - Field present as `'text'` → set the description to 'text'"""
362
+ #: Optional new commit ID to move the tag to
363
+ commit_id: Optional[Any] = None
364
+ #: Optional new description for the tag. Send `null` to clear an existing description.
365
+ description: Optional[Any] = None
366
+
367
+ @dataclass
368
+ class NewRootRequest:
369
+ """"""
370
+ vm_config: VmCreateVmConfig
371
+
372
+ @dataclass
373
+ class SetEnvVarsRequest:
374
+ """Request body for PUT /env_vars — sets (upserts) one or more environment variables. # Lifecycle model Environment variables are written to `/etc/environment` inside a VM **once at boot time** via a vsock `WriteFile` request. They are **not** live-synced to running VMs. This is intentional: VMs are ephemeral (create → use → destroy/branch), so env var changes naturally take effect on the next VM. The `replace` flag exists so callers can atomically express 'I want exactly these variables and nothing else' without having to DELETE each stale key individually. Without it, the only way to remove a variable from future VMs is a separate `DELETE /env_vars/{key}` call per key."""
375
+ #: If true, delete all existing variables before writing the new set. This gives 'set exactly these vars' semantics. Default: false (upsert).
376
+ replace: Optional[bool] = None
377
+ #: Key-value pairs to set. Keys must be valid shell identifiers (`^[A-Za-z_][A-Za-z0-9_]*$`, max 256 chars). Values max 8192 chars. When `replace` is false (default): existing keys are overwritten, keys not mentioned are left untouched (upsert semantics). When `replace` is true: all existing variables are deleted first, then only the provided vars are stored (replace-all semantics).
378
+ vars: Any
379
+
380
+ @dataclass
381
+ class VM:
382
+ """"""
383
+ created_at: str
384
+ owner_id: str
385
+ state: VmState
386
+ vm_id: str
387
+
388
+ @dataclass
389
+ class VmCreateVmConfig:
390
+ """Struct representing configuration options common to all VMs"""
391
+ #: The disk size, in MiB.
392
+ fs_size_mib: Optional[Any] = None
393
+ #: The filesystem base image name. Currently, must be 'default'
394
+ image_name: Optional[Any] = None
395
+ #: The kernel name. Currently, must be 'default.bin'
396
+ kernel_name: Optional[Any] = None
397
+ #: The RAM size, in MiB.
398
+ mem_size_mib: Optional[Any] = None
399
+ #: How many vCPUs to allocate to this VM (and its children)
400
+ vcpu_count: Optional[Any] = None
401
+
402
+ @dataclass
403
+ class VmDeleteResponse:
404
+ """Response body for DELETE /api/vm/{vm_id}"""
405
+ vm_id: str
406
+
407
+ @dataclass
408
+ class CreateTagResponse:
409
+ """Response body for POST /api/v1/commit_tags"""
410
+ #: The commit ID this tag points to
411
+ commit_id: str
412
+ #: The ID of the newly created tag
413
+ tag_id: str
414
+ #: The name of the tag
415
+ tag_name: str
416
+
417
+ @dataclass
418
+ class RepoTagInfo:
419
+ """Tag information within a repository context"""
420
+ #: The commit ID this tag currently points to
421
+ commit_id: str
422
+ #: When the tag was created
423
+ created_at: str
424
+ #: Optional description
425
+ description: Optional[Any] = None
426
+ #: Full reference in image_name:tag format
427
+ reference: str
428
+ #: The tag's unique identifier
429
+ tag_id: str
430
+ #: The tag name
431
+ tag_name: str
432
+ #: When the tag was last updated
433
+ updated_at: str
434
+
435
+ @dataclass
436
+ class VmExecLogResponse:
437
+ """Response for exec log tail requests."""
438
+ #: Returned log entries.
439
+ entries: list[VmExecLogEntry]
440
+ #: True when the end of file was reached.
441
+ eof: bool
442
+ #: Next byte offset to continue from.
443
+ next_offset: int
444
+
445
+ @dataclass
446
+ class VmMetadataResponse:
447
+ """Response for GET /api/v1/vm/{vm_id}/metadata"""
448
+ created_at: str
449
+ deleted_at: Optional[Any] = None
450
+ grandparent_vm_id: Optional[Any] = None
451
+ ip: str
452
+ owner_id: str
453
+ parent_commit_id: Optional[Any] = None
454
+ state: VmState
455
+ vm_id: str
456
+
457
+ @dataclass
458
+ class VmResizeDiskRequest:
459
+ """Request body for PATCH /api/vm/{vm_id}/disk"""
460
+ #: The new disk size in MiB. Must be strictly greater than the current size.
461
+ fs_size_mib: int
462
+
463
+ @dataclass
464
+ class VmCommitResponse:
465
+ """The response body for POST /api/vm/{vm_id}/commit"""
466
+ #: The UUID of the newly-created commit
467
+ commit_id: str
468
+
469
+
470
+ # ── Params types for operations with query parameters ───────────────
471
+
472
+ @dataclass
473
+ class ResizeVmDiskParams:
474
+ """If true, return an error immediately if the VM is still booting. Default: false"""
475
+ skip_wait_boot: Optional[bool] = None
476
+
477
+ @dataclass
478
+ class CreateNewRootVmParams:
479
+ """If true, wait for the newly-created VM to finish booting before returning. Default: false."""
480
+ wait_boot: Optional[bool] = None
481
+
482
+ @dataclass
483
+ class VmLogsParams:
484
+ """Byte offset into the log file (default: 0)"""
485
+ offset: Optional[int] = None
486
+ """Maximum number of log entries to return"""
487
+ max_entries: Optional[int] = None
488
+ """Filter by 'stdout' or 'stderr'"""
489
+ stream: Optional[str] = None
490
+
491
+ @dataclass
492
+ class BranchByRefParams:
493
+ """Number of VMs to branch (optional; default 1)"""
494
+ count: Optional[int] = None
495
+
496
+ @dataclass
497
+ class BranchByTagParams:
498
+ """Number of VMs to branch (optional; default 1)"""
499
+ count: Optional[int] = None
500
+
501
+ @dataclass
502
+ class BranchVmParams:
503
+ """If true, keep VM paused after commit. Only applicable when branching a VM ID."""
504
+ keep_paused: Optional[bool] = None
505
+ """If true, immediately return an error if VM is booting instead of waiting. Only applicable when branching a VM ID."""
506
+ skip_wait_boot: Optional[bool] = None
507
+ """Number of VMs to branch (optional; default 1)"""
508
+ count: Optional[int] = None
509
+
510
+ @dataclass
511
+ class UpdateVmStateParams:
512
+ """If true, error immediately if the VM is not finished booting. Defaults to false"""
513
+ skip_wait_boot: Optional[bool] = None
514
+
515
+ @dataclass
516
+ class DeleteVmParams:
517
+ """If true, return an error immediately if the VM is still booting. Default: false"""
518
+ skip_wait_boot: Optional[bool] = None
519
+
520
+ @dataclass
521
+ class BranchByVmParams:
522
+ """If true, keep VM paused after commit"""
523
+ keep_paused: Optional[bool] = None
524
+ """If true, immediately return an error if VM is booting instead of waiting"""
525
+ skip_wait_boot: Optional[bool] = None
526
+ """Number of VMs to branch (optional; default 1)"""
527
+ count: Optional[int] = None
528
+
529
+ @dataclass
530
+ class CommitVmParams:
531
+ """If true, keep VM paused after commit"""
532
+ keep_paused: Optional[bool] = None
533
+ """If true, return an error immediately if the VM is still booting. Default: false"""
534
+ skip_wait_boot: Optional[bool] = None
535
+
536
+ @dataclass
537
+ class ListDomainsParams:
538
+ """Filter by VM ID"""
539
+ vm_id: Optional[str] = None
540
+
541
+ @dataclass
542
+ class BranchByCommitParams:
543
+ """Number of VMs to branch (optional; default 1)"""
544
+ count: Optional[int] = None
545
+
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: vers-sdk
3
+ Version: 0.1.1
4
+ Summary: SDK for Orchestrator Control Plane API
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/hdresearch/python-sdk
7
+ Project-URL: Repository, https://github.com/hdresearch/python-sdk
8
+ Keywords: vers,sdk,api,vm,orchestration
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.24
@@ -0,0 +1,8 @@
1
+ vers_sdk/__init__.py,sha256=ugY30LkuyG5tchECMvhqHSsvtvoWVncOts2VS1uOlE4,743
2
+ vers_sdk/client.py,sha256=43PcyE5-6wpysExLYh9OTIKUmVVET-xMHDVh_QvuIh4,23419
3
+ vers_sdk/errors.py,sha256=riTyZFMt5rYVA960lODYuKSzym3EfjfSrmMyLXcD2p4,3521
4
+ vers_sdk/models.py,sha256=4JwwAiWbzU7HQtmxxLkHDUKWjD45w7vAEQbdecQPrSs,17565
5
+ vers_sdk-0.1.1.dist-info/METADATA,sha256=9Av1nDxBP34fM4OsCGDX4QqVzWUJ5_-_gDKON38KsAE,345
6
+ vers_sdk-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ vers_sdk-0.1.1.dist-info/top_level.txt,sha256=hE4Q_rjEOx9L3TbgqRzMTH5kz8BsvZU4sVUqmaG8Fh0,9
8
+ vers_sdk-0.1.1.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 @@
1
+ vers_sdk