opensandbox-code-interpreter 1.1.0__tar.gz → 1.1.1__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.
Files changed (25) hide show
  1. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/PKG-INFO +2 -2
  2. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/pyproject.toml +1 -1
  3. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/adapters/code_adapter.py +34 -17
  4. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/code_interpreter.py +44 -10
  5. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/models/code_sync.py +1 -0
  6. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/services/code.py +3 -3
  7. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/adapters/code_adapter.py +32 -8
  8. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/code_interpreter.py +34 -6
  9. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/.gitignore +0 -0
  10. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/LICENSE +0 -0
  11. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/README.md +0 -0
  12. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/__init__.py +0 -0
  13. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/adapters/__init__.py +0 -0
  14. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/adapters/converter/__init__.py +0 -0
  15. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/adapters/converter/code_execution_converter.py +0 -0
  16. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/adapters/factory.py +0 -0
  17. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/models/__init__.py +0 -0
  18. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/models/code.py +0 -0
  19. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/py.typed +0 -0
  20. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/services/__init__.py +0 -0
  21. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/__init__.py +0 -0
  22. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/adapters/__init__.py +0 -0
  23. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/adapters/factory.py +0 -0
  24. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/services/__init__.py +0 -0
  25. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.1.1}/src/code_interpreter/sync/services/code.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opensandbox-code-interpreter
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: OpenSandbox Code Interpreter Python SDK - Advanced code execution with persistent contexts
5
5
  Project-URL: Homepage, https://open-sandbox.ai
6
6
  Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
@@ -223,7 +223,7 @@ Classifier: Programming Language :: Python :: 3.13
223
223
  Classifier: Topic :: Software Development :: Libraries
224
224
  Classifier: Typing :: Typed
225
225
  Requires-Python: >=3.10
226
- Requires-Dist: opensandbox<2.0.0,>=1.1.0
226
+ Requires-Dist: opensandbox<2.0.0,>=1.1.1
227
227
  Requires-Dist: pydantic<3.0,>=2.4.2
228
228
  Description-Content-Type: text/markdown
229
229
 
@@ -43,7 +43,7 @@ classifiers = [
43
43
  ]
44
44
  dependencies = [
45
45
  "pydantic>=2.4.2,<3.0",
46
- "opensandbox>=1.1.0,<2.0.0",
46
+ "opensandbox>=1.1.1,<2.0.0",
47
47
  ]
48
48
 
49
49
  [project.urls]
@@ -72,14 +72,8 @@ class CodesAdapter(Codes):
72
72
  """
73
73
  Adapter implementation for code execution service.
74
74
 
75
- This adapter wraps auto-generated API clients and provides the concrete
76
- implementation of the Codes interface. It handles both standard
77
- API calls and SSE streaming for real-time code execution output.
78
-
79
- Similar to CommandServiceAdapter, this adapter uses:
80
- - Generated API clients for simple operations (create_context, interrupt)
81
- - Direct httpx SSE streaming for run
82
- - ExceptionConverter for unified exception handling
75
+ Handles standard API calls plus direct httpx SSE streaming for
76
+ real-time code execution output.
83
77
  """
84
78
 
85
79
  RUN_CODE_PATH = "/code"
@@ -104,11 +98,7 @@ class CodesAdapter(Codes):
104
98
  timeout_seconds = self.connection_config.request_timeout.total_seconds()
105
99
  timeout = httpx.Timeout(timeout_seconds)
106
100
 
107
- headers = {
108
- "User-Agent": self.connection_config.user_agent,
109
- **self.connection_config.headers,
110
- **self.execd_endpoint.headers,
111
- }
101
+ headers = self.execd_endpoint.build_request_headers(self.connection_config)
112
102
 
113
103
  # Execd API does not require authentication
114
104
  self._client = Client(
@@ -116,12 +106,19 @@ class CodesAdapter(Codes):
116
106
  timeout=timeout,
117
107
  )
118
108
 
119
- # Inject httpx client (adapter-owned)
109
+ # Adapter clients must own their transports: aclose() closes them,
110
+ # and the shared/user connection_config transport must survive that.
111
+ self._owns_clients = self.connection_config.owns_transport
112
+ main_transport: httpx.AsyncBaseTransport | None
113
+ if self._owns_clients:
114
+ main_transport = self.connection_config.new_owned_transport()
115
+ else:
116
+ main_transport = self.connection_config.transport
120
117
  self._httpx_client = httpx.AsyncClient(
121
118
  base_url=base_url,
122
119
  headers=headers,
123
120
  timeout=timeout,
124
- transport=self.connection_config.transport,
121
+ transport=main_transport,
125
122
  )
