| |
| """ |
| Hugging Face Unified Client |
| ================================== |
| تمام درخواستها از طریق این کلاینت به Hugging Face Space ارسال میشوند. |
| هیچ درخواست مستقیمی به API های خارجی ارسال نمیشود. |
| |
| ✅ تمام دادهها از Hugging Face |
| ✅ بدون WebSocket (فقط HTTP) |
| ✅ Cache و Retry مکانیزم |
| ✅ Error Handling |
| |
| References: crypto_resources_unified_2025-11-11.json |
| """ |
|
|
| import httpx |
| import asyncio |
| import logging |
| from typing import Dict, Any, List, Optional |
| from datetime import datetime, timedelta |
| import os |
| import hashlib |
| import json |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class HuggingFaceUnifiedClient: |
| """ |
| کلاینت یکپارچه برای تمام درخواستهای به Hugging Face Space |
| |
| این کلاینت **تنها** منبع دریافت داده است و به جای API های دیگر، |
| تمام دادهها را از Hugging Face Space دریافت میکند. |
| """ |
|
|
| def __init__(self): |
| """Initialize HuggingFace client with config""" |
| self.base_url = os.getenv( |
| "HF_SPACE_BASE_URL", |
| "https://really-amin-datasourceforcryptocurrency.hf.space" |
| ) |
| self.api_token = os.getenv("HF_API_TOKEN", "") |
| self.timeout = httpx.Timeout(30.0, connect=10.0) |
|
|
| |
| self.headers = { |
| "Content-Type": "application/json", |
| "User-Agent": "CryptoDataHub/1.0" |
| } |
|
|
| |
| if self.api_token: |
| self.headers["Authorization"] = f"Bearer {self.api_token}" |
|
|
| |
| self.cache = {} |
| self.cache_ttl = { |
| "market": 30, |
| "ohlcv": 60, |
| "news": 300, |
| "sentiment": 0, |
| "blockchain": 60, |
| } |
|
|
| logger.info(f"🚀 HuggingFace Unified Client initialized") |
| logger.info(f" Base URL: {self.base_url}") |
| logger.info(f" Auth: {'✅ Token configured' if self.api_token else '❌ No token'}") |
|
|
| def _get_cache_key(self, endpoint: str, params: Dict = None) -> str: |
| """Generate cache key from endpoint and params""" |
| cache_str = f"{endpoint}:{json.dumps(params or {}, sort_keys=True)}" |
| return hashlib.md5(cache_str.encode()).hexdigest() |
|
|
| def _get_cached(self, cache_key: str, cache_type: str) -> Optional[Dict]: |
| """Get data from cache if available and not expired""" |
| if cache_key not in self.cache: |
| return None |
|
|
| cached_data, cached_time = self.cache[cache_key] |
| ttl = self.cache_ttl.get(cache_type, 0) |
|
|
| if ttl == 0: |
| |
| return None |
|
|
| age = (datetime.now() - cached_time).total_seconds() |
| if age < ttl: |
| logger.info(f"📦 Cache HIT: {cache_key} (age: {age:.1f}s)") |
| return cached_data |
| else: |
| |
| logger.info(f"⏰ Cache EXPIRED: {cache_key} (age: {age:.1f}s, ttl: {ttl}s)") |
| del self.cache[cache_key] |
| return None |
|
|
| def _set_cache(self, cache_key: str, data: Dict, cache_type: str): |
| """Store data in cache""" |
| ttl = self.cache_ttl.get(cache_type, 0) |
| if ttl > 0: |
| self.cache[cache_key] = (data, datetime.now()) |
| logger.info(f"💾 Cache SET: {cache_key} (ttl: {ttl}s)") |
|
|
| async def _request( |
| self, |
| method: str, |
| endpoint: str, |
| params: Optional[Dict] = None, |
| json_body: Optional[Dict] = None, |
| cache_type: Optional[str] = None, |
| retry: int = 3 |
| ) -> Dict[str, Any]: |
| """ |
| Make HTTP request to HuggingFace Space |
| |
| Args: |
| method: HTTP method (GET, POST, etc.) |
| endpoint: API endpoint (e.g., "/api/market") |
| params: Query parameters |
| json_body: JSON body for POST requests |
| cache_type: Type of cache ("market", "ohlcv", etc.) |
| retry: Number of retry attempts |
| |
| Returns: |
| Response data as dict |
| """ |
| |
| if method.upper() == "GET" and cache_type: |
| cache_key = self._get_cache_key(endpoint, params) |
| cached = self._get_cached(cache_key, cache_type) |
| if cached: |
| return cached |
|
|
| |
| url = f"{self.base_url}{endpoint}" |
|
|
| |
| last_error = None |
| for attempt in range(retry): |
| try: |
| async with httpx.AsyncClient(timeout=self.timeout) as client: |
| if method.upper() == "GET": |
| response = await client.get(url, headers=self.headers, params=params) |
| elif method.upper() == "POST": |
| response = await client.post(url, headers=self.headers, json=json_body) |
| else: |
| raise ValueError(f"Unsupported HTTP method: {method}") |
|
|
| |
| response.raise_for_status() |
|
|
| |
| data = response.json() |
|
|
| |
| if method.upper() == "GET" and cache_type: |
| cache_key = self._get_cache_key(endpoint, params) |
| self._set_cache(cache_key, data, cache_type) |
|
|
| logger.info(f"✅ HF Request: {method} {endpoint} (attempt {attempt + 1}/{retry})") |
| return data |
|
|
| except httpx.HTTPStatusError as e: |
| last_error = e |
| logger.warning(f"❌ HF Request failed (attempt {attempt + 1}/{retry}): {e.response.status_code} - {e.response.text}") |
| if attempt < retry - 1: |
| await asyncio.sleep(1 * (attempt + 1)) |
| except Exception as e: |
| last_error = e |
| logger.error(f"❌ HF Request error (attempt {attempt + 1}/{retry}): {e}") |
| if attempt < retry - 1: |
| await asyncio.sleep(1 * (attempt + 1)) |
|
|
| |
| raise Exception(f"HuggingFace API request failed after {retry} attempts: {last_error}") |
|
|
| |
| |
| |
|
|
| async def get_market_prices( |
| self, |
| symbols: Optional[List[str]] = None, |
| limit: int = 100 |
| ) -> Dict[str, Any]: |
| """ |
| دریافت قیمتهای بازار از HuggingFace |
| |
| Endpoint: GET /api/market |
| |
| Args: |
| symbols: لیست سمبلها (مثلاً ['BTC', 'ETH']) |
| limit: تعداد نتایج |
| |
| Returns: |
| { |
| "success": True, |
| "data": [ |
| { |
| "symbol": "BTC", |
| "price": 50000.0, |
| "market_cap": 1000000000.0, |
| "volume_24h": 50000000.0, |
| "change_24h": 2.5, |
| "last_updated": 1234567890000 |
| }, |
| ... |
| ], |
| "source": "hf_engine", |
| "timestamp": 1234567890000, |
| "cached": False |
| } |
| """ |
| params = {"limit": limit} |
| if symbols: |
| params["symbols"] = ",".join(symbols) |
|
|
| return await self._request( |
| "GET", |
| "/api/market", |
| params=params, |
| cache_type="market" |
| ) |
|
|
| async def get_market_history( |
| self, |
| symbol: str, |
| timeframe: str = "1h", |
| limit: int = 1000 |
| ) -> Dict[str, Any]: |
| """ |
| دریافت دادههای تاریخی OHLCV از HuggingFace |
| |
| Endpoint: GET /api/market/history |
| |
| Args: |
| symbol: سمبل (مثلاً "BTCUSDT") |
| timeframe: بازه زمانی ("1m", "5m", "15m", "1h", "4h", "1d") |
| limit: تعداد کندلها |
| |
| Returns: |
| { |
| "success": True, |
| "data": [ |
| { |
| "timestamp": 1234567890000, |
| "open": 50000.0, |
| "high": 51000.0, |
| "low": 49500.0, |
| "close": 50500.0, |
| "volume": 1000000.0 |
| }, |
| ... |
| ], |
| "source": "hf_engine", |
| "timestamp": 1234567890000 |
| } |
| """ |
| params = { |
| "symbol": symbol, |
| "timeframe": timeframe, |
| "limit": limit |
| } |
|
|
| return await self._request( |
| "GET", |
| "/api/market/history", |
| params=params, |
| cache_type="ohlcv" |
| ) |
|
|
| |
| |
| |
|
|
| async def analyze_sentiment(self, text: str) -> Dict[str, Any]: |
| """ |
| تحلیل احساسات متن با مدلهای AI در HuggingFace |
| |
| Endpoint: POST /api/sentiment/analyze |
| |
| Args: |
| text: متن برای تحلیل |
| |
| Returns: |
| { |
| "success": True, |
| "data": { |
| "label": "positive", |
| "score": 0.95, |
| "sentiment": "positive", |
| "confidence": 0.95, |
| "text": "Bitcoin is...", |
| "timestamp": 1234567890000 |
| }, |
| "source": "hf_engine", |
| "timestamp": 1234567890000 |
| } |
| """ |
| json_body = {"text": text} |
|
|
| return await self._request( |
| "POST", |
| "/api/sentiment/analyze", |
| json_body=json_body, |
| cache_type=None |
| ) |
|
|
| |
| |
| |
|
|
| async def get_news( |
| self, |
| limit: int = 20, |
| source: Optional[str] = None |
| ) -> Dict[str, Any]: |
| """ |
| دریافت اخبار رمز ارز از HuggingFace |
| |
| Endpoint: GET /api/news |
| |
| Args: |
| limit: تعداد خبر |
| source: منبع خبر (اختیاری) |
| |
| Returns: |
| { |
| "articles": [ |
| { |
| "id": "123", |
| "title": "Bitcoin reaches new high", |
| "url": "https://...", |
| "source": "CoinDesk", |
| "published_at": "2025-01-01T00:00:00" |
| }, |
| ... |
| ], |
| "meta": { |
| "cache_ttl_seconds": 300, |
| "source": "hf" |
| } |
| } |
| """ |
| params = {"limit": limit} |
| if source: |
| params["source"] = source |
|
|
| return await self._request( |
| "GET", |
| "/api/news", |
| params=params, |
| cache_type="news" |
| ) |
|
|
| |
| |
| |
|
|
| async def get_blockchain_gas_prices(self, chain: str = "ethereum") -> Dict[str, Any]: |
| """ |
| دریافت قیمت گس از HuggingFace |
| |
| Endpoint: GET /api/crypto/blockchain/gas |
| |
| Args: |
| chain: نام بلاکچین (ethereum, bsc, polygon, etc.) |
| |
| Returns: |
| { |
| "chain": "ethereum", |
| "gas_prices": { |
| "fast": 50.0, |
| "standard": 30.0, |
| "slow": 20.0, |
| "unit": "gwei" |
| }, |
| "timestamp": "2025-01-01T00:00:00", |
| "meta": {...} |
| } |
| """ |
| params = {"chain": chain} |
|
|
| return await self._request( |
| "GET", |
| "/api/crypto/blockchain/gas", |
| params=params, |
| cache_type="blockchain" |
| ) |
|
|
| async def get_blockchain_stats( |
| self, |
| chain: str = "ethereum", |
| hours: int = 24 |
| ) -> Dict[str, Any]: |
| """ |
| دریافت آمار بلاکچین از HuggingFace |
| |
| Endpoint: GET /api/crypto/blockchain/stats |
| |
| Args: |
| chain: نام بلاکچین |
| hours: بازه زمانی (ساعت) |
| |
| Returns: |
| { |
| "chain": "ethereum", |
| "blocks_24h": 7000, |
| "transactions_24h": 1200000, |
| "avg_gas_price": 25.0, |
| "mempool_size": 100000, |
| "meta": {...} |
| } |
| """ |
| params = {"chain": chain, "hours": hours} |
|
|
| return await self._request( |
| "GET", |
| "/api/crypto/blockchain/stats", |
| params=params, |
| cache_type="blockchain" |
| ) |
|
|
| |
| |
| |
|
|
| async def get_whale_transactions( |
| self, |
| limit: int = 50, |
| chain: Optional[str] = None, |
| min_amount_usd: float = 100000 |
| ) -> Dict[str, Any]: |
| """ |
| دریافت تراکنشهای نهنگها از HuggingFace |
| |
| Endpoint: GET /api/crypto/whales/transactions |
| """ |
| params = { |
| "limit": limit, |
| "min_amount_usd": min_amount_usd |
| } |
| if chain: |
| params["chain"] = chain |
|
|
| return await self._request( |
| "GET", |
| "/api/crypto/whales/transactions", |
| params=params, |
| cache_type="market" |
| ) |
|
|
| async def get_whale_stats(self, hours: int = 24) -> Dict[str, Any]: |
| """ |
| دریافت آمار نهنگها از HuggingFace |
| |
| Endpoint: GET /api/crypto/whales/stats |
| """ |
| params = {"hours": hours} |
|
|
| return await self._request( |
| "GET", |
| "/api/crypto/whales/stats", |
| params=params, |
| cache_type="market" |
| ) |
|
|
| |
| |
| |
|
|
| async def health_check(self) -> Dict[str, Any]: |
| """ |
| بررسی سلامت HuggingFace Space |
| |
| Endpoint: GET /api/health |
| |
| Returns: |
| { |
| "success": True, |
| "status": "healthy", |
| "timestamp": 1234567890000, |
| "version": "1.0.0", |
| "database": "connected", |
| "cache": { |
| "market_data_count": 100, |
| "ohlc_count": 5000 |
| }, |
| "ai_models": { |
| "loaded": 3, |
| "failed": 0, |
| "total": 3 |
| }, |
| "source": "hf_engine" |
| } |
| """ |
| return await self._request( |
| "GET", |
| "/api/health", |
| cache_type=None |
| ) |
|
|
| async def get_system_status(self) -> Dict[str, Any]: |
| """ |
| دریافت وضعیت کل سیستم |
| |
| Endpoint: GET /api/status |
| """ |
| return await self._request( |
| "GET", |
| "/api/status", |
| cache_type=None |
| ) |
|
|
|
|
| |
| _hf_client_instance = None |
|
|
|
|
| def get_hf_client() -> HuggingFaceUnifiedClient: |
| """Get singleton instance of HuggingFace Unified Client""" |
| global _hf_client_instance |
| if _hf_client_instance is None: |
| _hf_client_instance = HuggingFaceUnifiedClient() |
| return _hf_client_instance |
|
|