Skip to content

[REFACTOR] Remove Otel Wrapper and Increase Code Coverage #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# API-TO-DATAFRAME DEVELOPMENT GUIDE

## Build & Test Commands
- Setup: `poetry install`
- Run all tests: `poetry run pytest`
- Run single test: `poetry run pytest tests/test_file.py::test_function`
- Coverage: `poetry run coverage run -m pytest && poetry run coverage report`
- Lint: `poetry run pylint src/`
- Format: `poetry run black src/ tests/`

## Code Style Guidelines
- Use Python 3.9+ features
- Import order: stdlib → third-party → local
- Type hints required for function parameters and return values
- Follow black formatting (max line length 88)
- Variable naming: snake_case for variables/functions, PascalCase for classes
- Error handling: use try/except with specific exceptions, log errors
- Use f-strings for string formatting
- Test coverage target: 90%+
- Use dataclasses or typed dictionaries for structured data

## Architecture
- MVC-like pattern: controllers, models, utils
- Core functionality in models package
- Client interactions in controller package
901 changes: 136 additions & 765 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "api-to-dataframe"
version = "1.5.2"
version = "2.0.0"
description = "A package to convert API responses to pandas dataframe"
authors = ["IvanildoBarauna <[email protected]>"]
readme = "README.md"
Expand All @@ -27,7 +27,6 @@ python = "^3.9"
pandas = "^2.2.3"
requests = "^2.32.3"
logging = "^0.4.9.6"
otel-wrapper = "^0.1.1"

[tool.poetry.group.dev.dependencies]
poetry-dynamic-versioning = "^1.3.0"
Expand Down
178 changes: 8 additions & 170 deletions src/api_to_dataframe/controller/client_builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from api_to_dataframe.models.retainer import retry_strategies, Strategies
from api_to_dataframe.models.get_data import GetData
from api_to_dataframe.utils.logger import logger, telemetry
import time
from api_to_dataframe.utils.logger import logger


class ClientBuilder:
Expand Down Expand Up @@ -37,38 +36,18 @@ def __init__( # pylint: disable=too-many-positional-arguments,too-many-argument
if endpoint == "":
error_msg = "endpoint cannot be an empty string"
logger.error(error_msg)
telemetry.logs().new_log(
msg=error_msg,
tags={"component": "ClientBuilder", "method": "__init__"},
level=40 # ERROR level
)
raise ValueError
if not isinstance(retries, int) or retries < 0:
error_msg = "retries must be a non-negative integer"
logger.error(error_msg)
telemetry.logs().new_log(
msg=error_msg,
tags={"component": "ClientBuilder", "method": "__init__"},
level=40 # ERROR level
)
raise ValueError
if not isinstance(initial_delay, int) or initial_delay < 0:
error_msg = "initial_delay must be a non-negative integer"
logger.error(error_msg)
telemetry.logs().new_log(
msg=error_msg,
tags={"component": "ClientBuilder", "method": "__init__"},
level=40 # ERROR level
)
raise ValueError
if not isinstance(connection_timeout, int) or connection_timeout < 0:
error_msg = "connection_timeout must be a non-negative integer"
logger.error(error_msg)
telemetry.logs().new_log(
msg=error_msg,
tags={"component": "ClientBuilder", "method": "__init__"},
level=40 # ERROR level
)
raise ValueError

self.endpoint = endpoint
Expand All @@ -78,28 +57,6 @@ def __init__( # pylint: disable=too-many-positional-arguments,too-many-argument
self.retries = retries
self.delay = initial_delay

# Record client initialization metric
telemetry.metrics().metric_increment(
name="client.initialization",
tags={
"endpoint": endpoint,
"retry_strategy": retry_strategy.name,
"connection_timeout": str(connection_timeout)
}
)

# Log initialization
telemetry.logs().new_log(
msg=f"ClientBuilder initialized with endpoint {endpoint}",
tags={
"endpoint": endpoint,
"retry_strategy": retry_strategy.name,
"connection_timeout": str(connection_timeout),
"component": "ClientBuilder"
},
level=20 # INFO level
)

@retry_strategies
def get_api_data(self):
"""
Expand All @@ -112,67 +69,13 @@ def get_api_data(self):
Returns:
dict: The JSON response from the API as a dictionary.
"""
# Use the telemetry spans with new API
span = telemetry.traces().new_span("get_api_data")
try:
# Add span attributes
span.set_attribute("endpoint", self.endpoint)
span.set_attribute("retry_strategy", self.retry_strategy.name)
span.set_attribute("connection_timeout", self.connection_timeout)

# Log the API request
telemetry.logs().new_log(
msg=f"Making API request to {self.endpoint}",
tags={
"endpoint": self.endpoint,
"component": "ClientBuilder",
"method": "get_api_data"
},
level=20 # INFO level
)

# Record the start time for response time measurement
start_time = time.time()

# Make the API request
response = GetData.get_response(
endpoint=self.endpoint,
headers=self.headers,
connection_timeout=self.connection_timeout,
)

# Calculate response time
response_time = time.time() - start_time

# Record response time as histogram
telemetry.metrics().record_histogram(
name="api.response_time",
tags={"endpoint": self.endpoint},
value=response_time
)

# Record successful request metric
telemetry.metrics().metric_increment(
name="api.request.success",
tags={"endpoint": self.endpoint}
)

# Log success
telemetry.logs().new_log(
msg=f"API request to {self.endpoint} successful",
tags={
"endpoint": self.endpoint,
"response_status": response.status_code,
"response_time": response_time,
"component": "ClientBuilder",
"method": "get_api_data"
},
level=20 # INFO level
)
response = GetData.get_response(
endpoint=self.endpoint,
headers=self.headers,
connection_timeout=self.connection_timeout,
)

return response.json()
finally:
span.end()
return response.json()

@staticmethod
def api_to_dataframe(response: dict):
Expand All @@ -189,69 +92,4 @@ def api_to_dataframe(response: dict):
Returns:
DataFrame: A pandas DataFrame containing the data from the API response.
"""
# Use telemetry with new API
span = telemetry.traces().new_span("api_to_dataframe")
try:
response_size = len(response) if isinstance(response, list) else 1
span.set_attribute("response_size", response_size)

# Log conversion start
telemetry.logs().new_log(
msg="Converting API response to DataFrame",
tags={
"response_size": response_size,
"response_type": type(response).__name__,
"component": "ClientBuilder",
"method": "api_to_dataframe"
},
level=20 # INFO level
)

try:
# Convert to dataframe
df = GetData.to_dataframe(response)

# Record metrics
telemetry.metrics().metric_increment(
name="dataframe.conversion.success",
tags={"size": len(df)}
)

# Log success
telemetry.logs().new_log(
msg="Successfully converted API response to DataFrame",
tags={
"dataframe_rows": len(df),
"dataframe_columns": len(df.columns),
"component": "ClientBuilder",
"method": "api_to_dataframe"
},
level=20 # INFO level
)

return df

except Exception as e:
# Record failure metric
telemetry.metrics().metric_increment(
name="dataframe.conversion.failure",
tags={"error_type": type(e).__name__}
)

# Log error
error_msg = f"Failed to convert API response to DataFrame: {str(e)}"
telemetry.logs().new_log(
msg=error_msg,
tags={
"error": str(e),
"error_type": type(e).__name__,
"component": "ClientBuilder",
"method": "api_to_dataframe"
},
level=40 # ERROR level
)

# Re-raise the exception
raise
finally:
span.end()
return GetData.to_dataframe(response)
Loading