126
123
  self._client.set_async_httpx_client(self._httpx_client)
127
124
 
@@ -134,6 +131,11 @@ class CodesAdapter(Codes):
134
131
  # SSE bootstraps bypass the retry wrapper: request bodies are
135
132
  # not replayable and a non-idempotent status opt-in would cause
136
133
  # duplicate execution on a resent SSE POST.
134
+ sse_transport = (
135
+ self.connection_config.new_owned_transport()
136
+ if self._owns_clients
137
+ else self.connection_config.transport
138
+ )
137
139
  self._sse_client = httpx.AsyncClient(
138
140
  headers=sse_headers,
139
141
  timeout=httpx.Timeout(
@@ -142,7 +144,7 @@ class CodesAdapter(Codes):
142
144
  write=timeout_seconds,
143
145
  pool=None,
144
146
  ),
145
- transport=unwrap_retry_transport(self.connection_config.transport),
147
+ transport=unwrap_retry_transport(sse_transport),
146
148
  )
147
149
 
148
150
  async def _get_client(self):
@@ -158,6 +160,21 @@ class CodesAdapter(Codes):
158
160
  """Return SSE client (read timeout disabled) for execd streaming."""
159
161
  return self._sse_client
160
162
 
163
+ async def aclose(self) -> None:
164
+ """Release the adapter-owned HTTP clients.
165
+
166
+ Never closes the shared ``connection_config.transport``; the sandbox
167
+ stays usable after ``aclose()``. Clients wrapping a user-supplied
168
+ transport are left open (httpx would close the caller's transport
169
+ with them).
170
+ """
171
+ if not self._owns_clients:
172
+ return
173
+ try:
174
+ await self._httpx_client.aclose()
175
+ finally:
176
+ await self._sse_client.aclose()
177
+
161
178
  async def create_context(self, language: str) -> CodeContext:
162
179
  """
163
180
  Creates a new execution context for code interpretation.
@@ -268,7 +285,7 @@ class CodesAdapter(Codes):
268
285
  """
269
286
  Executes code within the specified context using SSE streaming.
270
287
 
271
- Similar to CommandServiceAdapter.run, this uses direct httpx
288
+ Uses direct httpx
272
289
  streaming to handle SSE responses from the execd service.
273
290
  """
274
291
  if not code.strip():
@@ -22,6 +22,7 @@ support, session management, and variable persistence.
22
22
  """
23
23
 
24
24
  import asyncio
25
+ import contextlib
25
26
  import logging
26
27
  import time
27
28
  from datetime import timedelta
@@ -64,7 +65,7 @@ class CodeInterpreter:
64
65
 
65
66
  Key Features:
66
67
 
67
- - Multi-language Code Execution: Support for Python, JavaScript, Bash, Java, Kotlin
68
+ - Multi-language Code Execution: Python, JavaScript, TypeScript, Go, Bash
68
69
  - Session Management: Persistent execution contexts with variable state
69
70
  - Sandbox Integration: Full access to underlying sandbox file system and command execution
70
71
  - Streaming Execution: Real-time code execution with output streaming
@@ -73,11 +74,12 @@ class CodeInterpreter:
73
74
  Usage Example:
74
75
 
75
76
  ```python
76
- # First create a sandbox instance
77
-
77
+ # The sandbox must use the opensandbox/code-interpreter image (or a
78
+ # derivative); the strict health check requires the Jupyter runtime.
78
79
  sandbox = await Sandbox.create(
79
- "python:3.11",
80
- resource={"cpu": "1", "memory": "2Gi"}
80
+ "opensandbox/code-interpreter:latest",
81
+ entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
82
+ resource={"cpu": "1", "memory": "2Gi"},
81
83
  )
82
84
 
83
85
  # Then create a code interpreter wrapping the sandbox
@@ -87,9 +89,10 @@ class CodeInterpreter:
87
89
  from code_interpreter.models.code import SupportedLanguage
88
90
  context = await interpreter.codes.create_context(SupportedLanguage.PYTHON)
89
91
  result = await interpreter.codes.run("print('Hello World')", context=context)
90
- print(result.logs.stdout) # Output: Hello World
92
+ print(result.text) # Output: Hello World
91
93
 
92
94
  # Access underlying sandbox for file operations
95
+ from opensandbox.models.filesystem import WriteEntry
93
96
  await interpreter.sandbox.files.write_files([
94
97
  WriteEntry(path="data.txt", data="Hello")
95
98
  ])
@@ -98,10 +101,16 @@ class CodeInterpreter:
98
101
  context=context,
99
102
  )
100
103
 
101
- # Always clean up resources
104
+ # Clean up: closing the interpreter releases its HTTP clients; the
105
+ # underlying sandbox lifecycle stays caller-owned.
106
+ await interpreter.aclose()
102
107
  await sandbox.kill()
103
108
  await sandbox.close()
104
109
  ```
