runcloud-sdk 0.8.2__tar.gz → 0.9.0__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.2/runcloud_sdk.egg-info → runcloud_sdk-0.9.0}/PKG-INFO +16 -1
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/README.md +15 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/pyproject.toml +1 -1
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/client.py +30 -4
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0/runcloud_sdk.egg-info}/PKG-INFO +16 -1
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/tests/test_sdk.py +44 -3
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/LICENSE +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/__init__.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/__init__.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/blaxel.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/daytona.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/e2b.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/modal.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/sprites.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud/compat/vercel.py +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud_sdk.egg-info/SOURCES.txt +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud_sdk.egg-info/dependency_links.txt +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud_sdk.egg-info/requires.txt +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/runcloud_sdk.egg-info/top_level.txt +0 -0
- {runcloud_sdk-0.8.2 → runcloud_sdk-0.9.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runcloud-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Python SDK for run.cloud, including sandbox-provider compatibility adapters
|
|
5
5
|
Author: Newly
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -62,6 +62,21 @@ finally:
|
|
|
62
62
|
automation, set `RUN_CLOUD_API_KEY` and optionally `RUN_CLOUD_API_URL`, or pass
|
|
63
63
|
`api_key` and `api_url` directly.
|
|
64
64
|
|
|
65
|
+
A sandbox's name is a label you own: set it at create, change it later, and
|
|
66
|
+
filter on it. Naming plus `resume` is how the same sandbox goes back to the same
|
|
67
|
+
piece of work instead of booting a new one:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
matches = cloud.list(name=f"project-{project_id}")
|
|
71
|
+
box = matches[0].resume() if matches else cloud.create(name=f"project-{project_id}")
|
|
72
|
+
|
|
73
|
+
box.rename(f"project-{project_id}-v2")
|
|
74
|
+
box.rename("") # clears the label
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Names are not unique and mean nothing to the platform, so check `state` on a
|
|
78
|
+
match before using it. Renaming works in any state but `destroyed`.
|
|
79
|
+
|
|
65
80
|
## Compatibility adapters
|
|
66
81
|
|
|
67
82
|
The same distribution includes migration adapters:
|
|
@@ -44,6 +44,21 @@ finally:
|
|
|
44
44
|
automation, set `RUN_CLOUD_API_KEY` and optionally `RUN_CLOUD_API_URL`, or pass
|
|
45
45
|
`api_key` and `api_url` directly.
|
|
46
46
|
|
|
47
|
+
A sandbox's name is a label you own: set it at create, change it later, and
|
|
48
|
+
filter on it. Naming plus `resume` is how the same sandbox goes back to the same
|
|
49
|
+
piece of work instead of booting a new one:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
matches = cloud.list(name=f"project-{project_id}")
|
|
53
|
+
box = matches[0].resume() if matches else cloud.create(name=f"project-{project_id}")
|
|
54
|
+
|
|
55
|
+
box.rename(f"project-{project_id}-v2")
|
|
56
|
+
box.rename("") # clears the label
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Names are not unique and mean nothing to the platform, so check `state` on a
|
|
60
|
+
match before using it. Renaming works in any state but `destroyed`.
|
|
61
|
+
|
|
47
62
|
## Compatibility adapters
|
|
48
63
|
|
|
49
64
|
The same distribution includes migration adapters:
|
|
@@ -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
|
|
@@ -370,8 +372,17 @@ class Client:
|
|
|
370
372
|
sid = urllib.parse.quote(sandbox_id, safe="")
|
|
371
373
|
return Sandbox(self, self.request("GET", f"/run-cloud/sandboxes/{sid}"))
|
|
372
374
|
|
|
373
|
-
def list(
|
|
374
|
-
|
|
375
|
+
def list(
|
|
376
|
+
self, state: Optional[str] = None, name: Optional[str] = None
|
|
377
|
+
) -> list["Sandbox"]:
|
|
378
|
+
# Both filters are applied server-side, so finding a sandbox by the name
|
|
379
|
+
# you gave it does not mean paging the whole fleet and matching locally.
|
|
380
|
+
filters = [
|
|
381
|
+
(key, value)
|
|
382
|
+
for key, value in (("state", state), ("name", name))
|
|
383
|
+
if value
|
|
384
|
+
]
|
|
385
|
+
query = "?" + urllib.parse.urlencode(filters) if filters else ""
|
|
375
386
|
payload = self.request("GET", "/run-cloud/sandboxes" + query) or {}
|
|
376
387
|
return [Sandbox(self, item) for item in payload.get("items", [])]
|
|
377
388
|
|
|
@@ -546,6 +557,21 @@ class Sandbox:
|
|
|
546
557
|
)
|
|
547
558
|
return self
|
|
548
559
|
|
|
560
|
+
def rename(self, name: str) -> "Sandbox":
|
|
561
|
+
"""Relabel the sandbox.
|
|
562
|
+
|
|
563
|
+
The name is a label you own and the filter ``list(name=...)`` matches on,
|
|
564
|
+
so this is how a sandbox gets re-tagged without destroying it. Works in
|
|
565
|
+
any state but destroyed; an empty name clears the label. Names are not
|
|
566
|
+
unique and are capped at 128 characters.
|
|
567
|
+
"""
|
|
568
|
+
self._data = self._client.request(
|
|
569
|
+
"POST",
|
|
570
|
+
f"/run-cloud/sandboxes/{urllib.parse.quote(self.id, safe='')}/name",
|
|
571
|
+
{"name": name},
|
|
572
|
+
)
|
|
573
|
+
return self
|
|
574
|
+
|
|
549
575
|
def set_timeout(self, seconds: int) -> "Sandbox":
|
|
550
576
|
self._data = self._client.request(
|
|
551
577
|
"POST",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runcloud-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Python SDK for run.cloud, including sandbox-provider compatibility adapters
|
|
5
5
|
Author: Newly
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -62,6 +62,21 @@ finally:
|
|
|
62
62
|
automation, set `RUN_CLOUD_API_KEY` and optionally `RUN_CLOUD_API_URL`, or pass
|
|
63
63
|
`api_key` and `api_url` directly.
|
|
64
64
|
|
|
65
|
+
A sandbox's name is a label you own: set it at create, change it later, and
|
|
66
|
+
filter on it. Naming plus `resume` is how the same sandbox goes back to the same
|
|
67
|
+
piece of work instead of booting a new one:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
matches = cloud.list(name=f"project-{project_id}")
|
|
71
|
+
box = matches[0].resume() if matches else cloud.create(name=f"project-{project_id}")
|
|
72
|
+
|
|
73
|
+
box.rename(f"project-{project_id}-v2")
|
|
74
|
+
box.rename("") # clears the label
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Names are not unique and mean nothing to the platform, so check `state` on a
|
|
78
|
+
match before using it. Renaming works in any state but `destroyed`.
|
|
79
|
+
|
|
65
80
|
## Compatibility adapters
|
|
66
81
|
|
|
67
82
|
The same distribution includes migration adapters:
|
|
@@ -114,6 +114,35 @@ class SDKTests(unittest.TestCase):
|
|
|
114
114
|
self.recorder.calls,
|
|
115
115
|
)
|
|
116
116
|
|
|
117
|
+
def test_rename_posts_the_label_and_list_filters_on_it(self):
|
|
118
|
+
box = self.client.create()
|
|
119
|
+
box.rename("proj_42")
|
|
120
|
+
self.assertIn(
|
|
121
|
+
("POST", "/run-cloud/sandboxes/sb_python/name", {"name": "proj_42"}),
|
|
122
|
+
self.recorder.calls,
|
|
123
|
+
)
|
|
124
|
+
# The response replaces the cached record, so the new name is readable
|
|
125
|
+
# without a second get().
|
|
126
|
+
self.assertEqual(box.name, "compat")
|
|
127
|
+
|
|
128
|
+
# An empty name clears the label rather than being rejected client-side.
|
|
129
|
+
box.rename("")
|
|
130
|
+
self.assertIn(
|
|
131
|
+
("POST", "/run-cloud/sandboxes/sb_python/name", {"name": ""}),
|
|
132
|
+
self.recorder.calls,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.client.list(name="proj_42")
|
|
136
|
+
self.assertIn(
|
|
137
|
+
("GET", "/run-cloud/sandboxes?name=proj_42", None),
|
|
138
|
+
self.recorder.calls,
|
|
139
|
+
)
|
|
140
|
+
self.client.list(state="running", name="proj 42")
|
|
141
|
+
self.assertIn(
|
|
142
|
+
("GET", "/run-cloud/sandboxes?state=running&name=proj+42", None),
|
|
143
|
+
self.recorder.calls,
|
|
144
|
+
)
|
|
145
|
+
|
|
117
146
|
def test_exec_contract_defaults_legacy_true_and_preserves_false(self):
|
|
118
147
|
for frame, expected in [
|
|
119
148
|
({"stream": "exit"}, 0),
|
|
@@ -338,7 +367,7 @@ class SDKTests(unittest.TestCase):
|
|
|
338
367
|
Client(api_key="rc_live_test").request("GET", "/run-cloud/account")
|
|
339
368
|
|
|
340
369
|
request = urlopen.call_args.args[0]
|
|
341
|
-
self.assertEqual(request.get_header("User-agent"), "runcloud-python/0.8.
|
|
370
|
+
self.assertEqual(request.get_header("User-agent"), "runcloud-python/0.8.3")
|
|
342
371
|
|
|
343
372
|
def test_exec_stream_opens_authenticated_websocket_and_sends_command(self):
|
|
344
373
|
session = WebSocketSession()
|
|
@@ -368,7 +397,7 @@ class SDKTests(unittest.TestCase):
|
|
|
368
397
|
connect.assert_called_once_with(
|
|
369
398
|
"wss://api.example.test/run-cloud/sandboxes/sb_1/exec/stream?mode=command",
|
|
370
399
|
additional_headers={"Authorization": "Bearer rc_live_test"},
|
|
371
|
-
user_agent_header="runcloud-python/0.8.
|
|
400
|
+
user_agent_header="runcloud-python/0.8.3",
|
|
372
401
|
open_timeout=60.0,
|
|
373
402
|
close_timeout=5,
|
|
374
403
|
max_size=8 << 20,
|
|
@@ -398,7 +427,12 @@ class CredentialTests(unittest.TestCase):
|
|
|
398
427
|
clear=False,
|
|
399
428
|
)
|
|
400
429
|
self.environment.start()
|
|
401
|
-
for name in (
|
|
430
|
+
for name in (
|
|
431
|
+
"RUN_CLOUD_API_KEY",
|
|
432
|
+
"RUN_CLOUD_API_TOKEN",
|
|
433
|
+
"RUN_CLOUD_CI_ENVIRONMENT_TOKEN",
|
|
434
|
+
"RUN_CLOUD_API_URL",
|
|
435
|
+
):
|
|
402
436
|
os.environ.pop(name, None)
|
|
403
437
|
|
|
404
438
|
def tearDown(self):
|
|
@@ -458,6 +492,13 @@ class CredentialTests(unittest.TestCase):
|
|
|
458
492
|
self.assertEqual(client.api_key, "environment-key")
|
|
459
493
|
self.assertEqual(client.api_url, "https://api.run.cloud")
|
|
460
494
|
|
|
495
|
+
def test_ci_environment_capability_is_used_without_an_api_key(self):
|
|
496
|
+
os.environ["RUN_CLOUD_CI_ENVIRONMENT_TOKEN"] = "rce_environment"
|
|
497
|
+
|
|
498
|
+
client = Client()
|
|
499
|
+
|
|
500
|
+
self.assertEqual(client.api_key, "rce_environment")
|
|
501
|
+
|
|
461
502
|
def test_api_url_override_applies_to_saved_login(self):
|
|
462
503
|
self.save_credentials()
|
|
463
504
|
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
|