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

View file

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