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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: api-to-dataframe
3
- Version: 1.5.0
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.0,<0.2.0)
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.0"
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.0"
30
+ otel-wrapper = "^0.1.1"
31
31
 
32
32
  [tool.poetry.group.dev.dependencies]
33
33
  poetry-dynamic-versioning = "^1.3.0"
@@ -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 context manager
116
- with telemetry.traces().span_in_context("get_api_data") as (span, _):
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
- return response.json()
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 for this operation
190
- with telemetry.traces().span_in_context("api_to_dataframe") as (span, _):
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
- with telemetry.traces().span_in_context("http_request") as (span, _):
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
- with telemetry.traces().span_in_context("convert_to_dataframe") as (span, _):
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
- with telemetry.traces().span_in_context("retry_operation") as (span, _):
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