From 65b353ffae64e738bd437f26f27c4eb6a6c49adc Mon Sep 17 00:00:00 2001 From: _run Date: Mon, 1 Aug 2022 12:40:43 +0500 Subject: [PATCH 1/6] Added InputFile --- telebot/apihelper.py | 9 +++++++++ telebot/types.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 07f2f57..bf9e23a 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import time from datetime import datetime +import os try: import ujson as json @@ -87,6 +88,14 @@ def _make_request(token, method_name, method='get', params=None, files=None): logger.debug("Request: method={0} url={1} params={2} files={3}".format(method, request_url, params, files).replace(token, token.split(':')[0] + ":{TOKEN}")) read_timeout = READ_TIMEOUT connect_timeout = CONNECT_TIMEOUT + + if files: + # process types.InputFile + for key, value in files.items(): + if isinstance(value, types.InputFile): + files[key] = value.file + + if files and format_header_param: fields.format_header_param = _no_encode(format_header_param) if params: diff --git a/telebot/types.py b/telebot/types.py index 6c5a1f0..f331073 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- +from io import IOBase import logging +import os +from pathlib import Path from typing import Dict, List, Optional, Union from abc import ABC @@ -6601,3 +6604,23 @@ class ChatAdministratorRights(JsonDeserializable, JsonSerializable, Dictionaryab +class InputFile: + def __init__(self, file) -> None: + self._file, self.file_name = self._resolve_file(file) + + def _resolve_file(self, file): + if isinstance(file, str): + _file = open(file, 'rb') + return _file, os.path.basename(file.name) + elif isinstance(file, IOBase): + return file, os.path.basename(file.name) + elif isinstance(file, Path): + _file = open(file, 'rb') + return _file, os.path.basename(file.name) + else: + raise TypeError("File must be a string or a file-like object(pathlib.Path, io.IOBase).") + + + @property + def file(self): + return self._file From d03f3b2c52a1577f374e7a74d0407b937e234706 Mon Sep 17 00:00:00 2001 From: _run Date: Mon, 1 Aug 2022 14:09:44 +0500 Subject: [PATCH 2/6] Updated asyncio_helper to support InputFile and fixed unecessary methods --- telebot/asyncio_helper.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 843ee0b..18a0a19 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -57,7 +57,7 @@ class SessionManager: session_manager = SessionManager() async def _process_request(token, url, method='get', params=None, files=None, request_timeout=None): - params = prepare_data(params, files) + params = _prepare_data(params, files) if request_timeout is None: request_timeout = REQUEST_TIMEOUT timeout = aiohttp.ClientTimeout(total=request_timeout) @@ -85,20 +85,7 @@ async def _process_request(token, url, method='get', params=None, files=None, re - -def prepare_file(obj): - """ - returns os.path.basename for a given file - - :param obj: - :return: - """ - name = getattr(obj, 'name', None) - if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>': - return os.path.basename(name) - - -def prepare_data(params=None, files=None): +def _prepare_data(params=None, files=None): """ prepare data for request. @@ -114,15 +101,10 @@ def prepare_data(params=None, files=None): if files: for key, f in files.items(): - if isinstance(f, tuple): - if len(f) == 2: - filename, fileobj = f - else: - raise ValueError('Tuple must have exactly 2 elements: filename, fileobj') - else: - filename, fileobj = prepare_file(f) or key, f + if isinstance(f, types.InputFile): + f = f.file - data.add_field(key, fileobj, filename=filename) + data.add_field(key, f, filename=key) return data From c5a69944be575acf5ccd16a85682488002723170 Mon Sep 17 00:00:00 2001 From: _run Date: Fri, 12 Aug 2022 14:33:24 +0500 Subject: [PATCH 3/6] Documentation improvements and file name fix --- telebot/types.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index f331073..0315a36 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -6605,22 +6605,61 @@ class ChatAdministratorRights(JsonDeserializable, JsonSerializable, Dictionaryab class InputFile: + """ + A class to send files through Telegram Bot API. + + You need to pass a file, which should be an instance of :class:`io.IOBase` or + :class:`pathlib.Path`, or :obj:`str`. + + If you pass an :obj:`str` as a file, it will be opened and closed by the class. + + :param file: A file to send. + :type file: :class:`io.IOBase` or :class:`pathlib.Path` or :obj:`str` + + .. code-block:: python3 + :caption: Example on sending a file using this class + + from telebot.types import InputFile + + # Sending a file from disk + bot.send_document( + chat_id, + InputFile('/path/to/file/file.txt') + ) + + + # Sending a file from an io.IOBase object + with open('/path/to/file/file.txt', 'rb') as f: + bot.send_document( + chat_id, + InputFile(f) + ) + + # Sending a file using pathlib.Path: + bot.send_document( + chat_id, + InputFile(pathlib.Path('/path/to/file/file.txt')) + ) + """ def __init__(self, file) -> None: self._file, self.file_name = self._resolve_file(file) def _resolve_file(self, file): if isinstance(file, str): _file = open(file, 'rb') - return _file, os.path.basename(file.name) + return _file, os.path.basename(_file.name) elif isinstance(file, IOBase): return file, os.path.basename(file.name) elif isinstance(file, Path): _file = open(file, 'rb') - return _file, os.path.basename(file.name) + return _file, os.path.basename(_file.name) else: raise TypeError("File must be a string or a file-like object(pathlib.Path, io.IOBase).") @property def file(self): + """ + File object. + """ return self._file From e860f114c60e954490119e88261ac98412c93936 Mon Sep 17 00:00:00 2001 From: _run Date: Fri, 12 Aug 2022 14:37:00 +0500 Subject: [PATCH 4/6] Forgot to remove unnecessary import --- telebot/apihelper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index bf9e23a..7ef5ffc 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import time from datetime import datetime -import os try: import ujson as json From 26db76f207bb39ca70cc803a32702348f66156ef Mon Sep 17 00:00:00 2001 From: _run Date: Fri, 12 Aug 2022 15:25:51 +0500 Subject: [PATCH 5/6] Fix backward comptability --- telebot/apihelper.py | 4 +++- telebot/asyncio_helper.py | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 7ef5ffc..a3572c6 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import copy import time from datetime import datetime @@ -89,8 +90,9 @@ def _make_request(token, method_name, method='get', params=None, files=None): connect_timeout = CONNECT_TIMEOUT if files: + files_copy = copy.deepcopy(files) # process types.InputFile - for key, value in files.items(): + for key, value in files_copy.items(): if isinstance(value, types.InputFile): files[key] = value.file diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 18a0a19..8141657 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -83,11 +83,17 @@ async def _process_request(token, url, method='get', params=None, files=None, re if not got_result: raise RequestTimeout("Request timeout. Request: method={0} url={1} params={2} files={3} request_timeout={4}".format(method, url, params, files, request_timeout, current_try)) - +def _prepare_file(obj): + """ + Prepares file for upload. + """ + name = getattr(obj, 'name', None) + if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>': + return os.path.basename(name) def _prepare_data(params=None, files=None): """ - prepare data for request. + Adds the parameters and files to the request. :param params: :param files: @@ -98,13 +104,20 @@ def _prepare_data(params=None, files=None): if params: for key, value in params.items(): data.add_field(key, str(value)) - if files: for key, f in files.items(): - if isinstance(f, types.InputFile): - f = f.file + if isinstance(f, tuple): + if len(f) == 2: + file_name, file = f + else: + raise ValueError('Tuple must have exactly 2 elements: filename, fileobj') + elif isinstance(f, types.InputFile): + file_name = f.file_name + file = f.file + else: + file_name, file = _prepare_file(f) or key, f - data.add_field(key, f, filename=key) + data.add_field(key, file, filename=file_name) return data From 5471b88da6ad698a376d0356a2ec70cd6db44538 Mon Sep 17 00:00:00 2001 From: _run Date: Fri, 12 Aug 2022 19:25:46 +0500 Subject: [PATCH 6/6] Update apihelper.py --- telebot/apihelper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index a3572c6..b4e89f7 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import copy import time from datetime import datetime @@ -90,7 +89,7 @@ def _make_request(token, method_name, method='get', params=None, files=None): connect_timeout = CONNECT_TIMEOUT if files: - files_copy = copy.deepcopy(files) + files_copy = dict(files) # process types.InputFile for key, value in files_copy.items(): if isinstance(value, types.InputFile):