11from contextlib import asynccontextmanager
22from dataclasses import dataclass
3- from typing import AsyncGenerator , Literal , Union , cast
3+ from typing import AsyncGenerator , Literal , Optional , Union , cast
44from uuid import UUID
55
66from aiofiles .tempfile import SpooledTemporaryFile
77
88from pybotx .bot .contextvars import bot_id_var , bot_var , chat_id_var
99from pybotx .constants import CHUNK_SIZE
10+ from pybotx .missing import MissingOptional , Undefined
1011from pybotx .models .api_base import VerifiedPayloadBaseModel
1112from pydantic import ConfigDict
1213from pybotx .models .enums import (
@@ -29,9 +30,27 @@ class AsyncFileBase:
2930 _file_url : str
3031 _file_mimetype : str
3132 _file_hash : str
33+ file_preview : Optional [str ] = None
34+ file_preview_height : Optional [int ] = None
35+ file_preview_width : Optional [int ] = None
36+ file_encryption_algo : Optional [str ] = None
37+ chunk_size : Optional [int ] = None
38+ caption : Optional [str ] = None
39+
40+ @property
41+ def file_url (self ) -> str :
42+ return self ._file_url
43+
44+ @property
45+ def file_mimetype (self ) -> str :
46+ return self ._file_mimetype
47+
48+ @property
49+ def file_hash (self ) -> str :
50+ return self ._file_hash
3251
3352 @asynccontextmanager
34- async def open (self ) -> AsyncGenerator [SpooledTemporaryFile , None ]:
53+ async def open (self , * , is_preview : bool = False ) -> AsyncGenerator [SpooledTemporaryFile , None ]:
3554 bot = bot_var .get ()
3655
3756 async with SpooledTemporaryFile (max_size = CHUNK_SIZE ) as tmp_file :
@@ -40,6 +59,7 @@ async def open(self) -> AsyncGenerator[SpooledTemporaryFile, None]:
4059 chat_id = chat_id_var .get (),
4160 file_id = self ._file_id ,
4261 async_buffer = tmp_file ,
62+ is_preview = is_preview ,
4363 )
4464
4565 yield tmp_file
@@ -53,7 +73,7 @@ class Image(AsyncFileBase):
5373@dataclass
5474class Video (AsyncFileBase ):
5575 type : Literal [AttachmentTypes .VIDEO ]
56- duration : int
76+ duration : int = 0
5777
5878
5979@dataclass
@@ -64,7 +84,7 @@ class Document(AsyncFileBase):
6484@dataclass
6585class Voice (AsyncFileBase ):
6686 type : Literal [AttachmentTypes .VOICE ]
67- duration : int
87+ duration : int = 0
6888
6989
7090class APIAsyncFileBase (VerifiedPayloadBaseModel ):
@@ -75,8 +95,14 @@ class APIAsyncFileBase(VerifiedPayloadBaseModel):
7595 file_name : str
7696 file_size : int
7797 file_hash : str
98+ file_preview : MissingOptional [str ] = Undefined
99+ file_preview_height : MissingOptional [int ] = Undefined
100+ file_preview_width : MissingOptional [int ] = Undefined
101+ file_encryption_algo : MissingOptional [str ] = Undefined
102+ chunk_size : MissingOptional [int ] = Undefined
103+ caption : MissingOptional [str ] = Undefined
78104
79- model_config = ConfigDict (extra = "allow" )
105+ model_config = ConfigDict (extra = "allow" , arbitrary_types_allowed = True )
80106
81107
82108class ApiAsyncFileImage (APIAsyncFileBase ):
@@ -107,6 +133,14 @@ class ApiAsyncFileVoice(APIAsyncFileBase):
107133File = Union [Image , Video , Document , Voice ]
108134
109135
136+ def _to_optional (value : MissingOptional [Union [str , int ]]) -> Optional [Union [str , int ]]:
137+ return None if value is Undefined else value
138+
139+
140+ def _to_missing (value : Optional [Union [str , int ]]) -> MissingOptional [Union [str , int ]]:
141+ return Undefined if value is None else value
142+
143+
110144def convert_async_file_from_domain (file : File ) -> APIAsyncFile :
111145 attachment_type = convert_attachment_type_from_domain (file .type )
112146
@@ -121,6 +155,12 @@ def convert_async_file_from_domain(file: File) -> APIAsyncFile:
121155 file = file ._file_url ,
122156 file_mime_type = file ._file_mimetype ,
123157 file_hash = file ._file_hash ,
158+ file_preview = _to_missing (file .file_preview ),
159+ file_preview_height = _to_missing (file .file_preview_height ),
160+ file_preview_width = _to_missing (file .file_preview_width ),
161+ file_encryption_algo = _to_missing (file .file_encryption_algo ),
162+ chunk_size = _to_missing (file .chunk_size ),
163+ caption = _to_missing (file .caption ),
124164 )
125165
126166 if attachment_type == APIAttachmentTypes .VIDEO :
@@ -135,6 +175,12 @@ def convert_async_file_from_domain(file: File) -> APIAsyncFile:
135175 file = file ._file_url ,
136176 file_mime_type = file ._file_mimetype ,
137177 file_hash = file ._file_hash ,
178+ file_preview = _to_missing (file .file_preview ),
179+ file_preview_height = _to_missing (file .file_preview_height ),
180+ file_preview_width = _to_missing (file .file_preview_width ),
181+ file_encryption_algo = _to_missing (file .file_encryption_algo ),
182+ chunk_size = _to_missing (file .chunk_size ),
183+ caption = _to_missing (file .caption ),
138184 )
139185
140186 if attachment_type == APIAttachmentTypes .DOCUMENT :
@@ -148,6 +194,12 @@ def convert_async_file_from_domain(file: File) -> APIAsyncFile:
148194 file = file ._file_url ,
149195 file_mime_type = file ._file_mimetype ,
150196 file_hash = file ._file_hash ,
197+ file_preview = _to_missing (file .file_preview ),
198+ file_preview_height = _to_missing (file .file_preview_height ),
199+ file_preview_width = _to_missing (file .file_preview_width ),
200+ file_encryption_algo = _to_missing (file .file_encryption_algo ),
201+ chunk_size = _to_missing (file .chunk_size ),
202+ caption = _to_missing (file .caption ),
151203 )
152204
153205 if attachment_type == APIAttachmentTypes .VOICE :
@@ -162,6 +214,12 @@ def convert_async_file_from_domain(file: File) -> APIAsyncFile:
162214 file = file ._file_url ,
163215 file_mime_type = file ._file_mimetype ,
164216 file_hash = file ._file_hash ,
217+ file_preview = _to_missing (file .file_preview ),
218+ file_preview_height = _to_missing (file .file_preview_height ),
219+ file_preview_width = _to_missing (file .file_preview_width ),
220+ file_encryption_algo = _to_missing (file .file_encryption_algo ),
221+ chunk_size = _to_missing (file .chunk_size ),
222+ caption = _to_missing (file .caption ),
165223 )
166224
167225 raise NotImplementedError (f"Unsupported attachment type: { attachment_type } " )
@@ -182,6 +240,21 @@ def convert_async_file_to_domain(async_file: APIAsyncFile) -> File:
182240 _file_mimetype = async_file .file_mime_type ,
183241 _file_url = async_file .file ,
184242 _file_hash = async_file .file_hash ,
243+ file_preview = cast (Optional [str ], _to_optional (async_file .file_preview )),
244+ file_preview_height = cast (
245+ Optional [int ],
246+ _to_optional (async_file .file_preview_height ),
247+ ),
248+ file_preview_width = cast (
249+ Optional [int ],
250+ _to_optional (async_file .file_preview_width ),
251+ ),
252+ file_encryption_algo = cast (
253+ Optional [str ],
254+ _to_optional (async_file .file_encryption_algo ),
255+ ),
256+ chunk_size = cast (Optional [int ], _to_optional (async_file .chunk_size )),
257+ caption = cast (Optional [str ], _to_optional (async_file .caption )),
185258 )
186259
187260 if attachment_type == AttachmentTypes .VIDEO :
@@ -197,6 +270,21 @@ def convert_async_file_to_domain(async_file: APIAsyncFile) -> File:
197270 _file_mimetype = async_file .file_mime_type ,
198271 _file_url = async_file .file ,
199272 _file_hash = async_file .file_hash ,
273+ file_preview = cast (Optional [str ], _to_optional (async_file .file_preview )),
274+ file_preview_height = cast (
275+ Optional [int ],
276+ _to_optional (async_file .file_preview_height ),
277+ ),
278+ file_preview_width = cast (
279+ Optional [int ],
280+ _to_optional (async_file .file_preview_width ),
281+ ),
282+ file_encryption_algo = cast (
283+ Optional [str ],
284+ _to_optional (async_file .file_encryption_algo ),
285+ ),
286+ chunk_size = cast (Optional [int ], _to_optional (async_file .chunk_size )),
287+ caption = cast (Optional [str ], _to_optional (async_file .caption )),
200288 )
201289
202290 if attachment_type == AttachmentTypes .DOCUMENT :
@@ -211,6 +299,21 @@ def convert_async_file_to_domain(async_file: APIAsyncFile) -> File:
211299 _file_mimetype = async_file .file_mime_type ,
212300 _file_url = async_file .file ,
213301 _file_hash = async_file .file_hash ,
302+ file_preview = cast (Optional [str ], _to_optional (async_file .file_preview )),
303+ file_preview_height = cast (
304+ Optional [int ],
305+ _to_optional (async_file .file_preview_height ),
306+ ),
307+ file_preview_width = cast (
308+ Optional [int ],
309+ _to_optional (async_file .file_preview_width ),
310+ ),
311+ file_encryption_algo = cast (
312+ Optional [str ],
313+ _to_optional (async_file .file_encryption_algo ),
314+ ),
315+ chunk_size = cast (Optional [int ], _to_optional (async_file .chunk_size )),
316+ caption = cast (Optional [str ], _to_optional (async_file .caption )),
214317 )
215318
216319 if attachment_type == AttachmentTypes .VOICE :
@@ -226,6 +329,21 @@ def convert_async_file_to_domain(async_file: APIAsyncFile) -> File:
226329 _file_mimetype = async_file .file_mime_type ,
227330 _file_url = async_file .file ,
228331 _file_hash = async_file .file_hash ,
332+ file_preview = cast (Optional [str ], _to_optional (async_file .file_preview )),
333+ file_preview_height = cast (
334+ Optional [int ],
335+ _to_optional (async_file .file_preview_height ),
336+ ),
337+ file_preview_width = cast (
338+ Optional [int ],
339+ _to_optional (async_file .file_preview_width ),
340+ ),
341+ file_encryption_algo = cast (
342+ Optional [str ],
343+ _to_optional (async_file .file_encryption_algo ),
344+ ),
345+ chunk_size = cast (Optional [int ], _to_optional (async_file .chunk_size )),
346+ caption = cast (Optional [str ], _to_optional (async_file .caption )),
229347 )
230348
231349 raise NotImplementedError (f"Unsupported attachment type: { attachment_type } " )
0 commit comments