Skip to content

fix: мелкие исправления надёжности#92

Merged
love-apples merged 7 commits intolove-apples:mainfrom
bish-x:fix/minor-reliability-fixes
Apr 11, 2026
Merged

fix: мелкие исправления надёжности#92
love-apples merged 7 commits intolove-apples:mainfrom
bish-x:fix/minor-reliability-fixes

Conversation

@bish-x
Copy link
Copy Markdown
Contributor

@bish-x bish-x commented Apr 8, 2026

Описание

1. Исправлена проверка marker в пагинации

Файлы: methods/get_chats.py, methods/get_members_chat.py

if self.marker: пропускал marker=0. В get_updates.py маркер уже проверялся корректно через if self.marker is not None: — приведено к единообразию.

2. Защита от исключений в _resolve_from_user

Файл: utils/updates.py

bot.get_chat_member() вызывался без try/except. Если пользователь уже покинул чат к моменту обработки MessageRemoved, API возвращал ошибку, что приводило к потере всего батча обновлений.

3. Идемпотентность __ready в диспетчере

Файл: dispatcher.py

При повторном вызове startup() (например, FastAPI lifespan + ручной вызов) on_started_func вызывался дважды, а self.routers накапливал дубликаты. Добавлен флаг _ready для защиты от повторного входа.

4. Убран избыточный list comprehension в фильтре Command

Файл: filters/command.py

[commands.lower() for commands in self.commands] создавался на каждое сообщение, хотя self.commands уже в нижнем регистре после __init__. Убрана лишняя аллокация.

Тестирование

  • Все существующие тесты проходят
  • ruff check / ruff format — без замечаний

bish-x and others added 2 commits April 8, 2026 19:52
- webhook/base.py: предупреждение при запуске без secret
- exceptions/dispatcher.py: __repr__ = __str__ для HandlerException и MiddlewareException (защита memory_context от утечки через %r)
- methods/subscribe_webhook.py: предупреждение при подписке на HTTP URL
- bot.py: __repr__ возвращает Bot(token='***') для защиты токена в логах

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Исправлена проверка marker=0 в пагинации (get_chats, get_members_chat)
- Обёрнуты вызовы get_chat_member в contextlib.suppress в _resolve_from_user
- Добавлен флаг _ready для идемпотентности __ready в диспетчере
- Убран избыточный list comprehension в фильтре Command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread maxapi/utils/updates.py Outdated
event.from_user = await bot.get_chat_member(
chat_id=event.chat_id, user_id=event.user_id
)
with contextlib.suppress(Exception):
Copy link
Copy Markdown
Collaborator

@Olegt0rr Olegt0rr Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Слишком общее исключение - лучше ограничить исключениями данного приложения. Не понятна мотивация заглушения ошибки - насколько мы хотим подавить ошибку? Может пусть лучше явно возникнет? Или пусть как минимум оставит следы?

Если это влияет выше на потерю батча обновлений - то надо это проблему выше решать, но текущее обновление ожидаемо должно явно упасть с ошибкой

Заменяет contextlib.suppress(Exception) на явный except (MaxApiError, MaxConnection)
с logger.warning(), чтобы ошибки оставляли след в логах вместо молчаливого подавления.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

PR направлен на повышение надёжности и предсказуемости поведения SDK: исправляет пограничные случаи пагинации, делает обработку обновлений устойчивее к ошибкам API, предотвращает повторную “инициализацию” диспетчера и убирает лишние аллокации в фильтре команд.

Changes:

  • Исправлена обработка marker=0 в методах пагинации.
  • Добавлена защита try/except вокруг get_chat_member() при enrich’е событий обновлений.
  • Сделан startup()/__ready() идемпотентным и оптимизирован Command-фильтр.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/test_utils/test_message_media_upload.py Адаптация теста под изменение поведения закрытия сессии при ошибке upload token.