110
+
111
+ CodeInterpreter also supports the async context-manager protocol; exiting
112
+ the ``async with`` block releases the interpreter's HTTP clients (not the
113
+ sandbox).
105
114
  """
106
115
 
107
116
  def __init__(self, sandbox: Sandbox, code_service: Codes) -> None:
@@ -161,6 +170,24 @@ class CodeInterpreter:
161
170
  """
162
171
  return self._sandbox.commands
163
172
 
173
+ async def aclose(self) -> None:
174
+ """
175
+ Release resources owned by the code interpreter.
176
+
177
+ Closes the HTTP clients used by the code execution service. The
178
+ underlying sandbox is NOT affected; manage its lifecycle separately
179
+ (``sandbox.kill()`` / ``sandbox.close()``). Safe to call multiple times.
180
+ """
181
+ aclose = getattr(self._code_service, "aclose", None)
182
+ if aclose is not None:
183
+ await aclose()
184
+
185
+ async def __aenter__(self) -> "CodeInterpreter":
186
+ return self
187
+
188
+ async def __aexit__(self, exc_type, exc, tb) -> None:
189
+ await self.aclose()
190
+
164
191
  @property
165
192
  def metrics(self):
166
193
  """
@@ -335,9 +362,16 @@ class CodeInterpreter:
335
362
  )
336
363
 
337
364
  interpreter = cls(sandbox, code_execution_service)
338
-
339
- if not skip_health_check:
340
- await interpreter.check_ready(ready_timeout, health_check_polling_interval)
365
+ try:
366
+ if not skip_health_check:
367
+ await interpreter.check_ready(
368
+ ready_timeout, health_check_polling_interval
369
+ )
370
+ except Exception:
371
+ # Don't leak the service's clients on a failed creation.
372
+ with contextlib.suppress(Exception):
373
+ await interpreter.aclose()
374
+ raise
341
375
 
342
376
  logger.info(f"Code interpreter {sandbox.id} created successfully")
343
377
 
@@ -27,6 +27,7 @@ class SupportedLanguageSync:
27
27
  GO = "go"
28
28
  TYPESCRIPT = "typescript"
29
29
  BASH = "bash"
30
+ JAVASCRIPT = "javascript"
30
31
 
31
32
 
32
33
  class CodeContextSync(BaseModel):
@@ -38,10 +38,10 @@ class Codes(Protocol):
38
38
  Supported Languages:
39
39
 
40
40
  - Python: Full Python 3.x support with package management
41
- - JavaScript/Node.js: ES6+ with npm package support
41
+ - JavaScript: ES6+ with npm package support
42
+ - TypeScript
43
+ - Go
42
44
  - Bash: Shell scripting with full system access
43
- - Java: Compilation and execution with classpath management
44
- - Kotlin: Script and compiled Kotlin execution
45
45
 
46
46
  Key Features:
47
47
 
@@ -99,18 +99,22 @@ class CodesAdapterSync(CodesSync):
99
99
  base_url = f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}"
100
100
  timeout_seconds = self.connection_config.request_timeout.total_seconds()
101
101
  timeout = httpx.Timeout(timeout_seconds)
102
- headers = {
103
- "User-Agent": self.connection_config.user_agent,
104
- **self.connection_config.headers,
105
- **(self.execd_endpoint.headers or {}),
106
- }
107
-
102
+ headers = self.execd_endpoint.build_request_headers(self.connection_config)
103
+
104
+ # Adapter clients must own their transports: close() closes them,
105
+ # and the shared/user connection_config transport must survive that.
106
+ self._owns_clients = self.connection_config.owns_transport
107
+ main_transport: httpx.BaseTransport | None
108
+ if self._owns_clients:
109
+ main_transport = self.connection_config.new_owned_transport()
110
+ else:
111
+ main_transport = self.connection_config.transport
108
112
  self._client = Client(base_url=base_url, timeout=timeout)
109
113
  self._httpx_client = httpx.Client(
110
114
  base_url=base_url,
111
115
  headers=headers,
112
116
  timeout=timeout,
113
- transport=self.connection_config.transport,
117
+ transport=main_transport,
114
118
  )
115
119
  self._client.set_httpx_client(self._httpx_client)
116
120
 
@@ -122,12 +126,17 @@ class CodesAdapterSync(CodesSync):
122
126
  # SSE bootstraps bypass the retry wrapper: request bodies are
123
127
  # not replayable and a non-idempotent status opt-in would cause
124
128
  # duplicate execution on a resent SSE POST.
129
+ sse_transport = (
130
+ self.connection_config.new_owned_transport()
131
+ if self._owns_clients
132
+ else self.connection_config.transport
133
+ )
125
134
  self._sse_client = httpx.Client(
126
135
  headers=sse_headers,
127
136
  timeout=httpx.Timeout(
128
137
  connect=timeout_seconds, read=None, write=timeout_seconds, pool=None
129
138
  ),
130
- transport=unwrap_retry_transport(self.connection_config.transport),
139
+ transport=unwrap_retry_transport(sse_transport),
131
140
  )
132
141
 
133
142
  def _get_execd_url(self, path: str) -> str:
@@ -136,6 +145,21 @@ class CodesAdapterSync(CodesSync):
136
145
  f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
137
146
  )
138
147
 
148
+ def close(self) -> None:
149
+ """Release the adapter-owned HTTP clients.
150
+
151
+ Never closes the shared ``connection_config.transport``; the sandbox
152
+ stays usable after ``close()``. Clients wrapping a user-supplied
153
+ transport are left open (httpx would close the caller's transport
154
+ with them).
155
+ """
156
+ if not self._owns_clients:
157
+ return
158
+ try:
159
+ self._httpx_client.close()
160
+ finally:
161
+ self._sse_client.close()
162
+
139
163
  def create_context(self, language: str) -> CodeContextSync:
140
164
  """
