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.
Files changed (20) hide show
  1. {runcloud_sdk-0.8.1/runcloud_sdk.egg-info → runcloud_sdk-0.8.3}/PKG-INFO +1 -1
  2. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/pyproject.toml +1 -1
  3. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/client.py +15 -2
  4. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3/runcloud_sdk.egg-info}/PKG-INFO +1 -1
  5. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/tests/test_sdk.py +39 -4
  6. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/LICENSE +0 -0
  7. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/README.md +0 -0
  8. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/__init__.py +0 -0
  9. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/__init__.py +0 -0
  10. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/blaxel.py +0 -0
  11. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/daytona.py +0 -0
  12. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/e2b.py +0 -0
  13. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/modal.py +0 -0
  14. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/sprites.py +0 -0
  15. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud/compat/vercel.py +0 -0
  16. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/SOURCES.txt +0 -0
  17. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/dependency_links.txt +0 -0
  18. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/requires.txt +0 -0
  19. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/runcloud_sdk.egg-info/top_level.txt +0 -0
  20. {runcloud_sdk-0.8.1 → runcloud_sdk-0.8.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runcloud-sdk
3
- Version: 0.8.1
3
+ Version: 0.8.3
4
4
  Summary: Python SDK for run.cloud, including sandbox-provider compatibility adapters
5
5
  Author: Newly
6
6
  License-Expression: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "runcloud-sdk"
3
- version = "0.8.1"
3
+ version = "0.8.3"
4
4
  description = "Python SDK for run.cloud, including sandbox-provider compatibility adapters"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -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.1"
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(os.environ.get("RUN_CLOUD_API_TOKEN"))
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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runcloud-sdk
3
- Version: 0.8.1
3
+ Version: 0.8.3
4
4
  Summary: Python SDK for run.cloud, including sandbox-provider compatibility adapters
5
5
  Author: Newly
6
6
  License-Expression: Apache-2.0
@@ -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.1")
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.1",
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 ("RUN_CLOUD_API_KEY", "RUN_CLOUD_API_TOKEN", "RUN_CLOUD_API_URL"):
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