jebin2's picture
refactor
bcc8074
"""
Base Service Infrastructure
Provides the foundation for plug-and-play services in the API gateway.
All services (auth, credit, gemini, etc.) extend this base infrastructure.
Core Components:
- BaseService: Abstract base class for all services
- ServiceConfig: Configuration container
- ServiceRegistry: Global registry for service discovery
- MiddlewareProtocol: Type definition for middleware functions
Usage:
class MyService(BaseService):
@classmethod
def register(cls, **config):
# Service-specific registration
pass
@classmethod
def get_middleware(cls):
# Return middleware function if needed
return MyMiddleware()
"""
import logging
from abc import ABC, abstractmethod
from typing import Dict, Type, Optional, Callable, Any
from starlette.middleware.base import BaseHTTPMiddleware
logger = logging.getLogger(__name__)
class ServiceConfig:
"""
Base configuration container for services.
Services can extend this to add their specific configuration.
"""
def __init__(self, **kwargs):
"""Initialize configuration with arbitrary key-value pairs."""
self._config = kwargs
def get(self, key: str, default: Any = None) -> Any:
"""Get configuration value."""
return self._config.get(key, default)
def set(self, key: str, value: Any) -> None:
"""Set configuration value."""
self._config[key] = value
def __getitem__(self, key: str) -> Any:
"""Dictionary-style access."""
return self._config[key]
def __setitem__(self, key: str, value: Any) -> None:
"""Dictionary-style assignment."""
self._config[key] = value
def __contains__(self, key: str) -> bool:
"""Check if key exists."""
return key in self._config
class BaseService(ABC):
"""
Abstract base class for all plug-and-play services.
Services must implement:
- register(): Register service configuration at startup
- get_middleware(): Return middleware if service needs request interception
- on_shutdown(): Cleanup on app shutdown
"""
# Service name (override in subclass)
SERVICE_NAME: str = "base_service"
# Service configuration
_config: Optional[ServiceConfig] = None
# Registration state
_registered: bool = False
@classmethod
@abstractmethod
def register(cls, **config) -> None:
"""
Register service configuration at application startup.
Args:
**config: Service-specific configuration parameters
Raises:
RuntimeError: If service is already registered
"""
if cls._registered:
raise RuntimeError(f"{cls.SERVICE_NAME} is already registered")
cls._config = ServiceConfig(**config)
cls._registered = True
logger.info(f"✅ {cls.SERVICE_NAME} registered successfully")
@classmethod
def get_middleware(cls) -> Optional[BaseHTTPMiddleware]:
"""
Return middleware instance if service needs request interception.
Returns:
Middleware instance or None if service doesn't need middleware
"""
return None
@classmethod
def on_shutdown(cls) -> None:
"""
Cleanup hook called during application shutdown.
Override this to perform cleanup (close connections, save state, etc.)
"""
pass
@classmethod
def is_registered(cls) -> bool:
"""Check if service has been registered."""
return cls._registered
@classmethod
def assert_registered(cls) -> None:
"""
Assert that service has been registered.
Raises:
RuntimeError: If service is not registered
"""
if not cls._registered:
raise RuntimeError(
f"{cls.SERVICE_NAME} is not registered. "
f"Call {cls.SERVICE_NAME}.register() at application startup."
)
@classmethod
def get_config(cls) -> ServiceConfig:
"""
Get service configuration.
Returns:
ServiceConfig instance
Raises:
RuntimeError: If service is not registered
"""
cls.assert_registered()
return cls._config
class ServiceRegistry:
"""
Global registry for service discovery and management.
Tracks all registered services and provides lookup functionality.
"""
_services: Dict[str, Type[BaseService]] = {}
@classmethod
def register_service(cls, service_class: Type[BaseService]) -> None:
"""
Register a service class in the global registry.
Args:
service_class: Service class to register
"""
service_name = service_class.SERVICE_NAME
if service_name in cls._services:
logger.warning(f"Service '{service_name}' already registered, overwriting")
cls._services[service_name] = service_class
logger.debug(f"Registered service: {service_name}")
@classmethod
def get_service(cls, service_name: str) -> Optional[Type[BaseService]]:
"""
Get a service class by name.
Args:
service_name: Name of the service to retrieve
Returns:
Service class or None if not found
"""
return cls._services.get(service_name)
@classmethod
def get_all_services(cls) -> Dict[str, Type[BaseService]]:
"""
Get all registered services.
Returns:
Dictionary mapping service names to service classes
"""
return cls._services.copy()
@classmethod
def get_all_middleware(cls) -> list:
"""
Get middleware from all registered services.
Returns:
List of middleware instances in registration order
"""
middleware_list = []
for service_name, service_class in cls._services.items():
if service_class.is_registered():
middleware = service_class.get_middleware()
if middleware:
middleware_list.append(middleware)
logger.debug(f"Added middleware from service: {service_name}")
return middleware_list
@classmethod
def shutdown_all(cls) -> None:
"""
Call shutdown hooks for all registered services.
"""
logger.info("Shutting down all services...")
for service_name, service_class in cls._services.items():
try:
service_class.on_shutdown()
logger.debug(f"Shutdown complete: {service_name}")
except Exception as e:
logger.error(f"Error shutting down {service_name}: {e}")
logger.info("All services shut down")
__all__ = [
'BaseService',
'ServiceConfig',
'ServiceRegistry',
]