Add new service for dependency injection.

This commit is contained in:
arabcoders 2025-06-16 19:45:59 +03:00
parent ba650fe2bc
commit 4ee0a9962b
3 changed files with 101 additions and 43 deletions

View file

@ -5,12 +5,13 @@ import logging
from collections.abc import Awaitable
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any
import anyio
from aiohttp import web
from aiohttp.web import Request, RequestHandler, Response
from app.library.Services import Services
from .cache import Cache
from .config import Config
from .DownloadQueue import DownloadQueue
@ -24,27 +25,32 @@ LOG: logging.Logger = logging.getLogger("http_api")
class HttpAPI:
di_context: dict[str, Any] = {}
def __init__(self, root_path: Path, queue: DownloadQueue):
self.queue: DownloadQueue = queue or DownloadQueue.get_instance()
self.encoder: Encoder = Encoder()
self.config: Config = Config.get_instance()
self._notify: EventBus = EventBus.get_instance()
self.rootPath: Path = root_path
self.cache = Cache()
self.app: web.Application | None = None
self.di_context: dict[str, Any] = {
"queue": self.queue,
"encoder": self.encoder,
"config": self.config,
"notify": self._notify,
"cache": self.cache,
"app": self.app,
"http_api": self,
"root_path": self.rootPath,
}
services = Services.get_instance()
services.add_all(
{
k: v
for k, v in {
"queue": self.queue,
"encoder": self.encoder,
"config": self.config,
"notify": self._notify,
"cache": self.cache,
"app": self.app,
"http_api": self,
"root_path": self.rootPath,
}.items()
if not services.has(k)
}
)
async def on_shutdown(self, _: web.Application):
pass
@ -297,8 +303,6 @@ class HttpAPI:
Response: The response object.
"""
context = {**self.di_context.copy(), "request": request}
try:
sig = inspect.signature(handler)
expected_args = sig.parameters.keys()
@ -307,8 +311,7 @@ class HttpAPI:
if 1 == len(expected_args) and "request" in expected_args:
response = await handler(request)
else:
filtered = {k: v for k, v in context.items() if k in expected_args}
response = await handler(**filtered)
response = await Services.get_instance().handle_async(handler, request=request)
except TypeError as te:
LOG.exception(te)
if "missing 1 required positional argument" in str(te) and "request" in str(te):

View file

@ -1,5 +1,4 @@
import functools
import inspect
import logging
from pathlib import Path
from typing import Any
@ -8,6 +7,7 @@ import socketio
from aiohttp import web
from app.library.router import RouteType, get_routes
from app.library.Services import Services
from app.library.Utils import load_modules
from .config import Config
@ -41,7 +41,6 @@ class HttpSocket:
self.queue = queue or DownloadQueue.get_instance()
self._notify = EventBus.get_instance()
# logger=True, engineio_logger=True,
self.sio = sio or socketio.AsyncServer(
async_handlers=True,
async_mode="aiohttp",
@ -58,14 +57,21 @@ class HttpSocket:
def emit(e: Event, _, **kwargs):
return self.sio.emit(event=e.event, data=encoder.encode(e.data), **kwargs)
self.di_context = {
"config": self.config,
"queue": self.queue,
"sio": self.sio,
"encoder": encoder,
"notify": self._notify,
"root_path": self.rootPath,
}
services = Services.get_instance()
services.add_all(
{
k: v
for k, v in {
"config": self.config,
"queue": self.queue,
"sio": self.sio,
"encoder": encoder,
"notify": self._notify,
"root_path": self.rootPath,
}.items()
if not services.has(k)
}
)
self._notify.subscribe("frontend", emit, f"{__class__.__name__}.emit")
@ -107,23 +113,11 @@ class HttpSocket:
LOG.debug(
f"Add ({route.name}) {route.method.value if isinstance(route.method,RouteType) else route.method}: {route.path}."
)
self.sio.on(route.path)(HttpSocket._injector(route.handler, route.path, self.di_context))
self.sio.on(route.path)(HttpSocket._injector(route.handler, route.path))
@staticmethod
def _injector(func, event: str, container: dict):
sig: inspect.Signature = inspect.signature(func)
def _injector(func, event: str):
async def wrapper(sid, data, **kwargs):
args = {}
merged = {**container, "sid": sid, "data": data, "event_name": event}
if isinstance(kwargs, dict):
merged.update(kwargs)
for name in sig.parameters:
if name in merged:
args[name] = merged[name]
return await func(**args)
return await Services.get_instance().handle_async(func, sid=sid, data=data, event=event, **kwargs)
return wrapper

61
app/library/Services.py Normal file
View file

@ -0,0 +1,61 @@
import inspect
from typing import Any, TypeVar
from app.library.Singleton import Singleton
T = TypeVar("T")
class Services(metaclass=Singleton):
_dct: dict[str, T] = {}
_instance = None
"""The instance of the class."""
@staticmethod
def get_instance() -> "Services":
if Services._instance is None:
Services._instance = Services()
return Services._instance
def add(self, name: str, service: T):
self._dct[name] = service
def add_all(self, services: dict[str, T]):
for name, service in services.items():
self.add(name, service)
def get(self, name: str) -> T | None:
return self._dct.get(name)
def has(self, name: str) -> bool:
return name in self._dct
def remove(self, name: str):
if name not in self._dct:
return
self._dct.pop(name, None)
def clear(self):
self._dct.clear()
def get_all(self) -> dict[str, T]:
return self._dct.copy()
async def handle_async(self, handler: callable, **kwargs) -> Any:
context = {**self.get_all(), **kwargs}
sig = inspect.signature(handler)
expected_args = sig.parameters.keys()
filtered = {k: v for k, v in context.items() if k in expected_args}
return await handler(**filtered)
def handle_sync(self, handler: callable, **kwargs) -> Any:
context = {**self.get_all(), **kwargs}
sig = inspect.signature(handler)
expected_args = sig.parameters.keys()
filtered = {k: v for k, v in context.items() if k in expected_args}
return handler(**filtered)