boxd 0.1.2.dev13__tar.gz → 0.1.3__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 (42) hide show
  1. {boxd-0.1.2.dev13/src/boxd.egg-info → boxd-0.1.3}/PKG-INFO +1 -1
  2. {boxd-0.1.2.dev13 → boxd-0.1.3}/pyproject.toml +1 -1
  3. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_sync.py +10 -0
  4. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/box.py +46 -0
  5. {boxd-0.1.2.dev13 → boxd-0.1.3/src/boxd.egg-info}/PKG-INFO +1 -1
  6. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_e2e.py +4 -8
  7. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_e2e_v2.py +3 -7
  8. {boxd-0.1.2.dev13 → boxd-0.1.3}/LICENSE +0 -0
  9. {boxd-0.1.2.dev13 → boxd-0.1.3}/README.md +0 -0
  10. {boxd-0.1.2.dev13 → boxd-0.1.3}/setup.cfg +0 -0
  11. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/__init__.py +0 -0
  12. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_generated/__init__.py +0 -0
  13. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_generated/api_pb2.py +0 -0
  14. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_generated/api_pb2_grpc.py +0 -0
  15. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_utils.py +0 -0
  16. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_version_check.py +0 -0
  17. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/aio.py +0 -0
  18. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/auth.py +0 -0
  19. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/billing.py +0 -0
  20. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/boxes.py +0 -0
  21. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/client.py +0 -0
  22. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/disks.py +0 -0
  23. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/domains.py +0 -0
  24. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/errors.py +0 -0
  25. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/exec.py +0 -0
  26. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/networks.py +0 -0
  27. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/templates.py +0 -0
  28. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/tokens.py +0 -0
  29. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/types.py +0 -0
  30. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/SOURCES.txt +0 -0
  31. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/dependency_links.txt +0 -0
  32. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/requires.txt +0 -0
  33. {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/top_level.txt +0 -0
  34. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_auth.py +0 -0
  35. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_boxes.py +0 -0
  36. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_exec.py +0 -0
  37. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_files.py +0 -0
  38. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_lifecycle.py +0 -0
  39. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_proxies.py +0 -0
  40. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_utils.py +0 -0
  41. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_v2.py +0 -0
  42. {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boxd
3
- Version: 0.1.2.dev13
3
+ Version: 0.1.3
4
4
  Summary: Python SDK for the boxd cloud VM platform
5
5
  Author: Azin
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "boxd"
3
- version = "0.1.2.dev13"
3
+ version = "0.1.3"
4
4
  description = "Python SDK for the boxd cloud VM platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -88,6 +88,16 @@ class Box(_SyncBase):
88
88
  """Resume this VM from suspension."""
89
89
  return self._run(self._async.resume())
90
90
 
91
+ def wait_until_ready(self, timeout: float = 90.0, poll_interval: float = 0.5) -> None:
92
+ """Block until the VM is fully ready (status='running' + exec works).
93
+
94
+ Call this after ``create()`` or ``fork()`` before any operation that
95
+ depends on the VM being fully usable — especially before another
96
+ ``fork()``, which gates fast-path live-fork on the source VM's Raft
97
+ status. See :meth:`boxd.box.Box.wait_until_ready` for details.
98
+ """
99
+ self._run(self._async.wait_until_ready(timeout=timeout, poll_interval=poll_interval))
100
+
91
101
  # ── Exec ─────────────────────────────────────────────────────
92
102
 
93
103
  def exec(
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import time
6
7
  from dataclasses import dataclass, field
7
8
  from pathlib import Path
8
9
  from typing import TYPE_CHECKING
@@ -15,6 +16,8 @@ from .errors import from_grpc_error
15
16
  from .exec import ExecProcess, ExecResult, StreamReader, StreamWriter
16
17
  from .types import Proxy, ResumeResult, SuspendResult
17
18
 
19
+ _TERMINAL_STATES = frozenset({"destroyed", "failed", "stopped"})
20
+
18
21
  if TYPE_CHECKING:
19
22
  from .client import Compute
20
23
 
@@ -74,6 +77,49 @@ class Box(GrpcCaller):
74
77
  self.status = "running"
75
78
  return ResumeResult(resume_us=resp.resume_us)
76
79
 
80
+ async def wait_until_ready(
81
+ self,
82
+ timeout: float = 90.0,
83
+ poll_interval: float = 0.5,
84
+ ) -> None:
85
+ """Block until the VM is fully ready to use.
86
+
87
+ Returns once the VM's status flips to ``running`` AND an ``exec``
88
+ round-trip succeeds.
89
+
90
+ ``create()`` and ``fork()`` return as soon as the server has scheduled
91
+ the VM — at that point ``exec`` may already work, but the VM's status
92
+ in Raft can still be ``pending`` for ~1s. Operations that gate on the
93
+ authoritative state (notably another ``fork()``) will see the stale
94
+ status and fall back to a cold-boot path that drops the source's
95
+ in-memory state (e.g. tmpfs). Always call ``wait_until_ready()``
96
+ before acting on a fresh Box.
97
+ """
98
+ deadline = time.monotonic() + timeout
99
+
100
+ while time.monotonic() < deadline:
101
+ fresh = await self._client.box.get(self.id)
102
+ self.status = fresh.status
103
+ if fresh.status == "running":
104
+ break
105
+ if fresh.status in _TERMINAL_STATES:
106
+ raise RuntimeError(
107
+ f"VM {self.name} entered terminal state '{fresh.status}' while waiting for ready"
108
+ )
109
+ await asyncio.sleep(poll_interval)
110
+ else:
111
+ raise TimeoutError(f"VM {self.name} did not reach 'running' within {timeout}s")
112
+
113
+ while time.monotonic() < deadline:
114
+ try:
115
+ await self.exec("true")
116
+ return
117
+ except Exception:
118
+ await asyncio.sleep(poll_interval)
119
+ raise TimeoutError(
120
+ f"VM {self.name} reached 'running' but exec did not succeed within {timeout}s"
121
+ )
122
+
77
123
  # ── Exec ──────────────────────────────────────────────────────
78
124
 
79
125
  async def exec(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boxd
3
- Version: 0.1.2.dev13
3
+ Version: 0.1.3
4
4
  Summary: Python SDK for the boxd cloud VM platform
5
5
  Author: Azin
6
6
  License-Expression: MIT
@@ -13,14 +13,10 @@ from boxd.errors import BoxdError
13
13
 
14
14
 
15
15
  def _wait_ready(box, timeout=90):
16
- """Poll until the VM accepts exec commands."""
17
- for i in range(timeout):
18
- try:
19
- box.exec("true")
20
- return i
21
- except Exception:
22
- time.sleep(1)
23
- pytest.fail(f"VM {box.name} not ready after {timeout}s")
16
+ """Wait until the VM is fully ready (status=running + exec works)."""
17
+ t0 = time.monotonic()
18
+ box.wait_until_ready(timeout=float(timeout))
19
+ return int(time.monotonic() - t0)
24
20
 
25
21
 
26
22
  pytestmark = pytest.mark.e2e
@@ -8,13 +8,9 @@ pytestmark = pytest.mark.e2e
8
8
 
9
9
 
10
10
  def _wait_ready(box, timeout=90):
11
- for i in range(timeout):
12
- try:
13
- box.exec("true")
14
- return i
15
- except Exception:
16
- time.sleep(1)
17
- pytest.fail(f"VM {box.name} not ready after {timeout}s")
11
+ t0 = time.monotonic()
12
+ box.wait_until_ready(timeout=float(timeout))
13
+ return int(time.monotonic() - t0)
18
14
 
19
15
 
20
16
  # ── Domains ─────────────────────────────────────────────────────
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes