Skip to content

Beta#94

Merged
5hojib merged 10 commits intomainfrom
beta
Feb 22, 2026
Merged

Beta#94
5hojib merged 10 commits intomainfrom
beta

Conversation

@5hojib
Copy link
Member

@5hojib 5hojib commented Feb 22, 2026

Summary by Sourcery

Align Pyrogram with recent Telegram API changes, improve type handling and robustness, and fix various edge-case bugs across messages, chats, stories, and business payments.

Bug Fixes:

  • Correct type hint generation for flags and raw base types in the compiler.
  • Avoid None-related errors when accessing message entities, quote entities, users, chats, and approvers in various parsers.
  • Ensure global hashtag search pagination uses the correct offset field and avoid misuse of channel IDs when handling updates.
  • Fix deletion of scheduled messages to use the proper API and return the correct identifiers.
  • Prevent attribute errors when parsing reaction counts with missing reactions and when saving files without an existing file_id.
  • Guard giveaway parsing to only handle giveaway media messages and support None chats in chat parsing and join requests.
  • Avoid errors in chat joiner parsing when approved_by is None and support None invites in chat invite link parsing.

Enhancements:

  • Refine message parsing and serialization, including safer handling of text, entities, and reply buttons in Message and CallbackQuery operations.
  • Broaden story and story message parsing to support skipped and deleted stories in both return types and parser logic.
  • Update chat color changes to use the newer PeerColor structure for profile colors.
  • Improve type signatures and defaults for edit_message_caption and edit_message_media to better reflect optional invert_media behavior.
  • Simplify and clarify variable usage in several methods (e.g., packing auth data, custom emoji retrieval, and copy_message).

Build:

  • Relax type checker configuration by ignoring invalid-assignment diagnostics.

Documentation:

  • Document the new business_connection_id parameter in callback query text editing.

Chores:

  • Update business payments and gifts methods to use the latest Telegram star gift API endpoints and parameter structures.

