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 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):
|
||||||
|
|
|
||||||
|
|
@ -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
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