- Add Prometheus metrics for marketplace API throughput and error rates with new dashboard panels - Implement confidential transaction models with encryption support and access control - Add key management system with registration, rotation, and audit logging - Create services and registry routers for service discovery and management - Integrate ZK proof generation for privacy-preserving receipts - Add metrics instru
297 lines
9.6 KiB
Python
297 lines
9.6 KiB
Python
"""
|
|
Core components for AITBC Enterprise Connectors SDK
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Optional, Dict, Any, Callable, Awaitable
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timedelta
|
|
import aiohttp
|
|
from aiohttp import ClientTimeout, ClientSession
|
|
|
|
from .auth import AuthHandler
|
|
from .rate_limiter import RateLimiter
|
|
from .metrics import MetricsCollector
|
|
from .exceptions import ConfigurationError
|
|
|
|
|
|
@dataclass
|
|
class ConnectorConfig:
|
|
"""Configuration for AITBC connectors"""
|
|
|
|
# API Configuration
|
|
base_url: str
|
|
api_key: str
|
|
api_version: str = "v1"
|
|
|
|
# Connection Settings
|
|
timeout: float = 30.0
|
|
max_connections: int = 100
|
|
max_retries: int = 3
|
|
retry_backoff: float = 1.0
|
|
|
|
# Rate Limiting
|
|
rate_limit: Optional[int] = None # Requests per second
|
|
burst_limit: Optional[int] = None
|
|
|
|
# Authentication
|
|
auth_type: str = "bearer" # bearer, basic, custom
|
|
auth_config: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
# Webhooks
|
|
webhook_secret: Optional[str] = None
|
|
webhook_endpoint: Optional[str] = None
|
|
|
|
# Monitoring
|
|
enable_metrics: bool = True
|
|
metrics_endpoint: Optional[str] = None
|
|
|
|
# Logging
|
|
log_level: str = "INFO"
|
|
log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
|
# Enterprise Features
|
|
enterprise_id: Optional[str] = None
|
|
tenant_id: Optional[str] = None
|
|
region: Optional[str] = None
|
|
|
|
def __post_init__(self):
|
|
"""Validate configuration"""
|
|
if not self.base_url:
|
|
raise ConfigurationError("base_url is required")
|
|
if not self.api_key:
|
|
raise ConfigurationError("api_key is required")
|
|
|
|
# Set up logging
|
|
logging.basicConfig(
|
|
level=getattr(logging, self.log_level.upper()),
|
|
format=self.log_format
|
|
)
|
|
|
|
|
|
class AITBCClient:
|
|
"""Main client for AITBC Enterprise Connectors"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: ConnectorConfig,
|
|
session: Optional[ClientSession] = None,
|
|
auth_handler: Optional[AuthHandler] = None,
|
|
rate_limiter: Optional[RateLimiter] = None,
|
|
metrics: Optional[MetricsCollector] = None
|
|
):
|
|
self.config = config
|
|
self.logger = logging.getLogger(f"aitbc.{self.__class__.__name__}")
|
|
|
|
# Initialize components with dependency injection
|
|
self._session = session or self._create_session()
|
|
self._auth = auth_handler or AuthHandler(config)
|
|
self._rate_limiter = rate_limiter or RateLimiter(config)
|
|
self._metrics = metrics or MetricsCollector(config) if config.enable_metrics else None
|
|
|
|
# Event handlers
|
|
self._event_handlers: Dict[str, list] = {}
|
|
|
|
# Connection state
|
|
self._connected = False
|
|
self._last_activity = None
|
|
|
|
def _create_session(self) -> ClientSession:
|
|
"""Create HTTP session with configuration"""
|
|
timeout = ClientTimeout(total=self.config.timeout)
|
|
|
|
# Set up headers
|
|
headers = {
|
|
"User-Agent": f"AITBC-SDK/{__version__}",
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
return ClientSession(
|
|
timeout=timeout,
|
|
headers=headers,
|
|
connector=aiohttp.TCPConnector(
|
|
limit=self.config.max_connections,
|
|
limit_per_host=self.config.max_connections // 4
|
|
)
|
|
)
|
|
|
|
async def connect(self) -> None:
|
|
"""Establish connection to AITBC"""
|
|
if self._connected:
|
|
return
|
|
|
|
try:
|
|
# Test connection
|
|
await self._test_connection()
|
|
|
|
# Start metrics collection
|
|
if self._metrics:
|
|
await self._metrics.start()
|
|
|
|
self._connected = True
|
|
self._last_activity = datetime.utcnow()
|
|
|
|
self.logger.info("Connected to AITBC")
|
|
await self._emit_event("connected", {"timestamp": self._last_activity})
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Failed to connect: {e}")
|
|
raise
|
|
|
|
async def disconnect(self) -> None:
|
|
"""Close connection to AITBC"""
|
|
if not self._connected:
|
|
return
|
|
|
|
try:
|
|
# Stop metrics collection
|
|
if self._metrics:
|
|
await self._metrics.stop()
|
|
|
|
# Close session
|
|
await self._session.close()
|
|
|
|
self._connected = False
|
|
self.logger.info("Disconnected from AITBC")
|
|
await self._emit_event("disconnected", {"timestamp": datetime.utcnow()})
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error during disconnect: {e}")
|
|
|
|
async def request(
|
|
self,
|
|
method: str,
|
|
path: str,
|
|
**kwargs
|
|
) -> Dict[str, Any]:
|
|
"""Make authenticated request to AITBC API"""
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
# Apply rate limiting
|
|
if self.config.rate_limit:
|
|
await self._rate_limiter.acquire()
|
|
|
|
# Prepare request
|
|
url = f"{self.config.base_url}/{self.config.api_version}/{path.lstrip('/')}"
|
|
|
|
# Add authentication
|
|
headers = kwargs.pop("headers", {})
|
|
auth_headers = await self._auth.get_headers()
|
|
headers.update(auth_headers)
|
|
|
|
# Retry logic
|
|
last_exception = None
|
|
for attempt in range(self.config.max_retries + 1):
|
|
try:
|
|
start_time = datetime.utcnow()
|
|
|
|
async with self._session.request(
|
|
method,
|
|
url,
|
|
headers=headers,
|
|
**kwargs
|
|
) as response:
|
|
# Record metrics
|
|
if self._metrics:
|
|
duration = (datetime.utcnow() - start_time).total_seconds()
|
|
await self._metrics.record_request(
|
|
method=method,
|
|
path=path,
|
|
status=response.status,
|
|
duration=duration
|
|
)
|
|
|
|
# Handle response
|
|
if response.status == 429:
|
|
retry_after = int(response.headers.get("Retry-After", self.config.retry_backoff))
|
|
await asyncio.sleep(retry_after)
|
|
continue
|
|
|
|
response.raise_for_status()
|
|
|
|
data = await response.json()
|
|
self._last_activity = datetime.utcnow()
|
|
|
|
return data
|
|
|
|
except aiohttp.ClientError as e:
|
|
last_exception = e
|
|
if attempt < self.config.max_retries:
|
|
backoff = self.config.retry_backoff * (2 ** attempt)
|
|
self.logger.warning(f"Request failed, retrying in {backoff}s: {e}")
|
|
await asyncio.sleep(backoff)
|
|
else:
|
|
self.logger.error(f"Request failed after {self.config.max_retries} retries: {e}")
|
|
raise
|
|
|
|
raise last_exception
|
|
|
|
async def get(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
"""Make GET request"""
|
|
return await self.request("GET", path, **kwargs)
|
|
|
|
async def post(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
"""Make POST request"""
|
|
return await self.request("POST", path, **kwargs)
|
|
|
|
async def put(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
"""Make PUT request"""
|
|
return await self.request("PUT", path, **kwargs)
|
|
|
|
async def delete(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
"""Make DELETE request"""
|
|
return await self.request("DELETE", path, **kwargs)
|
|
|
|
def on(self, event: str, handler: Callable[[Dict[str, Any]], Awaitable[None]]):
|
|
"""Register event handler"""
|
|
if event not in self._event_handlers:
|
|
self._event_handlers[event] = []
|
|
self._event_handlers[event].append(handler)
|
|
|
|
def off(self, event: str, handler: Callable):
|
|
"""Unregister event handler"""
|
|
if event in self._event_handlers:
|
|
try:
|
|
self._event_handlers[event].remove(handler)
|
|
except ValueError:
|
|
pass
|
|
|
|
async def _emit_event(self, event: str, data: Dict[str, Any]):
|
|
"""Emit event to registered handlers"""
|
|
if event in self._event_handlers:
|
|
tasks = []
|
|
for handler in self._event_handlers[event]:
|
|
tasks.append(handler(data))
|
|
|
|
if tasks:
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
async def _test_connection(self):
|
|
"""Test connection to AITBC"""
|
|
try:
|
|
await self.get("/health")
|
|
except Exception as e:
|
|
raise ConnectionError(f"Failed to connect to AITBC: {e}")
|
|
|
|
@property
|
|
def is_connected(self) -> bool:
|
|
"""Check if client is connected"""
|
|
return self._connected
|
|
|
|
@property
|
|
def last_activity(self) -> Optional[datetime]:
|
|
"""Get last activity timestamp"""
|
|
return self._last_activity
|
|
|
|
async def __aenter__(self):
|
|
"""Async context manager entry"""
|
|
await self.connect()
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
"""Async context manager exit"""
|
|
await self.disconnect()
|