mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2026-03-19 03:36:30 +01:00
refactor: backend standardization (service layer, validation, route splitting) + image cache and Scryfall API fixes
This commit is contained in:
parent
e81b47bccf
commit
f784741416
35 changed files with 7054 additions and 4344 deletions
202
code/web/services/registry.py
Normal file
202
code/web/services/registry.py
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
"""Service registry for dependency injection.
|
||||
|
||||
Provides a centralized registry for managing service instances and dependencies.
|
||||
Supports singleton and factory patterns with thread-safe access.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Dict, Optional, Type, TypeVar, cast
|
||||
import threading
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class ServiceRegistry:
|
||||
"""Thread-safe service registry for dependency injection.
|
||||
|
||||
Manages service instances and factories with support for:
|
||||
- Singleton services (one instance per registry)
|
||||
- Factory services (new instance per request)
|
||||
- Lazy initialization
|
||||
- Thread-safe access
|
||||
|
||||
Example:
|
||||
registry = ServiceRegistry()
|
||||
registry.register_singleton(SessionService, session_service_instance)
|
||||
registry.register_factory(BuildService, lambda: BuildService(deps...))
|
||||
|
||||
# Get services
|
||||
session_svc = registry.get(SessionService)
|
||||
build_svc = registry.get(BuildService)
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize empty registry."""
|
||||
self._singletons: Dict[Type[Any], Any] = {}
|
||||
self._factories: Dict[Type[Any], Callable[[], Any]] = {}
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def register_singleton(self, service_type: Type[T], instance: T) -> None:
|
||||
"""Register a singleton service instance.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
instance: Service instance to register
|
||||
|
||||
Raises:
|
||||
ValueError: If service already registered
|
||||
"""
|
||||
with self._lock:
|
||||
if service_type in self._singletons or service_type in self._factories:
|
||||
raise ValueError(f"Service {service_type.__name__} already registered")
|
||||
self._singletons[service_type] = instance
|
||||
|
||||
def register_factory(self, service_type: Type[T], factory: Callable[[], T]) -> None:
|
||||
"""Register a factory for creating service instances.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
factory: Factory function that returns service instance
|
||||
|
||||
Raises:
|
||||
ValueError: If service already registered
|
||||
"""
|
||||
with self._lock:
|
||||
if service_type in self._singletons or service_type in self._factories:
|
||||
raise ValueError(f"Service {service_type.__name__} already registered")
|
||||
self._factories[service_type] = factory
|
||||
|
||||
def register_lazy_singleton(self, service_type: Type[T], factory: Callable[[], T]) -> None:
|
||||
"""Register a lazy-initialized singleton service.
|
||||
|
||||
The factory will be called once on first access, then the instance is cached.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
factory: Factory function that returns service instance
|
||||
|
||||
Raises:
|
||||
ValueError: If service already registered
|
||||
"""
|
||||
with self._lock:
|
||||
if service_type in self._singletons or service_type in self._factories:
|
||||
raise ValueError(f"Service {service_type.__name__} already registered")
|
||||
|
||||
# Wrap factory to cache result
|
||||
instance_cache: Dict[str, Any] = {}
|
||||
|
||||
def lazy_factory() -> T:
|
||||
if "instance" not in instance_cache:
|
||||
instance_cache["instance"] = factory()
|
||||
return instance_cache["instance"]
|
||||
|
||||
self._factories[service_type] = lazy_factory
|
||||
|
||||
def get(self, service_type: Type[T]) -> T:
|
||||
"""Get service instance.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
|
||||
Returns:
|
||||
Service instance
|
||||
|
||||
Raises:
|
||||
KeyError: If service not registered
|
||||
"""
|
||||
with self._lock:
|
||||
# Check singletons first
|
||||
if service_type in self._singletons:
|
||||
return cast(T, self._singletons[service_type])
|
||||
|
||||
# Check factories
|
||||
if service_type in self._factories:
|
||||
return cast(T, self._factories[service_type]())
|
||||
|
||||
raise KeyError(f"Service {service_type.__name__} not registered")
|
||||
|
||||
def try_get(self, service_type: Type[T]) -> Optional[T]:
|
||||
"""Try to get service instance, return None if not registered.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
|
||||
Returns:
|
||||
Service instance or None
|
||||
"""
|
||||
try:
|
||||
return self.get(service_type)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def is_registered(self, service_type: Type[Any]) -> bool:
|
||||
"""Check if service is registered.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
|
||||
Returns:
|
||||
True if registered
|
||||
"""
|
||||
with self._lock:
|
||||
return service_type in self._singletons or service_type in self._factories
|
||||
|
||||
def unregister(self, service_type: Type[Any]) -> None:
|
||||
"""Unregister a service.
|
||||
|
||||
Args:
|
||||
service_type: Service type/interface
|
||||
"""
|
||||
with self._lock:
|
||||
self._singletons.pop(service_type, None)
|
||||
self._factories.pop(service_type, None)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered services."""
|
||||
with self._lock:
|
||||
self._singletons.clear()
|
||||
self._factories.clear()
|
||||
|
||||
def get_registered_types(self) -> list[Type[Any]]:
|
||||
"""Get list of all registered service types.
|
||||
|
||||
Returns:
|
||||
List of service types
|
||||
"""
|
||||
with self._lock:
|
||||
return list(self._singletons.keys()) + list(self._factories.keys())
|
||||
|
||||
|
||||
# Global registry instance
|
||||
_global_registry: Optional[ServiceRegistry] = None
|
||||
_global_registry_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_registry() -> ServiceRegistry:
|
||||
"""Get the global service registry instance.
|
||||
|
||||
Creates registry on first access (lazy initialization).
|
||||
|
||||
Returns:
|
||||
Global ServiceRegistry instance
|
||||
"""
|
||||
global _global_registry
|
||||
|
||||
if _global_registry is None:
|
||||
with _global_registry_lock:
|
||||
if _global_registry is None:
|
||||
_global_registry = ServiceRegistry()
|
||||
|
||||
return _global_registry
|
||||
|
||||
|
||||
def reset_registry() -> None:
|
||||
"""Reset the global registry (primarily for testing).
|
||||
|
||||
Clears all registered services and creates a new registry instance.
|
||||
"""
|
||||
global _global_registry
|
||||
|
||||
with _global_registry_lock:
|
||||
_global_registry = ServiceRegistry()
|
||||
Loading…
Add table
Add a link
Reference in a new issue