recallrai 0.1.1__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of recallrai might be problematic. Click here for more details.
- recallrai/__init__.py +2 -5
- recallrai/client.py +67 -55
- recallrai/exceptions/__init__.py +21 -9
- recallrai/exceptions/auth.py +2 -5
- recallrai/exceptions/base.py +5 -16
- recallrai/exceptions/merge_conflicts.py +69 -0
- recallrai/exceptions/network.py +6 -23
- recallrai/exceptions/server.py +17 -40
- recallrai/exceptions/sessions.py +6 -28
- recallrai/exceptions/users.py +16 -31
- recallrai/exceptions/validation.py +2 -9
- recallrai/merge_conflict.py +189 -0
- recallrai/models/__init__.py +41 -8
- recallrai/models/merge_conflict.py +151 -0
- recallrai/models/session.py +67 -35
- recallrai/models/user.py +138 -23
- recallrai/session.py +197 -150
- recallrai/user.py +353 -78
- recallrai/utils/__init__.py +3 -2
- recallrai/utils/http_client.py +38 -23
- recallrai-0.3.0.dist-info/METADATA +902 -0
- recallrai-0.3.0.dist-info/RECORD +23 -0
- {recallrai-0.1.1.dist-info → recallrai-0.3.0.dist-info}/WHEEL +1 -1
- recallrai-0.1.1.dist-info/METADATA +0 -440
- recallrai-0.1.1.dist-info/RECORD +0 -20
recallrai/user.py
CHANGED
|
@@ -2,14 +2,27 @@
|
|
|
2
2
|
User management functionality for the RecallrAI SDK.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, List, Dict, Optional
|
|
6
7
|
from .utils import HTTPClient
|
|
7
|
-
from .models import
|
|
8
|
+
from .models import (
|
|
9
|
+
UserModel,
|
|
10
|
+
SessionModel,
|
|
11
|
+
SessionList,
|
|
12
|
+
UserMemoriesList,
|
|
13
|
+
UserMessagesList,
|
|
14
|
+
MergeConflictList,
|
|
15
|
+
MergeConflictStatus,
|
|
16
|
+
MergeConflictModel,
|
|
17
|
+
)
|
|
8
18
|
from .session import Session
|
|
19
|
+
from .merge_conflict import MergeConflict
|
|
9
20
|
from .exceptions import (
|
|
10
21
|
UserNotFoundError,
|
|
11
22
|
UserAlreadyExistsError,
|
|
23
|
+
InvalidCategoriesError,
|
|
12
24
|
SessionNotFoundError,
|
|
25
|
+
MergeConflictNotFoundError,
|
|
13
26
|
RecallrAIError
|
|
14
27
|
)
|
|
15
28
|
from logging import getLogger
|
|
@@ -33,8 +46,8 @@ class User:
|
|
|
33
46
|
Initialize a user.
|
|
34
47
|
|
|
35
48
|
Args:
|
|
36
|
-
http_client: HTTP client for API communication
|
|
37
|
-
user_data: User data model with user information
|
|
49
|
+
http_client: HTTP client for API communication.
|
|
50
|
+
user_data: User data model with user information.
|
|
38
51
|
"""
|
|
39
52
|
self._http = http_client
|
|
40
53
|
self._user_data = user_data
|
|
@@ -43,41 +56,40 @@ class User:
|
|
|
43
56
|
self.created_at = user_data.created_at
|
|
44
57
|
self.last_active_at = user_data.last_active_at
|
|
45
58
|
|
|
46
|
-
def update(self, new_metadata: Optional[Dict[str, Any]] = None, new_user_id: Optional[str] = None) ->
|
|
59
|
+
def update(self, new_metadata: Optional[Dict[str, Any]] = None, new_user_id: Optional[str] = None) -> None:
|
|
47
60
|
"""
|
|
48
61
|
Update this user's metadata or ID.
|
|
49
62
|
|
|
50
63
|
Args:
|
|
51
|
-
new_metadata: New metadata to associate with the user
|
|
52
|
-
new_user_id: New ID for the user
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
The updated user object
|
|
64
|
+
new_metadata: New metadata to associate with the user.
|
|
65
|
+
new_user_id: New ID for the user.
|
|
56
66
|
|
|
57
67
|
Raises:
|
|
58
|
-
UserNotFoundError: If the user is not found
|
|
59
|
-
UserAlreadyExistsError: If a user with the new_user_id already exists
|
|
60
|
-
AuthenticationError: If the API key or project ID is invalid
|
|
61
|
-
InternalServerError: If the server encounters an error
|
|
62
|
-
NetworkError: If there are network issues
|
|
63
|
-
TimeoutError: If the request times out
|
|
64
|
-
RecallrAIError: For other API-related errors
|
|
68
|
+
UserNotFoundError: If the user is not found.
|
|
69
|
+
UserAlreadyExistsError: If a user with the new_user_id already exists.
|
|
70
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
71
|
+
InternalServerError: If the server encounters an error.
|
|
72
|
+
NetworkError: If there are network issues.
|
|
73
|
+
TimeoutError: If the request times out.
|
|
74
|
+
RecallrAIError: For other API-related errors.
|
|
65
75
|
"""
|
|
66
76
|
data = {}
|
|
67
77
|
if new_metadata is not None:
|
|
68
|
-
data["
|
|
78
|
+
data["new_metadata"] = new_metadata
|
|
69
79
|
if new_user_id is not None:
|
|
70
80
|
data["new_user_id"] = new_user_id
|
|
71
81
|
|
|
72
82
|
response = self._http.put(f"/api/v1/users/{self.user_id}", data=data)
|
|
73
83
|
|
|
74
84
|
if response.status_code == 404:
|
|
75
|
-
|
|
85
|
+
detail = response.json().get("detail", f"User with ID {self.user_id} not found")
|
|
86
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
76
87
|
elif response.status_code == 409:
|
|
77
|
-
|
|
88
|
+
detail = response.json().get("detail", f"User with ID {new_user_id} already exists")
|
|
89
|
+
raise UserAlreadyExistsError(message=detail, http_status=response.status_code)
|
|
78
90
|
elif response.status_code != 200:
|
|
79
91
|
raise RecallrAIError(
|
|
80
|
-
message=
|
|
92
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
81
93
|
http_status=response.status_code
|
|
82
94
|
)
|
|
83
95
|
|
|
@@ -88,124 +100,387 @@ class User:
|
|
|
88
100
|
self.user_id = updated_data.user_id
|
|
89
101
|
self.metadata = updated_data.metadata
|
|
90
102
|
self.last_active_at = updated_data.last_active_at
|
|
103
|
+
|
|
104
|
+
def refresh(self) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Refresh this user's data from the server.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
UserNotFoundError: If the user is not found.
|
|
110
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
111
|
+
InternalServerError: If the server encounters an error.
|
|
112
|
+
NetworkError: If there are network issues.
|
|
113
|
+
TimeoutError: If the request times out.
|
|
114
|
+
RecallrAIError: For other API-related errors.
|
|
115
|
+
"""
|
|
116
|
+
response = self._http.get(f"/api/v1/users/{self.user_id}")
|
|
117
|
+
|
|
118
|
+
if response.status_code == 404:
|
|
119
|
+
detail = response.json().get("detail", f"User with ID {self.user_id} not found")
|
|
120
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
121
|
+
elif response.status_code != 200:
|
|
122
|
+
raise RecallrAIError(
|
|
123
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
124
|
+
http_status=response.status_code
|
|
125
|
+
)
|
|
91
126
|
|
|
92
|
-
|
|
127
|
+
refreshed_data = UserModel.from_api_response(response.json())
|
|
128
|
+
|
|
129
|
+
# Update internal state
|
|
130
|
+
self._user_data = refreshed_data
|
|
131
|
+
self.user_id = refreshed_data.user_id
|
|
132
|
+
self.metadata = refreshed_data.metadata
|
|
133
|
+
self.created_at = refreshed_data.created_at
|
|
134
|
+
self.last_active_at = refreshed_data.last_active_at
|
|
93
135
|
|
|
94
136
|
def delete(self) -> None:
|
|
95
137
|
"""
|
|
96
138
|
Delete this user.
|
|
97
139
|
|
|
98
140
|
Raises:
|
|
99
|
-
UserNotFoundError: If the user is not found
|
|
100
|
-
AuthenticationError: If the API key or project ID is invalid
|
|
101
|
-
InternalServerError: If the server encounters an error
|
|
102
|
-
NetworkError: If there are network issues
|
|
103
|
-
TimeoutError: If the request times out
|
|
104
|
-
RecallrAIError: For other API-related errors
|
|
141
|
+
UserNotFoundError: If the user is not found.
|
|
142
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
143
|
+
InternalServerError: If the server encounters an error.
|
|
144
|
+
NetworkError: If there are network issues.
|
|
145
|
+
TimeoutError: If the request times out.
|
|
146
|
+
RecallrAIError: For other API-related errors.
|
|
105
147
|
"""
|
|
106
148
|
response = self._http.delete(f"/api/v1/users/{self.user_id}")
|
|
107
149
|
|
|
108
150
|
if response.status_code == 404:
|
|
109
|
-
|
|
151
|
+
detail = response.json().get("detail", f"User with ID {self.user_id} not found")
|
|
152
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
110
153
|
elif response.status_code != 204:
|
|
111
154
|
raise RecallrAIError(
|
|
112
|
-
message=
|
|
155
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
113
156
|
http_status=response.status_code
|
|
114
157
|
)
|
|
115
158
|
|
|
116
|
-
def create_session(
|
|
159
|
+
def create_session(
|
|
160
|
+
self,
|
|
161
|
+
auto_process_after_seconds: int = 600,
|
|
162
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
163
|
+
) -> Session:
|
|
117
164
|
"""
|
|
118
165
|
Create a new session for this user.
|
|
119
166
|
|
|
120
167
|
Args:
|
|
121
|
-
|
|
168
|
+
auto_process_after_seconds: Seconds of inactivity allowed before automaticly processing the session (min 600).
|
|
169
|
+
metadata: Optional metadata for the session.
|
|
122
170
|
|
|
123
171
|
Returns:
|
|
124
|
-
A Session object to interact with the created session
|
|
172
|
+
A Session object to interact with the created session.
|
|
125
173
|
|
|
126
174
|
Raises:
|
|
127
|
-
UserNotFoundError: If the user is not found
|
|
128
|
-
AuthenticationError: If the API key or project ID is invalid
|
|
129
|
-
InternalServerError: If the server encounters an error
|
|
130
|
-
NetworkError: If there are network issues
|
|
131
|
-
TimeoutError: If the request times out
|
|
132
|
-
RecallrAIError: For other API-related errors
|
|
175
|
+
UserNotFoundError: If the user is not found.
|
|
176
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
177
|
+
InternalServerError: If the server encounters an error.
|
|
178
|
+
NetworkError: If there are network issues.
|
|
179
|
+
TimeoutError: If the request times out.
|
|
180
|
+
RecallrAIError: For other API-related errors.
|
|
133
181
|
"""
|
|
182
|
+
payload: Dict[str, Any] = {
|
|
183
|
+
"auto_process_after_seconds": auto_process_after_seconds,
|
|
184
|
+
"metadata": metadata or {},
|
|
185
|
+
}
|
|
134
186
|
response = self._http.post(
|
|
135
187
|
f"/api/v1/users/{self.user_id}/sessions",
|
|
136
|
-
data=
|
|
188
|
+
data=payload,
|
|
137
189
|
)
|
|
138
190
|
|
|
139
191
|
if response.status_code == 404:
|
|
140
|
-
|
|
192
|
+
detail = response.json().get("detail", f"User {self.user_id} not found")
|
|
193
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
141
194
|
elif response.status_code != 201:
|
|
142
195
|
raise RecallrAIError(
|
|
143
|
-
message=
|
|
196
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
144
197
|
http_status=response.status_code
|
|
145
198
|
)
|
|
146
199
|
|
|
147
|
-
|
|
148
|
-
return Session(self._http, self.user_id,
|
|
200
|
+
session_data = SessionModel.from_api_response(response.json())
|
|
201
|
+
return Session(self._http, self.user_id, session_data)
|
|
149
202
|
|
|
150
203
|
def get_session(self, session_id: str) -> Session:
|
|
151
204
|
"""
|
|
152
205
|
Get an existing session for this user.
|
|
153
206
|
|
|
154
207
|
Args:
|
|
155
|
-
session_id: ID of the session to retrieve
|
|
208
|
+
session_id: ID of the session to retrieve.
|
|
156
209
|
|
|
157
210
|
Returns:
|
|
158
|
-
A Session object to interact with the session
|
|
211
|
+
A Session object to interact with the session.
|
|
159
212
|
|
|
160
213
|
Raises:
|
|
161
|
-
UserNotFoundError: If the user is not found
|
|
162
|
-
SessionNotFoundError: If the session is not found
|
|
163
|
-
AuthenticationError: If the API key or project ID is invalid
|
|
164
|
-
InternalServerError: If the server encounters an error
|
|
165
|
-
NetworkError: If there are network issues
|
|
166
|
-
TimeoutError: If the request times out
|
|
167
|
-
RecallrAIError: For other API-related errors
|
|
168
|
-
"""
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
214
|
+
UserNotFoundError: If the user is not found.
|
|
215
|
+
SessionNotFoundError: If the session is not found.
|
|
216
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
217
|
+
InternalServerError: If the server encounters an error.
|
|
218
|
+
NetworkError: If there are network issues.
|
|
219
|
+
TimeoutError: If the request times out.
|
|
220
|
+
RecallrAIError: For other API-related errors.
|
|
221
|
+
"""
|
|
222
|
+
# First, verify the session exists by fetching its details
|
|
223
|
+
response = self._http.get(f"/api/v1/users/{self.user_id}/sessions/{session_id}")
|
|
224
|
+
|
|
225
|
+
if response.status_code == 404:
|
|
226
|
+
# Check if it's a user not found or session not found error
|
|
227
|
+
detail = response.json().get('detail', '')
|
|
228
|
+
if f"User {self.user_id} not found" in detail:
|
|
229
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
230
|
+
else:
|
|
231
|
+
raise SessionNotFoundError(message=detail, http_status=response.status_code)
|
|
232
|
+
elif response.status_code != 200:
|
|
233
|
+
raise RecallrAIError(
|
|
234
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
235
|
+
http_status=response.status_code
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
session_data = SessionModel.from_api_response(response.json())
|
|
239
|
+
return Session(self._http, self.user_id, session_data)
|
|
240
|
+
|
|
241
|
+
def list_sessions(
|
|
242
|
+
self,
|
|
243
|
+
offset: int = 0,
|
|
244
|
+
limit: int = 10,
|
|
245
|
+
metadata_filter: Optional[Dict[str, Any]] = None,
|
|
246
|
+
user_metadata_filter: Optional[Dict[str, Any]] = None,
|
|
247
|
+
) -> SessionList:
|
|
180
248
|
"""
|
|
181
249
|
List sessions for this user with pagination.
|
|
182
250
|
|
|
183
251
|
Args:
|
|
184
|
-
offset: Number of records to skip
|
|
185
|
-
limit: Maximum number of records to return
|
|
252
|
+
offset: Number of records to skip.
|
|
253
|
+
limit: Maximum number of records to return.
|
|
254
|
+
metadata_filter: Optional metadata filter for sessions.
|
|
255
|
+
user_metadata_filter: Optional metadata filter for the user.
|
|
186
256
|
|
|
187
257
|
Returns:
|
|
188
|
-
List of sessions with pagination info
|
|
258
|
+
List of sessions with pagination info.
|
|
189
259
|
|
|
190
260
|
Raises:
|
|
191
|
-
UserNotFoundError: If the user is not found
|
|
192
|
-
AuthenticationError: If the API key or project ID is invalid
|
|
193
|
-
InternalServerError: If the server encounters an error
|
|
194
|
-
NetworkError: If there are network issues
|
|
195
|
-
TimeoutError: If the request times out
|
|
196
|
-
RecallrAIError: For other API-related errors
|
|
261
|
+
UserNotFoundError: If the user is not found.
|
|
262
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
263
|
+
InternalServerError: If the server encounters an error.
|
|
264
|
+
NetworkError: If there are network issues.
|
|
265
|
+
TimeoutError: If the request times out.
|
|
266
|
+
RecallrAIError: For other API-related errors.
|
|
197
267
|
"""
|
|
268
|
+
params: Dict[str, Any] = {"offset": offset, "limit": limit}
|
|
269
|
+
if metadata_filter is not None:
|
|
270
|
+
params["metadata_filter"] = json.dumps(metadata_filter)
|
|
271
|
+
if user_metadata_filter is not None:
|
|
272
|
+
params["user_metadata_filter"] = json.dumps(user_metadata_filter)
|
|
273
|
+
|
|
198
274
|
response = self._http.get(
|
|
199
275
|
f"/api/v1/users/{self.user_id}/sessions",
|
|
200
|
-
params=
|
|
276
|
+
params=params,
|
|
201
277
|
)
|
|
202
278
|
|
|
203
279
|
if response.status_code == 404:
|
|
204
|
-
|
|
280
|
+
detail = response.json().get("detail", f"User {self.user_id} not found")
|
|
281
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
205
282
|
elif response.status_code != 200:
|
|
206
283
|
raise RecallrAIError(
|
|
207
|
-
message=
|
|
284
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
208
285
|
http_status=response.status_code
|
|
209
286
|
)
|
|
210
287
|
|
|
211
|
-
return SessionList.from_api_response(response.json())
|
|
288
|
+
return SessionList.from_api_response(response.json(), self.user_id, self._http)
|
|
289
|
+
|
|
290
|
+
def list_memories(
|
|
291
|
+
self,
|
|
292
|
+
offset: int = 0,
|
|
293
|
+
limit: int = 20,
|
|
294
|
+
categories: Optional[List[str]] = None,
|
|
295
|
+
include_previous_versions: bool = True,
|
|
296
|
+
include_connected_memories: bool = True,
|
|
297
|
+
) -> UserMemoriesList:
|
|
298
|
+
"""
|
|
299
|
+
List memories for this user with optional category filters.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
offset: Number of records to skip.
|
|
303
|
+
limit: Maximum number of records to return (1-200).
|
|
304
|
+
categories: Optional list of category names to filter by.
|
|
305
|
+
include_previous_versions: Include full version history for each memory (default: True).
|
|
306
|
+
include_connected_memories: Include connected memories (default: True).
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
UserMemoriesList: Paginated list of memory items.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
UserNotFoundError: If the user is not found.
|
|
313
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
314
|
+
InternalServerError: If the server encounters an error.
|
|
315
|
+
NetworkError: If there are network issues.
|
|
316
|
+
TimeoutError: If the request times out.
|
|
317
|
+
RecallrAIError: For other API-related errors.
|
|
318
|
+
"""
|
|
319
|
+
params: Dict[str, Any] = {
|
|
320
|
+
"offset": offset,
|
|
321
|
+
"limit": limit,
|
|
322
|
+
"include_previous_versions": include_previous_versions,
|
|
323
|
+
"include_connected_memories": include_connected_memories,
|
|
324
|
+
}
|
|
325
|
+
if categories is not None:
|
|
326
|
+
params["categories"] = categories
|
|
327
|
+
|
|
328
|
+
response = self._http.get(
|
|
329
|
+
f"/api/v1/users/{self.user_id}/memories",
|
|
330
|
+
params=params,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if response.status_code == 404:
|
|
334
|
+
detail = response.json().get("detail", f"User {self.user_id} not found")
|
|
335
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
336
|
+
elif response.status_code == 400:
|
|
337
|
+
# Backend returns 400 for invalid categories
|
|
338
|
+
detail = response.json().get('detail', 'Invalid categories provided')
|
|
339
|
+
raise InvalidCategoriesError(
|
|
340
|
+
message=detail,
|
|
341
|
+
http_status=response.status_code
|
|
342
|
+
)
|
|
343
|
+
elif response.status_code != 200:
|
|
344
|
+
raise RecallrAIError(
|
|
345
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
346
|
+
http_status=response.status_code,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return UserMemoriesList.from_api_response(response.json())
|
|
350
|
+
|
|
351
|
+
def list_merge_conflicts(
|
|
352
|
+
self,
|
|
353
|
+
offset: int = 0,
|
|
354
|
+
limit: int = 10,
|
|
355
|
+
status: Optional[MergeConflictStatus] = None,
|
|
356
|
+
sort_by: str = "created_at",
|
|
357
|
+
sort_order: str = "desc",
|
|
358
|
+
) -> MergeConflictList:
|
|
359
|
+
"""
|
|
360
|
+
List merge conflicts for this user.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
offset: Number of records to skip.
|
|
364
|
+
limit: Maximum number of records to return.
|
|
365
|
+
status: Optional filter by conflict status.
|
|
366
|
+
sort_by: Field to sort by (created_at, resolved_at).
|
|
367
|
+
sort_order: Sort order (asc, desc).
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
MergeConflictList: Paginated list of merge conflicts.
|
|
371
|
+
|
|
372
|
+
Raises:
|
|
373
|
+
UserNotFoundError: If the user is not found.
|
|
374
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
375
|
+
InternalServerError: If the server encounters an error.
|
|
376
|
+
NetworkError: If there are network issues.
|
|
377
|
+
TimeoutError: If the request times out.
|
|
378
|
+
RecallrAIError: For other API-related errors.
|
|
379
|
+
"""
|
|
380
|
+
params: Dict[str, Any] = {
|
|
381
|
+
"offset": offset,
|
|
382
|
+
"limit": limit,
|
|
383
|
+
"sort_by": sort_by,
|
|
384
|
+
"sort_order": sort_order,
|
|
385
|
+
}
|
|
386
|
+
if status is not None:
|
|
387
|
+
params["status"] = status.value
|
|
388
|
+
|
|
389
|
+
response = self._http.get(
|
|
390
|
+
f"/api/v1/users/{self.user_id}/merge-conflicts",
|
|
391
|
+
params=params,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if response.status_code == 404:
|
|
395
|
+
detail = response.json().get("detail", f"User {self.user_id} not found")
|
|
396
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
397
|
+
elif response.status_code != 200:
|
|
398
|
+
raise RecallrAIError(
|
|
399
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
400
|
+
http_status=response.status_code,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
return MergeConflictList.from_api_response(response.json(), self._http, self.user_id)
|
|
404
|
+
|
|
405
|
+
def get_merge_conflict(self, conflict_id: str) -> MergeConflict:
|
|
406
|
+
"""
|
|
407
|
+
Get a specific merge conflict by ID.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
conflict_id: Unique identifier of the merge conflict.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
MergeConflict: The merge conflict object.
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
UserNotFoundError: If the user is not found.
|
|
417
|
+
MergeConflictNotFoundError: If the merge conflict is not found.
|
|
418
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
419
|
+
InternalServerError: If the server encounters an error.
|
|
420
|
+
NetworkError: If there are network issues.
|
|
421
|
+
TimeoutError: If the request times out.
|
|
422
|
+
RecallrAIError: For other API-related errors.
|
|
423
|
+
"""
|
|
424
|
+
response = self._http.get(
|
|
425
|
+
f"/api/v1/users/{self.user_id}/merge-conflicts/{conflict_id}"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if response.status_code == 404:
|
|
429
|
+
# Check if it's a user not found or conflict not found error
|
|
430
|
+
detail = response.json().get('detail', '')
|
|
431
|
+
if f"User {self.user_id} not found" in detail:
|
|
432
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
433
|
+
else:
|
|
434
|
+
raise MergeConflictNotFoundError(message=detail, http_status=response.status_code)
|
|
435
|
+
elif response.status_code != 200:
|
|
436
|
+
raise RecallrAIError(
|
|
437
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
438
|
+
http_status=response.status_code
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
conflict_data = MergeConflictModel.from_api_response(response.json())
|
|
442
|
+
return MergeConflict(self._http, self.user_id, conflict_data)
|
|
443
|
+
|
|
444
|
+
def get_last_n_messages(self, n: int) -> UserMessagesList:
|
|
445
|
+
"""
|
|
446
|
+
Get the last N messages for this user across all their sessions.
|
|
447
|
+
|
|
448
|
+
This method is useful for chatbot applications where you want to see
|
|
449
|
+
the recent conversation history for context.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
n: Number of recent messages to retrieve (1-100, default: 10).
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
UserMessagesList: List of the most recent messages.
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
UserNotFoundError: If the user is not found.
|
|
459
|
+
AuthenticationError: If the API key or project ID is invalid.
|
|
460
|
+
InternalServerError: If the server encounters an error.
|
|
461
|
+
NetworkError: If there are network issues.
|
|
462
|
+
TimeoutError: If the request times out.
|
|
463
|
+
RecallrAIError: For other API-related errors.
|
|
464
|
+
ValueError: If n is not between 1 and 100.
|
|
465
|
+
"""
|
|
466
|
+
if not (1 <= n <= 100):
|
|
467
|
+
raise ValueError("n must be between 1 and 100")
|
|
468
|
+
|
|
469
|
+
response = self._http.get(
|
|
470
|
+
f"/api/v1/users/{self.user_id}/messages",
|
|
471
|
+
params={"limit": n}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
if response.status_code == 404:
|
|
475
|
+
detail = response.json().get("detail", f"User with ID {self.user_id} not found")
|
|
476
|
+
raise UserNotFoundError(message=detail, http_status=response.status_code)
|
|
477
|
+
elif response.status_code != 200:
|
|
478
|
+
raise RecallrAIError(
|
|
479
|
+
message=response.json().get('detail', 'Unknown error'),
|
|
480
|
+
http_status=response.status_code
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
return UserMessagesList.from_api_response(response.json())
|
|
484
|
+
|
|
485
|
+
def __repr__(self) -> str:
|
|
486
|
+
return f"<User id={self.user_id} created_at={self.created_at} last_active_at={self.last_active_at}>"
|
recallrai/utils/__init__.py
CHANGED
recallrai/utils/http_client.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
HTTP client for making requests to the RecallrAI API.
|
|
3
|
+
"""
|
|
3
4
|
|
|
4
|
-
from
|
|
5
|
+
from json import JSONDecodeError
|
|
6
|
+
from httpx import Response, Client, TimeoutException, ConnectError
|
|
5
7
|
from typing import Any, Dict, Optional
|
|
6
8
|
from ..exceptions import (
|
|
7
9
|
TimeoutError,
|
|
8
|
-
NetworkError as CustomNetworkError,
|
|
9
10
|
ConnectionError,
|
|
10
11
|
ValidationError,
|
|
11
12
|
InternalServerError,
|
|
@@ -26,11 +27,12 @@ class HTTPClient:
|
|
|
26
27
|
Initialize the HTTP client.
|
|
27
28
|
|
|
28
29
|
Args:
|
|
29
|
-
api_key: Your RecallrAI API key
|
|
30
|
-
project_id: Your project ID
|
|
31
|
-
base_url: The base URL for the RecallrAI API
|
|
32
|
-
timeout: Request timeout in seconds
|
|
30
|
+
api_key: Your RecallrAI API key.
|
|
31
|
+
project_id: Your project ID.
|
|
32
|
+
base_url: The base URL for the RecallrAI API.
|
|
33
|
+
timeout: Request timeout in seconds.
|
|
33
34
|
"""
|
|
35
|
+
|
|
34
36
|
self.api_key = api_key
|
|
35
37
|
self.project_id = project_id
|
|
36
38
|
self.base_url = base_url.rstrip("/")
|
|
@@ -42,8 +44,7 @@ class HTTPClient:
|
|
|
42
44
|
"X-Project-Id": self.project_id,
|
|
43
45
|
"Content-Type": "application/json",
|
|
44
46
|
"Accept": "application/json",
|
|
45
|
-
"User-Agent": "RecallrAI-Python-SDK",
|
|
46
|
-
# TODO: "SDK-Version": "0.1.0",
|
|
47
|
+
"User-Agent": f"RecallrAI-Python-SDK/RecallrAI-Python-SDK/RecallrAI-Python-SDK/0.3.0",
|
|
47
48
|
},
|
|
48
49
|
)
|
|
49
50
|
|
|
@@ -58,13 +59,13 @@ class HTTPClient:
|
|
|
58
59
|
Make a request to the RecallrAI API.
|
|
59
60
|
|
|
60
61
|
Args:
|
|
61
|
-
method: HTTP method (GET, POST, PUT, DELETE)
|
|
62
|
-
path: API endpoint path
|
|
63
|
-
params: Query parameters
|
|
64
|
-
data: Request body data
|
|
62
|
+
method: HTTP method (GET, POST, PUT, DELETE).
|
|
63
|
+
path: API endpoint path.
|
|
64
|
+
params: Query parameters.
|
|
65
|
+
data: Request body data.
|
|
65
66
|
|
|
66
67
|
Returns:
|
|
67
|
-
The parsed JSON response
|
|
68
|
+
The parsed JSON response.
|
|
68
69
|
"""
|
|
69
70
|
url = f"{self.base_url}{path}"
|
|
70
71
|
|
|
@@ -82,26 +83,40 @@ class HTTPClient:
|
|
|
82
83
|
params=params,
|
|
83
84
|
json=data,
|
|
84
85
|
)
|
|
86
|
+
|
|
87
|
+
# Try to parse to JSON to catch JSON errors early
|
|
88
|
+
_ = response.json()
|
|
89
|
+
|
|
85
90
|
if response.status_code == 422:
|
|
91
|
+
detail = response.json().get("detail", "Validation error")
|
|
86
92
|
raise ValidationError(
|
|
87
|
-
|
|
93
|
+
message=detail,
|
|
94
|
+
http_status=response.status_code
|
|
88
95
|
)
|
|
89
96
|
elif response.status_code == 500:
|
|
97
|
+
detail = response.json().get("detail", "Internal server error")
|
|
90
98
|
raise InternalServerError(
|
|
91
|
-
|
|
99
|
+
message=detail,
|
|
100
|
+
http_status=response.status_code
|
|
92
101
|
)
|
|
93
102
|
elif response.status_code == 401:
|
|
103
|
+
detail = response.json().get("detail", "Authentication failed")
|
|
94
104
|
raise AuthenticationError(
|
|
95
|
-
|
|
105
|
+
message=detail,
|
|
106
|
+
http_status=response.status_code
|
|
96
107
|
)
|
|
97
108
|
|
|
98
109
|
return response
|
|
99
110
|
except TimeoutException as e:
|
|
100
|
-
raise TimeoutError(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
raise TimeoutError(
|
|
112
|
+
message=f"Request timed out: {e}",
|
|
113
|
+
http_status=0 # No HTTP status for timeout
|
|
114
|
+
)
|
|
115
|
+
except (ConnectError, JSONDecodeError) as e:
|
|
116
|
+
raise ConnectionError(
|
|
117
|
+
message=f"Failed to connect to the API: {e}",
|
|
118
|
+
http_status=0 # No HTTP status for connection error
|
|
119
|
+
)
|
|
105
120
|
except Exception as e:
|
|
106
121
|
# Handle other exceptions as needed
|
|
107
122
|
raise e
|