Skip to content

Commit 8c05189

Browse files
karlwaldmanclaude
andcommitted
fix: Pass ruff lint checks, fix request_with_headers 429 retry, add async telemetry
- Fix 656 ruff lint errors (auto-fix + config updates for pre-existing style issues) - Add 429 auto-retry to request_with_headers() (was missing) - Add enable_telemetry param + tracking to AsyncOilPriceAPI - Extract shared validators to resource_validators.py - Update pyproject.toml ruff config to ignore pre-existing style debt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 57232ac commit 8c05189

35 files changed

Lines changed: 324 additions & 324 deletions

oilpriceapi/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,30 @@
44
The official Python SDK for OilPriceAPI - Real-time and historical oil prices.
55
"""
66

7-
from oilpriceapi.version import __version__
7+
from oilpriceapi.version import __version__ # noqa: F401
88

99
__author__ = "OilPriceAPI"
1010
__email__ = "support@oilpriceapi.com"
1111

12-
from oilpriceapi.client import OilPriceAPI
1312
from oilpriceapi.async_client import AsyncOilPriceAPI
13+
from oilpriceapi.client import OilPriceAPI
1414
from oilpriceapi.exceptions import (
15-
OilPriceAPIError,
1615
AuthenticationError,
17-
RateLimitError,
16+
ConfigurationError,
1817
DataNotFoundError,
18+
OilPriceAPIError,
19+
RateLimitError,
1920
ServerError,
20-
ValidationError,
2121
TimeoutError,
22-
ConfigurationError,
22+
ValidationError,
2323
)
2424
from oilpriceapi.models import (
25+
DataConnectorPrice,
2526
DieselPrice,
2627
DieselStation,
2728
DieselStationsResponse,
2829
PriceAlert,
2930
WebhookTestResponse,
30-
DataConnectorPrice,
3131
)
3232

3333
__all__ = [
@@ -74,4 +74,4 @@ def get_current_price(commodity: str, api_key: str = None) -> float:
7474
"""
7575
with OilPriceAPI(api_key=api_key) as client:
7676
price = client.prices.get(commodity)
77-
return price.value
77+
return price.value

