runcloud-sdk 0.7.0__tar.gz → 0.7.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.7.0/runcloud_sdk.egg-info → runcloud_sdk-0.7.2}/PKG-INFO +6 -2
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/README.md +5 -1
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/pyproject.toml +1 -1
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/client.py +66 -5
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2/runcloud_sdk.egg-info}/PKG-INFO +6 -2
- runcloud_sdk-0.7.2/tests/test_sdk.py +198 -0
- runcloud_sdk-0.7.0/tests/test_sdk.py +0 -79
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/LICENSE +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/__init__.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/__init__.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/blaxel.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/daytona.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/e2b.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/modal.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/sprites.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud/compat/vercel.py +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud_sdk.egg-info/SOURCES.txt +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud_sdk.egg-info/dependency_links.txt +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/runcloud_sdk.egg-info/top_level.txt +0 -0
- {runcloud_sdk-0.7.0 → runcloud_sdk-0.7.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runcloud-sdk
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Python SDK for run.cloud, including sandbox-provider compatibility adapters
|
|
5
5
|
Author: Newly
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,7 +21,7 @@ Python SDK for [run.cloud](https://run.cloud).
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
pip install runcloud-sdk
|
|
24
|
-
|
|
24
|
+
runcloud login
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
```python
|
|
@@ -36,6 +36,10 @@ finally:
|
|
|
36
36
|
box.destroy()
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
`Client()` automatically uses the credential saved by `runcloud login`. For
|
|
40
|
+
automation, set `RUN_CLOUD_API_KEY` and optionally `RUN_CLOUD_API_URL`, or pass
|
|
41
|
+
`api_key` and `api_url` directly.
|
|
42
|
+
|
|
39
43
|
## Compatibility adapters
|
|
40
44
|
|
|
41
45
|
The same distribution includes migration adapters:
|
|
@@ -4,7 +4,7 @@ Python SDK for [run.cloud](https://run.cloud).
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pip install runcloud-sdk
|
|
7
|
-
|
|
7
|
+
runcloud login
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
```python
|
|
@@ -19,6 +19,10 @@ finally:
|
|
|
19
19
|
box.destroy()
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
`Client()` automatically uses the credential saved by `runcloud login`. For
|
|
23
|
+
automation, set `RUN_CLOUD_API_KEY` and optionally `RUN_CLOUD_API_URL`, or pass
|
|
24
|
+
`api_key` and `api_url` directly.
|
|
25
|
+
|
|
22
26
|
## Compatibility adapters
|
|
23
27
|
|
|
24
28
|
The same distribution includes migration adapters:
|
|
@@ -5,13 +5,17 @@ from __future__ import annotations
|
|
|
5
5
|
import base64
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import time
|
|
8
9
|
import urllib.error
|
|
9
10
|
import urllib.parse
|
|
10
11
|
import urllib.request
|
|
11
12
|
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
12
14
|
from typing import Any, Callable, Optional, Union
|
|
13
15
|
|
|
14
16
|
Transport = Callable[[str, str, Any], Any]
|
|
17
|
+
DEFAULT_API_URL = "https://api.run.cloud"
|
|
18
|
+
USER_AGENT = "runcloud-python/0.7.2"
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
class RunCloudError(Exception):
|
|
@@ -38,6 +42,47 @@ class ExecResult:
|
|
|
38
42
|
stderr: str
|
|
39
43
|
|
|
40
44
|
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class _SavedCredentials:
|
|
47
|
+
api_key: str
|
|
48
|
+
api_url: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _nonempty_string(value: Any) -> Optional[str]:
|
|
52
|
+
if not isinstance(value, str):
|
|
53
|
+
return None
|
|
54
|
+
normalized = value.strip()
|
|
55
|
+
return normalized or None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _load_saved_credentials() -> Optional[_SavedCredentials]:
|
|
59
|
+
configured_home = _nonempty_string(os.environ.get("RUN_CLOUD_HOME"))
|
|
60
|
+
credentials_home = (
|
|
61
|
+
Path(configured_home).expanduser()
|
|
62
|
+
if configured_home
|
|
63
|
+
else Path.home() / ".run-cloud"
|
|
64
|
+
)
|
|
65
|
+
try:
|
|
66
|
+
payload = json.loads((credentials_home / "credentials").read_text())
|
|
67
|
+
except (OSError, TypeError, ValueError):
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
if not isinstance(payload, dict):
|
|
71
|
+
return None
|
|
72
|
+
api_key = _nonempty_string(payload.get("token"))
|
|
73
|
+
api_url = _nonempty_string(payload.get("apiUrl"))
|
|
74
|
+
expires_at = payload.get("expiresAt")
|
|
75
|
+
if (
|
|
76
|
+
not api_key
|
|
77
|
+
or not api_url
|
|
78
|
+
or isinstance(expires_at, bool)
|
|
79
|
+
or not isinstance(expires_at, (int, float))
|
|
80
|
+
or expires_at < time.time()
|
|
81
|
+
):
|
|
82
|
+
return None
|
|
83
|
+
return _SavedCredentials(api_key=api_key, api_url=api_url)
|
|
84
|
+
|
|
85
|
+
|
|
41
86
|
class Client:
|
|
42
87
|
def __init__(
|
|
43
88
|
self,
|
|
@@ -46,17 +91,32 @@ class Client:
|
|
|
46
91
|
timeout: float = 60.0,
|
|
47
92
|
transport: Optional[Transport] = None,
|
|
48
93
|
):
|
|
94
|
+
explicit_api_key = _nonempty_string(api_key)
|
|
95
|
+
environment_api_key = _nonempty_string(
|
|
96
|
+
os.environ.get("RUN_CLOUD_API_KEY")
|
|
97
|
+
) or _nonempty_string(os.environ.get("RUN_CLOUD_API_TOKEN"))
|
|
98
|
+
saved_credentials = (
|
|
99
|
+
None
|
|
100
|
+
if explicit_api_key or environment_api_key
|
|
101
|
+
else _load_saved_credentials()
|
|
102
|
+
)
|
|
49
103
|
self.api_key = (
|
|
50
|
-
|
|
51
|
-
or
|
|
52
|
-
or
|
|
104
|
+
explicit_api_key
|
|
105
|
+
or environment_api_key
|
|
106
|
+
or (saved_credentials.api_key if saved_credentials else None)
|
|
53
107
|
)
|
|
54
108
|
if not self.api_key:
|
|
55
109
|
raise ValueError(
|
|
56
|
-
"Run Cloud API key required.
|
|
110
|
+
"Run Cloud API key required. Run `runcloud login`, set "
|
|
111
|
+
"RUN_CLOUD_API_KEY, or pass Client(api_key=...)."
|
|
57
112
|
)
|
|
113
|
+
explicit_api_url = _nonempty_string(api_url)
|
|
114
|
+
environment_api_url = _nonempty_string(os.environ.get("RUN_CLOUD_API_URL"))
|
|
58
115
|
self.api_url = (
|
|
59
|
-
|
|
116
|
+
explicit_api_url
|
|
117
|
+
or environment_api_url
|
|
118
|
+
or (saved_credentials.api_url if saved_credentials else None)
|
|
119
|
+
or DEFAULT_API_URL
|
|
60
120
|
).rstrip("/")
|
|
61
121
|
self.timeout = timeout
|
|
62
122
|
self._transport = transport
|
|
@@ -72,6 +132,7 @@ class Client:
|
|
|
72
132
|
headers={
|
|
73
133
|
"Authorization": "Bearer " + self.api_key,
|
|
74
134
|
"Content-Type": "application/json",
|
|
135
|
+
"User-Agent": USER_AGENT,
|
|
75
136
|
},
|
|
76
137
|
)
|
|
77
138
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runcloud-sdk
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Python SDK for run.cloud, including sandbox-provider compatibility adapters
|
|
5
5
|
Author: Newly
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,7 +21,7 @@ Python SDK for [run.cloud](https://run.cloud).
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
pip install runcloud-sdk
|
|
24
|
-
|
|
24
|
+
runcloud login
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
```python
|
|
@@ -36,6 +36,10 @@ finally:
|
|
|
36
36
|
box.destroy()
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
`Client()` automatically uses the credential saved by `runcloud login`. For
|
|
40
|
+
automation, set `RUN_CLOUD_API_KEY` and optionally `RUN_CLOUD_API_URL`, or pass
|
|
41
|
+
`api_key` and `api_url` directly.
|
|
42
|
+
|
|
39
43
|
## Compatibility adapters
|
|
40
44
|
|
|
41
45
|
The same distribution includes migration adapters:
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import time
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from unittest.mock import patch
|
|
10
|
+
|
|
11
|
+
from runcloud import Client, UnsupportedCompatibilityFeatureError
|
|
12
|
+
from runcloud.compat import modal
|
|
13
|
+
from runcloud.compat.blaxel import SandboxInstance, SyncSandboxInstance
|
|
14
|
+
from runcloud.compat.daytona import Daytona
|
|
15
|
+
from runcloud.compat.e2b import Sandbox as E2BSandbox
|
|
16
|
+
from runcloud.compat.sprites import SpritesClient
|
|
17
|
+
from runcloud.compat.vercel import Sandbox as VercelSandbox
|
|
18
|
+
|
|
19
|
+
SANDBOX = {"id": "sb_python", "name": "compat", "state": "running"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Recorder:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.calls = []
|
|
25
|
+
|
|
26
|
+
def __call__(self, method, path, body):
|
|
27
|
+
self.calls.append((method, path, body))
|
|
28
|
+
if path.endswith("/exec"):
|
|
29
|
+
return {"exit_code": 0, "stdout": "ok\n", "stderr": ""}
|
|
30
|
+
if path.endswith("/fs"):
|
|
31
|
+
return {"content": base64.b64encode(b"artifact").decode()}
|
|
32
|
+
if path == "/run-cloud/sandboxes" and method == "GET":
|
|
33
|
+
return {"items": [SANDBOX]}
|
|
34
|
+
return SANDBOX
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Response:
|
|
38
|
+
def __enter__(self):
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def __exit__(self, *_):
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def read(self):
|
|
45
|
+
return b"{}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SDKTests(unittest.TestCase):
|
|
49
|
+
def setUp(self):
|
|
50
|
+
self.recorder = Recorder()
|
|
51
|
+
self.client = Client(api_key="rc_live_test", transport=self.recorder)
|
|
52
|
+
|
|
53
|
+
def test_native_routes(self):
|
|
54
|
+
box = self.client.create(cpu=1.5, memory=2048)
|
|
55
|
+
self.assertEqual(box.exec("echo ok").stdout, "ok\n")
|
|
56
|
+
self.assertEqual(box.read_file("/tmp/a"), b"artifact")
|
|
57
|
+
box.pause()
|
|
58
|
+
box.resume()
|
|
59
|
+
box.destroy()
|
|
60
|
+
self.assertIn(
|
|
61
|
+
(
|
|
62
|
+
"POST",
|
|
63
|
+
"/run-cloud/sandboxes",
|
|
64
|
+
{
|
|
65
|
+
"image": "runcloud/agent-base",
|
|
66
|
+
"cpu": 1.5,
|
|
67
|
+
"memory": 2048,
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
self.recorder.calls,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def test_compatibility_adapters_share_the_public_client(self):
|
|
74
|
+
app = modal.App.lookup("compat", client=self.client)
|
|
75
|
+
self.assertEqual(modal.Sandbox.create(app=app).exec("echo ok").wait(), 0)
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
E2BSandbox.create(client=self.client).commands.run("echo ok").exit_code, 0
|
|
78
|
+
)
|
|
79
|
+
self.assertEqual(
|
|
80
|
+
Daytona(client=self.client).create().process.exec("echo ok").exit_code, 0
|
|
81
|
+
)
|
|
82
|
+
self.assertEqual(
|
|
83
|
+
VercelSandbox.create(client=self.client).run_command("echo").exit_code, 0
|
|
84
|
+
)
|
|
85
|
+
self.assertTrue(SyncSandboxInstance.create(client=self.client))
|
|
86
|
+
async_blaxel = asyncio.run(SandboxInstance.create(client=self.client))
|
|
87
|
+
asyncio.run(async_blaxel.delete())
|
|
88
|
+
self.assertTrue(SpritesClient(client=self.client).create_sprite("compat"))
|
|
89
|
+
|
|
90
|
+
def test_unsupported_features_fail_loudly(self):
|
|
91
|
+
with self.assertRaises(UnsupportedCompatibilityFeatureError):
|
|
92
|
+
modal.Image.debian_slim().pip_install("requests")
|
|
93
|
+
|
|
94
|
+
def test_requests_identify_the_python_sdk(self):
|
|
95
|
+
with patch(
|
|
96
|
+
"runcloud.client.urllib.request.urlopen",
|
|
97
|
+
return_value=Response(),
|
|
98
|
+
) as urlopen:
|
|
99
|
+
Client(api_key="rc_live_test").request("GET", "/run-cloud/account")
|
|
100
|
+
|
|
101
|
+
request = urlopen.call_args.args[0]
|
|
102
|
+
self.assertEqual(request.get_header("User-agent"), "runcloud-python/0.7.2")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class CredentialTests(unittest.TestCase):
|
|
106
|
+
def setUp(self):
|
|
107
|
+
self.temporary_directory = tempfile.TemporaryDirectory()
|
|
108
|
+
self.run_cloud_home = Path(self.temporary_directory.name)
|
|
109
|
+
self.environment = patch.dict(
|
|
110
|
+
os.environ,
|
|
111
|
+
{"RUN_CLOUD_HOME": str(self.run_cloud_home)},
|
|
112
|
+
clear=False,
|
|
113
|
+
)
|
|
114
|
+
self.environment.start()
|
|
115
|
+
for name in ("RUN_CLOUD_API_KEY", "RUN_CLOUD_API_TOKEN", "RUN_CLOUD_API_URL"):
|
|
116
|
+
os.environ.pop(name, None)
|
|
117
|
+
|
|
118
|
+
def tearDown(self):
|
|
119
|
+
self.environment.stop()
|
|
120
|
+
self.temporary_directory.cleanup()
|
|
121
|
+
|
|
122
|
+
def save_credentials(
|
|
123
|
+
self,
|
|
124
|
+
*,
|
|
125
|
+
token="saved-key",
|
|
126
|
+
api_url="https://saved.example/api/",
|
|
127
|
+
expires_at=None,
|
|
128
|
+
):
|
|
129
|
+
payload = {
|
|
130
|
+
"token": token,
|
|
131
|
+
"apiUrl": api_url,
|
|
132
|
+
"appUrl": "https://run.cloud",
|
|
133
|
+
"expiresAt": expires_at or time.time() + 3600,
|
|
134
|
+
}
|
|
135
|
+
(self.run_cloud_home / "credentials").write_text(json.dumps(payload))
|
|
136
|
+
|
|
137
|
+
def test_uses_saved_cli_credentials(self):
|
|
138
|
+
self.save_credentials()
|
|
139
|
+
|
|
140
|
+
client = Client()
|
|
141
|
+
|
|
142
|
+
self.assertEqual(client.api_key, "saved-key")
|
|
143
|
+
self.assertEqual(client.api_url, "https://saved.example/api")
|
|
144
|
+
|
|
145
|
+
def test_modal_app_lookup_uses_saved_cli_credentials(self):
|
|
146
|
+
self.save_credentials()
|
|
147
|
+
|
|
148
|
+
app = modal.App.lookup("my-app", create_if_missing=True)
|
|
149
|
+
|
|
150
|
+
self.assertEqual(app.client.api_key, "saved-key")
|
|
151
|
+
self.assertEqual(app.client.api_url, "https://saved.example/api")
|
|
152
|
+
|
|
153
|
+
def test_explicit_credentials_override_environment_and_saved_login(self):
|
|
154
|
+
self.save_credentials()
|
|
155
|
+
os.environ["RUN_CLOUD_API_KEY"] = "environment-key"
|
|
156
|
+
os.environ["RUN_CLOUD_API_URL"] = "https://environment.example"
|
|
157
|
+
|
|
158
|
+
client = Client(
|
|
159
|
+
api_key="explicit-key",
|
|
160
|
+
api_url="https://explicit.example/",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self.assertEqual(client.api_key, "explicit-key")
|
|
164
|
+
self.assertEqual(client.api_url, "https://explicit.example")
|
|
165
|
+
|
|
166
|
+
def test_environment_credentials_override_saved_login(self):
|
|
167
|
+
self.save_credentials()
|
|
168
|
+
os.environ["RUN_CLOUD_API_KEY"] = "environment-key"
|
|
169
|
+
|
|
170
|
+
client = Client()
|
|
171
|
+
|
|
172
|
+
self.assertEqual(client.api_key, "environment-key")
|
|
173
|
+
self.assertEqual(client.api_url, "https://api.run.cloud")
|
|
174
|
+
|
|
175
|
+
def test_api_url_override_applies_to_saved_login(self):
|
|
176
|
+
self.save_credentials()
|
|
177
|
+
os.environ["RUN_CLOUD_API_URL"] = "https://override.example/"
|
|
178
|
+
|
|
179
|
+
client = Client()
|
|
180
|
+
|
|
181
|
+
self.assertEqual(client.api_key, "saved-key")
|
|
182
|
+
self.assertEqual(client.api_url, "https://override.example")
|
|
183
|
+
|
|
184
|
+
def test_ignores_expired_saved_credentials(self):
|
|
185
|
+
self.save_credentials(expires_at=time.time() - 1)
|
|
186
|
+
|
|
187
|
+
with self.assertRaisesRegex(ValueError, "runcloud login"):
|
|
188
|
+
Client()
|
|
189
|
+
|
|
190
|
+
def test_ignores_malformed_saved_credentials(self):
|
|
191
|
+
(self.run_cloud_home / "credentials").write_text("{not json")
|
|
192
|
+
|
|
193
|
+
with self.assertRaisesRegex(ValueError, "RUN_CLOUD_API_KEY"):
|
|
194
|
+
Client()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
unittest.main()
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import base64
|
|
3
|
-
import unittest
|
|
4
|
-
|
|
5
|
-
from runcloud import Client, UnsupportedCompatibilityFeatureError
|
|
6
|
-
from runcloud.compat import modal
|
|
7
|
-
from runcloud.compat.blaxel import SandboxInstance, SyncSandboxInstance
|
|
8
|
-
from runcloud.compat.daytona import Daytona
|
|
9
|
-
from runcloud.compat.e2b import Sandbox as E2BSandbox
|
|
10
|
-
from runcloud.compat.sprites import SpritesClient
|
|
11
|
-
from runcloud.compat.vercel import Sandbox as VercelSandbox
|
|
12
|
-
|
|
13
|
-
SANDBOX = {"id": "sb_python", "name": "compat", "state": "running"}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Recorder:
|
|
17
|
-
def __init__(self):
|
|
18
|
-
self.calls = []
|
|
19
|
-
|
|
20
|
-
def __call__(self, method, path, body):
|
|
21
|
-
self.calls.append((method, path, body))
|
|
22
|
-
if path.endswith("/exec"):
|
|
23
|
-
return {"exit_code": 0, "stdout": "ok\n", "stderr": ""}
|
|
24
|
-
if path.endswith("/fs"):
|
|
25
|
-
return {"content": base64.b64encode(b"artifact").decode()}
|
|
26
|
-
if path == "/run-cloud/sandboxes" and method == "GET":
|
|
27
|
-
return {"items": [SANDBOX]}
|
|
28
|
-
return SANDBOX
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class SDKTests(unittest.TestCase):
|
|
32
|
-
def setUp(self):
|
|
33
|
-
self.recorder = Recorder()
|
|
34
|
-
self.client = Client(api_key="rc_live_test", transport=self.recorder)
|
|
35
|
-
|
|
36
|
-
def test_native_routes(self):
|
|
37
|
-
box = self.client.create(cpu=1.5, memory=2048)
|
|
38
|
-
self.assertEqual(box.exec("echo ok").stdout, "ok\n")
|
|
39
|
-
self.assertEqual(box.read_file("/tmp/a"), b"artifact")
|
|
40
|
-
box.pause()
|
|
41
|
-
box.resume()
|
|
42
|
-
box.destroy()
|
|
43
|
-
self.assertIn(
|
|
44
|
-
(
|
|
45
|
-
"POST",
|
|
46
|
-
"/run-cloud/sandboxes",
|
|
47
|
-
{
|
|
48
|
-
"image": "runcloud/agent-base",
|
|
49
|
-
"cpu": 1.5,
|
|
50
|
-
"memory": 2048,
|
|
51
|
-
},
|
|
52
|
-
),
|
|
53
|
-
self.recorder.calls,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
def test_compatibility_adapters_share_the_public_client(self):
|
|
57
|
-
app = modal.App.lookup("compat", client=self.client)
|
|
58
|
-
self.assertEqual(modal.Sandbox.create(app=app).exec("echo ok").wait(), 0)
|
|
59
|
-
self.assertEqual(
|
|
60
|
-
E2BSandbox.create(client=self.client).commands.run("echo ok").exit_code, 0
|
|
61
|
-
)
|
|
62
|
-
self.assertEqual(
|
|
63
|
-
Daytona(client=self.client).create().process.exec("echo ok").exit_code, 0
|
|
64
|
-
)
|
|
65
|
-
self.assertEqual(
|
|
66
|
-
VercelSandbox.create(client=self.client).run_command("echo").exit_code, 0
|
|
67
|
-
)
|
|
68
|
-
self.assertTrue(SyncSandboxInstance.create(client=self.client))
|
|
69
|
-
async_blaxel = asyncio.run(SandboxInstance.create(client=self.client))
|
|
70
|
-
asyncio.run(async_blaxel.delete())
|
|
71
|
-
self.assertTrue(SpritesClient(client=self.client).create_sprite("compat"))
|
|
72
|
-
|
|
73
|
-
def test_unsupported_features_fail_loudly(self):
|
|
74
|
-
with self.assertRaises(UnsupportedCompatibilityFeatureError):
|
|
75
|
-
modal.Image.debian_slim().pip_install("requests")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if __name__ == "__main__":
|
|
79
|
-
unittest.main()
|
|
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
|