Skip to content

Commit cce1ea6

Browse files
authored
Merge branch 'main' into dependabot/github_actions/actions/upload-pages-artifact-4
2 parents 797cfeb + 9f7c390 commit cce1ea6

63 files changed

Lines changed: 6368 additions & 1645 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ coverage.xml
1010
.hatch
1111
.pytest_cache/
1212
junit.xml
13+
.beamignore
1314
# Jupyter Notebook
1415
.ipynb_checkpoints
1516
# pyenv
@@ -30,3 +31,4 @@ junit.xml
3031
uv.lock
3132
# DiskCache cache file
3233
./model_cache
34+
node_modules/

README.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,258 @@
2424
[![PyUp](https://pyup.io/repos/github/phil65/exxec/shield.svg)](https://pyup.io/repos/github/phil65/exxec/)
2525

2626
[Read the documentation!](https://phil65.github.io/exxec/)
27+
28+
29+
30+
### Basic Usage
31+
32+
Use the `get_environment()` function to create execution environments:
33+
34+
```python
35+
from exxec import get_environment
36+
37+
# Local execution (same process)
38+
env = get_environment("local")
39+
40+
# Subprocess execution (separate process when executing python code)
41+
env = get_environment("local", isolated=True)
42+
43+
# Docker execution (containerized)
44+
env = get_environment("docker")
45+
46+
# Execute code
47+
async with env:
48+
result = await env.execute("""
49+
async def main():
50+
return "Hello from execution environment!"
51+
""")
52+
print(result.result) # "Hello from execution environment!"
53+
```
54+
55+
## Available Providers
56+
57+
### Local Provider
58+
Executes code in the same Python process. Fastest option but offers no isolation.
59+
60+
```python
61+
env = get_environment("local", timeout=30.0)
62+
```
63+
64+
**Parameters:**
65+
- `timeout` (float): Execution timeout in seconds (default: 30.0)
66+
- `isolated` (bool): Whether to execute code in a separate process (default: False)
67+
- `language` (Language): Programming language (default: "python")
68+
69+
70+
### Docker Provider
71+
Executes code in Docker containers for strong isolation and reproducible environments.
72+
73+
```python
74+
env = get_environment(
75+
"docker",
76+
image="python:3.13-slim",
77+
timeout=60.0,
78+
language="python"
79+
)
80+
```
81+
82+
**Parameters:**
83+
- `lifespan_handler`: Tool server context manager (optional)
84+
- `image` (str): Docker image to use (default: "python:3.13-slim")
85+
- `timeout` (float): Execution timeout in seconds (default: 60.0)
86+
- `language` (Language): Programming language (default: "python")
87+
88+
89+
### Daytona Provider
90+
Executes code in remote Daytona sandboxes for cloud-based development environments.
91+
92+
```python
93+
env = get_environment(
94+
"daytona",
95+
api_url="https://api.daytona.io",
96+
api_key="your-api-key",
97+
timeout=300.0,
98+
keep_alive=False
99+
)
100+
```
101+
102+
**Parameters:**
103+
- `api_url` (str): Daytona API URL (optional, uses env vars if not provided)
104+
- `api_key` (str): API key for authentication (optional)
105+
- `target` (str): Target configuration (optional)
106+
- `image` (str): Container image (default: "python:3.13-slim")
107+
- `timeout` (float): Execution timeout in seconds (default: 300.0)
108+
- `keep_alive` (bool): Keep sandbox running after execution (default: False)
109+
110+
111+
### E2B Provider
112+
Executes code in E2B sandboxes for secure, ephemeral execution environments.
113+
114+
```python
115+
env = get_environment(
116+
"e2b",
117+
template="python",
118+
timeout=300.0,
119+
keep_alive=False,
120+
language="python"
121+
)
122+
```
123+
124+
**Parameters:**
125+
- `template` (str): E2B template to use (optional)
126+
- `timeout` (float): Execution timeout in seconds (default: 300.0)
127+
- `keep_alive` (bool): Keep sandbox running after execution (default: False)
128+
- `language` (Language): Programming language (default: "python")
129+
130+
131+
### Beam Provider
132+
Executes code in Beam cloud sandboxes for scalable, serverless execution environments.
133+
134+
```python
135+
env = get_environment(
136+
"beam",
137+
cpu=1.0,
138+
memory=128,
139+
keep_warm_seconds=600,
140+
timeout=300.0,
141+
language="python"
142+
)
143+
```
144+
145+
**Parameters:**
146+
- `cpu` (float | str): CPU cores allocated to the container (default: 1.0)
147+
- `memory` (int | str): Memory allocated to the container in MiB (default: 128)
148+
- `keep_warm_seconds` (int): Seconds to keep sandbox alive, -1 for no timeout (default: 600)
149+
- `timeout` (float): Execution timeout in seconds (default: 300.0)
150+
- `language` (Language): Programming language (default: "python")
151+
152+
153+
### MCP Provider
154+
Executes Python code with Model Context Protocol support for AI integrations.
155+
156+
```python
157+
env = get_environment(
158+
"mcp",
159+
dependencies=["requests", "numpy"],
160+
allow_networking=True,
161+
timeout=30.0
162+
)
163+
```
164+
165+
**Parameters:**
166+
- `dependencies` (list[str]): Python packages to install (optional)
167+
- `allow_networking` (bool): Allow network access (default: True)
168+
- `timeout` (float): Execution timeout in seconds (default: 30.0)
169+
170+
171+
## Code Execution Patterns
172+
173+
All providers support two execution patterns:
174+
175+
### 1. Main Function Pattern
176+
```python
177+
code = """
178+
async def main():
179+
# Your code here
180+
return "result"
181+
"""
182+
```
183+
184+
### 2. Result Variable Pattern
185+
```python
186+
code = """
187+
import math
188+
_result = math.pi * 2
189+
"""
190+
```
191+
192+
## Error Handling
193+
194+
Execution results include comprehensive error information:
195+
196+
```python
197+
async with env:
198+
result = await env.execute(code)
199+
if result.success:
200+
print(f"Result: {result.result}")
201+
print(f"Duration: {result.duration:.3f}s")
202+
else:
203+
print(f"Error: {result.error}")
204+
print(f"Error Type: {result.error_type}")
205+
```
206+
207+
## Multi-Language Support
208+
209+
Some providers support multiple programming languages:
210+
211+
```python
212+
# JavaScript execution
213+
env = get_environment("subprocess", language="javascript", executable="node")
214+
215+
# TypeScript execution
216+
env = get_environment("docker", language="typescript", image="node:18")
217+
```
218+
219+
## Advanced Usage
220+
221+
### Context Managers
222+
All environments are async context managers for proper resource cleanup:
223+
224+
```python
225+
async with get_environment("docker") as env:
226+
result1 = await env.execute(code1)
227+
result2 = await env.execute(code2) # Reuses same container
228+
# Container automatically cleaned up
229+
```
230+
231+
### Custom Configurations
232+
Each provider supports environment-specific customization:
233+
234+
```python
235+
# Docker with custom image and networking
236+
env = get_environment(
237+
"docker",
238+
image="tensorflow/tensorflow:latest-py3",
239+
timeout=600.0
240+
)
241+
242+
# Subprocess with specific Python version
243+
env = get_environment(
244+
"subprocess",
245+
executable="/usr/bin/python3.11",
246+
timeout=120.0
247+
)
248+
```
249+
250+
### Streaming Output
251+
252+
Some providers support streaming output line by line, useful for long-running processes:
253+
254+
```python
255+
from exxec import get_environment
256+
257+
# Stream output from subprocess execution
258+
env = get_environment("subprocess")
259+
260+
async with env:
261+
async for line in env.execute_stream("""
262+
import time
263+
for i in range(5):
264+
print(f"Processing step {i+1}...")
265+
time.sleep(1)
266+
print("Done!")
267+
"""):
268+
print(f"Live output: {line}")
269+
270+
# Also works with Docker execution
271+
env = get_environment("docker")
272+
async with env:
273+
async for line in env.execute_stream(code):
274+
# Process each line as it's produced
275+
if "ERROR" in line:
276+
print(f"⚠️ {line}")
277+
else:
278+
print(f"{line}")
279+
```
280+
281+
**Supported providers:** `docker`, `local`, `beam`, `e2b`, `modal`, `vercel`, `ssh`, `daytona`

pyproject.toml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[project]
44
name = "exxec"
5-
version = "0.1.0"
5+
version = "0.4.0"
66
description = "Execution environments"
77
readme = "README.md"
88
requires-python = ">=3.13"
@@ -29,6 +29,8 @@ classifiers = [
2929
"Typing :: Typed",
3030
]
3131
dependencies = [
32+
"anyenv>=1.11.6",
33+
"ptyprocess>=0.7.0",
3234
"pydantic",
3335
"schemez",
3436
# Only add below (Copier)
@@ -44,12 +46,15 @@ Source = "https://github.com/phil65/exxec"
4446

4547
[project.optional-dependencies]
4648
beam = ["beam-client>=0.2.187"]
49+
cloudflare = ["httpx>=0.27"]
4750
daytona = ["daytona; python_version < '3.14'"]
48-
docker = ["testcontainers", "morefs"]
51+
docker = ["testcontainers"]
4952
e2b = ["e2b-code-interpreter"]
53+
hopx = ["hopx-ai"]
5054
mcp = ["fastmcp", "mcp-run-python"]
5155
microsandbox = ["microsandbox"]
5256
modal = ["modal"]
57+
sprites = ["sprites-py"]
5358
ssh = ["asyncssh", "sshfs"]
5459
vercel = ["vercel"]
5560

@@ -101,8 +106,8 @@ docstring-style = "google"
101106

102107
[tool.mypy]
103108
python_version = "3.13"
104-
disable_error_code = ["misc", "import"]
105109
pretty = true
110+
strict = true
106111
check_untyped_defs = true
107112
exclude = ["venv/", ".venv/", "tests/"]
108113
plugins = ["pydantic.mypy"]
@@ -127,9 +132,13 @@ reportSelfClsParameterName = false
127132
reportPrivateImportUsage = false
128133

129134
[tool.pytest]
135+
addopts = ["-m", "not integration"]
130136
log_cli = true
131137
log_date_format = "%Y-%m-%d %H:%M:%S"
132138
log_format = "%(asctime)s %(levelname)s %(message)s"
139+
markers = [
140+
"integration: marks tests as integration tests (deselected by default)",
141+
]
133142
minversion = "9.0"
134143
testpaths = ["tests/"]
135144
asyncio_mode = "auto"
@@ -281,10 +290,20 @@ preview = true
281290
python-version = "3.13"
282291
python-platform = "all"
283292

293+
[tool.ty.rules]
294+
unresolved-import = "ignore"
295+
unused-type-ignore-comment = "ignore"
296+
284297
[tool.uv]
285298
default-groups = ["dev", "lint", "docs"]
299+
# Override to resolve beam-client (<14) vs daytona (>=15) websockets conflict
300+
override-dependencies = ["websockets>=15.0.0"]
286301

287302
[tool.uv.build-backend]
303+
module-name = [
304+
"exxec",
305+
"exxec_config",
306+
]
288307
wheel-exclude = [
289308
".mypy_cache/**",
290309
"__pycache__/**",

0 commit comments

Comments
 (0)