fix: исправлена надёжность загрузки файлов#94
Conversation
- format.value → format: убран .value, так как TextFormat наследует StrEnum и сериализуется как строка автоматически (send_message.py, edit_message.py) - text: null не отправляется когда text is None (send_message.py) - устранён двойной DeprecationWarning для parse_mode в bot.send_message и bot.edit_message — передаётся parse_mode=None после resolve_format
- Убрано закрытие глобальной сессии бота при ошибке upload VIDEO/AUDIO:
session.close() закрывал основную сессию, после чего все последующие
запросы падали с RuntimeError: Session is closed
- Исправлен MIME-type при загрузке файлов: заменён механический
f"{type.value}/{ext.lstrip('.')}" на mimetypes.guess_type() для
корректных значений (.jpg→image/jpeg, .mp3→audio/mpeg и т.д.)
- Добавлен timeout для fallback ClientSession при закрытой основной
сессии: без него upload мог висеть бесконечно при зависании сервера
There was a problem hiding this comment.
Pull request overview
PR нацелен на повышение надёжности загрузки медиа/файлов и устойчивости работы HTTP-сессий при upload-ошибках в maxapi.
Changes:
- Убрано закрытие глобальной
bot.sessionпри ошибке загрузки VIDEO/AUDIO (и обновлён тест). - Исправлено определение
content_typeпри загрузке файла по пути черезmimetypes.guess_type()и добавлен timeout для временных upload-сессий. - Дополнительно внесены изменения вне описанного upload-скоупа (IDs в update-событиях, сериализация
formatв send/edit message, копирование данных контекста,Bot.__repr__, лог вытеснения контекста).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_utils/test_message_media_upload.py | Обновление теста под новое поведение (не закрывать глобальную сессию при upload-ошибке). |
| maxapi/utils/message.py | Убрано закрытие bot.session при отсутствии upload token для VIDEO/AUDIO. |
| maxapi/connection/base.py | MIME-type через mimetypes.guess_type() и timeout для fallback ClientSession() при upload. |
| maxapi/types/updates/user_removed.py | get_ids() теперь возвращает user.user_id вместо admin_id. |
| maxapi/types/updates/user_added.py | get_ids() теперь возвращает user.user_id вместо inviter_id. |
| maxapi/methods/send_message.py | Изменена сборка JSON (в т.ч. сериализация format, установка text). |
| maxapi/methods/edit_message.py | Изменена сериализация format в JSON. |
| maxapi/dispatcher.py | Добавлен debug-лог при LRU-вытеснении контекста. |
| maxapi/context/context.py | get_data() возвращает копию контекста вместо исходного dict. |
| maxapi/bot.py | Добавлен __repr__, изменена прокидка parse_mode в методы сообщений. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if self.format is not None: | ||
| json["format"] = self.format.value | ||
| json["format"] = self.format |
There was a problem hiding this comment.
json["format"] now stores the TextFormat enum instance directly. In this codebase the JSON payload is expected to contain the string value (TextFormat.*.value), and current tests assert on that (see tests/test_format_parameter.py). Please serialize explicitly (e.g., use .value or str(self.format)) to keep the request payload stable and tests passing.
| json["format"] = self.format | |
| json["format"] = self.format.value |
| json["notify"] = self.notify | ||
| if self.format is not None: | ||
| json["format"] = self.format.value | ||
| json["format"] = self.format |
There was a problem hiding this comment.
json["format"] is set to the TextFormat enum instance rather than its string value. The surrounding code/tests expect the API payload to use the string form (e.g., .value), so this will break tests/test_format_parameter.py and may change the outgoing JSON unexpectedly. Serialize the enum explicitly before putting it into json.
| json["format"] = self.format | |
| json["format"] = self.format.value |
| def __repr__(self) -> str: | ||
| return "Bot(token='***')" | ||
|
|
There was a problem hiding this comment.
PR description focuses on upload reliability, but this PR also changes unrelated behaviors (e.g., adding a custom Bot.__repr__, changing format serialization in send/edit message, changing update get_ids results, context get_data returning a copy). Please either update the PR description to cover these changes or split them into a separate PR to keep review scope clear.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…_ids Добавлены тесты для: - mimetypes.guess_type() фоллбэк в upload_file (base.py) - Создание вре��енной ClientSession с timeout при отсутствии сессии - UserAdded.get_ids() и UserRemoved.get_ids() возвращающие user.user_id Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Подтянуты PR из upstream: love-apples#93 (FSM), love-apples#96 (download_file), love-apples#101 (fetch user/chat), love-apples#105 (ClipboardButton), love-apples#109 (share payload), love-apples#110 (webhook secret warning). Конфликт в tests/test_types.py: принят upstream-стиль (явный update_type, разнесённые assert). Сохранены доп. тесты test_get_ids_ignores_inviter_id / test_get_ids_ignores_admin_id — их purpose именно цель PR love-apples#94 (не путать inviter_id/admin_id с user.user_id).
|
Конфликты разрешены, ветка перенесена на актуальный Большая часть исходного scope PR уже въехала в После мерджа осталось:
Локально: |
Описание
Загрузка файлов (upload reliability)
1. Убрано закрытие глобальной сессии бота при ошибке upload
Файл:
utils/message.pyПри загрузке VIDEO/AUDIO, если upload-сервер не возвращал
token, вызывалсяawait bot.session.close(), что закрывало глобальную HTTP-сессию бота. После этогоbot.sessionоставался неNone(закрытый объект), условие пересоздания вrequest()не срабатывало — все последующие API-запросы бота падали сRuntimeError: Session is closedдо перезапуска процесса.2. Исправлен MIME-type при загрузке файлов через
upload_fileФайл:
connection/base.pyРанее MIME-type конструировался механически:
f"{type.value}/{ext.lstrip('.')}", что давало некорректные значения для распространённых форматов (.jpg→image/jpgвместоimage/jpegи т.д.). Заменено наmimetypes.guess_type()с фоллбэком{type}/*.3. Добавлен timeout для fallback upload-сессий
Файл:
connection/base.pyFallback
ClientSession()создавался без timeout, что могло привести к бесконечному ожиданию. Добавленtimeout=bot.default_connection.timeout.Сериализация (send_message / edit_message)
4. Исправлена сериализация
formatв SendMessage и EditMessageФайлы:
methods/send_message.py,methods/edit_message.pyformatпередавался как.value(строка из enum), но API ожидает уже десериализованное значение. Убран лишний.value.5. Поле
textв SendMessage теперь опциональноФайл:
methods/send_message.pyjson["text"] = self.textвыставлялось безусловно. Теперьtextдобавляется в payload только если неNone— это нужно для отправки медиа без текста.6. Устранена передача
parse_modeпри наличииformatФайл:
bot.pyВ
send_messageиedit_messageодновременно передавалисьformatиparse_mode, что могло вызвать конфликт. Теперьparse_mode=Noneпри наличииformat.Прочие улучшения
7.
Bot.__repr__с маскировкой токенаФайл:
bot.pyДобавлен
__repr__, возвращающийBot(token='***')для безопасного логирования.8.
get_ids()в UserAdded и UserRemovedФайлы:
types/updates/user_added.py,types/updates/user_removed.pyget_ids()возвращал(chat_id, inviter_id)/(chat_id, admin_id), что некорректно для маршрутизации FSM-контекстов. Исправлено на(chat_id, user.user_id).9.
context.get_data()возвращает копиюФайл:
context/context.pyMemoryContext.get_data()возвращал ссылку на внутренний_context, что позволяло мутировать состояние без блокировки. Теперь возвращается.copy().10. Логирование вытеснения LRU-контекстов
Файл:
dispatcher.pyПри вытеснении контекста из LRU-кеша теперь логируется ключ вытесненного контекста.
Тестирование
UserAdded.get_ids(),UserRemoved.get_ids()ruff check/ruff format— без замечаний