diff --git a/api/ovdashboard_api/core/config.py b/api/ovdashboard_api/core/config.py index f0c0f36..91405fd 100644 --- a/api/ovdashboard_api/core/config.py +++ b/api/ovdashboard_api/core/config.py @@ -3,15 +3,9 @@ Python representation of the "config.txt" file inside the WebDAV directory. """ import logging -import tomllib from typing import Any from pydantic import BaseModel -from webdav3.exceptions import RemoteResourceNotFound - -# from .caldav import CalDAV -from .settings import SETTINGS -from .webdav import WebDAV _logger = logging.getLogger(__name__) @@ -107,28 +101,3 @@ class Config(BaseModel): server: ServerUIConfig = ServerUIConfig() ticker: TickerConfig = TickerConfig() calendar: CalendarConfig = CalendarConfig() - - -async def get_config() -> Config: - """ - Load the configuration instance from the server using `TOML`. - """ - - try: - cfg_str = await WebDAV.read_str(SETTINGS.webdav.config_filename) - cfg = Config.model_validate(tomllib.loads(cfg_str)) - - except RemoteResourceNotFound: - _logger.warning( - f"Config file {SETTINGS.webdav.config_filename!r} not found, creating ..." - ) - - cfg = Config() - # cfg.calendar.aggregates["All Events"] = list(await CalDAV.calendars) - - # await WebDAV.write_str( - # SETTINGS.webdav.config_filename, - # tomli_w.dumps(cfg.model_dump()), - # ) - - return cfg diff --git a/api/ovdashboard_api/routers/v1/_common.py b/api/ovdashboard_api/routers/v1/_common.py index be0b53c..859f511 100644 --- a/api/ovdashboard_api/routers/v1/_common.py +++ b/api/ovdashboard_api/routers/v1/_common.py @@ -4,15 +4,17 @@ Dependables for defining Routers. import logging import re -from dataclasses import dataclass, field -from typing import Awaitable, Callable, Generic, ParamSpec, Self, TypeVar +import tomllib +import tomli_w from fastapi import Depends, HTTPException, params, status from webdav3.exceptions import RemoteResourceNotFound from ...core.caldav import CalDAV -from ...core.config import Config, get_config +from ...core.config import Config +from ...core.settings import SETTINGS from ...core.webdav import WebDAV +from ._list_manager import Dependable, DependableFn, ListManager _logger = logging.getLogger(__name__) @@ -22,78 +24,30 @@ _RESPONSE_OK = { }, } -Params = ParamSpec("Params") -Return = TypeVar("Return") -type DependableFn[**Params, Return] = Callable[Params, Awaitable[Return]] +async def get_config() -> Config: + """ + Load the configuration instance from the server using `TOML`. + """ + try: + cfg_str = await WebDAV.read_str(SETTINGS.webdav.config_filename) + cfg = Config.model_validate(tomllib.loads(cfg_str)) -@dataclass(slots=True, frozen=True) -class Dependable(Generic[Params, Return]): - func: DependableFn[Params, Return] - responses: dict = field(default_factory=lambda: _RESPONSE_OK.copy()) - - -@dataclass(slots=True, frozen=True) -class ListManager: - lister: Dependable[[], list[str]] - filter: Dependable[[str], list[str]] - getter: Dependable[[str], str] - - @classmethod - def from_lister(cls, lister: Dependable[[], list[str]]) -> Self: - async def _filter_fn( - prefix: str, - names: list[str] = Depends(lister.func), - ) -> list[str]: - if isinstance(names, params.Depends): - names = await lister.func() - - _logger.debug("filter %s from %s", repr(prefix), repr(names)) - - return [item for item in names if item.lower().startswith(prefix.lower())] - - async def _getter_fn( - prefix: str, - names: list[str] = Depends(_filter_fn), - ) -> str: - if isinstance(names, params.Depends): - names = await _filter_fn(prefix) - - _logger.debug("get %s from %s", repr(prefix), repr(names)) - - match names: - case [name]: - return name - - case []: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - - case _: - raise HTTPException(status_code=status.HTTP_409_CONFLICT) - - return cls( - lister=lister, - filter=Dependable(_filter_fn), - getter=Dependable( - func=_getter_fn, - responses={ - **_RESPONSE_OK, - status.HTTP_404_NOT_FOUND: { - "description": "Prefix not found", - "content": None, - }, - status.HTTP_409_CONFLICT: { - "description": "Ambiguous prefix", - "content": None, - }, - }, - ), + except RemoteResourceNotFound: + _logger.warning( + f"Config file {SETTINGS.webdav.config_filename!r} not found, creating ..." ) - @classmethod - def from_lister_fn(cls, lister_fn: DependableFn[[], list[str]]) -> Self: - return cls.from_lister(Dependable(lister_fn)) + cfg = Config() + cfg.calendar.aggregates["All Events"] = list(await CalDAV.calendars) + + await WebDAV.write_str( + SETTINGS.webdav.config_filename, + tomli_w.dumps(cfg.model_dump()), + ) + + return cfg def get_remote_path( diff --git a/api/ovdashboard_api/routers/v1/_list_manager.py b/api/ovdashboard_api/routers/v1/_list_manager.py new file mode 100644 index 0000000..b967d34 --- /dev/null +++ b/api/ovdashboard_api/routers/v1/_list_manager.py @@ -0,0 +1,86 @@ +import logging +from dataclasses import dataclass, field +from typing import Awaitable, Callable, Generic, ParamSpec, Self, TypeVar + +from fastapi import Depends, HTTPException, params, status + +_logger = logging.getLogger(__name__) + +_RESPONSE_OK = { + status.HTTP_200_OK: { + "description": "Operation successful", + }, +} + +Params = ParamSpec("Params") +Return = TypeVar("Return") + +type DependableFn[**Params, Return] = Callable[Params, Awaitable[Return]] + + +@dataclass(slots=True, frozen=True) +class Dependable(Generic[Params, Return]): + func: DependableFn[Params, Return] + responses: dict = field(default_factory=lambda: _RESPONSE_OK.copy()) + + +@dataclass(slots=True, frozen=True) +class ListManager: + lister: Dependable[[], list[str]] + filter: Dependable[[str], list[str]] + getter: Dependable[[str], str] + + @classmethod + def from_lister(cls, lister: Dependable[[], list[str]]) -> Self: + async def _filter_fn( + prefix: str, + names: list[str] = Depends(lister.func), + ) -> list[str]: + if isinstance(names, params.Depends): + names = await lister.func() + + _logger.debug("filter %s from %s", repr(prefix), repr(names)) + + return [item for item in names if item.lower().startswith(prefix.lower())] + + async def _getter_fn( + prefix: str, + names: list[str] = Depends(_filter_fn), + ) -> str: + if isinstance(names, params.Depends): + names = await _filter_fn(prefix) + + _logger.debug("get %s from %s", repr(prefix), repr(names)) + + match names: + case [name]: + return name + + case []: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + + case _: + raise HTTPException(status_code=status.HTTP_409_CONFLICT) + + return cls( + lister=lister, + filter=Dependable(_filter_fn), + getter=Dependable( + func=_getter_fn, + responses={ + **_RESPONSE_OK, + status.HTTP_404_NOT_FOUND: { + "description": "Prefix not found", + "content": None, + }, + status.HTTP_409_CONFLICT: { + "description": "Ambiguous prefix", + "content": None, + }, + }, + ), + ) + + @classmethod + def from_lister_fn(cls, lister_fn: DependableFn[[], list[str]]) -> Self: + return cls.from_lister(Dependable(lister_fn)) diff --git a/api/ovdashboard_api/routers/v1/aggregate.py b/api/ovdashboard_api/routers/v1/aggregate.py index 2dd3a2e..e0178ae 100644 --- a/api/ovdashboard_api/routers/v1/aggregate.py +++ b/api/ovdashboard_api/routers/v1/aggregate.py @@ -12,8 +12,8 @@ from fastapi import APIRouter, Depends from ...core.caldav import CalDAV from ...core.calevent import CalEvent -from ...core.config import Config, get_config -from ._common import LM_AGGREGATES, LM_CALENDARS +from ...core.config import Config +from ._common import LM_AGGREGATES, LM_CALENDARS, get_config _logger = logging.getLogger(__name__) diff --git a/api/ovdashboard_api/routers/v1/calendar.py b/api/ovdashboard_api/routers/v1/calendar.py index 8e78be2..1948342 100644 --- a/api/ovdashboard_api/routers/v1/calendar.py +++ b/api/ovdashboard_api/routers/v1/calendar.py @@ -11,8 +11,8 @@ import logging from fastapi import APIRouter, Depends from ...core.caldav import CalDAV, CalEvent -from ...core.config import CalendarUIConfig, Config, get_config -from ._common import LM_CALENDARS +from ...core.config import CalendarUIConfig, Config +from ._common import LM_CALENDARS, get_config _logger = logging.getLogger(__name__) diff --git a/api/ovdashboard_api/routers/v1/image.py b/api/ovdashboard_api/routers/v1/image.py index a7f191c..5ff6d8f 100644 --- a/api/ovdashboard_api/routers/v1/image.py +++ b/api/ovdashboard_api/routers/v1/image.py @@ -13,10 +13,10 @@ from fastapi import APIRouter, Depends from fastapi.responses import StreamingResponse from PIL import Image -from ...core.config import Config, ImageUIConfig, get_config +from ...core.config import Config, ImageUIConfig from ...core.dav_common import webdav_ensure_files, webdav_ensure_path from ...core.webdav import WebDAV -from ._common import LM_IMAGE, RP_IMAGE +from ._common import LM_IMAGE, RP_IMAGE, get_config _logger = logging.getLogger(__name__) diff --git a/api/ovdashboard_api/routers/v1/misc.py b/api/ovdashboard_api/routers/v1/misc.py index e8a4995..8ddbd42 100644 --- a/api/ovdashboard_api/routers/v1/misc.py +++ b/api/ovdashboard_api/routers/v1/misc.py @@ -11,8 +11,9 @@ from socket import AF_INET, SOCK_DGRAM, socket from fastapi import APIRouter, Depends -from ...core.config import Config, LogoUIConfig, ServerUIConfig, get_config +from ...core.config import Config, LogoUIConfig, ServerUIConfig from ...core.settings import SETTINGS +from ._common import get_config _logger = logging.getLogger(__name__) diff --git a/api/ovdashboard_api/routers/v1/ticker.py b/api/ovdashboard_api/routers/v1/ticker.py index 58f4a1b..21135c5 100644 --- a/api/ovdashboard_api/routers/v1/ticker.py +++ b/api/ovdashboard_api/routers/v1/ticker.py @@ -12,10 +12,10 @@ from typing import Iterator import markdown from fastapi import APIRouter, Depends -from ...core.config import Config, TickerUIConfig, get_config +from ...core.config import Config, TickerUIConfig from ...core.dav_common import webdav_ensure_files, webdav_ensure_path from ...core.webdav import WebDAV -from ._common import LM_TEXT, RP_TEXT +from ._common import LM_TEXT, RP_TEXT, get_config _logger = logging.getLogger(__name__)