From 4ee0a9962b6f7cde7cee44d8a2ded373e64554db Mon Sep 17 00:00:00 2001 From: arabcoders Date: Mon, 16 Jun 2025 19:45:59 +0300 Subject: [PATCH] Add new service for dependency injection. --- app/library/HttpAPI.py | 39 +++++++++++++------------ app/library/HttpSocket.py | 44 ++++++++++++---------------- app/library/Services.py | 61 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 app/library/Services.py diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index 03185baa..d567809f 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -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): diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py index 7c00e212..190221ec 100644 --- a/app/library/HttpSocket.py +++ b/app/library/HttpSocket.py @@ -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 diff --git a/app/library/Services.py b/app/library/Services.py new file mode 100644 index 00000000..dabfa640 --- /dev/null +++ b/app/library/Services.py @@ -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)