runcloud-sdk 0.8.1__tar.gz → 0.8.2__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.
- {runcloud_sdk-0.8.1/runcloud_sdk.egg-info → runcloud_sdk-0.8.2}/PKG-INFO +1 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/pyproject.toml +1 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/client.py +12 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2/runcloud_sdk.egg-info}/PKG-INFO +1 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/tests/test_sdk.py +26 -3
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/LICENSE +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/README.md +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/__init__.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/__init__.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/blaxel.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/daytona.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/e2b.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/modal.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/sprites.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud/compat/vercel.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud_sdk.egg-info/SOURCES.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud_sdk.egg-info/dependency_links.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud_sdk.egg-info/requires.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/runcloud_sdk.egg-info/top_level.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.2}/setup.cfg +0 -0
|
@@ -20,7 +20,7 @@ from websockets.sync.client import connect as websocket_connect
|
|
|
20
20
|
Transport = Callable[[str, str, Any], Any]
|
|
21
21
|
StreamTransport = Callable[[str, dict[str, Any]], Any]
|
|
22
22
|
DEFAULT_API_URL = "https://api.run.cloud"
|
|
23
|
-
USER_AGENT = "runcloud-python/0.8.
|
|
23
|
+
USER_AGENT = "runcloud-python/0.8.2"
|
|
24
24
|
FILE_CHUNK_BYTES = 512 * 1024
|
|
25
25
|
|
|
26
26
|
|
|
@@ -518,6 +518,17 @@ class Sandbox:
|
|
|
518
518
|
)
|
|
519
519
|
if payload.get("error"):
|
|
520
520
|
raise RunCloudError(0, str(payload["error"]))
|
|
521
|
+
expected_next_offset = offset + len(chunk)
|
|
522
|
+
next_offset = payload.get("next_offset")
|
|
523
|
+
if chunk and (
|
|
524
|
+
isinstance(next_offset, bool)
|
|
525
|
+
or not isinstance(next_offset, int)
|
|
526
|
+
or next_offset != expected_next_offset
|
|
527
|
+
):
|
|
528
|
+
raise RunCloudError(
|
|
529
|
+
0,
|
|
530
|
+
"file write chunk was not acknowledged; the file may be incomplete",
|
|
531
|
+
)
|
|
521
532
|
|
|
522
533
|
def pause(self) -> "Sandbox":
|
|
523
534
|
self._data = self._client.request(
|
|
@@ -9,7 +9,7 @@ import unittest
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from unittest.mock import patch
|
|
11
11
|
|
|
12
|
-
from runcloud import Client, UnsupportedCompatibilityFeatureError
|
|
12
|
+
from runcloud import Client, RunCloudError, UnsupportedCompatibilityFeatureError
|
|
13
13
|
from runcloud.compat import modal
|
|
14
14
|
from runcloud.compat.blaxel import SandboxInstance, SyncSandboxInstance
|
|
15
15
|
from runcloud.compat.daytona import Daytona
|
|
@@ -30,6 +30,9 @@ class Recorder:
|
|
|
30
30
|
if path.endswith("/exec"):
|
|
31
31
|
return {"exit_code": 0, "stdout": "ok\n", "stderr": ""}
|
|
32
32
|
if path.endswith("/fs"):
|
|
33
|
+
if body["op"] == "write":
|
|
34
|
+
chunk = base64.b64decode(body["content"])
|
|
35
|
+
return {"next_offset": body["offset"] + len(chunk)}
|
|
33
36
|
return {"content": base64.b64encode(b"artifact").decode()}
|
|
34
37
|
if path == "/run-cloud/sandboxes" and method == "GET":
|
|
35
38
|
return {"items": [SANDBOX]}
|
|
@@ -199,6 +202,26 @@ class SDKTests(unittest.TestCase):
|
|
|
199
202
|
],
|
|
200
203
|
)
|
|
201
204
|
|
|
205
|
+
def test_write_file_rejects_an_unacknowledged_continuation_chunk(self):
|
|
206
|
+
write_count = 0
|
|
207
|
+
|
|
208
|
+
def transport(method, path, body):
|
|
209
|
+
nonlocal write_count
|
|
210
|
+
if method == "GET":
|
|
211
|
+
return SANDBOX
|
|
212
|
+
self.assertEqual(path, "/run-cloud/sandboxes/sb_python/fs")
|
|
213
|
+
write_count += 1
|
|
214
|
+
if write_count == 1:
|
|
215
|
+
return {"next_offset": 512 * 1024}
|
|
216
|
+
return {}
|
|
217
|
+
|
|
218
|
+
box = Client(api_key="rc_live_test", transport=transport).get("sb_python")
|
|
219
|
+
|
|
220
|
+
with self.assertRaisesRegex(RunCloudError, "file write chunk was not acknowledged"):
|
|
221
|
+
box.write_file("/tmp/truncated.bin", b"a" * (512 * 1024) + b"tail")
|
|
222
|
+
|
|
223
|
+
self.assertEqual(write_count, 2)
|
|
224
|
+
|
|
202
225
|
def test_compatibility_adapters_share_the_public_client(self):
|
|
203
226
|
app = modal.App.lookup("compat", client=self.client)
|
|
204
227
|
modal_sandbox = modal.Sandbox.create(app=app)
|
|
@@ -315,7 +338,7 @@ class SDKTests(unittest.TestCase):
|
|
|
315
338
|
Client(api_key="rc_live_test").request("GET", "/run-cloud/account")
|
|
316
339
|
|
|
317
340
|
request = urlopen.call_args.args[0]
|
|
318
|
-
self.assertEqual(request.get_header("User-agent"), "runcloud-python/0.8.
|
|
341
|
+
self.assertEqual(request.get_header("User-agent"), "runcloud-python/0.8.2")
|
|
319
342
|
|
|
320
343
|
def test_exec_stream_opens_authenticated_websocket_and_sends_command(self):
|
|
321
344
|
session = WebSocketSession()
|
|
@@ -345,7 +368,7 @@ class SDKTests(unittest.TestCase):
|
|
|
345
368
|
connect.assert_called_once_with(
|
|
346
369
|
"wss://api.example.test/run-cloud/sandboxes/sb_1/exec/stream?mode=command",
|
|
347
370
|
additional_headers={"Authorization": "Bearer rc_live_test"},
|
|
348
|
-
user_agent_header="runcloud-python/0.8.
|
|
371
|
+
user_agent_header="runcloud-python/0.8.2",
|
|
349
372
|
open_timeout=60.0,
|
|
350
373
|
close_timeout=5,
|
|
351
374
|
max_size=8 << 20,
|
|
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
|