Source code for osmium_chat.photo
from typing import TYPE_CHECKING, Any, Coroutine, Generator
from osmium_protos import (
PB_ChatPhoto,
PB_DownloadFilePart,
PB_FileRef,
PB_FileRefChatPhotoFileRef,
)
if TYPE_CHECKING:
from osmium_chat.client import Client
__all__: tuple[str, ...] = (
"Photo",
)
# 1 MiB per download request.
_DOWNLOAD_CHUNK_SIZE: int = 1024 * 1024
class _PhotoDownload:
"""Awaitable returned by :meth:`Photo.download`.
Supports both ``data = await photo.download()`` and
``async with photo.download() as data:``; in either form the result is the
full photo :class:`bytes`.
"""
__slots__: tuple[str, ...] = (
"_coro",
)
def __init__(self, coro: "Coroutine[Any, Any, bytes]") -> None:
self._coro = coro
def __await__(self) -> "Generator[Any, None, bytes]":
return self._coro.__await__()
async def __aenter__(self) -> bytes:
return await self._coro
async def __aexit__(self, *exc: object) -> None:
return None
[docs]
class Photo:
"""A chat photo: its file id and an inline preview blob."""
__slots__: tuple[str, ...] = (
"file_id",
"preview",
"_client",
)
def __init__(self, photo: PB_ChatPhoto, client: "Client") -> None:
"""Build a photo from a protobuf payload.
:param photo: The raw ``PB_ChatPhoto`` to read fields from.
:param client: The client used to download the photo.
"""
self.file_id: int = photo.file_id
self.preview: bytes = photo.preview
self._client = client
[docs]
def download(self) -> _PhotoDownload:
"""Fetch the full photo contents as :class:`bytes`.
The return value can be awaited directly or used as an async context
manager; both yield the complete photo bytes::
data = await photo.download()
async with photo.download() as data:
...
:returns: An awaitable / async context manager resolving to the bytes.
:raises RequestError: If the gateway rejects any part request.
"""
return _PhotoDownload(self._download())
async def _download(self) -> bytes:
"""Request the photo in :data:`_DOWNLOAD_CHUNK_SIZE`-byte windows and
reassemble them in order, stopping once a short or empty part signals
the end of the file.
"""
file_ref = PB_FileRef(chat_photo=PB_FileRefChatPhotoFileRef(file_id=self.file_id))
chunks: list[bytes] = []
offset = 0
while True:
result = await self._client.request(PB_DownloadFilePart(
file_ref=file_ref,
offset=offset,
length=_DOWNLOAD_CHUNK_SIZE,
))
part = result.file_part
if part is None or not part.data:
break
chunks.append(part.data)
offset += len(part.data)
if len(part.data) < _DOWNLOAD_CHUNK_SIZE:
break
return b"".join(chunks)