chore: httpx retry transport + healthcheck stdlib + mypy config
- mcp_common/http.py: nuovo helper async_client() con AsyncHTTPTransport(retries=3) per gestire connection error transient + call_with_retry() generic async retry decorator. Sostituite 25 occorrenze httpx.AsyncClient(...) in deribit/hyperliquid/sentiment/ macro client. 5 nuovi test. - Dockerfile healthcheck: passato da python+httpx subprocess a stdlib urllib.request.urlopen() su tutti i 6 servizi MCP. Zero dipendenze esterne nel runtime check, timeout esplicito 3s, image leggermente più snella. - pyproject.toml: aggiunto [tool.mypy] python_version=3.13 con mypy_path multi-package + override ignore_missing_imports per i vendor SDK (pybit, alpaca, hyperliquid, pythonjsonlogger). mypy 1.20 in dev deps; ruff pinned 0.5.x. mcp_common passa mypy clean; 44 errori tipo pre-esistenti nei servizi affiorati ma non bloccanti — fix da pianificare separatamente. - 455 test verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import xml.etree.ElementTree as ET
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from mcp_common.http import async_client
|
||||
|
||||
CRYPTOPANIC_URL = "https://cryptopanic.com/api/v1/posts/"
|
||||
ALTERNATIVE_ME_URL = "https://api.alternative.me/fng/"
|
||||
@@ -18,7 +19,7 @@ MESSARI_NEWS_URL = "https://data.messari.io/api/v1/news"
|
||||
async def _fetch_coindesk_headlines(limit: int = 20) -> list[dict[str, Any]]:
|
||||
items: list[dict[str, Any]] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
|
||||
async with async_client(timeout=10.0, follow_redirects=True) as client:
|
||||
resp = await client.get(COINDESK_RSS)
|
||||
if resp.status_code != 200:
|
||||
return items
|
||||
@@ -54,7 +55,7 @@ async def _fetch_cryptocompare_news(limit: int = 20) -> list[dict[str, Any]]:
|
||||
"""CER-017: CryptoCompare news free (no key needed)."""
|
||||
items: list[dict[str, Any]] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
resp = await client.get(CRYPTOCOMPARE_NEWS_URL, params={"lang": "EN"})
|
||||
if resp.status_code != 200:
|
||||
return items
|
||||
@@ -82,7 +83,7 @@ async def _fetch_messari_news(limit: int = 20) -> list[dict[str, Any]]:
|
||||
"""CER-017: Messari news free (no key needed for basic feed)."""
|
||||
items: list[dict[str, Any]] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
resp = await client.get(MESSARI_NEWS_URL)
|
||||
if resp.status_code != 200:
|
||||
return items
|
||||
@@ -164,7 +165,7 @@ async def fetch_crypto_news(api_key: str = "", limit: int = 20) -> dict[str, Any
|
||||
async def _fetch_cryptopanic_news(api_key: str, limit: int) -> list[dict[str, Any]]:
|
||||
"""Cryptopanic as one of the sources. Failure → []."""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
resp = await client.get(
|
||||
CRYPTOPANIC_URL,
|
||||
params={"auth_token": api_key, "public": "true"},
|
||||
@@ -189,7 +190,7 @@ async def _fetch_cryptopanic_news(api_key: str, limit: int) -> list[dict[str, An
|
||||
async def _fetch_lunarcrush(symbol: str, api_key: str) -> dict | None:
|
||||
"""CER-P2-005: LunarCrush v4 social metrics. Ritorna None se fail."""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
resp = await client.get(
|
||||
LUNARCRUSH_COIN_URL.format(symbol=symbol.upper()),
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
@@ -220,7 +221,7 @@ async def fetch_social_sentiment(symbol: str = "BTC") -> dict[str, Any]:
|
||||
Altrimenti deriva proxy da fear_greed per popolare twitter/reddit sentiment
|
||||
(marcato come derived=True così l'agent sa che è proxy).
|
||||
"""
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
fng_resp = await client.get(ALTERNATIVE_ME_URL, params={"limit": 1})
|
||||
fng_data = fng_resp.json()
|
||||
fng_list = fng_data.get("data", [])
|
||||
@@ -275,7 +276,7 @@ async def fetch_funding_rates(asset: str = "BTC") -> dict[str, Any]:
|
||||
okx_inst = f"{asset}-USDT-SWAP"
|
||||
rates: list[dict[str, Any]] = []
|
||||
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
# Binance
|
||||
try:
|
||||
resp = await client.get(BINANCE_FUNDING_URL, params={"symbol": usdt_symbol})
|
||||
@@ -341,7 +342,7 @@ async def fetch_cross_exchange_funding(assets: list[str] | None = None) -> dict[
|
||||
|
||||
assets = [a.upper() for a in (assets or ["BTC", "ETH", "SOL"])]
|
||||
snapshot: dict[str, dict[str, Any]] = {}
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
for asset in assets:
|
||||
rates: dict[str, float | None] = {
|
||||
"binance": None,
|
||||
@@ -530,7 +531,7 @@ async def fetch_cointegration_pairs(
|
||||
interval = "1h"
|
||||
limit = max(50, lookback_hours)
|
||||
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
async with async_client(timeout=15.0) as client:
|
||||
for pair in pairs:
|
||||
if len(pair) != 2:
|
||||
continue
|
||||
@@ -574,7 +575,7 @@ async def fetch_world_news() -> dict[str, Any]:
|
||||
"""Fetch world financial news from free RSS feeds."""
|
||||
articles: list[dict[str, Any]] = []
|
||||
|
||||
async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
|
||||
async with async_client(timeout=10.0, follow_redirects=True) as client:
|
||||
for source_name, url in WORLD_NEWS_FEEDS:
|
||||
try:
|
||||
resp = await client.get(url)
|
||||
@@ -614,7 +615,7 @@ async def fetch_oi_history(asset: str = "BTC", period: str = "5m", limit: int =
|
||||
limit = max(1, min(int(limit), 500))
|
||||
points: list[dict[str, Any]] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
async with async_client(timeout=10.0) as client:
|
||||
resp = await client.get(
|
||||
BINANCE_OI_HIST_URL,
|
||||
params={"symbol": symbol, "period": period, "limit": limit},
|
||||
|
||||
Reference in New Issue
Block a user