""" 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', ]