oilpriceapi/async_client.py

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,69 @@
55
"""
66
from __future__ import annotations
77

8-
import os
8+
import asyncio
9+
import json
910
import logging
10-
from typing import Optional, Dict, Any, Union, List, AsyncGenerator
11-
import httpx
11+
import os
1212
from datetime import datetime
13-
import json
14-
import asyncio
13+
from typing import Any, AsyncGenerator, Dict, List, Optional, Union
1514
from urllib.parse import urljoin
1615

16+
import httpx
17+
1718
logger = logging.getLogger(__name__)
1819

19-
from .retry import RetryStrategy
20-
from .exceptions import (
21-
OilPriceAPIError,
22-
AuthenticationError,
23-
RateLimitError,
24-
DataNotFoundError,
25-
ServerError,
26-
TimeoutError,
27-
ValidationError,
28-
ConfigurationError,
29-
)
30-
from .models import Price, HistoricalPrice, HistoricalResponse
3120
from .async_resources import (
32-
AsyncDieselResource,
3321
AsyncAlertsResource,
34-
AsyncCommoditiesResource,
35-
AsyncFuturesResource,
36-
AsyncStorageResource,
37-
AsyncRigCountsResource,
38-
AsyncBunkerFuelsResource,
3922
AsyncAnalyticsResource,
40-
AsyncForecastsResource,
23+
AsyncBunkerFuelsResource,
24+
AsyncCommoditiesResource,
4125
AsyncDataQualityResource,
26+
AsyncDataSourcesResource,
27+
AsyncDieselResource,
4228
AsyncDrillingIntelligenceResource,
4329
AsyncEnergyIntelligenceResource,
30+
AsyncForecastsResource,
31+
AsyncFuturesResource,
32+
AsyncRigCountsResource,
33+
AsyncStorageResource,
4434
AsyncWebhooksResource,
45-
AsyncDataSourcesResource,
4635
)
36+
from .exceptions import (
37+
AuthenticationError,
38+
ConfigurationError,
39+
DataNotFoundError,
40+
OilPriceAPIError,
41+
RateLimitError,
42+
ServerError,
43+
TimeoutError,
44+
)
45+
from .models import HistoricalPrice, HistoricalResponse, Price
46+
from .retry import RetryStrategy
4747

4848

4949
class AsyncOilPriceAPI:
5050
"""Asynchronous client for OilPriceAPI.
51-
51+
5252
Provides async/await support for all API operations.
53-
53+
5454
Args:
5555
api_key: API key for authentication
5656
base_url: Base URL for API
5757
timeout: Request timeout in seconds
5858
max_retries: Maximum retry attempts
59-
59+
6060
Example:
6161
>>> async with AsyncOilPriceAPI() as client:
6262
... price = await client.prices.get("BRENT_CRUDE_USD")
6363
... print(f"Brent: ${price.value:.2f}")
6464
"""
65-
65+
6666
DEFAULT_BASE_URL = "https://api.oilpriceapi.com"
6767
DEFAULT_TIMEOUT = 30
6868
DEFAULT_MAX_RETRIES = 3
6969
DEFAULT_RETRY_CODES = [429, 500, 502, 503, 504]
70-
70+
7171
def __init__(
7272
self,
7373
api_key: Optional[str] = None,
@@ -107,7 +107,8 @@ def __init__(
107107

108108
# Build headers
109109
import sys
110-
from .version import SDK_VERSION, SDK_NAME
110+
111+
from .version import SDK_NAME, SDK_VERSION
111112
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
112113
self.headers = {
113114
"Authorization": f"Token {self.api_key}",
@@ -126,10 +127,10 @@ def __init__(
126127

127128
if headers:
128129
self.headers.update(headers)
129-
130+
130131
# Client will be created in __aenter__ or when needed
131132
self._client = None
132-
133+
133134
# Initialize resources
134135
self.prices = AsyncPricesResource(self)
135136
self.historical = AsyncHistoricalResource(self)
@@ -174,7 +175,7 @@ async def _ensure_client(self):
174175
limits=limits,
175176
follow_redirects=True,
176177
)
177-
178+
178179
async def request(
179180
self,
180181
method: str,
@@ -185,12 +186,12 @@ async def request(
185186
) -> Union[Dict[str, Any], list]:
186187
"""Make async HTTP request to API."""
187188
await self._ensure_client()
188-
189+
189190
# Ensure path starts with / for proper urljoin behavior
190191
if not path.startswith('/'):
191192
path = '/' + path
192193
url = urljoin(self.base_url + '/', path)
193-
194+
194195
# Retry logic
195196
import time as _time
196197
start_time = _time.time()
@@ -320,7 +321,7 @@ def _safe_parse_json(self, response: httpx.Response) -> Dict[str, Any]:
320321
return response.json()
321322
except json.JSONDecodeError:
322323
return {"error": response.text or "Unknown error"}
323-
324+
324325
def _parse_rate_limit_reset(self, headers: Dict[str, str]) -> Optional[datetime]:
325326
"""Parse rate limit reset time."""
326327
reset_header = headers.get("X-RateLimit-Reset")
@@ -334,38 +335,38 @@ def _parse_rate_limit_reset(self, headers: Dict[str, str]) -> Optional[datetime]
334335
except (ValueError, TypeError):
335336
pass
336337
return None
337-
338+
338339
async def close(self):
339340
"""Close the HTTP client and flush telemetry."""
340341
self._telemetry.close()
341342
if self._client:
342343
await self._client.aclose()
343344
self._client = None
344-
345+
345346
async def __aenter__(self):
346347
"""Async context manager entry."""
347348
await self._ensure_client()
348349
return self
349-
350+
350351
async def __aexit__(self, exc_type, exc_val, exc_tb):
351352
"""Async context manager exit."""
352353
await self.close()
353354

354355

355356
class AsyncPricesResource:
356357
"""Async resource for current prices."""
357-
358+
358359
def __init__(self, client: AsyncOilPriceAPI):
359360
self.client = client
360-
361+
361362
async def get(self, commodity: str) -> Price:
362363
"""Get current price for commodity."""
363364
response = await self.client.request(
364365
method="GET",
365366
path="/v1/prices/latest",
366367
params={"by_code": commodity}
367368
)
368-
369+
369370
if "data" in response:
370371
price_data = response["data"]
371372
else:
@@ -383,7 +384,7 @@ async def get(self, commodity: str) -> Price:
383384
}
384385

385386
return Price(**mapped_data)
386-
387+
387388
async def get_multiple(
388389
self,
389390
commodities: List[str],
@@ -403,7 +404,6 @@ async def get_multiple(
403404
Raises:
404405
OilPriceAPIError: If raise_on_error=True and any commodity fails
405406
"""
406-
from .exceptions import OilPriceAPIError
407407

