simplex 1.0.0__py3-none-any.whl → 1.0.8__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.

Potentially problematic release.


This version of simplex might be problematic. Click here for more details.

simplex/__init__.py CHANGED
@@ -1,4 +1,35 @@
1
- from .simplex import Simplex
1
+ """
2
+ Simplex Python SDK
2
3
 
3
- __version__ = "0.1.7"
4
- __all__ = ["Simplex"]
4
+ Official Python SDK for the Simplex API - A workflow automation platform.
5
+
6
+ Example usage:
7
+ >>> from simplex import SimplexClient
8
+ >>> client = SimplexClient(api_key="your-api-key")
9
+ >>> result = client.workflows.run("workflow-id")
10
+ """
11
+
12
+ from simplex.client import SimplexClient
13
+ from simplex.errors import (
14
+ SimplexError,
15
+ NetworkError,
16
+ ValidationError,
17
+ AuthenticationError,
18
+ RateLimitError,
19
+ WorkflowError,
20
+ )
21
+ from simplex.resources.workflow import Workflow
22
+ from simplex.resources.workflow_session import WorkflowSession
23
+
24
+ __version__ = "1.0.0"
25
+ __all__ = [
26
+ "SimplexClient",
27
+ "SimplexError",
28
+ "NetworkError",
29
+ "ValidationError",
30
+ "AuthenticationError",
31
+ "RateLimitError",
32
+ "WorkflowError",
33
+ "Workflow",
34
+ "WorkflowSession",
35
+ ]
simplex/client.py ADDED
@@ -0,0 +1,355 @@
1
+ """
2
+ Main SimplexClient class for the Simplex SDK.
3
+
4
+ This module provides the SimplexClient, which is the primary entry point
5
+ for interacting with the Simplex API.
6
+ """
7
+
8
+ import json
9
+ from typing import Any, Dict, Optional
10
+
11
+ from simplex.errors import WorkflowError
12
+ from simplex.http_client import HttpClient
13
+ from simplex.resources.workflow import Workflow
14
+ from simplex.resources.workflow_session import WorkflowSession
15
+ from simplex.types import (
16
+ Add2FAConfigResponse,
17
+ CreateWorkflowSessionResponse,
18
+ GetSessionStoreResponse,
19
+ SimplexClientOptions,
20
+ )
21
+
22
+
23
+ class SimplexClient:
24
+ """
25
+ Main client for interacting with the Simplex API.
26
+
27
+ This is the primary entry point for the SDK. It provides access to all
28
+ Simplex API functionality through resource classes and utility methods.
29
+
30
+ Example:
31
+ >>> from simplex import SimplexClient
32
+ >>> client = SimplexClient(api_key="your-api-key")
33
+ >>>
34
+ >>> # Run a workflow
35
+ >>> result = client.workflows.run("workflow-id")
36
+ >>>
37
+ >>> # Create a workflow session
38
+ >>> with client.create_workflow_session("test", "https://example.com") as session:
39
+ ... session.goto("https://example.com/login")
40
+ ... session.run_agent("Login Agent")
41
+
42
+ Attributes:
43
+ workflows: Resource for workflow operations
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ api_key: str,
49
+ timeout: int = 30,
50
+ max_retries: int = 3,
51
+ retry_delay: int = 1,
52
+ base_url: str = "https://api.simplex.sh"
53
+ ):
54
+ """
55
+ Initialize the Simplex client.
56
+
57
+ Args:
58
+ api_key: Your Simplex API key (required)
59
+ timeout: Request timeout in seconds (default: 30)
60
+ max_retries: Maximum number of retry attempts (default: 3)
61
+ retry_delay: Delay between retries in seconds (default: 1)
62
+ base_url: Base URL for the API (default: "https://api.simplex.sh")
63
+
64
+ Raises:
65
+ ValueError: If api_key is not provided
66
+
67
+ Example:
68
+ >>> client = SimplexClient(
69
+ ... api_key="your-api-key",
70
+ ... timeout=60,
71
+ ... max_retries=5
72
+ ... )
73
+ """
74
+ if not api_key:
75
+ raise ValueError("api_key is required")
76
+
77
+ # Initialize HTTP client
78
+ self._http_client = HttpClient(
79
+ base_url=base_url,
80
+ api_key=api_key,
81
+ timeout=timeout,
82
+ retry_attempts=max_retries,
83
+ retry_delay=retry_delay
84
+ )
85
+
86
+ # Initialize resource classes
87
+ self.workflows = Workflow(self._http_client)
88
+
89
+ def create_workflow_session(
90
+ self,
91
+ name: str,
92
+ url: str,
93
+ proxies: bool = False,
94
+ session_data: Optional[Any] = None
95
+ ) -> WorkflowSession:
96
+ """
97
+ Create a new workflow session with context manager support.
98
+
99
+ This method creates a new browser session that can be controlled
100
+ programmatically. The returned WorkflowSession can be used as a
101
+ context manager for automatic cleanup.
102
+
103
+ Args:
104
+ name: Name for this workflow session
105
+ url: Starting URL for the browser session
106
+ proxies: Whether to use proxies (default: False)
107
+ session_data: Optional data to associate with the session
108
+
109
+ Returns:
110
+ WorkflowSession object for interacting with the session
111
+
112
+ Raises:
113
+ WorkflowError: If session creation fails
114
+
115
+ Example:
116
+ >>> # Using as a context manager (recommended)
117
+ >>> with client.create_workflow_session("test", "https://example.com") as session:
118
+ ... session.goto("https://example.com/login")
119
+ ... session.run_agent("Login Agent")
120
+ ... # Session automatically closed when exiting the with block
121
+ >>>
122
+ >>> # Or manage manually
123
+ >>> session = client.create_workflow_session("test", "https://example.com")
124
+ >>> try:
125
+ ... session.goto("https://example.com/login")
126
+ ... finally:
127
+ ... session.close()
128
+ """
129
+ # Ensure URL has protocol
130
+ if not url.startswith('http://') and not url.startswith('https://'):
131
+ url = 'https://' + url
132
+
133
+ request_data = {
134
+ 'workflow_name': name,
135
+ 'url': url,
136
+ 'proxies': proxies
137
+ }
138
+
139
+ if session_data is not None:
140
+ request_data['session_data'] = json.dumps(session_data)
141
+
142
+ try:
143
+ response: CreateWorkflowSessionResponse = self._http_client.post(
144
+ '/create_workflow_session',
145
+ data=request_data
146
+ )
147
+
148
+ # Create and return WorkflowSession object
149
+ return WorkflowSession(
150
+ self._http_client,
151
+ {
152
+ 'sessionId': response['session_id'],
153
+ 'workflowId': response['workflow_id'],
154
+ 'livestreamUrl': response['livestream_url'],
155
+ 'connectUrl': response['connect_url'],
156
+ 'vncUrl': response['vnc_url']
157
+ }
158
+ )
159
+ except Exception as e:
160
+ raise WorkflowError(
161
+ f"Failed to create workflow session: {str(e)}"
162
+ )
163
+
164
+ def get_session_store(self, session_id: str) -> GetSessionStoreResponse:
165
+ """
166
+ Retrieve session store data for a specific session.
167
+
168
+ The session store contains data that was saved during workflow execution.
169
+
170
+ Args:
171
+ session_id: ID of the session to retrieve store data for
172
+
173
+ Returns:
174
+ Response containing session store data
175
+
176
+ Raises:
177
+ WorkflowError: If retrieving session store fails
178
+
179
+ Example:
180
+ >>> store = client.get_session_store("session-123")
181
+ >>> if store['succeeded']:
182
+ ... data = store['session_store']
183
+ ... print(f"Session data: {data}")
184
+ """
185
+ try:
186
+ response: GetSessionStoreResponse = self._http_client.post(
187
+ '/get_session_store',
188
+ data={'session_id': session_id}
189
+ )
190
+ return response
191
+ except Exception as e:
192
+ raise WorkflowError(
193
+ f"Failed to get session store: {str(e)}",
194
+ session_id=session_id
195
+ )
196
+
197
+ def download_session_files(
198
+ self,
199
+ session_id: str,
200
+ filename: Optional[str] = None
201
+ ) -> bytes:
202
+ """
203
+ Download files from a session.
204
+
205
+ This method downloads files that were created or downloaded during
206
+ a workflow session. If no filename is specified, all files are
207
+ downloaded as a zip archive.
208
+
209
+ Args:
210
+ session_id: ID of the session to download files from
211
+ filename: Optional specific filename to download
212
+
213
+ Returns:
214
+ File content as bytes
215
+
216
+ Raises:
217
+ WorkflowError: If file download fails
218
+
219
+ Example:
220
+ >>> # Download all files as zip
221
+ >>> zip_data = client.download_session_files("session-123")
222
+ >>> with open("session_files.zip", "wb") as f:
223
+ ... f.write(zip_data)
224
+ >>>
225
+ >>> # Download specific file
226
+ >>> file_data = client.download_session_files("session-123", "report.pdf")
227
+ >>> with open("report.pdf", "wb") as f:
228
+ ... f.write(file_data)
229
+ """
230
+ try:
231
+ params = {'session_id': session_id}
232
+ if filename:
233
+ params['filename'] = filename
234
+
235
+ content = self._http_client.download_file('/download_session_files', params=params)
236
+
237
+ # Check if the response is a JSON error by trying to decode and parse it
238
+ try:
239
+ text = content.decode('utf-8')
240
+ data = json.loads(text)
241
+ if isinstance(data, dict) and data.get('succeeded') is False:
242
+ raise WorkflowError(
243
+ data.get('error') or 'Failed to download session files',
244
+ session_id=session_id
245
+ )
246
+ except (UnicodeDecodeError, json.JSONDecodeError):
247
+ # If decoding/parsing fails, it's binary data (the file), which is what we want
248
+ pass
249
+
250
+ return content
251
+ except WorkflowError:
252
+ raise
253
+ except Exception as e:
254
+ raise WorkflowError(
255
+ f"Failed to download session files: {str(e)}",
256
+ session_id=session_id
257
+ )
258
+
259
+ def add_2fa_config(
260
+ self,
261
+ seed: str,
262
+ name: Optional[str] = None,
263
+ partial_url: Optional[str] = None
264
+ ) -> Add2FAConfigResponse:
265
+ """
266
+ Add a 2FA (Two-Factor Authentication) configuration.
267
+
268
+ This allows workflows to automatically handle 2FA authentication
269
+ when encountering sites that require it.
270
+
271
+ Args:
272
+ seed: The 2FA seed/secret key
273
+ name: Optional name for this 2FA configuration
274
+ partial_url: Optional partial URL to match for auto-detection
275
+
276
+ Returns:
277
+ Response with configuration details
278
+
279
+ Raises:
280
+ WorkflowError: If adding the configuration fails
281
+
282
+ Example:
283
+ >>> result = client.add_2fa_config(
284
+ ... seed="JBSWY3DPEHPK3PXP",
285
+ ... name="My Service",
286
+ ... partial_url="example.com"
287
+ ... )
288
+ >>> if result['succeeded']:
289
+ ... print(f"Total configs: {result['total_configs']}")
290
+ """
291
+ request_data = {'seed': seed}
292
+
293
+ if name:
294
+ request_data['name'] = name
295
+
296
+ if partial_url:
297
+ request_data['partial_url'] = partial_url
298
+
299
+ try:
300
+ response: Add2FAConfigResponse = self._http_client.post_json(
301
+ '/add_2fa_config',
302
+ data=request_data
303
+ )
304
+
305
+ if not response.get('succeeded') and response.get('error'):
306
+ raise WorkflowError(
307
+ f"Failed to add 2FA config: {response['error']}"
308
+ )
309
+
310
+ return response
311
+ except WorkflowError:
312
+ raise
313
+ except Exception as e:
314
+ raise WorkflowError(
315
+ f"Failed to add 2FA config: {str(e)}"
316
+ )
317
+
318
+ def update_api_key(self, api_key: str) -> None:
319
+ """
320
+ Update the API key used for authentication.
321
+
322
+ This allows you to change the API key without creating a new client instance.
323
+
324
+ Args:
325
+ api_key: New API key to use
326
+
327
+ Example:
328
+ >>> client.update_api_key("new-api-key")
329
+ """
330
+ self._http_client.update_api_key(api_key)
331
+
332
+ def set_custom_header(self, key: str, value: str) -> None:
333
+ """
334
+ Set a custom header to be included with all requests.
335
+
336
+ Args:
337
+ key: Header name
338
+ value: Header value
339
+
340
+ Example:
341
+ >>> client.set_custom_header("X-Custom-Header", "custom-value")
342
+ """
343
+ self._http_client.set_header(key, value)
344
+
345
+ def remove_custom_header(self, key: str) -> None:
346
+ """
347
+ Remove a custom header.
348
+
349
+ Args:
350
+ key: Header name to remove
351
+
352
+ Example:
353
+ >>> client.remove_custom_header("X-Custom-Header")
354
+ """
355
+ self._http_client.remove_header(key)
simplex/errors.py ADDED
@@ -0,0 +1,160 @@
1
+ """
2
+ Custom exception classes for the Simplex SDK.
3
+
4
+ This module defines a hierarchy of exceptions that can be raised by the SDK,
5
+ allowing for specific error handling based on the type of error encountered.
6
+ """
7
+
8
+ from typing import Any, Optional
9
+
10
+
11
+ class SimplexError(Exception):
12
+ """
13
+ Base exception class for all Simplex SDK errors.
14
+
15
+ All custom exceptions in the SDK inherit from this class, making it easy
16
+ to catch any SDK-related error with a single except clause.
17
+
18
+ Attributes:
19
+ message: Human-readable error message
20
+ status_code: HTTP status code if applicable
21
+ data: Additional error data or context
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ message: str,
27
+ status_code: Optional[int] = None,
28
+ data: Optional[Any] = None
29
+ ):
30
+ """
31
+ Initialize a SimplexError.
32
+
33
+ Args:
34
+ message: Human-readable error message
35
+ status_code: HTTP status code if this is an HTTP error
36
+ data: Additional error context or data
37
+ """
38
+ super().__init__(message)
39
+ self.message = message
40
+ self.status_code = status_code
41
+ self.data = data
42
+
43
+ def __str__(self) -> str:
44
+ """Return a string representation of the error."""
45
+ if self.status_code:
46
+ return f"[{self.status_code}] {self.message}"
47
+ return self.message
48
+
49
+
50
+ class NetworkError(SimplexError):
51
+ """
52
+ Raised when a network-related error occurs.
53
+
54
+ This includes connection failures, timeouts, and other network issues
55
+ that prevent communication with the Simplex API.
56
+ """
57
+
58
+ def __init__(self, message: str):
59
+ """
60
+ Initialize a NetworkError.
61
+
62
+ Args:
63
+ message: Description of the network error
64
+ """
65
+ super().__init__(f"Network error: {message}")
66
+
67
+
68
+ class ValidationError(SimplexError):
69
+ """
70
+ Raised when request validation fails (HTTP 400).
71
+
72
+ This indicates that the request data was invalid or malformed,
73
+ such as missing required fields or invalid parameter values.
74
+ """
75
+
76
+ def __init__(self, message: str, data: Optional[Any] = None):
77
+ """
78
+ Initialize a ValidationError.
79
+
80
+ Args:
81
+ message: Description of the validation error
82
+ data: Additional validation error details
83
+ """
84
+ super().__init__(message, status_code=400, data=data)
85
+
86
+
87
+ class AuthenticationError(SimplexError):
88
+ """
89
+ Raised when authentication fails (HTTP 401 or 403).
90
+
91
+ This typically indicates an invalid API key or insufficient permissions
92
+ to access the requested resource.
93
+ """
94
+
95
+ def __init__(self, message: str):
96
+ """
97
+ Initialize an AuthenticationError.
98
+
99
+ Args:
100
+ message: Description of the authentication error
101
+ """
102
+ super().__init__(message, status_code=401)
103
+
104
+
105
+ class RateLimitError(SimplexError):
106
+ """
107
+ Raised when rate limit is exceeded (HTTP 429).
108
+
109
+ The Simplex API has rate limits to prevent abuse. When exceeded,
110
+ this error is raised with information about when to retry.
111
+
112
+ Attributes:
113
+ retry_after: Number of seconds to wait before retrying
114
+ """
115
+
116
+ def __init__(self, message: str, retry_after: Optional[int] = None):
117
+ """
118
+ Initialize a RateLimitError.
119
+
120
+ Args:
121
+ message: Description of the rate limit error
122
+ retry_after: Number of seconds to wait before retrying
123
+ """
124
+ super().__init__(message, status_code=429)
125
+ self.retry_after = retry_after
126
+
127
+
128
+ class WorkflowError(SimplexError):
129
+ """
130
+ Raised when a workflow operation fails.
131
+
132
+ This is a specialized error for workflow-related failures,
133
+ including session creation, workflow execution, and agent tasks.
134
+
135
+ Attributes:
136
+ workflow_id: The ID of the workflow that failed (if applicable)
137
+ session_id: The ID of the session that failed (if applicable)
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ message: str,
143
+ workflow_id: Optional[str] = None,
144
+ session_id: Optional[str] = None
145
+ ):
146
+ """
147
+ Initialize a WorkflowError.
148
+
149
+ Args:
150
+ message: Description of the workflow error
151
+ workflow_id: ID of the workflow (if applicable)
152
+ session_id: ID of the session (if applicable)
153
+ """
154
+ super().__init__(
155
+ message,
156
+ status_code=500,
157
+ data={"workflow_id": workflow_id, "session_id": session_id}
158
+ )
159
+ self.workflow_id = workflow_id
160
+ self.session_id = session_id