boxd 0.2.18.dev892__tar.gz → 0.2.18.dev895__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.
Files changed (43) hide show
  1. {boxd-0.2.18.dev892/src/boxd.egg-info → boxd-0.2.18.dev895}/PKG-INFO +3 -1
  2. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/pyproject.toml +6 -4
  3. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_client.py +1 -1
  4. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_credentials.py +2 -3
  5. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_generated/api_pb2_grpc.py +21 -23
  6. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_mappers.py +1 -1
  7. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_requests.py +6 -6
  8. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_urls.py +2 -2
  9. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/models.py +1 -1
  10. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/machines.py +6 -6
  11. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/orgs.py +4 -4
  12. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895/src/boxd.egg-info}/PKG-INFO +3 -1
  13. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_credentials.py +2 -2
  14. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_delete_self.py +1 -1
  15. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_namespaces.py +7 -8
  16. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_requests.py +1 -1
  17. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_transport.py +4 -4
  18. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_urls.py +1 -1
  19. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/LICENSE +0 -0
  20. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/README.md +0 -0
  21. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/setup.cfg +0 -0
  22. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/__init__.py +0 -0
  23. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_generated/__init__.py +0 -0
  24. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_generated/api_pb2.py +0 -0
  25. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_streaming.py +0 -0
  26. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_transport.py +0 -0
  27. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/_version_check.py +0 -0
  28. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/errors.py +0 -0
  29. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/__init__.py +0 -0
  30. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/account.py +0 -0
  31. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/credentials.py +0 -0
  32. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/disks.py +0 -0
  33. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/domains.py +0 -0
  34. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/snapshots.py +0 -0
  35. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/teams.py +0 -0
  36. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd/resources/vars.py +0 -0
  37. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd.egg-info/SOURCES.txt +0 -0
  38. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd.egg-info/dependency_links.txt +0 -0
  39. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd.egg-info/requires.txt +0 -0
  40. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/src/boxd.egg-info/top_level.txt +0 -0
  41. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_mappers.py +0 -0
  42. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_streaming.py +0 -0
  43. {boxd-0.2.18.dev892 → boxd-0.2.18.dev895}/tests/test_version_check.py +0 -0
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boxd
3
- Version: 0.2.18.dev892
3
+ Version: 0.2.18.dev895
4
4
  Summary: Python SDK for the boxd cloud VM platform
5
5
  Author: Azin
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://boxd.sh
8
+ Project-URL: Repository, https://github.com/azin-tech/boxd-sdk-python
9
+ Project-URL: Issues, https://github.com/azin-tech/boxd-sdk-python/issues
8
10
  Keywords: boxd,vm,microvm,sandbox,compute,grpc,sdk
9
11
  Classifier: Development Status :: 4 - Beta
10
12
  Classifier: Intended Audience :: Developers
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "boxd"
3
- version = "0.2.18.dev892"
3
+ version = "0.2.18.dev895"
4
4
  description = "Python SDK for the boxd cloud VM platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -25,8 +25,8 @@ dependencies = [
25
25
  # 1.80 needs grpcio>=1.80 / protobuf>=6.31.1 at runtime, never older).
26
26
  # Keep these in lockstep with the grpcio-tools pin below — bumping
27
27
  # the codegen toolchain without bumping these floors reintroduces
28
- # BOX-70 (resolvers install an old protobuf/grpcio that can't load
29
- # the newer gencode).
28
+ # broken installs: resolvers pick an old protobuf/grpcio that can't
29
+ # load the newer gencode.
30
30
  "grpcio>=1.80",
31
31
  "protobuf>=6.31.1",
32
32
  "pydantic>=2",
