|
1 | 1 | import base64
|
2 | 2 | import requests
|
3 | 3 | from socketdev.core.classes import Response
|
4 |
| -from socketdev.exceptions import APIKeyMissing, APIFailure, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound |
| 4 | +from socketdev.exceptions import ( |
| 5 | + APIKeyMissing, APIFailure, APIAccessDenied, APIInsufficientQuota, |
| 6 | + APIResourceNotFound, APITimeout, APIConnectionError, APIBadGateway, |
| 7 | + APIInsufficientPermissions, APIOrganizationNotAllowed |
| 8 | +) |
5 | 9 | from socketdev.version import __version__
|
| 10 | +from requests.exceptions import Timeout, ConnectionError |
| 11 | +import time |
6 | 12 |
|
7 | 13 |
|
8 | 14 | class API:
|
@@ -31,23 +37,61 @@ def do_request(
|
31 | 37 | }
|
32 | 38 | url = f"{self.api_url}/{path}"
|
33 | 39 | try:
|
| 40 | + start_time = time.time() |
34 | 41 | response = requests.request(
|
35 | 42 | method.upper(), url, headers=headers, data=payload, files=files, timeout=self.request_timeout
|
36 | 43 | )
|
| 44 | + request_duration = time.time() - start_time |
37 | 45 |
|
38 | 46 | if response.status_code == 401:
|
39 | 47 | raise APIAccessDenied("Unauthorized")
|
40 | 48 | if response.status_code == 403:
|
41 |
| - raise APIInsufficientQuota("Insufficient max_quota for API method") |
| 49 | + try: |
| 50 | + error_message = response.json().get('error', {}).get('message', '') |
| 51 | + if "Insufficient permissions for API method" in error_message: |
| 52 | + raise APIInsufficientPermissions(error_message) |
| 53 | + elif "Organization not allowed" in error_message: |
| 54 | + raise APIOrganizationNotAllowed(error_message) |
| 55 | + elif "Insufficient max quota" in error_message: |
| 56 | + raise APIInsufficientQuota(error_message) |
| 57 | + else: |
| 58 | + raise APIAccessDenied(error_message or "Access denied") |
| 59 | + except ValueError: |
| 60 | + # If JSON parsing fails |
| 61 | + raise APIAccessDenied("Access denied") |
42 | 62 | if response.status_code == 404:
|
43 | 63 | raise APIResourceNotFound(f"Path not found {path}")
|
44 | 64 | if response.status_code == 429:
|
45 |
| - raise APIInsufficientQuota("Insufficient quota for API route") |
| 65 | + retry_after = response.headers.get('retry-after') |
| 66 | + if retry_after: |
| 67 | + try: |
| 68 | + seconds = int(retry_after) |
| 69 | + minutes = seconds // 60 |
| 70 | + remaining_seconds = seconds % 60 |
| 71 | + time_msg = f" Quota will reset in {minutes} minutes and {remaining_seconds} seconds" |
| 72 | + except ValueError: |
| 73 | + time_msg = f" Retry after: {retry_after}" |
| 74 | + else: |
| 75 | + time_msg = "" |
| 76 | + raise APIInsufficientQuota(f"Insufficient quota for API route.{time_msg}") |
| 77 | + if response.status_code == 502: |
| 78 | + raise APIBadGateway("Upstream server error") |
46 | 79 | if response.status_code >= 400:
|
47 |
| - raise APIFailure("Bad Request") |
| 80 | + raise APIFailure(f"Bad Request: HTTP {response.status_code}") |
48 | 81 |
|
49 | 82 | return response
|
50 | 83 |
|
| 84 | + except Timeout: |
| 85 | + request_duration = time.time() - start_time |
| 86 | + raise APITimeout(f"Request timed out after {request_duration:.2f} seconds") |
| 87 | + except ConnectionError as error: |
| 88 | + request_duration = time.time() - start_time |
| 89 | + raise APIConnectionError(f"Connection error after {request_duration:.2f} seconds: {error}") |
| 90 | + except (APIAccessDenied, APIInsufficientQuota, APIResourceNotFound, APIFailure, |
| 91 | + APITimeout, APIConnectionError, APIBadGateway, APIInsufficientPermissions, |
| 92 | + APIOrganizationNotAllowed): |
| 93 | + # Let all our custom exceptions propagate up unchanged |
| 94 | + raise |
51 | 95 | except Exception as error:
|
52 |
| - response = Response(text=f"{error}", error=True, status_code=500) |
53 |
| - raise APIFailure(response) |
| 96 | + # Only truly unexpected errors get wrapped in a generic APIFailure |
| 97 | + raise APIFailure(f"Unexpected error: {error}", status_code=500) |
0 commit comments