pyromind-sdk 0.0.12__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.
- pyromind_sdk/__init__.py +65 -0
- pyromind_sdk/client/__init__.py +30 -0
- pyromind_sdk/client/base.py +270 -0
- pyromind_sdk/client/client.py +138 -0
- pyromind_sdk/client/inference.py +135 -0
- pyromind_sdk/client/instance.py +231 -0
- pyromind_sdk/client/models.py +608 -0
- pyromind_sdk/client/sandboxes.py +296 -0
- pyromind_sdk/client/storage.py +708 -0
- pyromind_sdk/client/training.py +202 -0
- pyromind_sdk/client/workflow/__init__.py +52 -0
- pyromind_sdk/client/workflow/converter.py +1560 -0
- pyromind_sdk/client/workflow/validator.py +1716 -0
- pyromind_sdk/common/__init__.py +0 -0
- pyromind_sdk/common/constants.py +95 -0
- pyromind_sdk/common/node_sdk.py +140 -0
- pyromind_sdk/nodes/__init__.py +65 -0
- pyromind_sdk/nodes/command_executor.py +562 -0
- pyromind_sdk/nodes/function_call_wrapper.py +310 -0
- pyromind_sdk/nodes/node_validator.py +150 -0
- pyromind_sdk/nodes/python_function_executor.py +160 -0
- pyromind_sdk/nodes/python_to_yaml.py +455 -0
- pyromind_sdk/nodes/type_converter.py +123 -0
- pyromind_sdk/nodes/yaml_loader.py +798 -0
- pyromind_sdk/tests/__init__.py +0 -0
- pyromind_sdk/tests/test_yaml_nodes.py +334 -0
- pyromind_sdk-0.0.12.dist-info/METADATA +366 -0
- pyromind_sdk-0.0.12.dist-info/RECORD +31 -0
- pyromind_sdk-0.0.12.dist-info/WHEEL +5 -0
- pyromind_sdk-0.0.12.dist-info/licenses/LICENSE +22 -0
- pyromind_sdk-0.0.12.dist-info/top_level.txt +1 -0
pyromind_sdk/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyroMind Node SDK
|
|
3
|
+
|
|
4
|
+
A lightweight SDK stub for local development and testing of third-party nodes
|
|
5
|
+
without the full platform codebase (without `app.models.nodes`).
|
|
6
|
+
|
|
7
|
+
In the real platform runtime environment, nodes should prioritize importing
|
|
8
|
+
base classes from `app.models.nodes`.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
# Export YAML nodes functionality
|
|
14
|
+
from .nodes import (
|
|
15
|
+
load_nodes_from_yaml,
|
|
16
|
+
load_all_nodes_from_directory,
|
|
17
|
+
create_node_class_from_yaml,
|
|
18
|
+
convert_node_class_to_yaml,
|
|
19
|
+
yaml_to_node_class,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Export API client functionality
|
|
23
|
+
from .client import (
|
|
24
|
+
PyroMindClient,
|
|
25
|
+
PyroMindAPIError,
|
|
26
|
+
SandboxesClient,
|
|
27
|
+
InstanceClient,
|
|
28
|
+
InferenceClient,
|
|
29
|
+
TrainingClient,
|
|
30
|
+
StorageClient,
|
|
31
|
+
PyroMindAPIClient,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Export workflow functionality
|
|
35
|
+
from .client.workflow import (
|
|
36
|
+
WorkflowLiteConverter,
|
|
37
|
+
LayoutGenerator,
|
|
38
|
+
to_workflow_lite,
|
|
39
|
+
to_workflow_standard,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"__version__",
|
|
44
|
+
# YAML nodes functionality
|
|
45
|
+
"load_nodes_from_yaml",
|
|
46
|
+
"load_all_nodes_from_directory",
|
|
47
|
+
"create_node_class_from_yaml",
|
|
48
|
+
"convert_node_class_to_yaml",
|
|
49
|
+
"yaml_to_node_class",
|
|
50
|
+
# API client functionality
|
|
51
|
+
"PyroMindClient",
|
|
52
|
+
"PyroMindAPIError",
|
|
53
|
+
"SandboxesClient",
|
|
54
|
+
"InstanceClient",
|
|
55
|
+
"InferenceClient",
|
|
56
|
+
"TrainingClient",
|
|
57
|
+
"StorageClient",
|
|
58
|
+
"PyroMindAPIClient",
|
|
59
|
+
# Workflow functionality
|
|
60
|
+
"WorkflowLiteConverter",
|
|
61
|
+
"LayoutGenerator",
|
|
62
|
+
"to_workflow_lite",
|
|
63
|
+
"to_workflow_standard",
|
|
64
|
+
]
|
|
65
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyroMind API Client SDK
|
|
3
|
+
|
|
4
|
+
This module provides a Python client SDK for interacting with the PyroMind API v1.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import PyroMindClient, PyroMindAPIError
|
|
8
|
+
from .sandboxes import SandboxesClient
|
|
9
|
+
from .instance import InstanceClient
|
|
10
|
+
from .inference import InferenceClient
|
|
11
|
+
from .training import TrainingClient
|
|
12
|
+
from .storage import StorageClient
|
|
13
|
+
from .client import PyroMindAPIClient
|
|
14
|
+
from .workflow import (
|
|
15
|
+
validate_workflow,
|
|
16
|
+
ValidationError
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"PyroMindClient",
|
|
21
|
+
"PyroMindAPIError",
|
|
22
|
+
"SandboxesClient",
|
|
23
|
+
"InstanceClient",
|
|
24
|
+
"InferenceClient",
|
|
25
|
+
"TrainingClient",
|
|
26
|
+
"StorageClient",
|
|
27
|
+
"PyroMindAPIClient",
|
|
28
|
+
"validate_workflow",
|
|
29
|
+
"ValidationError",
|
|
30
|
+
]
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base client class for PyroMind API
|
|
3
|
+
|
|
4
|
+
This module provides the base client class that handles authentication,
|
|
5
|
+
HTTP requests, and error handling for all API clients.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import requests
|
|
10
|
+
from typing import Optional, Dict, Any, Union
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util.retry import Retry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Constants
|
|
16
|
+
DEFAULT_API_BASE_URL = "https://api.pyromind.ai/api/v1"
|
|
17
|
+
DEFAULT_TIMEOUT = 30
|
|
18
|
+
DEFAULT_MAX_RETRIES = 3
|
|
19
|
+
ENV_API_KEY = "PYROMIND_API_KEY"
|
|
20
|
+
ENV_BASE_URL = "PYROMIND_BASE_URL"
|
|
21
|
+
RETRY_STATUS_CODES = [429, 500, 502, 503, 504]
|
|
22
|
+
RETRY_ALLOWED_METHODS = ["GET", "POST", "PUT", "DELETE"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PyroMindAPIError(Exception):
|
|
26
|
+
"""Base exception for PyroMind API errors"""
|
|
27
|
+
def __init__(self, message: str, status_code: Optional[int] = None, response: Optional[Dict] = None):
|
|
28
|
+
self.message = message
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
self.response = response
|
|
31
|
+
super().__init__(self.message)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PyroMindClient:
|
|
35
|
+
"""
|
|
36
|
+
Base client class for PyroMind API
|
|
37
|
+
|
|
38
|
+
Handles authentication, HTTP requests, and error handling.
|
|
39
|
+
All resource-specific clients inherit from this class.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
api_key: Bearer token for API authentication. If not provided, will try to
|
|
43
|
+
read from PYROMIND_API_KEY environment variable. If neither is
|
|
44
|
+
provided, will raise ValueError.
|
|
45
|
+
base_url: Base URL for the API. If not provided, will try to read from
|
|
46
|
+
PYROMIND_BASE_URL environment variable. If neither is provided,
|
|
47
|
+
defaults to https://api.pyromind.ai/api/v1
|
|
48
|
+
timeout: Request timeout in seconds (default: 30)
|
|
49
|
+
max_retries: Maximum number of retries for failed requests (default: 3)
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If api_key is not provided and PYROMIND_API_KEY environment
|
|
53
|
+
variable is not set.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
api_key: Optional[str] = None,
|
|
59
|
+
base_url: Optional[str] = None,
|
|
60
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
61
|
+
max_retries: int = DEFAULT_MAX_RETRIES
|
|
62
|
+
):
|
|
63
|
+
# Get API key from parameter or environment variable
|
|
64
|
+
if api_key is None:
|
|
65
|
+
api_key = os.getenv(ENV_API_KEY)
|
|
66
|
+
|
|
67
|
+
if not api_key:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"API key is required. Please provide it either as a parameter "
|
|
70
|
+
f"or set the {ENV_API_KEY} environment variable."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Strip whitespace from API key
|
|
74
|
+
api_key = api_key.strip()
|
|
75
|
+
|
|
76
|
+
# Get base URL from parameter, environment variable, or use default
|
|
77
|
+
if base_url is None:
|
|
78
|
+
base_url = os.getenv(ENV_BASE_URL, DEFAULT_API_BASE_URL)
|
|
79
|
+
|
|
80
|
+
if not base_url:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Base URL is required. Please provide it either as a parameter "
|
|
83
|
+
f"or set the {ENV_BASE_URL} environment variable."
|
|
84
|
+
)
|
|
85
|
+
self.api_key = api_key
|
|
86
|
+
self.base_url = base_url.rstrip('/')
|
|
87
|
+
self.timeout = timeout
|
|
88
|
+
|
|
89
|
+
# Create session with retry strategy
|
|
90
|
+
self.session = requests.Session()
|
|
91
|
+
retry_strategy = Retry(
|
|
92
|
+
total=max_retries,
|
|
93
|
+
backoff_factor=1,
|
|
94
|
+
status_forcelist=RETRY_STATUS_CODES,
|
|
95
|
+
allowed_methods=RETRY_ALLOWED_METHODS
|
|
96
|
+
)
|
|
97
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
98
|
+
self.session.mount("http://", adapter)
|
|
99
|
+
self.session.mount("https://", adapter)
|
|
100
|
+
|
|
101
|
+
# Set default headers
|
|
102
|
+
self.session.headers.update({
|
|
103
|
+
"Authorization": f"Bearer {api_key}",
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Accept": "application/json"
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
def _extract_data(self, response: Dict[str, Any]) -> Any:
|
|
109
|
+
"""
|
|
110
|
+
Extract data from API response
|
|
111
|
+
|
|
112
|
+
API responses are wrapped in {success: True, data: {...}} format.
|
|
113
|
+
This method extracts the actual data from the response.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
response: API response dictionary
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Extracted data from response
|
|
120
|
+
"""
|
|
121
|
+
if isinstance(response, dict):
|
|
122
|
+
if "data" in response:
|
|
123
|
+
return response["data"]
|
|
124
|
+
# If response doesn't have 'data' field, return the whole response
|
|
125
|
+
# (for backward compatibility)
|
|
126
|
+
return response
|
|
127
|
+
return response
|
|
128
|
+
|
|
129
|
+
def _request(
|
|
130
|
+
self,
|
|
131
|
+
method: str,
|
|
132
|
+
endpoint: str,
|
|
133
|
+
params: Optional[Dict[str, Any]] = None,
|
|
134
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
135
|
+
**kwargs
|
|
136
|
+
) -> Dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Make an HTTP request to the API
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
method: HTTP method (GET, POST, PUT, DELETE)
|
|
142
|
+
endpoint: API endpoint (relative to base_url)
|
|
143
|
+
params: Query parameters
|
|
144
|
+
json_data: JSON request body
|
|
145
|
+
**kwargs: Additional arguments to pass to requests
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Response JSON data as dictionary
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
PyroMindAPIError: If the request fails
|
|
152
|
+
"""
|
|
153
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
response = self.session.request(
|
|
157
|
+
method=method,
|
|
158
|
+
url=url,
|
|
159
|
+
params=params,
|
|
160
|
+
json=json_data,
|
|
161
|
+
timeout=self.timeout,
|
|
162
|
+
**kwargs
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Handle non-2xx responses
|
|
166
|
+
if not response.ok:
|
|
167
|
+
error_data = None
|
|
168
|
+
try:
|
|
169
|
+
error_data = response.json()
|
|
170
|
+
except:
|
|
171
|
+
error_data = {"message": response.text}
|
|
172
|
+
|
|
173
|
+
# Provide more detailed error messages for specific status codes
|
|
174
|
+
if response.status_code == 400:
|
|
175
|
+
error_message = (
|
|
176
|
+
"Bad Request (400). The request was invalid or malformed. "
|
|
177
|
+
"This usually means there's an issue with the request parameters, "
|
|
178
|
+
"request body format, or missing required fields."
|
|
179
|
+
) + f"\nResponse: {response.text}"
|
|
180
|
+
elif response.status_code == 401:
|
|
181
|
+
error_message = (
|
|
182
|
+
"Authentication failed (401). Please check your API key. "
|
|
183
|
+
"The API key may be invalid, expired, or incorrectly formatted."
|
|
184
|
+
)
|
|
185
|
+
if error_data.get("message"):
|
|
186
|
+
error_message += f" Server message: {error_data.get('message')}"
|
|
187
|
+
elif response.status_code == 404:
|
|
188
|
+
error_message = "Resource not found (404). The requested resource does not exist."
|
|
189
|
+
# Try to extract detailed error information from the response
|
|
190
|
+
if isinstance(error_data, dict):
|
|
191
|
+
# Check for nested error structure (APIResponse format)
|
|
192
|
+
if "error" in error_data:
|
|
193
|
+
error_obj = error_data.get("error", {})
|
|
194
|
+
if isinstance(error_obj, dict):
|
|
195
|
+
error_code = error_obj.get("code", "")
|
|
196
|
+
error_msg = error_obj.get("message", "")
|
|
197
|
+
if error_code:
|
|
198
|
+
error_message = f"{error_code}: {error_msg}"
|
|
199
|
+
elif error_msg:
|
|
200
|
+
error_message = error_msg
|
|
201
|
+
elif "detail" in error_data:
|
|
202
|
+
detail = error_data.get("detail")
|
|
203
|
+
if isinstance(detail, dict) and "error" in detail:
|
|
204
|
+
error_obj = detail.get("error", {})
|
|
205
|
+
if isinstance(error_obj, dict):
|
|
206
|
+
error_code = error_obj.get("code", "")
|
|
207
|
+
error_msg = error_obj.get("message", "")
|
|
208
|
+
if error_code:
|
|
209
|
+
error_message = f"{error_code}: {error_msg}"
|
|
210
|
+
elif error_msg:
|
|
211
|
+
error_message = error_msg
|
|
212
|
+
elif isinstance(detail, str):
|
|
213
|
+
error_message = detail
|
|
214
|
+
elif error_data.get("message"):
|
|
215
|
+
error_message += f" Details: {error_data.get('message')}"
|
|
216
|
+
elif response.status_code == 422:
|
|
217
|
+
error_message = "Unprocessable Entity (422). The request was well-formed but contains semantic errors."
|
|
218
|
+
if error_data.get("message"):
|
|
219
|
+
error_message += f" Details: {error_data.get('message')}"
|
|
220
|
+
if error_data.get("detail"):
|
|
221
|
+
error_message += f"\nValidation details: {error_data.get('detail')}"
|
|
222
|
+
else:
|
|
223
|
+
error_message = error_data.get("message", f"API request failed with status {response.status_code}")
|
|
224
|
+
if error_data.get("detail"):
|
|
225
|
+
error_message += f"\nDetails: {error_data.get('detail')}"
|
|
226
|
+
|
|
227
|
+
raise PyroMindAPIError(
|
|
228
|
+
message=error_message,
|
|
229
|
+
status_code=response.status_code,
|
|
230
|
+
response=error_data
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Return JSON response
|
|
234
|
+
if response.content:
|
|
235
|
+
return response.json()
|
|
236
|
+
return {}
|
|
237
|
+
|
|
238
|
+
except requests.exceptions.RequestException as e:
|
|
239
|
+
raise PyroMindAPIError(
|
|
240
|
+
message=f"Request failed: {str(e)}",
|
|
241
|
+
status_code=None
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]:
|
|
245
|
+
"""Make a GET request"""
|
|
246
|
+
return self._request("GET", endpoint, params=params, **kwargs)
|
|
247
|
+
|
|
248
|
+
def post(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]:
|
|
249
|
+
"""Make a POST request"""
|
|
250
|
+
return self._request("POST", endpoint, json_data=json_data, **kwargs)
|
|
251
|
+
|
|
252
|
+
def put(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]:
|
|
253
|
+
"""Make a PUT request"""
|
|
254
|
+
return self._request("PUT", endpoint, json_data=json_data, **kwargs)
|
|
255
|
+
|
|
256
|
+
def delete(self, endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
257
|
+
"""Make a DELETE request"""
|
|
258
|
+
return self._request("DELETE", endpoint, **kwargs)
|
|
259
|
+
|
|
260
|
+
def close(self):
|
|
261
|
+
"""Close the session"""
|
|
262
|
+
self.session.close()
|
|
263
|
+
|
|
264
|
+
def __enter__(self):
|
|
265
|
+
"""Context manager entry"""
|
|
266
|
+
return self
|
|
267
|
+
|
|
268
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
269
|
+
"""Context manager exit"""
|
|
270
|
+
self.close()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main PyroMind API Client
|
|
3
|
+
|
|
4
|
+
This module provides the main client class that integrates all resource clients.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from .base import PyroMindClient
|
|
10
|
+
from .sandboxes import SandboxesClient
|
|
11
|
+
from .instance import InstanceClient
|
|
12
|
+
from .inference import InferenceClient
|
|
13
|
+
from .training import TrainingClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PyroMindAPIClient:
|
|
17
|
+
"""
|
|
18
|
+
Main PyroMind API Client
|
|
19
|
+
|
|
20
|
+
This class provides a unified interface to all PyroMind API resources.
|
|
21
|
+
It integrates all resource-specific clients (Sandboxes, Instance, Inference, Training).
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
api_key: Bearer token for API authentication. If not provided, will try to
|
|
25
|
+
read from PYROMIND_API_KEY environment variable. If neither is
|
|
26
|
+
provided, will raise ValueError.
|
|
27
|
+
base_url: Base URL for the API. If not provided, will try to read from
|
|
28
|
+
PYROMIND_BASE_URL environment variable. If neither is provided,
|
|
29
|
+
defaults to https://api.pyromind.ai/api/v1
|
|
30
|
+
timeout: Request timeout in seconds (default: 30)
|
|
31
|
+
max_retries: Maximum number of retries for failed requests (default: 3)
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ValueError: If api_key is not provided and PYROMIND_API_KEY environment
|
|
35
|
+
variable is not set.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
```python
|
|
39
|
+
from pyromind_sdk import PyroMindAPIClient
|
|
40
|
+
|
|
41
|
+
client = PyroMindAPIClient(api_key="your-api-key")
|
|
42
|
+
|
|
43
|
+
# List all sandboxes
|
|
44
|
+
sandboxes = client.sandboxes.list()
|
|
45
|
+
|
|
46
|
+
# Create a Jupyter instance
|
|
47
|
+
from pyromind_sdk.client.models import JupyterRequest, ResourceConfig
|
|
48
|
+
jupyter = client.instance.create(
|
|
49
|
+
JupyterRequest(
|
|
50
|
+
name="my-jupyter",
|
|
51
|
+
image="jupyter/scipy-notebook:latest",
|
|
52
|
+
resources=ResourceConfig(cpu="2", memory="4Gi")
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
api_key: Optional[str] = None,
|
|
61
|
+
base_url: Optional[str] = None,
|
|
62
|
+
timeout: int = 30,
|
|
63
|
+
max_retries: int = 3
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the PyroMind API Client
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
api_key: Bearer token for API authentication. If not provided, will try to
|
|
70
|
+
read from PYROMIND_API_KEY environment variable.
|
|
71
|
+
base_url: Base URL for the API. If not provided, will try to read from
|
|
72
|
+
PYROMIND_BASE_URL environment variable. If neither is provided,
|
|
73
|
+
defaults to https://api.pyromind.ai/api/v1
|
|
74
|
+
timeout: Request timeout in seconds
|
|
75
|
+
max_retries: Maximum number of retries for failed requests
|
|
76
|
+
"""
|
|
77
|
+
# Get API key from parameter or environment variable
|
|
78
|
+
if api_key is None:
|
|
79
|
+
api_key = os.getenv("PYROMIND_API_KEY")
|
|
80
|
+
|
|
81
|
+
if not api_key:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"API key is required. Please provide it either as a parameter "
|
|
84
|
+
"or set the PYROMIND_API_KEY environment variable."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Get base URL from parameter, environment variable, or use default
|
|
88
|
+
if base_url is None:
|
|
89
|
+
base_url = os.getenv("PYROMIND_BASE_URL", "https://api.pre2-pyromind.ai/api/v1")
|
|
90
|
+
|
|
91
|
+
self._base_client = PyroMindClient(
|
|
92
|
+
api_key=api_key,
|
|
93
|
+
base_url=base_url,
|
|
94
|
+
timeout=timeout,
|
|
95
|
+
max_retries=max_retries
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Initialize resource clients
|
|
99
|
+
self.sandboxes = SandboxesClient(
|
|
100
|
+
api_key=api_key,
|
|
101
|
+
base_url=base_url,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
max_retries=max_retries
|
|
104
|
+
)
|
|
105
|
+
self.instance = InstanceClient(
|
|
106
|
+
api_key=api_key,
|
|
107
|
+
base_url=base_url,
|
|
108
|
+
timeout=timeout,
|
|
109
|
+
max_retries=max_retries
|
|
110
|
+
)
|
|
111
|
+
self.inference = InferenceClient(
|
|
112
|
+
api_key=api_key,
|
|
113
|
+
base_url=base_url,
|
|
114
|
+
timeout=timeout,
|
|
115
|
+
max_retries=max_retries
|
|
116
|
+
)
|
|
117
|
+
self.training = TrainingClient(
|
|
118
|
+
api_key=api_key,
|
|
119
|
+
base_url=base_url,
|
|
120
|
+
timeout=timeout,
|
|
121
|
+
max_retries=max_retries
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def close(self):
|
|
125
|
+
"""Close all client sessions"""
|
|
126
|
+
self._base_client.close()
|
|
127
|
+
self.sandboxes.close()
|
|
128
|
+
self.instance.close()
|
|
129
|
+
self.inference.close()
|
|
130
|
+
self.training.close()
|
|
131
|
+
|
|
132
|
+
def __enter__(self):
|
|
133
|
+
"""Context manager entry"""
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
137
|
+
"""Context manager exit"""
|
|
138
|
+
self.close()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inference API Client
|
|
3
|
+
|
|
4
|
+
This module provides a client for managing inference jobs via the PyroMind API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from .base import PyroMindClient
|
|
9
|
+
from .models import (
|
|
10
|
+
InferenceJobRequest,
|
|
11
|
+
InferenceJobResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InferenceClient(PyroMindClient):
|
|
16
|
+
"""
|
|
17
|
+
Client for managing inference jobs
|
|
18
|
+
|
|
19
|
+
Provides methods for creating, listing, getting, and deleting inference jobs.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def list(self) -> List[InferenceJobResponse]:
|
|
23
|
+
"""
|
|
24
|
+
List all inference jobs
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
List of InferenceJobResponse objects
|
|
28
|
+
"""
|
|
29
|
+
response = self.get("/inference")
|
|
30
|
+
# API returns {success: True, data: {...}} format
|
|
31
|
+
data = self._extract_data(response)
|
|
32
|
+
|
|
33
|
+
# Handle different possible data structures
|
|
34
|
+
if isinstance(data, dict) and "inference_jobs" in data:
|
|
35
|
+
jobs_data = data["inference_jobs"]
|
|
36
|
+
elif isinstance(data, dict) and "pagination" in data:
|
|
37
|
+
# Response format: {inference_jobs: [...], pagination: {...}}
|
|
38
|
+
jobs_data = data.get("inference_jobs", [])
|
|
39
|
+
elif isinstance(data, list):
|
|
40
|
+
jobs_data = data
|
|
41
|
+
else:
|
|
42
|
+
jobs_data = []
|
|
43
|
+
|
|
44
|
+
# Convert each job data to InferenceJobResponse
|
|
45
|
+
return [InferenceJobResponse(**job) if isinstance(job, dict) else job for job in jobs_data]
|
|
46
|
+
|
|
47
|
+
def create(self, request: InferenceJobRequest) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Create a new inference job
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
request: InferenceJobCreateRequest with job configuration
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Job ID string
|
|
56
|
+
"""
|
|
57
|
+
response = self.post("/inference", json_data=request.model_dump())
|
|
58
|
+
# API returns {success: True, data: {...}} format
|
|
59
|
+
data = self._extract_data(response)
|
|
60
|
+
|
|
61
|
+
# Backend returns either {job_id: "..."} or full job object
|
|
62
|
+
if isinstance(data, dict) and "job_id" in data:
|
|
63
|
+
return data["job_id"]
|
|
64
|
+
elif isinstance(data, dict) and "id" in data:
|
|
65
|
+
return data["id"]
|
|
66
|
+
else:
|
|
67
|
+
# Try to parse as InferenceJobResponse and extract ID
|
|
68
|
+
job_response = InferenceJobResponse(**data)
|
|
69
|
+
return job_response.id
|
|
70
|
+
|
|
71
|
+
def get_job(self, job_id: str) -> InferenceJobResponse:
|
|
72
|
+
"""
|
|
73
|
+
Get a specific inference job by ID
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
job_id: ID of the inference job to retrieve
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
InferenceJobResponse object
|
|
80
|
+
"""
|
|
81
|
+
response = self.get(f"/inference/{job_id}")
|
|
82
|
+
# API returns {success: True, data: {...}} format
|
|
83
|
+
data = self._extract_data(response)
|
|
84
|
+
|
|
85
|
+
# Backend returns the job data directly in the data field
|
|
86
|
+
return InferenceJobResponse(**data)
|
|
87
|
+
|
|
88
|
+
def update(self, job_id: str, request) -> InferenceJobResponse:
|
|
89
|
+
"""
|
|
90
|
+
Update an inference job
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
job_id: ID of the inference job to update
|
|
94
|
+
request: InferenceJobRequest with updated configuration
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
InferenceJobResponse object
|
|
98
|
+
"""
|
|
99
|
+
# Import here to avoid circular dependency
|
|
100
|
+
from .models import InferenceJobRequest
|
|
101
|
+
if not isinstance(request, InferenceJobRequest):
|
|
102
|
+
request = InferenceJobRequest(**request)
|
|
103
|
+
|
|
104
|
+
request_dict = request.model_dump(exclude_none=True)
|
|
105
|
+
|
|
106
|
+
response = self.put(f"/inference/{job_id}", json_data=request_dict)
|
|
107
|
+
# API returns {success: True, data: {...}} format
|
|
108
|
+
data = self._extract_data(response)
|
|
109
|
+
|
|
110
|
+
return InferenceJobResponse(**data)
|
|
111
|
+
|
|
112
|
+
def delete(self, job_id: str) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Delete an inference job
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
job_id: ID of the inference job to delete
|
|
118
|
+
"""
|
|
119
|
+
self._request("DELETE", f"/inference/{job_id}")
|
|
120
|
+
|
|
121
|
+
def pause(self, job_id: str) -> InferenceJobResponse:
|
|
122
|
+
"""
|
|
123
|
+
Pause an inference job
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
job_id: ID of the inference job to pause
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
InferenceJobResponse object
|
|
130
|
+
"""
|
|
131
|
+
response = self.post(f"/inference/{job_id}/pause")
|
|
132
|
+
# API returns {success: True, data: {...}} format
|
|
133
|
+
data = self._extract_data(response)
|
|
134
|
+
|
|
135
|
+
return InferenceJobResponse(**data)
|