254 lines
7.6 KiB
Python
254 lines
7.6 KiB
Python
"""
|
|
Vendor Registry System for Interface Routing (Issue #11).
|
|
|
|
This module provides:
|
|
1. VendorCapability - Enum for vendor capabilities
|
|
2. VendorMetadata - Dataclass for vendor information
|
|
3. VendorRegistry - Thread-safe singleton for vendor registration and lookup
|
|
4. VendorRegistrationError - Custom exception for registration errors
|
|
|
|
The registry enables centralized vendor management with priority-based
|
|
routing, capability tracking, and thread-safe access patterns.
|
|
"""
|
|
|
|
import threading
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
|
|
class VendorCapability(str, Enum):
|
|
"""
|
|
Enum for vendor capabilities.
|
|
|
|
Defines standard data provider capabilities for routing requests
|
|
to appropriate vendors.
|
|
"""
|
|
|
|
STOCK_DATA = "stock_data"
|
|
FUNDAMENTALS = "fundamentals"
|
|
TECHNICAL_INDICATORS = "technical_indicators"
|
|
NEWS = "news"
|
|
MACROECONOMIC = "macroeconomic"
|
|
INSIDER_DATA = "insider_data"
|
|
|
|
|
|
@dataclass
|
|
class VendorMetadata:
|
|
"""
|
|
Metadata for a registered vendor.
|
|
|
|
Attributes:
|
|
name: Vendor identifier (e.g., "alpha_vantage")
|
|
capabilities: List of VendorCapability values
|
|
methods: Dict mapping method names to implementation names
|
|
priority: Vendor priority for routing (higher = preferred)
|
|
rate_limit: Maximum calls per minute (None = unlimited)
|
|
"""
|
|
|
|
name: str
|
|
capabilities: List[str]
|
|
methods: Dict[str, str]
|
|
priority: int = 0
|
|
rate_limit: Optional[int] = None
|
|
|
|
|
|
class VendorRegistrationError(Exception):
|
|
"""Exception raised for vendor registration errors."""
|
|
pass
|
|
|
|
|
|
class VendorRegistry:
|
|
"""
|
|
Thread-safe singleton registry for vendor management.
|
|
|
|
Provides centralized registration, lookup, and routing for data vendors.
|
|
Uses double-checked locking for thread-safe singleton pattern.
|
|
|
|
Thread Safety:
|
|
All public methods use internal locking to ensure thread-safe access.
|
|
Registry state is protected by _lock during mutations.
|
|
|
|
Usage:
|
|
registry = VendorRegistry()
|
|
registry.register_vendor(vendor_class, metadata)
|
|
vendors = registry.get_vendor_for_method("get_stock_data")
|
|
"""
|
|
|
|
_instance = None
|
|
_lock = threading.Lock()
|
|
|
|
def __new__(cls):
|
|
"""
|
|
Create or return singleton instance with double-checked locking.
|
|
|
|
Returns:
|
|
VendorRegistry: Singleton instance
|
|
"""
|
|
if cls._instance is None:
|
|
with cls._lock:
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
cls._instance._initialized = False
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
"""Initialize registry storage (only once for singleton)."""
|
|
if self._initialized:
|
|
return
|
|
|
|
with self._lock:
|
|
if self._initialized:
|
|
return
|
|
|
|
self._vendors: Dict[str, Dict[str, Any]] = {}
|
|
self._method_map: Dict[str, List[Dict[str, Any]]] = {}
|
|
self._initialized = True
|
|
|
|
def register_vendor(self, vendor_class: type, metadata: VendorMetadata) -> None:
|
|
"""
|
|
Register a vendor with its metadata.
|
|
|
|
Args:
|
|
vendor_class: Vendor class to register
|
|
metadata: VendorMetadata describing vendor capabilities
|
|
|
|
Thread Safety:
|
|
Uses lock to ensure atomic registration
|
|
"""
|
|
with self._lock:
|
|
# Store vendor and metadata
|
|
self._vendors[metadata.name] = {
|
|
'class': vendor_class,
|
|
'metadata': metadata
|
|
}
|
|
|
|
# Update method map for each method
|
|
for method_name, impl_name in metadata.methods.items():
|
|
if method_name not in self._method_map:
|
|
self._method_map[method_name] = []
|
|
|
|
# Remove existing entry for this vendor if present
|
|
self._method_map[method_name] = [
|
|
entry for entry in self._method_map[method_name]
|
|
if entry['vendor'] != metadata.name
|
|
]
|
|
|
|
# Add new entry
|
|
self._method_map[method_name].append({
|
|
'vendor': metadata.name,
|
|
'priority': metadata.priority,
|
|
'implementation': impl_name
|
|
})
|
|
|
|
# Sort by priority (highest first)
|
|
self._method_map[method_name].sort(
|
|
key=lambda x: x['priority'],
|
|
reverse=True
|
|
)
|
|
|
|
def get_vendor_for_method(self, method_name: str) -> List[str]:
|
|
"""
|
|
Get list of vendors supporting a method, ordered by priority.
|
|
|
|
Args:
|
|
method_name: Method name to lookup (e.g., "get_stock_data")
|
|
|
|
Returns:
|
|
List of vendor names ordered by priority (highest first)
|
|
Empty list if no vendors support the method
|
|
|
|
Thread Safety:
|
|
Read-only operation, no locking needed for immutable view
|
|
"""
|
|
if method_name not in self._method_map:
|
|
return []
|
|
|
|
return [entry['vendor'] for entry in self._method_map[method_name]]
|
|
|
|
def get_vendor_metadata(self, vendor_name: str) -> VendorMetadata:
|
|
"""
|
|
Get metadata for a specific vendor.
|
|
|
|
Args:
|
|
vendor_name: Name of vendor to lookup
|
|
|
|
Returns:
|
|
VendorMetadata for the vendor
|
|
|
|
Raises:
|
|
ValueError: If vendor not found
|
|
|
|
Thread Safety:
|
|
Read-only operation, no locking needed for immutable view
|
|
"""
|
|
if vendor_name not in self._vendors:
|
|
raise ValueError(f"Vendor '{vendor_name}' not found")
|
|
|
|
return self._vendors[vendor_name]['metadata']
|
|
|
|
def list_all_vendors(self) -> List[str]:
|
|
"""
|
|
List all registered vendor names.
|
|
|
|
Returns:
|
|
List of registered vendor names
|
|
|
|
Thread Safety:
|
|
Read-only operation, no locking needed for immutable view
|
|
"""
|
|
return list(self._vendors.keys())
|
|
|
|
def get_methods_by_capability(self, capability: str) -> List[str]:
|
|
"""
|
|
Get all methods provided by vendors with a specific capability.
|
|
|
|
Args:
|
|
capability: Capability to search for (e.g., "stock_data")
|
|
|
|
Returns:
|
|
List of method names provided by vendors with this capability
|
|
|
|
Thread Safety:
|
|
Read-only operation, no locking needed for immutable view
|
|
"""
|
|
methods = set()
|
|
|
|
for vendor_name, vendor_data in self._vendors.items():
|
|
metadata = vendor_data['metadata']
|
|
if capability in metadata.capabilities:
|
|
methods.update(metadata.methods.keys())
|
|
|
|
return list(methods)
|
|
|
|
def get_vendor_implementation(self, vendor_name: str, method_name: str) -> Optional[str]:
|
|
"""
|
|
Get the implementation name for a specific vendor and method.
|
|
|
|
Args:
|
|
vendor_name: Name of vendor
|
|
method_name: Name of method
|
|
|
|
Returns:
|
|
Implementation method name, or None if not found
|
|
|
|
Thread Safety:
|
|
Read-only operation, no locking needed for immutable view
|
|
"""
|
|
if vendor_name not in self._vendors:
|
|
return None
|
|
|
|
metadata = self._vendors[vendor_name]['metadata']
|
|
return metadata.methods.get(method_name)
|
|
|
|
def clear_registry(self) -> None:
|
|
"""
|
|
Clear all registered vendors (primarily for testing).
|
|
|
|
Thread Safety:
|
|
Uses lock to ensure atomic clear operation
|
|
"""
|
|
with self._lock:
|
|
self._vendors.clear()
|
|
self._method_map.clear()
|