55"""
66from __future__ import annotations
77
8- import os
8+ import asyncio
9+ import json
910import logging
10- from typing import Optional , Dict , Any , Union , List , AsyncGenerator
11- import httpx
11+ import os
1212from datetime import datetime
13- import json
14- import asyncio
13+ from typing import Any , AsyncGenerator , Dict , List , Optional , Union
1514from urllib .parse import urljoin
1615
16+ import httpx
17+
1718logger = 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
3120from .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
4949class 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
355356class 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
442442class 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
0 commit comments