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/user.py CHANGED
@@ -2,14 +2,27 @@
2
2
  User management functionality for the RecallrAI SDK.
3
3
  """
4
4
 
5
- from typing import Any, Dict, Optional
5
+ import json
6
+ from typing import Any, List, Dict, Optional
6
7
  from .utils import HTTPClient
7
- from .models import User as UserModel, SessionList
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) -> 'User':
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["metadata"] = new_metadata
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
- raise UserNotFoundError(user_id=self.user_id)
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
- raise UserAlreadyExistsError(user_id=new_user_id)
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=f"Failed to update user: {response.json().get('detail', 'Unknown error')}",
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
- return self
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
- raise UserNotFoundError(user_id=self.user_id)
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=f"Failed to delete user: {response.json().get('detail', 'Unknown error')}",
155
+ message=response.json().get('detail', 'Unknown error'),
113
156
  http_status=response.status_code
114
157
  )
115
158
 
116
- def create_session(self, auto_process_after_minutes: int = -1) -> 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
- auto_process_after_minutes: Minutes to wait before auto-processing (-1 to disable)
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={"auto_process_after_minutes": auto_process_after_minutes},
188
+ data=payload,
137
189
  )
138
190
 
139
191
  if response.status_code == 404:
140
- raise UserNotFoundError(user_id=self.user_id)
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=f"Failed to create session: {response.json().get('detail', 'Unknown error')}",
196
+ message=response.json().get('detail', 'Unknown error'),
144
197
  http_status=response.status_code
145
198
  )
146
199
 
147
- session_id = response.json()["session_id"]
148
- return Session(self._http, self.user_id, session_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
- # Verify the session exists by checking its status
170
- session = Session(self._http, self.user_id, session_id)
171
- try:
172
- session.get_status() # This will raise appropriate errors if the session doesn't exist
173
- return session
174
- except SessionNotFoundError:
175
- raise
176
- except Exception as e:
177
- raise RecallrAIError(f"Error retrieving session: {str(e)}")
178
-
179
- def list_sessions(self, offset: int = 0, limit: int = 10) -> SessionList:
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={"offset": offset, "limit": limit},
276
+ params=params,
201
277
  )
202
278
 
203
279
  if response.status_code == 404:
204
- raise UserNotFoundError(user_id=self.user_id)
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=f"Failed to list sessions: {response.json().get('detail', 'Unknown error')}",
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}>"
@@ -1,5 +1,6 @@
1
- # Path: recallrai/utils/__init__.py
2
- # Description: Package initialization for utilities
1
+ """
2
+ Utility functions for the SDK.
3
+ """
3
4
 
4
5
  from .http_client import HTTPClient
5
6
 
@@ -1,11 +1,12 @@
1
- # Path: recallrai/utils/http.py
2
- # Description: HTTP client utilities for the RecallrAI SDK
1
+ """
2
+ HTTP client for making requests to the RecallrAI API.
3
+ """
3
4
 
4
- from httpx import Response, Client, TimeoutException, NetworkError, ConnectError
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
- details=response.json()["detail"],
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
- details=response.json()["detail"],
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
- details=response.json()["detail"],
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(f"Request timed out: {e}")
101
- except NetworkError as e:
102
- raise CustomNetworkError(f"Network error occurred: {e}")
103
- except ConnectError as e:
104
- raise ConnectionError(f"Failed to connect to the API: {e}")
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