def __init__( self, api_key: str, base_url: str = "https://eodhd.com/api", timeout: float = 20.0, transport: Optional[httpx.BaseTransport] = None, ): self._api_key = api_key self._base_url = base_url.rstrip("/") self._client = httpx.Client(base_url=self._base_url, timeout=timeout, transport=transport) def _get(self, path: str, **params): params["api_token"] = self._api_key params.setdefault("fmt", "json") resp = self._client.get(path, params=params) if resp.status_code == 401: raise ProviderAuthError(f"EODHD auth failed for {path}") if resp.status_code == 429: raise ProviderRateLimitError(f"EODHD rate limit hit for {path}") if resp.status_code == 404: raise ProviderNotFoundError(f"EODHD 404 for {path}") resp.raise_for_status() return resp.json() def get_company_profile(self, ticker: str, exchange_mic: Optional[str] = None) -> ProviderCompanyProfile: code = f"{ticker}.{exchange_mic}" if exchange_mic else ticker data = self._get(f"/fundamentals/{code}") general = data.get("General", {}) return ProviderCompanyProfile( ticker=ticker, exchange_mic=general.get("Exchange"), legal_name=general.get("Name", ticker), display_name=general.get("Name", ticker), country_iso2=general.get("CountryISO"), sector=general.get("Sector"), industry=general.get("Industry"), currency=general.get("CurrencyCode", "USD"), isin=general.get("ISIN"), website=general.get("WebURL"), description=general.get("Description"), beta=None, ) def get_prices(self, ticker: str, start: date, end: date) -> list[ProviderPriceBar]: """AUDIT FIX (Part D — docs/AUDIT_VALIDATION_D.md). This used to raise `KeyError` on one malformed bar, discarding every well-formed bar in the same response, and to pass a non-numeric `close` straight through as a `str`. Both behaviours were pinned by `tests/test_eodhd_adapter.py` in Part A7 and deliberately left for this part, so the fix could be applied to FMP and EODHD together instead of making the two inconsistent. Now: each row is validated individually; a bad row is dropped with a recorded reason and the good rows are returned. `self.last_price_validation` holds the report.""" rows = self._get(f"/eod/{ticker}", period="d", **{"from": start.isoformat(), "to": end.isoformat()}) report = ValidationReport() bars = [] for r in rows: clean = validate_price_bar( { "date": r.get("date"), "open": r.get("open"), "high": r.get("high"), "low": r.get("low"), "close": r.get("close"), "adjusted_close": r.get("adjusted_close"), "volume": r.get("volume"), }, report, ) if clean is None: continue bars.append(ProviderPriceBar( date=_parse_date(clean["date"]), open=clean["open"], high=clean["high"], low=clean["low"], close=clean["close"], adjusted_close=clean["adjusted_close"], volume=clean["volume"], currency="USD", )) self.last_price_validation = report return bars def get_income_statements(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]: raise NotImplementedError( "EODHD statement field mapping not yet implemented — see module docstring; use FMP as " "primary or contribute the mapping once verified against a live EODHD response." ) def get_balance_sheets(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]: raise NotImplementedError("EODHD balance sheet mapping not yet implemented — see module docstring.") def get_cash_flows(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]: raise NotImplementedError("EODHD cash flow mapping not yet implemented — see module docstring.") def get_estimates(self, ticker: str) -> list[ProviderEstimateRow]: raise NotImplementedError("EODHD estimates mapping not yet implemented — see module docstring.") def get_dividends(self, ticker: str) -> list[ProviderDividendRow]: raise NotImplementedError("EODHD dividends mapping not yet implemented — see module docstring.")