from abc import abstractmethod from logging import Logger from pathlib import Path from typing import ( Any, Final, Iterable, Iterator, Protocol, Sequence, TypeVar, overload, ) from numpy.typing import NDArray _T = TypeVar("_T") class ProgressHandler(Protocol): @overload def wrap(self, values: Sequence[_T]) -> Iterator[_T]: ... @overload def wrap(self, values: Iterable[_T], total: int) -> Iterator[_T]: ... class FileHandler: @abstractmethod def make_path(self, filename: str) -> Path: ... @abstractmethod def notify_created(self, path: Path): ... @abstractmethod def _create( self, path: Path, content: bytes, text: bool = False ) -> Path | None: ... def create(self, filename: str, content: bytes, text: bool = False): path = self._create(self.make_path(filename), content, text) if path is not None: self.notify_created(path) def image(self, filename: str, image: NDArray[Any]): import imageio.v3 as iio from pygifsicle import optimize # type: ignore path = self.make_path(filename) iio.imwrite(path, image) # type: ignore optimize(path, options=["--no-warnings"]) self.notify_created(path) def video(self, filename: str, video: NDArray[Any]): import cv2 path = self.make_path(filename) fps = 5 out = cv2.VideoWriter( path.as_posix(), cv2.VideoWriter_fourcc(*"vp80"), # type: ignore fps, (video.shape[2], video.shape[1]), True, ) for picture in video: out.write(picture) out.release() self.notify_created(path) class BaseSolver: def __init__( self, logger: Logger, verbose: bool, year: int, day: int, progress: ProgressHandler, files: FileHandler | None = None, ): self.logger: Final = logger self.verbose: Final = verbose self.year: Final = year self.day: Final = day self.progress: Final = progress self.files: Final = files @abstractmethod def solve(self, input: str) -> Iterator[Any] | None: ...