141
165
  Create a new execution context for code interpretation (sync).
@@ -17,6 +17,7 @@
17
17
  Synchronous Code Interpreter SDK.
18
18
  """
19
19
 
20
+ import contextlib
20
21
  import logging
21
22
  import time
22
23
  from datetime import timedelta
@@ -73,14 +74,18 @@ class CodeInterpreterSync:
73
74
  ```python
74
75
  from opensandbox.sync.sandbox import SandboxSync
75
76
  from code_interpreter.sync.code_interpreter import CodeInterpreterSync
76
- from code_interpreter.models.code import SupportedLanguage
77
+ from code_interpreter.models.code_sync import SupportedLanguageSync
77
78
 
78
- sandbox = SandboxSync.create("python:3.11")
79
+ sandbox = SandboxSync.create(
80
+ "opensandbox/code-interpreter:latest",
81
+ entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
82
+ )
79
83
  interpreter = CodeInterpreterSync.create(sandbox=sandbox)
80
84
 
81
- ctx = interpreter.codes.create_context(SupportedLanguage.PYTHON)
85
+ ctx = interpreter.codes.create_context(SupportedLanguageSync.PYTHON)
82
86
  result = interpreter.codes.run("print('hi')", context=ctx)
83
87
 
88
+ interpreter.close()
84
89
  sandbox.kill()
85
90
  sandbox.close()
86
91
  ```
@@ -139,6 +144,24 @@ class CodeInterpreterSync:
139
144
  """
140
145
  return self._sandbox.commands
141
146
 
147
+ def close(self) -> None:
148
+ """
149
+ Release resources owned by the code interpreter.
150
+
151
+ Closes the HTTP clients used by the code execution service. The
152
+ underlying sandbox is NOT affected; manage its lifecycle separately
153
+ (``sandbox.kill()`` / ``sandbox.close()``). Safe to call multiple times.
154
+ """
155
+ close = getattr(self._code_service, "close", None)
156
+ if close is not None:
157
+ close()
158
+
159
+ def __enter__(self) -> "CodeInterpreterSync":
160
+ return self
161
+
162
+ def __exit__(self, exc_type, exc, tb) -> None:
163
+ self.close()
164
+
142
165
  @property
143
166
  def metrics(self):
144
167
  """
@@ -289,9 +312,14 @@ class CodeInterpreterSync:
289
312
  code_service = factory.create_code_execution_service(endpoint)
290
313
 
291
314
  interpreter = cls(sandbox, code_service)
292
-
293
- if not skip_health_check:
294
- interpreter.check_ready(ready_timeout, health_check_polling_interval)
315
+ try:
316
+ if not skip_health_check:
317
+ interpreter.check_ready(ready_timeout, health_check_polling_interval)
318
+ except Exception:
319
+ # Don't leak the service's clients on a failed creation.
320
+ with contextlib.suppress(Exception):
321
+ interpreter.close()
322
+ raise
295
323
 
296
324
  logger.info(f"Code interpreter {sandbox.id} created successfully")
297
325
  return interpreter