@@ -42,13 +42,15 @@ dependencies = [
42
42
 
43
43
  [project.urls]
44
44
  Homepage = "https://boxd.sh"
45
+ Repository = "https://github.com/azin-tech/boxd-sdk-python"
46
+ Issues = "https://github.com/azin-tech/boxd-sdk-python/issues"
45
47
 
46
48
  [project.optional-dependencies]
47
49
  dev = [
48
50
  # Capped at 1.80: newer grpcio-tools stamps gencode against protobuf
49
51
  # 7.x, which bumps the runtime floor above what shared environments
50
52
  # (e.g. anything depending on beta9's protobuf<7) can satisfy. See
51
- # BOX-70 — bump this only alongside the dependency floors above.
53
+ # the dependency floors above: bump this only alongside them.
52
54
  "grpcio-tools>=1.60,<=1.80",
53
55
  "pytest>=8",
54
56
  "pytest-asyncio>=0.23",
@@ -31,7 +31,7 @@ class Boxd:
31
31
 
32
32
  boxd = Boxd() # production
33
33
  boxd = Boxd(api_key="bxd_...")
34
- boxd = Boxd(base_url="https://boxd-stg.sh:9443") # any other cluster
34
+ boxd = Boxd(base_url="https://boxd.example.com:9443") # any other cluster
35
35
 
36
36
  Args:
37
37
  api_key: A `bxd_` API key. Defaults to `BOXD_API_KEY`.
@@ -28,8 +28,7 @@ import httpx
28
28
 
29
29
  from .errors import AuthenticationError, RateLimitError
30
30
 
31
- # The in-VM metadata adapter (`crates/boxd-worker/src/metadata.rs`) listens on
32
- # the VM's default gateway.
31
+ # The in-VM metadata adapter listens on the VM's default gateway.
33
32
  METADATA_PORT = 9002
34
33
  METADATA_TOKEN_PATH = "/v1/auth/token"
35
34
  # Proxy-minted, HMAC-signed identity token exposed to every guest process by
@@ -57,7 +56,7 @@ _NO_CREDENTIALS = (
57
56
  def detect_gateway() -> str | None:
58
57
  """Read the default gateway from ``/proc/net/route``.
59
58
 
60
- Same approach as `crates/boxd-sdk/src/client.rs`: the gateway column of the
59
+ Same approach as the in-VM `boxd` CLI: the gateway column of the
61
60
  default route is a hex-encoded little-endian IPv4 address.
62
61
  """
63
62
  try:
@@ -658,7 +658,7 @@ class BoxdApiServicer(object):
658
658
  raise NotImplementedError('Method not implemented!')
659
659
 
660
660
  def SetVmNetworks(self, request, context):
661
- """Replaces a VM's whole network-tag set (BOX-41) — not add/remove, the
661
+ """Replaces a VM's whole network-tag set — not add/remove, the
662
662
  caller sends the complete desired set. Networks drive east-west
663
663
  reachability within the caller's org; see CreateVmRequest.networks.
664
664
  """
@@ -667,7 +667,7 @@ class BoxdApiServicer(object):
667
667
  raise NotImplementedError('Method not implemented!')
668
668
 
669
669
  def SetVmEgressAllow(self, request, context):
670
- """Replaces a VM's egress allowlist (BOX-209): hostnames / wildcards and
670
+ """Replaces a VM's egress allowlist: hostnames / wildcards and
671
671
  public IPv4 addresses / CIDRs the machine may reach. Empty = no
672
672
  allowlist. Only reachable through the public API (no CLI, no in-VM route).
673
673
  """
@@ -676,7 +676,7 @@ class BoxdApiServicer(object):
676
676
  raise NotImplementedError('Method not implemented!')
677
677
 
678
678
  def UpdateVmLabels(self, request, context):
679
- """Edits a machine's labels (BOX-470): user-set key=value metadata for
679
+ """Edits a machine's labels: user-set key=value metadata for
680
680
  grouping and finding machines. Returns the labels after the edit.
681
681
  """
682
682
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -708,7 +708,7 @@ class BoxdApiServicer(object):
708
708
  rejects it exactly like ListVms omits it and Exec refuses it, so a
709
709
  caller polling GetVm to confirm teardown never sees a stale "running"
710
710
  (or any other live-looking) status for a machine it already destroyed
711
- (BOX-281).
711
+ .
712
712
  """
713
713
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
714
714
  context.set_details('Method not implemented!')
@@ -786,7 +786,7 @@ class BoxdApiServicer(object):
786
786
  Cancelling the call — a client-side timeout expiring, or the caller
787
787
  half-closing/cancelling the stream — does NOT just walk away from the
788
788
  remote command: it kills it. The in-VM agent runs every exec'd command
789
- in its own process group (BOX-284) and, once it notices this RPC has
789
+ in its own process group and, once it notices this RPC has
790
790
  been torn down while the command is still running, sends SIGTERM to
791
791
  that whole group, then SIGKILL after a short grace period if it hasn't
792
792
  exited. Both SDKs' `timeout` / `stream.close()` (`close()`) go through
@@ -1045,7 +1045,7 @@ class BoxdApiServicer(object):
1045
1045
  raise NotImplementedError('Method not implemented!')
1046
1046
 
1047
1047
  def CreateSnapshot(self, request, context):
1048
- """Templates are operator-managed (boxd-ctl) and used only as the fast default
1048
+ """Templates are managed by the platform and used only as the fast default
1049
1049
  path of `new` when a prepped template matches the image + size — there is no
1050
1050
  public API to create/list/delete templates or create a VM from a specific one.
1051
1051
  Snapshots: user/org point-in-time VM captures (memory + disk), replicated
@@ -1238,14 +1238,14 @@ class BoxdApiServicer(object):
1238
1238
 
1239
1239
  def GetOrgBilling(self, request, context):
1240
1240
  """The org's shared plan: aggregate quota + current usage. Any member may
1241
- view it (read-only; the operator sets the quota via boxd-ctl).
1241
+ view it (read-only; the platform sets the quota).
1242
1242
  """
1243
1243
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
1244
1244
  context.set_details('Method not implemented!')
1245
1245
  raise NotImplementedError('Method not implemented!')
1246
1246
 
1247
1247
  def GetOrgUsage(self, request, context):
1248
- """Usage and cost (BOX-290): what each machine used and cost over a window,
1248
+ """Usage and cost: what each machine used and cost over a window,
1249
1249
  and billing activity (what was actually charged). Org owners and admins
1250
1250
  only; API keys fenced to the org work, tokens minted inside a VM don't.
1251
1251
  """
@@ -1266,8 +1266,7 @@ class BoxdApiServicer(object):
1266
1266
  raise NotImplementedError('Method not implemented!')
1267
1267
 
1268
1268
  def SetBackupSchedule(self, request, context):
1269
- """Disaster recovery: scheduled VM disk backups to object storage
1270
- (plans/2026-08-10-dr-vm-backups.md). Off by default — SetBackupSchedule
1269
+ """Disaster recovery: scheduled VM disk backups to object storage. Off by default — SetBackupSchedule
1271
1270
  is refused server-side if the cluster has no [backups] object store
1272
1271
  configured.
1273
1272
  """
@@ -1294,7 +1293,7 @@ class BoxdApiServicer(object):
1294
1293
  raise NotImplementedError('Method not implemented!')
1295
1294
 
1296
1295
  def TriggerBackup(self, request, context):
1297
- """On-demand "back up now" (BOX-148) — captures immediately, independent of
1296
+ """On-demand "back up now" — captures immediately, independent of
1298
1297
  (and without disturbing) any schedule. Retention resolves from the VM's
1299
1298
  schedule, else the org default, else the platform default.
1300
1299
  """
@@ -1303,7 +1302,7 @@ class BoxdApiServicer(object):
1303
1302
  raise NotImplementedError('Method not implemented!')
1304
1303
 
1305
1304
  def DeleteBackup(self, request, context):
1306
- """Manual deletion of a single backup by id (BOX-148). Refused for a backup
1305
+ """Manual deletion of a single backup by id. Refused for a backup
1307
1306
  still being captured or one a newer dedup pointer depends on.
1308
1307
  """
1309
1308
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -1321,8 +1320,7 @@ class BoxdApiServicer(object):
1321
1320
  def SetOrgBackupsDefault(self, request, context):
1322
1321
  """The org's default backup schedule for NEW VMs (existing VMs are
1323
1322
  unchanged; a fork always copies its own source's schedule instead of
1324
- this default — see plans/2026-08-10-dr-vm-backups.md Q3). Self-service
1325
- counterpart to the operator-only `boxd-ctl org set-backups-default`.
1323
+ this default). Self-service: org admins set it themselves.
1326
1324
  Set/Clear require org admin; Get is readable by any member — same shape
1327
1325
  as SetOrgDomain/GetOrgDomain above.
1328
1326
  """
@@ -1337,7 +1335,7 @@ class BoxdApiServicer(object):
1337
1335
  raise NotImplementedError('Method not implemented!')
1338
1336
 
1339
1337
  def SetOrgMachineDefaults(self, request, context):
1340
- """The org's default machine SIZE (BOX-154): what a machine gets at CREATE
1338
+ """The org's default machine SIZE: what a machine gets at CREATE
1341
1339
  when the request names no size. Distinct from the quota: the quota stays
1342
1340
  the operator-owned ceiling and the per-request maximum; this is the
1343
1341
  org-admin-owned preference below it (clamped to the ceiling). Every
@@ -1356,12 +1354,12 @@ class BoxdApiServicer(object):
1356
1354
  raise NotImplementedError('Method not implemented!')
1357
1355
 
1358
1356
  def SetOrgIdleIgnoreOutboundDefault(self, request, context):
1359
- """The org's default for idle.ignore-outbound (BOX-532): whether a NEW
1357
+ """The org's default for idle.ignore-outbound: whether a NEW
1360
1358
  machine in the org starts with its idle clock ignoring outbound traffic
1361
1359
  when its create request does not say. An org without one follows the
1362
1360
  platform's setting. Setting it also sets it on the existing machines and
1363
1361
  snapshots the caller may manage, unless the Set asks for
1364
- `new_machines_only` (BOX-547); a fork or a restore keeps its source's
1362
+ `new_machines_only`; a fork or a restore keeps its source's
1365
1363
  value either way.
1366
1364
  Set/Clear require org admin; Get is readable by any member.
1367
1365
  """
@@ -1376,7 +1374,7 @@ class BoxdApiServicer(object):
1376
1374
  raise NotImplementedError('Method not implemented!')
1377
1375
 
1378
1376
  def SetOrgBotProtectionDefault(self, request, context):
1379
- """The org's default bot protection mode (BOX-523): what a NEW machine in
1377
+ """The org's default bot protection mode: what a NEW machine in
1380
1378
  the org starts with. An org without one follows the cluster's setting
1381
1379
  for new machines. Every machine keeps the mode it has, so changing this
1382
1380
  only affects machines created afterwards; a fork or a restore keeps its
@@ -1394,8 +1392,8 @@ class BoxdApiServicer(object):
1394
1392
  raise NotImplementedError('Method not implemented!')
1395
1393
 
1396
1394
  def CreateOrg(self, request, context):
1397
- """Org creation, roster, and membership (self-serve, mirrors the console's
1398
- org.rs exactly — same Raft commands, same business rules). Any signed-in
1395
+ """Org creation, roster, and membership (self-serve, same business rules as
1396
+ the console). Any signed-in
1399
1397
  user may create an org (becomes its owner+admin); invite/revoke/remove/
1400
1398
  set-role require org admin.
1401
1399
 
@@ -1438,7 +1436,7 @@ class BoxdApiServicer(object):
1438
1436
  raise NotImplementedError('Method not implemented!')
1439
1437
 
1440
1438
  def RemoveOrgMember(self, request, context):
1441
- """Refused for an org owner/admin (operator-only via boxd-ctl) or a member
1439
+ """Refused for an org owner/admin (only the platform can remove those) or a member
1442
1440
  who still owns VMs billed to this org — same guardrails as the console.
1443
1441
  """
1444
1442
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -1447,7 +1445,7 @@ class BoxdApiServicer(object):
1447
1445
 
1448
1446
  def SetOrgMemberRole(self, request, context):
1449
1447
  """Promote a member to admin, or demote an admin back to member. Refused for
1450
- the owner (use boxd-ctl set-owner to transfer ownership) and for a target
1448
+ the owner (ownership is transferred by the platform) and for a target
1451
1449
  that isn't a member. Demoting ANOTHER admin is owner-only (an admin may
1452
1450
  only step down themselves) — otherwise any admin could demote a peer and
1453
1451
  then RemoveOrgMember them, defeating that RPC's admin guard. Promoting to
@@ -1458,7 +1456,7 @@ class BoxdApiServicer(object):
1458
1456
  raise NotImplementedError('Method not implemented!')
1459
1457
 
1460
1458
  def ListTeams(self, request, context):
1461
- """Teams (BOX-104/BOX-106): org-scoped sub-grouping of members, each with an
1459
+ """Teams: org-scoped sub-grouping of members, each with an
1462
1460
  optional default snapshot new team machines boot from. Creating a team
1463
1461
  requires org admin; every other write requires team-admin (inherited by
1464
1462
  every org admin, or held directly via a team_members.role='admin' row).
@@ -556,7 +556,7 @@ def api_key(msg) -> ApiKey:
556
556
  org=msg.org,
557
557
  kind=msg.kind,
558
558
  created_by=msg.created_by,
559
- # A server from before BOX-554 lists only the caller's own keys and
559
+ # An older server lists only the caller's own keys and
560
560
  # sends neither field.
561
561
  mine=msg.mine or not msg.created_by,
562
562
  )
@@ -77,7 +77,7 @@ def vm_config(
77
77
  srf.auto_suspend_timeout_secs = auto_suspend_timeout
78
78
  config.srf.CopyFrom(srf)
79
79
  if idle_ignore_outbound is not None:
80
- # `optional` on the wire (BOX-532): unset means "not requested", which
80
+ # `optional` on the wire: unset means "not requested", which
81
81
  # is not the same as False. A new machine then takes its org's default
82
82
  # and a fork or restore keeps the source's value. Only touch it when
83
83
  # asked, and send an explicit False as False.
@@ -420,9 +420,9 @@ def set_org_idle_ignore_outbound_default(
420
420
  `enabled` is `optional` on the wire, and absence is what clears the
421
421
  default, so None must stay unset rather than become False.
422
422
 
423
- Existing machines change unless `new_machines_only` (BOX-547). The wire
424
- also carries `apply_existing` as its opposite, so a server from before
425
- BOX-547, which only reads that field, does the same. `apply_existing` is
423
+ Existing machines change unless `new_machines_only`. The wire
424
+ also carries `apply_existing` as its opposite, so an older server,
425
+ which only reads that field, does the same. `apply_existing` is
426
426
  accepted from callers written against 0.2.16 and changes nothing, but
427
427
  asking for it together with `new_machines_only` is a contradiction.
428
428
  """
@@ -434,7 +434,7 @@ def set_org_idle_ignore_outbound_default(
434
434
  request = api_pb2.SetOrgIdleIgnoreOutboundDefaultRequest(
435
435
  org_id=org or "",
436
436
  new_machines_only=new_machines_only,
437
- # A clear has nothing to apply, and a pre-BOX-547 server refuses a
437
+ # A clear has nothing to apply, and an older server refuses a
438
438
  # clear that asks for it.
439
439
  apply_existing=enabled is not None and not new_machines_only,
440
440
  )
@@ -458,7 +458,7 @@ def unix_seconds(t: datetime | int | float | None) -> int:
458
458
 
459
459
 
460
460
  def set_delete_after(machine_id: str, seconds: int, cron: str | None):
461
- """`SetDeleteAfter` for a duration or a cron expression (BOX-469). The
461
+ """`SetDeleteAfter` for a duration or a cron expression. The
462
462
  server rejects both at once; here we catch it before the round trip."""
463
463
  if seconds and cron:
464
464
  raise ValueError("pass seconds or cron, not both")
@@ -13,7 +13,7 @@ from typing import NamedTuple
13
13
 
14
14
  # gRPC front door, over TLS. The proxy still accepts plain HTTP/2 on the same
15
15
  # port for older clients, so an explicit http:// base_url keeps working against
16
- # any cluster (see `crates/boxd-proxy/src/api_transport.rs`).
16
+ # any cluster.
17
17
  DEFAULT_BASE_URL = "https://boxd.sh:9443"
18
18
 
19
19
  EXCHANGE_PATH = "/api/v1/auth/token"
@@ -40,7 +40,7 @@ def resolve_base_url(explicit: str | None) -> str | None:
40
40
  def console_base_url(base_url: str) -> str:
41
41
  """Map a gRPC base URL to the web-console origin that fronts `/api/v1/...`.
42
42
 
43
- Ported from `console_base_url` in `crates/boxd-cli/src/commands.rs`, with one
43
+ Same mapping as the `boxd` CLI uses, with one
44
44
  deliberate difference: the port is dropped. The CLI's input is a bare
45
45
  `https://boxd.sh`, but the SDK's default carries the gRPC port
46
46
  (`:9443`) — preserving it would aim the exchange at the gRPC listener. A
@@ -77,7 +77,7 @@ class MachineAccess(BaseModel):
77
77
  the client has no endpoint to derive the console origin from. Opening it
78
78
  needs a signed-in browser; the machine is woken if it is asleep."""
79
79
  custom_domains: list[CustomDomain]
80
- """Per-VM custom domains bound to this machine (BOX-30), e.g.
80
+ """Per-VM custom domains bound to this machine, e.g.
81
81
  `app.example.com`. Distinct from `domain` above, which is the org's
82
82
  wildcard domain or the cluster zone. Empty if none are bound."""
83
83
 
@@ -42,7 +42,7 @@ from ..models import (
42
42
  )
43
43
  from . import Namespace
44
44
 
45
- # ── delete_self (BOX-468) ────────────────────────────────────────────
45
+ # ── delete_self ────────────────────────────────────────────
46
46
 
47
47
  _SELF_DESTROY_PATH = "/v1/self/destroy"
48
48
  _SELF_DELETE_TIMEOUT_SECS = 30.0
@@ -209,7 +209,7 @@ def _exec_init(
209
209
  # Same reasoning as `env`: no cwd field on ExecChunk, so `cd` ahead of
210
210
  # the command through the same shell. `&&` means a `cwd` that doesn't
211
211
  # exist fails fast with `cd`'s own error instead of silently running
212
- # in the default directory (BOX-292).
212
+ # in the default directory.
213
213
  line = f"cd {shlex.quote(cwd)} && {line}"
214
214
  return req.exec_chunk(
215
215
  machine_id=machine_id, command=line, tty=tty, cols=cols, rows=rows
@@ -378,7 +378,7 @@ class Machines(Namespace):
378
378
  created = self._transport.call(method, request)
379
379
  self._apply_delete_after(created.vm_id, delete_after, delete_after_cron)
380
380
  # Non-zero only for a machine made from a snapshot, a team's default
381
- # one included (BOX-557).
381
+ # one included.
382
382
  return _with_paused_automations(
383
383
  self._reread(created), created.paused_automations, from_snapshot=True
384
384
  )
@@ -400,7 +400,7 @@ class Machines(Namespace):
400
400
  Raises `NotFoundError` for a destroyed machine's id/name, same as
401
401
  `delete()` followed by `list()` omitting it — polling `get(id)`
402
402
  after `delete(id)` returns is a reliable way to confirm teardown
403
- completed (BOX-281).
403
+ completed.
404
404
  """
405
405
  return m.machine(
406
406
  self._transport.call("GetVm", api_pb2.GetVmRequest(vm_id=machine_id)),
@@ -476,7 +476,7 @@ class Machines(Namespace):
476
476
 
477
477
  def delete_self(self, *, confirm: str) -> SelfDeleteResult:
478
478
  """Delete **the machine this code is running on** — call it inside a
479
- boxd machine when its task is done (BOX-468).
479
+ boxd machine when its task is done.
480
480
 
481
481
  This is the SDK twin of ``boxd machine remove --self``, not a plain
482
482
  :meth:`delete` of your own id: the same server checks apply, and the
@@ -1291,7 +1291,7 @@ class AsyncMachines(Namespace):
1291
1291
  Raises `NotFoundError` for a destroyed machine's id/name, same as
1292
1292
  `delete()` followed by `list()` omitting it — polling `get(id)`
1293
1293
  after `delete(id)` returns is a reliable way to confirm teardown
1294
- completed (BOX-281).
1294
+ completed.
1295
1295
  """
1296
1296
  return m.machine(
1297
1297
  await self._transport.call("GetVm", api_pb2.GetVmRequest(vm_id=machine_id)),
@@ -97,7 +97,7 @@ class Orgs(Namespace):
97
97
  def remove_member(self, user_id: str, org: str = "") -> None:
98
98
  """Remove a plain member from the org. Requires org admin.
99
99
 
100
- Refused for an org owner/admin (operator-only, via boxd-ctl) or for a
100
+ Refused for an org owner/admin (only the platform can remove those) or for a
101
101
  member who still owns machines billed to this org — move them to
102
102
  personal billing or destroy them first.
103
103
  """
@@ -111,7 +111,7 @@ class Orgs(Namespace):
111
111
 
112
112
  Requires org admin. Demoting another admin is owner-only (you can
113
113
  always demote yourself). Refused for the org owner, whose ownership
114
- is transferred with boxd-ctl instead.
114
+ is transferred by the platform instead.
115
115
  """
116
116
  self._transport.call(
117
117
  "SetOrgMemberRole",
@@ -462,7 +462,7 @@ class AsyncOrgs(Namespace):
462
462
  async def remove_member(self, user_id: str, org: str = "") -> None:
463
463
  """Remove a plain member from the org. Requires org admin.
464
464
 
465
- Refused for an org owner/admin (operator-only, via boxd-ctl) or for a
465
+ Refused for an org owner/admin (only the platform can remove those) or for a
466
466
  member who still owns machines billed to this org — move them to
467
467
  personal billing or destroy them first.
468
468
  """
@@ -476,7 +476,7 @@ class AsyncOrgs(Namespace):
476
476
 
477
477
  Requires org admin. Demoting another admin is owner-only (you can
478
478
  always demote yourself). Refused for the org owner, whose ownership
479
- is transferred with boxd-ctl instead.
479
+ is transferred by the platform instead.
480
480
  """
481
481
  await self._transport.call(
482
482
  "SetOrgMemberRole",
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boxd
3
- Version: 0.2.18.dev892
3
+ Version: 0.2.18.dev895
4
4
  Summary: Python SDK for the boxd cloud VM platform
5
5
  Author: Azin
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://boxd.sh
8
+ Project-URL: Repository, https://github.com/azin-tech/boxd-sdk-python
9
+ Project-URL: Issues, https://github.com/azin-tech/boxd-sdk-python/issues
8
10
  Keywords: boxd,vm,microvm,sandbox,compute,grpc,sdk
9
11
  Classifier: Development Status :: 4 - Beta
10
12
  Classifier: Intended Audience :: Developers
@@ -137,7 +137,7 @@ def test_both_signals_mint_a_token_and_report_the_cluster(monkeypatch):
137
137
  return {
138
138
  "token": "vm-jwt",
139
139
  "expires_at": time.time() + 900,
140
- "api_url": "http://boxd-stg.sh:9443",
140
+ "api_url": "http://staging.example.com:9443",
141
141
  }
142
142
 
143
143
  class FakeClient:
@@ -161,7 +161,7 @@ def test_both_signals_mint_a_token_and_report_the_cluster(monkeypatch):
161
161
  assert c.token() == "vm-jwt"
162
162
  assert seen["url"] == "http://10.42.0.1:9002/v1/auth/token"
163
163
  # An unconfigured client learns its cluster from the machine.
164
- assert c.discovered_base_url == "http://boxd-stg.sh:9443"
164
+ assert c.discovered_base_url == "http://staging.example.com:9443"
165
165
 
166
166
 
167
167
 
@@ -1,4 +1,4 @@
1
- """`machines.delete_self()` (BOX-468) against a stand-in metadata adapter."""
1
+ """`machines.delete_self()` against a stand-in metadata adapter."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -173,7 +173,7 @@ def _forked(name):
173
173
 
174
174
 
175
175
  def test_create_sends_idle_ignore_outbound_only_when_asked():
176
- # `optional` on the wire (BOX-532): unset lets a new machine take its
176
+ # `optional` on the wire: unset lets a new machine take its
177
177
  # org's default, so a defaulted False must never go out.
178
178
  transport = FakeTransport(
179
179
  [*_created("a"), *_created("b"), *_created("c"), *_created("d")]
@@ -421,7 +421,7 @@ async def test_async_fork_reports_paused_automations_too():
421
421
 
422
422
 
423
423
  def test_create_from_snapshot_reports_the_automations_it_came_with_paused():
424
- # BOX-557: a machine from a snapshot is born with its automations paused,
424
+ # A machine from a snapshot is born with its automations paused,
425
425
  # like a fork.
426
426
  transport = FakeTransport(
427
427
  [
@@ -513,9 +513,8 @@ def test_exec_folds_the_stream_into_one_result():
513
513
 
514
514
 
515
515
  def test_exec_default_encoding_corrupts_invalid_utf8():
516
- # BOX-280: the default string mode is lossy — invalid bytes become
517
- # U+FFFD and the byte length changes. Documents the failure this issue
518
- # is about, alongside the encoding="buffer" escape hatch below.
516
+ # The default string mode is lossy — invalid bytes become U+FFFD and
517
+ # the byte length changes. Documents that failure, alongside the encoding="buffer" escape hatch below.
519
518
  transport = FakeTransport([[api_pb2.ExecChunk(data=b"\x00\x01\xff\xfe", exit_code=0)]])
520
519
  result = Machines(transport).exec("alpha", "printf '\\000\\001\\377\\376'")
521
520
  assert result.stdout == "\x00\x01��"
@@ -969,7 +968,7 @@ def test_orgs_set_get_clear_bot_protection_default():
969
968
  )
970
969
  ns = Orgs(transport)
971
970
 
972
- # By default existing machines are changed too (BOX-541).
971
+ # By default existing machines are changed too.
973
972
  applied = ns.set_bot_protection_default("o1", mode="sleep_only")
974
973
  method, request = transport.calls[0]
975
974
  assert method == "SetOrgBotProtectionDefault"
@@ -1037,7 +1036,7 @@ def test_orgs_set_get_clear_idle_ignore_outbound_default():
1037
1036
  )
1038
1037
  ns = Orgs(transport)
1039
1038
 
1040
- # BOX-547: existing machines change by default. `apply_existing` rides
1039
+ # Existing machines change by default. `apply_existing` rides
1041
1040
  # along as the opposite of `new_machines_only`, so a server that predates
1042
1041
  # `new_machines_only` does the same.
1043
1042
  applied = ns.set_idle_ignore_outbound_default("o1", enabled=True)
@@ -1094,7 +1093,7 @@ def test_orgs_set_get_clear_idle_ignore_outbound_default():
1094
1093
  assert (unset.is_set, unset.enabled, unset.platform_enabled) == (False, True, True)
1095
1094
 
1096
1095
  # Clearing is the same RPC with `enabled` ABSENT, not False, and asks for
1097
- # nothing to be applied (a server from before BOX-547 refuses that).
1096
+ # nothing to be applied (an older server refuses that).
1098
1097
  ns.clear_idle_ignore_outbound_default("o1")
1099
1098
  method, request = transport.calls[5]
1100
1099
  assert method == "SetOrgIdleIgnoreOutboundDefault"
@@ -69,7 +69,7 @@ def _vm_config_on_the_wire(config):
69
69
 
70
70
 
71
71
  def test_idle_ignore_outbound_left_unset_when_not_given():
72
- # `optional` on the wire (BOX-532): unset lets a new machine take its
72
+ # `optional` on the wire: unset lets a new machine take its
73
73
  # org's default, so a defaulted False must never go out.
74
74
  config = req.vm_config(vcpu=2)
75
75
  assert not config.HasField("idle_ignore_outbound")
@@ -169,7 +169,7 @@ def test_bearer_token_and_version_header_are_sent():
169
169
 
170
170
  def test_explicit_base_url_wins_over_the_discovered_one():
171
171
  credentials = FakeCredentials()
172
- credentials.discovered_base_url = "http://boxd-stg.sh:9443"
172
+ credentials.discovered_base_url = "http://staging.example.com:9443"
173
173
  t = SyncTransport(
174
174
  base_url="http://explicit:9443",
175
175
  credentials=credentials,
@@ -181,11 +181,11 @@ def test_explicit_base_url_wins_over_the_discovered_one():
181
181
 
182
182
  def test_discovered_base_url_is_used_when_none_was_given():
183
183
  credentials = FakeCredentials()
184
- credentials.discovered_base_url = "http://boxd-stg.sh:9443"
184
+ credentials.discovered_base_url = "http://staging.example.com:9443"
185
185
  t = SyncTransport(
186
186
  base_url=None, credentials=credentials, timeout=1.0, max_retries=2
187
187
  )
188
- assert t.base_url == "http://boxd-stg.sh:9443"
188
+ assert t.base_url == "http://staging.example.com:9443"
189
189
 
190
190
 
191
191
  def test_production_is_the_last_resort():
@@ -298,7 +298,7 @@ async def _no_sleep(_seconds):
298
298
  return None
299
299
 
300
300
 
301
- # --- stream wrapping (BOX-152) ---
301
+ # --- stream wrapping ---
302
302
 
303
303
 
304
304
  class FakeStreamCall:
@@ -56,7 +56,7 @@ def test_empty_string_is_not_a_choice(monkeypatch):
56
56
  [
57
57
  ("http://boxd.sh:9443", "https://app.boxd.sh"),
58
58
  ("https://boxd.sh", "https://app.boxd.sh"),
59
- ("http://boxd-stg.sh:9443", "https://app.boxd-stg.sh"),
59
+ ("http://staging.example.com:9443", "https://app.staging.example.com"),
60
60
  ("http://boxd.sh:9443", "https://app.boxd.sh"),
61
61
  # A path is stripped along with the port.
62
62
  ("http://boxd.sh:9443/grpc", "https://app.boxd.sh"),
File without changes
File without changes
File without changes