-
Notifications
You must be signed in to change notification settings - Fork 141
Expand file tree
/
Copy patherrors.py
More file actions
111 lines (83 loc) · 3.63 KB
/
Copy patherrors.py
File metadata and controls
111 lines (83 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
from requests import Response
import json
from datetime import datetime, timezone
from typing import Optional
from urllib.parse import urlparse
# RFC 9110 §5.6.7: HTTP-date "IMF-fixdate" format used by `Retry-After`.
_HTTP_DATE_FMT = "%a, %d %b %Y %H:%M:%S GMT"
def _parse_retry_after(value: Optional[str]) -> Optional[float]:
"""
Parse an HTTP `Retry-After` header into a delay in seconds.
Returns `None` if missing or unparseable. Per RFC 9110, the value is
either a non-negative integer of seconds or an IMF-fixdate HTTP-date.
"""
if not value:
return None
try:
return max(float(value), 0.0)
except ValueError:
pass
try:
retry_at = datetime.strptime(value, _HTTP_DATE_FMT).replace(tzinfo=timezone.utc)
except ValueError:
return None
return max((retry_at - datetime.now(timezone.utc)).total_seconds(), 0.0)
class FelderaError(Exception):
"""
Generic class for Feldera error handling
"""
def __init__(self, message: str) -> None:
self.message = message
super().__init__(self.message)
def __str__(self) -> str:
return f"FelderaError. Error message: {self.message}"
class FelderaAPIError(FelderaError):
"""Error sent by Feldera API"""
def __init__(self, error: str, request: Response) -> None:
self.status_code = request.status_code
self.error = error
self.error_code = None
self.message = None
self.details = None
self.retry_after: Optional[float] = _parse_retry_after(
request.headers.get("Retry-After")
)
err_msg = ""
if request.text:
try:
json_data = json.loads(request.text)
self.error_code = json_data.get("error_code")
if self.error_code:
err_msg += f"\nError Code: {self.error_code}"
self.message = json_data.get("message")
if self.message:
err_msg += f"\nMessage: {self.message}"
self.details = json_data.get("details")
if self.details:
err_msg += f"\nDetails: {self.details}"
except Exception:
self.message = request.text
err_msg += request.text
err_msg += f"\nResponse Status: {request.status_code}"
if int(request.status_code) == 401:
parsed = urlparse(request.request.url)
auth_err = f"\nAuthorization error at '{parsed.scheme}://{parsed.hostname}': "
auth = request.request.headers.get("Authorization")
if auth is None:
err_msg += f"{auth_err}no credential provided"
else:
# The credential may be an API key or a JWT/bearer token; do not
# assume which. The server `message` above carries the specifics.
err_msg += f"{auth_err}credential rejected (invalid or expired token / API key)"
err_msg = err_msg.strip()
super().__init__(err_msg)
# Compatibility alias: the RFC and some docs refer to this as `FelderaApiError`.
FelderaApiError = FelderaAPIError
class FelderaTimeoutError(FelderaError):
"""Error when Feldera operation takes longer than expected"""
def __init__(self, err: str) -> None:
super().__init__(f"Timeout connecting to Feldera: {err}")
class FelderaCommunicationError(FelderaError):
"""Error when connection to Feldera"""
def __init__(self, err: str) -> None:
super().__init__(f"Cannot connect to Feldera API: {err}")