Skip to content
Merged

Beta #94

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions compiler/api/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

is_flag = FLAGS_RE.match(type)
is_flag = bool(FLAGS_RE.match(type))

if is_flag:
type = type.split("?")[1]

if type in CORE_TYPES:
if type == "long" or "int" in type:
type = "int"
elif type == "double":
type = "float"
elif type == "string":
type = "str"
elif type in {"Bool", "true"}:
type = "bool"
else: # bytes and object
type = "bytes"
elif type in {"Object", "!X"}:
type = "TLObject"
elif re.match("^vector", type, re.IGNORECASE):
sub_type = type.split("<")[1][:-1]
type = f"List[{get_type_hint(sub_type)}]"
else:
ns, name = type.split(".") if "." in type else ("", type)
type = '"raw.base.' + ".".join([ns, name]).strip(".") + '"'
def get_hint(t: str) -> str:
if t in CORE_TYPES:
if t in {"long", "int", "int128", "int256"}:
return "int"
if t == "double":
return "float"
if t == "string":
return "str"
if t in {"Bool", "true"}:
return "bool"
return "bytes"
if t in {"Object", "!X"}:
return "TLObject"
if re.match("^vector", t, re.IGNORECASE):
sub_type = t.split("<", 1)[1][:-1]
return f"list[{get_hint(sub_type)}]"
ns, name = t.split(".") if "." in t else ("", t)
return "raw.base." + ".".join([ns, name]).strip(".")

hint = get_hint(type)

if is_flag:
if "raw.base" in hint:
return f'"{hint} | None" = None'
return f"{hint} | None = None"

return f"Optional[{type}] = None" if is_flag else type
if "raw.base" in hint:
return f'"{hint}"'
return hint


def sort_args(args: list[tuple[str, str]]):
Expand Down
6 changes: 4 additions & 2 deletions compiler/api/template/combinator.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

from io import BytesIO

from pyrogram.raw.core.primitives import Int, Long, Int128, Int256, Bool, Bytes, String, Double, Vector
from pyrogram.raw.core import TLObject
from pyrogram import raw
from typing import List, Optional, Any
from typing import Any

{warning}

Expand All @@ -12,7 +14,7 @@ class {name}({base}): # type: ignore
"""{docstring}
"""

__slots__: List[str] = [{slots}]
__slots__: list[str] = [{slots}]

ID = {id}
QUALNAME = "{qualname}"
Expand Down
2 changes: 2 additions & 0 deletions compiler/api/template/type.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{warning}

from __future__ import annotations

from pyrogram.raw.core import TLObject

