simplex 2.0.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.
simplex/__init__.py ADDED
@@ -0,0 +1,50 @@
1
+ """
2
+ Simplex Python SDK
3
+
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.run_workflow("workflow-id", variables={"key": "value"})
10
+ >>>
11
+ >>> # Poll for completion
12
+ >>> import time
13
+ >>> while True:
14
+ ... status = client.get_session_status(result["session_id"])
15
+ ... if not status["in_progress"]:
16
+ ... break
17
+ ... time.sleep(1)
18
+ >>>
19
+ >>> if status["success"]:
20
+ ... print("Outputs:", status["scraper_outputs"])
21
+ """
22
+
23
+ from simplex.client import SimplexClient
24
+ from simplex.errors import (
25
+ AuthenticationError,
26
+ NetworkError,
27
+ RateLimitError,
28
+ SimplexError,
29
+ ValidationError,
30
+ WorkflowError,
31
+ )
32
+ from simplex.types import (
33
+ FileMetadata,
34
+ RunWorkflowResponse,
35
+ SessionStatusResponse,
36
+ )
37
+
38
+ __version__ = "2.0.0"
39
+ __all__ = [
40
+ "SimplexClient",
41
+ "SimplexError",
42
+ "NetworkError",
43
+ "ValidationError",
44
+ "AuthenticationError",
45
+ "RateLimitError",
46
+ "WorkflowError",
47
+ "FileMetadata",
48
+ "SessionStatusResponse",
49
+ "RunWorkflowResponse",
50
+ ]
@@ -0,0 +1,220 @@
1
+ """
2
+ Internal HTTP client for the Simplex SDK.
3
+
4
+ This module provides a robust HTTP client with automatic retry logic,
5
+ error handling, and support for various request types.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from typing import Any
12
+ from urllib.parse import urlencode
13
+
14
+ import requests
15
+
16
+ from simplex.errors import (
17
+ AuthenticationError,
18
+ NetworkError,
19
+ RateLimitError,
20
+ SimplexError,
21
+ ValidationError,
22
+ )
23
+
24
+ __version__ = "2.0.0"
25
+
26
+
27
+ class HttpClient:
28
+ """
29
+ Internal HTTP client with retry logic and error handling.
30
+
31
+ This client handles all communication with the Simplex API, including:
32
+ - Automatic retry with exponential backoff for 429, 5xx errors
33
+ - Error mapping to custom exceptions
34
+ - Support for form-encoded and JSON requests
35
+ - File downloads
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ base_url: str,
41
+ api_key: str,
42
+ timeout: int = 30,
43
+ max_retries: int = 3,
44
+ retry_delay: float = 1.0,
45
+ ):
46
+ """
47
+ Initialize the HTTP client.
48
+
49
+ Args:
50
+ base_url: Base URL for the API (e.g., 'https://api.simplex.sh')
51
+ api_key: Your Simplex API key
52
+ timeout: Request timeout in seconds (default: 30)
53
+ max_retries: Maximum number of retry attempts (default: 3)
54
+ retry_delay: Base delay between retries in seconds (default: 1.0)
55
+ """
56
+ self.base_url = base_url.rstrip("/")
57
+ self.api_key = api_key
58
+ self.timeout = timeout
59
+ self.max_retries = max_retries
60
+ self.retry_delay = retry_delay
61
+
62
+ self.session = requests.Session()
63
+ self.session.headers.update(
64
+ {
65
+ "X-API-Key": api_key,
66
+ "User-Agent": f"Simplex-Python-SDK/{__version__}",
67
+ }
68
+ )
69
+
70
+ def _should_retry(self, status_code: int | None) -> bool:
71
+ """Determine if a request should be retried based on status code."""
72
+ if status_code is None:
73
+ return True # Network error
74
+ return status_code == 429 or status_code >= 500
75
+
76
+ def _handle_error(self, response: requests.Response) -> SimplexError:
77
+ """Convert HTTP errors to appropriate exception types."""
78
+ status_code = response.status_code
79
+
80
+ try:
81
+ data = response.json()
82
+ if isinstance(data, dict):
83
+ message = data.get("message") or data.get("error") or "An error occurred"
84
+ else:
85
+ message = str(data)
86
+ except ValueError:
87
+ message = response.text or "An error occurred"
88
+
89
+ if status_code == 400:
90
+ return ValidationError(message, data=response.json() if response.text else None)
91
+ elif status_code in [401, 403]:
92
+ return AuthenticationError(message)
93
+ elif status_code == 429:
94
+ retry_after = response.headers.get("Retry-After")
95
+ retry_after_seconds = int(retry_after) if retry_after and retry_after.isdigit() else None
96
+ return RateLimitError(message, retry_after=retry_after_seconds)
97
+ else:
98
+ return SimplexError(message, status_code=status_code, data=response.json() if response.text else None)
99
+
100
+ def _make_request(
101
+ self,
102
+ method: str,
103
+ path: str,
104
+ data: Any = None,
105
+ params: dict[str, Any] | None = None,
106
+ headers: dict[str, str] | None = None,
107
+ **kwargs: Any,
108
+ ) -> requests.Response:
109
+ """
110
+ Make an HTTP request with retry logic.
111
+
112
+ Args:
113
+ method: HTTP method (GET, POST, etc.)
114
+ path: API endpoint path
115
+ data: Request body data
116
+ params: Query parameters
117
+ headers: Additional headers for this request
118
+ **kwargs: Additional arguments to pass to requests
119
+
120
+ Returns:
121
+ Response object
122
+
123
+ Raises:
124
+ SimplexError: If the request fails after all retries
125
+ """
126
+ url = f"{self.base_url}{path}"
127
+ attempt = 0
128
+ last_exception: SimplexError | None = None
129
+
130
+ while attempt <= self.max_retries:
131
+ try:
132
+ response = self.session.request(
133
+ method=method,
134
+ url=url,
135
+ data=data,
136
+ params=params,
137
+ headers=headers,
138
+ timeout=self.timeout,
139
+ **kwargs,
140
+ )
141
+
142
+ if not response.ok:
143
+ error = self._handle_error(response)
144
+
145
+ if self._should_retry(response.status_code) and attempt < self.max_retries:
146
+ attempt += 1
147
+ time.sleep(self.retry_delay * attempt)
148
+ continue
149
+
150
+ raise error
151
+
152
+ return response
153
+
154
+ except requests.exceptions.RequestException as e:
155
+ last_exception = NetworkError(str(e))
156
+
157
+ if attempt < self.max_retries:
158
+ attempt += 1
159
+ time.sleep(self.retry_delay * attempt)
160
+ continue
161
+
162
+ raise last_exception
163
+
164
+ if last_exception:
165
+ raise last_exception
166
+ raise NetworkError("Request failed after all retries")
167
+
168
+ def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
169
+ """Make a GET request and return JSON response."""
170
+ response = self._make_request("GET", path, params=params)
171
+ return response.json()
172
+
173
+ def post(
174
+ self,
175
+ path: str,
176
+ data: dict[str, Any] | None = None,
177
+ ) -> Any:
178
+ """
179
+ Make a POST request with form-encoded data.
180
+
181
+ Args:
182
+ path: API endpoint path
183
+ data: Form data to send
184
+
185
+ Returns:
186
+ Parsed JSON response
187
+ """
188
+ import json as json_module
189
+
190
+ form_data = {}
191
+ if data:
192
+ for key, value in data.items():
193
+ if value is not None:
194
+ if isinstance(value, (dict, list)):
195
+ form_data[key] = json_module.dumps(value)
196
+ else:
197
+ form_data[key] = str(value)
198
+
199
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
200
+ response = self._make_request(
201
+ "POST",
202
+ path,
203
+ data=urlencode(form_data) if form_data else None,
204
+ headers=headers,
205
+ )
206
+ return response.json()
207
+
208
+ def download_file(self, path: str, params: dict[str, Any] | None = None) -> bytes:
209
+ """
210
+ Download a file from the API.
211
+
212
+ Args:
213
+ path: API endpoint path
214
+ params: Query parameters
215
+
216
+ Returns:
217
+ File content as bytes
218
+ """
219
+ response = self._make_request("GET", path, params=params)
220
+ return response.content
simplex/client.py ADDED
@@ -0,0 +1,289 @@
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
+ from __future__ import annotations
9
+
10
+ import json
11
+ from typing import Any
12
+
13
+ from simplex._http_client import HttpClient
14
+ from simplex.errors import WorkflowError
15
+ from simplex.types import RunWorkflowResponse, SessionStatusResponse
16
+
17
+
18
+ class SimplexClient:
19
+ """
20
+ Main client for interacting with the Simplex API.
21
+
22
+ This is the primary entry point for the SDK. It provides a flat API
23
+ for all Simplex API functionality.
24
+
25
+ Example:
26
+ >>> from simplex import SimplexClient
27
+ >>> client = SimplexClient(api_key="your-api-key")
28
+ >>>
29
+ >>> # Run a workflow
30
+ >>> result = client.run_workflow("workflow-id", variables={"key": "value"})
31
+ >>>
32
+ >>> # Poll for completion
33
+ >>> import time
34
+ >>> while True:
35
+ ... status = client.get_session_status(result["session_id"])
36
+ ... if not status["in_progress"]:
37
+ ... break
38
+ ... time.sleep(1)
39
+ >>>
40
+ >>> if status["success"]:
41
+ ... print("Scraper outputs:", status["scraper_outputs"])
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ api_key: str,
47
+ base_url: str = "https://api.simplex.sh",
48
+ timeout: int = 30,
49
+ max_retries: int = 3,
50
+ retry_delay: float = 1.0,
51
+ ):
52
+ """
53
+ Initialize the Simplex client.
54
+
55
+ Args:
56
+ api_key: Your Simplex API key (required)
57
+ base_url: Base URL for the API (default: "https://api.simplex.sh")
58
+ timeout: Request timeout in seconds (default: 30)
59
+ max_retries: Maximum number of retry attempts (default: 3)
60
+ retry_delay: Delay between retries in seconds (default: 1.0)
61
+
62
+ Raises:
63
+ ValueError: If api_key is not provided
64
+ """
65
+ if not api_key:
66
+ raise ValueError("api_key is required")
67
+
68
+ self._http_client = HttpClient(
69
+ base_url=base_url,
70
+ api_key=api_key,
71
+ timeout=timeout,
72
+ max_retries=max_retries,
73
+ retry_delay=retry_delay,
74
+ )
75
+
76
+ def run_workflow(
77
+ self,
78
+ workflow_id: str,
79
+ variables: dict[str, Any] | None = None,
80
+ metadata: str | None = None,
81
+ webhook_url: str | None = None,
82
+ ) -> RunWorkflowResponse:
83
+ """
84
+ Run a workflow by its ID.
85
+
86
+ Args:
87
+ workflow_id: The ID of the workflow to run
88
+ variables: Dictionary of variables to pass to the workflow
89
+ metadata: Optional metadata string to attach to the workflow run
90
+ webhook_url: Optional webhook URL for status updates
91
+
92
+ Returns:
93
+ RunWorkflowResponse with session_id and other details
94
+
95
+ Raises:
96
+ WorkflowError: If the workflow fails to start
97
+
98
+ Example:
99
+ >>> result = client.run_workflow(
100
+ ... "workflow-id",
101
+ ... variables={"email": "user@example.com"}
102
+ ... )
103
+ >>> print(f"Session ID: {result['session_id']}")
104
+ """
105
+ request_data: dict[str, Any] = {"workflow_id": workflow_id}
106
+
107
+ if variables is not None:
108
+ request_data["variables"] = variables
109
+ if metadata is not None:
110
+ request_data["metadata"] = metadata
111
+ if webhook_url is not None:
112
+ request_data["webhook_url"] = webhook_url
113
+
114
+ try:
115
+ response: RunWorkflowResponse = self._http_client.post(
116
+ "/run_workflow",
117
+ data=request_data,
118
+ )
119
+ return response
120
+ except Exception as e:
121
+ if isinstance(e, WorkflowError):
122
+ raise
123
+ raise WorkflowError(f"Failed to run workflow: {e}", workflow_id=workflow_id)
124
+
125
+ def get_session_status(self, session_id: str) -> SessionStatusResponse:
126
+ """
127
+ Get the status of a session.
128
+
129
+ Use this method to poll for workflow completion. The session is complete
130
+ when `in_progress` is False.
131
+
132
+ Args:
133
+ session_id: The session ID to check
134
+
135
+ Returns:
136
+ SessionStatusResponse with status, metadata, and scraper outputs
137
+
138
+ Raises:
139
+ WorkflowError: If retrieving status fails
140
+
141
+ Example:
142
+ >>> status = client.get_session_status("session-123")
143
+ >>> if not status["in_progress"]:
144
+ ... if status["success"]:
145
+ ... print("Success! Outputs:", status["scraper_outputs"])
146
+ ... else:
147
+ ... print("Failed")
148
+ """
149
+ try:
150
+ response: SessionStatusResponse = self._http_client.get(
151
+ f"/get_session_status/{session_id}"
152
+ )
153
+ return response
154
+ except Exception as e:
155
+ if isinstance(e, WorkflowError):
156
+ raise
157
+ raise WorkflowError(
158
+ f"Failed to get session status: {e}",
159
+ session_id=session_id,
160
+ )
161
+
162
+ def download_session_files(
163
+ self,
164
+ session_id: str,
165
+ filename: str | None = None,
166
+ ) -> bytes:
167
+ """
168
+ Download files from a session.
169
+
170
+ Downloads files that were created or downloaded during a workflow session.
171
+ If no filename is specified, all files are downloaded as a zip archive.
172
+
173
+ Args:
174
+ session_id: ID of the session to download files from
175
+ filename: Optional specific filename to download
176
+
177
+ Returns:
178
+ File content as bytes
179
+
180
+ Raises:
181
+ WorkflowError: If file download fails
182
+
183
+ Example:
184
+ >>> # Download all files as zip
185
+ >>> zip_data = client.download_session_files("session-123")
186
+ >>> with open("files.zip", "wb") as f:
187
+ ... f.write(zip_data)
188
+ >>>
189
+ >>> # Download specific file
190
+ >>> pdf_data = client.download_session_files("session-123", "report.pdf")
191
+ """
192
+ try:
193
+ params: dict[str, str] = {"session_id": session_id}
194
+ if filename:
195
+ params["filename"] = filename
196
+
197
+ content = self._http_client.download_file("/download_session_files", params=params)
198
+
199
+ # Check if the response is a JSON error
200
+ try:
201
+ text = content.decode("utf-8")
202
+ data = json.loads(text)
203
+ if isinstance(data, dict) and data.get("succeeded") is False:
204
+ raise WorkflowError(
205
+ data.get("error") or "Failed to download session files",
206
+ session_id=session_id,
207
+ )
208
+ except (UnicodeDecodeError, json.JSONDecodeError):
209
+ # Binary data (the file), which is what we want
210
+ pass
211
+
212
+ return content
213
+ except WorkflowError:
214
+ raise
215
+ except Exception as e:
216
+ raise WorkflowError(
217
+ f"Failed to download session files: {e}",
218
+ session_id=session_id,
219
+ )
220
+
221
+ def retrieve_session_replay(self, session_id: str) -> bytes:
222
+ """
223
+ Retrieve the session replay video for a completed session.
224
+
225
+ Downloads a video (MP4) recording of the browser session after it
226
+ has completed.
227
+
228
+ Args:
229
+ session_id: ID of the session to retrieve replay for
230
+
231
+ Returns:
232
+ Video content as bytes (MP4 format)
233
+
234
+ Raises:
235
+ WorkflowError: If retrieving session replay fails
236
+
237
+ Example:
238
+ >>> video_data = client.retrieve_session_replay("session-123")
239
+ >>> with open("replay.mp4", "wb") as f:
240
+ ... f.write(video_data)
241
+ """
242
+ try:
243
+ content = self._http_client.download_file(f"/retrieve_session_replay/{session_id}")
244
+ return content
245
+ except Exception as e:
246
+ if isinstance(e, WorkflowError):
247
+ raise
248
+ raise WorkflowError(
249
+ f"Failed to retrieve session replay: {e}",
250
+ session_id=session_id,
251
+ )
252
+
253
+ def retrieve_session_logs(self, session_id: str) -> Any:
254
+ """
255
+ Retrieve the session logs for a completed session.
256
+
257
+ Downloads the detailed logs of all actions and events that occurred
258
+ during the workflow session execution.
259
+
260
+ Args:
261
+ session_id: ID of the session to retrieve logs for
262
+
263
+ Returns:
264
+ Parsed JSON logs containing session events and details
265
+
266
+ Raises:
267
+ WorkflowError: If retrieving session logs fails
268
+
269
+ Example:
270
+ >>> logs = client.retrieve_session_logs("session-123")
271
+ >>> for entry in logs:
272
+ ... print(f"{entry['timestamp']}: {entry['message']}")
273
+ """
274
+ try:
275
+ content = self._http_client.download_file(f"/retrieve_session_logs/{session_id}")
276
+ text = content.decode("utf-8")
277
+ return json.loads(text)
278
+ except json.JSONDecodeError as e:
279
+ raise WorkflowError(
280
+ f"Failed to parse session logs: {e}",
281
+ session_id=session_id,
282
+ )
283
+ except Exception as e:
284
+ if isinstance(e, WorkflowError):
285
+ raise
286
+ raise WorkflowError(
287
+ f"Failed to retrieve session logs: {e}",
288
+ session_id=session_id,
289
+ )
simplex/errors.py ADDED
@@ -0,0 +1,119 @@
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 __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+
13
+ class SimplexError(Exception):
14
+ """
15
+ Base exception class for all Simplex SDK errors.
16
+
17
+ All custom exceptions in the SDK inherit from this class, making it easy
18
+ to catch any SDK-related error with a single except clause.
19
+
20
+ Attributes:
21
+ message: Human-readable error message
22
+ status_code: HTTP status code if applicable
23
+ data: Additional error data or context
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ message: str,
29
+ status_code: int | None = None,
30
+ data: Any = None,
31
+ ):
32
+ super().__init__(message)
33
+ self.message = message
34
+ self.status_code = status_code
35
+ self.data = data
36
+
37
+ def __str__(self) -> str:
38
+ if self.status_code:
39
+ return f"[{self.status_code}] {self.message}"
40
+ return self.message
41
+
42
+
43
+ class NetworkError(SimplexError):
44
+ """
45
+ Raised when a network-related error occurs.
46
+
47
+ This includes connection failures, timeouts, and other network issues
48
+ that prevent communication with the Simplex API.
49
+ """
50
+
51
+ def __init__(self, message: str):
52
+ super().__init__(f"Network error: {message}")
53
+
54
+
55
+ class ValidationError(SimplexError):
56
+ """
57
+ Raised when request validation fails (HTTP 400).
58
+
59
+ This indicates that the request data was invalid or malformed,
60
+ such as missing required fields or invalid parameter values.
61
+ """
62
+
63
+ def __init__(self, message: str, data: Any = None):
64
+ super().__init__(message, status_code=400, data=data)
65
+
66
+
67
+ class AuthenticationError(SimplexError):
68
+ """
69
+ Raised when authentication fails (HTTP 401 or 403).
70
+
71
+ This typically indicates an invalid API key or insufficient permissions
72
+ to access the requested resource.
73
+ """
74
+
75
+ def __init__(self, message: str):
76
+ super().__init__(message, status_code=401)
77
+
78
+
79
+ class RateLimitError(SimplexError):
80
+ """
81
+ Raised when rate limit is exceeded (HTTP 429).
82
+
83
+ The Simplex API has rate limits to prevent abuse. When exceeded,
84
+ this error is raised with information about when to retry.
85
+
86
+ Attributes:
87
+ retry_after: Number of seconds to wait before retrying
88
+ """
89
+
90
+ def __init__(self, message: str, retry_after: int | None = None):
91
+ super().__init__(message, status_code=429)
92
+ self.retry_after = retry_after
93
+
94
+
95
+ class WorkflowError(SimplexError):
96
+ """
97
+ Raised when a workflow operation fails.
98
+
99
+ This is a specialized error for workflow-related failures,
100
+ including session creation, workflow execution, and agent tasks.
101
+
102
+ Attributes:
103
+ workflow_id: The ID of the workflow that failed (if applicable)
104
+ session_id: The ID of the session that failed (if applicable)
105
+ """
106
+
107
+ def __init__(
108
+ self,
109
+ message: str,
110
+ workflow_id: str | None = None,
111
+ session_id: str | None = None,
112
+ ):
113
+ super().__init__(
114
+ message,
115
+ status_code=500,
116
+ data={"workflow_id": workflow_id, "session_id": session_id},
117
+ )
118
+ self.workflow_id = workflow_id
119
+ self.session_id = session_id
simplex/types.py ADDED
@@ -0,0 +1,64 @@
1
+ """
2
+ Type definitions for the Simplex SDK.
3
+
4
+ This module contains TypedDict classes used for type hinting throughout the SDK.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, TypedDict
10
+
11
+
12
+ class FileMetadata(TypedDict):
13
+ """
14
+ Metadata for a file downloaded or created during a session.
15
+
16
+ Attributes:
17
+ filename: The filename
18
+ download_url: The URL the file was downloaded from
19
+ file_size: File size in bytes
20
+ download_timestamp: ISO timestamp when the file was downloaded/created
21
+ """
22
+
23
+ filename: str
24
+ download_url: str
25
+ file_size: int
26
+ download_timestamp: str
27
+
28
+
29
+ class SessionStatusResponse(TypedDict):
30
+ """
31
+ Response from polling session status.
32
+
33
+ Attributes:
34
+ in_progress: Whether the session is still running
35
+ success: Whether the session completed successfully (None while in progress)
36
+ metadata: Custom metadata provided when the session was started
37
+ workflow_metadata: Metadata from the workflow definition
38
+ file_metadata: Metadata for files downloaded during the session
39
+ scraper_outputs: Scraper outputs collected during the session
40
+ """
41
+
42
+ in_progress: bool
43
+ success: bool | None
44
+ metadata: dict[str, Any]
45
+ workflow_metadata: dict[str, Any]
46
+ file_metadata: list[FileMetadata]
47
+ scraper_outputs: list[Any]
48
+
49
+
50
+ class RunWorkflowResponse(TypedDict):
51
+ """
52
+ Response from running a workflow.
53
+
54
+ Attributes:
55
+ succeeded: Whether the workflow started successfully
56
+ message: Human-readable status message
57
+ session_id: Unique identifier for this workflow session
58
+ vnc_url: URL for VNC access to the workflow session
59
+ """
60
+
61
+ succeeded: bool
62
+ message: str
63
+ session_id: str
64
+ vnc_url: str
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.4
2
+ Name: simplex
3
+ Version: 2.0.0
4
+ Summary: Official Python SDK for the Simplex API
5
+ Author-email: Simplex <support@simplex.sh>
6
+ License: MIT
7
+ Project-URL: Homepage, https://simplex.sh
8
+ Project-URL: Documentation, https://docs.simplex.sh
9
+ Project-URL: Repository, https://github.com/simplexlabs/simplex-python
10
+ Keywords: simplex,api,sdk,workflow,automation,browser,scraping
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: requests>=2.25.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
29
+ Requires-Dist: types-requests>=2.25.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Simplex Python SDK
33
+
34
+ Official Python SDK for the [Simplex API](https://simplex.sh) - A powerful workflow automation platform for browser-based tasks.
35
+
36
+ [![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install simplex
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ import time
49
+ from simplex import SimplexClient
50
+
51
+ # Initialize the client
52
+ client = SimplexClient(api_key="your-api-key")
53
+
54
+ # Run a workflow
55
+ response = client.run_workflow(
56
+ "workflow-id",
57
+ variables={"email": "user@example.com"}
58
+ )
59
+
60
+ print(f"Session started: {response['session_id']}")
61
+
62
+ # Poll for completion
63
+ while True:
64
+ status = client.get_session_status(response["session_id"])
65
+ if not status["in_progress"]:
66
+ break
67
+ time.sleep(1)
68
+
69
+ # Check results
70
+ if status["success"]:
71
+ print("Success!")
72
+ print("Scraper outputs:", status["scraper_outputs"])
73
+ print("File metadata:", status["file_metadata"])
74
+ else:
75
+ print("Failed")
76
+ ```
77
+
78
+ ## API Reference
79
+
80
+ ### SimplexClient
81
+
82
+ ```python
83
+ client = SimplexClient(
84
+ api_key="your-api-key",
85
+ base_url="https://api.simplex.sh", # Optional
86
+ timeout=30, # Request timeout in seconds
87
+ max_retries=3, # Retry attempts for failed requests
88
+ retry_delay=1.0, # Delay between retries in seconds
89
+ )
90
+ ```
91
+
92
+ ### Methods
93
+
94
+ #### `run_workflow(workflow_id, variables=None, metadata=None, webhook_url=None)`
95
+
96
+ Run a workflow by its ID.
97
+
98
+ ```python
99
+ response = client.run_workflow(
100
+ "workflow-id",
101
+ variables={"key": "value"},
102
+ metadata="optional metadata",
103
+ webhook_url="https://your-webhook.com/callback"
104
+ )
105
+
106
+ print(response["session_id"]) # Session ID for polling
107
+ print(response["vnc_url"]) # VNC URL to watch the session
108
+ ```
109
+
110
+ #### `get_session_status(session_id)`
111
+
112
+ Get the status of a running or completed session.
113
+
114
+ ```python
115
+ status = client.get_session_status("session-id")
116
+
117
+ print(status["in_progress"]) # True while running
118
+ print(status["success"]) # True/False when complete, None while running
119
+ print(status["scraper_outputs"]) # Data collected by scrapers
120
+ print(status["file_metadata"]) # Metadata for downloaded files
121
+ print(status["metadata"]) # Custom metadata
122
+ print(status["workflow_metadata"]) # Workflow metadata
123
+ ```
124
+
125
+ #### `download_session_files(session_id, filename=None)`
126
+
127
+ Download files from a completed session.
128
+
129
+ ```python
130
+ # Download all files as a zip
131
+ zip_data = client.download_session_files("session-id")
132
+ with open("files.zip", "wb") as f:
133
+ f.write(zip_data)
134
+
135
+ # Download a specific file
136
+ pdf_data = client.download_session_files("session-id", filename="report.pdf")
137
+ with open("report.pdf", "wb") as f:
138
+ f.write(pdf_data)
139
+ ```
140
+
141
+ #### `retrieve_session_replay(session_id)`
142
+
143
+ Download the session replay video (MP4).
144
+
145
+ ```python
146
+ video = client.retrieve_session_replay("session-id")
147
+ with open("replay.mp4", "wb") as f:
148
+ f.write(video)
149
+ ```
150
+
151
+ #### `retrieve_session_logs(session_id)`
152
+
153
+ Get the session logs as parsed JSON.
154
+
155
+ ```python
156
+ logs = client.retrieve_session_logs("session-id")
157
+ for entry in logs:
158
+ print(f"{entry['timestamp']}: {entry['message']}")
159
+ ```
160
+
161
+ ## Error Handling
162
+
163
+ The SDK provides specific exception types for different error scenarios:
164
+
165
+ ```python
166
+ from simplex import (
167
+ SimplexClient,
168
+ SimplexError,
169
+ WorkflowError,
170
+ AuthenticationError,
171
+ RateLimitError,
172
+ NetworkError,
173
+ ValidationError,
174
+ )
175
+
176
+ client = SimplexClient(api_key="your-api-key")
177
+
178
+ try:
179
+ result = client.run_workflow("workflow-id")
180
+ except AuthenticationError as e:
181
+ print(f"Invalid API key: {e.message}")
182
+ except RateLimitError as e:
183
+ print(f"Rate limited. Retry after {e.retry_after} seconds")
184
+ except ValidationError as e:
185
+ print(f"Invalid request: {e.message}")
186
+ except WorkflowError as e:
187
+ print(f"Workflow error: {e.message}")
188
+ print(f"Session ID: {e.session_id}")
189
+ except NetworkError as e:
190
+ print(f"Network error: {e.message}")
191
+ except SimplexError as e:
192
+ print(f"General error: {e.message}")
193
+ ```
194
+
195
+ ## Type Hints
196
+
197
+ The SDK includes full type hints for better IDE support:
198
+
199
+ ```python
200
+ from simplex import (
201
+ SimplexClient,
202
+ SessionStatusResponse,
203
+ RunWorkflowResponse,
204
+ FileMetadata,
205
+ )
206
+
207
+ client = SimplexClient(api_key="your-api-key")
208
+ response: RunWorkflowResponse = client.run_workflow("workflow-id")
209
+ status: SessionStatusResponse = client.get_session_status(response["session_id"])
210
+ ```
211
+
212
+ ## Requirements
213
+
214
+ - Python 3.9+
215
+ - `requests>=2.25.0`
216
+
217
+ ## License
218
+
219
+ MIT License - see [LICENSE](LICENSE) for details.
220
+
221
+ ## Support
222
+
223
+ - Documentation: [https://docs.simplex.sh](https://docs.simplex.sh)
224
+ - Email: support@simplex.sh
@@ -0,0 +1,10 @@
1
+ simplex/__init__.py,sha256=Okie1I8gw2-rwBZaNoqOXggKgcuy9I-wezRnH4yMFoQ,1188
2
+ simplex/_http_client.py,sha256=BbDsNYnfpydjNNXNw7gkyoRXGIG-QFMXfQgKN93WBxU,6819
3
+ simplex/client.py,sha256=xPw5eQhF7V9os0iXwT0jQbIWPjqloj-oXZD_6RXQmk0,9480
4
+ simplex/errors.py,sha256=xfRn6FCbQT_YgjZJrWq9MQPj8LMfjNAhNYMBW6XL8aw,3323
5
+ simplex/types.py,sha256=pc4n-HQ2hNm1LfK5psgh-UDKfvl1_pN9EXfkCyFTkvM,1765
6
+ simplex-2.0.0.dist-info/licenses/LICENSE,sha256=TyxVTRp5rBigFCL8EDC9Bv7AZfb4JBMVUZUeCs4Pk6Y,1063
7
+ simplex-2.0.0.dist-info/METADATA,sha256=VN8m-8rhvYg5ZIV7L_mjlscZm0-xrsuw1eg4t1daWBc,6024
8
+ simplex-2.0.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
9
+ simplex-2.0.0.dist-info/top_level.txt,sha256=cbMH1bYpN0A3gP-ecibPRHasHoqB-01T_2BUFS8p0CE,8
10
+ simplex-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Simplex
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 @@
1
+ simplex