ovdashboard/api/ovdashboard_api/routers/v1/_common.py

216 lines
5.5 KiB
Python

"""
Dependables for defining Routers.
"""
import re
from dataclasses import dataclass
from logging import getLogger
from typing import Awaitable, Callable, Generic, ParamSpec, TypeVar
from fastapi import Depends, HTTPException, status
from webdav3.exceptions import RemoteResourceNotFound
from ...core.config import get_config
from ...core.webdav import WebDAV
# from ...core.caldav import CalDAV
# from ...core.config import Config, get_config
_logger = getLogger(__name__)
_RESPONSE_OK = {
status.HTTP_200_OK: {
"description": "Operation successful",
},
}
Params = ParamSpec("Params")
Return = TypeVar("Return")
type _DepCallable[**Params, Return] = Callable[Params, Awaitable[Return]]
@dataclass(slots=True, frozen=True)
class Dependable(Generic[Params, Return]):
func: _DepCallable[Params, Return]
responses: dict
@dataclass(init=False, slots=True, frozen=True)
class ListManager:
lister: Dependable[[], list[str]]
filter: Dependable[[str], list[str]]
getter: Dependable[[str], str]
def __init__(
self,
lister: Dependable[[], list[str]],
) -> None:
object.__setattr__(self, "lister", lister)
async def _filter(
prefix: str,
names: list[str] = Depends(self.lister.func),
) -> list[str]:
return [item for item in names if item.lower().startswith(prefix.lower())]
object.__setattr__(
self, "filter", Dependable(func=_filter, responses=_RESPONSE_OK)
)
async def _getter(
names: list[str] = Depends(self.filter.func),
) -> str:
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)
object.__setattr__(
self,
"getter",
Dependable(
func=_getter,
responses={
**_RESPONSE_OK,
status.HTTP_404_NOT_FOUND: {
"description": "Prefix not found",
"content": None,
},
status.HTTP_409_CONFLICT: {
"description": "Ambiguous prefix",
"content": None,
},
},
),
)
def get_remote_path(
path_name: str,
) -> _DepCallable[[], str]:
async def _get_remote_path() -> str:
cfg = await get_config()
return getattr(cfg, path_name)
return _get_remote_path
def list_files(
rp: _DepCallable[[], str],
*,
re: re.Pattern[str],
) -> Dependable[[], list[str]]:
"""
List files in remote `path` matching the RegEx `re`
"""
async def _list_files(
remote_path: str = Depends(rp),
) -> list[str]:
try:
return await WebDAV.list_files(remote_path, regex=re)
except RemoteResourceNotFound:
_logger.error("WebDAV path %s lost!", repr(remote_path))
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return Dependable(
func=_list_files,
responses={
**_RESPONSE_OK,
status.HTTP_404_NOT_FOUND: {
"description": "Remote path not found",
"content": None,
},
},
)
# async def list_calendar_names() -> list[str]:
# """
# List calendar names
# """
# return await CalDAV.calendars
# async def list_aggregate_names(
# cfg: Config = Depends(get_config),
# ) -> list[str]:
# """
# List aggregate calendar names
# """
# return list(cfg.calendar.aggregates.keys())
def filter_prefix(
src: Dependable[[], list[str]],
) -> Dependable[[str], list[str]]:
"""
Filter names from an async source `src` for names starting with a given prefix.
"""
async def _filter_prefix(
prefix: str,
) -> list[str]:
return list(
item
for item in (await src.func())
if item.lower().startswith(prefix.lower())
)
return Dependable(
func=_filter_prefix,
responses={
**_RESPONSE_OK,
status.HTTP_404_NOT_FOUND: {
"description": f"Failure in lister {src.__class__.__name__!r}",
"content": None,
},
},
)
def filter_prefix_unique(
src: Dependable[[str], list[str]],
) -> Dependable[[str], str]:
"""
Determines if a given prefix is unique in the list produced by the async source `src`.
On success, produces the unique name with that prefix. Otherwise, throws a HTTPException.
"""
async def _filter_prefix_unique(
prefix: str,
) -> str:
names = await src.func(prefix)
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 Dependable(
func=_filter_prefix_unique,
responses={
**_RESPONSE_OK,
status.HTTP_404_NOT_FOUND: {
"description": "Prefix not found",
"content": None,
},
status.HTTP_409_CONFLICT: {
"description": "Ambiguous prefix",
"content": None,
},
},
)