openbb-oilpriceapi 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OilPriceAPI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: openbb-oilpriceapi
3
+ Version: 0.1.0
4
+ Summary: OpenBB provider for OilPriceAPI - Real-time oil and commodity prices (WTI, Brent, Urals, Natural Gas, Coal)
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: openbb,oil,commodity,api,energy,prices,wti,brent,urals
8
+ Author: OilPriceAPI
9
+ Author-email: support@oilpriceapi.com
10
+ Requires-Python: >=3.10,<3.14
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Dist: httpx (>=0.27.0,<0.28.0)
18
+ Requires-Dist: openbb-core (>=1.5.1,<2.0.0)
19
+ Requires-Dist: tenacity (>=8.2.0,<9.0.0)
20
+ Project-URL: Repository, https://github.com/OilpriceAPI/openbb-oilpriceapi
21
+ Description-Content-Type: text/markdown
22
+
23
+ # openbb-oilpriceapi
24
+
25
+ [![PyPI version](https://badge.fury.io/py/openbb-oilpriceapi.svg)](https://badge.fury.io/py/openbb-oilpriceapi)
26
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
27
+
28
+ OpenBB provider for [OilPriceAPI](https://oilpriceapi.com) - Real-time oil and commodity prices.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install openbb-oilpriceapi
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ```python
39
+ from openbb import obb
40
+
41
+ # Configure your API key (get free key at https://oilpriceapi.com/signup)
42
+ obb.user.credentials.oilpriceapi_api_key = "your_api_key"
43
+
44
+ # Get all commodity prices
45
+ prices = obb.commodity.oil.price(provider="oilpriceapi")
46
+ df = prices.to_dataframe()
47
+ print(df)
48
+
49
+ # Get specific commodity (WTI crude)
50
+ wti = obb.commodity.oil.price(symbol="WTI", provider="oilpriceapi")
51
+ print(f"WTI Price: ${wti.results[0].price}/barrel")
52
+ ```
53
+
54
+ ## Supported Commodities
55
+
56
+ | Symbol | Name | Description |
57
+ |--------|------|-------------|
58
+ | `WTI` | WTI Crude Oil | West Texas Intermediate benchmark |
59
+ | `BRENT` | Brent Crude Oil | North Sea benchmark |
60
+ | `URALS` | Urals Crude Oil | Russian export blend |
61
+ | `DUBAI` | Dubai Crude Oil | Middle East benchmark |
62
+ | `NG` | Natural Gas (US) | Henry Hub |
63
+ | `NG_EU` | Natural Gas (EU) | TTF |
64
+ | `NG_UK` | Natural Gas (UK) | NBP |
65
+ | `COAL` | Coal | Thermal coal |
66
+ | `DIESEL_US` | US Diesel | National average |
67
+ | `GASOLINE_US` | US Gasoline | National average |
68
+
69
+ ## Configuration
70
+
71
+ ### Set Credentials
72
+
73
+ You can set your API key in multiple ways:
74
+
75
+ **Option 1: In Python**
76
+ ```python
77
+ obb.user.credentials.oilpriceapi_api_key = "your_key"
78
+ ```
79
+
80
+ **Option 2: Via settings file**
81
+ Add to `~/.openbb_platform/user_settings.json`:
82
+ ```json
83
+ {
84
+ "credentials": {
85
+ "oilpriceapi_api_key": "your_key"
86
+ }
87
+ }
88
+ ```
89
+
90
+ **Option 3: Environment variable**
91
+ ```bash
92
+ export OPENBB_OILPRICEAPI_API_KEY="your_key"
93
+ ```
94
+
95
+ ## Examples
96
+
97
+ ### Get Price History
98
+
99
+ ```python
100
+ # Get WTI prices for the past week
101
+ history = obb.commodity.oil.historical(
102
+ symbol="WTI",
103
+ period="past_week",
104
+ provider="oilpriceapi"
105
+ )
106
+ df = history.to_dataframe()
107
+ ```
108
+
109
+ ### Compare Commodities
110
+
111
+ ```python
112
+ import pandas as pd
113
+
114
+ # Get all prices and compare
115
+ prices = obb.commodity.oil.price(provider="oilpriceapi")
116
+ df = prices.to_dataframe()
117
+
118
+ # Filter for crude oils
119
+ crude = df[df['symbol'].isin(['WTI', 'BRENT', 'URALS'])]
120
+ print(crude[['symbol', 'price', 'change_percent']])
121
+ ```
122
+
123
+ ### Visualize Prices
124
+
125
+ ```python
126
+ import matplotlib.pyplot as plt
127
+
128
+ history = obb.commodity.oil.historical(
129
+ symbol="BRENT",
130
+ period="past_month",
131
+ provider="oilpriceapi"
132
+ )
133
+ df = history.to_dataframe()
134
+
135
+ plt.figure(figsize=(10, 6))
136
+ plt.plot(df['date'], df['price'])
137
+ plt.title('Brent Crude - Past Month')
138
+ plt.ylabel('Price (USD/barrel)')
139
+ plt.show()
140
+ ```
141
+
142
+ ## API Reference
143
+
144
+ ### OilPrice
145
+
146
+ Fetch latest commodity prices.
147
+
148
+ **Parameters:**
149
+ - `symbol` (str, optional): Commodity symbol. If not provided, returns all commodities.
150
+ - `provider` (str): Must be "oilpriceapi"
151
+
152
+ **Returns:**
153
+ - `symbol`: Commodity symbol
154
+ - `name`: Commodity name
155
+ - `price`: Current price
156
+ - `currency`: Price currency
157
+ - `unit`: Unit of measurement
158
+ - `updated_at`: Last update timestamp
159
+ - `change`: Price change (absolute)
160
+ - `change_percent`: Price change (percentage)
161
+
162
+ ### OilHistorical
163
+
164
+ Fetch historical price data.
165
+
166
+ **Parameters:**
167
+ - `symbol` (str, required): Commodity symbol
168
+ - `period` (str): Historical period - `past_day` (24h hourly), `past_week` (7d daily), `past_month` (30d daily). Default: `past_week`
169
+ - `provider` (str): Must be "oilpriceapi"
170
+
171
+ **Returns:**
172
+ - `date`: Price timestamp
173
+ - `symbol`: Commodity symbol
174
+ - `price`: Price at timestamp
175
+ - `currency`: Price currency
176
+ - `unit`: Unit of measurement
177
+
178
+ ## Development
179
+
180
+ ```bash
181
+ # Clone the repository
182
+ git clone https://github.com/OilpriceAPI/openbb-oilpriceapi.git
183
+ cd openbb-oilpriceapi
184
+
185
+ # Install dependencies
186
+ poetry install
187
+
188
+ # Run tests
189
+ poetry run pytest
190
+
191
+ # Run tests with coverage
192
+ poetry run pytest --cov=openbb_oilpriceapi
193
+ ```
194
+
195
+ ## Links
196
+
197
+ - [OilPriceAPI Website](https://oilpriceapi.com)
198
+ - [OilPriceAPI Documentation](https://docs.oilpriceapi.com)
199
+ - [OpenBB Platform](https://openbb.co)
200
+ - [GitHub Repository](https://github.com/OilpriceAPI/openbb-oilpriceapi)
201
+
202
+ ## License
203
+
204
+ MIT License - see [LICENSE](LICENSE) for details.
205
+
@@ -0,0 +1,182 @@
1
+ # openbb-oilpriceapi
2
+
3
+ [![PyPI version](https://badge.fury.io/py/openbb-oilpriceapi.svg)](https://badge.fury.io/py/openbb-oilpriceapi)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ OpenBB provider for [OilPriceAPI](https://oilpriceapi.com) - Real-time oil and commodity prices.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install openbb-oilpriceapi
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```python
17
+ from openbb import obb
18
+
19
+ # Configure your API key (get free key at https://oilpriceapi.com/signup)
20
+ obb.user.credentials.oilpriceapi_api_key = "your_api_key"
21
+
22
+ # Get all commodity prices
23
+ prices = obb.commodity.oil.price(provider="oilpriceapi")
24
+ df = prices.to_dataframe()
25
+ print(df)
26
+
27
+ # Get specific commodity (WTI crude)
28
+ wti = obb.commodity.oil.price(symbol="WTI", provider="oilpriceapi")
29
+ print(f"WTI Price: ${wti.results[0].price}/barrel")
30
+ ```
31
+
32
+ ## Supported Commodities
33
+
34
+ | Symbol | Name | Description |
35
+ |--------|------|-------------|
36
+ | `WTI` | WTI Crude Oil | West Texas Intermediate benchmark |
37
+ | `BRENT` | Brent Crude Oil | North Sea benchmark |
38
+ | `URALS` | Urals Crude Oil | Russian export blend |
39
+ | `DUBAI` | Dubai Crude Oil | Middle East benchmark |
40
+ | `NG` | Natural Gas (US) | Henry Hub |
41
+ | `NG_EU` | Natural Gas (EU) | TTF |
42
+ | `NG_UK` | Natural Gas (UK) | NBP |
43
+ | `COAL` | Coal | Thermal coal |
44
+ | `DIESEL_US` | US Diesel | National average |
45
+ | `GASOLINE_US` | US Gasoline | National average |
46
+
47
+ ## Configuration
48
+
49
+ ### Set Credentials
50
+
51
+ You can set your API key in multiple ways:
52
+
53
+ **Option 1: In Python**
54
+ ```python
55
+ obb.user.credentials.oilpriceapi_api_key = "your_key"
56
+ ```
57
+
58
+ **Option 2: Via settings file**
59
+ Add to `~/.openbb_platform/user_settings.json`:
60
+ ```json
61
+ {
62
+ "credentials": {
63
+ "oilpriceapi_api_key": "your_key"
64
+ }
65
+ }
66
+ ```
67
+
68
+ **Option 3: Environment variable**
69
+ ```bash
70
+ export OPENBB_OILPRICEAPI_API_KEY="your_key"
71
+ ```
72
+
73
+ ## Examples
74
+
75
+ ### Get Price History
76
+
77
+ ```python
78
+ # Get WTI prices for the past week
79
+ history = obb.commodity.oil.historical(
80
+ symbol="WTI",
81
+ period="past_week",
82
+ provider="oilpriceapi"
83
+ )
84
+ df = history.to_dataframe()
85
+ ```
86
+
87
+ ### Compare Commodities
88
+
89
+ ```python
90
+ import pandas as pd
91
+
92
+ # Get all prices and compare
93
+ prices = obb.commodity.oil.price(provider="oilpriceapi")
94
+ df = prices.to_dataframe()
95
+
96
+ # Filter for crude oils
97
+ crude = df[df['symbol'].isin(['WTI', 'BRENT', 'URALS'])]
98
+ print(crude[['symbol', 'price', 'change_percent']])
99
+ ```
100
+
101
+ ### Visualize Prices
102
+
103
+ ```python
104
+ import matplotlib.pyplot as plt
105
+
106
+ history = obb.commodity.oil.historical(
107
+ symbol="BRENT",
108
+ period="past_month",
109
+ provider="oilpriceapi"
110
+ )
111
+ df = history.to_dataframe()
112
+
113
+ plt.figure(figsize=(10, 6))
114
+ plt.plot(df['date'], df['price'])
115
+ plt.title('Brent Crude - Past Month')
116
+ plt.ylabel('Price (USD/barrel)')
117
+ plt.show()
118
+ ```
119
+
120
+ ## API Reference
121
+
122
+ ### OilPrice
123
+
124
+ Fetch latest commodity prices.
125
+
126
+ **Parameters:**
127
+ - `symbol` (str, optional): Commodity symbol. If not provided, returns all commodities.
128
+ - `provider` (str): Must be "oilpriceapi"
129
+
130
+ **Returns:**
131
+ - `symbol`: Commodity symbol
132
+ - `name`: Commodity name
133
+ - `price`: Current price
134
+ - `currency`: Price currency
135
+ - `unit`: Unit of measurement
136
+ - `updated_at`: Last update timestamp
137
+ - `change`: Price change (absolute)
138
+ - `change_percent`: Price change (percentage)
139
+
140
+ ### OilHistorical
141
+
142
+ Fetch historical price data.
143
+
144
+ **Parameters:**
145
+ - `symbol` (str, required): Commodity symbol
146
+ - `period` (str): Historical period - `past_day` (24h hourly), `past_week` (7d daily), `past_month` (30d daily). Default: `past_week`
147
+ - `provider` (str): Must be "oilpriceapi"
148
+
149
+ **Returns:**
150
+ - `date`: Price timestamp
151
+ - `symbol`: Commodity symbol
152
+ - `price`: Price at timestamp
153
+ - `currency`: Price currency
154
+ - `unit`: Unit of measurement
155
+
156
+ ## Development
157
+
158
+ ```bash
159
+ # Clone the repository
160
+ git clone https://github.com/OilpriceAPI/openbb-oilpriceapi.git
161
+ cd openbb-oilpriceapi
162
+
163
+ # Install dependencies
164
+ poetry install
165
+
166
+ # Run tests
167
+ poetry run pytest
168
+
169
+ # Run tests with coverage
170
+ poetry run pytest --cov=openbb_oilpriceapi
171
+ ```
172
+
173
+ ## Links
174
+
175
+ - [OilPriceAPI Website](https://oilpriceapi.com)
176
+ - [OilPriceAPI Documentation](https://docs.oilpriceapi.com)
177
+ - [OpenBB Platform](https://openbb.co)
178
+ - [GitHub Repository](https://github.com/OilpriceAPI/openbb-oilpriceapi)
179
+
180
+ ## License
181
+
182
+ MIT License - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,55 @@
1
+ """OpenBB OilPriceAPI Provider Module.
2
+
3
+ This provider enables access to real-time oil and commodity prices
4
+ from OilPriceAPI through the OpenBB Platform.
5
+
6
+ Supported commodities:
7
+ - WTI Crude Oil (WTI)
8
+ - Brent Crude Oil (BRENT)
9
+ - Urals Crude Oil (URALS)
10
+ - Dubai Crude Oil (DUBAI)
11
+ - Natural Gas US (NG)
12
+ - Natural Gas EU (NG_EU)
13
+ - Natural Gas UK (NG_UK)
14
+ - Coal (COAL)
15
+ - Diesel US (DIESEL_US)
16
+ - Gasoline US (GASOLINE_US)
17
+
18
+ Usage:
19
+ from openbb import obb
20
+
21
+ # Configure credentials
22
+ obb.user.credentials.oilpriceapi_api_key = "your_api_key"
23
+
24
+ # Get all prices
25
+ prices = obb.commodity.oil.price(provider="oilpriceapi")
26
+
27
+ # Get specific commodity
28
+ wti = obb.commodity.oil.price(symbol="WTI", provider="oilpriceapi")
29
+ """
30
+
31
+ from openbb_core.provider.abstract.provider import Provider
32
+ from openbb_oilpriceapi.models.oil_price import OilPriceAPIFetcher
33
+ from openbb_oilpriceapi.models.oil_historical import OilHistoricalFetcher
34
+
35
+ oilpriceapi_provider = Provider(
36
+ name="oilpriceapi",
37
+ website="https://oilpriceapi.com",
38
+ description=(
39
+ "OilPriceAPI provides real-time and historical oil and commodity prices "
40
+ "including WTI, Brent, Urals crude oil, natural gas, coal, and diesel. "
41
+ "Get your free API key at https://oilpriceapi.com"
42
+ ),
43
+ credentials=["api_key"],
44
+ fetcher_dict={
45
+ "OilPrice": OilPriceAPIFetcher,
46
+ "OilHistorical": OilHistoricalFetcher,
47
+ },
48
+ repr_name="OilPriceAPI",
49
+ instructions=(
50
+ "Get your free API key at https://oilpriceapi.com/signup\n"
51
+ "Set credentials: obb.user.credentials.oilpriceapi_api_key = 'your_key'"
52
+ ),
53
+ )
54
+
55
+ __all__ = ["oilpriceapi_provider"]
@@ -0,0 +1,29 @@
1
+ """OilPriceAPI models."""
2
+
3
+ from openbb_oilpriceapi.models.oil_price import (
4
+ OilPriceAPIFetcher,
5
+ OilPriceAPIQueryParams,
6
+ OilPriceAPIData,
7
+ OilPriceAPIError,
8
+ AuthenticationError,
9
+ RateLimitError,
10
+ NotFoundError,
11
+ )
12
+ from openbb_oilpriceapi.models.oil_historical import (
13
+ OilHistoricalFetcher,
14
+ OilHistoricalQueryParams,
15
+ OilHistoricalData,
16
+ )
17
+
18
+ __all__ = [
19
+ "OilPriceAPIFetcher",
20
+ "OilPriceAPIQueryParams",
21
+ "OilPriceAPIData",
22
+ "OilPriceAPIError",
23
+ "AuthenticationError",
24
+ "RateLimitError",
25
+ "NotFoundError",
26
+ "OilHistoricalFetcher",
27
+ "OilHistoricalQueryParams",
28
+ "OilHistoricalData",
29
+ ]
@@ -0,0 +1,208 @@
1
+ """OilPriceAPI Historical Price model and fetcher."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Literal
5
+
6
+ import httpx
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.abstract.query_params import QueryParams
9
+ from openbb_core.provider.abstract.data import Data
10
+ from pydantic import Field, field_validator
11
+ from tenacity import (
12
+ retry,
13
+ stop_after_attempt,
14
+ wait_exponential,
15
+ retry_if_exception_type,
16
+ )
17
+
18
+ from openbb_oilpriceapi.utils.constants import (
19
+ SYMBOL_MAPPING,
20
+ AVAILABLE_SYMBOLS,
21
+ OILPRICEAPI_BASE_URL,
22
+ REVERSE_SYMBOL_MAPPING,
23
+ )
24
+ from openbb_oilpriceapi.models.oil_price import (
25
+ AuthenticationError,
26
+ RateLimitError,
27
+ NotFoundError,
28
+ )
29
+
30
+
31
+ # Supported historical periods
32
+ HISTORICAL_PERIODS = ["past_day", "past_week", "past_month"]
33
+
34
+
35
+ class OilHistoricalQueryParams(QueryParams):
36
+ """OilPriceAPI Historical Query Parameters.
37
+
38
+ Source: https://oilpriceapi.com
39
+ """
40
+
41
+ __json_schema_extra__ = {
42
+ "symbol": {
43
+ "multiple_items_allowed": False,
44
+ "choices": AVAILABLE_SYMBOLS,
45
+ },
46
+ "period": {
47
+ "multiple_items_allowed": False,
48
+ "choices": HISTORICAL_PERIODS,
49
+ },
50
+ }
51
+
52
+ symbol: str = Field(
53
+ description="The commodity symbol to fetch historical data for. "
54
+ f"Available symbols: {', '.join(AVAILABLE_SYMBOLS)}",
55
+ )
56
+ period: Literal["past_day", "past_week", "past_month"] = Field(
57
+ default="past_week",
58
+ description="Historical period: past_day (24h hourly), past_week (7d daily), past_month (30d daily).",
59
+ )
60
+
61
+ @field_validator("symbol", mode="before")
62
+ @classmethod
63
+ def validate_symbol(cls, v: str) -> str:
64
+ """Validate that symbol is in the list of available symbols."""
65
+ v_upper = v.upper()
66
+ if v_upper not in AVAILABLE_SYMBOLS:
67
+ raise ValueError(
68
+ f"Invalid symbol '{v}'. Available symbols: {', '.join(AVAILABLE_SYMBOLS)}"
69
+ )
70
+ return v_upper
71
+
72
+
73
+ class OilHistoricalData(Data):
74
+ """OilPriceAPI Historical Price Data Model."""
75
+
76
+ date: datetime = Field(description="The timestamp of the price data point.")
77
+ symbol: str = Field(description="The commodity symbol/code.")
78
+ price: float = Field(description="The price at this timestamp.")
79
+ currency: str = Field(default="USD", description="The price currency.")
80
+ unit: str = Field(default="barrel", description="The unit of measurement.")
81
+
82
+
83
+ class OilHistoricalFetcher(Fetcher[OilHistoricalQueryParams, list[OilHistoricalData]]):
84
+ """OilPriceAPI Historical Price Fetcher."""
85
+
86
+ require_credentials = True
87
+
88
+ @staticmethod
89
+ def transform_query(params: dict[str, Any]) -> OilHistoricalQueryParams:
90
+ """Transform the query parameters."""
91
+ return OilHistoricalQueryParams(**params)
92
+
93
+ @staticmethod
94
+ @retry(
95
+ stop=stop_after_attempt(3),
96
+ wait=wait_exponential(multiplier=1, min=1, max=10),
97
+ retry=retry_if_exception_type(RateLimitError),
98
+ reraise=True,
99
+ )
100
+ async def _fetch_with_retry(
101
+ client: httpx.AsyncClient,
102
+ url: str,
103
+ headers: dict[str, str],
104
+ ) -> dict[str, Any]:
105
+ """Fetch data with retry logic for rate limits."""
106
+ response = await client.get(url, headers=headers)
107
+
108
+ if response.status_code == 401:
109
+ raise AuthenticationError(
110
+ "Invalid API key. Check your OilPriceAPI credentials."
111
+ )
112
+ if response.status_code == 429:
113
+ raise RateLimitError(
114
+ "Rate limit exceeded. Retrying with exponential backoff..."
115
+ )
116
+ if response.status_code == 404:
117
+ raise NotFoundError("Commodity not found.")
118
+
119
+ response.raise_for_status()
120
+ return response.json()
121
+
122
+ @staticmethod
123
+ async def aextract_data(
124
+ query: OilHistoricalQueryParams,
125
+ credentials: dict[str, str] | None,
126
+ **kwargs: Any,
127
+ ) -> list[dict[str, Any]]:
128
+ """Extract historical data from OilPriceAPI."""
129
+ api_key = credentials.get("api_key") if credentials else None
130
+ if not api_key:
131
+ raise AuthenticationError(
132
+ "OilPriceAPI API key is required. "
133
+ "Get a free key at https://oilpriceapi.com"
134
+ )
135
+
136
+ headers = {
137
+ "Authorization": f"Token {api_key}",
138
+ "Accept": "application/json",
139
+ }
140
+
141
+ # Map OpenBB symbol to OilPriceAPI code
142
+ oilpriceapi_code = SYMBOL_MAPPING.get(query.symbol, query.symbol)
143
+ url = f"{OILPRICEAPI_BASE_URL}/prices/{query.period}?by_code={oilpriceapi_code}"
144
+
145
+ async with httpx.AsyncClient(timeout=30.0) as client:
146
+ try:
147
+ data = await OilHistoricalFetcher._fetch_with_retry(
148
+ client, url, headers
149
+ )
150
+ except RateLimitError:
151
+ raise RateLimitError(
152
+ "Rate limit exceeded after 3 retries. "
153
+ "Please wait before making more requests."
154
+ )
155
+
156
+ # Handle response structure
157
+ if "data" in data:
158
+ if "prices" in data["data"]:
159
+ return data["data"]["prices"]
160
+ elif isinstance(data["data"], list):
161
+ return data["data"]
162
+
163
+ return []
164
+
165
+ @staticmethod
166
+ def transform_data(
167
+ query: OilHistoricalQueryParams,
168
+ data: list[dict[str, Any]],
169
+ **kwargs: Any,
170
+ ) -> list[OilHistoricalData]:
171
+ """Transform API response to OilHistoricalData models."""
172
+ results = []
173
+
174
+ for item in data:
175
+ # Parse the timestamp
176
+ date_str = item.get("created_at") or item.get("date") or item.get("timestamp")
177
+ if date_str:
178
+ if isinstance(date_str, str):
179
+ date_str = date_str.replace("Z", "+00:00")
180
+ try:
181
+ date = datetime.fromisoformat(date_str)
182
+ except ValueError:
183
+ continue # Skip invalid dates
184
+ else:
185
+ date = date_str
186
+ else:
187
+ continue # Skip entries without dates
188
+
189
+ # Get symbol from query (historical data is for specific symbol)
190
+ symbol = query.symbol
191
+
192
+ # Clean up unit string
193
+ unit = item.get("unit", "").replace("per ", "").strip() or "barrel"
194
+
195
+ results.append(
196
+ OilHistoricalData(
197
+ date=date,
198
+ symbol=symbol,
199
+ price=float(item.get("price", 0)),
200
+ currency=item.get("currency", "USD"),
201
+ unit=unit,
202
+ )
203
+ )
204
+
205
+ # Sort by date ascending
206
+ results.sort(key=lambda x: x.date)
207
+
208
+ return results
@@ -0,0 +1,234 @@
1
+ """OilPriceAPI Oil Price model and fetcher."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ import httpx
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.abstract.query_params import QueryParams
9
+ from openbb_core.provider.abstract.data import Data
10
+ from pydantic import Field, field_validator
11
+ from tenacity import (
12
+ retry,
13
+ stop_after_attempt,
14
+ wait_exponential,
15
+ retry_if_exception_type,
16
+ )
17
+
18
+ from openbb_oilpriceapi.utils.constants import (
19
+ SYMBOL_MAPPING,
20
+ AVAILABLE_SYMBOLS,
21
+ OILPRICEAPI_BASE_URL,
22
+ REVERSE_SYMBOL_MAPPING,
23
+ )
24
+
25
+
26
+ class OilPriceAPIError(Exception):
27
+ """Base exception for OilPriceAPI errors."""
28
+
29
+ pass
30
+
31
+
32
+ class AuthenticationError(OilPriceAPIError):
33
+ """Raised when API key is invalid or missing."""
34
+
35
+ pass
36
+
37
+
38
+ class RateLimitError(OilPriceAPIError):
39
+ """Raised when rate limit is exceeded."""
40
+
41
+ pass
42
+
43
+
44
+ class NotFoundError(OilPriceAPIError):
45
+ """Raised when commodity is not found."""
46
+
47
+ pass
48
+
49
+
50
+ class OilPriceAPIQueryParams(QueryParams):
51
+ """OilPriceAPI Query Parameters.
52
+
53
+ Source: https://oilpriceapi.com
54
+ """
55
+
56
+ __json_schema_extra__ = {
57
+ "symbol": {
58
+ "multiple_items_allowed": False,
59
+ "choices": AVAILABLE_SYMBOLS,
60
+ }
61
+ }
62
+
63
+ symbol: str | None = Field(
64
+ default=None,
65
+ description="The commodity symbol to fetch. If None, returns all available commodities. "
66
+ f"Available symbols: {', '.join(AVAILABLE_SYMBOLS)}",
67
+ )
68
+
69
+ @field_validator("symbol", mode="before")
70
+ @classmethod
71
+ def validate_symbol(cls, v: str | None) -> str | None:
72
+ """Validate that symbol is in the list of available symbols."""
73
+ if v is None:
74
+ return v
75
+ v_upper = v.upper()
76
+ if v_upper not in AVAILABLE_SYMBOLS:
77
+ raise ValueError(
78
+ f"Invalid symbol '{v}'. Available symbols: {', '.join(AVAILABLE_SYMBOLS)}"
79
+ )
80
+ return v_upper
81
+
82
+
83
+ class OilPriceAPIData(Data):
84
+ """OilPriceAPI Oil Price Data Model."""
85
+
86
+ symbol: str = Field(description="The commodity symbol/code.")
87
+ name: str = Field(description="The commodity name.")
88
+ price: float = Field(description="The current price.")
89
+ currency: str = Field(description="The price currency (e.g., USD).")
90
+ unit: str = Field(description="The unit of measurement (e.g., barrel, therm).")
91
+ updated_at: datetime = Field(description="The timestamp of the last price update.")
92
+ change: float | None = Field(
93
+ default=None, description="The absolute price change."
94
+ )
95
+ change_percent: float | None = Field(
96
+ default=None, description="The percentage price change."
97
+ )
98
+
99
+
100
+ class OilPriceAPIFetcher(Fetcher[OilPriceAPIQueryParams, list[OilPriceAPIData]]):
101
+ """OilPriceAPI Oil Price Fetcher."""
102
+
103
+ require_credentials = True
104
+
105
+ @staticmethod
106
+ def transform_query(params: dict[str, Any]) -> OilPriceAPIQueryParams:
107
+ """Transform the query parameters."""
108
+ return OilPriceAPIQueryParams(**params)
109
+
110
+ @staticmethod
111
+ @retry(
112
+ stop=stop_after_attempt(3),
113
+ wait=wait_exponential(multiplier=1, min=1, max=10),
114
+ retry=retry_if_exception_type(RateLimitError),
115
+ reraise=True,
116
+ )
117
+ async def _fetch_with_retry(
118
+ client: httpx.AsyncClient,
119
+ url: str,
120
+ headers: dict[str, str],
121
+ ) -> dict[str, Any]:
122
+ """Fetch data with retry logic for rate limits."""
123
+ response = await client.get(url, headers=headers)
124
+
125
+ if response.status_code == 401:
126
+ raise AuthenticationError(
127
+ "Invalid API key. Check your OilPriceAPI credentials."
128
+ )
129
+ if response.status_code == 429:
130
+ raise RateLimitError(
131
+ "Rate limit exceeded. Retrying with exponential backoff..."
132
+ )
133
+ if response.status_code == 404:
134
+ raise NotFoundError("Commodity not found.")
135
+
136
+ response.raise_for_status()
137
+ return response.json()
138
+
139
+ @staticmethod
140
+ async def aextract_data(
141
+ query: OilPriceAPIQueryParams,
142
+ credentials: dict[str, str] | None,
143
+ **kwargs: Any,
144
+ ) -> list[dict[str, Any]]:
145
+ """Extract data from OilPriceAPI."""
146
+ api_key = credentials.get("api_key") if credentials else None
147
+ if not api_key:
148
+ raise AuthenticationError(
149
+ "OilPriceAPI API key is required. "
150
+ "Get a free key at https://oilpriceapi.com"
151
+ )
152
+
153
+ headers = {
154
+ "Authorization": f"Token {api_key}",
155
+ "Accept": "application/json",
156
+ }
157
+
158
+ async with httpx.AsyncClient(timeout=30.0) as client:
159
+ # Determine endpoint based on symbol
160
+ if query.symbol:
161
+ # Map OpenBB symbol to OilPriceAPI code
162
+ oilpriceapi_code = SYMBOL_MAPPING.get(query.symbol, query.symbol)
163
+ url = f"{OILPRICEAPI_BASE_URL}/prices/latest?by_code={oilpriceapi_code}"
164
+ else:
165
+ url = f"{OILPRICEAPI_BASE_URL}/prices/latest"
166
+
167
+ try:
168
+ data = await OilPriceAPIFetcher._fetch_with_retry(client, url, headers)
169
+ except RateLimitError:
170
+ # Re-raise with user-friendly message after retries exhausted
171
+ raise RateLimitError(
172
+ "Rate limit exceeded after 3 retries. "
173
+ "Please wait before making more requests."
174
+ )
175
+
176
+ # Handle different response structures
177
+ if "data" in data:
178
+ if "prices" in data["data"]:
179
+ # Multiple prices response
180
+ return data["data"]["prices"]
181
+ elif "price" in data["data"]:
182
+ # Single price response
183
+ return [data["data"]["price"]]
184
+ else:
185
+ return [data["data"]]
186
+
187
+ return []
188
+
189
+ @staticmethod
190
+ def transform_data(
191
+ query: OilPriceAPIQueryParams,
192
+ data: list[dict[str, Any]],
193
+ **kwargs: Any,
194
+ ) -> list[OilPriceAPIData]:
195
+ """Transform API response to OilPriceAPIData models."""
196
+ results = []
197
+
198
+ for item in data:
199
+ # Parse the timestamp
200
+ updated_at_str = item.get("created_at") or item.get("updated_at")
201
+ if updated_at_str:
202
+ if isinstance(updated_at_str, str):
203
+ # Handle ISO format with or without Z suffix
204
+ updated_at_str = updated_at_str.replace("Z", "+00:00")
205
+ try:
206
+ updated_at = datetime.fromisoformat(updated_at_str)
207
+ except ValueError:
208
+ updated_at = datetime.now()
209
+ else:
210
+ updated_at = updated_at_str
211
+ else:
212
+ updated_at = datetime.now()
213
+
214
+ # Get the symbol - use OpenBB symbol if available
215
+ raw_symbol = item.get("code", item.get("symbol", "UNKNOWN"))
216
+ symbol = REVERSE_SYMBOL_MAPPING.get(raw_symbol, raw_symbol)
217
+
218
+ # Clean up unit string
219
+ unit = item.get("unit", "").replace("per ", "").strip()
220
+
221
+ results.append(
222
+ OilPriceAPIData(
223
+ symbol=symbol,
224
+ name=item.get("name", ""),
225
+ price=float(item.get("price", 0)),
226
+ currency=item.get("currency", "USD"),
227
+ unit=unit or "barrel",
228
+ updated_at=updated_at,
229
+ change=item.get("change"),
230
+ change_percent=item.get("change_percent"),
231
+ )
232
+ )
233
+
234
+ return results
@@ -0,0 +1,9 @@
1
+ """OilPriceAPI utilities."""
2
+
3
+ from openbb_oilpriceapi.utils.constants import (
4
+ SYMBOL_MAPPING,
5
+ AVAILABLE_SYMBOLS,
6
+ OILPRICEAPI_BASE_URL,
7
+ )
8
+
9
+ __all__ = ["SYMBOL_MAPPING", "AVAILABLE_SYMBOLS", "OILPRICEAPI_BASE_URL"]
@@ -0,0 +1,44 @@
1
+ """OilPriceAPI constants and symbol mappings."""
2
+
3
+ from typing import Literal
4
+
5
+ # Base URL for OilPriceAPI
6
+ OILPRICEAPI_BASE_URL = "https://api.oilpriceapi.com/v1"
7
+
8
+ # Mapping from OpenBB symbols to OilPriceAPI commodity codes
9
+ SYMBOL_MAPPING: dict[str, str] = {
10
+ # Crude Oil
11
+ "WTI": "WTI_USD",
12
+ "BRENT": "BRENT_CRUDE_USD",
13
+ "URALS": "URALS_USD",
14
+ "DUBAI": "DUBAI_CRUDE_USD",
15
+ # Natural Gas
16
+ "NG": "NATURAL_GAS_USD",
17
+ "NG_EU": "NATURAL_GAS_EU",
18
+ "NG_UK": "NATURAL_GAS_GBP",
19
+ # Coal
20
+ "COAL": "COAL_USD",
21
+ # Diesel/Gasoline
22
+ "DIESEL_US": "DIESEL_US_NATIONAL",
23
+ "GASOLINE_US": "GASOLINE_US_NATIONAL",
24
+ }
25
+
26
+ # Reverse mapping for transforming API responses
27
+ REVERSE_SYMBOL_MAPPING: dict[str, str] = {v: k for k, v in SYMBOL_MAPPING.items()}
28
+
29
+ # List of available symbols for validation
30
+ AVAILABLE_SYMBOLS: list[str] = list(SYMBOL_MAPPING.keys())
31
+
32
+ # Type for symbol literals (for type hints)
33
+ SymbolType = Literal[
34
+ "WTI",
35
+ "BRENT",
36
+ "URALS",
37
+ "DUBAI",
38
+ "NG",
39
+ "NG_EU",
40
+ "NG_UK",
41
+ "COAL",
42
+ "DIESEL_US",
43
+ "GASOLINE_US",
44
+ ]
@@ -0,0 +1,36 @@
1
+ [tool.poetry]
2
+ name = "openbb-oilpriceapi"
3
+ version = "0.1.0"
4
+ description = "OpenBB provider for OilPriceAPI - Real-time oil and commodity prices (WTI, Brent, Urals, Natural Gas, Coal)"
5
+ authors = ["OilPriceAPI <support@oilpriceapi.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ packages = [{ include = "openbb_oilpriceapi" }]
9
+ repository = "https://github.com/OilpriceAPI/openbb-oilpriceapi"
10
+ keywords = ["openbb", "oil", "commodity", "api", "energy", "prices", "wti", "brent", "urals"]
11
+
12
+ [tool.poetry.dependencies]
13
+ python = ">=3.10,<3.14"
14
+ openbb-core = "^1.5.1"
15
+ httpx = "^0.27.0"
16
+ tenacity = "^8.2.0"
17
+
18
+ [tool.poetry.group.dev.dependencies]
19
+ pytest = "^8.0.0"
20
+ pytest-asyncio = "^0.23.0"
21
+ pytest-cov = "^4.1.0"
22
+ respx = "^0.21.0"
23
+
24
+ [build-system]
25
+ requires = ["poetry-core"]
26
+ build-backend = "poetry.core.masonry.api"
27
+
28
+ [tool.poetry.plugins."openbb_provider_extension"]
29
+ oilpriceapi = "openbb_oilpriceapi:oilpriceapi_provider"
30
+
31
+ [tool.pytest.ini_options]
32
+ asyncio_mode = "auto"
33
+ testpaths = ["tests"]
34
+
35
+ [tool.coverage.run]
36
+ source = ["openbb_oilpriceapi"]