api-to-dataframe 1.5.0__tar.gz → 1.5.2__tar.gz
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.
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/PKG-INFO +2 -2
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/pyproject.toml +2 -2
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/controller/client_builder.py +37 -31
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/models/get_data.py +37 -31
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/models/retainer.py +27 -24
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/LICENSE +0 -0
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/README.md +0 -0
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/__init__.py +0 -0
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/utils/__init__.py +0 -0
- {api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/utils/logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: api-to-dataframe
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: A package to convert API responses to pandas dataframe
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: IvanildoBarauna
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.8
|
|
17
17
|
Requires-Dist: logging (>=0.4.9.6,<0.5.0.0)
|
|
18
|
-
Requires-Dist: otel-wrapper (>=0.1.
|
|
18
|
+
Requires-Dist: otel-wrapper (>=0.1.1,<0.2.0)
|
|
19
19
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
20
20
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
21
21
|
Project-URL: Documentation, https://github.com/IvanildoBarauna/api-to-dataframe/blob/main/README.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "api-to-dataframe"
|
|
3
|
-
version = "1.5.
|
|
3
|
+
version = "1.5.2"
|
|
4
4
|
description = "A package to convert API responses to pandas dataframe"
|
|
5
5
|
authors = ["IvanildoBarauna <ivanildo.jnr@outlook.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -27,7 +27,7 @@ python = "^3.9"
|
|
|
27
27
|
pandas = "^2.2.3"
|
|
28
28
|
requests = "^2.32.3"
|
|
29
29
|
logging = "^0.4.9.6"
|
|
30
|
-
otel-wrapper = "^0.1.
|
|
30
|
+
otel-wrapper = "^0.1.1"
|
|
31
31
|
|
|
32
32
|
[tool.poetry.group.dev.dependencies]
|
|
33
33
|
poetry-dynamic-versioning = "^1.3.0"
|
{api_to_dataframe-1.5.0 → api_to_dataframe-1.5.2}/src/api_to_dataframe/controller/client_builder.py
RENAMED
|
@@ -38,8 +38,8 @@ class ClientBuilder:
|
|
|
38
38
|
error_msg = "endpoint cannot be an empty string"
|
|
39
39
|
logger.error(error_msg)
|
|
40
40
|
telemetry.logs().new_log(
|
|
41
|
-
msg=error_msg,
|
|
42
|
-
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
41
|
+
msg=error_msg,
|
|
42
|
+
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
43
43
|
level=40 # ERROR level
|
|
44
44
|
)
|
|
45
45
|
raise ValueError
|
|
@@ -47,8 +47,8 @@ class ClientBuilder:
|
|
|
47
47
|
error_msg = "retries must be a non-negative integer"
|
|
48
48
|
logger.error(error_msg)
|
|
49
49
|
telemetry.logs().new_log(
|
|
50
|
-
msg=error_msg,
|
|
51
|
-
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
50
|
+
msg=error_msg,
|
|
51
|
+
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
52
52
|
level=40 # ERROR level
|
|
53
53
|
)
|
|
54
54
|
raise ValueError
|
|
@@ -56,8 +56,8 @@ class ClientBuilder:
|
|
|
56
56
|
error_msg = "initial_delay must be a non-negative integer"
|
|
57
57
|
logger.error(error_msg)
|
|
58
58
|
telemetry.logs().new_log(
|
|
59
|
-
msg=error_msg,
|
|
60
|
-
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
59
|
+
msg=error_msg,
|
|
60
|
+
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
61
61
|
level=40 # ERROR level
|
|
62
62
|
)
|
|
63
63
|
raise ValueError
|
|
@@ -65,8 +65,8 @@ class ClientBuilder:
|
|
|
65
65
|
error_msg = "connection_timeout must be a non-negative integer"
|
|
66
66
|
logger.error(error_msg)
|
|
67
67
|
telemetry.logs().new_log(
|
|
68
|
-
msg=error_msg,
|
|
69
|
-
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
68
|
+
msg=error_msg,
|
|
69
|
+
tags={"component": "ClientBuilder", "method": "__init__"},
|
|
70
70
|
level=40 # ERROR level
|
|
71
71
|
)
|
|
72
72
|
raise ValueError
|
|
@@ -77,17 +77,17 @@ class ClientBuilder:
|
|
|
77
77
|
self.headers = headers
|
|
78
78
|
self.retries = retries
|
|
79
79
|
self.delay = initial_delay
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
# Record client initialization metric
|
|
82
82
|
telemetry.metrics().metric_increment(
|
|
83
83
|
name="client.initialization",
|
|
84
84
|
tags={
|
|
85
|
-
"endpoint": endpoint,
|
|
85
|
+
"endpoint": endpoint,
|
|
86
86
|
"retry_strategy": retry_strategy.name,
|
|
87
87
|
"connection_timeout": str(connection_timeout)
|
|
88
88
|
}
|
|
89
89
|
)
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
# Log initialization
|
|
92
92
|
telemetry.logs().new_log(
|
|
93
93
|
msg=f"ClientBuilder initialized with endpoint {endpoint}",
|
|
@@ -112,13 +112,14 @@ class ClientBuilder:
|
|
|
112
112
|
Returns:
|
|
113
113
|
dict: The JSON response from the API as a dictionary.
|
|
114
114
|
"""
|
|
115
|
-
# Use the telemetry spans with
|
|
116
|
-
|
|
115
|
+
# Use the telemetry spans with new API
|
|
116
|
+
span = telemetry.traces().new_span("get_api_data")
|
|
117
|
+
try:
|
|
117
118
|
# Add span attributes
|
|
118
119
|
span.set_attribute("endpoint", self.endpoint)
|
|
119
120
|
span.set_attribute("retry_strategy", self.retry_strategy.name)
|
|
120
121
|
span.set_attribute("connection_timeout", self.connection_timeout)
|
|
121
|
-
|
|
122
|
+
|
|
122
123
|
# Log the API request
|
|
123
124
|
telemetry.logs().new_log(
|
|
124
125
|
msg=f"Making API request to {self.endpoint}",
|
|
@@ -129,33 +130,33 @@ class ClientBuilder:
|
|
|
129
130
|
},
|
|
130
131
|
level=20 # INFO level
|
|
131
132
|
)
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
# Record the start time for response time measurement
|
|
134
135
|
start_time = time.time()
|
|
135
|
-
|
|
136
|
+
|
|
136
137
|
# Make the API request
|
|
137
138
|
response = GetData.get_response(
|
|
138
139
|
endpoint=self.endpoint,
|
|
139
140
|
headers=self.headers,
|
|
140
141
|
connection_timeout=self.connection_timeout,
|
|
141
142
|
)
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
# Calculate response time
|
|
144
145
|
response_time = time.time() - start_time
|
|
145
|
-
|
|
146
|
+
|
|
146
147
|
# Record response time as histogram
|
|
147
148
|
telemetry.metrics().record_histogram(
|
|
148
149
|
name="api.response_time",
|
|
149
150
|
tags={"endpoint": self.endpoint},
|
|
150
151
|
value=response_time
|
|
151
152
|
)
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
# Record successful request metric
|
|
154
155
|
telemetry.metrics().metric_increment(
|
|
155
156
|
name="api.request.success",
|
|
156
157
|
tags={"endpoint": self.endpoint}
|
|
157
158
|
)
|
|
158
|
-
|
|
159
|
+
|
|
159
160
|
# Log success
|
|
160
161
|
telemetry.logs().new_log(
|
|
161
162
|
msg=f"API request to {self.endpoint} successful",
|
|
@@ -169,7 +170,9 @@ class ClientBuilder:
|
|
|
169
170
|
level=20 # INFO level
|
|
170
171
|
)
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
return response.json()
|
|
174
|
+
finally:
|
|
175
|
+
span.end()
|
|
173
176
|
|
|
174
177
|
@staticmethod
|
|
175
178
|
def api_to_dataframe(response: dict):
|
|
@@ -186,11 +189,12 @@ class ClientBuilder:
|
|
|
186
189
|
Returns:
|
|
187
190
|
DataFrame: A pandas DataFrame containing the data from the API response.
|
|
188
191
|
"""
|
|
189
|
-
# Use telemetry
|
|
190
|
-
|
|
192
|
+
# Use telemetry with new API
|
|
193
|
+
span = telemetry.traces().new_span("api_to_dataframe")
|
|
194
|
+
try:
|
|
191
195
|
response_size = len(response) if isinstance(response, list) else 1
|
|
192
196
|
span.set_attribute("response_size", response_size)
|
|
193
|
-
|
|
197
|
+
|
|
194
198
|
# Log conversion start
|
|
195
199
|
telemetry.logs().new_log(
|
|
196
200
|
msg="Converting API response to DataFrame",
|
|
@@ -202,17 +206,17 @@ class ClientBuilder:
|
|
|
202
206
|
},
|
|
203
207
|
level=20 # INFO level
|
|
204
208
|
)
|
|
205
|
-
|
|
209
|
+
|
|
206
210
|
try:
|
|
207
211
|
# Convert to dataframe
|
|
208
212
|
df = GetData.to_dataframe(response)
|
|
209
|
-
|
|
213
|
+
|
|
210
214
|
# Record metrics
|
|
211
215
|
telemetry.metrics().metric_increment(
|
|
212
216
|
name="dataframe.conversion.success",
|
|
213
217
|
tags={"size": len(df)}
|
|
214
218
|
)
|
|
215
|
-
|
|
219
|
+
|
|
216
220
|
# Log success
|
|
217
221
|
telemetry.logs().new_log(
|
|
218
222
|
msg="Successfully converted API response to DataFrame",
|
|
@@ -224,16 +228,16 @@ class ClientBuilder:
|
|
|
224
228
|
},
|
|
225
229
|
level=20 # INFO level
|
|
226
230
|
)
|
|
227
|
-
|
|
231
|
+
|
|
228
232
|
return df
|
|
229
|
-
|
|
233
|
+
|
|
230
234
|
except Exception as e:
|
|
231
235
|
# Record failure metric
|
|
232
236
|
telemetry.metrics().metric_increment(
|
|
233
237
|
name="dataframe.conversion.failure",
|
|
234
238
|
tags={"error_type": type(e).__name__}
|
|
235
239
|
)
|
|
236
|
-
|
|
240
|
+
|
|
237
241
|
# Log error
|
|
238
242
|
error_msg = f"Failed to convert API response to DataFrame: {str(e)}"
|
|
239
243
|
telemetry.logs().new_log(
|
|
@@ -246,6 +250,8 @@ class ClientBuilder:
|
|
|
246
250
|
},
|
|
247
251
|
level=40 # ERROR level
|
|
248
252
|
)
|
|
249
|
-
|
|
253
|
+
|
|
250
254
|
# Re-raise the exception
|
|
251
255
|
raise
|
|
256
|
+
finally:
|
|
257
|
+
span.end()
|
|
@@ -7,11 +7,12 @@ class GetData:
|
|
|
7
7
|
@staticmethod
|
|
8
8
|
def get_response(endpoint: str, headers: dict, connection_timeout: int):
|
|
9
9
|
# Start a span for the API request
|
|
10
|
-
|
|
10
|
+
span = telemetry.traces().new_span("http_request")
|
|
11
|
+
try:
|
|
11
12
|
span.set_attribute("http.url", endpoint)
|
|
12
13
|
span.set_attribute("http.method", "GET")
|
|
13
14
|
span.set_attribute("http.timeout", connection_timeout)
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
# Log the request
|
|
16
17
|
telemetry.logs().new_log(
|
|
17
18
|
msg=f"Sending HTTP GET request to {endpoint}",
|
|
@@ -23,18 +24,18 @@ class GetData:
|
|
|
23
24
|
},
|
|
24
25
|
level=20 # INFO level
|
|
25
26
|
)
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
try:
|
|
28
29
|
# Make the request
|
|
29
30
|
response = requests.get(endpoint, timeout=connection_timeout, headers=headers)
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
# Set response attributes on span
|
|
32
33
|
span.set_attribute("http.status_code", response.status_code)
|
|
33
34
|
span.set_attribute("http.response_content_length", len(response.content))
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
# Attempt to raise for status to catch errors
|
|
36
37
|
response.raise_for_status()
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
# Log successful response
|
|
39
40
|
telemetry.logs().new_log(
|
|
40
41
|
msg=f"Received HTTP {response.status_code} response from {endpoint}",
|
|
@@ -47,7 +48,7 @@ class GetData:
|
|
|
47
48
|
},
|
|
48
49
|
level=20 # INFO level
|
|
49
50
|
)
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
# Record successful request metric
|
|
52
53
|
telemetry.metrics().metric_increment(
|
|
53
54
|
name="http.request.success",
|
|
@@ -56,16 +57,16 @@ class GetData:
|
|
|
56
57
|
"status_code": response.status_code
|
|
57
58
|
}
|
|
58
59
|
)
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
return response
|
|
61
|
-
|
|
62
|
+
|
|
62
63
|
except requests.exceptions.RequestException as e:
|
|
63
64
|
# Record the exception on the span
|
|
64
65
|
span.record_exception(e)
|
|
65
66
|
span.set_attribute("error", True)
|
|
66
67
|
span.set_attribute("error.type", type(e).__name__)
|
|
67
68
|
span.set_attribute("error.message", str(e))
|
|
68
|
-
|
|
69
|
+
|
|
69
70
|
# Log the error
|
|
70
71
|
telemetry.logs().new_log(
|
|
71
72
|
msg=f"HTTP request failed: {str(e)}",
|
|
@@ -78,7 +79,7 @@ class GetData:
|
|
|
78
79
|
},
|
|
79
80
|
level=40 # ERROR level
|
|
80
81
|
)
|
|
81
|
-
|
|
82
|
+
|
|
82
83
|
# Record failure metric
|
|
83
84
|
telemetry.metrics().metric_increment(
|
|
84
85
|
name="http.request.failure",
|
|
@@ -87,19 +88,22 @@ class GetData:
|
|
|
87
88
|
"error_type": type(e).__name__
|
|
88
89
|
}
|
|
89
90
|
)
|
|
90
|
-
|
|
91
|
+
|
|
91
92
|
# Re-raise the exception
|
|
92
93
|
raise
|
|
94
|
+
finally:
|
|
95
|
+
span.end()
|
|
93
96
|
|
|
94
97
|
@staticmethod
|
|
95
98
|
def to_dataframe(response):
|
|
96
99
|
# Start a span for dataframe conversion
|
|
97
|
-
|
|
100
|
+
span = telemetry.traces().new_span("convert_to_dataframe")
|
|
101
|
+
try:
|
|
98
102
|
# Set attributes about the data
|
|
99
103
|
data_size = len(response) if isinstance(response, list) else 1
|
|
100
104
|
span.set_attribute("data.size", data_size)
|
|
101
105
|
span.set_attribute("data.type", type(response).__name__)
|
|
102
|
-
|
|
106
|
+
|
|
103
107
|
# Log conversion attempt
|
|
104
108
|
telemetry.logs().new_log(
|
|
105
109
|
msg="Converting data to DataFrame",
|
|
@@ -111,16 +115,16 @@ class GetData:
|
|
|
111
115
|
},
|
|
112
116
|
level=20 # INFO level
|
|
113
117
|
)
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
try:
|
|
116
120
|
# Convert to DataFrame
|
|
117
121
|
df = pd.DataFrame(response)
|
|
118
|
-
|
|
122
|
+
|
|
119
123
|
# Check if DataFrame is empty
|
|
120
124
|
if df.empty:
|
|
121
125
|
error_msg = "::: DataFrame is empty :::"
|
|
122
126
|
logger.error(error_msg)
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
# Log the error with OpenTelemetry
|
|
125
129
|
telemetry.logs().new_log(
|
|
126
130
|
msg=error_msg,
|
|
@@ -132,20 +136,20 @@ class GetData:
|
|
|
132
136
|
},
|
|
133
137
|
level=40 # ERROR level
|
|
134
138
|
)
|
|
135
|
-
|
|
139
|
+
|
|
136
140
|
# Record empty DataFrame metric
|
|
137
141
|
telemetry.metrics().metric_increment(
|
|
138
142
|
name="dataframe.empty",
|
|
139
143
|
tags={"data_type": type(response).__name__}
|
|
140
144
|
)
|
|
141
|
-
|
|
145
|
+
|
|
142
146
|
# Set span as error
|
|
143
147
|
span.set_attribute("error", True)
|
|
144
148
|
span.set_attribute("error.type", "ValueError")
|
|
145
149
|
span.set_attribute("error.message", error_msg)
|
|
146
|
-
|
|
150
|
+
|
|
147
151
|
raise ValueError(error_msg)
|
|
148
|
-
|
|
152
|
+
|
|
149
153
|
# Log success
|
|
150
154
|
telemetry.logs().new_log(
|
|
151
155
|
msg="Successfully converted data to DataFrame",
|
|
@@ -157,35 +161,35 @@ class GetData:
|
|
|
157
161
|
},
|
|
158
162
|
level=20 # INFO level
|
|
159
163
|
)
|
|
160
|
-
|
|
164
|
+
|
|
161
165
|
# Record dataframe metrics
|
|
162
166
|
telemetry.metrics().record_gauge(
|
|
163
167
|
name="dataframe.rows",
|
|
164
168
|
tags={"data_type": type(response).__name__},
|
|
165
169
|
value=float(len(df))
|
|
166
170
|
)
|
|
167
|
-
|
|
171
|
+
|
|
168
172
|
telemetry.metrics().record_gauge(
|
|
169
173
|
name="dataframe.columns",
|
|
170
174
|
tags={"data_type": type(response).__name__},
|
|
171
175
|
value=float(len(df.columns))
|
|
172
176
|
)
|
|
173
|
-
|
|
177
|
+
|
|
174
178
|
# Set additional span attributes
|
|
175
179
|
span.set_attribute("dataframe.rows", len(df))
|
|
176
180
|
span.set_attribute("dataframe.columns", len(df.columns))
|
|
177
|
-
|
|
181
|
+
|
|
178
182
|
return df
|
|
179
|
-
|
|
183
|
+
|
|
180
184
|
except ValueError:
|
|
181
185
|
# Re-raise ValueErrors (like empty DataFrame)
|
|
182
186
|
raise
|
|
183
|
-
|
|
187
|
+
|
|
184
188
|
except Exception as err:
|
|
185
189
|
# Log the error
|
|
186
190
|
error_msg = f"Invalid response for transform in dataframe: {err}"
|
|
187
191
|
logger.error(error_msg)
|
|
188
|
-
|
|
192
|
+
|
|
189
193
|
# Log with OpenTelemetry
|
|
190
194
|
telemetry.logs().new_log(
|
|
191
195
|
msg=error_msg,
|
|
@@ -198,18 +202,20 @@ class GetData:
|
|
|
198
202
|
},
|
|
199
203
|
level=40 # ERROR level
|
|
200
204
|
)
|
|
201
|
-
|
|
205
|
+
|
|
202
206
|
# Record conversion failure metric
|
|
203
207
|
telemetry.metrics().metric_increment(
|
|
204
208
|
name="dataframe.conversion.error",
|
|
205
209
|
tags={"error_type": type(err).__name__}
|
|
206
210
|
)
|
|
207
|
-
|
|
211
|
+
|
|
208
212
|
# Record the exception on the span
|
|
209
213
|
span.record_exception(err)
|
|
210
214
|
span.set_attribute("error", True)
|
|
211
215
|
span.set_attribute("error.type", type(err).__name__)
|
|
212
216
|
span.set_attribute("error.message", str(err))
|
|
213
|
-
|
|
217
|
+
|
|
214
218
|
# Raise TypeError with original error as cause
|
|
215
219
|
raise TypeError(error_msg) from err
|
|
220
|
+
finally:
|
|
221
|
+
span.end()
|
|
@@ -15,23 +15,24 @@ class Strategies(Enum):
|
|
|
15
15
|
def retry_strategies(func):
|
|
16
16
|
def wrapper(*args, **kwargs): # pylint: disable=inconsistent-return-statements
|
|
17
17
|
retry_number = 0
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
# Get the endpoint for better observability context
|
|
20
20
|
endpoint = args[0].endpoint if hasattr(args[0], 'endpoint') else 'unknown'
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Start a span for this entire retry operation
|
|
23
|
-
|
|
23
|
+
span = telemetry.traces().new_span("retry_operation")
|
|
24
|
+
try:
|
|
24
25
|
span.set_attribute("endpoint", endpoint)
|
|
25
26
|
span.set_attribute("retry_strategy", args[0].retry_strategy.name)
|
|
26
27
|
span.set_attribute("max_retries", args[0].retries)
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
while retry_number < args[0].retries:
|
|
29
30
|
try:
|
|
30
31
|
# Log retry attempt if not the first attempt
|
|
31
32
|
if retry_number > 0:
|
|
32
33
|
# Log using traditional logger
|
|
33
34
|
logger.info(f"Trying for the {retry_number} of {Constants.MAX_OF_RETRIES} retries. Using {args[0].retry_strategy}")
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
# Log using OpenTelemetry
|
|
36
37
|
telemetry.logs().new_log(
|
|
37
38
|
msg=f"Retry attempt {retry_number} of {args[0].retries}",
|
|
@@ -44,7 +45,7 @@ def retry_strategies(func):
|
|
|
44
45
|
},
|
|
45
46
|
level=20 # INFO level
|
|
46
47
|
)
|
|
47
|
-
|
|
48
|
+
|
|
48
49
|
# Record retry metric
|
|
49
50
|
telemetry.metrics().metric_increment(
|
|
50
51
|
name="api.request.retry",
|
|
@@ -53,13 +54,13 @@ def retry_strategies(func):
|
|
|
53
54
|
"retry_strategy": args[0].retry_strategy.name
|
|
54
55
|
}
|
|
55
56
|
)
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
# Update span with current retry count
|
|
58
59
|
span.set_attribute("current_retry", retry_number)
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
# Execute the wrapped function
|
|
61
62
|
result = func(*args, **kwargs)
|
|
62
|
-
|
|
63
|
+
|
|
63
64
|
# If we got here, it succeeded - record success metric after retries
|
|
64
65
|
if retry_number > 0:
|
|
65
66
|
telemetry.metrics().metric_increment(
|
|
@@ -70,7 +71,7 @@ def retry_strategies(func):
|
|
|
70
71
|
"retry_strategy": args[0].retry_strategy.name
|
|
71
72
|
}
|
|
72
73
|
)
|
|
73
|
-
|
|
74
|
+
|
|
74
75
|
telemetry.logs().new_log(
|
|
75
76
|
msg=f"Request succeeded after {retry_number} retries",
|
|
76
77
|
tags={
|
|
@@ -81,16 +82,16 @@ def retry_strategies(func):
|
|
|
81
82
|
},
|
|
82
83
|
level=20 # INFO level
|
|
83
84
|
)
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
return result
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
except RequestException as e:
|
|
88
89
|
retry_number += 1
|
|
89
|
-
|
|
90
|
+
|
|
90
91
|
# Record exception in the span
|
|
91
92
|
span.record_exception(e)
|
|
92
93
|
span.set_attribute("failed_attempt", retry_number)
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
# Handle different retry strategies
|
|
95
96
|
if args[0].retry_strategy == Strategies.NO_RETRY_STRATEGY:
|
|
96
97
|
# Log failure with OpenTelemetry
|
|
@@ -104,7 +105,7 @@ def retry_strategies(func):
|
|
|
104
105
|
},
|
|
105
106
|
level=40 # ERROR level
|
|
106
107
|
)
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
# Record failure metric
|
|
109
110
|
telemetry.metrics().metric_increment(
|
|
110
111
|
name="api.request.failure",
|
|
@@ -114,9 +115,9 @@ def retry_strategies(func):
|
|
|
114
115
|
"retry_strategy": "none"
|
|
115
116
|
}
|
|
116
117
|
)
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
raise e
|
|
119
|
-
|
|
120
|
+
|
|
120
121
|
# Apply delay based on strategy
|
|
121
122
|
if args[0].retry_strategy == Strategies.LINEAR_RETRY_STRATEGY:
|
|
122
123
|
delay = args[0].delay
|
|
@@ -124,7 +125,7 @@ def retry_strategies(func):
|
|
|
124
125
|
elif args[0].retry_strategy == Strategies.EXPONENTIAL_RETRY_STRATEGY:
|
|
125
126
|
delay = args[0].delay * retry_number
|
|
126
127
|
strategy_name = "exponential"
|
|
127
|
-
|
|
128
|
+
|
|
128
129
|
# Log retry delay
|
|
129
130
|
telemetry.logs().new_log(
|
|
130
131
|
msg=f"Request failed, retrying in {delay}s (strategy: {strategy_name})",
|
|
@@ -139,7 +140,7 @@ def retry_strategies(func):
|
|
|
139
140
|
},
|
|
140
141
|
level=30 # WARNING level
|
|
141
142
|
)
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
# Record the delay time in metrics
|
|
144
145
|
telemetry.metrics().record_gauge(
|
|
145
146
|
name="api.retry.delay",
|
|
@@ -150,16 +151,16 @@ def retry_strategies(func):
|
|
|
150
151
|
},
|
|
151
152
|
value=float(delay)
|
|
152
153
|
)
|
|
153
|
-
|
|
154
|
+
|
|
154
155
|
# Sleep for the calculated delay
|
|
155
156
|
time.sleep(delay)
|
|
156
|
-
|
|
157
|
+
|
|
157
158
|
# Check if we've reached max retries
|
|
158
159
|
if retry_number in (args[0].retries, Constants.MAX_OF_RETRIES):
|
|
159
160
|
# Log final failure
|
|
160
161
|
error_msg = f"Failed after {retry_number} retries"
|
|
161
162
|
logger.error(error_msg)
|
|
162
|
-
|
|
163
|
+
|
|
163
164
|
telemetry.logs().new_log(
|
|
164
165
|
msg=error_msg,
|
|
165
166
|
tags={
|
|
@@ -172,7 +173,7 @@ def retry_strategies(func):
|
|
|
172
173
|
},
|
|
173
174
|
level=40 # ERROR level
|
|
174
175
|
)
|
|
175
|
-
|
|
176
|
+
|
|
176
177
|
# Record final failure metric
|
|
177
178
|
telemetry.metrics().metric_increment(
|
|
178
179
|
name="api.request.retry.exhausted",
|
|
@@ -182,7 +183,9 @@ def retry_strategies(func):
|
|
|
182
183
|
"error_type": type(e).__name__
|
|
183
184
|
}
|
|
184
185
|
)
|
|
185
|
-
|
|
186
|
+
|
|
186
187
|
raise e
|
|
188
|
+
finally:
|
|
189
|
+
span.end()
|
|
187
190
|
|
|
188
191
|
return wrapper
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|