runcloud-sdk 0.8.1__tar.gz → 0.8.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.
- {runcloud_sdk-0.8.1/runcloud_sdk.egg-info → runcloud_sdk-0.8.3}/PKG-INFO +1 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/pyproject.toml +1 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/client.py +15 -2
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3/runcloud_sdk.egg-info}/PKG-INFO +1 -1
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/tests/test_sdk.py +39 -4
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/LICENSE +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/README.md +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/__init__.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/__init__.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/blaxel.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/daytona.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/e2b.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/modal.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/sprites.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/vercel.py +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/SOURCES.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/dependency_links.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/requires.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/top_level.txt +0 -0
- {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/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.3"
|
|
24
24
|
FILE_CHUNK_BYTES = 512 * 1024
|
|
25
25
|
|
|
26
26
|
|
|
@@ -253,7 +253,9 @@ class Client:
|
|
|
253
253
|
explicit_api_key = _nonempty_string(api_key)
|
|
254
254
|
environment_api_key = _nonempty_string(
|
|
255
255
|
os.environ.get("RUN_CLOUD_API_KEY")
|
|
256
|
-
) or _nonempty_string(
|
|
256
|
+
) or _nonempty_string(
|
|
257
|
+
os.environ.get("RUN_CLOUD_API_TOKEN")
|
|
258
|
+
) or _nonempty_string(os.environ.get("RUN_CLOUD_CI_ENVIRONMENT_TOKEN"))
|
|
257
259
|
saved_credentials = (
|
|
258
260
|
None
|
|
259
261
|
if explicit_api_key or environment_api_key
|
|
@@ -518,6 +520,17 @@ class Sandbox:
|
|
|
518
520
|
)
|
|
519
521
|
if payload.get("error"):
|
|
520
522
|
raise RunCloudError(0, str(payload["error"]))
|
|
523
|
+
expected_next_offset = offset + len(chunk)
|
|
524
|
+
next_offset = payload.get("next_offset")
|
|
525
|
+
if chunk and (
|
|
526
|
+
isinstance(next_offset, bool)
|
|
527
|
+
or not isinstance(next_offset, int)
|
|
528
|
+
or next_offset != expected_next_offset
|
|
529
|
+
):
|
|
530
|
+
raise RunCloudError(
|
|
531
|
+
0,
|
|
532
|
+
"file write chunk was not acknowledged; the file may be incomplete",
|
|
533
|
+
)
|
|
521
534
|
|
|
522
535
|
def pause(self) -> "Sandbox":
|
|
523
536
|
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.3")
|
|
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.3",
|
|
349
372
|
open_timeout=60.0,
|
|
350
373
|
close_timeout=5,
|
|
351
374
|
max_size=8 << 20,
|
|
@@ -375,7 +398,12 @@ class CredentialTests(unittest.TestCase):
|
|
|
375
398
|
clear=False,
|
|
376
399
|
)
|
|
377
400
|
self.environment.start()
|
|
378
|
-
for name in (
|
|
401
|
+
for name in (
|
|
402
|
+
"RUN_CLOUD_API_KEY",
|
|
403
|
+
"RUN_CLOUD_API_TOKEN",
|
|
404
|
+
"RUN_CLOUD_CI_ENVIRONMENT_TOKEN",
|
|
405
|
+
"RUN_CLOUD_API_URL",
|
|
406
|
+
):
|
|
379
407
|
os.environ.pop(name, None)
|
|
380
408
|
|
|
381
409
|
def tearDown(self):
|
|
@@ -435,6 +463,13 @@ class CredentialTests(unittest.TestCase):
|
|
|
435
463
|
self.assertEqual(client.api_key, "environment-key")
|
|
436
464
|
self.assertEqual(client.api_url, "https://api.run.cloud")
|
|
437
465
|
|
|
466
|
+
def test_ci_environment_capability_is_used_without_an_api_key(self):
|
|
467
|
+
os.environ["RUN_CLOUD_CI_ENVIRONMENT_TOKEN"] = "rce_environment"
|
|
468
|
+
|
|
469
|
+
client = Client()
|
|
470
|
+
|
|
471
|
+
self.assertEqual(client.api_key, "rce_environment")
|
|
472
|
+
|
|
438
473
|
def test_api_url_override_applies_to_saved_login(self):
|
|
439
474
|
self.save_credentials()
|
|
440
475
|
os.environ["RUN_CLOUD_API_URL"] = "https://override.example/"
|
|
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
|