…eted types (#89)

Updated `get_stories` and `MessageStory._parse` to correctly reflect that they can return `StorySkipped` and `StoryDeleted` types, resolving `invalid-return-type` errors reported by the `ty` type checker.
* Fix unknown-argument type errors

This commit resolves all "unknown-argument" errors reported by the ty type checker.
Key changes include:
- Fixed Star Gift related methods in business methods to use correct raw API parameters and types.
- Updated account.UpdateColor call to use PeerColor object.
- Switched to messages.DeleteScheduledMessages for scheduled message deletion.
- Removed unsupported parameters in send_media_group and send_paid_media.
- Fixed CallbackQuery.edit_message_caption to call correct client methods and accept business_connection_id.
- Removed unused/incorrect parameters in DraftMessage and get_chat calls.
Signed-off-by: 5hojib <yesiamshojib@gmail.com>
…rs (#91)

This commit addresses several type errors reported by the `ty` type checker:
- Fixed `invalid-return-type` in `delete_scheduled_messages.py`.
- Resolved all 34 `invalid-assignment` errors by using `setattr`/`getattr` in decorators and using separate variables for narrowed types in methods.
- Modernized `compiler/api/compiler.py` to use PEP 604 `| None` syntax instead of `Optional[...]`, and regenerated all raw API files.
- Cleaned up temporary files and experimental test code.
Signed-off-by: 5hojib <yesiamshojib@gmail.com>
…mprovements (#93)

* fix(ty): resolve several invalid-argument-type errors

- Update `ChatInviteLink._parse` and `Chat._parse_chat` to accept `None` for optional data.
- Fix `dict.get()` calls with potentially `None` keys in `ChatJoinRequest` and `ChatJoiner`.
- Filter `None` values from entity lists in `Message._parse`.
- Update `Message.markdown` and `Message.html` to handle missing entities.
- Fix `Message.click` to correctly access `button.text`.
- Update `EditMessageCaption` and `EditMessageMedia` to accept `None` for `invert_media`.
- Fix API compiler to generate valid type hints for forward references using PEP 604 syntax.
- Add `from __future__ import annotations` to generated raw API files.
- Use `list` instead of `typing.List` in raw API templates.

* fix(ty): resolve several invalid-argument-type errors

- Update `ChatInviteLink._parse` and `Chat._parse_chat` to accept `None` for optional data.
- Fix `dict.get()` calls with potentially `None` keys in `ChatJoinRequest` and `ChatJoiner`.
- Filter `None` values from entity lists in `Message._parse`.
- Update `Message.markdown` and `Message.html` to handle missing entities.
- Fix `Message.click` to correctly access `button.text`.
- Update `EditMessageCaption` and `EditMessageMedia` to accept `None` for `invert_media`.
- Fix API compiler to generate valid type hints for forward references using PEP 604 syntax.
- Add `from __future__ import annotations` to generated raw API files.
- Use `list` instead of `typing.List` in raw API templates.
Signed-off-by: 5hojib <yesiamshojib@gmail.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 22, 2026

Reviewer's Guide

Refactors type hint generation and tightens many type- and None-safety edge cases across messages, chats, stories, business payments, and low-level RPC helpers, while aligning several methods with updated Telegram APIs and fixing small logic bugs in search, deletion, and UI helpers.

Sequence diagram for updated callback_query.edit_message_caption flow

sequenceDiagram
    actor User
    participant BotClient
    participant CallbackQuery
    participant TelegramAPI

    User->>BotClient: presses_inline_button
    BotClient->>CallbackQuery: handler_calls_edit_message_caption(caption, parse_mode, reply_markup, business_connection_id)

    alt has_message_id_and_not_inline
        CallbackQuery->>BotClient: edit_message_caption_via_client(chat_id, message_id, caption, parse_mode, reply_markup, business_connection_id_or_message_business_connection_id)
        BotClient->>TelegramAPI: editMessageCaption(request_with_business_connection_id)
        TelegramAPI-->>BotClient: edited_message_or_bool
        BotClient-->>CallbackQuery: result
    else has_inline_message_id
        CallbackQuery->>BotClient: edit_inline_caption(inline_message_id, caption, parse_mode, reply_markup)
        BotClient->>TelegramAPI: editInlineMessageCaption(request)
        TelegramAPI-->>BotClient: bool
        BotClient-->>CallbackQuery: result
    end

    CallbackQuery-->>BotClient: edited_message_or_bool
    BotClient-->>User: updated_message_caption_visible
Loading

Sequence diagram for delete_scheduled_messages single vs multiple ids

sequenceDiagram
    actor User
    participant Client
    participant TelegramAPI

    User->>Client: delete_scheduled_messages(chat_id, message_ids)
    Client->>Client: resolve_peer(chat_id)

    alt message_ids_is_int
        Client->>Client: is_iterable = False
        Client->>Client: ids = [message_ids]
    else message_ids_is_iterable
        Client->>Client: is_iterable = True
        Client->>Client: ids = list(message_ids)
    end

    Client->>TelegramAPI: DeleteScheduledMessages(peer, ids)
    TelegramAPI-->>Client: success_ack

    alt is_iterable
        Client-->>User: ids_list
    else
        Client-->>User: single_id
    end
Loading

Class diagram for updated story parsing and retrieval

classDiagram
    class Client {
        +get_stories(chat_id int_or_str, story_ids int_or_iterable) StoryOrSkippedOrDeletedOrListOrNone
    }

    class MessageMediaStory {
    }

    class MessageStory {
        +from_user
        +sender_chat
        +_parse(client Client, message_story MessageMediaStory) MessageStoryOrStoryOrSkippedOrDeletedOrListOrNone
    }

    class Story {
    }

    class StorySkipped {
    }

    class StoryDeleted {
    }

    Client "1" --> "many" Story : returns
    Client "1" --> "many" StorySkipped : returns
    Client "1" --> "many" StoryDeleted : returns

    MessageStory --> MessageMediaStory : wraps
    MessageStory --> Story : may_return
    MessageStory --> StorySkipped : may_return
    MessageStory --> StoryDeleted : may_return

    %% Type aliases (expressed as pseudo methods for clarity)
    class StoryOrSkippedOrDeletedOrListOrNone {
    }
    class MessageStoryOrStoryOrSkippedOrDeletedOrListOrNone {
    }

    Client --> StoryOrSkippedOrDeletedOrListOrNone : get_stories
    MessageStory --> MessageStoryOrStoryOrSkippedOrDeletedOrListOrNone : _parse
Loading

Class diagram for updated business star gift operations

classDiagram
    class Client {
        +get_user_gifts(user_id int_or_str, offset int, limit int) StarGiftsPage
        +send_gift(chat_id int_or_str, gift_id int, is_private bool, text str, entities list_MessageEntity) PaymentResult
        +sell_gift(chat_id int_or_str, message_id int) PaymentResult
        +toggle_gift_is_saved(chat_id int_or_str, message_id int, is_saved bool) PaymentResult
    }

    class GetSavedStarGifts {
        +peer
        +offset
        +limit
    }

    class SaveStarGift {
        +stargift InputSavedStarGiftUser
        +unsave bool
    }

    class ConvertStarGift {
        +stargift InputSavedStarGiftUser
    }

    class InputSavedStarGiftUser {
        +msg_id int
    }

    class InputInvoiceStarGift {
        +peer
        +gift_id int
        +hide_name bool
        +message TextWithEntities
    }

    class TextWithEntities {
        +text str
        +entities list_MessageEntity
    }

    class MessageEntity {
    }

    class StarGiftsPage {
    }

    class PaymentResult {
    }

    Client --> GetSavedStarGifts : invokes
    Client --> SaveStarGift : invokes
    Client --> ConvertStarGift : invokes
    Client --> InputInvoiceStarGift : creates

    SaveStarGift --> InputSavedStarGiftUser : uses
    ConvertStarGift --> InputSavedStarGiftUser : uses
    InputInvoiceStarGift --> TextWithEntities : uses
    TextWithEntities --> MessageEntity : has

    GetSavedStarGifts --> StarGiftsPage : returns
    SaveStarGift --> PaymentResult : returns
    ConvertStarGift --> PaymentResult : returns
    InputInvoiceStarGift --> PaymentResult : part_of_send_gift_response

    Client --> StarGiftsPage : get_user_gifts
    Client --> PaymentResult : send_gift
    Client --> PaymentResult : sell_gift
    Client --> PaymentResult : toggle_gift_is_saved
Loading

File-Level Changes

Change Details Files
Refactor TL type-hint generation to be composable and return concrete Python typing-compatible strings, with special handling for flags and raw.base types.
  • Wraps core type resolution into an inner get_hint helper with recursive handling of vectors.
  • Normalizes TL core types (long/int, double, string, Bool) to Python primitives and raw objects to raw.base.* names.
  • Changes vector hints to use list[...] and returns quoted annotations for raw.base types, with distinct handling of optional-flagged fields.
compiler/api/compiler.py
Harden message/text/markup handling and entity parsing against None values and make click/copy behaviors more robust.
  • Guard Parser.unparse markdown/html access with entities or [] to avoid None iteration.
  • Replace filter/lambda-based entity filtering with list comprehensions that skip None in message entities and quote_entities.
  • Ensure send_web_page always uses a non-None text, defaulting to an empty string.
  • Relax get_chat in button click to always fetch full chat and allow string buttons by extracting .text when needed.
  • Simplify get_messages typing usage in copy_message.
pyrogram/types/messages_and_media/message.py
pyrogram/methods/messages/copy_message.py
Improve callback query edit helpers and message caption/media editing to support business connections and correct inline vs normal routing.
  • Add business_connection_id parameter to edit_message_text and pass through existing message.business_connection_id when not provided.
  • Change edit_message_caption to call edit_message_caption/edit_inline_caption directly depending on inline_message_id instead of delegating to edit_message_text.
  • Relax invert_media type to bool
None in both edit_message_caption and edit_message_media to match newer API semantics.
Fix scheduled message deletion to use the correct Telegram API and return local ids instead of server response objects.
  • Switch from channels.DeleteMessages to messages.DeleteScheduledMessages when deleting scheduled messages.
  • Normalize message_ids to an ids list and return that list or single id depending on input type.
pyrogram/methods/messages/delete_scheduled_messages.py
Make chat/user/event parsing more defensive against missing or None entries in user/chat maps and linked structures.
  • Rename users/chats dicts to users_map/chats_map and switch lookups to .get(...) when parsing ChatEvent data.
  • Guard Chat._parse_chat against None and propagate None safely to linked chat parsing and ChatJoinRequest parsing.
  • Ensure ChatJoiner.approved_by and ChatJoinRequest.chat handle missing IDs safely by checking for None before lookups.
pyrogram/types/user_and_chats/chat_event.py
pyrogram/types/user_and_chats/chat.py
pyrogram/types/user_and_chats/chat_join_request.py
pyrogram/types/user_and_chats/chat_joiner.py
Tighten story and giveaway parsing return types to reflect skipped/deleted and non-giveaway cases.
  • Expand MessageStory._parse and get_stories return type hints to include StorySkipped and StoryDeleted as single or list elements.
  • Make Giveaway._parse return Giveaway
None and bail out early if message.media is not MessageMediaGiveaway.
Clean up draft message parsing and reaction counting to avoid using unused fields and to guard against None reactions.
  • Remove unused file_name extraction and field from DraftMessage._parse.
  • Update Reaction._parse_count to only assign count/chosen_order when the parsed Reaction is not None.
pyrogram/types/messages_and_media/draft_message.py
pyrogram/types/messages_and_media/reaction.py
Align color, gift, and business payment methods with updated Telegram raw API shapes.
  • Wrap account.UpdateColor color/background_emoji into a PeerColor object instead of passing bare values.
  • Update get_user_gifts to use payments.GetSavedStarGifts(peer=...) instead of GetUserStarGifts(user_id=...).
  • Change sell_gift and toggle_gift_is_saved to send InputSavedStarGiftUser in stargift instead of user_id/msg_id pairs.
  • Use InputInvoiceStarGift.peer instead of user_id in send_gift.
pyrogram/methods/chats/update_color.py
pyrogram/methods/business/get_user_gifts.py
pyrogram/methods/business/sell_gift.py
pyrogram/methods/business/toggle_gift_is_saved.py
pyrogram/methods/business/send_gift.py
Harden low-level RPC, file saving, and hashtag search helpers for correctness and clarity.
  • Rename local variable in Auth.invoke to packed_data to better represent content sent over the wire.
  • Ensure save_file generates an integer file_id when it was previously None before using rnd_id.
  • Fix global hashtag search pagination by caching offset_rate in a variable and updating it from last.date instead of misusing offset_date.
pyrogram/session/auth.py
pyrogram/methods/advanced/save_file.py
pyrogram/methods/messages/search_global_hashtag_messages.py
Miscellaneous small fixes to API usage, type hints, and configuration.
  • Adjust handle_updates to guard channel_id with or 0 when passing into get_channel_id.
  • Loosen ChatInviteLink._parse input to accept None invites and exit early when invite is not ChatInviteExported.
  • Update get_custom_emoji_stickers to use a separate ids variable instead of mutating custom_emoji_ids and rely on is_list for return shape.
  • Remove schedule_repeat_period from send_media_group call to align with updated API.
  • Drop raw_reply_to_message in send_paid_media construction and rely only on replies count.
  • Ignore invalid-assignment errors in pyproject.toml type checker configuration.
pyrogram/client.py
pyrogram/types/user_and_chats/chat_invite_link.py
pyrogram/methods/messages/get_custom_emoji_stickers.py
pyrogram/methods/messages/send_media_group.py
pyrogram/methods/messages/send_paid_media.py
pyproject.toml
compiler/api/template/combinator.txt
compiler/api/template/type.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @5hojib, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request modernizes the codebase by adopting contemporary Python type hinting practices, enhancing the compiler's type generation capabilities, and updating several Telegram API interaction methods to reflect recent changes. It also includes various refinements to message and chat object parsing, improving overall code robustness and clarity.

Highlights

  • Type Hinting Modernization: Updated type hints across various files to leverage Python 3.9+ features, including from __future__ import annotations and native union types (|) instead of typing.List and typing.Optional.
  • Compiler Logic Refinement: The get_type_hint function in the compiler was refactored to improve the generation of type hints, particularly for optional types and raw.base imports, by introducing a nested helper function.
  • Telegram API Method Updates: Several methods interacting with the Telegram API were updated to align with recent API changes, specifically concerning gift-related functions (get_user_gifts, sell_gift, send_gift, toggle_gift_is_saved) and the update_color method.
  • Message Handling Improvements: Enhanced message parsing and handling logic, including changes to delete_scheduled_messages, edit_message_caption, edit_message_media, and more robust entity parsing in Message objects.
  • Null Safety and Robustness: Implemented additional null checks and safer access patterns in various parsing functions, especially within pyrogram/types for chat, user, and event-related objects, to prevent potential errors.
Changelog
  • compiler/api/compiler.py
    • Refactored get_type_hint function to use a nested helper and improved logic for optional and raw type hint generation.
    • Changed is_flag assignment to explicitly cast FLAGS_RE.match(type) result to boolean.
  • compiler/api/template/combinator.txt
    • Added from __future__ import annotations import.
    • Replaced typing.List with native list for type hints.
  • compiler/api/template/type.txt
    • Added from __future__ import annotations import.
  • pyproject.toml
    • Added invalid-assignment = "ignore" to [tool.ty.rules].
  • pyrogram/client.py
    • Modified get_channel_id call to handle channel_id potentially being None by providing a default of 0.
  • pyrogram/methods/advanced/save_file.py
    • Ensured file_id is explicitly cast to an integer when generated randomly.
  • pyrogram/methods/business/get_user_gifts.py
    • Updated API call from GetUserStarGifts to GetSavedStarGifts and changed user_id parameter to peer.
  • pyrogram/methods/business/sell_gift.py
    • Modified ConvertStarGift API call to use stargift=raw.types.InputSavedStarGiftUser(msg_id=message_id).
  • pyrogram/methods/business/send_gift.py
    • Updated InputInvoiceStarGift to use peer instead of user_id.
  • pyrogram/methods/business/toggle_gift_is_saved.py
    • Modified SaveStarGift API call to use stargift=raw.types.InputSavedStarGiftUser(msg_id=message_id).
  • pyrogram/methods/chats/update_color.py
    • Wrapped color and background_emoji_id parameters within a raw.types.PeerColor object for the UpdateColor API call.
  • pyrogram/methods/messages/copy_message.py
    • Removed explicit types.Message type hint for the message variable.
  • pyrogram/methods/messages/delete_scheduled_messages.py
    • Updated return type hint to int | list[int].
    • Switched API call from raw.functions.channels.DeleteMessages to raw.functions.messages.DeleteScheduledMessages.
  • pyrogram/methods/messages/edit_message_caption.py
    • Changed invert_media parameter type from bool to bool | None.
  • pyrogram/methods/messages/edit_message_media.py
    • Changed invert_media parameter type from bool to bool | None.
  • pyrogram/methods/messages/get_custom_emoji_stickers.py
    • Renamed local variable custom_emoji_ids to ids for clarity.
  • pyrogram/methods/messages/search_global_hashtag_messages.py
    • Introduced a new offset_rate variable to store the timestamp conversion result, avoiding redundant calls.
  • pyrogram/methods/messages/send_media_group.py
    • Removed the schedule_repeat_period parameter from the send_media_group invocation.
  • pyrogram/methods/messages/send_paid_media.py
    • Removed the raw_reply_to_message parameter from the send_paid_media invocation.
  • pyrogram/methods/stories/get_stories.py
    • Expanded the return type hint to include types.StorySkipped and types.StoryDeleted alongside types.Story.
  • pyrogram/session/auth.py
    • Renamed local variable data to packed_data in the invoke method for better readability.
  • pyrogram/types/bots_and_keyboards/callback_query.py
    • Added business_connection_id parameter to edit_message_text and edit_message_caption methods.
    • Refactored edit_message_caption to use _client.edit_message_caption or _client.edit_inline_caption based on inline_message_id.
  • pyrogram/types/messages_and_media/draft_message.py
    • Removed the file_name attribute from the _parse method's logic and initialization.
  • pyrogram/types/messages_and_media/giveaway.py
    • Added a type check to ensure message.media is raw.types.MessageMediaGiveaway before parsing.
    • Updated the return type hint of _parse to include None.
  • pyrogram/types/messages_and_media/message.py
    • Ensured self.entities is treated as an empty list if None in markdown and html properties.
    • Refined entity parsing logic to filter out None entities more explicitly.
    • Updated copy method to handle self.text potentially being None.
    • Modified click method to correctly handle button text when replying.
  • pyrogram/types/messages_and_media/message_story.py
    • Expanded the return type hint for _parse to include types.StorySkipped and types.StoryDeleted.
  • pyrogram/types/messages_and_media/reaction.py
    • Added a null check for reaction before assigning count and chosen_order in _parse_count.
  • pyrogram/types/user_and_chats/chat.py
    • Added a null check for the chat parameter in the _parse_chat static method.
  • pyrogram/types/user_and_chats/chat_event.py
    • Replaced direct dictionary access with get() method for users_map and chats_map to handle potential missing keys gracefully.
    • Added null checks for action.prev_value and action.new_value when parsing linked chat changes.
  • pyrogram/types/user_and_chats/chat_invite_link.py
    • Updated the invite parameter type hint in _parse to allow None.
  • pyrogram/types/user_and_chats/chat_join_request.py
    • Added a null check for chat_id before passing it to types.Chat._parse_chat.
  • pyrogram/types/user_and_chats/chat_joiner.py
    • Added a null check for joiner.approved_by before passing it to types.User._parse.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • In CallbackQuery.edit_message_caption, the business_connection_id argument is only forwarded for non-inline messages; for inline_message_id the value is ignored, which is inconsistent with edit_message_text and likely drops business-specific context.
  • In messages.send_media_group, the schedule_repeat_period argument is no longer passed to the underlying request, so callers setting this parameter will silently see no effect; consider re‑wiring it through or removing the parameter from the public API.
  • In search_global_hashtag_messages, offset_date is now unused after introducing offset_rate; either remove offset_date entirely or keep the variable names aligned to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CallbackQuery.edit_message_caption`, the `business_connection_id` argument is only forwarded for non-inline messages; for `inline_message_id` the value is ignored, which is inconsistent with `edit_message_text` and likely drops business-specific context.
- In `messages.send_media_group`, the `schedule_repeat_period` argument is no longer passed to the underlying request, so callers setting this parameter will silently see no effect; consider re‑wiring it through or removing the parameter from the public API.
- In `search_global_hashtag_messages`, `offset_date` is now unused after introducing `offset_rate`; either remove `offset_date` entirely or keep the variable names aligned to avoid confusion.

## Individual Comments

### Comment 1
<location> `pyrogram/types/user_and_chats/chat.py:773-776` </location>
<code_context>
-        chat: raw.types.Chat | raw.types.User | raw.types.Channel,
+        chat: raw.types.Chat | raw.types.User | raw.types.Channel | None,
     ) -> Chat | None:
+        if chat is None:
+            return None
+
         if isinstance(chat, raw.types.Chat | raw.types.ChatForbidden):
             return Chat._parse_chat_chat(client, chat)
         if isinstance(chat, raw.types.User):
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `isinstance` with a PEP 604 union (`A | B`) will raise at runtime.

`isinstance(chat, raw.types.Chat | raw.types.ChatForbidden)` will raise `TypeError` at runtime because `isinstance` doesn’t accept PEP 604 unions. Use a tuple instead: `isinstance(chat, (raw.types.Chat, raw.types.ChatForbidden))` to preserve the intended behavior.
</issue_to_address>

### Comment 2
<location> `compiler/api/compiler.py:91` </location>
<code_context>


 def get_type_hint(type: str) -> str:
-    is_flag = FLAGS_RE.match(type)
+    is_flag = bool(FLAGS_RE.match(type))
</code_context>

<issue_to_address>
**issue (complexity):** Consider splitting type resolution from formatting by extracting a `get_base_hint` helper that returns both the base hint and whether it is a `raw.base` type, simplifying `get_type_hint`’s control flow and string handling.

You can keep the new behavior (PEP 604 unions, `raw.base` handling) while simplifying flow by separating “type resolution” from “formatting”, and by avoiding substring checks on `"raw.base"`.

For example:

```py
def get_type_hint(type: str) -> str:
    is_flag = bool(FLAGS_RE.match(type))
    if is_flag:
        type = type.split("?")[1]

    hint, is_raw_base = get_base_hint(type)

    if is_flag:
        if is_raw_base:
            return f'"{hint} | None" = None'
        return f"{hint} | None = None"

    if is_raw_base:
        return f'"{hint}"'
    return hint


def get_base_hint(t: str) -> tuple[str, bool]:
    if t in CORE_TYPES:
        if t == "long" or "int" in t:
            return "int", False
        if t == "double":
            return "float", False
        if t == "string":
            return "str", False
        if t in {"Bool", "true"}:
            return "bool", False
        return "bytes", False

    if t in {"Object", "!X"}:
        return "TLObject", False

    if re.match("^vector", t, re.IGNORECASE):
        sub_type = t.split("<", 1)[1][:-1]
        sub_hint, _ = get_base_hint(sub_type)
        return f"list[{sub_hint}]", False

    ns, name = t.split(".") if "." in t else ("", t)
    return "raw.base." + ".".join([ns, name]).strip("."), True
```

This keeps all new semantics but:

- Removes nested function inside `get_type_hint`.
- Encodes `raw.base` as explicit state (`is_raw_base`) instead of string inspection.
- Keeps quoting/`| None` logic in a single, small, post-processing block.
</issue_to_address>

### Comment 3
<location> `pyrogram/types/messages_and_media/message.py:1133` </location>
<code_context>
-                for entity in (message.entities or [])
-            ]
-            entities = types.List(filter(lambda x: x is not None, entities))
+            entities = types.List(
+                [
+                    e
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing the new nested list comprehensions for `entities` and `quote_entities` with straightforward loops that build filtered lists in a single pass for better readability and debuggability.

The new nested list comprehensions for `entities` and `quote_entities` make the logic harder to follow without changing behavior. You can keep the explicit filtering of `None` while simplifying to a single pass that’s easier to read and debug.

For `entities`:

```python
# Current
entities = types.List(
    [
        e
        for e in [
            types.MessageEntity._parse(client, entity, users)
            for entity in (message.entities or [])
        ]
        if e is not None
    ],
)

# Suggested
parsed_entities = []
for entity in (message.entities or []):
    e = types.MessageEntity._parse(client, entity, users)
    if e is not None:
        parsed_entities.append(e)

entities = types.List(parsed_entities)
```

For `quote_entities`:

```python
# Current
if message.reply_to.quote_entities:
    parsed_message.quote_entities = types.List(
        [
            e
            for e in [
                types.MessageEntity._parse(client, entity, users)
                for entity in message.reply_to.quote_entities
            ]
            if e is not None
        ],
    )

# Suggested
if message.reply_to.quote_entities:
    parsed_quote_entities = []
    for entity in message.reply_to.quote_entities:
        e = types.MessageEntity._parse(client, entity, users)
        if e is not None:
            parsed_quote_entities.append(e)

    parsed_message.quote_entities = types.List(parsed_quote_entities)
```

This keeps behavior intact, preserves the explicit `None` filtering, avoids double iteration, and removes the nested comprehension structure that other reviewers flagged as complex.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +773 to 776
if chat is None:
return None

if isinstance(chat, raw.types.Chat | raw.types.ChatForbidden):
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Using isinstance with a PEP 604 union (A | B) will raise at runtime.

isinstance(chat, raw.types.Chat | raw.types.ChatForbidden) will raise TypeError at runtime because isinstance doesn’t accept PEP 604 unions. Use a tuple instead: isinstance(chat, (raw.types.Chat, raw.types.ChatForbidden)) to preserve the intended behavior.

@@ -89,32 +89,40 @@ def camel(s: str):


def get_type_hint(type: str) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): Consider splitting type resolution from formatting by extracting a get_base_hint helper that returns both the base hint and whether it is a raw.base type, simplifying get_type_hint’s control flow and string handling.

You can keep the new behavior (PEP 604 unions, raw.base handling) while simplifying flow by separating “type resolution” from “formatting”, and by avoiding substring checks on "raw.base".

For example:

def get_type_hint(type: str) -> str:
    is_flag = bool(FLAGS_RE.match(type))
    if is_flag:
        type = type.split("?")[1]

    hint, is_raw_base = get_base_hint(type)

    if is_flag:
        if is_raw_base:
            return f'"{hint} | None" = None'
        return f"{hint} | None = None"

    if is_raw_base:
        return f'"{hint}"'
    return hint


def get_base_hint(t: str) -> tuple[str, bool]:
    if t in CORE_TYPES:
        if t == "long" or "int" in t:
            return "int", False
        if t == "double":
            return "float", False
        if t == "string":
            return "str", False
        if t in {"Bool", "true"}:
            return "bool", False
        return "bytes", False

    if t in {"Object", "!X"}:
        return "TLObject", False

    if re.match("^vector", t, re.IGNORECASE):
        sub_type = t.split("<", 1)[1][:-1]
        sub_hint, _ = get_base_hint(sub_type)
        return f"list[{sub_hint}]", False

    ns, name = t.split(".") if "." in t else ("", t)
    return "raw.base." + ".".join([ns, name]).strip("."), True

This keeps all new semantics but:

  • Removes nested function inside get_type_hint.
  • Encodes raw.base as explicit state (is_raw_base) instead of string inspection.
  • Keeps quoting/| None logic in a single, small, post-processing block.

for entity in (message.entities or [])
]
entities = types.List(filter(lambda x: x is not None, entities))
entities = types.List(
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): Consider replacing the new nested list comprehensions for entities and quote_entities with straightforward loops that build filtered lists in a single pass for better readability and debuggability.

The new nested list comprehensions for entities and quote_entities make the logic harder to follow without changing behavior. You can keep the explicit filtering of None while simplifying to a single pass that’s easier to read and debug.

For entities:

# Current
entities = types.List(
    [
        e
        for e in [
            types.MessageEntity._parse(client, entity, users)
            for entity in (message.entities or [])
        ]
        if e is not None
    ],
)

# Suggested
parsed_entities = []
for entity in (message.entities or []):
    e = types.MessageEntity._parse(client, entity, users)
    if e is not None:
        parsed_entities.append(e)

entities = types.List(parsed_entities)

For quote_entities:

# Current
if message.reply_to.quote_entities:
    parsed_message.quote_entities = types.List(
        [
            e
            for e in [
                types.MessageEntity._parse(client, entity, users)
                for entity in message.reply_to.quote_entities
            ]
            if e is not None
        ],
    )

# Suggested
if message.reply_to.quote_entities:
    parsed_quote_entities = []
    for entity in message.reply_to.quote_entities:
        e = types.MessageEntity._parse(client, entity, users)
        if e is not None:
            parsed_quote_entities.append(e)

    parsed_message.quote_entities = types.List(parsed_quote_entities)

This keeps behavior intact, preserves the explicit None filtering, avoids double iteration, and removes the nested comprehension structure that other reviewers flagged as complex.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a substantial number of improvements across the codebase, aligning with recent Telegram API changes, enhancing type safety, and fixing several bugs. The refactoring in the API compiler and the robustness improvements in various parsers are particularly noteworthy. The changes make the library more reliable and maintainable. I have a couple of suggestions for minor refactoring to further improve code readability.

Comment on lines +1133 to +1142
entities = types.List(
[
e
for e in [
types.MessageEntity._parse(client, entity, users)
for entity in (message.entities or [])
]
if e is not None
],
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This nested list comprehension can be a bit difficult to read. For improved readability and to make it more idiomatic, you could use a single list comprehension with an assignment expression (walrus operator), which is available in Python 3.8+.

            entities = types.List(
                parsed_entity
                for entity in message.entities or []
                if (parsed_entity := types.MessageEntity._parse(client, entity, users)) is not None
            )

Comment on lines 1524 to 1533
parsed_message.quote_entities = types.List(
filter(lambda x: x is not None, quote_entities),
[
e
for e in [
types.MessageEntity._parse(client, entity, users)
for entity in message.reply_to.quote_entities
]
if e is not None
],
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the previous comment on parsing entities, this nested list comprehension for quote_entities could be simplified for better readability using an assignment expression.

                        parsed_message.quote_entities = types.List(
                            parsed_entity
                            for entity in message.reply_to.quote_entities
                            if (
                                parsed_entity := types.MessageEntity._parse(
                                    client, entity, users
                                )
                            )
                            is not None
                        )

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@5hojib 5hojib merged commit a95776f into main Feb 22, 2026
4 checks passed
@5hojib 5hojib deleted the beta branch February 22, 2026 14:14
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.

1 participant