Add new service for dependency injection.
This commit is contained in:
parent
ba650fe2bc
commit
4ee0a9962b
3 changed files with 101 additions and 43 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
61
app/library/Services.py
Normal 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)
|
||||
Loading…
Reference in a new issue