from __future__ import annotations import datetime import mimetypes from dataclasses import dataclass from typing import Protocol import filetype @dataclass(kw_only=True) class UploadCredentials: key: str bucket: str region: str access_key_id: str secret_access_key: str session_token: str expires_at: datetime.datetime class UploadAsset(Protocol): @property def data(self) -> bytes: ... @property def content_type(self) -> str: ... @dataclass(kw_only=True, frozen=True) class BinaryAsset: data: bytes content_type: str extension: str file_size: int filename: str | None = None @classmethod def from_data(cls, data: bytes, *, filename: str | None = None) -> BinaryAsset: content_type: str | None = filetype.guess_mime(data) if content_type is None: raise ValueError("Cannot get file content type.") extension = mimetypes.guess_extension(content_type) if extension is None: raise ValueError("Cannot get file extension.") return cls( data=data, content_type=content_type, extension=extension, file_size=len(data), filename=filename, ) @dataclass(kw_only=True, frozen=True) class ImageAsset: data: bytes content_type: str extension: str file_size: int filename: str | None = None size: tuple[int, int] is_cropped: bool = False is_compressed: bool = False @property def is_processed(self) -> bool: return self.is_compressed or self.is_cropped @classmethod def from_binary( cls, asset: BinaryAsset, *, size: tuple[int, int], is_cropped: bool = False, is_compressed: bool = False, ) -> ImageAsset: return cls( data=asset.data, content_type=asset.content_type, extension=asset.extension, file_size=asset.file_size, filename=asset.filename, size=size, is_cropped=is_cropped, is_compressed=is_compressed, )