From 6086953b3667c25cb78e7226d9590128640c3056 Mon Sep 17 00:00:00 2001 From: Badiboy Date: Fri, 18 Sep 2026 23:34:17 +0300 Subject: [PATCH 1/2] Async retry fix: https://github.com/eternnoir/pyTelegramBotAPI/pull/2599 Proposed retry extension to handle file uploading retry correctly. --- telebot/apihelper.py | 51 +++++++++++++++++++++++ telebot/asyncio_helper.py | 86 +++++++++++++++++++++++++++++++-------- 2 files changed, 121 insertions(+), 16 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index a687066f1..f46b9e99e 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -81,6 +81,50 @@ def _get_req_session(reset=False): return util.per_thread('req_session', lambda: session if session else requests.sessions.Session(), reset) +def _get_retryable_file_positions(files): + """Return upload stream positions, or None when an upload cannot be rewound.""" + positions = [] + if not files: + return positions + + for value in files.values(): + if isinstance(value, (tuple, list)): + if len(value) < 2: + continue + value = value[1] + if not hasattr(value, 'read'): + continue + + try: + positions.append((value, value.tell())) + except (AttributeError, OSError, ValueError): + return None + + return positions + + +def _rewind_file_positions(positions): + """ + Restore upload streams to the positions captured before the first request. + + ``requests`` builds a new multipart body for every call, but it reads each + file object while constructing that body. If a request fails after a file + has been read, its stream can be positioned at EOF. Retrying without a + rewind would then produce an empty or truncated upload. + + ``positions`` is collected before the first request by + :func:`_get_retryable_file_positions`. A ``False`` result means that at + least one stream cannot safely be rewound. The caller must then stop + retrying rather than risk sending a corrupted multipart body. + """ + try: + for file, position in positions: + file.seek(position) + except (AttributeError, OSError, ValueError): + return False + return True + + def _make_request(token, method_name, method='get', params=None, files=None): """ Makes a request to the Telegram API. @@ -137,6 +181,7 @@ def _make_request(token, method_name, method='get', params=None, files=None): elif RETRY_ON_ERROR and RETRY_ENGINE == 1: got_result = False current_try = 0 + file_positions = _get_retryable_file_positions(files) while not got_result and current_try Date: Fri, 18 Sep 2026 23:43:23 +0300 Subject: [PATCH 2/2] Functions unification. --- telebot/apihelper.py | 56 ++++++------------------------------ telebot/asyncio_helper.py | 60 +++++++-------------------------------- telebot/util.py | 35 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 97 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index f46b9e99e..cd7d0f97e 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -81,50 +81,6 @@ def _get_req_session(reset=False): return util.per_thread('req_session', lambda: session if session else requests.sessions.Session(), reset) -def _get_retryable_file_positions(files): - """Return upload stream positions, or None when an upload cannot be rewound.""" - positions = [] - if not files: - return positions - - for value in files.values(): - if isinstance(value, (tuple, list)): - if len(value) < 2: - continue - value = value[1] - if not hasattr(value, 'read'): - continue - - try: - positions.append((value, value.tell())) - except (AttributeError, OSError, ValueError): - return None - - return positions - - -def _rewind_file_positions(positions): - """ - Restore upload streams to the positions captured before the first request. - - ``requests`` builds a new multipart body for every call, but it reads each - file object while constructing that body. If a request fails after a file - has been read, its stream can be positioned at EOF. Retrying without a - rewind would then produce an empty or truncated upload. - - ``positions`` is collected before the first request by - :func:`_get_retryable_file_positions`. A ``False`` result means that at - least one stream cannot safely be rewound. The caller must then stop - retrying rather than risk sending a corrupted multipart body. - """ - try: - for file, position in positions: - file.seek(position) - except (AttributeError, OSError, ValueError): - return False - return True - - def _make_request(token, method_name, method='get', params=None, files=None): """ Makes a request to the Telegram API. @@ -181,7 +137,11 @@ def _make_request(token, method_name, method='get', params=None, files=None): elif RETRY_ON_ERROR and RETRY_ENGINE == 1: got_result = False current_try = 0 - file_positions = _get_retryable_file_positions(files) + file_streams = ( + value[1] if isinstance(value, (tuple, list)) and len(value) >= 2 else value + for value in (files or {}).values() + ) + file_positions = util._get_retryable_file_positions(file_streams) while not got_result and current_try