ai-computer-client 0.1.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ env/
26
+ ENV/
27
+ .env
28
+
29
+ # IDE
30
+ .idea/
31
+ .vscode/
32
+ *.swp
33
+ *.swo
34
+
35
+ # Distribution
36
+ dist/
37
+ build/
38
+ *.egg-info/
39
+
40
+ # Testing
41
+ .coverage
42
+ htmlcov/
43
+ .pytest_cache/
44
+ .tox/
45
+
46
+ # Misc
47
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AI Computer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-computer-client
3
+ Version: 0.1.0
4
+ Summary: Python client for interacting with the AI Computer service
5
+ Project-URL: Homepage, https://github.com/ColeMurray/ai-computer-client-python
6
+ Project-URL: Documentation, https://github.com/ColeMurray/ai-computer-client-python#readme
7
+ Author: AI Computer
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Requires-Python: >=3.7
20
+ Requires-Dist: aiohttp>=3.8.0
21
+ Requires-Dist: typing-extensions>=4.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # AI Computer Python Client
29
+
30
+ A Python client for interacting with the AI Computer service. This client provides a simple interface for executing Python code in an isolated sandbox environment.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install ai-computer-client
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ import asyncio
42
+ from ai_computer import SandboxClient
43
+
44
+ async def main():
45
+ # Initialize the client
46
+ client = SandboxClient()
47
+
48
+ # Setup the client (gets token and creates sandbox)
49
+ setup_response = await client.setup()
50
+ if not setup_response.success:
51
+ print(f"Setup failed: {setup_response.error}")
52
+ return
53
+
54
+ try:
55
+ # Execute some code
56
+ code = """
57
+ print('Hello from AI Computer!')
58
+ result = 42
59
+ print(f'The answer is {result}')
60
+ """
61
+
62
+ # Stream execution
63
+ async for event in client.execute_code_stream(code):
64
+ if event.type == 'stdout':
65
+ print(f"Output: {event.data}")
66
+ elif event.type == 'stderr':
67
+ print(f"Error: {event.data}")
68
+ elif event.type == 'error':
69
+ print(f"Execution error: {event.data}")
70
+ break
71
+ elif event.type == 'completed':
72
+ print("Execution completed")
73
+ break
74
+
75
+ finally:
76
+ # Clean up
77
+ await client.cleanup()
78
+
79
+ if __name__ == "__main__":
80
+ asyncio.run(main())
81
+ ```
82
+
83
+ ## Features
84
+
85
+ - Asynchronous API
86
+ - Streaming execution output
87
+ - Automatic sandbox management
88
+ - Error handling and timeouts
89
+ - Type hints for better IDE support
90
+
91
+ ## API Reference
92
+
93
+ ### SandboxClient
94
+
95
+ The main client class for interacting with the AI Computer service.
96
+
97
+ #### Methods
98
+
99
+ - `setup()`: Initialize the client and create a sandbox
100
+ - `execute_code(code: str, timeout: int = 30)`: Execute code and return combined output
101
+ - `execute_code_stream(code: str, timeout: int = 30)`: Execute code and stream output
102
+ - `cleanup()`: Delete the sandbox
103
+ - `wait_for_ready()`: Wait for sandbox to be ready
104
+
105
+ ## Development
106
+
107
+ ### Running Tests
108
+
109
+ ```bash
110
+ pip install -e ".[dev]"
111
+ pytest
112
+ ```
113
+
114
+ ### Contributing
115
+
116
+ 1. Fork the repository
117
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
118
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
119
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
120
+ 5. Open a Pull Request
121
+
122
+ ## License
123
+
124
+ MIT License
@@ -0,0 +1,97 @@
1
+ # AI Computer Python Client
2
+
3
+ A Python client for interacting with the AI Computer service. This client provides a simple interface for executing Python code in an isolated sandbox environment.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install ai-computer-client
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import asyncio
15
+ from ai_computer import SandboxClient
16
+
17
+ async def main():
18
+ # Initialize the client
19
+ client = SandboxClient()
20
+
21
+ # Setup the client (gets token and creates sandbox)
22
+ setup_response = await client.setup()
23
+ if not setup_response.success:
24
+ print(f"Setup failed: {setup_response.error}")
25
+ return
26
+
27
+ try:
28
+ # Execute some code
29
+ code = """
30
+ print('Hello from AI Computer!')
31
+ result = 42
32
+ print(f'The answer is {result}')
33
+ """
34
+
35
+ # Stream execution
36
+ async for event in client.execute_code_stream(code):
37
+ if event.type == 'stdout':
38
+ print(f"Output: {event.data}")
39
+ elif event.type == 'stderr':
40
+ print(f"Error: {event.data}")
41
+ elif event.type == 'error':
42
+ print(f"Execution error: {event.data}")
43
+ break
44
+ elif event.type == 'completed':
45
+ print("Execution completed")
46
+ break
47
+
48
+ finally:
49
+ # Clean up
50
+ await client.cleanup()
51
+
52
+ if __name__ == "__main__":
53
+ asyncio.run(main())
54
+ ```
55
+
56
+ ## Features
57
+
58
+ - Asynchronous API
59
+ - Streaming execution output
60
+ - Automatic sandbox management
61
+ - Error handling and timeouts
62
+ - Type hints for better IDE support
63
+
64
+ ## API Reference
65
+
66
+ ### SandboxClient
67
+
68
+ The main client class for interacting with the AI Computer service.
69
+
70
+ #### Methods
71
+
72
+ - `setup()`: Initialize the client and create a sandbox
73
+ - `execute_code(code: str, timeout: int = 30)`: Execute code and return combined output
74
+ - `execute_code_stream(code: str, timeout: int = 30)`: Execute code and stream output
75
+ - `cleanup()`: Delete the sandbox
76
+ - `wait_for_ready()`: Wait for sandbox to be ready
77
+
78
+ ## Development
79
+
80
+ ### Running Tests
81
+
82
+ ```bash
83
+ pip install -e ".[dev]"
84
+ pytest
85
+ ```
86
+
87
+ ### Contributing
88
+
89
+ 1. Fork the repository
90
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
91
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
92
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
93
+ 5. Open a Pull Request
94
+
95
+ ## License
96
+
97
+ MIT License
@@ -0,0 +1,4 @@
1
+ from .client import SandboxClient, SandboxResponse, StreamEvent
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["SandboxClient", "SandboxResponse", "StreamEvent"]
@@ -0,0 +1,265 @@
1
+ import aiohttp
2
+ import json
3
+ import asyncio
4
+ from typing import Optional, Dict, AsyncGenerator, Union
5
+ from dataclasses import dataclass
6
+
7
+ @dataclass
8
+ class SandboxResponse:
9
+ """Response from sandbox operations.
10
+
11
+ Attributes:
12
+ success: Whether the operation was successful
13
+ data: Optional response data
14
+ error: Optional error message if operation failed
15
+ """
16
+ success: bool
17
+ data: Optional[Dict] = None
18
+ error: Optional[str] = None
19
+
20
+ @dataclass
21
+ class StreamEvent:
22
+ """Event from streaming code execution.
23
+
24
+ Attributes:
25
+ type: Type of event ('stdout', 'stderr', 'info', 'error', 'completed', 'keepalive')
26
+ data: Event data
27
+ """
28
+ type: str
29
+ data: str
30
+
31
+ class SandboxClient:
32
+ """Client for interacting with the AI Sandbox service.
33
+
34
+ This client provides methods to execute Python code in an isolated sandbox environment.
35
+ It handles authentication, sandbox creation/deletion, and code execution.
36
+
37
+ Args:
38
+ base_url: The base URL of the sandbox service
39
+ token: Optional pre-existing authentication token
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ base_url: str = "http://aicomputer.dev",
45
+ token: Optional[str] = None
46
+ ):
47
+ self.base_url = base_url.rstrip('/')
48
+ self.token = token
49
+ self.sandbox_id = None
50
+
51
+ async def setup(self) -> SandboxResponse:
52
+ """Initialize the client and create a sandbox.
53
+
54
+ This method:
55
+ 1. Gets a development token (if not provided)
56
+ 2. Creates a new sandbox
57
+ 3. Waits for the sandbox to be ready
58
+
59
+ Returns:
60
+ SandboxResponse indicating success/failure
61
+ """
62
+ async with aiohttp.ClientSession() as session:
63
+ # Get development token if not provided
64
+ if not self.token:
65
+ async with session.post(f"{self.base_url}/dev/token") as response:
66
+ if response.status == 200:
67
+ data = await response.json()
68
+ self.token = data["access_token"]
69
+ else:
70
+ text = await response.text()
71
+ return SandboxResponse(success=False, error=text)
72
+
73
+ # Create sandbox
74
+ headers = {"Authorization": f"Bearer {self.token}"}
75
+ async with session.post(f"{self.base_url}/api/v1/sandbox/create", headers=headers) as response:
76
+ if response.status == 200:
77
+ data = await response.json()
78
+ self.sandbox_id = data["sandbox_id"]
79
+ # Wait for sandbox to be ready
80
+ ready = await self.wait_for_ready()
81
+ if not ready.success:
82
+ return ready
83
+ return SandboxResponse(success=True, data=data)
84
+ else:
85
+ text = await response.text()
86
+ return SandboxResponse(success=False, error=text)
87
+
88
+ async def wait_for_ready(self, max_retries: int = 30, delay: int = 1) -> SandboxResponse:
89
+ """Wait for the sandbox to be in Running state.
90
+
91
+ Args:
92
+ max_retries: Maximum number of status check attempts
93
+ delay: Delay between retries in seconds
94
+
95
+ Returns:
96
+ SandboxResponse indicating if sandbox is ready
97
+ """
98
+ if not self.token or not self.sandbox_id:
99
+ return SandboxResponse(success=False, error="Client not properly initialized")
100
+
101
+ headers = {"Authorization": f"Bearer {self.token}"}
102
+
103
+ for _ in range(max_retries):
104
+ async with aiohttp.ClientSession() as session:
105
+ async with session.get(
106
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}/status",
107
+ headers=headers
108
+ ) as response:
109
+ if response.status == 200:
110
+ data = await response.json()
111
+ if data["status"] == "Running":
112
+ return SandboxResponse(success=True, data=data)
113
+ await asyncio.sleep(delay)
114
+
115
+ return SandboxResponse(success=False, error="Sandbox failed to become ready")
116
+
117
+ async def execute_code(
118
+ self,
119
+ code: Union[str, bytes],
120
+ timeout: int = 30
121
+ ) -> SandboxResponse:
122
+ """Execute Python code in the sandbox and return the combined output.
123
+
124
+ This method collects all output from the streaming response and returns it as a single result.
125
+ It captures both stdout and stderr, and handles any errors during execution.
126
+
127
+ Args:
128
+ code: Python code to execute
129
+ timeout: Maximum execution time in seconds
130
+
131
+ Returns:
132
+ SandboxResponse containing execution results
133
+ """
134
+ if not self.token or not self.sandbox_id:
135
+ return SandboxResponse(success=False, error="Client not properly initialized. Call setup() first")
136
+
137
+ # Ensure sandbox is ready
138
+ ready = await self.wait_for_ready()
139
+ if not ready.success:
140
+ return ready
141
+
142
+ headers = {
143
+ "Authorization": f"Bearer {self.token}",
144
+ "Content-Type": "application/json"
145
+ }
146
+
147
+ data = {
148
+ "code": code,
149
+ "timeout": timeout
150
+ }
151
+
152
+ try:
153
+ async with aiohttp.ClientSession() as session:
154
+ async with session.post(
155
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}/execute",
156
+ headers=headers,
157
+ json=data
158
+ ) as response:
159
+ if response.status != 200:
160
+ error_text = await response.text()
161
+ return SandboxResponse(success=False, error=error_text)
162
+
163
+ # Parse the response
164
+ result = await response.json()
165
+ return SandboxResponse(success=True, data=result)
166
+
167
+ except Exception as e:
168
+ return SandboxResponse(success=False, error=f"Connection error: {str(e)}")
169
+
170
+ async def execute_code_stream(
171
+ self,
172
+ code: Union[str, bytes],
173
+ timeout: int = 30
174
+ ) -> AsyncGenerator[StreamEvent, None]:
175
+ """Execute Python code in the sandbox and stream the output.
176
+
177
+ This method returns an async generator that yields StreamEvent objects containing
178
+ the type of event and the associated data.
179
+
180
+ Args:
181
+ code: Python code to execute
182
+ timeout: Maximum execution time in seconds
183
+
184
+ Yields:
185
+ StreamEvent objects with execution output/events
186
+ """
187
+ if not self.token or not self.sandbox_id:
188
+ yield StreamEvent(type="error", data="Client not properly initialized. Call setup() first")
189
+ return
190
+
191
+ # Ensure sandbox is ready
192
+ ready = await self.wait_for_ready()
193
+ if not ready.success:
194
+ yield StreamEvent(type="error", data=ready.error or "Sandbox not ready")
195
+ return
196
+
197
+ headers = {
198
+ "Authorization": f"Bearer {self.token}",
199
+ "Content-Type": "application/json"
200
+ }
201
+
202
+ data = {
203
+ "code": code,
204
+ "timeout": timeout
205
+ }
206
+
207
+ try:
208
+ # Create a ClientTimeout object with all timeout settings
209
+ timeout_settings = aiohttp.ClientTimeout(
210
+ total=timeout + 30, # Add buffer for connection overhead
211
+ connect=30,
212
+ sock_connect=30,
213
+ sock_read=timeout + 30
214
+ )
215
+
216
+ async with aiohttp.ClientSession(timeout=timeout_settings) as session:
217
+ async with session.post(
218
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}/execute/stream",
219
+ headers=headers,
220
+ json=data
221
+ ) as response:
222
+ if response.status != 200:
223
+ error_text = await response.text()
224
+ yield StreamEvent(type="error", data=error_text)
225
+ return
226
+
227
+ # Process the streaming response
228
+ async for line in response.content:
229
+ if line:
230
+ try:
231
+ event = json.loads(line.decode())
232
+ yield StreamEvent(type=event['type'], data=event['data'])
233
+
234
+ # Stop if we receive an error or completed event
235
+ if event['type'] in ['error', 'completed']:
236
+ break
237
+ except json.JSONDecodeError as e:
238
+ yield StreamEvent(type="error", data=f"Failed to parse event: {str(e)}")
239
+ break
240
+
241
+ except Exception as e:
242
+ yield StreamEvent(type="error", data=f"Connection error: {str(e)}")
243
+
244
+ async def cleanup(self) -> SandboxResponse:
245
+ """Delete the sandbox.
246
+
247
+ Returns:
248
+ SandboxResponse indicating success/failure of cleanup
249
+ """
250
+ if not self.token or not self.sandbox_id:
251
+ return SandboxResponse(success=True)
252
+
253
+ headers = {"Authorization": f"Bearer {self.token}"}
254
+ async with aiohttp.ClientSession() as session:
255
+ async with session.delete(
256
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}",
257
+ headers=headers
258
+ ) as response:
259
+ if response.status == 200:
260
+ data = await response.json()
261
+ self.sandbox_id = None
262
+ return SandboxResponse(success=True, data=data)
263
+ else:
264
+ text = await response.text()
265
+ return SandboxResponse(success=False, error=text)
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ai-computer-client"
7
+ version = "0.1.0"
8
+ description = "Python client for interacting with the AI Computer service"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "AI Computer" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.7",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ ]
26
+ dependencies = [
27
+ "aiohttp>=3.8.0",
28
+ "typing-extensions>=4.0.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/ColeMurray/ai-computer-client-python"
33
+ Documentation = "https://github.com/ColeMurray/ai-computer-client-python#readme"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["ai_computer"]
37
+
38
+ [tool.hatch.build]
39
+ include = [
40
+ "ai_computer/**/*.py",
41
+ "ai_computer/**/*.pyi",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ dev = [
46
+ "pytest>=7.0.0",
47
+ "pytest-asyncio>=0.21.0",
48
+ "pytest-cov>=4.0.0",
49
+ ]
50
+
51
+ [tool.pytest.ini_options]
52
+ asyncio_mode = "strict"
53
+ asyncio_default_fixture_loop_scope = "function"