withruntime 0.3.0__tar.gz → 0.4.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.
- {withruntime-0.3.0 → withruntime-0.4.0}/PKG-INFO +37 -2
- withruntime-0.4.0/README.md +89 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/pyproject.toml +1 -1
- {withruntime-0.3.0 → withruntime-0.4.0}/tests/test_client.py +9 -0
- withruntime-0.4.0/tests/test_e2b_code_interpreter.py +130 -0
- withruntime-0.4.0/tests/test_e2b_sandbox.py +413 -0
- withruntime-0.4.0/tests/test_limits.py +61 -0
- withruntime-0.4.0/tests/test_proxy.py +267 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_client.py +11 -6
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/__init__.py +2 -0
- withruntime-0.4.0/withruntime/_async_products/limits.py +20 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_http.py +28 -7
- withruntime-0.4.0/withruntime/_proxy.py +177 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_client.py +11 -6
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/__init__.py +2 -0
- withruntime-0.4.0/withruntime/_sync_products/limits.py +21 -0
- withruntime-0.4.0/withruntime/_version.py +1 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_ws.py +6 -5
- withruntime-0.4.0/withruntime/e2b/__init__.py +57 -0
- withruntime-0.4.0/withruntime/e2b/_async_io.py +54 -0
- withruntime-0.4.0/withruntime/e2b/_async_sandbox.py +641 -0
- withruntime-0.4.0/withruntime/e2b/_core.py +512 -0
- withruntime-0.4.0/withruntime/e2b/_sync_io.py +41 -0
- withruntime-0.4.0/withruntime/e2b/_sync_sandbox.py +640 -0
- withruntime-0.4.0/withruntime/e2b/code_interpreter/__init__.py +14 -0
- withruntime-0.4.0/withruntime/e2b/code_interpreter/_async_ci.py +143 -0
- withruntime-0.4.0/withruntime/e2b/code_interpreter/_models.py +101 -0
- withruntime-0.4.0/withruntime/e2b/code_interpreter/_sync_ci.py +142 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime.egg-info/PKG-INFO +37 -2
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime.egg-info/SOURCES.txt +18 -1
- withruntime-0.3.0/README.md +0 -54
- withruntime-0.3.0/withruntime/_version.py +0 -1
- {withruntime-0.3.0 → withruntime-0.4.0}/setup.cfg +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/tests/test_images_volumes_interpreter.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/tests/test_previews_network_desktop.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/tests/test_referrals.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/__init__.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/desktop.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/images.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/interpreter.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/network.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/previews.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/referrals.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_async_products/volumes.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_clock.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_connection.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_errors.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/desktop.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/images.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/interpreter.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/network.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/previews.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/referrals.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime/_sync_products/volumes.py +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime.egg-info/dependency_links.txt +0 -0
- {withruntime-0.3.0 → withruntime-0.4.0}/withruntime.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: withruntime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Runtime Cloud: one client (sync and async) for every Runtime product. Sandboxes first.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -50,10 +50,45 @@ A sandbox has `exec`, `exec_stream`, `spawn`, `terminal`, `files` (read, write,
|
|
|
50
50
|
list, glob, stat, move, remove, upload and download directories), `pause`,
|
|
51
51
|
`wake`, `extend`, `fork` and `snapshot`, and the `interpreter`, `network`,
|
|
52
52
|
`previews` and `desktop` products. The client has `sandboxes`, `images`,
|
|
53
|
-
`volumes`, `snapshots`, `feedback` and `support
|
|
53
|
+
`volumes`, `snapshots`, `limits`, `feedback` and `support`;
|
|
54
|
+
`runtime.limits.get()` (0.3.1 and later) says whether the key is read-only and
|
|
55
|
+
what its agent may still spend today. Every write carries an
|
|
54
56
|
idempotency key, so retries never do anything twice; errors are typed and carry
|
|
55
57
|
`code`, `hint` and `request_id`.
|
|
56
58
|
|
|
59
|
+
## Behind a proxy
|
|
60
|
+
|
|
61
|
+
The client reads `HTTPS_PROXY`, `HTTP_PROXY` and `NO_PROXY` (upper or lower
|
|
62
|
+
case; lower wins when both are set), sync and async. Calls to the API and
|
|
63
|
+
terminal WebSockets go through `HTTPS_PROXY` as a CONNECT tunnel, with any
|
|
64
|
+
`user:password@` in its address sent to the proxy. `HTTP_PROXY` is used only
|
|
65
|
+
for `http://` addresses, never for the HTTPS API. `NO_PROXY` lists hosts to
|
|
66
|
+
reach directly, split by commas or spaces: a name covers its subdomains, a
|
|
67
|
+
leading dot is allowed, `host:port` limits it to one port, and `*` means every
|
|
68
|
+
host. A proxy address without a scheme is `http://`; only `http://` proxies are
|
|
69
|
+
supported, and anything else fails at once with `invalid_proxy`. When a call
|
|
70
|
+
cannot get through, the error names the proxy it tried (without the password),
|
|
71
|
+
for example `No answer from Runtime at https://api.withruntime.com through the
|
|
72
|
+
proxy http://proxy.internal:3128 (HTTPS_PROXY).`
|
|
73
|
+
|
|
74
|
+
## Code written for E2B
|
|
75
|
+
|
|
76
|
+
`withruntime.e2b` runs code written for E2B's Python SDK on Runtime. Change
|
|
77
|
+
the import and set `RUNTIME_API_KEY`:
|
|
78
|
+
|
|
79
|
+
```python no-run
|
|
80
|
+
from withruntime.e2b import Sandbox, AsyncSandbox # was: from e2b import ...
|
|
81
|
+
from withruntime.e2b.code_interpreter import Sandbox # was: from e2b_code_interpreter import Sandbox
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Sandboxes get E2B's defaults: 2 vCPU, 512 MiB and a 300-second timeout.
|
|
85
|
+
Timeouts are in seconds, as in E2B's Python SDK. `runtime_create={...}` passes
|
|
86
|
+
Runtime's own create fields, for example `{"funding": "trial"}`. What Runtime
|
|
87
|
+
does not do the way E2B does raises `NotSupportedException` before anything
|
|
88
|
+
happens, naming what to use instead. Importing `withruntime` alone does not
|
|
89
|
+
load it. The sync `Sandbox` is generated from the async one by
|
|
90
|
+
`scripts/generate_e2b_sync.py`.
|
|
91
|
+
|
|
57
92
|
Docs: https://withruntime.com/docs/python.
|
|
58
93
|
|
|
59
94
|
The package was called `withruntime-cloud`, imported as `runtime_cloud`, until
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Runtime Cloud Python SDK
|
|
2
|
+
|
|
3
|
+
One client for every Runtime Cloud product, sync and async. Python 3.10 or
|
|
4
|
+
later, standard library only.
|
|
5
|
+
|
|
6
|
+
```bash no-run
|
|
7
|
+
pip install withruntime
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The client uses `RUNTIME_API_KEY` when it is set, and otherwise the connection
|
|
11
|
+
this machine saved when `npx withruntime login` connected it (one browser
|
|
12
|
+
approval, no key to copy). On a server, set `RUNTIME_API_KEY` from your secret
|
|
13
|
+
manager (create a key at https://withruntime.com/account/keys). Never put a key
|
|
14
|
+
in source code, a URL or a command-line argument.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from withruntime import Sandbox
|
|
18
|
+
|
|
19
|
+
with Sandbox.create() as sbx:
|
|
20
|
+
result = sbx.exec("python3 -c 'print(6 * 7)'")
|
|
21
|
+
print(result.exit_code, result.stdout)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`Sandbox.create()` needs no arguments and returns once the sandbox is running;
|
|
25
|
+
leaving the `with` block stops it. `AsyncRuntime` is the same client for
|
|
26
|
+
asyncio, method for method:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
from withruntime import AsyncRuntime
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def main():
|
|
34
|
+
async with AsyncRuntime() as runtime:
|
|
35
|
+
async with await runtime.sandboxes.create() as sbx:
|
|
36
|
+
print((await sbx.exec("uname -a")).stdout)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
asyncio.run(main())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A sandbox has `exec`, `exec_stream`, `spawn`, `terminal`, `files` (read, write,
|
|
43
|
+
list, glob, stat, move, remove, upload and download directories), `pause`,
|
|
44
|
+
`wake`, `extend`, `fork` and `snapshot`, and the `interpreter`, `network`,
|
|
45
|
+
`previews` and `desktop` products. The client has `sandboxes`, `images`,
|
|
46
|
+
`volumes`, `snapshots`, `limits`, `feedback` and `support`;
|
|
47
|
+
`runtime.limits.get()` (0.3.1 and later) says whether the key is read-only and
|
|
48
|
+
what its agent may still spend today. Every write carries an
|
|
49
|
+
idempotency key, so retries never do anything twice; errors are typed and carry
|
|
50
|
+
`code`, `hint` and `request_id`.
|
|
51
|
+
|
|
52
|
+
## Behind a proxy
|
|
53
|
+
|
|
54
|
+
The client reads `HTTPS_PROXY`, `HTTP_PROXY` and `NO_PROXY` (upper or lower
|
|
55
|
+
case; lower wins when both are set), sync and async. Calls to the API and
|
|
56
|
+
terminal WebSockets go through `HTTPS_PROXY` as a CONNECT tunnel, with any
|
|
57
|
+
`user:password@` in its address sent to the proxy. `HTTP_PROXY` is used only
|
|
58
|
+
for `http://` addresses, never for the HTTPS API. `NO_PROXY` lists hosts to
|
|
59
|
+
reach directly, split by commas or spaces: a name covers its subdomains, a
|
|
60
|
+
leading dot is allowed, `host:port` limits it to one port, and `*` means every
|
|
61
|
+
host. A proxy address without a scheme is `http://`; only `http://` proxies are
|
|
62
|
+
supported, and anything else fails at once with `invalid_proxy`. When a call
|
|
63
|
+
cannot get through, the error names the proxy it tried (without the password),
|
|
64
|
+
for example `No answer from Runtime at https://api.withruntime.com through the
|
|
65
|
+
proxy http://proxy.internal:3128 (HTTPS_PROXY).`
|
|
66
|
+
|
|
67
|
+
## Code written for E2B
|
|
68
|
+
|
|
69
|
+
`withruntime.e2b` runs code written for E2B's Python SDK on Runtime. Change
|
|
70
|
+
the import and set `RUNTIME_API_KEY`:
|
|
71
|
+
|
|
72
|
+
```python no-run
|
|
73
|
+
from withruntime.e2b import Sandbox, AsyncSandbox # was: from e2b import ...
|
|
74
|
+
from withruntime.e2b.code_interpreter import Sandbox # was: from e2b_code_interpreter import Sandbox
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Sandboxes get E2B's defaults: 2 vCPU, 512 MiB and a 300-second timeout.
|
|
78
|
+
Timeouts are in seconds, as in E2B's Python SDK. `runtime_create={...}` passes
|
|
79
|
+
Runtime's own create fields, for example `{"funding": "trial"}`. What Runtime
|
|
80
|
+
does not do the way E2B does raises `NotSupportedException` before anything
|
|
81
|
+
happens, naming what to use instead. Importing `withruntime` alone does not
|
|
82
|
+
load it. The sync `Sandbox` is generated from the async one by
|
|
83
|
+
`scripts/generate_e2b_sync.py`.
|
|
84
|
+
|
|
85
|
+
Docs: https://withruntime.com/docs/python.
|
|
86
|
+
|
|
87
|
+
The package was called `withruntime-cloud`, imported as `runtime_cloud`, until
|
|
88
|
+
0.3.0. Both names still work: `withruntime-cloud` installs this package and
|
|
89
|
+
`import runtime_cloud` gives you the same classes.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "withruntime"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Runtime Cloud: one client (sync and async) for every Runtime product. Sandboxes first."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -40,6 +40,15 @@ class Sync(unittest.TestCase):
|
|
|
40
40
|
piped = sbx.exec(["cat"], stdin="in", env={"TOKEN": "secret"})
|
|
41
41
|
self.assertIn("<in>", piped.stdout)
|
|
42
42
|
|
|
43
|
+
def test_fork_takes_funding_as_create_does(self):
|
|
44
|
+
# 23 September 2026: fork(funding="trial") was refused as "body.funding is
|
|
45
|
+
# not a known field". Forks are switched off on the fixture, so the real
|
|
46
|
+
# router's answer is the deliberate fork_unavailable, not a 400.
|
|
47
|
+
sbx = client().sandboxes.get(ID)
|
|
48
|
+
with self.assertRaises(ServiceUnavailableError) as caught:
|
|
49
|
+
sbx.fork(funding="trial")
|
|
50
|
+
self.assertEqual(caught.exception.code, "fork_unavailable")
|
|
51
|
+
|
|
43
52
|
def test_check_raises_with_the_output(self):
|
|
44
53
|
sbx = client().sandboxes.get(ID)
|
|
45
54
|
with self.assertRaises(CommandError) as caught:
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""The code interpreter adapter against the fake withruntime SDK."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import sys
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
8
|
+
|
|
9
|
+
from e2b_fake import World # noqa: E402
|
|
10
|
+
|
|
11
|
+
from withruntime.e2b.code_interpreter import (AsyncSandbox, CommandExitException, Context, Execution, # noqa: E402
|
|
12
|
+
NotSupportedException, OutputMessage, Result, Sandbox,
|
|
13
|
+
TimeoutException)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RunCode(unittest.TestCase):
|
|
17
|
+
def setUp(self) -> None:
|
|
18
|
+
self.world = World()
|
|
19
|
+
|
|
20
|
+
def create(self, **kwargs):
|
|
21
|
+
return Sandbox.create(client=self.world.client(), **kwargs)
|
|
22
|
+
|
|
23
|
+
def test_python_by_default(self):
|
|
24
|
+
sbx = self.create()
|
|
25
|
+
execution = sbx.run_code("1 + 1")
|
|
26
|
+
self.assertIsInstance(execution, Execution)
|
|
27
|
+
self.assertEqual(execution.text, "2")
|
|
28
|
+
self.assertIsInstance(execution.results[0], Result)
|
|
29
|
+
self.assertEqual(execution.results[0].formats(), ["text"])
|
|
30
|
+
self.assertEqual((execution.logs.stdout, execution.logs.stderr), (["out 1 + 1\n"], []))
|
|
31
|
+
self.assertEqual(execution.execution_count, 1)
|
|
32
|
+
self.assertIsNone(execution.error)
|
|
33
|
+
self.assertEqual(self.world.called("interpreter.run")[0],
|
|
34
|
+
("1 + 1", {"language": "python", "timeout_ms": 60_000}))
|
|
35
|
+
|
|
36
|
+
def test_base_commands_still_work(self):
|
|
37
|
+
from e2b_fake import Result as Run
|
|
38
|
+
self.world.exec = lambda *_: Run(1, "", "no\n")
|
|
39
|
+
with self.assertRaises(CommandExitException):
|
|
40
|
+
self.create().commands.run("false")
|
|
41
|
+
|
|
42
|
+
def test_rich_results(self):
|
|
43
|
+
self.world.interpreter = lambda *_: {
|
|
44
|
+
"status": "ok", "stdout": "a\nb\npartial", "stderr": "warn\n", "executionCount": 4, "error": None,
|
|
45
|
+
"results": [
|
|
46
|
+
{"main": False, "data": {"image/png": "iVBOR", "text/plain": "<Figure>"}, "refs": {}},
|
|
47
|
+
{"main": True, "data": {"application/vnd.runtime.table+json": {"columns": ["a"]}},
|
|
48
|
+
"refs": {"text/html": {"path": "/workspace/.runtime/interpreter/python/out/1.html"}}}]}
|
|
49
|
+
execution = self.create().run_code("df")
|
|
50
|
+
self.assertEqual(execution.logs.stdout, ["a\n", "b\n", "partial"])
|
|
51
|
+
figure, table = execution.results
|
|
52
|
+
self.assertEqual((figure.png, figure.text, figure.is_main_result), ("iVBOR", "<Figure>", False))
|
|
53
|
+
self.assertEqual(table.html, "bytes of /workspace/.runtime/interpreter/python/out/1.html")
|
|
54
|
+
self.assertEqual(table.extra, {"application/vnd.runtime.table+json": {"columns": ["a"]}})
|
|
55
|
+
self.assertIsNone(table.data)
|
|
56
|
+
self.assertIsNone(table.chart)
|
|
57
|
+
|
|
58
|
+
def test_errors_timeouts_and_refusals(self):
|
|
59
|
+
self.world.interpreter = lambda *_: {"status": "error", "stdout": "", "stderr": "", "results": [],
|
|
60
|
+
"error": {"name": "ZeroDivisionError", "value": "division by zero",
|
|
61
|
+
"traceback": "..."}}
|
|
62
|
+
sbx = self.create()
|
|
63
|
+
self.assertEqual(sbx.run_code("1/0").error.name, "ZeroDivisionError")
|
|
64
|
+
self.world.interpreter = lambda *_: {"status": "timeout", "stdout": "", "stderr": "", "results": []}
|
|
65
|
+
with self.assertRaises(TimeoutException):
|
|
66
|
+
sbx.run_code("while True: pass", timeout=1)
|
|
67
|
+
for language in ("bash", "r", "typescript", "java"):
|
|
68
|
+
with self.assertRaises(NotSupportedException):
|
|
69
|
+
sbx.run_code("1", language=language)
|
|
70
|
+
with self.assertRaises(NotSupportedException):
|
|
71
|
+
sbx.run_code("1", envs={"A": "1"})
|
|
72
|
+
|
|
73
|
+
def test_streams(self):
|
|
74
|
+
sbx = self.create()
|
|
75
|
+
out, results = [], []
|
|
76
|
+
sbx.run_code("print(1)", on_stdout=out.append, on_result=results.append)
|
|
77
|
+
self.assertIsInstance(out[0], OutputMessage)
|
|
78
|
+
self.assertEqual((out[0].line, out[0].error), ("out print(1)\n", False))
|
|
79
|
+
self.assertEqual([one.text for one in results], ["2"])
|
|
80
|
+
|
|
81
|
+
def test_sandbox_envs_reach_code_through_one_context(self):
|
|
82
|
+
sbx = self.create(envs={"TOKEN": "t"})
|
|
83
|
+
sbx.run_code("import os")
|
|
84
|
+
sbx.run_code("os.environ['TOKEN']")
|
|
85
|
+
self.assertEqual(self.world.called("contexts.create"),
|
|
86
|
+
[({"id": "e2b-python", "language": "python", "env": {"TOKEN": "t"}},)])
|
|
87
|
+
self.assertEqual([call[1] for call in self.world.called("interpreter.run")],
|
|
88
|
+
[{"context": "e2b-python", "timeout_ms": 60_000}] * 2)
|
|
89
|
+
other = Sandbox.create(client=self.world.client(), envs={"TOKEN": "t"})
|
|
90
|
+
other.runtime.contexts["e2b-python"] = {"id": "e2b-python", "language": "python", "cwd": "/workspace"}
|
|
91
|
+
other.run_code("1")
|
|
92
|
+
self.assertEqual(self.world.called("interpreter.run")[-1][1]["context"], "e2b-python")
|
|
93
|
+
|
|
94
|
+
def test_contexts(self):
|
|
95
|
+
sbx = self.create()
|
|
96
|
+
context = sbx.create_code_context(cwd="/tmp", language="javascript")
|
|
97
|
+
self.assertEqual(context, Context("ctx-1", "javascript", "/tmp"))
|
|
98
|
+
self.assertEqual(sbx.list_code_contexts(), [context])
|
|
99
|
+
sbx.run_code("x", context=context)
|
|
100
|
+
self.assertEqual(self.world.called("interpreter.run")[0][1], {"context": "ctx-1", "timeout_ms": 60_000})
|
|
101
|
+
sbx.restart_code_context(context)
|
|
102
|
+
sbx.remove_code_context("ctx-1")
|
|
103
|
+
self.assertEqual(self.world.called("contexts.remove"), [("ctx-1",)])
|
|
104
|
+
with self.assertRaises(NotSupportedException):
|
|
105
|
+
sbx.create_code_context(language="r")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class AsyncRunCode(unittest.TestCase):
|
|
109
|
+
def test_async(self):
|
|
110
|
+
world = World()
|
|
111
|
+
|
|
112
|
+
async def scenario():
|
|
113
|
+
seen = []
|
|
114
|
+
|
|
115
|
+
async def on_stdout(message):
|
|
116
|
+
seen.append(message.line)
|
|
117
|
+
|
|
118
|
+
sbx = await AsyncSandbox.create(client=world.async_client())
|
|
119
|
+
execution = await sbx.run_code("1 + 1", on_stdout=on_stdout)
|
|
120
|
+
await asyncio.sleep(0)
|
|
121
|
+
self.assertEqual(execution.text, "2")
|
|
122
|
+
self.assertEqual(seen, ["out 1 + 1\n"])
|
|
123
|
+
context = await sbx.create_code_context(language="python")
|
|
124
|
+
self.assertEqual((await sbx.list_code_contexts())[0].id, context.id)
|
|
125
|
+
|
|
126
|
+
asyncio.run(scenario())
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
unittest.main()
|