Files
oib c8be9d7414 feat: add marketplace metrics, privacy features, and service registry endpoints
- 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
2025-12-22 10:33:23 +01:00

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()