pyproject.toml Добавлен per-file ignore C90 для maxapi/utils/updates.py.
maxapi/webhook/base.py Предупреждение при запуске webhook без secret.
maxapi/utils/updates.py Оборачивание get_chat_member() в try/except + логирование.
maxapi/utils/message.py Убрано закрытие bot.session при отсутствии upload token для video/audio.
maxapi/methods/subscribe_webhook.py Предупреждение при URL вебхука без HTTPS.
maxapi/methods/get_members_chat.py Корректная проверка marker is not None.
maxapi/methods/get_chats.py Корректная проверка marker is not None.
maxapi/filters/command.py Удалена лишняя аллокация списка при case-insensitive проверке команды.
maxapi/exceptions/dispatcher.py __repr__ приравнен к __str__ для исключений диспетчера/мидлварей.
maxapi/dispatcher.py Добавлен флаг _ready для защиты от повторной инициализации; сброс в stop_polling().
maxapi/connection/base.py Более корректный content_type через mimetypes.guess_type и timeout для временных ClientSession.
maxapi/bot.py Безопасный __repr__ без утечки токена.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread maxapi/dispatcher.py
Comment on lines +245 to +248
if self._ready:
return
self._ready = True

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Флаг _ready выставляется в True до выполнения потенциально падающих шагов (например, check_me(), _prepare_handlers(), on_started_func). Если внутри __ready() произойдёт исключение, диспетчер останется в частично инициализированном состоянии, а повторный startup()/start_polling() будет сразу возвращаться из-за _ready=True. Лучше выставлять _ready=True только после успешной инициализации или сбрасывать флаг в except/finally при ошибке.

Copilot uses AI. Check for mistakes.
Comment thread maxapi/utils/updates.py Outdated
Comment on lines +75 to +80
try:
event.from_user = await bot.get_chat_member(
chat_id=event.chat_id, user_id=event.user_id
)
except (MaxApiError, MaxConnection) as exc:
logger.warning("Не удалось получить участника чата: %s", exc)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Логирование исключения через %s вызывает MaxApiError.__str__, который включает raw ответ API. Это может писать в логи большой payload и потенциально персональные данные. Лучше логировать безопасный минимум (например, code и контекст chat_id/user_id) без raw, либо вынести raw на debug-уровень.

Copilot uses AI. Check for mistakes.
Comment thread maxapi/utils/updates.py Outdated
Comment on lines +86 to +91
try:
event.from_user = await bot.get_chat_member(
chat_id=event.chat_id, user_id=event.admin_id
)
except (MaxApiError, MaxConnection) as exc:
logger.warning("Не удалось получить участника чата: %s", exc)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь та же проблема с логированием: MaxApiError.__str__ включает raw, что может приводить к утечке/зашумлению логов. Рекомендуется логировать только code/тип события и идентификаторы (chat_id, admin_id) без полного raw ответа, либо логировать raw только на debug.

