All notable changes to the OilPriceAPI Python SDK will be documented in this file.
- Typed
client.spreadsandclient.indicatorsresources (#99), sync and async. They cover the server-calculated/v1/spreads/*routes:crack,crack_historical,crack_all,gasoil_crack,basis,basis_historical,basis_all,curve_structure,curve_structure_all,margin,margin_historical,margin_all,physical_premium,physical_premium_historicalandphysical_premium_all. They also cover the/v1/indicators/*routes:fuel_switching,fuel_switching_historical,price_context,storage_analytics,storage_analytics_all,annotations,annotations_batch,cftc_positioning,cftc_positioning_historicalandcftc_positioning_all.- Each method returns a pydantic model from the new
oilpriceapi.metrics_modelsmodule. The models are typed from production responses captured on 2026-09-13. - Timestamps parse to timezone-aware
datetimeand calendar dates todate. Units, full-precision values and nulls are kept exactly as sent. - A key the server always emits is required. A 200 that drops it, changes its
type, or breaks the envelope raises
OilPriceAPIError(code="MALFORMED_RESPONSE")with the raw body. It is never defaulted. - History responses expose the server-applied
periodand, for crack spreads, thecoverageactually returned. - Blank selectors, invalid dates,
start_dateafterend_date, and more than 20 codes forannotations_batchare refused before any request is sent, withValidationError(anOilPriceAPIError) carryingfield,valueandstatus_code=None. The API would otherwise return a default window, or silently annotate only the first 20 codes. /v1/indicators/congressional-tradesis deliberately not exposed. It has never returned data in production, so there is no response shape to type.
- Each method returns a pydantic model from the new
- Subscription lifecycle:
get,update,pause,resume(#100). Sync and async, againstGET/PATCH /v1/subscriptions/{id}andPOST /v1/subscriptions/{id}/pause|resume. Each returns a typedSubscriptionwith the server's timestamps and nulls as sent.update()sends only the fields you pass (name,codes,interval,deliver_webhook,status). Ids and update payloads are validated before any request is built; an invalid one raisesValidationErrorwithstatus_code=None,fieldnaming the argument, and nothing sent. Unknown ids raiseDataNotFoundError; a refused update (interval below the plan minimum, webhook delivery the plan lacks) raisesValidationErrorwith the server'sdetails.update,pauseandresumeare writes and are sent once, likecreate: after an ambiguous timeout or 5xx the error carriesambiguous_write=Trueandget()tells you whether the change landed. - Typed LTL and parcel fuel-surcharge clients (#101).
client.fuel_surchargeon bothOilPriceAPIandAsyncOilPriceAPIcovers all six/v1/fuel-surchargeroutes:list(),latest(carrier),history(carrier, page=, per_page=),parcel_list(),parcel_latest(carrier),parcel_latest_rate(carrier, service_level)andparcel_history(carrier, service_level, page=, per_page=). Responses areFuelSurchargeRate,FuelSurchargeHistoryPage(with the server'smeta) andParcelFuelSurchargeCarriermodels typed from production payloads captured on 2026-09-13:effective_dateis adate,retrieved_ata timezone-awaredatetime, andsource, nullabledoe_diesel_priceanddiesel_bandare kept as sent. A success body missing a field the API always sends raisesOilPriceAPIError(code="MALFORMED_RESPONSE")instead of defaulting it. Carrier slugs, service levels and pagination are validated before any request; out-of-rangepage/per_pageare refused because the API clamps them silently. - Fuel-surcharge 400/404 bodies populate
error.suggestions. Thecovered_carriersandavailable_service_levelslists the API returns with an unknown carrier or a missing service level are now surfaced the same way commodity suggestions are.
SubscriptionEventis typed from the event the API sends (#149). It declaredtype,code,payloadandcreated_at, whichGET /v1/subscriptions/eventshas never sent, so they readNoneon every real event. Meanwhileid,observed_at,snapshot,deltas,sourceandtool_namewere untyped extras. The model now declares:- required
id,seq,watch_id,observed_at(a timezone-awaredatetime),snapshotanddeltas; - optional
sourceandtool_name.snapshotmaps each code to the newSubscriptionEventSnapshot(price,currency, optionalchange_24h_pctandas_of).deltasmaps each code to the newSubscriptionEventDelta(price_change, optionalpct_change). An event missing a required field raisesOilPriceAPIError(code="MALFORMED_RESPONSE").
- required
error.codeis no longer set to a human sentence (#145). For fail envelopes,{"status": "fail", "data": {"error": ...}}, the SDK copieddata.errorintoerror.code/error.machine_codewhatever it held. Every fuel-surcharge 400/404 puts the sentence itself there, for example"Unknown carrier 'nope'. Covered carriers: ...", so code-based branching saw a different string on every request.data.erroris now the code only when it is a snake-case token: upper-snake likeVALIDATION_ERRORorINTERVAL_FLOOR, or lower-snake likeinvalid_codeorno_price_data. A sentence stays inerror.messageanderror.codeisNone. A canonical nestederrorobject still takes precedence, and API-key redaction is unchanged.subscriptions.list()andsubscriptions.events()no longer report a malformed success as "nothing there" (#142), sync and async. A 200 without adata.subscriptionslist returned[], and one withoutdata.events/data.cursorreturned an empty page withcursor=None. Fed back asevents(since=page.cursor), thatNonedroppedsince, and the API reads a missingsinceas0, so the poller replayed the account's whole event history. Both now raiseOilPriceAPIError(code="MALFORMED_RESPONSE")with the raw body when the collection is missing or mistyped, a record is invalid,cursoris not a non-negative integer,has_moreis not a boolean, or the cursor is behindsinceor behind an event in the page. A genuinely empty list or page is still an empty success, andpage.cursoris now always anint.subscriptions.events(since=...)refuses a cursor the API would read as0. The API parsessincewithto_i, so"abc",""and-1replay every event and1.5becomes1(verified against production on 2026-09-13). Anything but a non-negativeintorNonenow raisesValidationError(field="since", status_code=None)before a request is sent.subscriptions.create()no longer turns a malformed success into a half-built record. It fell back to treating the wholedataobject as the subscription whendata.subscriptionwas missing, and leaked a raw pydantic orTypeErrorwhen the record was null or a list. It now raisesOilPriceAPIError(code="MALFORMED_RESPONSE"), the same as the new lifecycle methods.subscriptions.delete()validates the id before sending. An id such as"abc/pause"or""previously produced a request to a different route; it now raisesValidationError(field="subscription_id", status_code=None).- A bad subscription
intervalis now an SDK refusal as well as aValueError.subscriptions.create(interval=...),normalize_intervalandbuild_create_bodyraise the newSubscriptionIntervalError, a subclass of bothValidationErrorandValueError(likeFuturesContractError), soexcept OilPriceAPIErrorcatches it and existingexcept ValueErrorcode keeps working. It carriesfield="interval", the rejectedvalue, andstatus_code=None. Subscription.codesis required. A record with nocodesused to default to[], reading as a watch on nothing; the API always sends it, so a missing value now fails validation instead of being invented.
-
SubscriptionEvent.type,.code,.payloadand.created_atare deprecated and will be removed in 2.0.0 (#149). The events API never sends any of them. They are no longer pydantic fields and do not appear inmodel_dump(). Each is now a property that emits aDeprecationWarningon access:typereturnsNoneand has no equivalent, because every event is an interval snapshot.codereturnsNone. An event covers every watched code; uselist(event.snapshot).payloadreturnsNone. Useevent.snapshotandevent.deltas.created_atreturnsobserved_at, the event's timestamp, where it used to returnNone.
Parsing, polling and serializing events emit no warning.
- Energy Intelligence collection methods now return the collection they
promise (#107). Every EI method typed
List[Dict[str, Any]]returnedresponse["data"]-- but the EI controllers put their records under a named key insidedata.client.ei.rig_counts.by_basin()returned{"report_date": ..., "basins": [...]}where the signature and the docstring example promised the basin list, so the documentedfor basin in basins: basin["count"]iterated dict keys. The same defect ran throughby_state(states),historical(records), OPECby_country/historical/top_producers, oil-inventoryby_product/historical, drilling-productivityduc_wells/by_basin/historical/trends, forecasthistorical, and every well-permit and frac-focus collection -- 27 methods, sync and async. Each now returns the named list, and a success body missing that list raisesOilPriceAPIError(code="MALFORMED_RESPONSE")instead of handing back the envelope. An empty collection is still an empty list. ei.well_permits.get()andei.frac_focus.get()return the record, not its wrapper. Production nests these underdata.well_permit/data.frac_focus_disclosure, so the documentedpermit["operator"]raisedKeyError.ei.forecasts.prices()andei.forecasts.production()are typedDict[str, Any]. Both return a mapping keyed by commodity / series code, never a list; theList[Dict[str, Any]]annotation was wrong from the start. No behaviour change.- Docstring examples across the EI resources now use the field names the API
actually returns (
region/count, not the inventedname/rig_count), verified live on 2026-09-13. - The ActionCable handshake is bounded, and a failed setup no longer leaks the
socket (#108).
open_timeoutwas passed to the WebSocket upgrade and nothing else: the waits forwelcomeandconfirm_subscriptionthat follow had no deadline at all, so a socket that upgraded and then went quiet hung the caller indefinitely, and cancelling out of that hang left the upgraded socket open. Connect,welcomeandconfirm_subscriptionare now one bounded setup lifecycle governed by a newsetup_timeout(defaulting toopen_timeout, so the timeout you already configure does cover protocol setup), and every socket the stream allocates is closed on any failure, timeout or cancellation -- including a__aenter__that raises, where__aexit__never runs. - A failed reconnect consumes the reconnect budget instead of escaping on the
first attempt (#108). An
OSErrorraised while reconnecting inside theConnectionClosedhandler propagated straight out of the iterator, so a stream configured withmax_reconnect_attempts=10gave up after one. Transient failures now spend the configured consecutive-attempt budget with backoff and end inConnectionError: Stream lost after N reconnect attempts; a permanent refusal stops immediately with the newStreamAuthError(aConnectionErrorsubclass, so existing handlers are unaffected) rather than retrying a rejected key ten times. close()retires the stream. It is idempotent, closes the socket under a bounded teardown timeout, and prevents any subsequent reconnect;connect()on a closed stream raises instead of quietly opening a new socket. Reconnects now close the socket they are replacing.
- The per-method
if "data" in response: return response["data"]repeated through every EI resource is replaced by one shared helper (oilpriceapi/resources/ei/_envelopes.py).unwrap_well_permit_search_responsekeeps its name and its error message and now delegates to it, so there is one unwrapping implementation rather than two.
Behaviour change for callers who adapted to the bug: code reading
by_basin()["basins"], well_permits.list()["well_permits"] or
well_permits.get(id)["well_permit"] must drop that subscript. Code following
the documented signature was broken before and works now. ei.well_permits.latest()
and ei.frac_focus.latest() deliberately keep returning the envelope object so
their pagination and freshness counters stay reachable.
- A price response that omits
codeno longer comes back wearing the code you asked for (#127)._to_pricefilled an absentcodewith the requested commodity, soprice.commodity == requested-- the one consumer-side check that catches being served a different instrument (#112,mcp-server#90) -- passed by construction on exactly the rows where it needed to fail. An absent code now reads as absent (""), matching whatprices.get_all()has always done. Sync and async, every price-read path. Behaviour change: code that readprice.commodityon a response withoutcodenow sees""instead of the requested code; use the code you passed in if you need it echoed back. timeout=0is honoured instead of silently becoming 30. The constructor usedtimeout or self.DEFAULT_TIMEOUT, so an explicit zero -- a real httpx timeout meaning "fail immediately", and whatfloat(os.getenv("OPA_TIMEOUT", "0"))produces -- was discarded. The caller got a 30-second timeout and a hang that is very hard to attribute back to the constructor. Same defect, and the same line, as theordefaults fixed in 1.13.x formax_retriesandretry_on. Both clients.base_url=""no longer points the client at production. It now raisesConfigurationError. Whatever an empty string meant, silently talking to the live API -- and pinning the request-origin guard to an origin the caller did not choose -- was the worst available answer.
max_retries=0is accepted again, with aDeprecationWarning, and means one attempt. It was briefly rejected withConfigurationErrorat client construction, which takes a process down at startup for a value that constructed fine in 1.13.0.max_retriescounts total attempts, not retries after the first, so0now resolves to1-- one attempt, no retries -- and the warning says so. It does not go back to silently meaning 3.0will be refused in the next major version; passmax_retries=1to say "no retries" explicitly.- An integral float
max_retries(3.0) is coerced with aDeprecationWarninginstead of raising. That is what a JSON or YAML config round-trip produces for an integer. - Still refused, because no coercion is obviously right: a negative
max_retries, a non-integral float (2.5), a string, andbool(Truewould silently mean one attempt).
timeoutis validated: a negative or non-numeric value raisesConfigurationErrorinstead of being passed down to httpx unvalidated.
Nothing that worked in 1.13.0 raises in 1.14.0. Two silent behaviours change:
timeout=0 now means zero rather than 30, and max_retries=0 now means one
attempt rather than three. If you were relying on either of those defaults,
pass the value you want explicitly.
get_multiple()now batches — up to 20 codes per request instead of one request per code. The REST API accepts 20 commodity codes in a single request that counts once against quota, so the method whose purpose is fetching several prices previously cost up to 20x more quota than writing the call by hand. Through it the free plan was 50 code-reads a day; through the raw API it is 1,000.- The same fix is applied to
AsyncPricesResource.get_multiple(), which was worse:asyncio.gatherfanned out one request per code concurrently, which could also trip the 60-per-60-second rate limit on a long list. Chunks are still gathered concurrently, so 25 codes cost 2 requests rather than 25.
- Your quota consumption will drop. This is in your favour and requires no
code change, but it is a behaviour change: a
get_multiple()call that previously consumed N requests now consumesceil(N / 20). - The per-code failure contract is unchanged. Because the API rejects the whole
request when any code in it is unknown, a failed batch is retried per code —
for that chunk only — so
return_failures=Truestill reports exactly which code was at fault.
- Polling examples now default to an interval that fits the free plan (30 minutes), with a plan/interval table and the measured update cadence of the underlying data. Nothing we publish moves faster than about every 2.5 minutes, so a shorter timer returns the same number.
- Validate every customer-readable member in the exact built source distribution, including root release/configuration files and future nested package data, while explicitly excluding intentional test/tooling fixtures.
- Remove the unsupported universal-entitlement wording from the packaged environment example and reject never-existent promises that attribution headers change entitlements in authored and distributed release notes.
- Removed a nonexistent request-limit bonus claim from sync and async usage-attribution header comments.
- Added red-first recursive authored and installed-wheel claim coverage so telemetry or application metadata cannot be presented as changing account entitlements.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Route Brent, WTI, gasoil, and EU carbon futures through the API's instrument-generic paths in sync and async clients. Existing venue-slug and contract-code inputs remain compatible and normalize to those same paths.
- Document a coverage-gated permit-to-production workflow and package discovery keywords for well permits, drilling data, and well production.
- Accept live well-permit filters without a legacy free-form query and unwrap
the production
{ well_permits, meta }search response in sync and async clients while retaining positional-query compatibility. - Require the PyPI publisher to verify the complete checksummed artifact set, share one package-version parser, scan both workflow filename extensions, and allow bounded public-index propagation before release completion.
- Reject common fixed request and API-call rate spellings, including prefix, suffix, and hyphenated daily/hourly/minute forms, and direct examples to the reviewed live product facts.
- Match hyphenated free-tier wording and universal catalog claims in every readable wheel surface, and replace the remaining packaged docstrings with current-account and runtime-response terminology.
- Remove stale fixed plan-price, monthly allowance, cadence, uptime, and generic real-time claims from documentation and packaged docstrings.
- Recursively validate authored docs and package source, then scan the exact installed wheel and PyPI metadata during the release smoke test.
- Read release metadata without importing the uninstalled source package, so the trusted publisher can validate the exact wheel from a build-only clean environment before PyPI upload.
- Add sync and async
client.commodities.search(...), backed by the current API catalog rather than a bundled commodity-code list. - Expose bounded, credential-redacted
suggestionsandinvalid_codesfrom nested invalid-code error responses.
- Date-bearing resources now reject malformed or impossible
YYYY-MM-DDstrings locally while leaving well-formed range semantics to the API. - Historical DataFrame helpers now accept a
per_pagevalue from 1 to 1000 and fetch all pages automatically. Theclient.prices.to_dataframe(...)convenience path forwards the same option for date-range queries.
- Stop retrying exhausted daily, monthly, and trial quota responses. Sync and async clients now make one request at a durable quota wall while preserving bounded retry behavior for recoverable hourly and ambiguous 429 responses.
- Replace the demo synthetic's fixed catalogue-size assertion with an integrity contract for the original core codes and every usable returned row. Request, transport, and operating-system failures now fail the monitor instead of being converted to skips.
- Preserve each API record's currency and unit in current and historical DataFrames instead of labeling a missing currency as USD.
- Remove exact duplicate records introduced by overlapping page boundaries, stop safely on empty pages with stale continuation metadata, and return a stable schema for empty historical DataFrames.
- Replaced the PyPI storefront with reviewed source-timestamped wording and removed unsupported fixed catalog, traffic, cadence, and entitlement claims.
- Made the canonical first-request snippet fail closed unless symbol, numeric value, currency, unit, source, and an exact API timestamp field are present.
- Added a storefront claim guard and corrected the history snippet's declared endpoint to match its executable request path.
- Well Production Resource (beta):
client.well_production(and async mirror) covering/v1/well-production*—summary(),states(),state(),well(),top_producers(),cycle_time(),cycle_time_cohorts(). Per-well data is beta and limited to states with collected regulatory data; endpoints are gated on the Drilling Intelligence feature (403ENTERPRISE_REQUIRED). Closes #50.
- Removed a committed API-key fallback from
tests/sdk_audit_test.py; the audit script now readsOILPRICEAPI_KEY/OILPRICEAPI_TEST_KEYfrom the environment only and skips cleanly when unset.
- Loosen
sourcetyping and align examples with the API's masked source labels: the responsesourcenow returnsmarket_reportingfor non-government series (government labels likeEIA/opec.orgare unchanged). Modelsourcefields remain a freestr(no venue enum); test fixtures no longer use venue names such asICE. See oilpriceapi-api#4175.
- docs: registry storefront README — hero, "What can you get?" commodity table, and cross-SDK toolbox table so the PyPI page matches the other OilPriceAPI SDKs. No code changes.
- Analysis Resource (Technical Indicators):
client.analysiswithwith_indicators(df, indicators=[...])DataFrame helper and direct methodssma(),ema(),rsi(),macd(),bollinger_bands(),atr(). Pure pandas/numpy implementation, no new dependencies. Closes #3.
- Commodities Resource:
client.commodities.list(),get(code),categories()for commodity catalog discovery - Futures Resource:
client.futures.latest(),historical(),ohlc(),intraday(),spreads(),curve(),continuous()for futures contract data - Storage Resource:
client.storage.all(),cushing(),spr(),regional(),history()for oil inventory levels - Rig Counts Resource:
client.rig_counts.latest(),current(),historical(),trends(),summary()for Baker Hughes rig count data - Bunker Fuels Resource:
client.bunker_fuels.all(),port(),compare(),spreads(),historical(),export()for marine fuel prices - Analytics Resource:
client.analytics.performance(),statistics(),correlation(),trend(),spread(),forecast()for price analytics - Forecasts Resource:
client.forecasts.monthly(),accuracy(),archive(),get()for EIA monthly price forecasts - Data Quality Resource:
client.data_quality.summary(),reports(),report()for data quality monitoring - Drilling Intelligence Resource:
client.drilling.latest(),summary(),trends(),frac_spreads(),well_permits(),duc_wells(),completions(),wells_drilled(),basin()for drilling activity data - Energy Intelligence Resource:
client.eiwith 7 sub-resources:rig_counts,oil_inventories,opec_production,drilling_productivity,forecasts,well_permits,frac_focusfor comprehensive EIA data - Webhooks Resource:
client.webhooks.create(),list(),get(),update(),delete(),test(),events()for webhook management - Data Sources Resource:
client.data_sources.list(),get(),create(),update(),delete(),test(),logs(),health(),rotate_credentials()for data connector management - Enhanced Alerts: Added
test(),triggers(),analytics_history()methods to existing alerts resource - Data Connector Support:
client.get_data_connector_prices()for BYOS (Bring Your Own Subscription) prices - Telemetry Headers:
app_urlandapp_nameparameters for API usage attribution
- Diesel validation: Empty string state codes now properly rejected with ValidationError
- 84 new unit tests added (222 total, 0 failures)
- Test coverage improved from ~40% to 60%
- New test files for all 13 resource modules
None - All new resources are additive. Existing code continues to work unchanged.
-
CRITICAL: Historical Data Returns Wrong Commodity: Fixed issue where all historical queries returned BRENT_CRUDE_USD regardless of requested commodity
- Root cause: SDK was sending
commodityparameter but API expectsby_codeparameter - Impact: ALL historical queries since v1.4.0 returned incorrect data
- Solution: Changed parameter name from
commoditytoby_codein historical resource - Reported by: Idan (idan@comity.ai)
- Root cause: SDK was sending
-
Date Range Parameters Ignored: Fixed issue where start_date and end_date parameters were completely ignored
- Root cause: API endpoints were hardcoded to return last week/month/year from current date
- Impact: Requesting specific date ranges (e.g., Jan 2024) would return current period instead
- Solution: API now respects start_date and end_date parameters across all historical endpoints
- This fix was applied to the backend API simultaneously
- Strict Commodity Validation: API now validates commodity codes and returns clear error messages for invalid codes
- Before: Silently accepted invalid codes like "oijfoijofwijewef" and returned BRENT data
- After: Returns 400 Bad Request with list of valid codes
- Error includes link to
/v1/prices/metricsfor full list of valid commodity codes
None - This is a critical bug fix. Existing code will work correctly after update.
CRITICAL - All users of client.historical.get() should upgrade immediately. Previous versions return completely wrong data.
- Historical Queries Timeout Issue: Fixed 100% timeout rate on historical data requests
- Root cause: SDK was using hardcoded
/v1/prices/past_yearendpoint for all date ranges - Solution: Implemented intelligent endpoint selection based on date range
- 1 day range →
/v1/prices/past_dayendpoint - 7 day range →
/v1/prices/past_weekendpoint - 30 day range →
/v1/prices/past_monthendpoint - 365 day range →
/v1/prices/past_yearendpoint
- 1 day range →
- Performance improvement: 7x faster for 1 week queries, 3x faster for 1 month queries
- Root cause: SDK was using hardcoded
- Dynamic Timeout Management: Automatic timeout adjustment based on query size
- 1 week queries: 30 seconds (previously 30s, but now uses optimal endpoint)
- 1 month queries: 60 seconds
- 1 year queries: 120 seconds (up from 30s - fixes timeout issue)
- Custom timeout override:
historical.get(..., timeout=180)for very large queries
- Per-Request Timeout Override: Added
timeoutparameter toclient.request()method- Allows fine-grained timeout control for specific requests
- Historical resource automatically uses appropriate timeouts
- 1 week historical queries: 67s → ~10s (7x faster via
/past_weekendpoint) - 1 month historical queries: 67s → ~20s (3x faster via
/past_monthendpoint) - 1 year historical queries: Timeout (30s) → Success (67-85s with 120s timeout)
- Added 9 new tests for endpoint selection and timeout handling
- All 20 existing tests pass with new changes
- Test coverage for
historical.py: 88.68% (up from ~54%)
- Updated
historical.get()docstring with timeout parameter examples - Added clear examples for custom timeout usage
None - This is a backwards-compatible bug fix. Existing code will continue to work and will automatically benefit from performance improvements.
- Price Alerts: New
client.alertsresource for automated price monitoring - Alert CRUD Operations: Complete create, read, update, delete operations
- Webhook Notifications: HTTPS webhook support for alert triggers
- Alert Operators: 5 comparison operators (greater_than, less_than, equals, greater_than_or_equal, less_than_or_equal)
- Cooldown Periods: Rate limiting for alert triggers (0-1440 minutes)
- Webhook Testing: Test webhook endpoints before creating alerts
- DataFrame Support:
alerts.to_dataframe()- Convert alerts to pandas DataFrames - New Pydantic models:
PriceAlert- Alert configuration and statusWebhookTestResponse- Webhook test results
- Comprehensive Validation: Input validation for all alert parameters
- Type Safety: Full Pydantic models with datetime handling
- Error Handling: Specific ValidationError exceptions with field details
- Pandas Integration: Built-in DataFrame conversion for analysis
- Documentation: Complete docstrings with examples
Now supports 12 endpoints (up from 7):
GET /v1/prices/latest- Get latest commodity pricesGET /v1/prices- Get historical commodity pricesGET /v1/commodities- Get all commodities metadataGET /v1/commodities/categories- Get commodity categoriesGET /v1/commodities/{code}- Get specific commodity detailsGET /v1/diesel-prices- Get state average diesel pricesPOST /v1/diesel-prices/stations- Get nearby diesel stationsGET /v1/alerts- List all price alerts (NEW)GET /v1/alerts/{id}- Get specific alert (NEW)POST /v1/alerts- Create price alert (NEW)PATCH /v1/alerts/{id}- Update price alert (NEW)DELETE /v1/alerts/{id}- Delete price alert (NEW)
- Added comprehensive test suite for alerts resource (22 test cases)
- Tests cover all CRUD operations, validation, webhook testing, and DataFrame operations
- 82% coverage of alerts functionality
None - This is a backwards-compatible feature addition.
from oilpriceapi import OilPriceAPI
client = OilPriceAPI()
# Create a price alert
alert = client.alerts.create(
name="Brent High Alert",
commodity_code="BRENT_CRUDE_USD",
condition_operator="greater_than",
condition_value=85.00,
webhook_url="https://your-server.com/webhook",
cooldown_minutes=60
)
# List all alerts
alerts = client.alerts.list()
for alert in alerts:
print(f"{alert.name}: {alert.trigger_count} triggers")
# Update alert
client.alerts.update(alert.id, condition_value=90.00)
# Test webhook
test_result = client.alerts.test_webhook("https://your-server.com/webhook")
print(f"Webhook OK: {test_result.success}")
# Get as DataFrame
df = client.alerts.to_dataframe()- Diesel Prices Support: New
client.dieselresource for diesel price data - State Average Diesel Prices:
diesel.get_price(state)- Get EIA state-level diesel averages (free tier) - Station-Level Diesel Pricing:
diesel.get_stations(lat, lng, radius)- Get nearby diesel stations with current prices from Google Maps (paid tiers) - Diesel DataFrame Support:
diesel.to_dataframe()- Convert diesel data to pandas DataFrames - New Pydantic models:
DieselPrice- State average diesel price dataDieselStation- Individual diesel station with pricingDieselStationsResponse- Response from stations endpointDieselRegionalAverage- Regional average for comparisonDieselSearchArea- Search area detailsDieselStationsMetadata- Query metadata
- Input Validation: Comprehensive validation for coordinates, state codes, and radius
- Error Handling: Specific errors for tier restrictions (403) and rate limits (429)
- Type Safety: Full Pydantic models for all diesel operations
- Pandas Integration: Built-in DataFrame conversion for analysis
- Documentation: Complete docstrings with examples
Now supports 7 endpoints (up from 5):
GET /v1/prices/latest- Get latest commodity pricesGET /v1/prices- Get historical commodity pricesGET /v1/commodities- Get all commodities metadataGET /v1/commodities/categories- Get commodity categoriesGET /v1/commodities/{code}- Get specific commodity detailsGET /v1/diesel-prices- Get state average diesel prices (NEW)POST /v1/diesel-prices/stations- Get nearby diesel stations (NEW)
- Added comprehensive test suite for diesel resource (18 test cases)
- Tests cover input validation, error handling, and DataFrame operations
- 100% coverage of diesel functionality
None - This is a backwards-compatible feature addition.
from oilpriceapi import OilPriceAPI
client = OilPriceAPI()
# State average (free tier)
ca_price = client.diesel.get_price("CA")
print(f"California diesel: ${ca_price.price:.2f}/gallon")
# Nearby stations (paid tiers)
result = client.diesel.get_stations(lat=37.7749, lng=-122.4194)
cheapest = min(result.stations, key=lambda s: s.diesel_price)
print(f"Cheapest: {cheapest.name} at {cheapest.formatted_price}")
# DataFrame analysis
df = client.diesel.to_dataframe(states=["CA", "TX", "NY", "FL"])
print(df[["state", "price", "updated_at"]])- 🎉 Initial release of OilPriceAPI Python SDK
- ✅ Synchronous client (
OilPriceAPI) - ✅ Asynchronous client (
AsyncOilPriceAPI) - ✅ Type-safe models with Pydantic
- ✅ Current price operations (
client.prices.get()) - ✅ Historical data operations (
client.historical.get()) - ✅ Pandas DataFrame integration (
to_dataframe()) - ✅ Visualization module with Tufte-style charts
- ✅ Automatic retry logic with exponential backoff
- ✅ Rate limit handling
- ✅ Comprehensive error handling
- ✅ Context manager support (
withstatements) - ✅ Environment variable configuration
- ✅ Full type hints for IDE autocomplete
- ✅ Documentation and examples
- Current Prices: Get latest commodity prices
- Historical Data: Fetch past prices with flexible date ranges
- Multi-commodity: Support for Brent, WTI, Natural Gas, and more
- Pagination: Automatic handling of large datasets
- Data Export: Convert to pandas DataFrames for analysis
- Async Support: High-performance async/await operations
- Visualization: Built-in charting with matplotlib
- Type Safety: Full Pydantic validation
- Environment variable-based API key management
- No hardcoded credentials
- HTTPS-only communication
- Safe error messages that don't leak secrets
- Comprehensive README with examples
- API reference documentation
- Security policy (SECURITY.md)
- Contributing guidelines (CONTRIBUTING.md)
- Example scripts and notebooks
- Python 3.8+
- Python 3.9
- Python 3.10
- Python 3.11
- Python 3.12
# From PyPI
pip install --upgrade oilpriceapi
# From source
pip install -e ".[dev]"None - this is the initial release.
None.
N/A for initial release.