fix(review): address PR review findings for CORE-003
Critical fixes: - Add math.isfinite() check to reject NaN/Infinity in _safe_quote_price - Raise TypeError instead of silent 0.0 fallback in price_feed.py - Use dict instead of Mapping for external data validation Type improvements: - Add PortfolioSnapshot TypedDict for type safety - Add DisplayMode and EntryBasisMode Literal types - Add explicit dict[str, Any] annotation in to_dict() - Remove cast() in favor of type comment validation
This commit is contained in:
@@ -7,7 +7,6 @@ import logging
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Mapping
|
||||
|
||||
import yfinance as yf
|
||||
|
||||
@@ -52,15 +51,15 @@ class PriceFeed:
|
||||
self._cache = get_cache()
|
||||
|
||||
@staticmethod
|
||||
def _required_payload_value(payload: Mapping[str, object], key: str, *, context: str) -> object:
|
||||
def _required_payload_value(payload: dict[str, object], key: str, *, context: str) -> object:
|
||||
if key not in payload:
|
||||
raise TypeError(f"{context} is missing required field: {key}")
|
||||
return payload[key]
|
||||
|
||||
@classmethod
|
||||
def _normalize_cached_price_payload(cls, payload: object, *, expected_symbol: str) -> PriceData:
|
||||
if not isinstance(payload, Mapping):
|
||||
raise TypeError("cached price payload must be an object")
|
||||
if not isinstance(payload, dict):
|
||||
raise TypeError("cached price payload must be a plain dict")
|
||||
payload_symbol = str(payload.get("symbol", expected_symbol)).strip().upper()
|
||||
normalized_symbol = expected_symbol.strip().upper()
|
||||
if payload_symbol != normalized_symbol:
|
||||
@@ -69,7 +68,9 @@ class PriceFeed:
|
||||
if not isinstance(timestamp, str) or not timestamp.strip():
|
||||
raise TypeError("cached timestamp must be a non-empty ISO string")
|
||||
price_val = cls._required_payload_value(payload, "price", context="cached price payload")
|
||||
price = float(price_val) if isinstance(price_val, (int, float)) else 0.0
|
||||
if not isinstance(price_val, (int, float)):
|
||||
raise TypeError(f"cached price must be numeric, got {type(price_val).__name__}")
|
||||
price = float(price_val)
|
||||
return PriceData(
|
||||
symbol=payload_symbol,
|
||||
price=price,
|
||||
@@ -80,8 +81,8 @@ class PriceFeed:
|
||||
|
||||
@classmethod
|
||||
def _normalize_provider_price_payload(cls, payload: object, *, expected_symbol: str) -> PriceData:
|
||||
if not isinstance(payload, Mapping):
|
||||
raise TypeError("provider price payload must be an object")
|
||||
if not isinstance(payload, dict):
|
||||
raise TypeError("provider price payload must be a plain dict")
|
||||
payload_symbol = str(payload.get("symbol", expected_symbol)).strip().upper()
|
||||
normalized_symbol = expected_symbol.strip().upper()
|
||||
if payload_symbol != normalized_symbol:
|
||||
@@ -90,7 +91,9 @@ class PriceFeed:
|
||||
if not isinstance(timestamp, datetime):
|
||||
raise TypeError("provider timestamp must be a datetime")
|
||||
price_val = cls._required_payload_value(payload, "price", context="provider price payload")
|
||||
price = float(price_val) if isinstance(price_val, (int, float)) else 0.0
|
||||
if not isinstance(price_val, (int, float)):
|
||||
raise TypeError(f"provider price must be numeric, got {type(price_val).__name__}")
|
||||
price = float(price_val)
|
||||
return PriceData(
|
||||
symbol=payload_symbol,
|
||||
price=price,
|
||||
|
||||
Reference in New Issue
Block a user