feat: соответствие swagger 0.0.30#145
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Неееет копилот |
There was a problem hiding this comment.
Pull request overview
Этот PR приводит библиотеку в соответствие со Swagger 0.0.30 и закрывает связанные проблемы вокруг callback-ответов (редактирование сообщения через MessageCallback, работа инлайн‑клавиатур в примерах), а также синхронизирует модели/enum’ы с актуальными полями API.
Changes:
- Добавлена поддержка передачи
attachmentsвMessageCallback.edit()/answer()и обновлена сериализация вложений вSendCallback. - Обновлены enum’ы/модели под Swagger (права админов,
TextStyle/markup, поля вложений image/video/contact/share). - Удалены утилиты/тесты для генерации ссылок на сообщения (message_link) и упрощена модель
Message.url.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_utils/test_message_link.py | Удалены тесты утилит message_link (функциональность выведена из проекта). |
| tests/test_upload_file.py | Адаптация тестов к новому способу мокинга session.post (await вместо async context manager). |
| tests/test_swagger_alignment.py | Добавлены проверки совместимости моделей/enum’ов с актуальным Swagger. |
| tests/test_send_callback.py | Новый тест на сериализацию inline keyboard как attachment в /answers. |
| tests/test_messagecallback_none.py | Добавлены тесты на attachments в MessageCallback.edit(). |
| tests/test_message_url.py | Удалены тесты свойства Message.url (раньше включало генерацию ссылки). |
| tests/test_highlevel_shortcuts.py | Обновлены high-level shortcut тесты под новые аргументы attachments в callback-операциях. |
| tests/test_formatting.py | Переименование BLOCKQUOTE→QUOTE в тестах форматирования. |
| tests/test_coverage_gaps.py | Актуализация моков upload’а под await session.post(...). |
| tests/test_bot.py | Удалены тесты, завязанные на строковые значения send_action. |
| maxapi/utils/message_link.py | Удалена реализация message_link утилит. |
| maxapi/utils/formatting.py | Добавлен стиль Highlighted для <mark> / ^^..^^. |
| maxapi/utils/init.py | Убраны реэкспорты message_link из maxapi.utils. |
| maxapi/types/users.py | ChatAdmin расширен полем alias для совместимости со Swagger. |
| maxapi/types/updates/message_callback.py | MessageCallback.edit/answer расширены поддержкой attachments. |
| maxapi/types/message.py | Обновлена обработка стилей разметки; url теперь обычное поле (без генерации). |
| maxapi/types/attachments/video.py | Video/thumbnail сделаны более совместимыми со Swagger (nullable поля, default type). |
| maxapi/types/attachments/image.py | Обновлена форма photos в request payload и добавлен PhotoToken. |
| maxapi/types/attachments/attachment.py | Ослаблены ограничения на поля некоторых payload’ов и добавлен AttachmentPayload alias. |
| maxapi/types/init.py | Экспортирован PhotoToken. |
| maxapi/methods/send_callback.py | Ручная сериализация message.attachments с обработкой InputMedia/Upload. |
| maxapi/methods/send_action.py | Упрощён ввод action (теперь типизирован как SenderAction). |
| maxapi/methods/edit_chat.py | Уточнена валидация взаимоисключающих полей icon и обработка notify=False. |
| maxapi/enums/text_style.py | BLOCKQUOTE заменён на QUOTE, добавлен/актуализирован HIGHLIGHTED. |
| maxapi/enums/chat_permission.py | Добавлены новые значения прав админов под Swagger. |
| maxapi/connection/base.py | Изменён способ получения response при upload (await post вместо async with). |
| maxapi/bot.py | send_action теперь принимает SenderAction (без str в сигнатуре). |
| examples/03_keyboard_bot.py | Исправлен пример: использование event.edit(...) и корректная очистка клавиатуры. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| session = bot.session | ||
| if session is not None and not session.closed: | ||
| async with session.post(url=url, data=form) as response: | ||
| return await response.text() | ||
| response = await session.post(url=url, data=form) | ||
| return await response.text() |
| async with ClientSession( | ||
| timeout=bot.default_connection.timeout | ||
| ) as temp_session: | ||
| response = await temp_session.post(url=url, data=form) | ||
| return await response.text() |
| session = bot.session | ||
| if session is not None and not session.closed: | ||
| async with session.post(url=url, data=form) as response: | ||
| return await response.text() | ||
| response = await session.post(url=url, data=form) | ||
| return await response.text() |
| async with ClientSession( | ||
| timeout=bot.default_connection.timeout | ||
| ) as temp_session: | ||
| response = await temp_session.post(url=url, data=form) | ||
| return await response.text() |
| ) from e | ||
|
|
||
| self.bot = bot | ||
| self.chat_id = chat_id |
There was a problem hiding this comment.
Замечание дельное, а предложение хреновое. Нужно убедиться, что SenderAction это StrEnum и тогда можно убрать использование .value – тогда проблема уйдёт.
| text: str | None = None | ||
| attachments: list[Attachments] | None = Field(default_factory=list) # type: ignore | ||
| attachments: list[Attachment | AttachmentPayload | Attachments] | None = ( | ||
| Field(default_factory=list) | ||
| ) |
| self, | ||
| notification: str | None = None, | ||
| new_text: str | None = None, | ||
| attachments: Sequence[Attachment] | None = None, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (4)
maxapi/types/updates/message_callback.py:42
attachments: list[AttachmentInput] | None = Field(default_factory=list)declares the field aslist | Nonebut the default factory produces[], neverNone. Either drop| Nonefrom the annotation, or setdefault=None(and adapt downstream code) — keeping both is misleading to consumers and to type checkers (the field will never beNoneby default).
attachments: list[AttachmentInput] | None = Field(default_factory=list)
maxapi/methods/edit_chat.py:84
- The new check
sum(value is not None for value in dump.values()) != 1implicitly assumes the icon model has exactly one populated discriminator field, butPhotoAttachmentRequestPayloadnow contains 3 fields (url,token,photos). The previous logicNone not in counter or not counter[None] == 2was tied to a 2-field model and is now both wrong (off by one against the new shape) and brittle. The new logic is correct for the current shape but the docstring above (в модели должно быть ровно 2 поля с None) still describes the old 2-None invariant — please update the docstring to reflect "ровно одно непустое поле" so the constraint stays clear if more icon fields are added later.
if self.icon:
dump = self.icon.model_dump()
if sum(value is not None for value in dump.values()) != 1:
raise MaxIconParamsException(
"Все атрибуты модели Icon являются взаимоисключающими | "
"https://dev.max.ru/docs-api/methods/PATCH/chats/-chatId-"
)
maxapi/types/updates/message_callback.py:249
MessageCallback.answerexposes the newattachmentsparameter but its docstringArgs:section is not updated to mention it (onlynotification,new_text,link,notify,format,raise_if_not_existsare listed). Per the project's Google-style docstring convention used throughout this module, all public arguments should be documented.
async def answer(
self,
notification: str | None = None,
new_text: str | None = None,
attachments: Sequence[AttachmentInput] | None = None,
link: NewMessageLink | None = None,
format: ParseMode | None = None,
*,
notify: bool = True,
raise_if_not_exists: bool = True,
) -> SendedCallback:
"""
Отправляет ответ на callback с возможностью изменить текст
и параметры уведомления.
Args:
notification: Текст уведомления.
new_text: Новый текст сообщения.
link: Связь с другим сообщением.
notify: Отправлять ли уведомление.
format: Режим разбора текста.
raise_if_not_exists: Выдавать ошибку при отсутствии сообщения,
если пытаются изменить его содержимое (new_text/link/format).
Returns:
SendedCallback: Результат вызова send_callback бота.
"""
maxapi/types/updates/message_callback.py:144
MessageCallback.editaccepts the newattachmentsargument but its docstring is just a one-liner without anArgs:section. The new argument has non-trivial semantics worth documenting:Nonemeans "preserve existing attachments from the original body",[]clears them, and any non-empty list replaces them.
async def edit(
self,
text: str | None = None,
attachments: Sequence[AttachmentInput] | None = None,
link: NewMessageLink | None = None,
format: ParseMode | None = None,
*,
notification: str | None = None,
notify: bool = True,
raise_if_not_exists: bool = True,
) -> SendedCallback:
"""Изменить сообщение, связанное с callback."""
message = self.message
original_body = None if message is None else message.body
if original_body is None:
if raise_if_not_exists and (
text is not None or link is not None or format is not None
):
raise ValueError(
"Невозможно изменить сообщение: "
"исходное сообщение отсутствует"
)
return await self.ack(notification=notification)
bot = self._ensure_bot()
resolved_attachments: Sequence[AttachmentInput]
if attachments is None:
resolved_attachments = original_body.attachments or []
else:
resolved_attachments = attachments
| """ | ||
|
|
||
| vcf_info: str = "" # для корректного определения | ||
| vcf_info: str | None = "" |
| @@ -75,14 +75,15 @@ class ContactAttachmentPayload(BaseModel): | |||
| max_info: Дополнительная информация о пользователе. | |||
| """ | |||
|
|
|||
| vcf_info: str = "" # для корректного определения | |||
| vcf_info: str | None = "" | |||
| hash: str | None = None | |||
| max_info: User | None = None | |||
| text: str | None = None | ||
| attachments: list[Attachments] | None = Field(default_factory=list) # type: ignore | ||
| model_config = ConfigDict(arbitrary_types_allowed=True) | ||
|
|
||
| attachments: list[AttachmentInput] | None = Field(default_factory=list) |
| message_json["attachments"] = [] | ||
|
|
||
| if self.message.attachments: | ||
| for att in self.message.attachments: | ||
| if isinstance(att, (InputMedia, InputMediaBuffer)): | ||
| input_media = await process_input_media( | ||
| base_connection=self, | ||
| bot=bot, | ||
| att=att, | ||
| ) | ||
| message_json["attachments"].append( | ||
| input_media.model_dump() | ||
| ) | ||
| elif isinstance(att, Attachment) and isinstance( | ||
| att.payload, AttachmentUpload | ||
| ): | ||
| message_json["attachments"].append( | ||
| att.payload.model_dump() | ||
| ) | ||
| else: | ||
| message_json["attachments"].append(att.model_dump()) | ||
|
|
| att.payload, AttachmentUpload | ||
| ): | ||
| message_json["attachments"].append( | ||
| att.payload.model_dump() |
|
|
||
| if self.message.attachments: | ||
| for att in self.message.attachments: | ||
| if isinstance(att, (InputMedia, InputMediaBuffer)): |
| # Убираем клавиатуру, оставляем текст. | ||
| # Важно: attachments=None в Message.edit() трактуется как | ||
| # «не менять» и сохраняет существующие вложения. Чтобы | ||
| # действительно удалить клавиатуру — передаём пустой список. | ||
| await event.message.edit( | ||
| # Для callback-ответа пустой список attachments действительно | ||
| # очищает клавиатуру. | ||
| await event.edit( | ||
| text="Клавиатура убрана. Напишите /start для повтора.", | ||
| attachments=[], | ||
| ) | ||
| return |
…ы тесты для проверки
closes #141, #140