from __future__ import annotations import io from cgi import parse_header from shutil import copyfileobj from typing import Any, BinaryIO, Dict, Iterable, Optional class FileStorage: """A thin wrapper over incoming files.""" def __init__( self, stream: BinaryIO = None, filename: str = None, name: str = None, content_type: str = None, headers: Dict = None, ) -> None: self.name = name self.stream = stream or io.BytesIO() self.filename = filename if headers is None: headers = {} self.headers = headers if content_type is not None: headers["Content-Type"] = content_type @property def content_type(self) -> Optional[str]: """The content-type sent in the header.""" return self.headers.get("Content-Type") @property def content_length(self) -> int: """The content-length sent in the header.""" return int(self.headers.get("content-length", 0)) @property def mimetype(self) -> str: """Returns the mimetype parsed from the Content-Type header.""" return parse_header(self.headers.get("Content-Type"))[0] @property def mimetype_params(self) -> Dict[str, str]: """Returns the params parsed from the Content-Type header.""" return parse_header(self.headers.get("Content-Type"))[1] def save(self, destination: BinaryIO, buffer_size: int = 16384) -> None: """Save the file to the destination. Arguments: destination: A filename (str) or file object to write to. buffer_size: Buffer size as used as length in :func:`shutil.copyfileobj`. """ close_destination = False if isinstance(destination, str): destination = open(destination, "wb") close_destination = True try: copyfileobj(self.stream, destination, buffer_size) # type: ignore finally: if close_destination: destination.close() def close(self) -> None: try: self.stream.close() except Exception: pass def __bool__(self) -> bool: return bool(self.filename) def __getattr__(self, name: str) -> Any: return getattr(self.stream, name) def __iter__(self) -> Iterable[bytes]: return iter(self.stream) def __repr__(self) -> str: return f"<{self.__class__.__name__}: {self.filename} ({self.content_type}))>"