Compare commits
2 commits
b6798df29c
...
9551ee7729
| Author | SHA1 | Date | |
|---|---|---|---|
| 9551ee7729 | |||
| 3a255db15a |
13 changed files with 156 additions and 149 deletions
|
|
@ -4,19 +4,19 @@ Definition of an asyncio compatible CalDAV calendar.
|
|||
Caches events using `timed_alru_cache`.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from functools import total_ordering
|
||||
from logging import getLogger
|
||||
from typing import Annotated, Self
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, StringConstraints
|
||||
from vobject.base import Component
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
type StrippedStr = Annotated[str, StringConstraints(strip_whitespace=True)]
|
||||
|
||||
|
||||
@total_ordering
|
||||
@functools.total_ordering
|
||||
class CalEvent(BaseModel):
|
||||
"""
|
||||
A CalDAV calendar event.
|
||||
|
|
|
|||
|
|
@ -2,18 +2,12 @@
|
|||
Python representation of the "config.txt" file inside the WebDAV directory.
|
||||
"""
|
||||
|
||||
import tomllib
|
||||
from logging import getLogger
|
||||
import logging
|
||||
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 = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TickerUIConfig(BaseModel):
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
Definition of WebDAV and CalDAV clients.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
import logging
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
|
||||
from .. import __file__ as OVD_INIT
|
||||
from .webdav import WebDAV
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def webdav_ensure_path(remote_path: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import logging
|
|||
import re
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
from asyncify import asyncify
|
||||
from cache import AsyncTTL
|
||||
from cache.key import KEY
|
||||
from requests import Response
|
||||
from webdav3.client import Client as WebDAVclient
|
||||
|
||||
from .settings import SETTINGS
|
||||
|
|
@ -21,7 +21,7 @@ class WebDAV:
|
|||
path,
|
||||
data=None,
|
||||
headers_ext=None,
|
||||
) -> Response:
|
||||
) -> requests.Response:
|
||||
res = super().execute_request(action, path, data, headers_ext)
|
||||
|
||||
# the "Content-Length" header can randomly be missing on txt files,
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@
|
|||
Dependables for defining Routers.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from logging import getLogger
|
||||
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 = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
_RESPONSE_OK = {
|
||||
status.HTTP_200_OK: {
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
86
api/ovdashboard_api/routers/v1/_list_manager.py
Normal file
86
api/ovdashboard_api/routers/v1/_list_manager.py
Normal file
|
|
@ -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))
|
||||
|
|
@ -6,17 +6,16 @@ Router "aggregate" provides:
|
|||
- getting aggregate calendar events by name prefix
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from typing import Iterator
|
||||
import logging
|
||||
|
||||
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 = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/aggregate", tags=["calendar"])
|
||||
|
||||
|
|
@ -31,9 +30,9 @@ async def start_router() -> None:
|
|||
responses=LM_AGGREGATES.lister.responses,
|
||||
)
|
||||
async def list_aggregate_calendars(
|
||||
names: Iterator[str] = Depends(LM_AGGREGATES.lister.func),
|
||||
names: list[str] = Depends(LM_AGGREGATES.lister.func),
|
||||
) -> list[str]:
|
||||
return list(names)
|
||||
return names
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
@ -41,9 +40,9 @@ async def list_aggregate_calendars(
|
|||
responses=LM_AGGREGATES.filter.responses,
|
||||
)
|
||||
async def find_aggregate_calendars(
|
||||
names: Iterator[str] = Depends(LM_AGGREGATES.filter.func),
|
||||
names: list[str] = Depends(LM_AGGREGATES.filter.func),
|
||||
) -> list[str]:
|
||||
return list(names)
|
||||
return names
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ Router "calendar" provides:
|
|||
- getting calendar events by calendar name prefix
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
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 = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/calendar", tags=["calendar"])
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ Router "file" provides:
|
|||
- getting files by name prefix
|
||||
"""
|
||||
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from logging import getLogger
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
|
@ -17,7 +17,7 @@ from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
|
|||
from ...core.webdav import WebDAV
|
||||
from ._common import LM_FILE, RP_FILE
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
_magic = Magic(mime=True)
|
||||
|
||||
router = APIRouter(prefix="/file", tags=["file"])
|
||||
|
|
|
|||
|
|
@ -6,20 +6,19 @@ Router "image" provides:
|
|||
- getting image files in a uniform format by name prefix
|
||||
"""
|
||||
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from logging import getLogger
|
||||
|
||||
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 = getLogger(__name__)
|
||||
_PATH_NAME = "image_dir"
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/image", tags=["image"])
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ Router "misc" provides:
|
|||
- getting the device IP
|
||||
"""
|
||||
|
||||
from importlib.metadata import version
|
||||
from logging import getLogger
|
||||
import importlib.metadata
|
||||
import logging
|
||||
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 = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/misc", tags=["misc"])
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ async def get_ip() -> str:
|
|||
|
||||
@router.get("/version")
|
||||
async def get_version() -> str:
|
||||
return version("ovdashboard-api")
|
||||
return importlib.metadata.version("ovdashboard-api")
|
||||
|
||||
|
||||
@router.get("/config/server")
|
||||
|
|
|
|||
|
|
@ -7,17 +7,16 @@ Router "text" provides:
|
|||
- getting text file HTML content by name prefix (using Markdown)
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
import logging
|
||||
|
||||
import markdown
|
||||
from fastapi import APIRouter, Depends
|
||||
from markdown import markdown
|
||||
|
||||
from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
|
||||
from ...core.webdav import WebDAV
|
||||
from ._common import LM_TEXT, RP_TEXT
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
_PATH_NAME = "text_dir"
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/text", tags=["text"])
|
||||
|
||||
|
|
@ -80,4 +79,4 @@ async def get_raw_text_by_prefix(
|
|||
async def get_html_by_prefix(
|
||||
text: str = Depends(_get_raw_text_by_prefix),
|
||||
) -> str:
|
||||
return markdown(text)
|
||||
return markdown.markdown(text)
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@ Router "ticker" provides:
|
|||
- getting the ticker's UI config
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
import logging
|
||||
from typing import Iterator
|
||||
|
||||
import markdown
|
||||
from fastapi import APIRouter, Depends
|
||||
from markdown import markdown
|
||||
|
||||
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 = getLogger(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/ticker", tags=["text"])
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ async def get_ticker_content(
|
|||
async def get_ticker(
|
||||
ticker_content: str = Depends(get_ticker_content),
|
||||
) -> str:
|
||||
return markdown(ticker_content)
|
||||
return markdown.markdown(ticker_content)
|
||||
|
||||
|
||||
@router.get("/raw")
|
||||
|
|
|
|||
Loading…
Reference in a new issue