impello 0.2.0__py3-none-any.whl
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.
- impello/__init__.py +84 -0
- impello/_defaults.py +57 -0
- impello-0.2.0.dist-info/METADATA +109 -0
- impello-0.2.0.dist-info/RECORD +5 -0
- impello-0.2.0.dist-info/WHEEL +4 -0
impello/__init__.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""The Python SDK for Impello sandboxes.
|
|
2
|
+
|
|
3
|
+
Impello runs its own build of E2B's open-source ``infra``, so the sandbox API
|
|
4
|
+
is the same API. Unlike the TypeScript client, the Python one needs no changes
|
|
5
|
+
to work against it: there is no ``e2b_`` key pattern anywhere in ``e2b``, so it
|
|
6
|
+
takes an ``imp_`` key today. All this package has to supply is where to send
|
|
7
|
+
the request.
|
|
8
|
+
|
|
9
|
+
So this is a wrapper, and it is deliberately thin. It will be replaced by a
|
|
10
|
+
full clone before we take payments, for the same reason the TypeScript client
|
|
11
|
+
is one: ``pip show impello`` printing ``e2b`` is a poor thing for a paid
|
|
12
|
+
product to say about itself, and the client sends a ``publisher: e2b`` header
|
|
13
|
+
to our own servers. See ``docs/decisions/sdk-self-contained.md``.
|
|
14
|
+
|
|
15
|
+
Usage is E2B's, unchanged::
|
|
16
|
+
|
|
17
|
+
from impello import Sandbox
|
|
18
|
+
|
|
19
|
+
sandbox = Sandbox.create()
|
|
20
|
+
result = sandbox.commands.run("echo hello")
|
|
21
|
+
sandbox.pause()
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import e2b as _e2b
|
|
25
|
+
from e2b import * # noqa: F401,F403
|
|
26
|
+
from e2b.connection_config import ApiParams, merge_api_params
|
|
27
|
+
|
|
28
|
+
from ._defaults import DEFAULT_DOMAIN, defaults
|
|
29
|
+
|
|
30
|
+
__version__ = "0.2.0"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _ImpelloDefaults:
|
|
34
|
+
"""Bind Impello's settings underneath every call.
|
|
35
|
+
|
|
36
|
+
``ClientFactory._resolve_api_params`` is E2B's own seam for this: every
|
|
37
|
+
class method funnels through it, and ``merge_api_params`` already gives
|
|
38
|
+
per-call params precedence over bound ones. So this overrides one method
|
|
39
|
+
instead of mirroring the twelve descriptors on ``Sandbox``, and it changes
|
|
40
|
+
nothing about ``e2b`` itself. Importing this package does not retarget a
|
|
41
|
+
plain ``e2b`` call in the same process, and it does not touch ``os.environ``.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _resolve_api_params(cls, **opts: "ApiParams") -> "ApiParams":
|
|
46
|
+
return merge_api_params(defaults(), super()._resolve_api_params(**opts))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Sandbox(_ImpelloDefaults, _e2b.Sandbox):
|
|
50
|
+
"""An Impello sandbox."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AsyncSandbox(_ImpelloDefaults, _e2b.AsyncSandbox):
|
|
54
|
+
"""An Impello sandbox, async."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Impello(_e2b.E2B):
|
|
58
|
+
"""A client with its connection settings bound explicitly.
|
|
59
|
+
|
|
60
|
+
Use this instead of the module-level ``Sandbox`` when one process talks to
|
|
61
|
+
more than one account or more than one fleet. Clients are isolated from
|
|
62
|
+
each other. Params passed here beat the environment; params passed to a
|
|
63
|
+
single call beat both.
|
|
64
|
+
|
|
65
|
+
Example::
|
|
66
|
+
|
|
67
|
+
from impello import Impello
|
|
68
|
+
|
|
69
|
+
client = Impello(api_key="imp_...")
|
|
70
|
+
sandbox = client.Sandbox.create()
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, **opts: "ApiParams"):
|
|
74
|
+
super().__init__(**merge_api_params(defaults(), opts))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
*[name for name in dir(_e2b) if not name.startswith("_")],
|
|
79
|
+
"Sandbox",
|
|
80
|
+
"AsyncSandbox",
|
|
81
|
+
"Impello",
|
|
82
|
+
"DEFAULT_DOMAIN",
|
|
83
|
+
"__version__",
|
|
84
|
+
]
|
impello/_defaults.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Where an Impello setting comes from, and in what order.
|
|
2
|
+
|
|
3
|
+
Every setting reads an ``IMPELLO_`` name first, then the matching ``E2B_``
|
|
4
|
+
name. The fallback is the migration, not politeness: callers already export
|
|
5
|
+
``E2B_API_KEY``, ``E2B_API_URL`` and ``E2B_DOMAIN``, and the flag day that
|
|
6
|
+
changes the key prefix is hard enough without renaming every environment on the
|
|
7
|
+
same afternoon. Rename when convenient. Nothing breaks if you never do.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from e2b.connection_config import ApiParams
|
|
14
|
+
|
|
15
|
+
DEFAULT_DOMAIN = "sandbox.impello.ai"
|
|
16
|
+
|
|
17
|
+
# Each entry is one setting and the ``ApiParams`` key it fills.
|
|
18
|
+
_SETTINGS = (
|
|
19
|
+
("API_KEY", "api_key"),
|
|
20
|
+
("API_URL", "api_url"),
|
|
21
|
+
("DOMAIN", "domain"),
|
|
22
|
+
("SANDBOX_URL", "sandbox_url"),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read(name: str) -> Optional[str]:
|
|
27
|
+
"""Read one setting, Impello's name first."""
|
|
28
|
+
return os.getenv(f"IMPELLO_{name}") or os.getenv(f"E2B_{name}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def defaults() -> ApiParams:
|
|
32
|
+
"""The params bound to every Impello call, resolved fresh each time.
|
|
33
|
+
|
|
34
|
+
Read at call time rather than at import, so a test or a caller that sets an
|
|
35
|
+
environment variable after importing this package still gets it.
|
|
36
|
+
|
|
37
|
+
A setting that is absent is left out of the result. It must not appear as
|
|
38
|
+
``None``, because ``merge_api_params`` drops a ``None`` from the per-call
|
|
39
|
+
params but keeps one in the bound params, where it would clear the value
|
|
40
|
+
the SDK would otherwise resolve for itself.
|
|
41
|
+
"""
|
|
42
|
+
params = {}
|
|
43
|
+
|
|
44
|
+
for name, key in _SETTINGS:
|
|
45
|
+
value = _read(name)
|
|
46
|
+
if value:
|
|
47
|
+
params[key] = value
|
|
48
|
+
|
|
49
|
+
debug = _read("DEBUG")
|
|
50
|
+
if debug:
|
|
51
|
+
params["debug"] = debug.lower() == "true"
|
|
52
|
+
|
|
53
|
+
# Only the domain has a default. Without one the SDK falls back to E2B's
|
|
54
|
+
# own `e2b.app`, and the failure is a connection to somebody else's fleet.
|
|
55
|
+
params.setdefault("domain", DEFAULT_DOMAIN)
|
|
56
|
+
|
|
57
|
+
return params
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: impello
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python SDK for Impello sandboxes.
|
|
5
|
+
Project-URL: Homepage, https://impello.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/21-Dreams/impello
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: agents,code-interpreter,impello,microvm,sandbox
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: e2b<3,>=2.46.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# `impello`
|
|
14
|
+
|
|
15
|
+
The Python SDK for Impello sandboxes.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install impello
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from impello import Sandbox
|
|
27
|
+
|
|
28
|
+
sandbox = Sandbox.create()
|
|
29
|
+
result = sandbox.commands.run("echo hello")
|
|
30
|
+
|
|
31
|
+
print(result.stdout)
|
|
32
|
+
|
|
33
|
+
sandbox.pause()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Set your key in the environment, or pass it to each call:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export IMPELLO_API_KEY=imp_...
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
Sandbox.list(api_key="imp_...")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For a process that talks to more than one account or more than one fleet, bind
|
|
47
|
+
the settings to a client instead:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from impello import Impello
|
|
51
|
+
|
|
52
|
+
client = Impello(api_key="imp_...")
|
|
53
|
+
sandbox = client.Sandbox.create()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Settings
|
|
57
|
+
|
|
58
|
+
Every setting reads an `IMPELLO_` name first, then the matching `E2B_` name.
|
|
59
|
+
|
|
60
|
+
| Variable | Default | What it does |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `IMPELLO_API_KEY` | none | The key. Starts with `imp_` |
|
|
63
|
+
| `IMPELLO_DOMAIN` | `sandbox.impello.ai` | The domain the API and the sandboxes sit on |
|
|
64
|
+
| `IMPELLO_API_URL` | `https://api.<domain>` | The API address. Set this to reach a self-hosted API |
|
|
65
|
+
| `IMPELLO_SANDBOX_URL` | derived from the domain | The address a sandbox is reached at |
|
|
66
|
+
| `IMPELLO_DEBUG` | `false` | Talk to `http://localhost:3000` |
|
|
67
|
+
|
|
68
|
+
The `E2B_` fallback is the migration, not politeness. Callers already export
|
|
69
|
+
`E2B_API_KEY`, `E2B_API_URL` and `E2B_DOMAIN`. Rename when it suits you.
|
|
70
|
+
Nothing breaks if you never do.
|
|
71
|
+
|
|
72
|
+
Only the domain has a default. Without one the SDK falls back to E2B's own
|
|
73
|
+
`e2b.app`, and the failure is a connection to somebody else's fleet.
|
|
74
|
+
|
|
75
|
+
Params passed to a single call beat a client's params, which beat the
|
|
76
|
+
environment. That is E2B's own rule and this package keeps it.
|
|
77
|
+
|
|
78
|
+
## Why this one is a wrapper, when the TypeScript one is a copy
|
|
79
|
+
|
|
80
|
+
The TypeScript client cannot talk to Impello at all. Its `validateApiKey` runs
|
|
81
|
+
inside the `ApiClient` constructor, is not exported, has no off switch, and
|
|
82
|
+
accepts only `e2b_`. So `@impello/sdk` had to be a copy.
|
|
83
|
+
|
|
84
|
+
Python has no `e2b_` pattern anywhere. It takes an `imp_` key today. So the
|
|
85
|
+
only thing missing is where to send the request, and that is all this package
|
|
86
|
+
supplies.
|
|
87
|
+
|
|
88
|
+
It supplies it through `ClientFactory._resolve_api_params`, which is E2B's own
|
|
89
|
+
seam for binding defaults. That means one method is overridden rather than the
|
|
90
|
+
twelve descriptors on `Sandbox`, and **`e2b` itself is untouched**: importing
|
|
91
|
+
`impello` does not retarget a plain `e2b` call in the same process, and it
|
|
92
|
+
never writes to `os.environ`. A test holds that.
|
|
93
|
+
|
|
94
|
+
## This package will be replaced
|
|
95
|
+
|
|
96
|
+
A permanent wrapper is the worst outcome. `pip show impello` prints `e2b`, and
|
|
97
|
+
the client sends a `publisher: e2b` header to our own servers. So the wrapper
|
|
98
|
+
ships now and the full clone lands before we take payments, with
|
|
99
|
+
`e2b/api/client/` regenerated from our own spec.
|
|
100
|
+
|
|
101
|
+
See `docs/decisions/sdk-self-contained.md`.
|
|
102
|
+
|
|
103
|
+
## Develop
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python -m venv .venv
|
|
107
|
+
./.venv/bin/pip install -e . pytest
|
|
108
|
+
./.venv/bin/python -m pytest
|
|
109
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
impello/__init__.py,sha256=q0y_VBGCMoc7I2rejzF_2BjBgz-jltSmT87aExAwolE,2773
|
|
2
|
+
impello/_defaults.py,sha256=CXTF0-azML2Ph1el9Rr1P0Fgd9ZKHVLfUIcGOm5Z89M,1910
|
|
3
|
+
impello-0.2.0.dist-info/METADATA,sha256=viSALj9ELZONVDtmF1KipvR7HGSuDIxjvkIkUn5musk,3287
|
|
4
|
+
impello-0.2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
5
|
+
impello-0.2.0.dist-info/RECORD,,
|