File size: 10,357 Bytes
5f9e480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#!/usr/bin/env python3
"""
Crypto API Clean Client - Integration with HuggingFace Space
https://really-amin-crypto-api-clean-fixed.hf.space

Provides access to 281+ cryptocurrency resources across 12 categories:
- RPC Nodes (24)
- Block Explorers (33)
- Market Data APIs (33)
- News APIs (17)
- Sentiment APIs (14)
- On-chain Analytics APIs (14)
- Whale Tracking APIs (10)
- HuggingFace Resources (9)
- Free HTTP Endpoints (13)
- Local Backend Routes (106)
- CORS Proxies (7)
- Community Sentiment APIs (1)
"""

import httpx
import asyncio
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime, timezone
from functools import lru_cache

logger = logging.getLogger(__name__)

# Base URL for the Crypto API Clean HF Space
CRYPTO_API_CLEAN_BASE_URL = "https://really-amin-crypto-api-clean-fixed.hf.space"

# Cache for resources (they don't change frequently)
_resources_cache: Dict[str, Any] = {}
_cache_timestamp: Optional[datetime] = None
CACHE_TTL_SECONDS = 300  # 5 minutes


class CryptoAPICleanService:
    """
    Service for accessing Crypto API Clean HuggingFace Space
    Provides comprehensive cryptocurrency resource database access
    """
    
    def __init__(self, timeout: int = 15):
        self.base_url = CRYPTO_API_CLEAN_BASE_URL
        self.timeout = timeout
        self._client: Optional[httpx.AsyncClient] = None
    
    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create async HTTP client"""
        if self._client is None or self._client.is_closed:
            self._client = httpx.AsyncClient(timeout=self.timeout)
        return self._client
    
    async def close(self):
        """Close the HTTP client"""
        if self._client and not self._client.is_closed:
            await self._client.aclose()
    
    async def _request(self, endpoint: str, params: Dict = None) -> Dict[str, Any]:
        """
        Make async request to Crypto API Clean with proper error handling
        
        Returns standardized response format matching project patterns
        """
        provider = "CryptoAPIClean"
        start_time = datetime.now(timezone.utc)
        
        try:
            client = await self._get_client()
            url = f"{self.base_url}{endpoint}"
            
            response = await client.get(url, params=params)
            response.raise_for_status()
            
            data = response.json()
            response_time_ms = (datetime.now(timezone.utc) - start_time).total_seconds() * 1000
            
            logger.info(f"✅ {provider} - {endpoint} - {response_time_ms:.0f}ms")
            
            return {
                "provider": provider,
                "endpoint": endpoint,
                "data": data,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "response_time_ms": response_time_ms,
                "success": True,
                "error": None
            }
            
        except httpx.HTTPStatusError as e:
            logger.error(f"❌ {provider} - {endpoint} - HTTP {e.response.status_code}")
            return {
                "provider": provider,
                "endpoint": endpoint,
                "data": None,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "success": False,
                "error": f"HTTP {e.response.status_code}",
                "error_type": "http_error"
            }
        except httpx.TimeoutException:
            logger.error(f"❌ {provider} - {endpoint} - Timeout")
            return {
                "provider": provider,
                "endpoint": endpoint,
                "data": None,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "success": False,
                "error": "Request timeout",
                "error_type": "timeout"
            }
        except Exception as e:
            logger.error(f"❌ {provider} - {endpoint} - {str(e)}")
            return {
                "provider": provider,
                "endpoint": endpoint,
                "data": None,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "success": False,
                "error": str(e),
                "error_type": "exception"
            }
    
    # ===== RESOURCES DATABASE =====
    
    async def get_resources_stats(self) -> Dict[str, Any]:
        """
        Get resources database statistics
        Returns: total_resources, total_categories, category breakdown
        """
        return await self._request("/api/resources/stats")
    
    async def get_all_resources(self) -> Dict[str, Any]:
        """
        Get complete list of all 281+ resources
        Returns: Full resource database
        """
        return await self._request("/api/resources/list")
    
    async def get_categories(self) -> Dict[str, Any]:
        """
        Get list of all 12 resource categories
        Categories: rpc_nodes, block_explorers, market_data_apis, news_apis,
                   sentiment_apis, onchain_analytics_apis, whale_tracking_apis,
                   hf_resources, free_http_endpoints, cors_proxies, 
                   community_sentiment_apis, local_backend_routes
        """
        return await self._request("/api/categories")
    
    async def get_resources_by_category(self, category: str) -> Dict[str, Any]:
        """
        Get resources for a specific category
        
        Args:
            category: Category name (e.g., 'market_data_apis', 'sentiment_apis')
        """
        return await self._request(f"/api/resources/category/{category}")
    
    # ===== SYSTEM STATUS =====
    
    async def health_check(self) -> Dict[str, Any]:
        """Check API health status"""
        return await self._request("/health")
    
    # ===== CONVENIENCE METHODS =====
    
    async def get_total_resources_count(self) -> int:
        """
        Get total number of resources available
        
        Returns:
            int: Total resource count
        """
        result = await self.get_resources_stats()
        if result["success"] and result["data"]:
            return result["data"].get("total_resources", 0)
        return 0
    
    async def get_category_summary(self) -> Dict[str, int]:
        """
        Get summary of resources per category
        
        Returns:
            Dict mapping category names to resource counts
        """
        result = await self.get_resources_stats()
        if result["success"] and result["data"]:
            return result["data"].get("categories", {})
        return {}
    
    async def get_market_data_providers(self) -> List[Dict[str, Any]]:
        """
        Get all market data API providers (33 resources)
        """
        result = await self.get_resources_by_category("market_data_apis")
        if result["success"] and result["data"]:
            return result["data"].get("resources", [])
        return []
    
    async def get_sentiment_providers(self) -> List[Dict[str, Any]]:
        """
        Get all sentiment API providers (14 resources)
        """
        result = await self.get_resources_by_category("sentiment_apis")
        if result["success"] and result["data"]:
            return result["data"].get("resources", [])
        return []
    
    async def get_rpc_nodes(self) -> List[Dict[str, Any]]:
        """
        Get all RPC node providers (24 resources)
        """
        result = await self.get_resources_by_category("rpc_nodes")
        if result["success"] and result["data"]:
            return result["data"].get("resources", [])
        return []
    
    async def get_block_explorers(self) -> List[Dict[str, Any]]:
        """
        Get all block explorer APIs (33 resources)
        """
        result = await self.get_resources_by_category("block_explorers")
        if result["success"] and result["data"]:
            return result["data"].get("resources", [])
        return []


# ===== SINGLETON INSTANCE =====

_service_instance: Optional[CryptoAPICleanService] = None


def get_crypto_api_clean_service() -> CryptoAPICleanService:
    """Get singleton instance of Crypto API Clean Service"""
    global _service_instance
    if _service_instance is None:
        _service_instance = CryptoAPICleanService()
    return _service_instance


# ===== STANDALONE FUNCTIONS (for collectors compatibility) =====

async def fetch_crypto_api_clean_stats() -> Dict[str, Any]:
    """Fetch resource statistics from Crypto API Clean"""
    service = get_crypto_api_clean_service()
    return await service.get_resources_stats()


async def fetch_crypto_api_clean_resources(category: Optional[str] = None) -> Dict[str, Any]:
    """Fetch resources from Crypto API Clean"""
    service = get_crypto_api_clean_service()
    if category:
        return await service.get_resources_by_category(category)
    return await service.get_all_resources()


# ===== TEST =====

if __name__ == "__main__":
    async def main():
        service = get_crypto_api_clean_service()
        
        print("=" * 70)
        print("Testing Crypto API Clean Service")
        print("=" * 70)
        
        # Health check
        print("\n1. Health Check:")
        result = await service.health_check()
        print(f"   Success: {result['success']}")
        
        # Resource stats
        print("\n2. Resource Statistics:")
        result = await service.get_resources_stats()
        if result['success']:
            data = result['data']
            print(f"   Total Resources: {data.get('total_resources')}")
            print(f"   Total Categories: {data.get('total_categories')}")
            print(f"   Categories:")
            for cat, count in data.get('categories', {}).items():
                print(f"     - {cat}: {count}")
        
        # Category summary
        print("\n3. Market Data Providers:")
        providers = await service.get_market_data_providers()
        print(f"   Found {len(providers)} providers")
        
        # Sentiment providers
        print("\n4. Sentiment Providers:")
        providers = await service.get_sentiment_providers()
        print(f"   Found {len(providers)} providers")
        
        await service.close()
        print("\n" + "=" * 70)
        print("Tests completed!")
    
    asyncio.run(main())