Fix #665, add separate parametrs for saving to directory and file (#677)

* close #665

* add backward compatibility

* improve doc, codestyle

* warning text update

* use tmpdir fixture in tests
This commit is contained in:
darksidecat 2021-09-06 00:05:52 +03:00 committed by GitHub
parent 82b1b1ab03
commit 358ecc7821
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 171 additions and 11 deletions

View file

@ -1,5 +1,9 @@
import os
import pathlib
from io import IOBase
from typing import Union, Optional
from aiogram.utils.deprecated import warn_deprecated
class Downloadable:
@ -7,32 +11,86 @@ class Downloadable:
Mixin for files
"""
async def download(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
async def download(
self,
destination=None,
timeout=30,
chunk_size=65536,
seek=True,
make_dirs=True,
*,
destination_dir: Optional[Union[str, pathlib.Path]] = None,
destination_file: Optional[Union[str, pathlib.Path, IOBase]] = None
):
"""
Download file
:param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO`
At most one of these parameters can be used: :param destination_dir:, :param destination_file:
:param destination: deprecated, use :param destination_dir: or :param destination_file: instead
:param timeout: Integer
:param chunk_size: Integer
:param seek: Boolean - go to start of file when downloading is finished.
:param make_dirs: Make dirs if not exist
:param destination_dir: directory for saving files
:param destination_file: path to the file or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO`
:return: destination
"""
if destination:
warn_deprecated(
"destination parameter is deprecated, please use destination_dir or destination_file."
)
if destination_dir and destination_file:
raise ValueError(
"Use only one of the parameters: destination_dir or destination_file."
)
file, destination = await self._prepare_destination(
destination,
destination_dir,
destination_file,
make_dirs
)
return await self.bot.download_file(
file_path=file.file_path,
destination=destination,
timeout=timeout,
chunk_size=chunk_size,
seek=seek,
)
async def _prepare_destination(self, dest, destination_dir, destination_file, make_dirs):
file = await self.get_file()
is_path = True
if destination is None:
if not(any((dest, destination_dir, destination_file))):
destination = file.file_path
elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination):
destination = os.path.join(destination, file.file_path)
else:
is_path = False
if is_path and make_dirs:
elif dest: # backward compatibility
if isinstance(dest, IOBase):
return file, dest
if isinstance(dest, (str, pathlib.Path)) and os.path.isdir(dest):
destination = os.path.join(dest, file.file_path)
else:
destination = dest
elif destination_dir:
if isinstance(destination_dir, (str, pathlib.Path)):
destination = os.path.join(destination_dir, file.file_path)
else:
raise TypeError("destination_dir must be str or pathlib.Path")
else:
if isinstance(destination_file, IOBase):
return file, destination_file
elif isinstance(destination_file, (str, pathlib.Path)):
destination = destination_file
else:
raise TypeError("destination_file must be str, pathlib.Path or io.IOBase type")
if make_dirs and os.path.dirname(destination):
os.makedirs(os.path.dirname(destination), exist_ok=True)
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
chunk_size=chunk_size, seek=seek)
return file, destination
async def get_file(self):
"""