Merged in #115 (fcc4edf). Verified by execution (the POST retry rule) plus reading (the call sites) against main on 2026-09-13 — confirmed live.
What is wrong
#115 correctly keys replay safety on the HTTP method: POST and PATCH are sent exactly once. But the SDK has POST-shaped reads — queries with a body — and no audit of its own call sites was done, so those lost all retry resilience.
Confirmed POST-shaped reads that now get exactly one attempt:
| call site |
endpoint |
shape |
oilpriceapi/resources/diesel.py:219 |
POST /v1/diesel-prices/stations |
query by {lat, lng, radius} — pure read |
oilpriceapi/async_resources.py:65 |
same, async |
pure read |
oilpriceapi/resources/webhooks.py:217 |
POST /v1/webhooks/{id}/test |
diagnostic |
oilpriceapi/resources/alerts.py:538 |
POST /v1/alerts/{id}/test |
diagnostic |
data_sources.rotate_credentials() and the various create() calls are genuine writes and correctly should not retry — no change wanted there.
Repro
import httpx, time
from oilpriceapi import OilPriceAPI
time.sleep = lambda s: None
FIXTURE_KEY = "fixture-not-a-real-credential"
calls = []
def handler(request):
calls.append(str(request.url))
return httpx.Response(503, json={"status": "error"})
c = OilPriceAPI(api_key=FIXTURE_KEY)
c._client = httpx.Client(base_url=c.base_url, headers=c.headers,
transport=httpx.MockTransport(handler))
try:
c.diesel.get_stations(lat=37.7749, lng=-122.4194)
except Exception as e:
print(type(e).__name__, "attempts:", len(calls))
Measured against the same shape via client.request("POST", ...): attempts = 1. A GET to the same transport gives attempts = 3.
Before #115 a transient 503 or connection reset on a station lookup was retried twice and usually succeeded. Now it fails on the first blip.
Why it matters
A station lookup is idempotent by construction — the same lat/lng/radius returns the same answer and creates nothing. Removing its retries is a pure availability regression with no safety benefit. Customer-visible as intermittent failures on a call that used to ride out a single bad gateway response.
Suggested fix
The mechanism #115 built is right; the call sites just need to use it. Pass idempotent=True at each read-shaped POST:
response = self.client.request(
method="POST",
path="/v1/diesel-prices/stations",
json_data={"lat": lat, "lng": lng, "radius": radius},
idempotent=True,
)
Worth adding a short note in CONTRIBUTING/the resource docstrings: any new POST that is a query must pass idempotent=True, and a test that walks the resource modules asserting every method="POST" call site either passes idempotent= explicitly or is on an allow-list of genuine writes, so the next POST-shaped read does not quietly lose its retries.
Merged in #115 (
fcc4edf). Verified by execution (the POST retry rule) plus reading (the call sites) againstmainon 2026-09-13 — confirmed live.What is wrong
#115 correctly keys replay safety on the HTTP method: POST and PATCH are sent exactly once. But the SDK has POST-shaped reads — queries with a body — and no audit of its own call sites was done, so those lost all retry resilience.
Confirmed POST-shaped reads that now get exactly one attempt:
oilpriceapi/resources/diesel.py:219POST /v1/diesel-prices/stations{lat, lng, radius}— pure readoilpriceapi/async_resources.py:65oilpriceapi/resources/webhooks.py:217POST /v1/webhooks/{id}/testoilpriceapi/resources/alerts.py:538POST /v1/alerts/{id}/testdata_sources.rotate_credentials()and the variouscreate()calls are genuine writes and correctly should not retry — no change wanted there.Repro
Measured against the same shape via
client.request("POST", ...): attempts = 1. A GET to the same transport gives attempts = 3.Before #115 a transient 503 or connection reset on a station lookup was retried twice and usually succeeded. Now it fails on the first blip.
Why it matters
A station lookup is idempotent by construction — the same lat/lng/radius returns the same answer and creates nothing. Removing its retries is a pure availability regression with no safety benefit. Customer-visible as intermittent failures on a call that used to ride out a single bad gateway response.
Suggested fix
The mechanism #115 built is right; the call sites just need to use it. Pass
idempotent=Trueat each read-shaped POST:Worth adding a short note in
CONTRIBUTING/the resource docstrings: any new POST that is a query must passidempotent=True, and a test that walks the resource modules asserting everymethod="POST"call site either passesidempotent=explicitly or is on an allow-list of genuine writes, so the next POST-shaped read does not quietly lose its retries.