simile 0.5.3__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 simile might be problematic. Click here for more details.

simile/__init__.py ADDED
@@ -0,0 +1,61 @@
1
+ from .client import Simile
2
+ from .auth_client import SimileAuth
3
+ from .models import (
4
+ Population,
5
+ Agent,
6
+ DataItem,
7
+ PopulationInfo,
8
+ CreatePopulationPayload,
9
+ CreateAgentPayload,
10
+ CreateDataItemPayload,
11
+ UpdateDataItemPayload,
12
+ DeletionResponse,
13
+ OpenGenerationRequest,
14
+ OpenGenerationResponse,
15
+ ClosedGenerationRequest,
16
+ ClosedGenerationResponse,
17
+ MemoryStream,
18
+ MemoryTurn,
19
+ MemoryTurnType,
20
+ ContextMemoryTurn,
21
+ ImageMemoryTurn,
22
+ OpenQuestionMemoryTurn,
23
+ ClosedQuestionMemoryTurn,
24
+ )
25
+ from .exceptions import (
26
+ SimileAPIError,
27
+ SimileAuthenticationError,
28
+ SimileNotFoundError,
29
+ SimileBadRequestError,
30
+ )
31
+
32
+ __all__ = [
33
+ "Simile",
34
+ "SimileAuth",
35
+ "Population",
36
+ "PopulationInfo",
37
+ "Agent",
38
+ "DataItem",
39
+ "CreatePopulationPayload",
40
+ "CreateAgentPayload",
41
+ "CreateDataItemPayload",
42
+ "UpdateDataItemPayload",
43
+ "DeletionResponse",
44
+ "OpenGenerationRequest",
45
+ "OpenGenerationResponse",
46
+ "ClosedGenerationRequest",
47
+ "ClosedGenerationResponse",
48
+ "MemoryStream",
49
+ "MemoryTurn",
50
+ "MemoryTurnType",
51
+ "ContextMemoryTurn",
52
+ "ImageMemoryTurn",
53
+ "OpenQuestionMemoryTurn",
54
+ "ClosedQuestionMemoryTurn",
55
+ "SimileAPIError",
56
+ "SimileAuthenticationError",
57
+ "SimileNotFoundError",
58
+ "SimileBadRequestError",
59
+ ]
60
+
61
+ __version__ = "0.5.3"
simile/auth_client.py ADDED
@@ -0,0 +1,270 @@
1
+ """
2
+ Authentication client for Simile API using bearer token authentication.
3
+
4
+ This client handles authentication-related operations using Google OAuth tokens
5
+ rather than API keys.
6
+ """
7
+
8
+ import httpx
9
+ from httpx import AsyncClient
10
+ from typing import List, Optional, Union
11
+ import uuid
12
+ from datetime import datetime
13
+
14
+ from .models import BaseModel
15
+ from .exceptions import (
16
+ SimileAPIError,
17
+ SimileAuthenticationError,
18
+ SimileNotFoundError,
19
+ SimileBadRequestError,
20
+ )
21
+
22
+
23
+ # Import the models from auth_api_endpoints
24
+ # In production, these would be in a shared models file
25
+ class UserInfo(BaseModel):
26
+ """User information from Google authentication"""
27
+
28
+ user_id: str
29
+ email: str
30
+ name: str
31
+ picture: Optional[str] = None
32
+ created_at: datetime
33
+ last_login: datetime
34
+
35
+
36
+ class APIKey(BaseModel):
37
+ """API Key model"""
38
+
39
+ key_id: uuid.UUID
40
+ name: str
41
+ key_prefix: str
42
+ created_at: datetime
43
+ last_used: Optional[datetime] = None
44
+ expires_at: Optional[datetime] = None
45
+ is_active: bool = True
46
+
47
+
48
+ class APIKeyCreateResponse(BaseModel):
49
+ """Response when creating a new API key"""
50
+
51
+ key_id: uuid.UUID
52
+ name: str
53
+ key: str
54
+ key_prefix: str
55
+ created_at: datetime
56
+ expires_at: Optional[datetime] = None
57
+
58
+
59
+ class PopulationAccess(BaseModel):
60
+ """Population access information"""
61
+
62
+ population_id: uuid.UUID
63
+ name: str
64
+ description: Optional[str] = None
65
+ role: str
66
+ created_at: datetime
67
+ member_count: int
68
+ agent_count: int
69
+
70
+
71
+ class PopulationShareResponse(BaseModel):
72
+ """Response when creating a share code"""
73
+
74
+ share_code: str
75
+ population_id: uuid.UUID
76
+ role: str
77
+ expires_at: datetime
78
+ max_uses: Optional[int] = None
79
+ created_at: datetime
80
+
81
+
82
+ class PopulationJoinResponse(BaseModel):
83
+ """Response when joining a population"""
84
+
85
+ population_id: uuid.UUID
86
+ name: str
87
+ description: Optional[str] = None
88
+ role: str
89
+ message: str = "Successfully joined population"
90
+
91
+
92
+ DEFAULT_BASE_URL = "https://api.simile.ai/api/v1"
93
+ TIMEOUT_CONFIG = httpx.Timeout(5.0, read=30.0, write=30.0, pool=30.0)
94
+
95
+
96
+ class SimileAuth:
97
+ """
98
+ Authentication client for Simile API.
99
+
100
+ This client uses bearer token authentication from Google OAuth
101
+ instead of API keys.
102
+ """
103
+
104
+ def __init__(self, bearer_token: str, base_url: str = DEFAULT_BASE_URL):
105
+ """
106
+ Initialize the authentication client.
107
+
108
+ Args:
109
+ bearer_token: The Google OAuth bearer token
110
+ base_url: The base URL of the API
111
+ """
112
+ if not bearer_token:
113
+ raise ValueError("Bearer token is required.")
114
+ self.bearer_token = bearer_token
115
+ self.base_url = base_url.rstrip("/")
116
+ self._client = AsyncClient(
117
+ headers={"Authorization": f"Bearer {self.bearer_token}"},
118
+ timeout=TIMEOUT_CONFIG,
119
+ )
120
+
121
+ async def _request(
122
+ self, method: str, endpoint: str, **kwargs
123
+ ) -> Union[httpx.Response, BaseModel]:
124
+ """Make an HTTP request to the API."""
125
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
126
+ response_model_cls = kwargs.pop("response_model", None)
127
+
128
+ try:
129
+ response = await self._client.request(method, url, **kwargs)
130
+ response.raise_for_status()
131
+
132
+ if response_model_cls:
133
+ return response_model_cls(**response.json())
134
+ else:
135
+ return response
136
+ except httpx.HTTPStatusError as e:
137
+ status_code = e.response.status_code
138
+ try:
139
+ error_data = e.response.json()
140
+ detail = error_data.get("detail", e.response.text)
141
+ except Exception:
142
+ detail = e.response.text
143
+
144
+ if status_code == 401:
145
+ raise SimileAuthenticationError(detail=detail)
146
+ elif status_code == 404:
147
+ raise SimileNotFoundError(detail=detail)
148
+ elif status_code == 400:
149
+ raise SimileBadRequestError(detail=detail)
150
+ else:
151
+ raise SimileAPIError(
152
+ f"API request failed: {e}", status_code=status_code, detail=detail
153
+ )
154
+ except httpx.RequestError as e:
155
+ raise SimileAPIError(f"Request error: {type(e).__name__}: {e}")
156
+
157
+ async def get_current_user(self) -> UserInfo:
158
+ """Get current user information."""
159
+ response = await self._request("GET", "auth/me", response_model=UserInfo)
160
+ return response
161
+
162
+ async def list_api_keys(self) -> List[APIKey]:
163
+ """List all API keys for the current user."""
164
+ response = await self._request("GET", "auth/api-keys")
165
+ return [APIKey(**key_data) for key_data in response.json()]
166
+
167
+ async def create_api_key(
168
+ self, name: str, expires_in_days: Optional[int] = None
169
+ ) -> APIKeyCreateResponse:
170
+ """
171
+ Create a new API key.
172
+
173
+ Args:
174
+ name: Name for the API key
175
+ expires_in_days: Optional expiration time in days
176
+
177
+ Returns:
178
+ APIKeyCreateResponse with the full key (only shown once)
179
+ """
180
+ payload = {"name": name}
181
+ if expires_in_days is not None:
182
+ payload["expires_in_days"] = expires_in_days
183
+
184
+ response = await self._request(
185
+ "POST", "auth/api-keys", json=payload, response_model=APIKeyCreateResponse
186
+ )
187
+ return response
188
+
189
+ async def delete_api_key(self, key_id: Union[str, uuid.UUID]) -> dict:
190
+ """
191
+ Delete an API key.
192
+
193
+ Args:
194
+ key_id: The ID of the API key to delete
195
+
196
+ Returns:
197
+ Success message
198
+ """
199
+ response = await self._request("DELETE", f"auth/api-keys/{str(key_id)}")
200
+ return response.json()
201
+
202
+ async def list_accessible_populations(self) -> List[PopulationAccess]:
203
+ """List all populations the current user has access to."""
204
+ response = await self._request("GET", "auth/populations")
205
+ return [PopulationAccess(**pop_data) for pop_data in response.json()]
206
+
207
+ async def create_population_share_code(
208
+ self,
209
+ population_id: Union[str, uuid.UUID],
210
+ role: str = "viewer",
211
+ expires_in_hours: Optional[int] = 24,
212
+ max_uses: Optional[int] = None,
213
+ ) -> PopulationShareResponse:
214
+ """
215
+ Create a share code for a population.
216
+
217
+ Args:
218
+ population_id: The ID of the population to share
219
+ role: The role to grant (viewer or editor)
220
+ expires_in_hours: Hours until the code expires (default 24)
221
+ max_uses: Maximum number of times the code can be used
222
+
223
+ Returns:
224
+ PopulationShareResponse with the share code
225
+ """
226
+ payload = {
227
+ "population_id": str(population_id),
228
+ "role": role,
229
+ "expires_in_hours": expires_in_hours,
230
+ }
231
+ if max_uses is not None:
232
+ payload["max_uses"] = max_uses
233
+
234
+ response = await self._request(
235
+ "POST",
236
+ "auth/populations/share",
237
+ json=payload,
238
+ response_model=PopulationShareResponse,
239
+ )
240
+ return response
241
+
242
+ async def join_population_with_share_code(
243
+ self, share_code: str
244
+ ) -> PopulationJoinResponse:
245
+ """
246
+ Join a population using a share code.
247
+
248
+ Args:
249
+ share_code: The share code to use
250
+
251
+ Returns:
252
+ PopulationJoinResponse with population details
253
+ """
254
+ response = await self._request(
255
+ "POST",
256
+ "auth/populations/join",
257
+ json={"share_code": share_code},
258
+ response_model=PopulationJoinResponse,
259
+ )
260
+ return response
261
+
262
+ async def aclose(self):
263
+ """Close the HTTP client."""
264
+ await self._client.aclose()
265
+
266
+ async def __aenter__(self):
267
+ return self
268
+
269
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
270
+ await self.aclose()