Copilot uses AI. Check for mistakes.
Comment thread maxapi/methods/subscribe_webhook.py Outdated
Comment on lines +48 to +53
if not url.startswith("https://"):
warnings.warn(
"URL вебхука не использует HTTPS. "
"Обновления будут передаваться по незашифрованному каналу.",
stacklevel=2,
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Проверка not url.startswith("https://") срабатывает также для невалидных URL (например, без схемы или с ftp://), но текст предупреждения говорит только про отсутствие HTTPS. Либо валидируйте, что URL начинается с http:// или https:// (и поднимайте ValueError при другом), либо ограничьте предупреждение случаем url.startswith("http://").

Copilot uses AI. Check for mistakes.
Comment thread maxapi/webhook/base.py
Comment on lines 49 to +55
self.secret = secret
if self.secret is None:
logger_dp.warning(
"Webhook запущен без secret. "
"Рекомендуется установить secret для защиты "
"от поддельных обновлений."
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сейчас предупреждение выводится только когда secret is None. Если передать пустую строку secret="", проверки секрета фактически не будет (заголовок можно прислать пустым), но предупреждения не появится. Стоит либо валидировать secret (как минимум if not secret: ...), либо нормализовать пустую строку к None.

Copilot uses AI. Check for mistakes.
Comment thread maxapi/dispatcher.py
"""
if self.polling:
self.polling = False
self._ready = False
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stop_polling() сбрасывает _ready=False, поэтому последующий start_polling() снова зайдёт в __ready() и выполнит self.routers += [self]. Поскольку список routers здесь не очищается, при циклах stop/start в одном процессе будут накапливаться дубликаты роутеров (и потенциально лишние предупреждения/рост памяти). Либо не сбрасывайте _ready без очистки/восстановления routers, либо сделайте добавление self в routers идемпотентным (не добавлять, если уже есть).

Suggested change
self._ready = False

Copilot uses AI. Check for mistakes.
- updates.py: split except (MaxApiError, MaxConnection) into separate
  clauses; log only exc.code and chat_id (not raw) to avoid PII leakage
  via MaxApiError.__str__ which includes the raw API response
- dispatcher.py: moved _ready=True to after successful init completion
  (after check_me, _prepare_handlers, on_started_func) so partial init
  failure doesn't leave the dispatcher in a permanently-ready state
- dispatcher.py: made self.routers addition idempotent (if self not in
  self.routers) to prevent duplicates on stop+restart cycles
- webhook/base.py: changed 'secret is None' to 'not secret' so that
  empty string '' also triggers the missing-secret warning
- subscribe_webhook.py: narrowed URL check from 'not https://' to
  'http://' so only plaintext HTTP triggers the warning (not wss:// etc)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 89.18919% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
maxapi/connection/base.py 50.00% 1 Missing ⚠️
maxapi/filters/command.py 50.00% 1 Missing ⚠️
maxapi/methods/get_chats.py 0.00% 1 Missing ⚠️
maxapi/methods/get_members_chat.py 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

bish-x and others added 3 commits April 9, 2026 19:07
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers all 8 code paths flagged by Codecov:
- utils/updates.py: MaxApiError and MaxConnection in _resolve_from_user
- dispatcher.py: _ready flag prevents double init
- methods/subscribe_webhook.py: warns on http:// URL
- filters/command.py: case-insensitive command match
- methods/get_chats.py: marker=0 handled via `is not None`
- methods/get_members_chat.py: marker=0 handled via `is not None`
- connection/base.py: temp ClientSession + mimetypes.guess_extension
- bot.py: __repr__ does not leak token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- I001: отсортированы импорты
- F401: удалены неиспользуемые импорты (Bot, Dispatcher, logging)
- E501: разбиты длинные строки (docstrings переведены на русский,
  lambda вынесена в обычную функцию, patch разбит на несколько строк)
- ruff format: убрана лишняя пустая строка в test_message_media_upload.py
@love-apples love-apples merged commit f3d1366 into love-apples:main Apr 11, 2026
13 checks passed
bish-x added a commit to bish-x/maxapi that referenced this pull request Apr 14, 2026
Добавлен класс TestBaseMaxWebhookSecretWarning в tests/test_coverage_gaps.py
с тремя кейсами для проверки `if not self.secret` в webhook/base.py:50:

- secret=None — warning должен сработать
- secret="" — warning должен сработать (edge case, который и был
  причиной перехода с `is None` на `not self.secret` в love-apples#92)
- secret="my-secret" — warning должен молчать

Покрывает оставшийся непокрытый путь после слияния PR love-apples#92.
love-apples pushed a commit that referenced this pull request Apr 14, 2026
Добавлен класс TestBaseMaxWebhookSecretWarning в tests/test_coverage_gaps.py
с тремя кейсами для проверки `if not self.secret` в webhook/base.py:50:

- secret=None — warning должен сработать
- secret="" — warning должен сработать (edge case, который и был
  причиной перехода с `is None` на `not self.secret` в #92)
- secret="my-secret" — warning должен молчать

Покрывает оставшийся непокрытый путь после слияния PR #92.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants