diff --git a/api/advent22_api/core/advent_image.py b/api/advent22_api/core/advent_image.py index de8b66b..3a9b585 100644 --- a/api/advent22_api/core/advent_image.py +++ b/api/advent22_api/core/advent_image.py @@ -48,10 +48,10 @@ class AdventImage: self, xy: _XY, text: str | bytes, - font: ImageFont._Font, + font: "ImageFont._Font", anchor: str | None = "mm", **text_kwargs, - ) -> Image._Box | None: + ) -> "Image._Box | None": """ Koordinaten (links, oben, rechts, unten) des betroffenen Rechtecks bestimmen, wenn das Bild mit einem Text @@ -76,7 +76,7 @@ class AdventImage: async def get_average_color( self, - box: Image._Box, + box: "Image._Box", ) -> tuple[int, int, int]: """ Durchschnittsfarbe eines rechteckigen Ausschnitts in @@ -92,7 +92,7 @@ class AdventImage: self, xy: _XY, text: str | bytes, - font: ImageFont._Font, + font: "ImageFont._Font", anchor: str | None = "mm", **text_kwargs, ) -> None: diff --git a/api/advent22_api/core/calendar_config.py b/api/advent22_api/core/calendar_config.py index b029919..0e2107e 100644 --- a/api/advent22_api/core/calendar_config.py +++ b/api/advent22_api/core/calendar_config.py @@ -1,7 +1,13 @@ +import tomllib from typing import TypeAlias +import tomli_w +from fastapi import Depends from pydantic import BaseModel +from .config import Config +from .webdav import WebDAV + class DoorSaved(BaseModel): # Tag, an dem die Tür aufgeht @@ -23,3 +29,32 @@ class CalendarConfig(BaseModel): # Türen für die UI doors: DoorsSaved = [] + + @staticmethod + async def get_calendar_config( + cfg: Config = Depends(Config.get_config), + ) -> "CalendarConfig": + """ + Kalender Konfiguration lesen + """ + + txt = await WebDAV.read_str(path=f"files/{cfg.puzzle.calendar}") + return CalendarConfig.model_validate(tomllib.loads(txt)) + + async def set_calendar_config( + self, + cfg: Config = Depends(Config.get_config), + ) -> None: + """ + Kalender Konfiguration ändern + """ + + await WebDAV.write_str( + path=f"files/{cfg.puzzle.calendar}", + content=tomli_w.dumps( + self.model_dump( + exclude_defaults=True, + exclude_unset=True, + ) + ), + ) diff --git a/api/advent22_api/core/config.py b/api/advent22_api/core/config.py index 6da9173..d4a2a4c 100644 --- a/api/advent22_api/core/config.py +++ b/api/advent22_api/core/config.py @@ -1,5 +1,10 @@ +import tomllib + from pydantic import BaseModel +from .settings import SETTINGS +from .webdav import WebDAV + class User(BaseModel): name: str @@ -38,3 +43,12 @@ class Config(BaseModel): admin: User server: Server puzzle: Puzzle + + @staticmethod + async def get_config() -> "Config": + """ + Globale Konfiguration lesen + """ + + txt = await WebDAV.read_str(path=SETTINGS.config_filename) + return Config.model_validate(tomllib.loads(txt)) diff --git a/api/advent22_api/core/depends.py b/api/advent22_api/core/depends.py index 71322b6..40a4cd3 100644 --- a/api/advent22_api/core/depends.py +++ b/api/advent22_api/core/depends.py @@ -1,62 +1,20 @@ -import tomllib +from io import BytesIO from typing import cast -import tomli_w from fastapi import Depends from PIL import Image, ImageFont from .advent_image import _XY, AdventImage -from .calendar_config import CalendarConfig from .config import Config from .image_helpers import list_images_auto, load_image from .sequence_helpers import Random, set_len, shuffle -from .settings import SETTINGS from .webdav import WebDAV class AllTime: - @staticmethod - async def get_config() -> Config: - """ - Globale Konfiguration lesen - """ - - txt = await WebDAV.read_str(path=SETTINGS.config_filename) - return Config.model_validate(tomllib.loads(txt)) - - @staticmethod - async def get_calendar_config( - cfg: Config = Depends(get_config), - ) -> CalendarConfig: - """ - Kalender Konfiguration lesen - """ - - txt = await WebDAV.read_str(path=f"files/{cfg.puzzle.calendar}") - return CalendarConfig.model_validate(tomllib.loads(txt)) - - @staticmethod - async def set_calendar_config( - cal_cfg: CalendarConfig, - cfg: Config = Depends(get_config), - ) -> None: - """ - Kalender Konfiguration ändern - """ - - await WebDAV.write_str( - path=f"files/{cfg.puzzle.calendar}", - content=tomli_w.dumps( - cal_cfg.model_dump( - exclude_defaults=True, - exclude_unset=True, - ) - ), - ) - @staticmethod async def shuffle_solution( - cfg: Config = Depends(get_config), + cfg: Config = Depends(Config.get_config), ) -> str: """ Lösung: Reihenfolge zufällig bestimmen @@ -102,7 +60,7 @@ class Today: async def gen_auto_image( day: int, images: list[str] = Depends(AllTime.shuffle_images_auto), - cfg: Config = Depends(AllTime.get_config), + cfg: Config = Depends(Config.get_config), rnd: Random = Depends(get_random), part: str = Depends(get_part), ) -> Image.Image: @@ -115,7 +73,7 @@ class Today: image = await AdventImage.from_img(img) font = ImageFont.truetype( - font=await WebDAV.read_bytes(f"files/{cfg.server.font}"), + font=BytesIO(await WebDAV.read_bytes(f"files/{cfg.server.font}")), size=50, ) @@ -133,7 +91,7 @@ class Today: async def get_image( day: int, images: list[str] = Depends(AllTime.shuffle_images_auto), - cfg: Config = Depends(AllTime.get_config), + cfg: Config = Depends(Config.get_config), rnd: Random = Depends(get_random), part: str = Depends(get_part), ) -> Image.Image: diff --git a/api/advent22_api/core/image_helpers.py b/api/advent22_api/core/image_helpers.py index d494d7c..8c67b29 100644 --- a/api/advent22_api/core/image_helpers.py +++ b/api/advent22_api/core/image_helpers.py @@ -26,7 +26,7 @@ async def load_image(file_name: str) -> Image.Image: if not await WebDAV.file_exists(file_name): raise RuntimeError(f"DAV-File {file_name} does not exist!") - return Image.open(await WebDAV.read_bytes(file_name)) + return Image.open(BytesIO(await WebDAV.read_bytes(file_name))) async def api_return_image(img: Image.Image) -> StreamingResponse: diff --git a/api/advent22_api/core/sequence_helpers.py b/api/advent22_api/core/sequence_helpers.py index 6c550a2..049fc15 100644 --- a/api/advent22_api/core/sequence_helpers.py +++ b/api/advent22_api/core/sequence_helpers.py @@ -2,14 +2,14 @@ import itertools import random from typing import Any, Self, Sequence -from .depends import AllTime +from .config import Config class Random(random.Random): @classmethod async def get(cls, bonus_salt: Any = "") -> Self: - cfg = await AllTime.get_config() - return cls(f"{cfg.puzzle.solution}{bonus_salt}{cfg.puzzle.random_pepper}") + cfg = await Config.get_config() + return cls(f"{cfg.puzzle.solution}{cfg.puzzle.random_pepper}{bonus_salt}") async def shuffle(seq: Sequence, rnd: random.Random | None = None) -> list: diff --git a/api/advent22_api/core/webdav.py b/api/advent22_api/core/webdav.py index 6277090..034fadb 100644 --- a/api/advent22_api/core/webdav.py +++ b/api/advent22_api/core/webdav.py @@ -17,8 +17,8 @@ class WebDAV: } ) - @AsyncTTL(time_to_live=SETTINGS.cache_ttl) @classmethod + @AsyncTTL(time_to_live=SETTINGS.cache_ttl) async def list_files( cls, directory: str = "", @@ -33,8 +33,8 @@ class WebDAV: return [f"{directory}/{path}" for path in ls if regex.search(path)] - @AsyncTTL(time_to_live=SETTINGS.cache_ttl) @classmethod + @AsyncTTL(time_to_live=SETTINGS.cache_ttl) async def file_exists(cls, path: str) -> bool: """ `True`, wenn an Pfad `path` eine Datei existiert @@ -42,8 +42,8 @@ class WebDAV: return cls._webdav_client.check(path) - @AsyncTTL(time_to_live=SETTINGS.cache_ttl) @classmethod + @AsyncTTL(time_to_live=SETTINGS.cache_ttl) async def read_bytes(cls, path: str) -> bytes: """ Datei aus Pfad `path` als bytes laden @@ -51,10 +51,12 @@ class WebDAV: buffer = BytesIO() cls._webdav_client.resource(path).write_to(buffer) + buffer.seek(0) + return buffer.read() - @AsyncTTL(time_to_live=SETTINGS.cache_ttl) @classmethod + @AsyncTTL(time_to_live=SETTINGS.cache_ttl) async def read_str(cls, path: str, encoding="utf-8") -> str: """ Datei aus Pfad `path` als string laden @@ -63,9 +65,9 @@ class WebDAV: return (await cls.read_bytes(path)).decode(encoding=encoding).strip() @classmethod - async def write_buffer(cls, path: str, buffer: BytesIO) -> None: + async def write_bytes(cls, path: str, buffer: bytes) -> None: """ - Puffer `buffer` in Datei in Pfad `path` schreiben + Bytes `buffer` in Datei in Pfad `path` schreiben """ cls._webdav_client.resource(path).read_from(buffer) @@ -76,7 +78,4 @@ class WebDAV: String `content` in Datei in Pfad `path` schreiben """ - buffer = BytesIO(content.encode(encoding=encoding)) - buffer.seek(0) - - await cls.write_buffer(path, buffer) + await cls.write_bytes(path, content.encode(encoding=encoding)) diff --git a/api/advent22_api/routers/days.py b/api/advent22_api/routers/days.py index ef88346..9a2feea 100644 --- a/api/advent22_api/routers/days.py +++ b/api/advent22_api/routers/days.py @@ -2,10 +2,11 @@ from datetime import date from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import StreamingResponse +from PIL import Image -from ..config import Config, get_config -from ._image import AdventImage -from ._misc import api_return_image, get_image, shuffle +from ..core.config import Config +from ..core.depends import AllTime, Today +from ..core.image_helpers import api_return_image from .user import user_is_admin router = APIRouter(prefix="/days", tags=["days"]) @@ -13,17 +14,16 @@ router = APIRouter(prefix="/days", tags=["days"]) @router.on_event("startup") async def startup() -> None: - cfg = await get_config() + cfg = await Config.get_config() print(cfg.puzzle.solution) - print("".join(await shuffle(cfg.puzzle.solution))) + + shuffled_solution = await AllTime.shuffle_solution(cfg) + print(shuffled_solution) -@router.get("/letter/{index}") -async def get_letter( - index: int, - cfg: Config = Depends(get_config), -) -> str: - return (await shuffle(cfg.puzzle.solution))[index] +@router.get("/part/{day}") +async def get_part(part: str = Depends(Today.get_part)) -> str: + return part @router.get("/date") @@ -45,17 +45,17 @@ async def get_visible_days() -> int: async def user_can_view( - index: int, + day: int, ) -> bool: - return index < await get_visible_days() + return day < await get_visible_days() @router.get( - "/image/{index}", + "/image/{day}", response_class=StreamingResponse, ) async def get_image_for_day( - image: AdventImage = Depends(get_image), + image: Image.Image = Depends(Today.get_image), can_view: bool = Depends(user_can_view), is_admin: bool = Depends(user_is_admin), ) -> StreamingResponse: @@ -66,4 +66,4 @@ async def get_image_for_day( if not (can_view or is_admin): raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Wie unhöflich!!!") - return await api_return_image(image.img) + return await api_return_image(image) diff --git a/api/advent22_api/routers/general.py b/api/advent22_api/routers/general.py index ff4eb0b..2661374 100644 --- a/api/advent22_api/routers/general.py +++ b/api/advent22_api/routers/general.py @@ -1,15 +1,8 @@ from fastapi import APIRouter, Depends from fastapi.responses import StreamingResponse -from PIL import Image -from ..calendar_config import ( - CalendarConfig, - DoorsSaved, - get_calendar_config, - set_calendar_config, -) -from ..dav_common import dav_get_file -from ._misc import api_return_image +from ..core.calendar_config import CalendarConfig, DoorsSaved +from ..core.image_helpers import api_return_image, load_image router = APIRouter(prefix="/general", tags=["general"]) @@ -19,20 +12,18 @@ router = APIRouter(prefix="/general", tags=["general"]) response_class=StreamingResponse, ) async def get_image_for_day( - cal_cfg: CalendarConfig = Depends(get_calendar_config), + cal_cfg: CalendarConfig = Depends(CalendarConfig.get_calendar_config), ) -> StreamingResponse: """ Hintergrundbild laden """ - return await api_return_image( - Image.open(await dav_get_file(f"files/{cal_cfg.background}")) - ) + return await api_return_image(await load_image(f"files/{cal_cfg.background}")) @router.get("/doors") async def get_doors( - cal_cfg: CalendarConfig = Depends(get_calendar_config), + cal_cfg: CalendarConfig = Depends(CalendarConfig.get_calendar_config), ) -> DoorsSaved: """ Türchen lesen @@ -44,7 +35,7 @@ async def get_doors( @router.put("/doors") async def put_doors( doors: DoorsSaved, - cal_cfg: CalendarConfig = Depends(get_calendar_config), + cal_cfg: CalendarConfig = Depends(CalendarConfig.get_calendar_config), ) -> None: """ Türchen setzen @@ -54,4 +45,4 @@ async def put_doors( doors, key=lambda door: door.day, ) - await set_calendar_config(cal_cfg) + await cal_cfg.set_calendar_config() diff --git a/api/advent22_api/routers/user.py b/api/advent22_api/routers/user.py index d9f412a..255b54b 100644 --- a/api/advent22_api/routers/user.py +++ b/api/advent22_api/routers/user.py @@ -3,7 +3,7 @@ import secrets from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials -from ..config import Config, get_config +from ..core.config import Config router = APIRouter(prefix="/user", tags=["user"]) security = HTTPBasic() @@ -11,7 +11,7 @@ security = HTTPBasic() async def user_is_admin( credentials: HTTPBasicCredentials = Depends(security), - config: Config = Depends(get_config), + config: Config = Depends(Config.get_config), ) -> bool: username_correct = secrets.compare_digest(credentials.username, config.admin.name)