amigo_sdk 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.
Potentially problematic release.
This version of amigo_sdk might be problematic. Click here for more details.
- amigo_sdk/__init__.py +1 -0
- amigo_sdk/auth.py +30 -0
- amigo_sdk/config.py +48 -0
- amigo_sdk/errors.py +163 -0
- amigo_sdk/generated/model.py +15683 -0
- amigo_sdk/http_client.py +228 -0
- amigo_sdk/resources/conversation.py +208 -0
- amigo_sdk/resources/organization.py +22 -0
- amigo_sdk/resources/service.py +30 -0
- amigo_sdk/resources/user.py +57 -0
- amigo_sdk/sdk_client.py +105 -0
- amigo_sdk-0.1.1.dist-info/METADATA +192 -0
- amigo_sdk-0.1.1.dist-info/RECORD +16 -0
- amigo_sdk-0.1.1.dist-info/WHEEL +4 -0
- amigo_sdk-0.1.1.dist-info/entry_points.txt +3 -0
- amigo_sdk-0.1.1.dist-info/licenses/LICENSE +21 -0
amigo_sdk/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
amigo_sdk/auth.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from amigo_sdk.config import AmigoConfig
|
|
4
|
+
from amigo_sdk.errors import AuthenticationError
|
|
5
|
+
from amigo_sdk.generated.model import UserSignInWithApiKeyResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def sign_in_with_api_key(
|
|
9
|
+
cfg: AmigoConfig,
|
|
10
|
+
) -> UserSignInWithApiKeyResponse:
|
|
11
|
+
"""
|
|
12
|
+
Sign in with API key.
|
|
13
|
+
"""
|
|
14
|
+
async with httpx.AsyncClient() as client:
|
|
15
|
+
url = f"{cfg.base_url}/v1/{cfg.organization_id}/user/signin_with_api_key"
|
|
16
|
+
headers = {
|
|
17
|
+
"x-api-key": cfg.api_key,
|
|
18
|
+
"x-api-key-id": cfg.api_key_id,
|
|
19
|
+
"x-user-id": cfg.user_id,
|
|
20
|
+
}
|
|
21
|
+
try:
|
|
22
|
+
response = await client.post(url, headers=headers)
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
except httpx.HTTPStatusError as e:
|
|
25
|
+
raise AuthenticationError(f"Sign in with API key failed: {e}") from e
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
return UserSignInWithApiKeyResponse.model_validate_json(response.text)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise AuthenticationError(f"Invalid response format: {e}") from e
|
amigo_sdk/config.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
from pydantic_settings import BaseSettings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AmigoConfig(BaseSettings):
|
|
6
|
+
"""
|
|
7
|
+
Configuration for the Amigo API client.
|
|
8
|
+
|
|
9
|
+
Can be configured via three methods (in order of precedence):
|
|
10
|
+
1. Constructor parameters (highest precedence)
|
|
11
|
+
2. Environment variables with AMIGO_ prefix
|
|
12
|
+
3. .env file in the current working directory (lowest precedence)
|
|
13
|
+
|
|
14
|
+
Environment variables:
|
|
15
|
+
- AMIGO_API_KEY
|
|
16
|
+
- AMIGO_API_KEY_ID
|
|
17
|
+
- AMIGO_USER_ID
|
|
18
|
+
- AMIGO_BASE_URL
|
|
19
|
+
- AMIGO_ORGANIZATION_ID
|
|
20
|
+
|
|
21
|
+
Example .env file:
|
|
22
|
+
```
|
|
23
|
+
AMIGO_API_KEY=your_api_key_here
|
|
24
|
+
AMIGO_API_KEY_ID=your_api_key_id_here
|
|
25
|
+
AMIGO_USER_ID=your_user_id_here
|
|
26
|
+
AMIGO_ORGANIZATION_ID=your_org_id_here
|
|
27
|
+
AMIGO_BASE_URL=https://api.amigo.ai
|
|
28
|
+
```
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
api_key: str = Field(..., description="API key for authentication")
|
|
32
|
+
api_key_id: str = Field(..., description="API key ID for authentication")
|
|
33
|
+
user_id: str = Field(..., description="User ID for API requests")
|
|
34
|
+
organization_id: str = Field(..., description="Organization ID for API requests")
|
|
35
|
+
base_url: str = Field(
|
|
36
|
+
default="https://api.amigo.ai",
|
|
37
|
+
description="Base URL for the Amigo API",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
model_config = {
|
|
41
|
+
"env_prefix": "AMIGO_",
|
|
42
|
+
"env_file": ".env",
|
|
43
|
+
"env_file_encoding": "utf-8",
|
|
44
|
+
"case_sensitive": False,
|
|
45
|
+
"validate_assignment": True,
|
|
46
|
+
"frozen": True,
|
|
47
|
+
"extra": "ignore", # Ignore extra fields in .env file
|
|
48
|
+
}
|
amigo_sdk/errors.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AmigoError(Exception):
|
|
5
|
+
"""
|
|
6
|
+
Base class for Amigo API errors.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str,
|
|
12
|
+
*,
|
|
13
|
+
status_code: Optional[int] = None,
|
|
14
|
+
error_code: Optional[str] = None,
|
|
15
|
+
response_body: Optional[Any] = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
self.status_code = status_code
|
|
19
|
+
self.error_code = error_code
|
|
20
|
+
self.response_body = response_body
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
parts = [super().__str__()]
|
|
24
|
+
if self.status_code:
|
|
25
|
+
parts.append(f"(HTTP {self.status_code})")
|
|
26
|
+
if self.error_code:
|
|
27
|
+
parts.append(f"[{self.error_code}]")
|
|
28
|
+
return " ".join(parts)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---- 4xx client errors ------------------------------------------------------
|
|
32
|
+
class BadRequestError(AmigoError): # 400
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuthenticationError(AmigoError): # 401
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PermissionError(AmigoError): # 403
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NotFoundError(AmigoError): # 404
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConflictError(AmigoError): # 409
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RateLimitError(AmigoError): # 429
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---- Validation / semantic errors ------------------------------------------
|
|
57
|
+
class ValidationError(BadRequestError): # 422 or 400 with `errors` list
|
|
58
|
+
def __init__(self, *args, field_errors: Optional[dict[str, str]] = None, **kwargs):
|
|
59
|
+
super().__init__(*args, **kwargs)
|
|
60
|
+
self.field_errors = field_errors or {}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---- 5xx server errors ------------------------------------------------------
|
|
64
|
+
class ServerError(AmigoError): # 500
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ServiceUnavailableError(ServerError): # 503 / maintenance
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---- Internal SDK issues ----------------------------------------------------
|
|
73
|
+
class SDKInternalError(AmigoError):
|
|
74
|
+
"""JSON decoding failure, Pydantic model mismatch, etc."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---- Status code mapping ----------------------------------------------------
|
|
78
|
+
def get_error_class_for_status_code(status_code: int) -> type[AmigoError]:
|
|
79
|
+
"""Map HTTP status codes to appropriate AmigoError classes."""
|
|
80
|
+
error_map = {
|
|
81
|
+
400: BadRequestError,
|
|
82
|
+
401: AuthenticationError,
|
|
83
|
+
403: PermissionError,
|
|
84
|
+
404: NotFoundError,
|
|
85
|
+
409: ConflictError,
|
|
86
|
+
422: ValidationError,
|
|
87
|
+
429: RateLimitError,
|
|
88
|
+
500: ServerError,
|
|
89
|
+
503: ServiceUnavailableError,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Default to appropriate base class for status code ranges
|
|
93
|
+
if status_code in error_map:
|
|
94
|
+
return error_map[status_code]
|
|
95
|
+
elif 400 <= status_code < 500:
|
|
96
|
+
return BadRequestError
|
|
97
|
+
elif 500 <= status_code < 600:
|
|
98
|
+
return ServerError
|
|
99
|
+
else:
|
|
100
|
+
return AmigoError
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def raise_for_status(response, message: str = None) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Raise an appropriate AmigoError for non-2xx status codes.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
response: httpx.Response object
|
|
109
|
+
message: Optional custom error message
|
|
110
|
+
"""
|
|
111
|
+
if response.is_success:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
status_code = response.status_code
|
|
115
|
+
error_class = get_error_class_for_status_code(status_code)
|
|
116
|
+
|
|
117
|
+
# Try to extract error details from response
|
|
118
|
+
error_code = None
|
|
119
|
+
response_body = None
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
response_body = response.json()
|
|
123
|
+
# Try to extract error code if it exists in response
|
|
124
|
+
if isinstance(response_body, dict):
|
|
125
|
+
error_code = response_body.get("error_code") or response_body.get("code")
|
|
126
|
+
except Exception:
|
|
127
|
+
# If JSON parsing fails, use text content
|
|
128
|
+
try:
|
|
129
|
+
response_body = response.text
|
|
130
|
+
except Exception:
|
|
131
|
+
response_body = None
|
|
132
|
+
|
|
133
|
+
# Use provided message or generate default
|
|
134
|
+
if not message:
|
|
135
|
+
message = f"HTTP {status_code} error"
|
|
136
|
+
if isinstance(response_body, dict):
|
|
137
|
+
# Prefer common API error fields, including FastAPI's "detail"
|
|
138
|
+
for key in ("message", "error", "detail"):
|
|
139
|
+
api_message = response_body.get(key)
|
|
140
|
+
if api_message:
|
|
141
|
+
message = str(api_message)
|
|
142
|
+
break
|
|
143
|
+
elif isinstance(response_body, str) and response_body.strip():
|
|
144
|
+
# If the server returned a plain-text or JSON string body, surface it
|
|
145
|
+
message = response_body.strip()
|
|
146
|
+
|
|
147
|
+
# Handle ValidationError special case for field errors
|
|
148
|
+
if error_class == ValidationError and isinstance(response_body, dict):
|
|
149
|
+
field_errors = response_body.get("errors") or response_body.get("field_errors")
|
|
150
|
+
raise error_class(
|
|
151
|
+
message,
|
|
152
|
+
status_code=status_code,
|
|
153
|
+
error_code=error_code,
|
|
154
|
+
response_body=response_body,
|
|
155
|
+
field_errors=field_errors,
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
raise error_class(
|
|
159
|
+
message,
|
|
160
|
+
status_code=status_code,
|
|
161
|
+
error_code=error_code,
|
|
162
|
+
response_body=response_body,
|
|
163
|
+
)
|