py3.12/refac: partly working

This commit is contained in:
Jörn-Michael Miehe 2023-10-20 10:43:15 +02:00
parent a585d97f9f
commit b8b1c30313
10 changed files with 159 additions and 254 deletions

View file

@ -7,40 +7,34 @@ This file: Sets up logging.
from logging.config import dictConfig
from pydantic import BaseModel
from .core.settings import SETTINGS
# Logging configuration to be set for the server.
# https://stackoverflow.com/a/67937084
class LogConfig(BaseModel):
"""
Logging configuration to be set for the server.
https://stackoverflow.com/a/67937084
"""
# Logging config
version: int = 1
disable_existing_loggers: bool = False
formatters: dict = {
LOG_CONFIG = dict(
version=1,
disable_existing_loggers=False,
formatters={
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(levelprefix)s [%(asctime)s] %(name)s: %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
}
handlers: dict = {
},
handlers={
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
}
loggers: dict = {
},
loggers={
"ovdashboard_api": {
"handlers": ["default"],
"level": SETTINGS.log_level,
},
}
},
)
dictConfig(LogConfig().model_dump())
dictConfig(LOG_CONFIG)

View file

@ -6,14 +6,19 @@ Main script for `ovdashboard_api` module.
Creates the main `FastAPI` app.
"""
import logging
import time
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from .core.settings import SETTINGS
from .dav_common import webdav_check
from .core.webdav import WebDAV
from .routers import v1_router
_logger = logging.getLogger(__name__)
app = FastAPI(
title="OVDashboard API",
description="This API enables the `OVDashboard` service.",
@ -31,7 +36,11 @@ app = FastAPI(
)
app.include_router(v1_router)
webdav_check()
_logger.info(
"Production mode is %s.",
"enabled" if SETTINGS.production_mode else "disabled",
)
if SETTINGS.production_mode:
# Mount frontend in production mode
@ -44,15 +53,27 @@ if SETTINGS.production_mode:
name="frontend",
)
for _ in range(SETTINGS.webdav.retries):
if WebDAV._webdav_client.check(""):
break
_logger.warning(
"Waiting for WebDAV connection to %s ...",
repr(SETTINGS.webdav.url),
)
time.sleep(30)
else:
assert WebDAV._webdav_client.check("")
# Allow CORS in debug mode
app.add_middleware(
CORSMiddleware,
allow_origins=[
"*",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_methods=["*"],
allow_origins=["*"],
expose_headers=["*"],
)
_logger.debug("WebDAV connection ok.")

View file

@ -4,13 +4,12 @@ Python representation of the "config.txt" file inside the WebDAV directory.
import tomllib
from logging import getLogger
from typing import Any, Self
from typing import Any
import tomli_w
from pydantic import BaseModel
from webdav3.exceptions import RemoteResourceNotFound
from .caldav import CalDAV
# from .caldav import CalDAV
from .settings import SETTINGS
from .webdav import WebDAV
@ -109,27 +108,27 @@ class Config(BaseModel):
ticker: TickerConfig = TickerConfig()
calendar: CalendarConfig = CalendarConfig()
@classmethod
async def get(cls) -> Self:
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 = cls.model_validate(tomllib.loads(cfg_str))
cfg = Config.model_validate(tomllib.loads(cfg_str))
except RemoteResourceNotFound:
_logger.warning(
f"Config file {SETTINGS.webdav.config_filename!r} not found, creating ..."
)
cfg = cls()
cfg.calendar.aggregates["All Events"] = list(await CalDAV.calendars)
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()),
)
# await WebDAV.write_str(
# SETTINGS.webdav.config_filename,
# tomli_w.dumps(cfg.model_dump()),
# )
return cfg

View file

@ -0,0 +1,60 @@
"""
Definition of WebDAV and CalDAV clients.
"""
from logging import getLogger
from os import path
from pathlib import Path
from .. import __file__ as OVD_INIT
from .webdav import WebDAV
_logger = getLogger(__name__)
def webdav_ensure_path(remote_path: str) -> bool:
if WebDAV._webdav_client.check(remote_path):
_logger.debug(
"WebDAV path %s found.",
repr(remote_path),
)
return True
_logger.info(
"WebDAV path %s not found, creating ...",
repr(remote_path),
)
WebDAV._webdav_client.mkdir(remote_path)
return False
def get_skel_path(skel_file: str) -> Path:
skel_path = path.dirname(Path(OVD_INIT).absolute())
return Path(skel_path).joinpath("skel", skel_file)
def webdav_upload_skel(remote_path: str, *skel_files: str) -> None:
for skel_file in skel_files:
_logger.debug(
"Creating WebDAV file %s ...",
repr(skel_file),
)
WebDAV._webdav_client.upload_file(
f"{remote_path}/{skel_file}",
get_skel_path(skel_file),
)
def webdav_ensure_files(remote_path: str, *file_names: str) -> None:
missing_files = (
file_name
for file_name in file_names
if not WebDAV._webdav_client.check(f"{remote_path}/{file_name}")
)
webdav_upload_skel(
remote_path,
*missing_files,
)

View file

@ -1,172 +0,0 @@
"""
Definition of WebDAV and CalDAV clients.
"""
from functools import lru_cache
from logging import getLogger
from os import path
from pathlib import Path
from time import sleep
from typing import Any, Iterator
from asyncify import asyncify
from caldav import DAVClient as CalDAVclient
from caldav import Principal as CalDAVPrincipal
from webdav3.client import Client as WebDAVclient
from webdav3.client import Resource as WebDAVResource
from . import __file__ as OVD_INIT
from .core.settings import SETTINGS
_WEBDAV_CLIENT = WebDAVclient(
{
"webdav_hostname": SETTINGS.webdav.url,
"webdav_login": SETTINGS.webdav.username,
"webdav_password": SETTINGS.webdav.password,
"disable_check": SETTINGS.webdav_disable_check,
}
)
_logger = getLogger(__name__)
def webdav_check() -> None:
"""
Checks if base resources are available.
"""
_logger.info(
"Production mode is %s.",
"enabled" if SETTINGS.production_mode else "disabled",
)
if SETTINGS.production_mode:
for _ in range(SETTINGS.webdav.retries):
if _WEBDAV_CLIENT.check(""):
break
_logger.warning(
"Waiting for WebDAV connection to %s ...",
repr(SETTINGS.webdav.url),
)
sleep(30)
_logger.debug("WebDAV connection ok.")
elif not _WEBDAV_CLIENT.check(""):
_logger.error(
"WebDAV connection to %s FAILED!",
repr(SETTINGS.webdav.url),
)
raise ConnectionError(SETTINGS.webdav.url)
_logger.debug("WebDAV connection ok.")
if not _WEBDAV_CLIENT.check(SETTINGS.webdav_prefix):
_logger.error(
"WebDAV prefix directory %s NOT FOUND, please create it!",
repr(SETTINGS.webdav_prefix),
)
raise FileNotFoundError(SETTINGS.webdav_prefix)
_logger.debug("WebDAV prefix directory found.")
def webdav_ensure_path(remote_path: str) -> bool:
remote_path = f"{SETTINGS.webdav_prefix}/{remote_path}"
if _WEBDAV_CLIENT.check(remote_path):
_logger.debug(
"WebDAV path %s found.",
repr(remote_path),
)
return True
_logger.info(
"WebDAV path %s not found, creating ...",
repr(remote_path),
)
_WEBDAV_CLIENT.mkdir(remote_path)
return False
def get_skel_path(skel_file: str) -> Path:
skel_path = path.dirname(Path(OVD_INIT).absolute())
return Path(skel_path).joinpath("skel", skel_file)
def webdav_upload_skel(remote_path: str, *skel_files: str) -> None:
remote_path = f"{SETTINGS.webdav_prefix}/{remote_path}"
for skel_file in skel_files:
_logger.debug(
"Creating WebDAV file %s ...",
repr(skel_file),
)
_WEBDAV_CLIENT.upload_file(
f"{remote_path}/{skel_file}",
get_skel_path(skel_file),
)
def webdav_ensure_files(remote_path: str, *file_names: str) -> None:
missing_files = (
file_name
for file_name in file_names
if not _WEBDAV_CLIENT.check(
path.join(
SETTINGS.webdav_prefix,
remote_path,
file_name,
)
)
)
webdav_upload_skel(
remote_path,
*missing_files,
)
@lru_cache(maxsize=SETTINGS.cache_size)
def webdav_resource(remote_path: Any) -> WebDAVResource:
"""
Gets a resource using the main WebDAV client.
"""
return _WEBDAV_CLIENT.resource(f"{SETTINGS.webdav_prefix}/{remote_path}")
@asyncify
def webdav_list(remote_path: str) -> list[str]:
"""
Asynchronously lists a WebDAV path using the main WebDAV client.
"""
return _WEBDAV_CLIENT.list(f"{SETTINGS.webdav_prefix}/{remote_path}")
_CALDAV_CLIENT = CalDAVclient(
url=SETTINGS.caldav.url,
username=SETTINGS.caldav.username,
password=SETTINGS.caldav.password,
)
def caldav_principal() -> CalDAVPrincipal:
"""
Gets the `Principal` object of the main CalDAV client.
"""
return _CALDAV_CLIENT.principal()
@asyncify
def caldav_list() -> Iterator[str]:
"""
Asynchronously lists all calendars using the main WebDAV client.
"""
return (str(cal.name) for cal in caldav_principal().calendars())

View file

@ -6,16 +6,19 @@ This file: Main API router definition.
from fastapi import APIRouter
from . import aggregate, calendar, file, image, misc, text, ticker
# from . import aggregate, calendar, file, image, misc, text, ticker
from . import file, misc
router = APIRouter(prefix="/api/v1")
router.include_router(misc.router)
router.include_router(text.router)
router.include_router(ticker.router)
router.include_router(image.router)
# router.include_router(text.router)
# router.include_router(ticker.router)
# router.include_router(image.router)
router.include_router(file.router)
router.include_router(calendar.router)
router.include_router(aggregate.router)
# router.include_router(calendar.router)
# router.include_router(aggregate.router)
__all__ = ["router"]

View file

@ -10,8 +10,9 @@ from typing import Iterator, Protocol
from fastapi import HTTPException, status
from webdav3.exceptions import RemoteResourceNotFound
from ...config import Config
from ...dav_common import caldav_list, webdav_list
from ...core.caldav import CalDAV
from ...core.config import get_config
from ...core.webdav import WebDAV
_logger = getLogger(__name__)
@ -32,7 +33,7 @@ _RESPONSE_OK = {
}
@dataclass(frozen=True)
@dataclass(frozen=True, slots=True)
class FileNameLister:
"""
Can be called to create an iterator containing file names.
@ -55,13 +56,13 @@ class FileNameLister:
@property
async def remote_path(self) -> str:
cfg = await Config.get()
cfg = await get_config()
return str(cfg.model_dump()[self.path_name])
return getattr(cfg, self.path_name)
async def __call__(self) -> Iterator[str]:
try:
file_names = await webdav_list(await self.remote_path)
file_names = await WebDAV.list_files(await self.remote_path)
return (name for name in file_names if self.re.search(name))
@ -73,29 +74,29 @@ class FileNameLister:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
@dataclass(frozen=True)
@dataclass(frozen=True, slots=True)
class CalendarNameLister:
"""
Can be called to create an iterator containing calendar names.
"""
async def __call__(self) -> Iterator[str]:
return await caldav_list()
return iter(await CalDAV.calendars)
@dataclass(frozen=True)
@dataclass(frozen=True, slots=True)
class AggregateNameLister:
"""
Can be called to create an iterator containing aggregate calendar names.
"""
async def __call__(self) -> Iterator[str]:
cfg = await Config.get()
cfg = await get_config()
return iter(cfg.calendar.aggregates.keys())
@dataclass(frozen=True)
@dataclass(frozen=True, slots=True)
class PrefixFinder:
"""
Can be called to create an iterator containing some names, all starting
@ -124,7 +125,7 @@ class PrefixFinder:
)
@dataclass(frozen=True)
@dataclass(frozen=True, slots=True)
class PrefixUnique:
"""
Can be called to determine if a given prefix is unique in the list