class {name}(TLObject): # type: ignore
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,4 @@ ignore = [

[tool.ty.rules]
unresolved-import = "ignore"
invalid-assignment = "ignore"
2 changes: 1 addition & 1 deletion pyrogram/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ async def handle_updates(self, updates) -> None:
diff = await self.invoke(
raw.functions.updates.GetChannelDifference(
channel=await self.resolve_peer(
utils.get_channel_id(channel_id),
utils.get_channel_id(channel_id or 0),
),
filter=raw.types.ChannelMessagesFilter(
ranges=[
Expand Down
5 changes: 4 additions & 1 deletion pyrogram/methods/advanced/save_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ def create_rpc(chunk, file_part, is_big, file_id, file_total_parts):
pool_size = 2 if is_big else 1
workers_count = 4 if is_big else 1
is_missing_part = file_id is not None
file_id = file_id or self.rnd_id()

if file_id is None:
file_id = int(self.rnd_id())

md5_sum = md5() if not is_big and not is_missing_part else None

pool = [
Expand Down
4 changes: 2 additions & 2 deletions pyrogram/methods/business/get_user_gifts.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ async def get_user_gifts(

while True:
r = await self.invoke(
raw.functions.payments.GetUserStarGifts(
user_id=peer,
raw.functions.payments.GetSavedStarGifts(
peer=peer,
offset=offset,
limit=limit,
),
Expand Down
4 changes: 3 additions & 1 deletion pyrogram/methods/business/sell_gift.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ async def sell_gift(
raise ValueError("sender_user_id must belong to a user.")

return await self.invoke(
raw.functions.payments.ConvertStarGift(user_id=peer, msg_id=message_id),
raw.functions.payments.ConvertStarGift(
stargift=raw.types.InputSavedStarGiftUser(msg_id=message_id),
),
)
2 changes: 1 addition & 1 deletion pyrogram/methods/business/send_gift.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async def send_gift(
).values()

invoice = raw.types.InputInvoiceStarGift(
user_id=peer,
peer=peer,
gift_id=gift_id,
hide_name=is_private,
message=raw.types.TextWithEntities(text=text, entities=entities or [])
Expand Down
3 changes: 1 addition & 2 deletions pyrogram/methods/business/toggle_gift_is_saved.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ async def toggle_gift_is_saved(

return await self.invoke(
raw.functions.payments.SaveStarGift(
user_id=peer,
msg_id=message_id,
stargift=raw.types.InputSavedStarGiftUser(msg_id=message_id),
unsave=not is_saved,
),
)
6 changes: 4 additions & 2 deletions pyrogram/methods/chats/update_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ async def update_color(
r = await self.invoke(
raw.functions.account.UpdateColor(
for_profile=isinstance(color, enums.ProfileColor),
color=color.value,
background_emoji_id=background_emoji_id,
color=raw.types.PeerColor(
color=color.value,
background_emoji_id=background_emoji_id,
),
),
)
else:
Expand Down
2 changes: 1 addition & 1 deletion pyrogram/methods/messages/copy_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async def copy_message(
await app.copy_message(to_chat, from_chat, 123)

"""
message: types.Message = await self.get_messages(from_chat_id, message_id)
message = await self.get_messages(from_chat_id, message_id)

return await message.copy(
chat_id=chat_id,
Expand Down
20 changes: 14 additions & 6 deletions pyrogram/methods/messages/delete_scheduled_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async def delete_scheduled_messages(
self: pyrogram.Client,
chat_id: int | str,
message_ids: int | Iterable[int],
) -> int:
) -> int | list[int]:
"""Delete scheduled messages.

.. include:: /_includes/usable-by/users-bots.rst
Expand All @@ -41,11 +41,19 @@ async def delete_scheduled_messages(
await app.delete_scheduled_messages(chat_id, list_of_message_ids)
"""
peer = await self.resolve_peer(chat_id)
is_iterable = not isinstance(message_ids, int)
message_ids = list(message_ids) if is_iterable else [message_ids]

r = await self.invoke(
raw.functions.channels.DeleteMessages(peer=peer, id=message_ids),
if isinstance(message_ids, int):
is_iterable = False
ids = [message_ids]
else:
is_iterable = True
ids = list(message_ids)

await self.invoke(
raw.functions.messages.DeleteScheduledMessages(
peer=peer, # type: ignore
id=ids,
),
)

return r.messages if is_iterable else r.messages[0]
return ids if is_iterable else ids[0]
2 changes: 1 addition & 1 deletion pyrogram/methods/messages/edit_message_caption.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async def edit_message_caption(
caption: str,
parse_mode: enums.ParseMode | None = None,
caption_entities: list[types.MessageEntity] | None = None,
invert_media: bool = False,
invert_media: bool | None = None,
reply_markup: types.InlineKeyboardMarkup | None = None,
business_connection_id: str | None = None,
) -> types.Message | None:
Expand Down
2 changes: 1 addition & 1 deletion pyrogram/methods/messages/edit_message_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def edit_message_media(
file_name: str | None = None,
parse_mode: enums.ParseMode | None = None,
business_connection_id: str | None = None,
invert_media: bool = False,
invert_media: bool | None = None,
) -> types.Message | None:
"""Edit animation, audio, document, photo or video messages, or replace text with animation, audio, document, photo or video messages.

Expand Down
4 changes: 2 additions & 2 deletions pyrogram/methods/messages/get_custom_emoji_stickers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ async def get_custom_emoji_stickers(
a list, a single sticker is returned, otherwise a list of stickers is returned.
"""
is_list = isinstance(custom_emoji_ids, list)
custom_emoji_ids = [custom_emoji_ids] if not is_list else custom_emoji_ids
ids = [custom_emoji_ids] if not is_list else custom_emoji_ids

result = await self.invoke(
raw.functions.messages.GetCustomEmojiDocuments(
document_id=custom_emoji_ids,
document_id=ids,
),
)

Expand Down
5 changes: 3 additions & 2 deletions pyrogram/methods/messages/search_global_hashtag_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ async def search_global_hashtag_messages(
limit = min(100, total)

offset_peer = raw.types.InputPeerEmpty()
offset_rate = utils.datetime_to_timestamp(offset_date)

while True:
messages = await utils.parse_messages(
self,
await self.invoke(
raw.functions.channels.SearchPosts(
hashtag=hashtag,
offset_rate=utils.datetime_to_timestamp(offset_date),
offset_rate=offset_rate,
offset_peer=offset_peer,
offset_id=offset_id,
limit=limit,
Expand All @@ -77,7 +78,7 @@ async def search_global_hashtag_messages(

last = messages[-1]

offset_date = utils.datetime_to_timestamp(last.date)
offset_rate = utils.datetime_to_timestamp(last.date)
offset_peer = await self.resolve_peer(last.chat.id)
offset_id = last.id

Expand Down
1 change: 0 additions & 1 deletion pyrogram/methods/messages/send_media_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,6 @@ async def send_media_group(
background=background,
clear_draft=clear_draft,
update_stickersets_order=update_stickersets_order,
schedule_repeat_period=schedule_repeat_period,
send_as=await self.resolve_peer(send_as) if send_as else None,
quick_reply_shortcut=await utils.get_input_quick_reply_shortcut(
quick_reply_shortcut,
Expand Down
1 change: 0 additions & 1 deletion pyrogram/methods/messages/send_paid_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ async def send_paid_media(
"connection_id",
business_connection_id,
),
raw_reply_to_message=i.reply_to_message,
replies=0,
)
return None
8 changes: 7 additions & 1 deletion pyrogram/methods/stories/get_stories.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ async def get_stories(
self: pyrogram.Client,
chat_id: int | str,
story_ids: int | Iterable[int],
) -> types.Story | list[types.Story] | None:
) -> (
types.Story
| types.StorySkipped
| types.StoryDeleted
| list[types.Story | types.StorySkipped | types.StoryDeleted]
| None
):
"""Get one or more story from an user by using story identifiers.

.. include:: /_includes/usable-by/users.rst
Expand Down
4 changes: 2 additions & 2 deletions pyrogram/session/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ def unpack(b: BytesIO):
return TLObject.read(b)

async def invoke(self, data: TLObject):
data = self.pack(data)
await self.connection.send(data)
packed_data = self.pack(data)
await self.connection.send(packed_data)
response = BytesIO(await self.connection.recv())

return self.unpack(response)
Expand Down
38 changes: 27 additions & 11 deletions pyrogram/types/bots_and_keyboards/callback_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ async def edit_message_text(
parse_mode: enums.ParseMode | None = None,
disable_web_page_preview: bool | None = None,
reply_markup: types.InlineKeyboardMarkup | None = None,
business_connection_id: str | None = None,
) -> types.Message | bool | None:
"""Edit the text of messages attached to callback queries.

Expand All @@ -194,6 +195,10 @@ async def edit_message_text(
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.

business_connection_id (``str``, *optional*):
Unique identifier of the business connection.
for business bots only.

Returns:
:obj:`~pyrogram.types.Message` | ``bool``: On success, if the edited message was sent by the bot, the edited
message is returned, otherwise True is returned (message sent via the bot, as inline query result).
Expand All @@ -213,7 +218,9 @@ async def edit_message_text(
self.message,
"business_connection_id",
None,
),
)
if business_connection_id is None
else business_connection_id,
)
return await self._client.edit_inline_text(
inline_message_id=self.inline_message_id,
Expand Down Expand Up @@ -256,17 +263,26 @@ async def edit_message_caption(
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self.edit_message_text(
caption,
parse_mode,
reply_markup=reply_markup,
business_connection_id=getattr(
self.message,
"business_connection_id",
None,
if self.inline_message_id is None:
return await self._client.edit_message_caption(
chat_id=self.message.chat.id,
message_id=self.message.id,
caption=caption,
parse_mode=parse_mode,
reply_markup=reply_markup,
business_connection_id=getattr(
self.message,
"business_connection_id",
None,
)
if business_connection_id is None
else business_connection_id,
)
if business_connection_id is None
else business_connection_id,
return await self._client.edit_inline_caption(
inline_message_id=self.inline_message_id,
caption=caption,
parse_mode=parse_mode,
reply_markup=reply_markup,
)

async def edit_message_media(
Expand Down
Loading