Skip to content

Commit 7a4efd5

Browse files
karlwaldmanclaude
andcommitted
fix(types): annotate the shared _to_price helper for mypy
CI's mypy step failed on the new helper with 8 errors across both clients: 'Missing type parameters for generic type dict' and incompatible argument types for commodity/value/timestamp. The errors are real but not new behaviour — the previous Price(**mapped_data) form passed a dict of kwargs, which mypy cannot check, so the same looseness was always there and simply invisible. Writing the call out explicitly exposed it. Typed the parameter as Dict[str, Any] and cast the three fields pydantic validates anyway, with a comment saying why the casts are there. Not silenced with type: ignore — the shape is genuinely Any coming off JSON, and pydantic raises on anything actually wrong. mypy on both files: 0 errors. Full suite still 645 passed. Live smoke re-run after the change: 5 codes -> 1 request, 5 prices, BRENT_CRUDE_USD $93.60 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKAExynd9zoKwt6rYA66EA
1 parent af0f6a4 commit 7a4efd5

2 files changed

Lines changed: 23 additions & 13 deletions

File tree

‎oilpriceapi/async_client.py‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import asyncio
1010
import logging
1111
import os
12-
from typing import Any, AsyncGenerator, Dict, List, Optional, Union
12+
from datetime import datetime
13+
from typing import Any, AsyncGenerator, Dict, List, Optional, Union, cast
1314
from urllib.parse import urljoin
1415

1516
import httpx
@@ -375,14 +376,18 @@ def __init__(self, client: AsyncOilPriceAPI):
375376
self.client = client
376377

377378
@staticmethod
378-
def _to_price(price_data: dict, fallback_code: Optional[str] = None) -> Price:
379-
"""Map one API price row onto the Price model."""
379+
def _to_price(price_data: Dict[str, Any], fallback_code: Optional[str] = None) -> Price:
380+
"""Map one API price row onto the Price model.
381+
382+
Casts are for mypy; pydantic does the real validation. See
383+
PricesResource._to_price.
384+
"""
380385
return Price(
381-
commodity=price_data.get("code", fallback_code),
382-
value=price_data.get("price"),
386+
commodity=cast(str, price_data.get("code", fallback_code)),
387+
value=cast(float, price_data.get("price")),
383388
currency=price_data.get("currency", "USD"),
384-
unit=price_data.get("unit", "barrel"),
385-
timestamp=price_data.get("created_at"),
389+
unit=cast(str, price_data.get("unit", "barrel")),
390+
timestamp=cast(datetime, price_data.get("created_at")),
386391
)
387392

388393
async def _fetch_batch(self, codes: List[str]) -> List[Price]:

‎oilpriceapi/resources/prices.py‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from __future__ import annotations
88

99
from datetime import datetime
10-
from typing import List, Optional, Set, Tuple, Union
10+
from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast
1111

1212
from .._pagination import validate_page_size
1313
from ..models import Price
@@ -27,19 +27,24 @@ def __init__(self, client):
2727
self.client = client
2828

2929
@staticmethod
30-
def _to_price(price_data: dict, fallback_code: Optional[str] = None) -> Price:
30+
def _to_price(price_data: Dict[str, Any], fallback_code: Optional[str] = None) -> Price:
3131
"""Map one API price row onto the Price model.
3232
3333
Shared by get() and the batched path so the two cannot drift.
34+
35+
Values are passed to a pydantic model, which does the coercion and
36+
raises on anything genuinely wrong. The casts here are for mypy: the
37+
JSON payload is Dict[str, Any], and the previous Price(**mapped_data)
38+
form simply hid that from the type checker.
3439
"""
3540
return Price(
36-
commodity=price_data.get("code", fallback_code),
37-
value=price_data.get("price"),
41+
commodity=cast(str, price_data.get("code", fallback_code)),
42+
value=cast(float, price_data.get("price")),
3843
currency=price_data.get("currency"),
3944
# Retain the established oil-only fallback for legacy minimal
4045
# responses; any unit actually supplied by the API wins.
41-
unit=price_data.get("unit", "barrel"),
42-
timestamp=price_data.get("created_at"),
46+
unit=cast(str, price_data.get("unit", "barrel")),
47+
timestamp=cast(datetime, price_data.get("created_at")),
4348
)
4449

4550
def _fetch_batch(self, codes: List[str]) -> List[Price]:

0 commit comments

Comments
 (0)