View file

@ -11,9 +11,9 @@ from typing import Iterator
from fastapi import APIRouter, Depends
from ovdashboard_api.config import Config
from ...dav_calendar import CalEvent, DavCalendar
from ...core.caldav import CalDAV
from ...core.calevent import CalEvent
from ...core.config import Config
from ._common import AggregateNameLister, PrefixFinder, PrefixUnique
from .calendar import calendar_unique

View file

@ -15,8 +15,8 @@ from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse
from magic import Magic
from ...dav_common import webdav_ensure_files, webdav_ensure_path
from ...dav_file import DavFile
# from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
from ...core.webdav import WebDAV
from ._common import FileNameLister, PrefixFinder, PrefixUnique
_logger = getLogger(__name__)
@ -40,12 +40,12 @@ file_unique = PrefixUnique(file_finder)
async def start_router() -> None:
_logger.debug(f"{router.prefix} router starting.")
if not webdav_ensure_path(await file_lister.remote_path):
webdav_ensure_files(
await file_lister.remote_path,
"logo.svg",
"thw.svg",
)
# if not webdav_ensure_path(await file_lister.remote_path):
# webdav_ensure_files(
# await file_lister.remote_path,
# "logo.svg",
# "thw.svg",
# )
@router.get(
@ -79,8 +79,7 @@ async def get_file(
prefix: str,
name: str = Depends(file_unique),
) -> StreamingResponse:
dav_file = DavFile(f"{await file_lister.remote_path}/{name}")
buffer = BytesIO(await dav_file.as_bytes)
buffer = BytesIO(await WebDAV.read_bytes(f"{await file_lister.remote_path}/{name}"))
mime = _magic.from_buffer(buffer.read(2048))
buffer.seek(0)

View file

@ -11,8 +11,8 @@ from socket import AF_INET, SOCK_DGRAM, socket
from fastapi import APIRouter, Depends
from ...config import Config, LogoUIConfig, ServerUIConfig
from ...settings import SETTINGS
from ...core.config import Config, LogoUIConfig, ServerUIConfig, get_config
from ...core.settings import SETTINGS
_logger = getLogger(__name__)
@ -51,7 +51,7 @@ async def get_version() -> str:
response_model=ServerUIConfig,
)
async def get_server_ui_config(
cfg: Config = Depends(Config.get),
cfg: Config = Depends(get_config),
) -> ServerUIConfig:
return cfg.server
@ -61,6 +61,6 @@ async def get_server_ui_config(
response_model=LogoUIConfig,
)
async def get_logo_ui_config(
cfg: Config = Depends(Config.get),
cfg: Config = Depends(get_config),
) -> LogoUIConfig:
return cfg.logo