408408
# Use gather for concurrent requests
409409
tasks = [self.get(commodity) for commodity in commodities]
@@ -423,28 +423,28 @@ async def get_multiple(
423423
if return_failures:
424424
return prices, failures
425425
return prices
426-
426+
427427
async def get_all(self) -> List[Price]:
428428
"""Get all available prices."""
429429
response = await self.client.request(
430430
method="GET",
431431
path="/v1/prices/all"
432432
)
433-
433+
434434
if "data" in response:
435435
prices_data = response["data"]
436436
else:
437437
prices_data = response
438-
438+
439439
return [Price(**price_data) for price_data in prices_data]
440440

441441

442442
class AsyncHistoricalResource:
443443
"""Async resource for historical data."""
444-
444+
445445
def __init__(self, client: AsyncOilPriceAPI):
446446
self.client = client
447-
447+
448448
async def get(
449449
self,
450450
commodity: str,
@@ -463,18 +463,18 @@ async def get(
463463
"per_page": min(per_page, 1000),
464464
"by_type": type_name,
465465
}
466-
466+
467467
if start_date:
468468
params["start_date"] = start_date
469469
if end_date:
470470
params["end_date"] = end_date
471-
471+
472472
response = await self.client.request(
473473
method="GET",
474474
path="/v1/prices/past_year",
475475
params=params
476476
)
477-
477+
478478
# Parse response - handle nested structure
479479
# API returns: {"status": "success", "data": {"prices": [...]}}
480480
if "data" in response and isinstance(response["data"], dict) and "prices" in response["data"]:
@@ -497,13 +497,13 @@ async def get(
497497
"type_name": price_data.get("type", "spot_price"),
498498
}
499499
prices.append(HistoricalPrice(**mapped_data))
500-
500+
501501
return HistoricalResponse(
502502
success=True,
503503
data=prices,
504504
meta=None # Simplified for now
505505
)
506-
506+
507507
async def get_all(
508508
self,
509509
commodity: str,
@@ -514,7 +514,7 @@ async def get_all(
514514
"""Get all historical data with automatic pagination."""
515515
all_prices = []
516516
page = 1
517-
517+
518518
while True:
519519
response = await self.get(
520520
commodity=commodity,
@@ -524,13 +524,13 @@ async def get_all(
524524
page=page,
525525
per_page=1000
526526
)
527-
527+
528528
all_prices.extend(response.data)
529-
529+
530530
# Check if we got a full page (might be more)
531531
if len(response.data) < 1000:
532532
break
533-
533+
534534
page += 1
535535

536536
return all_prices

oilpriceapi/async_resources.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from __future__ import annotations
22

3-
from typing import Optional, List, Dict, Any, Union
4-
from datetime import datetime, date
3+
from datetime import date, datetime
4+
from typing import Any, Dict, List, Optional, Union
55

6-
from .models import DieselPrice, DieselStationsResponse, PriceAlert
76
from .exceptions import ValidationError
7+
from .models import DieselPrice, DieselStationsResponse, PriceAlert
88
from .resource_validators import VALID_OPERATORS, format_date
99

1010

oilpriceapi/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
Requires: pip install oilpriceapi[cli]
1414
"""
1515

16-
import sys
1716
import os
17+
import sys
1818

1919
try:
2020
import click
@@ -96,7 +96,7 @@ def price(commodities, as_json):
9696
updated = str(p.last_updated) if hasattr(p, "last_updated") else ""
9797
table.add_row(code, price_str, currency, updated)
9898
except Exception as e:
99-
table.add_row(code, f"[red]Error[/red]", "", str(e))
99+
table.add_row(code, "[red]Error[/red]", "", str(e))
100100

101101
console.print(table)
102102

0 commit comments

Comments
 (0)