magickmind 0.1.1__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.
- magick_mind/__init__.py +39 -0
- magick_mind/auth/__init__.py +9 -0
- magick_mind/auth/base.py +46 -0
- magick_mind/auth/email_password.py +268 -0
- magick_mind/client.py +188 -0
- magick_mind/config.py +28 -0
- magick_mind/exceptions.py +107 -0
- magick_mind/http/__init__.py +5 -0
- magick_mind/http/client.py +313 -0
- magick_mind/models/__init__.py +17 -0
- magick_mind/models/auth.py +30 -0
- magick_mind/models/common.py +32 -0
- magick_mind/models/errors.py +73 -0
- magick_mind/models/v1/__init__.py +83 -0
- magick_mind/models/v1/api_keys.py +115 -0
- magick_mind/models/v1/artifact.py +151 -0
- magick_mind/models/v1/chat.py +104 -0
- magick_mind/models/v1/corpus.py +82 -0
- magick_mind/models/v1/end_user.py +75 -0
- magick_mind/models/v1/history.py +94 -0
- magick_mind/models/v1/mindspace.py +130 -0
- magick_mind/models/v1/model.py +25 -0
- magick_mind/models/v1/project.py +73 -0
- magick_mind/realtime/__init__.py +5 -0
- magick_mind/realtime/client.py +202 -0
- magick_mind/realtime/handler.py +122 -0
- magick_mind/resources/README.md +201 -0
- magick_mind/resources/__init__.py +42 -0
- magick_mind/resources/base.py +31 -0
- magick_mind/resources/v1/__init__.py +19 -0
- magick_mind/resources/v1/api_keys.py +181 -0
- magick_mind/resources/v1/artifact.py +287 -0
- magick_mind/resources/v1/chat.py +120 -0
- magick_mind/resources/v1/corpus.py +156 -0
- magick_mind/resources/v1/end_user.py +181 -0
- magick_mind/resources/v1/history.py +88 -0
- magick_mind/resources/v1/mindspace.py +331 -0
- magick_mind/resources/v1/model.py +19 -0
- magick_mind/resources/v1/project.py +155 -0
- magick_mind/routes.py +76 -0
- magickmind-0.1.1.dist-info/METADATA +593 -0
- magickmind-0.1.1.dist-info/RECORD +43 -0
- magickmind-0.1.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""HTTP client for making API requests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import http.client
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
12
|
+
|
|
13
|
+
from magick_mind.auth.base import AuthProvider
|
|
14
|
+
from magick_mind.config import SDKConfig
|
|
15
|
+
from magick_mind.exceptions import (
|
|
16
|
+
MagickMindError,
|
|
17
|
+
ProblemDetailsException,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
ValidationError,
|
|
20
|
+
)
|
|
21
|
+
from magick_mind.models.errors import ErrorResponse, ProblemDetails
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HTTPClient:
|
|
27
|
+
"""
|
|
28
|
+
HTTP client wrapper for making authenticated API requests.
|
|
29
|
+
|
|
30
|
+
Handles:
|
|
31
|
+
- Authentication header injection
|
|
32
|
+
- Error handling and response parsing
|
|
33
|
+
- Retry logic
|
|
34
|
+
- Request/response logging
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: SDKConfig, auth: AuthProvider):
|
|
38
|
+
"""
|
|
39
|
+
Initialize HTTP client.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: SDK configuration
|
|
43
|
+
auth: Authentication provider
|
|
44
|
+
"""
|
|
45
|
+
self.config = config
|
|
46
|
+
self.auth = auth
|
|
47
|
+
self._client = httpx.Client(
|
|
48
|
+
timeout=config.timeout,
|
|
49
|
+
verify=config.verify_ssl,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def _get_headers(
|
|
53
|
+
self, extra_headers: Optional[Dict[str, str]] = None
|
|
54
|
+
) -> Dict[str, str]:
|
|
55
|
+
"""Build request headers with authentication."""
|
|
56
|
+
headers = {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"Accept": "application/json",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Add auth headers
|
|
62
|
+
auth_headers = self.auth.get_headers()
|
|
63
|
+
headers.update(auth_headers)
|
|
64
|
+
|
|
65
|
+
# Add any extra headers
|
|
66
|
+
if extra_headers:
|
|
67
|
+
headers.update(extra_headers)
|
|
68
|
+
|
|
69
|
+
return headers
|
|
70
|
+
|
|
71
|
+
def _build_url(self, path: str) -> str:
|
|
72
|
+
"""Build full URL from path."""
|
|
73
|
+
base = self.config.normalized_base_url()
|
|
74
|
+
# Ensure path starts with /
|
|
75
|
+
if not path.startswith("/"):
|
|
76
|
+
path = f"/{path}"
|
|
77
|
+
return f"{base}{path}"
|
|
78
|
+
|
|
79
|
+
def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Handle HTTP response and raise appropriate exceptions.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
response: HTTP response object
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Parsed JSON response data
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ProblemDetailsException: For RFC 7807 errors
|
|
91
|
+
ValidationError: For 400 Bad Request with field errors
|
|
92
|
+
RateLimitError: For rate limiting
|
|
93
|
+
MagickMindError: For malformed responses
|
|
94
|
+
"""
|
|
95
|
+
# Success path
|
|
96
|
+
if response.status_code < 400:
|
|
97
|
+
try:
|
|
98
|
+
return response.json()
|
|
99
|
+
except Exception:
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
# Rate limiting (special case)
|
|
103
|
+
if response.status_code == 429:
|
|
104
|
+
# Try RFC 7807 first
|
|
105
|
+
try:
|
|
106
|
+
error_response = ErrorResponse.model_validate(response.json())
|
|
107
|
+
raise RateLimitError(
|
|
108
|
+
error_response.error.detail,
|
|
109
|
+
status_code=429,
|
|
110
|
+
)
|
|
111
|
+
except (json.JSONDecodeError, PydanticValidationError):
|
|
112
|
+
raise RateLimitError(
|
|
113
|
+
"Rate limit exceeded",
|
|
114
|
+
status_code=429,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Parse error response
|
|
118
|
+
try:
|
|
119
|
+
data = response.json()
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
raise MagickMindError(
|
|
122
|
+
f"Non-JSON error response: {response.text[:200]}",
|
|
123
|
+
status_code=response.status_code,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# RFC 7807 format (98% of Bifrost endpoints)
|
|
127
|
+
if "error" in data and isinstance(data["error"], dict):
|
|
128
|
+
try:
|
|
129
|
+
error_response = ErrorResponse.model_validate(data)
|
|
130
|
+
problem = error_response.error
|
|
131
|
+
|
|
132
|
+
# Raise ValidationError for 400 with field errors
|
|
133
|
+
if problem.status == 400 and problem.errors:
|
|
134
|
+
raise ValidationError(problem, raw_response=data)
|
|
135
|
+
|
|
136
|
+
# Generic ProblemDetailsException
|
|
137
|
+
raise ProblemDetailsException(problem, raw_response=data)
|
|
138
|
+
|
|
139
|
+
except PydanticValidationError as e:
|
|
140
|
+
# Malformed RFC 7807 response
|
|
141
|
+
logger.warning("Malformed RFC 7807 response: %s", e)
|
|
142
|
+
raise MagickMindError(
|
|
143
|
+
f"Malformed error response: {data.get('error', {}).get('detail', 'Unknown error')}",
|
|
144
|
+
status_code=response.status_code,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Fallback: OpenAI middleware format {"code": 401, "message": "..."}
|
|
148
|
+
if "code" in data and "message" in data:
|
|
149
|
+
logger.debug("Received legacy error format from OpenAI middleware")
|
|
150
|
+
# Convert to RFC 7807 structure
|
|
151
|
+
problem = ProblemDetails(
|
|
152
|
+
type="about:blank",
|
|
153
|
+
title=http.client.responses.get(data["code"], "Error"),
|
|
154
|
+
status=data["code"],
|
|
155
|
+
detail=data["message"],
|
|
156
|
+
)
|
|
157
|
+
raise ProblemDetailsException(problem, raw_response=data)
|
|
158
|
+
|
|
159
|
+
# Unknown format
|
|
160
|
+
raise MagickMindError(
|
|
161
|
+
f"Unknown error response format: {data}",
|
|
162
|
+
status_code=response.status_code,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def get(
|
|
166
|
+
self,
|
|
167
|
+
path: str,
|
|
168
|
+
params: Optional[Dict[str, Any]] = None,
|
|
169
|
+
headers: Optional[Dict[str, str]] = None,
|
|
170
|
+
) -> Dict[str, Any]:
|
|
171
|
+
"""
|
|
172
|
+
Make a GET request.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
path: API endpoint path
|
|
176
|
+
params: Query parameters
|
|
177
|
+
headers: Additional headers
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Response data as dictionary
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
|
|
184
|
+
ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
|
|
185
|
+
ValidationError: For 400 Bad Request with field-level errors
|
|
186
|
+
RateLimitError: For 429 Too Many Requests
|
|
187
|
+
MagickMindError: For unexpected errors or malformed responses
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> response = client.http.get("/v1/mindspaces")
|
|
191
|
+
>>> print(response['data'])
|
|
192
|
+
"""
|
|
193
|
+
# Refresh auth if needed
|
|
194
|
+
self.auth.refresh_if_needed()
|
|
195
|
+
|
|
196
|
+
url = self._build_url(path)
|
|
197
|
+
request_headers = self._get_headers(headers)
|
|
198
|
+
|
|
199
|
+
response = self._client.get(url, params=params, headers=request_headers)
|
|
200
|
+
return self._handle_response(response)
|
|
201
|
+
|
|
202
|
+
def post(
|
|
203
|
+
self,
|
|
204
|
+
path: str,
|
|
205
|
+
json: Optional[Dict[str, Any]] = None,
|
|
206
|
+
headers: Optional[Dict[str, str]] = None,
|
|
207
|
+
) -> Dict[str, Any]:
|
|
208
|
+
"""
|
|
209
|
+
Make a POST request.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
path: API endpoint path
|
|
213
|
+
json: Request body as dictionary
|
|
214
|
+
headers: Additional headers
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Response data as dictionary
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
|
|
221
|
+
ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
|
|
222
|
+
ValidationError: For 400 Bad Request with field-level validation errors
|
|
223
|
+
RateLimitError: For 429 Too Many Requests
|
|
224
|
+
MagickMindError: For unexpected errors or malformed responses
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
>>> response = client.http.post(
|
|
228
|
+
... "/v1/magickmind/chat",
|
|
229
|
+
... json={"message": "Hello", "mindspace_id": "mind-123"}
|
|
230
|
+
... )
|
|
231
|
+
"""
|
|
232
|
+
# Refresh auth if needed
|
|
233
|
+
self.auth.refresh_if_needed()
|
|
234
|
+
|
|
235
|
+
url = self._build_url(path)
|
|
236
|
+
request_headers = self._get_headers(headers)
|
|
237
|
+
|
|
238
|
+
response = self._client.post(url, json=json, headers=request_headers)
|
|
239
|
+
return self._handle_response(response)
|
|
240
|
+
|
|
241
|
+
def put(
|
|
242
|
+
self,
|
|
243
|
+
path: str,
|
|
244
|
+
json: Optional[Dict[str, Any]] = None,
|
|
245
|
+
headers: Optional[Dict[str, str]] = None,
|
|
246
|
+
) -> Dict[str, Any]:
|
|
247
|
+
"""
|
|
248
|
+
Make a PUT request.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
path: API endpoint path
|
|
252
|
+
json: Request body as dictionary
|
|
253
|
+
headers: Additional headers
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Response data as dictionary
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
|
|
260
|
+
ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
|
|
261
|
+
ValidationError: For 400 Bad Request with field-level validation errors
|
|
262
|
+
RateLimitError: For 429 Too Many Requests
|
|
263
|
+
MagickMindError: For unexpected errors or malformed responses
|
|
264
|
+
"""
|
|
265
|
+
# Refresh auth if needed
|
|
266
|
+
self.auth.refresh_if_needed()
|
|
267
|
+
|
|
268
|
+
url = self._build_url(path)
|
|
269
|
+
request_headers = self._get_headers(headers)
|
|
270
|
+
|
|
271
|
+
response = self._client.put(url, json=json, headers=request_headers)
|
|
272
|
+
return self._handle_response(response)
|
|
273
|
+
|
|
274
|
+
def delete(
|
|
275
|
+
self, path: str, headers: Optional[Dict[str, str]] = None
|
|
276
|
+
) -> Dict[str, Any]:
|
|
277
|
+
"""
|
|
278
|
+
Make a DELETE request.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
path: API endpoint path
|
|
282
|
+
headers: Additional headers
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Response data as dictionary
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
|
|
289
|
+
ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
|
|
290
|
+
ValidationError: For 400 Bad Request with field-level validation errors
|
|
291
|
+
RateLimitError: For 429 Too Many Requests
|
|
292
|
+
MagickMindError: For unexpected errors or malformed responses
|
|
293
|
+
"""
|
|
294
|
+
# Refresh auth if needed
|
|
295
|
+
self.auth.refresh_if_needed()
|
|
296
|
+
|
|
297
|
+
url = self._build_url(path)
|
|
298
|
+
request_headers = self._get_headers(headers)
|
|
299
|
+
|
|
300
|
+
response = self._client.delete(url, headers=request_headers)
|
|
301
|
+
return self._handle_response(response)
|
|
302
|
+
|
|
303
|
+
def close(self) -> None:
|
|
304
|
+
"""Close the HTTP client."""
|
|
305
|
+
self._client.close()
|
|
306
|
+
|
|
307
|
+
def __enter__(self):
|
|
308
|
+
"""Context manager entry."""
|
|
309
|
+
return self
|
|
310
|
+
|
|
311
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
312
|
+
"""Context manager exit."""
|
|
313
|
+
self.close()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Pydantic models for API requests and responses."""
|
|
2
|
+
|
|
3
|
+
# Models are organized by version
|
|
4
|
+
# Each version has its own submodule with request/response schemas
|
|
5
|
+
|
|
6
|
+
# RFC 7807 error models (version-agnostic)
|
|
7
|
+
from magick_mind.models.errors import (
|
|
8
|
+
ErrorResponse,
|
|
9
|
+
ProblemDetails,
|
|
10
|
+
ValidationErrorField,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ErrorResponse",
|
|
15
|
+
"ProblemDetails",
|
|
16
|
+
"ValidationErrorField",
|
|
17
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Authentication-related Pydantic models."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LoginRequest(BaseModel):
|
|
7
|
+
"""Request model for /v1/auth/login."""
|
|
8
|
+
|
|
9
|
+
email: str
|
|
10
|
+
password: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RefreshRequest(BaseModel):
|
|
14
|
+
"""Request model for /v1/auth/refresh."""
|
|
15
|
+
|
|
16
|
+
refresh_token: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TokenResponse(BaseModel):
|
|
20
|
+
"""Response model from /v1/auth/login."""
|
|
21
|
+
|
|
22
|
+
access_token: str
|
|
23
|
+
expires_in: int
|
|
24
|
+
refresh_expires_in: int
|
|
25
|
+
refresh_token: str
|
|
26
|
+
token_type: str
|
|
27
|
+
id_token: str
|
|
28
|
+
not_before_policy: int = Field(..., alias="not-before-policy")
|
|
29
|
+
session_state: str
|
|
30
|
+
scope: str
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Common Pydantic models used across versions."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseResponse(BaseModel):
|
|
9
|
+
"""Base response structure from Bifrost API."""
|
|
10
|
+
|
|
11
|
+
success: bool
|
|
12
|
+
message: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Cursors(BaseModel):
|
|
16
|
+
"""Pagination cursors for Bifrost paginated responses."""
|
|
17
|
+
|
|
18
|
+
after: Optional[str] = Field(default=None, description="Cursor for next page")
|
|
19
|
+
before: Optional[str] = Field(default=None, description="Cursor for previous page")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PageInfo(BaseModel):
|
|
23
|
+
"""Pagination information for Bifrost paginated responses."""
|
|
24
|
+
|
|
25
|
+
cursors: Cursors = Field(
|
|
26
|
+
default_factory=lambda: Cursors(after=None, before=None),
|
|
27
|
+
description="Pagination cursors",
|
|
28
|
+
)
|
|
29
|
+
has_more: bool = Field(default=False, description="Whether there are more results")
|
|
30
|
+
has_previous: bool = Field(
|
|
31
|
+
default=False, description="Whether there are previous results"
|
|
32
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""RFC 7807 Problem Details error models for Bifrost API responses.
|
|
2
|
+
|
|
3
|
+
This module defines Pydantic models for parsing RFC 7807 compliant error
|
|
4
|
+
responses from the Bifrost API.
|
|
5
|
+
|
|
6
|
+
See: https://datatracker.ietf.org/doc/html/rfc7807
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ValidationErrorField(BaseModel):
|
|
15
|
+
"""RFC 7807 field-level validation error.
|
|
16
|
+
|
|
17
|
+
Represents a single field validation error, typically used in 400 Bad Request
|
|
18
|
+
responses with field-specific validation failures.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
field: The field name that failed validation
|
|
22
|
+
message: Human-readable error message for this field
|
|
23
|
+
code: Optional error code (e.g., "required", "invalid_format")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
field: str
|
|
27
|
+
message: str
|
|
28
|
+
code: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProblemDetails(BaseModel):
|
|
32
|
+
"""RFC 7807 Problem Details structure.
|
|
33
|
+
|
|
34
|
+
Standard structure for HTTP API error responses. This is the Pydantic model
|
|
35
|
+
representing the data structure, NOT the exception class.
|
|
36
|
+
|
|
37
|
+
The exception class that wraps this is ProblemDetailsException (created in task 02).
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
type: URI reference identifying the problem type (default: "about:blank")
|
|
41
|
+
title: Short, human-readable summary of the problem type
|
|
42
|
+
status: HTTP status code (400-599)
|
|
43
|
+
detail: Human-readable explanation specific to this occurrence
|
|
44
|
+
instance: URI reference identifying the specific occurrence (e.g., request path)
|
|
45
|
+
request_id: Trace ID for debugging (Bifrost extension field)
|
|
46
|
+
errors: List of field-level validation errors (Bifrost extension field)
|
|
47
|
+
|
|
48
|
+
Additional fields are allowed per RFC 7807 extension mechanism.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
type: str = "about:blank"
|
|
52
|
+
title: str
|
|
53
|
+
status: int = Field(ge=400, le=599)
|
|
54
|
+
detail: str
|
|
55
|
+
instance: str | None = None
|
|
56
|
+
request_id: str | None = None
|
|
57
|
+
errors: list[ValidationErrorField] = Field(default_factory=list)
|
|
58
|
+
|
|
59
|
+
model_config = ConfigDict(extra="allow")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ErrorResponse(BaseModel):
|
|
63
|
+
"""Bifrost error response wrapper.
|
|
64
|
+
|
|
65
|
+
Wraps the RFC 7807 ProblemDetails in an "error" envelope as returned by Bifrost.
|
|
66
|
+
|
|
67
|
+
Bifrost returns: {"error": {...problem details...}}
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
error: The RFC 7807 problem details
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
error: ProblemDetails
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""V1 API models."""
|
|
2
|
+
|
|
3
|
+
from magick_mind.models.v1.artifact import (
|
|
4
|
+
Artifact,
|
|
5
|
+
ArtifactWebhookPayload,
|
|
6
|
+
DeleteArtifactResponse,
|
|
7
|
+
FinalizeArtifactRequest,
|
|
8
|
+
FinalizeArtifactResponse,
|
|
9
|
+
GetArtifactResponse,
|
|
10
|
+
ListArtifactsResponse,
|
|
11
|
+
PresignArtifactRequest,
|
|
12
|
+
PresignArtifactResponse,
|
|
13
|
+
)
|
|
14
|
+
from magick_mind.models.v1.chat import ChatPayload, ChatSendRequest, ChatSendResponse
|
|
15
|
+
from magick_mind.models.v1.corpus import (
|
|
16
|
+
Corpus,
|
|
17
|
+
CreateCorpusRequest,
|
|
18
|
+
ListCorpusResponse,
|
|
19
|
+
UpdateCorpusRequest,
|
|
20
|
+
)
|
|
21
|
+
from magick_mind.models.v1.end_user import (
|
|
22
|
+
CreateEndUserRequest,
|
|
23
|
+
EndUser,
|
|
24
|
+
QueryEndUserResponse,
|
|
25
|
+
UpdateEndUserRequest,
|
|
26
|
+
)
|
|
27
|
+
from magick_mind.models.v1.history import ChatHistoryMessage, HistoryResponse
|
|
28
|
+
from magick_mind.models.v1.api_keys import (
|
|
29
|
+
ApiKey,
|
|
30
|
+
CreateApiKeyRequest,
|
|
31
|
+
CreateApiKeyResponse,
|
|
32
|
+
DeleteApiKeyResponse,
|
|
33
|
+
KeyResponse,
|
|
34
|
+
ListApiKeysResponse,
|
|
35
|
+
UpdateApiKeyRequest,
|
|
36
|
+
UpdateApiKeyResponse,
|
|
37
|
+
)
|
|
38
|
+
from magick_mind.models.v1.project import (
|
|
39
|
+
CreateProjectRequest,
|
|
40
|
+
GetProjectListResponse,
|
|
41
|
+
Project,
|
|
42
|
+
UpdateProjectRequest,
|
|
43
|
+
)
|
|
44
|
+
from magick_mind.models.v1.model import Model, ModelsListResponse
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"ChatSendRequest",
|
|
49
|
+
"ChatPayload",
|
|
50
|
+
"ChatSendResponse",
|
|
51
|
+
"ChatHistoryMessage",
|
|
52
|
+
"HistoryResponse",
|
|
53
|
+
"Project",
|
|
54
|
+
"CreateProjectRequest",
|
|
55
|
+
"GetProjectListResponse",
|
|
56
|
+
"EndUser",
|
|
57
|
+
"CreateEndUserRequest",
|
|
58
|
+
"QueryEndUserResponse",
|
|
59
|
+
"UpdateEndUserRequest",
|
|
60
|
+
"Artifact",
|
|
61
|
+
"PresignArtifactRequest",
|
|
62
|
+
"PresignArtifactResponse",
|
|
63
|
+
"GetArtifactResponse",
|
|
64
|
+
"ListArtifactsResponse",
|
|
65
|
+
"DeleteArtifactResponse",
|
|
66
|
+
"FinalizeArtifactRequest",
|
|
67
|
+
"FinalizeArtifactResponse",
|
|
68
|
+
"ArtifactWebhookPayload",
|
|
69
|
+
"Corpus",
|
|
70
|
+
"CreateCorpusRequest",
|
|
71
|
+
"ListCorpusResponse",
|
|
72
|
+
"UpdateCorpusRequest",
|
|
73
|
+
"ApiKey",
|
|
74
|
+
"KeyResponse",
|
|
75
|
+
"CreateApiKeyRequest",
|
|
76
|
+
"CreateApiKeyResponse",
|
|
77
|
+
"ListApiKeysResponse",
|
|
78
|
+
"UpdateApiKeyRequest",
|
|
79
|
+
"UpdateApiKeyResponse",
|
|
80
|
+
"DeleteApiKeyResponse",
|
|
81
|
+
"Model",
|
|
82
|
+
"ModelsListResponse",
|
|
83
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Keys models for Magick Mind SDK v1 API.
|
|
3
|
+
|
|
4
|
+
Provides Pydantic models for creating and managing API keys
|
|
5
|
+
for authenticating requests to the Bifrost backend.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ApiKey(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
API key metadata (does not include the actual key value).
|
|
16
|
+
|
|
17
|
+
Represents an API key for making authenticated requests.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(extra="allow")
|
|
21
|
+
|
|
22
|
+
key_id: str = Field(..., description="Unique key identifier")
|
|
23
|
+
key_alias: str = Field(..., description="Human-readable key alias/name")
|
|
24
|
+
user_id: str = Field(..., description="Owner user ID")
|
|
25
|
+
project_id: str = Field(..., description="Associated project ID")
|
|
26
|
+
update_at: str = Field(..., description="Last update timestamp")
|
|
27
|
+
create_at: str = Field(..., description="Creation timestamp")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class KeyResponse(BaseModel):
|
|
31
|
+
"""
|
|
32
|
+
Response containing the actual API key value.
|
|
33
|
+
|
|
34
|
+
Only returned when creating or updating a key.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(extra="allow")
|
|
38
|
+
|
|
39
|
+
key: Optional[str] = Field(None, description="The actual API key (only shown once)")
|
|
40
|
+
key_alias: Optional[str] = Field(None, description="Key alias/name")
|
|
41
|
+
key_id: Optional[str] = Field(None, description="Key identifier")
|
|
42
|
+
expires: Optional[str] = Field(None, description="Expiration timestamp")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CreateApiKeyRequest(BaseModel):
|
|
46
|
+
"""Request for creating a new API key."""
|
|
47
|
+
|
|
48
|
+
user_id: Optional[str] = Field(
|
|
49
|
+
None, description="User ID that owns this key (Relaxed)"
|
|
50
|
+
)
|
|
51
|
+
project_id: Optional[str] = Field(
|
|
52
|
+
None, description="Project ID to associate with (Relaxed)"
|
|
53
|
+
)
|
|
54
|
+
models: Optional[list[str]] = Field(None, description="Allowed models (Relaxed)")
|
|
55
|
+
key_alias: Optional[str] = Field(
|
|
56
|
+
None, description="Human-readable key name (Relaxed)"
|
|
57
|
+
)
|
|
58
|
+
duration: Optional[str] = Field(
|
|
59
|
+
None, description="Key validity duration (e.g., '30d', '1y')"
|
|
60
|
+
)
|
|
61
|
+
team_id: Optional[str] = Field(None, description="Optional team ID")
|
|
62
|
+
max_budget: Optional[float] = Field(None, description="Optional spending limit")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CreateApiKeyResponse(BaseModel):
|
|
66
|
+
"""Response for API key creation."""
|
|
67
|
+
|
|
68
|
+
success: bool = Field(..., description="Request success status")
|
|
69
|
+
message: str = Field(..., description="Response message")
|
|
70
|
+
key: KeyResponse = Field(..., description="The created API key details")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ListApiKeysResponse(BaseModel):
|
|
74
|
+
"""Response for listing API keys."""
|
|
75
|
+
|
|
76
|
+
success: bool = Field(..., description="Request success status")
|
|
77
|
+
message: str = Field(..., description="Response message")
|
|
78
|
+
keys: list[ApiKey] = Field(..., description="List of API keys (metadata only)")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class UpdateApiKeyRequest(BaseModel):
|
|
82
|
+
"""Request for updating an existing API key."""
|
|
83
|
+
|
|
84
|
+
key: Optional[str] = Field(None, description="The API key to update (Relaxed)")
|
|
85
|
+
models: Optional[list[str]] = Field(
|
|
86
|
+
None, description="Updated allowed models (Relaxed)"
|
|
87
|
+
)
|
|
88
|
+
key_alias: Optional[str] = Field(
|
|
89
|
+
None, description="Updated key alias/name (Relaxed)"
|
|
90
|
+
)
|
|
91
|
+
duration: Optional[str] = Field(None, description="Updated validity duration")
|
|
92
|
+
max_budget: Optional[float] = Field(None, description="Updated spending limit")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class UpdateApiKeyResponse(BaseModel):
|
|
96
|
+
"""Response for API key update."""
|
|
97
|
+
|
|
98
|
+
success: Optional[bool] = Field(None, description="Request success status")
|
|
99
|
+
message: Optional[str] = Field(None, description="Response message")
|
|
100
|
+
key: Optional[KeyResponse] = Field(
|
|
101
|
+
None, description="The updated API key details (Optional in Apidog)"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class DeleteApiKeyRequest(BaseModel):
|
|
106
|
+
"""Request for deleting an API key."""
|
|
107
|
+
|
|
108
|
+
key_id: str = Field(..., description="Key ID to delete")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class DeleteApiKeyResponse(BaseModel):
|
|
112
|
+
"""Response for API key deletion."""
|
|
113
|
+
|
|
114
|
+
success: bool = Field(..., description="Request success status")
|
|
115
|
+
message: str = Field(..., description="Deletion confirmation message")
|