diff --git a/changes/unreleased/5186.VngJMeztfXKDXAhcqTxJjK.toml b/changes/unreleased/5186.VngJMeztfXKDXAhcqTxJjK.toml new file mode 100644 index 00000000000..43d894d3860 --- /dev/null +++ b/changes/unreleased/5186.VngJMeztfXKDXAhcqTxJjK.toml @@ -0,0 +1,5 @@ +other = "Centralize `de_json` into `TelegramObject`" +[[pull_requests]] +uid = "5186" +author_uids = ["harshil21"] +closes_threads = [] diff --git a/src/telegram/_botcommandscope.py b/src/telegram/_botcommandscope.py index cde05a79ac6..44aa70389d5 100644 --- a/src/telegram/_botcommandscope.py +++ b/src/telegram/_botcommandscope.py @@ -19,16 +19,13 @@ # pylint: disable=redefined-builtin """This module contains objects representing Telegram bot command scopes.""" -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._telegramobject import TelegramObject from telegram._utils import enum from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class BotCommandScope(TelegramObject): """Base class for objects that represent the scope to which bot commands are applied. @@ -62,6 +59,19 @@ class BotCommandScope(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "default": "BotCommandScopeDefault", + "all_private_chats": "BotCommandScopeAllPrivateChats", + "all_group_chats": "BotCommandScopeAllGroupChats", + "all_chat_administrators": "BotCommandScopeAllChatAdministrators", + "chat": "BotCommandScopeChat", + "chat_administrators": "BotCommandScopeChatAdministrators", + "chat_member": "BotCommandScopeChatMember", + }, + ) + DEFAULT: Final[str] = constants.BotCommandScopeType.DEFAULT """:const:`telegram.constants.BotCommandScopeType.DEFAULT`""" ALL_PRIVATE_CHATS: Final[str] = constants.BotCommandScopeType.ALL_PRIVATE_CHATS @@ -84,39 +94,6 @@ def __init__(self, type: str, *, api_kwargs: JSONDict | None = None): self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BotCommandScope": - """Converts JSON data to the appropriate :class:`BotCommandScope` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`, optional): The bot associated with this object. Defaults to - :obj:`None`, in which case shortcut methods will not be available. - - .. versionchanged:: 21.4 - :paramref:`bot` is now optional and defaults to :obj:`None` - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[BotCommandScope]] = { - cls.DEFAULT: BotCommandScopeDefault, - cls.ALL_PRIVATE_CHATS: BotCommandScopeAllPrivateChats, - cls.ALL_GROUP_CHATS: BotCommandScopeAllGroupChats, - cls.ALL_CHAT_ADMINISTRATORS: BotCommandScopeAllChatAdministrators, - cls.CHAT: BotCommandScopeChat, - cls.CHAT_ADMINISTRATORS: BotCommandScopeChatAdministrators, - cls.CHAT_MEMBER: BotCommandScopeChatMember, - } - - if cls is BotCommandScope and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - return super().de_json(data=data, bot=bot) - class BotCommandScopeDefault(BotCommandScope): """Represents the default scope of bot commands. Default commands are used if no commands with diff --git a/src/telegram/_business.py b/src/telegram/_business.py index 5b2026c62d0..58b619465be 100644 --- a/src/telegram/_business.py +++ b/src/telegram/_business.py @@ -25,24 +25,19 @@ from zoneinfo import ZoneInfo from telegram._chat import Chat -from telegram._files.location import Location from telegram._files.sticker import Sticker from telegram._telegramobject import TelegramObject -from telegram._user import User from telegram._utils.argumentparsing import ( - de_json_optional, - de_list_optional, parse_sequence_arg, ) from telegram._utils.datetime import ( - extract_tzinfo_from_defaults, - from_timestamp, get_zone_info, ) from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot + from telegram._files.location import Location + from telegram._user import User class BusinessBotRights(TelegramObject): @@ -266,20 +261,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BusinessConnection": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo) - data["user"] = de_json_optional(data.get("user"), User, bot) - data["rights"] = de_json_optional(data.get("rights"), BusinessBotRights, bot) - - return super().de_json(data=data, bot=bot) - class BusinessMessagesDeleted(TelegramObject): """ @@ -333,15 +314,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BusinessMessagesDeleted": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - - return super().de_json(data=data, bot=bot) - class BusinessIntro(TelegramObject): """ @@ -387,15 +359,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BusinessIntro": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) - - return super().de_json(data=data, bot=bot) - class BusinessLocation(TelegramObject): """ @@ -436,15 +399,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BusinessLocation": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["location"] = de_json_optional(data.get("location"), Location, bot) - - return super().de_json(data=data, bot=bot) - class BusinessOpeningHoursInterval(TelegramObject): """ @@ -678,14 +632,3 @@ def is_open(self, datetime: dtm.datetime) -> bool: return True return False - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BusinessOpeningHours": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["opening_hours"] = de_list_optional( - data.get("opening_hours"), BusinessOpeningHoursInterval, bot - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_callbackquery.py b/src/telegram/_callbackquery.py index 9004de89388..38ce19814c0 100644 --- a/src/telegram/_callbackquery.py +++ b/src/telegram/_callbackquery.py @@ -27,13 +27,11 @@ from telegram._message import MaybeInaccessibleMessage, Message from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput, TimePeriod if TYPE_CHECKING: from telegram import ( - Bot, GameHighScore, InlineKeyboardMarkup, InputMedia, @@ -153,16 +151,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "CallbackQuery": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["message"] = de_json_optional(data.get("message"), Message, bot) - - return super().de_json(data=data, bot=bot) - async def answer( self, text: str | None = None, diff --git a/src/telegram/_chatbackground.py b/src/telegram/_chatbackground.py index 5b71259d58e..fe833b0f518 100644 --- a/src/telegram/_chatbackground.py +++ b/src/telegram/_chatbackground.py @@ -19,18 +19,15 @@ """This module contains objects related to chat backgrounds.""" from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._files.document import Document from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class BackgroundFill(TelegramObject): """Base class for Telegram BackgroundFill Objects. It can be one of: @@ -57,6 +54,15 @@ class BackgroundFill(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "solid": "BackgroundFillSolid", + "gradient": "BackgroundFillGradient", + "freeform_gradient": "BackgroundFillFreeformGradient", + }, + ) + SOLID: Final[constants.BackgroundFillType] = constants.BackgroundFillType.SOLID """:const:`telegram.constants.BackgroundFillType.SOLID`""" GRADIENT: Final[constants.BackgroundFillType] = constants.BackgroundFillType.GRADIENT @@ -79,22 +85,6 @@ def __init__( self._id_attrs = (self.type,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BackgroundFill": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - _class_mapping: dict[str, type[BackgroundFill]] = { - cls.SOLID: BackgroundFillSolid, - cls.GRADIENT: BackgroundFillGradient, - cls.FREEFORM_GRADIENT: BackgroundFillFreeformGradient, - } - - if cls is BackgroundFill and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - return super().de_json(data=data, bot=bot) - class BackgroundFillSolid(BackgroundFill): """ @@ -243,6 +233,16 @@ class BackgroundType(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "fill": "BackgroundTypeFill", + "wallpaper": "BackgroundTypeWallpaper", + "pattern": "BackgroundTypePattern", + "chat_theme": "BackgroundTypeChatTheme", + }, + ) + FILL: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.FILL """:const:`telegram.constants.BackgroundTypeType.FILL`""" WALLPAPER: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.WALLPAPER @@ -265,29 +265,6 @@ def __init__( self._id_attrs = (self.type,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BackgroundType": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - _class_mapping: dict[str, type[BackgroundType]] = { - cls.FILL: BackgroundTypeFill, - cls.WALLPAPER: BackgroundTypeWallpaper, - cls.PATTERN: BackgroundTypePattern, - cls.CHAT_THEME: BackgroundTypeChatTheme, - } - - if cls is BackgroundType and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - if "fill" in data: - data["fill"] = de_json_optional(data.get("fill"), BackgroundFill, bot) - - if "document" in data: - data["document"] = de_json_optional(data.get("document"), Document, bot) - - return super().de_json(data=data, bot=bot) - class BackgroundTypeFill(BackgroundType): """ @@ -522,12 +499,3 @@ def __init__( self._id_attrs = (self.type,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatBackground": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["type"] = de_json_optional(data.get("type"), BackgroundType, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_chatboost.py b/src/telegram/_chatboost.py index 2386f2e6a5c..4889f9c9ec8 100644 --- a/src/telegram/_chatboost.py +++ b/src/telegram/_chatboost.py @@ -20,20 +20,16 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._chat import Chat from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ChatBoostAdded(TelegramObject): """ @@ -94,6 +90,15 @@ class ChatBoostSource(TelegramObject): __slots__ = ("source",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "source", + { + "premium": "ChatBoostSourcePremium", + "gift_code": "ChatBoostSourceGiftCode", + "giveaway": "ChatBoostSourceGiveaway", + }, + ) + PREMIUM: Final[str] = constants.ChatBoostSources.PREMIUM """:const:`telegram.constants.ChatBoostSources.PREMIUM`""" GIFT_CODE: Final[str] = constants.ChatBoostSources.GIFT_CODE @@ -110,25 +115,6 @@ def __init__(self, source: str, *, api_kwargs: JSONDict | None = None): self._id_attrs = (self.source,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatBoostSource": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - _class_mapping: dict[str, type[ChatBoostSource]] = { - cls.PREMIUM: ChatBoostSourcePremium, - cls.GIFT_CODE: ChatBoostSourceGiftCode, - cls.GIVEAWAY: ChatBoostSourceGiveaway, - } - - if cls is ChatBoostSource and data.get("source") in _class_mapping: - return _class_mapping[data.pop("source")].de_json(data=data, bot=bot) - - if "user" in data: - data["user"] = de_json_optional(data.get("user"), User, bot) - - return super().de_json(data=data, bot=bot) - class ChatBoostSourcePremium(ChatBoostSource): """ @@ -285,18 +271,6 @@ def __init__( self._id_attrs = (self.boost_id, self.add_date, self.expiration_date, self.source) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatBoost": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["source"] = de_json_optional(data.get("source"), ChatBoostSource, bot) - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["add_date"] = from_timestamp(data.get("add_date"), tzinfo=loc_tzinfo) - data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) - class ChatBoostUpdated(TelegramObject): """This object represents a boost added to a chat or changed. @@ -332,16 +306,6 @@ def __init__( self._id_attrs = (self.chat.id, self.boost) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatBoostUpdated": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["boost"] = de_json_optional(data.get("boost"), ChatBoost, bot) - - return super().de_json(data=data, bot=bot) - class ChatBoostRemoved(TelegramObject): """ @@ -386,18 +350,6 @@ def __init__( self._id_attrs = (self.chat, self.boost_id, self.remove_date, self.source) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatBoostRemoved": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["source"] = de_json_optional(data.get("source"), ChatBoostSource, bot) - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["remove_date"] = from_timestamp(data.get("remove_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) - class UserChatBoosts(TelegramObject): """This object represents a list of boosts added to a chat by a user. @@ -429,12 +381,3 @@ def __init__( self._id_attrs = (self.boosts,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UserChatBoosts": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["boosts"] = de_list_optional(data.get("boosts"), ChatBoost, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_chatfullinfo.py b/src/telegram/_chatfullinfo.py index 7142b5feb6a..7404a4c513a 100644 --- a/src/telegram/_chatfullinfo.py +++ b/src/telegram/_chatfullinfo.py @@ -34,20 +34,16 @@ from telegram._uniquegift import UniqueGiftColors from telegram._userrating import UserRating from telegram._utils.argumentparsing import ( - de_json_optional, - de_list_optional, parse_sequence_arg, to_timedelta, ) from telegram._utils.datetime import ( - extract_tzinfo_from_defaults, - from_timestamp, get_timedelta_value, ) from telegram._utils.types import JSONDict, TimePeriod if TYPE_CHECKING: - from telegram import Bot, BusinessIntro, BusinessLocation, BusinessOpeningHours, Message + from telegram import BusinessIntro, BusinessLocation, BusinessOpeningHours, Message class ChatFullInfo(_ChatBase): @@ -623,52 +619,3 @@ def message_auto_delete_time(self) -> int | dtm.timedelta | None: return get_timedelta_value( self._message_auto_delete_time, attribute="message_auto_delete_time" ) - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatFullInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["emoji_status_expiration_date"] = from_timestamp( - data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo - ) - - data["photo"] = de_json_optional(data.get("photo"), ChatPhoto, bot) - data["accepted_gift_types"] = de_json_optional( - data.get("accepted_gift_types"), AcceptedGiftTypes, bot - ) - - from telegram import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415 - BusinessIntro, - BusinessLocation, - BusinessOpeningHours, - Message, - ) - - data["pinned_message"] = de_json_optional(data.get("pinned_message"), Message, bot) - data["permissions"] = de_json_optional(data.get("permissions"), ChatPermissions, bot) - data["location"] = de_json_optional(data.get("location"), ChatLocation, bot) - data["available_reactions"] = de_list_optional( - data.get("available_reactions"), ReactionType, bot - ) - data["birthdate"] = de_json_optional(data.get("birthdate"), Birthdate, bot) - data["personal_chat"] = de_json_optional(data.get("personal_chat"), Chat, bot) - data["business_intro"] = de_json_optional(data.get("business_intro"), BusinessIntro, bot) - data["business_location"] = de_json_optional( - data.get("business_location"), BusinessLocation, bot - ) - data["business_opening_hours"] = de_json_optional( - data.get("business_opening_hours"), BusinessOpeningHours, bot - ) - data["parent_chat"] = de_json_optional(data.get("parent_chat"), Chat, bot) - - data["rating"] = de_json_optional(data.get("rating"), UserRating, bot) - data["unique_gift_colors"] = de_json_optional( - data.get("unique_gift_colors"), UniqueGiftColors, bot - ) - data["first_profile_audio"] = de_json_optional(data.get("first_profile_audio"), Audio, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_chatinvitelink.py b/src/telegram/_chatinvitelink.py index 880349fd0fb..f107f585904 100644 --- a/src/telegram/_chatinvitelink.py +++ b/src/telegram/_chatinvitelink.py @@ -19,21 +19,13 @@ """This module contains an object that represents an invite link for a chat.""" import datetime as dtm -from typing import TYPE_CHECKING from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional, to_timedelta -from telegram._utils.datetime import ( - extract_tzinfo_from_defaults, - from_timestamp, - get_timedelta_value, -) +from telegram._utils.argumentparsing import to_timedelta +from telegram._utils.datetime import get_timedelta_value from telegram._utils.types import JSONDict, TimePeriod -if TYPE_CHECKING: - from telegram import Bot - class ChatInviteLink(TelegramObject): """This object represents an invite link for a chat. @@ -191,16 +183,3 @@ def __init__( @property def subscription_period(self) -> int | dtm.timedelta | None: return get_timedelta_value(self._subscription_period, attribute="subscription_period") - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatInviteLink": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["creator"] = de_json_optional(data.get("creator"), User, bot) - data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_chatjoinrequest.py b/src/telegram/_chatjoinrequest.py index 75c2087d794..16c72609205 100644 --- a/src/telegram/_chatjoinrequest.py +++ b/src/telegram/_chatjoinrequest.py @@ -19,20 +19,14 @@ """This module contains an object that represents a Telegram ChatJoinRequest.""" import datetime as dtm -from typing import TYPE_CHECKING from telegram._chat import Chat from telegram._chatinvitelink import ChatInviteLink from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput -if TYPE_CHECKING: - from telegram import Bot - class ChatJoinRequest(TelegramObject): """This object represents a join request sent to a chat. @@ -130,21 +124,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatJoinRequest": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo) - data["invite_link"] = de_json_optional(data.get("invite_link"), ChatInviteLink, bot) - - return super().de_json(data=data, bot=bot) - async def approve( self, *, diff --git a/src/telegram/_chatlocation.py b/src/telegram/_chatlocation.py index b93cfc8a624..75afd1db57b 100644 --- a/src/telegram/_chatlocation.py +++ b/src/telegram/_chatlocation.py @@ -18,17 +18,13 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a location to which a chat is connected.""" -from typing import TYPE_CHECKING, Final +from typing import Final from telegram import constants from telegram._files.location import Location from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ChatLocation(TelegramObject): """This object represents a location to which a chat is connected. @@ -68,15 +64,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatLocation": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["location"] = de_json_optional(data.get("location"), Location, bot) - - return super().de_json(data=data, bot=bot) - MIN_ADDRESS: Final[int] = constants.LocationLimit.MIN_CHAT_LOCATION_ADDRESS """:const:`telegram.constants.LocationLimit.MIN_CHAT_LOCATION_ADDRESS` diff --git a/src/telegram/_chatmember.py b/src/telegram/_chatmember.py index be55c8469e8..ae67028bc2c 100644 --- a/src/telegram/_chatmember.py +++ b/src/telegram/_chatmember.py @@ -19,19 +19,14 @@ """This module contains an object that represents a Telegram ChatMember.""" import datetime as dtm -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ChatMember(TelegramObject): """Base class for Telegram ChatMember Objects. @@ -77,6 +72,18 @@ class ChatMember(TelegramObject): __slots__ = ("status", "user") + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "status", + { + "creator": "ChatMemberOwner", + "administrator": "ChatMemberAdministrator", + "member": "ChatMemberMember", + "restricted": "ChatMemberRestricted", + "left": "ChatMemberLeft", + "kicked": "ChatMemberBanned", + }, + ) + ADMINISTRATOR: Final[str] = constants.ChatMemberStatus.ADMINISTRATOR """:const:`telegram.constants.ChatMemberStatus.ADMINISTRATOR`""" OWNER: Final[str] = constants.ChatMemberStatus.OWNER @@ -106,38 +113,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatMember": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - _class_mapping: dict[str, type[ChatMember]] = { - cls.OWNER: ChatMemberOwner, - cls.ADMINISTRATOR: ChatMemberAdministrator, - cls.MEMBER: ChatMemberMember, - cls.RESTRICTED: ChatMemberRestricted, - cls.LEFT: ChatMemberLeft, - cls.BANNED: ChatMemberBanned, - } - - if cls is ChatMember and data.get("status") in _class_mapping: - return _class_mapping[data.pop("status")].de_json(data=data, bot=bot) - - data["user"] = de_json_optional(data.get("user"), User, bot) - if "until_date" in data: - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["until_date"] = from_timestamp(data.get("until_date"), tzinfo=loc_tzinfo) - - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if cls is ChatMemberRestricted and data.get("can_send_media_messages") is not None: - api_kwargs = {"can_send_media_messages": data.pop("can_send_media_messages")} - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) - - return super().de_json(data=data, bot=bot) - class ChatMemberOwner(ChatMember): """ @@ -600,6 +575,8 @@ class ChatMemberRestricted(ChatMember): "until_date", ) + __REMOVED_API_FIELDS__: ClassVar[frozenset[str]] = frozenset({"can_send_media_messages"}) + def __init__( self, user: User, diff --git a/src/telegram/_chatmemberupdated.py b/src/telegram/_chatmemberupdated.py index 2434376d459..640292b9b8d 100644 --- a/src/telegram/_chatmemberupdated.py +++ b/src/telegram/_chatmemberupdated.py @@ -19,20 +19,14 @@ """This module contains an object that represents a Telegram ChatMemberUpdated.""" import datetime as dtm -from typing import TYPE_CHECKING from telegram._chat import Chat from telegram._chatinvitelink import ChatInviteLink from telegram._chatmember import ChatMember from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ChatMemberUpdated(TelegramObject): """This object represents changes in the status of a chat member. @@ -142,23 +136,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatMemberUpdated": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo) - data["old_chat_member"] = de_json_optional(data.get("old_chat_member"), ChatMember, bot) - data["new_chat_member"] = de_json_optional(data.get("new_chat_member"), ChatMember, bot) - data["invite_link"] = de_json_optional(data.get("invite_link"), ChatInviteLink, bot) - - return super().de_json(data=data, bot=bot) - def _get_attribute_difference(self, attribute: str) -> tuple[object, object]: try: old = self.old_chat_member[attribute] diff --git a/src/telegram/_chatowner.py b/src/telegram/_chatowner.py index 1d8ce6619bd..fd8c4608bd6 100644 --- a/src/telegram/_chatowner.py +++ b/src/telegram/_chatowner.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a chat owner change in the chat.""" -from typing import TYPE_CHECKING - from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ChatOwnerChanged(TelegramObject): """This object represents a service message about an ownership change in the chat. @@ -60,15 +54,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatOwnerChanged": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["new_owner"] = de_json_optional(data.get("new_owner"), User, bot) - - return super().de_json(data=data, bot=bot) - class ChatOwnerLeft(TelegramObject): """This object represents a service message about the chat owner leaving the chat. @@ -102,12 +87,3 @@ def __init__( self._id_attrs = (self.new_owner,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatOwnerLeft": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["new_owner"] = de_json_optional(data.get("new_owner"), User, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_chatpermissions.py b/src/telegram/_chatpermissions.py index 2116f4f4c33..9aa0129a13c 100644 --- a/src/telegram/_chatpermissions.py +++ b/src/telegram/_chatpermissions.py @@ -18,14 +18,9 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram ChatPermission.""" -from typing import TYPE_CHECKING - from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ChatPermissions(TelegramObject): """Describes actions that a non-administrator user is allowed to take in a chat. @@ -166,6 +161,12 @@ class ChatPermissions(TelegramObject): "can_send_voice_notes", ) + __REMOVED_API_FIELDS__ = frozenset( + { + "can_send_media_messages", + } + ) + def __init__( self, can_send_messages: bool | None = None, @@ -245,16 +246,3 @@ def no_permissions(cls) -> "ChatPermissions": .. versionadded:: 20.0 """ return cls(*(False,) * len(cls.__slots__)) - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatPermissions": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if data.get("can_send_media_messages") is not None: - api_kwargs["can_send_media_messages"] = data.pop("can_send_media_messages") - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) diff --git a/src/telegram/_checklists.py b/src/telegram/_checklists.py index 16b7676eb5b..a4b94022022 100644 --- a/src/telegram/_checklists.py +++ b/src/telegram/_checklists.py @@ -20,20 +20,18 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from telegram._chat import Chat from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.types import JSONDict -from telegram.constants import ZERO_DATE if TYPE_CHECKING: - from telegram import Bot, Message + from telegram import Message class ChecklistTask(TelegramObject): @@ -112,25 +110,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChecklistTask": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - if (date := data.get("completion_date")) == 0: - data["completion_date"] = ZERO_DATE - else: - data["completion_date"] = from_timestamp(date, tzinfo=loc_tzinfo) - - data["completed_by_user"] = de_json_optional(data.get("completed_by_user"), User, bot) - data["completed_by_chat"] = de_json_optional(data.get("completed_by_chat"), Chat, bot) - data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot) - - return super().de_json(data=data, bot=bot) - def parse_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`text` from a given :class:`telegram.MessageEntity` of :attr:`text_entities`. @@ -231,16 +210,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Checklist": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["title_entities"] = de_list_optional(data.get("title_entities"), MessageEntity, bot) - data["tasks"] = de_list_optional(data.get("tasks"), ChecklistTask, bot) - - return super().de_json(data=data, bot=bot) - def parse_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`title` from a given :class:`telegram.MessageEntity` of :attr:`title_entities`. @@ -321,7 +290,7 @@ class ChecklistTasksDone(TelegramObject): def __init__( self, - checklist_message: Optional["Message"] = None, + checklist_message: "Message | None" = None, marked_as_done_task_ids: Sequence[int] | None = None, marked_as_not_done_task_ids: Sequence[int] | None = None, *, @@ -338,18 +307,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChecklistTasksDone": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # needs to be imported here to avoid circular import issues - from telegram import Message # pylint: disable=import-outside-toplevel # noqa: PLC0415 - - data["checklist_message"] = de_json_optional(data.get("checklist_message"), Message, bot) - - return super().de_json(data=data, bot=bot) - class ChecklistTasksAdded(TelegramObject): """ @@ -380,7 +337,7 @@ class ChecklistTasksAdded(TelegramObject): def __init__( self, tasks: Sequence[ChecklistTask], - checklist_message: Optional["Message"] = None, + checklist_message: "Message | None" = None, *, api_kwargs: JSONDict | None = None, ): @@ -391,16 +348,3 @@ def __init__( self._id_attrs = (self.tasks,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChecklistTasksAdded": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # needs to be imported here to avoid circular import issues - from telegram import Message # pylint: disable=import-outside-toplevel # noqa: PLC0415 - - data["checklist_message"] = de_json_optional(data.get("checklist_message"), Message, bot) - data["tasks"] = ChecklistTask.de_list(data.get("tasks", []), bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_choseninlineresult.py b/src/telegram/_choseninlineresult.py index 41022e066da..8f8b733cf81 100644 --- a/src/telegram/_choseninlineresult.py +++ b/src/telegram/_choseninlineresult.py @@ -21,14 +21,12 @@ from typing import TYPE_CHECKING -from telegram._files.location import Location from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot + from telegram._files.location import Location class ChosenInlineResult(TelegramObject): @@ -91,15 +89,3 @@ def __init__( self._id_attrs = (self.result_id,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChosenInlineResult": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Required - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - # Optionals - data["location"] = de_json_optional(data.get("location"), Location, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_directmessagestopic.py b/src/telegram/_directmessagestopic.py index 384c4db71c8..4aeee4f2dda 100644 --- a/src/telegram/_directmessagestopic.py +++ b/src/telegram/_directmessagestopic.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the DirectMessagesTopic class.""" -from typing import TYPE_CHECKING, Optional - from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram._bot import Bot - class DirectMessagesTopic(TelegramObject): """ @@ -75,12 +69,3 @@ def __init__( self._id_attrs = (self.topic_id, self.user) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "DirectMessagesTopic": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["user"] = de_json_optional(data.get("user"), User, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_files/_basemedium.py b/src/telegram/_files/_basemedium.py index a6feb4325f3..5d9091d7248 100644 --- a/src/telegram/_files/_basemedium.py +++ b/src/telegram/_files/_basemedium.py @@ -48,7 +48,6 @@ class _BaseMedium(TelegramObject): Can't be used to download or reuse the file. file_size (:obj:`int`): Optional. File size. - """ __slots__ = ("file_id", "file_size", "file_unique_id") diff --git a/src/telegram/_files/_basethumbedmedium.py b/src/telegram/_files/_basethumbedmedium.py index bf03f37b3c0..96d17a8079c 100644 --- a/src/telegram/_files/_basethumbedmedium.py +++ b/src/telegram/_files/_basethumbedmedium.py @@ -18,16 +18,12 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """Common base class for media objects with thumbnails""" -from typing import TYPE_CHECKING, TypeVar +from typing import TypeVar from telegram._files._basemedium import _BaseMedium from telegram._files.photosize import PhotoSize -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - # pylint: disable=invalid-name ThumbedMT_co = TypeVar("ThumbedMT_co", bound="_BaseThumbedMedium", covariant=True) @@ -63,6 +59,11 @@ class _BaseThumbedMedium(_BaseMedium): """ __slots__ = ("thumbnail",) + __REMOVED_API_FIELDS__ = frozenset( + { + "thumb", + } + ) def __init__( self, @@ -81,20 +82,3 @@ def __init__( ) self.thumbnail: PhotoSize | None = thumbnail - - @classmethod - def de_json(cls: type[ThumbedMT_co], data: JSONDict, bot: "Bot | None" = None) -> ThumbedMT_co: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # In case this wasn't already done by the subclass - if not isinstance(data.get("thumbnail"), PhotoSize): - data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if data.get("thumb") is not None: - api_kwargs["thumb"] = data.pop("thumb") - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) diff --git a/src/telegram/_files/sticker.py b/src/telegram/_files/sticker.py index e7854b53ab9..9a32eb34f1b 100644 --- a/src/telegram/_files/sticker.py +++ b/src/telegram/_files/sticker.py @@ -23,15 +23,14 @@ from telegram import constants from telegram._files._basethumbedmedium import _BaseThumbedMedium -from telegram._files.file import File from telegram._files.photosize import PhotoSize from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot + from telegram._files.file import File class Sticker(_BaseThumbedMedium): @@ -144,6 +143,11 @@ class Sticker(_BaseThumbedMedium): "type", "width", ) + __REMOVED_API_FIELDS__ = frozenset( + { + "thumb", + } + ) def __init__( self, @@ -194,23 +198,6 @@ def __init__( CUSTOM_EMOJI: Final[str] = constants.StickerType.CUSTOM_EMOJI """:const:`telegram.constants.StickerType.CUSTOM_EMOJI`""" - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Sticker": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot) - data["mask_position"] = de_json_optional(data.get("mask_position"), MaskPosition, bot) - data["premium_animation"] = de_json_optional(data.get("premium_animation"), File, bot) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if data.get("thumb") is not None: - api_kwargs["thumb"] = data.pop("thumb") - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) - class StickerSet(TelegramObject): """This object represents a sticker set. @@ -281,6 +268,7 @@ class StickerSet(TelegramObject): "thumbnail", "title", ) + __REMOVED_API_FIELDS__ = frozenset({"contains_masks", "is_animated", "is_video", "thumb"}) def __init__( self, @@ -303,23 +291,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "StickerSet": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot) - data["stickers"] = de_list_optional(data.get("stickers"), Sticker, bot) - - api_kwargs = {} - # These are deprecated fields that TG still returns for backwards compatibility - # Let's filter them out to speed up the de-json process - for deprecated_field in ("contains_masks", "thumb", "is_animated", "is_video"): - if deprecated_field in data: - api_kwargs[deprecated_field] = data.pop(deprecated_field) - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) - class MaskPosition(TelegramObject): """This object describes the position on faces where a mask should be placed by default. diff --git a/src/telegram/_files/venue.py b/src/telegram/_files/venue.py index 3594e91e10a..5b2ab2ff557 100644 --- a/src/telegram/_files/venue.py +++ b/src/telegram/_files/venue.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Venue.""" -from typing import TYPE_CHECKING - from telegram._files.location import Location from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class Venue(TelegramObject): """This object represents a venue. @@ -102,12 +96,3 @@ def __init__( self._id_attrs = (self.location, self.title) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Venue": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["location"] = de_json_optional(data.get("location"), Location, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_files/video.py b/src/telegram/_files/video.py index d197e053cd2..3b0b99f6e73 100644 --- a/src/telegram/_files/video.py +++ b/src/telegram/_files/video.py @@ -20,18 +20,14 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._files._basethumbedmedium import _BaseThumbedMedium from telegram._files.photosize import PhotoSize from telegram._files.videoquality import VideoQuality -from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg, to_timedelta +from telegram._utils.argumentparsing import parse_sequence_arg, to_timedelta from telegram._utils.datetime import get_timedelta_value from telegram._utils.types import JSONDict, TimePeriod -if TYPE_CHECKING: - from telegram import Bot - class Video(_BaseThumbedMedium): """This object represents a video file. @@ -167,13 +163,3 @@ def duration(self) -> int | dtm.timedelta: @property def start_timestamp(self) -> dtm.timedelta | None | int: return get_timedelta_value(self._start_timestamp, attribute="start_timestamp") - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Video": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["cover"] = de_list_optional(data.get("cover"), PhotoSize, bot) - data["qualities"] = de_list_optional(data.get("qualities"), VideoQuality, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_games/game.py b/src/telegram/_games/game.py index 898838e8b97..7135628f501 100644 --- a/src/telegram/_games/game.py +++ b/src/telegram/_games/game.py @@ -19,19 +19,15 @@ """This module contains an object that represents a Telegram Game.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._files.animation import Animation from telegram._files.photosize import PhotoSize from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.strings import TextEncoding from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class Game(TelegramObject): """ @@ -124,17 +120,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Game": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) - data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot) - data["animation"] = de_json_optional(data.get("animation"), Animation, bot) - - return super().de_json(data=data, bot=bot) - def parse_text_entity(self, entity: MessageEntity) -> str: """Returns the text from a given :class:`telegram.MessageEntity`. diff --git a/src/telegram/_games/gamehighscore.py b/src/telegram/_games/gamehighscore.py index 482a61c9659..3b13a2ea887 100644 --- a/src/telegram/_games/gamehighscore.py +++ b/src/telegram/_games/gamehighscore.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram GameHighScore.""" -from typing import TYPE_CHECKING - from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class GameHighScore(TelegramObject): """This object represents one row of the high scores table for a game. @@ -60,12 +54,3 @@ def __init__( self._id_attrs = (self.position, self.user, self.score) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "GameHighScore": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["user"] = de_json_optional(data.get("user"), User, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_gifts.py b/src/telegram/_gifts.py index 84a9bad7100..ad843b336b6 100644 --- a/src/telegram/_gifts.py +++ b/src/telegram/_gifts.py @@ -20,19 +20,15 @@ """This module contains classes related to gifs sent by bots.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._chat import Chat from telegram._files.sticker import Sticker from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class GiftBackground(TelegramObject): """This object describes the background of a gift. @@ -228,16 +224,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Gift": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) - data["publisher_chat"] = de_json_optional(data.get("publisher_chat"), Chat, bot) - data["background"] = de_json_optional(data.get("background"), GiftBackground, bot) - return super().de_json(data=data, bot=bot) - class Gifts(TelegramObject): """This object represent a list of gifts. @@ -270,14 +256,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Gifts": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["gifts"] = de_list_optional(data.get("gifts"), Gift, bot) - return super().de_json(data=data, bot=bot) - class GiftInfo(TelegramObject): """Describes a service message about a regular gift that was sent or received. @@ -385,16 +363,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "GiftInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["gift"] = de_json_optional(data.get("gift"), Gift, bot) - data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot) - - return super().de_json(data=data, bot=bot) - def parse_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`text` from a given :class:`telegram.MessageEntity` of :attr:`entities`. diff --git a/src/telegram/_giveaway.py b/src/telegram/_giveaway.py index 3fe25c40920..d44a4eb04c3 100644 --- a/src/telegram/_giveaway.py +++ b/src/telegram/_giveaway.py @@ -25,12 +25,11 @@ from telegram._chat import Chat from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot, Message + from telegram import Message class Giveaway(TelegramObject): @@ -137,21 +136,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Giveaway": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["chats"] = de_list_optional(data.get("chats"), Chat, bot) - data["winners_selection_date"] = from_timestamp( - data.get("winners_selection_date"), tzinfo=loc_tzinfo - ) - - return super().de_json(data=data, bot=bot) - class GiveawayCreated(TelegramObject): """This object represents a service message about the creation of a scheduled giveaway. @@ -292,22 +276,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "GiveawayWinners": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["winners"] = de_list_optional(data.get("winners"), User, bot) - data["winners_selection_date"] = from_timestamp( - data.get("winners_selection_date"), tzinfo=loc_tzinfo - ) - - return super().de_json(data=data, bot=bot) - class GiveawayCompleted(TelegramObject): """This object represents a service message about the completion of a giveaway without public @@ -363,17 +331,3 @@ def __init__( ) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "GiveawayCompleted": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Unfortunately, this needs to be here due to cyclic imports - from telegram._message import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415 - Message, - ) - - data["giveaway_message"] = de_json_optional(data.get("giveaway_message"), Message, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_inline/inlinekeyboardbutton.py b/src/telegram/_inline/inlinekeyboardbutton.py index 7915ac0aa8e..9a3cf7e062b 100644 --- a/src/telegram/_inline/inlinekeyboardbutton.py +++ b/src/telegram/_inline/inlinekeyboardbutton.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram InlineKeyboardButton.""" -from typing import TYPE_CHECKING, Final +from typing import Final from telegram import constants from telegram._copytextbutton import CopyTextButton @@ -26,13 +26,9 @@ from telegram._loginurl import LoginUrl from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict from telegram._webappinfo import WebAppInfo -if TYPE_CHECKING: - from telegram import Bot - class InlineKeyboardButton(TelegramObject): """This object represents one button of an inline keyboard. @@ -344,21 +340,6 @@ def _set_id_attrs(self) -> None: self.icon_custom_emoji_id, ) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InlineKeyboardButton": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["login_url"] = de_json_optional(data.get("login_url"), LoginUrl, bot) - data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot) - data["callback_game"] = de_json_optional(data.get("callback_game"), CallbackGame, bot) - data["switch_inline_query_chosen_chat"] = de_json_optional( - data.get("switch_inline_query_chosen_chat"), SwitchInlineQueryChosenChat, bot - ) - data["copy_text"] = de_json_optional(data.get("copy_text"), CopyTextButton, bot) - - return super().de_json(data=data, bot=bot) - def update_callback_data(self, callback_data: str | object) -> None: """ Sets :attr:`callback_data` to the passed object. Intended to be used by diff --git a/src/telegram/_inline/inlinekeyboardmarkup.py b/src/telegram/_inline/inlinekeyboardmarkup.py index 37040bc9ec5..a3162eae0b4 100644 --- a/src/telegram/_inline/inlinekeyboardmarkup.py +++ b/src/telegram/_inline/inlinekeyboardmarkup.py @@ -19,16 +19,12 @@ """This module contains an object that represents a Telegram InlineKeyboardMarkup.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton from telegram._telegramobject import TelegramObject from telegram._utils.markup import check_keyboard_type from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class InlineKeyboardMarkup(TelegramObject): """ @@ -91,21 +87,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InlineKeyboardMarkup": - """See :meth:`telegram.TelegramObject.de_json`.""" - - keyboard = [] - for row in data["inline_keyboard"]: - tmp = [] - for col in row: - btn = InlineKeyboardButton.de_json(col, bot) - if btn: - tmp.append(btn) - keyboard.append(tmp) - - return cls(keyboard) - @classmethod def from_button(cls, button: InlineKeyboardButton, **kwargs: object) -> "InlineKeyboardMarkup": """Shortcut for:: diff --git a/src/telegram/_inline/inlinequery.py b/src/telegram/_inline/inlinequery.py index 48ff6162f17..b825059d667 100644 --- a/src/telegram/_inline/inlinequery.py +++ b/src/telegram/_inline/inlinequery.py @@ -23,16 +23,15 @@ from typing import TYPE_CHECKING, Final from telegram import constants -from telegram._files.location import Location from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput, TimePeriod if TYPE_CHECKING: - from telegram import Bot, InlineQueryResult + from telegram import InlineQueryResult + from telegram._files.location import Location class InlineQuery(TelegramObject): @@ -132,16 +131,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InlineQuery": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["location"] = de_json_optional(data.get("location"), Location, bot) - - return super().de_json(data=data, bot=bot) - async def answer( self, results: ( diff --git a/src/telegram/_inline/inlinequeryresultsbutton.py b/src/telegram/_inline/inlinequeryresultsbutton.py index 7f124fe7fc8..eaa065f8334 100644 --- a/src/telegram/_inline/inlinequeryresultsbutton.py +++ b/src/telegram/_inline/inlinequeryresultsbutton.py @@ -18,17 +18,13 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the class that represent a Telegram InlineQueryResultsButton.""" -from typing import TYPE_CHECKING, Final +from typing import Final from telegram import constants from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict from telegram._webappinfo import WebAppInfo -if TYPE_CHECKING: - from telegram import Bot - class InlineQueryResultsButton(TelegramObject): """This object represents a button to be shown above inline query results. You **must** use @@ -98,14 +94,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InlineQueryResultsButton": - """See :meth:`telegram.TelegramObject.de_json`.""" - - data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot) - - return super().de_json(data=data, bot=bot) - MIN_START_PARAMETER_LENGTH: Final[int] = ( constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH ) diff --git a/src/telegram/_inline/inputinvoicemessagecontent.py b/src/telegram/_inline/inputinvoicemessagecontent.py index 536726e3afa..6eec6479d85 100644 --- a/src/telegram/_inline/inputinvoicemessagecontent.py +++ b/src/telegram/_inline/inputinvoicemessagecontent.py @@ -19,16 +19,12 @@ """This module contains a class that represents a Telegram InputInvoiceMessageContent.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._inline.inputmessagecontent import InputMessageContent from telegram._payment.labeledprice import LabeledPrice -from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class InputInvoiceMessageContent(InputMessageContent): """ @@ -254,12 +250,3 @@ def __init__( self.currency, self.prices, ) - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InputInvoiceMessageContent": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["prices"] = de_list_optional(data.get("prices"), LabeledPrice, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_inline/preparedinlinemessage.py b/src/telegram/_inline/preparedinlinemessage.py index b390c228e3e..fb03082f1f7 100644 --- a/src/telegram/_inline/preparedinlinemessage.py +++ b/src/telegram/_inline/preparedinlinemessage.py @@ -19,15 +19,10 @@ """This module contains an object that represents a Telegram Prepared inline Message.""" import datetime as dtm -from typing import TYPE_CHECKING from telegram._telegramobject import TelegramObject -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class PreparedInlineMessage(TelegramObject): """Describes an inline message to be sent by a user of a Mini App. @@ -66,14 +61,3 @@ def __init__( self._id_attrs = (self.id,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PreparedInlineMessage": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_keyboardbutton.py b/src/telegram/_keyboardbutton.py index 750062bf516..91c3394424e 100644 --- a/src/telegram/_keyboardbutton.py +++ b/src/telegram/_keyboardbutton.py @@ -18,18 +18,12 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram KeyboardButton.""" -from typing import TYPE_CHECKING - from telegram._keyboardbuttonpolltype import KeyboardButtonPollType from telegram._keyboardbuttonrequest import KeyboardButtonRequestChat, KeyboardButtonRequestUsers from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict from telegram._webappinfo import WebAppInfo -if TYPE_CHECKING: - from telegram import Bot - class KeyboardButton(TelegramObject): """ @@ -181,6 +175,12 @@ class KeyboardButton(TelegramObject): "web_app", ) + __REMOVED_API_FIELDS__ = frozenset( + { + "request_user", + } + ) + def __init__( self, text: str, @@ -222,27 +222,3 @@ def __init__( ) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "KeyboardButton": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["request_poll"] = de_json_optional( - data.get("request_poll"), KeyboardButtonPollType, bot - ) - data["request_users"] = de_json_optional( - data.get("request_users"), KeyboardButtonRequestUsers, bot - ) - data["request_chat"] = de_json_optional( - data.get("request_chat"), KeyboardButtonRequestChat, bot - ) - data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if request_user := data.get("request_user"): - api_kwargs = {"request_user": request_user} - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) diff --git a/src/telegram/_keyboardbuttonrequest.py b/src/telegram/_keyboardbuttonrequest.py index b5e0b050436..7eac5677087 100644 --- a/src/telegram/_keyboardbuttonrequest.py +++ b/src/telegram/_keyboardbuttonrequest.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains two objects to request chats/users.""" -from typing import TYPE_CHECKING - from telegram._chatadministratorrights import ChatAdministratorRights from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class KeyboardButtonRequestUsers(TelegramObject): """This object defines the criteria used to request a suitable user. The identifier of the @@ -254,17 +248,3 @@ def __init__( self._id_attrs = (self.request_id,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "KeyboardButtonRequestChat": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["user_administrator_rights"] = de_json_optional( - data.get("user_administrator_rights"), ChatAdministratorRights, bot - ) - data["bot_administrator_rights"] = de_json_optional( - data.get("bot_administrator_rights"), ChatAdministratorRights, bot - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_menubutton.py b/src/telegram/_menubutton.py index e570c5a6648..c7615587496 100644 --- a/src/telegram/_menubutton.py +++ b/src/telegram/_menubutton.py @@ -18,18 +18,14 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains objects related to Telegram menu buttons.""" -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict from telegram._webappinfo import WebAppInfo -if TYPE_CHECKING: - from telegram import Bot - class MenuButton(TelegramObject): """This object describes the bot's menu button in a private chat. It should be one of @@ -57,6 +53,15 @@ class MenuButton(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "commands": "MenuButtonCommands", + "web_app": "MenuButtonWebApp", + "default": "MenuButtonDefault", + }, + ) + def __init__( self, type: str, @@ -70,35 +75,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "MenuButton": - """Converts JSON data to the appropriate :class:`MenuButton` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`, optional): The bot associated with this object. Defaults to - :obj:`None`, in which case shortcut methods will not be available. - - .. versionchanged:: 21.4 - :paramref:`bot` is now optional and defaults to :obj:`None` - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[MenuButton]] = { - cls.COMMANDS: MenuButtonCommands, - cls.WEB_APP: MenuButtonWebApp, - cls.DEFAULT: MenuButtonDefault, - } - - if cls is MenuButton and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data, bot=bot) - return super().de_json(data=data, bot=bot) - COMMANDS: Final[str] = constants.MenuButtonType.COMMANDS """:const:`telegram.constants.MenuButtonType.COMMANDS`""" WEB_APP: Final[str] = constants.MenuButtonType.WEB_APP @@ -165,15 +141,6 @@ def __init__(self, text: str, web_app: WebAppInfo, *, api_kwargs: JSONDict | Non self._id_attrs = (self.type, self.text, self.web_app) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "MenuButtonWebApp": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class MenuButtonDefault(MenuButton): """Describes that no specific value for the menu button was set. diff --git a/src/telegram/_message.py b/src/telegram/_message.py index c0d018ec406..3b82ca8dec0 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -23,7 +23,7 @@ import re from collections.abc import Sequence from html import escape -from typing import TYPE_CHECKING, TypedDict +from typing import TYPE_CHECKING, ClassVar, TypedDict from telegram._chat import Chat from telegram._chatbackground import ChatBackground @@ -73,8 +73,8 @@ from telegram._telegramobject import TelegramObject from telegram._uniquegift import UniqueGiftInfo from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp, to_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg +from telegram._utils.datetime import to_timestamp from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.strings import TextEncoding @@ -195,31 +195,20 @@ def is_accessible(self) -> bool: return self.date != ZERO_DATE @classmethod - def _de_json( + def de_json( cls, - data: JSONDict | None, + data: JSONDict, bot: "Bot | None" = None, - api_kwargs: JSONDict | None = None, - ) -> "MaybeInaccessibleMessage | None": + ) -> "MaybeInaccessibleMessage": """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - + # Dispatch *before* the plan runs so that the raw ``date`` value is still + # an integer and can be compared to 0. if cls is MaybeInaccessibleMessage: - if data["date"] == 0: + data = cls._parse_data(data) + if data.get("date") == 0: return InaccessibleMessage.de_json(data=data, bot=bot) return Message.de_json(data=data, bot=bot) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - # this is to include the Literal from InaccessibleMessage - if data["date"] == 0: - data["date"] = ZERO_DATE - else: - data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) + return super().de_json(data=data, bot=bot) class InaccessibleMessage(MaybeInaccessibleMessage): @@ -257,6 +246,18 @@ def __init__( class Message(MaybeInaccessibleMessage): + __REMOVED_API_FIELDS__: ClassVar[frozenset[str]] = frozenset( + { + "user_shared", + "forward_from", + "forward_from_chat", + "forward_from_message_id", + "forward_signature", + "forward_sender_name", + "forward_date", + } + ) + # fmt: off """This object represents a message. @@ -1506,207 +1507,6 @@ def link(self) -> str | None: return baselink return None - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Message": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["sender_chat"] = de_json_optional(data.get("sender_chat"), Chat, bot) - data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot) - data["caption_entities"] = de_list_optional( - data.get("caption_entities"), MessageEntity, bot - ) - data["reply_to_message"] = de_json_optional(data.get("reply_to_message"), Message, bot) - data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo) - data["audio"] = de_json_optional(data.get("audio"), Audio, bot) - data["document"] = de_json_optional(data.get("document"), Document, bot) - data["animation"] = de_json_optional(data.get("animation"), Animation, bot) - data["game"] = de_json_optional(data.get("game"), Game, bot) - data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) - data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) - data["story"] = de_json_optional(data.get("story"), Story, bot) - data["video"] = de_json_optional(data.get("video"), Video, bot) - data["voice"] = de_json_optional(data.get("voice"), Voice, bot) - data["video_note"] = de_json_optional(data.get("video_note"), VideoNote, bot) - data["contact"] = de_json_optional(data.get("contact"), Contact, bot) - data["location"] = de_json_optional(data.get("location"), Location, bot) - data["venue"] = de_json_optional(data.get("venue"), Venue, bot) - data["new_chat_members"] = de_list_optional(data.get("new_chat_members"), User, bot) - data["left_chat_member"] = de_json_optional(data.get("left_chat_member"), User, bot) - data["new_chat_photo"] = de_list_optional(data.get("new_chat_photo"), PhotoSize, bot) - data["message_auto_delete_timer_changed"] = de_json_optional( - data.get("message_auto_delete_timer_changed"), MessageAutoDeleteTimerChanged, bot - ) - data["pinned_message"] = de_json_optional( - data.get("pinned_message"), MaybeInaccessibleMessage, bot - ) - data["invoice"] = de_json_optional(data.get("invoice"), Invoice, bot) - data["successful_payment"] = de_json_optional( - data.get("successful_payment"), SuccessfulPayment, bot - ) - data["passport_data"] = de_json_optional(data.get("passport_data"), PassportData, bot) - data["poll"] = de_json_optional(data.get("poll"), Poll, bot) - data["dice"] = de_json_optional(data.get("dice"), Dice, bot) - data["via_bot"] = de_json_optional(data.get("via_bot"), User, bot) - data["proximity_alert_triggered"] = de_json_optional( - data.get("proximity_alert_triggered"), ProximityAlertTriggered, bot - ) - data["reply_markup"] = de_json_optional( - data.get("reply_markup"), InlineKeyboardMarkup, bot - ) - data["video_chat_scheduled"] = de_json_optional( - data.get("video_chat_scheduled"), VideoChatScheduled, bot - ) - data["video_chat_started"] = de_json_optional( - data.get("video_chat_started"), VideoChatStarted, bot - ) - data["video_chat_ended"] = de_json_optional( - data.get("video_chat_ended"), VideoChatEnded, bot - ) - data["video_chat_participants_invited"] = de_json_optional( - data.get("video_chat_participants_invited"), VideoChatParticipantsInvited, bot - ) - data["web_app_data"] = de_json_optional(data.get("web_app_data"), WebAppData, bot) - data["forum_topic_closed"] = de_json_optional( - data.get("forum_topic_closed"), ForumTopicClosed, bot - ) - data["forum_topic_created"] = de_json_optional( - data.get("forum_topic_created"), ForumTopicCreated, bot - ) - data["forum_topic_reopened"] = de_json_optional( - data.get("forum_topic_reopened"), ForumTopicReopened, bot - ) - data["forum_topic_edited"] = de_json_optional( - data.get("forum_topic_edited"), ForumTopicEdited, bot - ) - data["general_forum_topic_hidden"] = de_json_optional( - data.get("general_forum_topic_hidden"), GeneralForumTopicHidden, bot - ) - data["general_forum_topic_unhidden"] = de_json_optional( - data.get("general_forum_topic_unhidden"), GeneralForumTopicUnhidden, bot - ) - data["write_access_allowed"] = de_json_optional( - data.get("write_access_allowed"), WriteAccessAllowed, bot - ) - data["users_shared"] = de_json_optional(data.get("users_shared"), UsersShared, bot) - data["chat_shared"] = de_json_optional(data.get("chat_shared"), ChatShared, bot) - data["chat_background_set"] = de_json_optional( - data.get("chat_background_set"), ChatBackground, bot - ) - data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot) - data["refunded_payment"] = de_json_optional( - data.get("refunded_payment"), RefundedPayment, bot - ) - data["gift"] = de_json_optional(data.get("gift"), GiftInfo, bot) - data["unique_gift"] = de_json_optional(data.get("unique_gift"), UniqueGiftInfo, bot) - data["paid_message_price_changed"] = de_json_optional( - data.get("paid_message_price_changed"), PaidMessagePriceChanged, bot - ) - - # Unfortunately, this needs to be here due to cyclic imports - from telegram._giveaway import ( # pylint: disable=C0415 # noqa: PLC0415 - Giveaway, - GiveawayCompleted, - GiveawayCreated, - GiveawayWinners, - ) - from telegram._messageorigin import ( # pylint: disable=C0415 # noqa: PLC0415 - MessageOrigin, - ) - from telegram._reply import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415 - ExternalReplyInfo, - TextQuote, - ) - from telegram._suggestedpost import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415 - SuggestedPostApprovalFailed, - SuggestedPostApproved, - SuggestedPostDeclined, - SuggestedPostInfo, - SuggestedPostPaid, - SuggestedPostRefunded, - ) - - data["giveaway"] = de_json_optional(data.get("giveaway"), Giveaway, bot) - data["giveaway_completed"] = de_json_optional( - data.get("giveaway_completed"), GiveawayCompleted, bot - ) - data["giveaway_created"] = de_json_optional( - data.get("giveaway_created"), GiveawayCreated, bot - ) - data["giveaway_winners"] = de_json_optional( - data.get("giveaway_winners"), GiveawayWinners, bot - ) - data["link_preview_options"] = de_json_optional( - data.get("link_preview_options"), LinkPreviewOptions, bot - ) - data["external_reply"] = de_json_optional( - data.get("external_reply"), ExternalReplyInfo, bot - ) - data["quote"] = de_json_optional(data.get("quote"), TextQuote, bot) - data["forward_origin"] = de_json_optional(data.get("forward_origin"), MessageOrigin, bot) - data["reply_to_story"] = de_json_optional(data.get("reply_to_story"), Story, bot) - data["boost_added"] = de_json_optional(data.get("boost_added"), ChatBoostAdded, bot) - data["sender_business_bot"] = de_json_optional(data.get("sender_business_bot"), User, bot) - data["direct_message_price_changed"] = de_json_optional( - data.get("direct_message_price_changed"), DirectMessagePriceChanged, bot - ) - data["checklist"] = de_json_optional(data.get("checklist"), Checklist, bot) - data["checklist_tasks_done"] = de_json_optional( - data.get("checklist_tasks_done"), ChecklistTasksDone, bot - ) - data["checklist_tasks_added"] = de_json_optional( - data.get("checklist_tasks_added"), ChecklistTasksAdded, bot - ) - data["direct_messages_topic"] = de_json_optional( - data.get("direct_messages_topic"), DirectMessagesTopic, bot - ) - data["suggested_post_declined"] = de_json_optional( - data.get("suggested_post_declined"), SuggestedPostDeclined, bot - ) - data["suggested_post_paid"] = de_json_optional( - data.get("suggested_post_paid"), SuggestedPostPaid, bot - ) - data["suggested_post_refunded"] = de_json_optional( - data.get("suggested_post_refunded"), SuggestedPostRefunded, bot - ) - data["suggested_post_info"] = de_json_optional( - data.get("suggested_post_info"), SuggestedPostInfo, bot - ) - data["suggested_post_approved"] = de_json_optional( - data.get("suggested_post_approved"), SuggestedPostApproved, bot - ) - data["suggested_post_approval_failed"] = de_json_optional( - data.get("suggested_post_approval_failed"), SuggestedPostApprovalFailed, bot - ) - data["gift_upgrade_sent"] = de_json_optional(data.get("gift_upgrade_sent"), GiftInfo, bot) - data["chat_owner_changed"] = de_json_optional( - data.get("chat_owner_changed"), ChatOwnerChanged, bot - ) - data["chat_owner_left"] = de_json_optional(data.get("chat_owner_left"), ChatOwnerLeft, bot) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - for key in ( - "user_shared", - "forward_from", - "forward_from_chat", - "forward_from_message_id", - "forward_signature", - "forward_sender_name", - "forward_date", - ): - if entry := data.get(key): - api_kwargs = {key: entry} - - return super()._de_json( # type: ignore[return-value] - data=data, bot=bot, api_kwargs=api_kwargs - ) - @property def effective_attachment( self, diff --git a/src/telegram/_messageentity.py b/src/telegram/_messageentity.py index 7eebf30c848..62cf83e28f3 100644 --- a/src/telegram/_messageentity.py +++ b/src/telegram/_messageentity.py @@ -22,20 +22,15 @@ import datetime as dtm import itertools from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import Final from telegram import constants from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.strings import TextEncoding from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - _SEM = Sequence["MessageEntity"] @@ -183,19 +178,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "MessageEntity": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["user"] = de_json_optional(data.get("user"), User, bot) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["unix_time"] = from_timestamp(data.get("unix_time"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) - @staticmethod def adjust_message_entities_to_utf_16(text: str, entities: _SEM) -> _SEM: """Utility functionality for converting the offset and length of entities from diff --git a/src/telegram/_messageorigin.py b/src/telegram/_messageorigin.py index 767f5a4a0af..63e22487721 100644 --- a/src/telegram/_messageorigin.py +++ b/src/telegram/_messageorigin.py @@ -19,20 +19,15 @@ """This module contains the classes that represent Telegram MessageOigin.""" import datetime as dtm -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._chat import Chat from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class MessageOrigin(TelegramObject): """ @@ -68,6 +63,16 @@ class MessageOrigin(TelegramObject): "type", ) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "user": "MessageOriginUser", + "hidden_user": "MessageOriginHiddenUser", + "chat": "MessageOriginChat", + "channel": "MessageOriginChannel", + }, + ) + USER: Final[str] = constants.MessageOriginType.USER """:const:`telegram.constants.MessageOriginType.USER`""" HIDDEN_USER: Final[str] = constants.MessageOriginType.HIDDEN_USER @@ -95,36 +100,6 @@ def __init__( ) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "MessageOrigin": - """Converts JSON data to the appropriate :class:`MessageOrigin` object, i.e. takes - care of selecting the correct subclass. - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[MessageOrigin]] = { - cls.USER: MessageOriginUser, - cls.HIDDEN_USER: MessageOriginHiddenUser, - cls.CHAT: MessageOriginChat, - cls.CHANNEL: MessageOriginChannel, - } - if cls is MessageOrigin and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo) - - if "sender_user" in data: - data["sender_user"] = de_json_optional(data.get("sender_user"), User, bot) - - if "sender_chat" in data: - data["sender_chat"] = de_json_optional(data.get("sender_chat"), Chat, bot) - - if "chat" in data: - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - - return super().de_json(data=data, bot=bot) - class MessageOriginUser(MessageOrigin): """ diff --git a/src/telegram/_messagereactionupdated.py b/src/telegram/_messagereactionupdated.py index d5e5471954f..b2e0f16421a 100644 --- a/src/telegram/_messagereactionupdated.py +++ b/src/telegram/_messagereactionupdated.py @@ -20,19 +20,14 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._chat import Chat from telegram._reaction import ReactionCount, ReactionType from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class MessageReactionCountUpdated(TelegramObject): """This class represents reaction changes on a message with anonymous reactions. @@ -86,20 +81,6 @@ def __init__( self._id_attrs = (self.chat, self.message_id, self.date, self.reactions) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "MessageReactionCountUpdated": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo) - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["reactions"] = de_list_optional(data.get("reactions"), ReactionCount, bot) - - return super().de_json(data=data, bot=bot) - class MessageReactionUpdated(TelegramObject): """This class represents a change of a reaction on a message performed by a user. @@ -181,20 +162,3 @@ def __init__( self.new_reaction, ) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "MessageReactionUpdated": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo) - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["old_reaction"] = de_list_optional(data.get("old_reaction"), ReactionType, bot) - data["new_reaction"] = de_list_optional(data.get("new_reaction"), ReactionType, bot) - data["user"] = de_json_optional(data.get("user"), User, bot) - data["actor_chat"] = de_json_optional(data.get("actor_chat"), Chat, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_ownedgift.py b/src/telegram/_ownedgift.py index 37ae8f693a6..feee77383dd 100644 --- a/src/telegram/_ownedgift.py +++ b/src/telegram/_ownedgift.py @@ -20,7 +20,7 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._gifts import Gift @@ -29,14 +29,10 @@ from telegram._uniquegift import UniqueGift from telegram._user import User from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class OwnedGift(TelegramObject): """This object describes a gift received and owned by a user or a chat. Currently, it @@ -64,6 +60,14 @@ class OwnedGift(TelegramObject): UNIQUE: Final[str] = constants.OwnedGiftType.UNIQUE """:const:`telegram.constants.OwnedGiftType.UNIQUE`""" + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "regular": "OwnedGiftRegular", + "unique": "OwnedGiftUnique", + }, + ) + def __init__( self, type: str, # pylint: disable=redefined-builtin @@ -76,31 +80,6 @@ def __init__( self._id_attrs = (self.type,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "OwnedGift": - """Converts JSON data to the appropriate :class:`OwnedGift` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`, optional): The bot associated with this object. - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[OwnedGift]] = { - cls.REGULAR: OwnedGiftRegular, - cls.UNIQUE: OwnedGiftUnique, - } - - if cls is OwnedGift and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - return super().de_json(data=data, bot=bot) - class OwnedGifts(TelegramObject): """Contains the list of gifts received and owned by a user or a chat. @@ -146,14 +125,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "OwnedGifts": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["gifts"] = de_list_optional(data.get("gifts"), OwnedGift, bot) - return super().de_json(data=data, bot=bot) - class OwnedGiftRegular(OwnedGift): """Describes a regular gift owned by a user or a chat. @@ -286,19 +257,6 @@ def __init__( self._id_attrs = (self.type, self.gift, self.send_date) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "OwnedGiftRegular": - """See :meth:`telegram.OwnedGift.de_json`.""" - data = cls._parse_data(data) - - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["send_date"] = from_timestamp(data.get("send_date"), tzinfo=loc_tzinfo) - data["sender_user"] = de_json_optional(data.get("sender_user"), User, bot) - data["gift"] = de_json_optional(data.get("gift"), Gift, bot) - data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - def parse_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`text` from a given :class:`telegram.MessageEntity` of :attr:`entities`. @@ -438,18 +396,3 @@ def __init__( self.next_transfer_date: dtm.datetime | None = next_transfer_date self._id_attrs = (self.type, self.gift, self.send_date) - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "OwnedGiftUnique": - """See :meth:`telegram.OwnedGift.de_json`.""" - data = cls._parse_data(data) - - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["send_date"] = from_timestamp(data.get("send_date"), tzinfo=loc_tzinfo) - data["sender_user"] = de_json_optional(data.get("sender_user"), User, bot) - data["gift"] = de_json_optional(data.get("gift"), UniqueGift, bot) - data["next_transfer_date"] = from_timestamp( - data.get("next_transfer_date"), tzinfo=loc_tzinfo - ) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] diff --git a/src/telegram/_paidmedia.py b/src/telegram/_paidmedia.py index 67af46710a5..6a03b8a56c1 100644 --- a/src/telegram/_paidmedia.py +++ b/src/telegram/_paidmedia.py @@ -20,17 +20,13 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import TYPE_CHECKING, ClassVar, Final from telegram import constants -from telegram._files.photosize import PhotoSize from telegram._files.video import Video from telegram._telegramobject import TelegramObject -from telegram._user import User from telegram._utils import enum from telegram._utils.argumentparsing import ( - de_json_optional, - de_list_optional, parse_sequence_arg, to_timedelta, ) @@ -38,7 +34,8 @@ from telegram._utils.types import JSONDict, TimePeriod if TYPE_CHECKING: - from telegram import Bot + from telegram._files.photosize import PhotoSize + from telegram._user import User class PaidMedia(TelegramObject): @@ -62,6 +59,15 @@ class PaidMedia(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "preview": "PaidMediaPreview", + "photo": "PaidMediaPhoto", + "video": "PaidMediaVideo", + }, + ) + PREVIEW: Final[str] = constants.PaidMediaType.PREVIEW """:const:`telegram.constants.PaidMediaType.PREVIEW`""" PHOTO: Final[str] = constants.PaidMediaType.PHOTO @@ -81,35 +87,6 @@ def __init__( self._id_attrs = (self.type,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMedia": - """Converts JSON data to the appropriate :class:`PaidMedia` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`, optional): The bot associated with this object. - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[PaidMedia]] = { - cls.PREVIEW: PaidMediaPreview, - cls.PHOTO: PaidMediaPhoto, - cls.VIDEO: PaidMediaVideo, - } - - if cls is PaidMedia and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - if "duration" in data: - data["duration"] = dtm.timedelta(seconds=s) if (s := data.get("duration")) else None - - return super().de_json(data=data, bot=bot) - class PaidMediaPreview(PaidMedia): """The paid media isn't available before the payment. @@ -202,13 +179,6 @@ def __init__( self._id_attrs = (self.type, self.photo) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMediaPhoto": - data = cls._parse_data(data) - - data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class PaidMediaVideo(PaidMedia): """ @@ -243,13 +213,6 @@ def __init__( self._id_attrs = (self.type, self.video) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMediaVideo": - data = cls._parse_data(data) - - data["video"] = de_json_optional(data.get("video"), Video, bot) - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class PaidMediaInfo(TelegramObject): """ @@ -287,13 +250,6 @@ def __init__( self._id_attrs = (self.star_count, self.paid_media) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMediaInfo": - data = cls._parse_data(data) - - data["paid_media"] = de_list_optional(data.get("paid_media"), PaidMedia, bot) - return super().de_json(data=data, bot=bot) - class PaidMediaPurchased(TelegramObject): """This object contains information about a paid media purchase. @@ -330,10 +286,3 @@ def __init__( self._id_attrs = (self.from_user, self.paid_media_payload) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMediaPurchased": - data = cls._parse_data(data) - - data["from_user"] = User.de_json(data=data.pop("from"), bot=bot) - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_passport/credentials.py b/src/telegram/_passport/credentials.py index d5a16443980..6252ffba0a0 100644 --- a/src/telegram/_passport/credentials.py +++ b/src/telegram/_passport/credentials.py @@ -20,7 +20,7 @@ import json from base64 import b64decode from collections.abc import Sequence -from typing import TYPE_CHECKING, no_type_check +from typing import no_type_check try: from cryptography.hazmat.backends import default_backend @@ -39,14 +39,11 @@ CRYPTO_INSTALLED = False from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.strings import TextEncoding from telegram._utils.types import JSONDict from telegram.error import PassportDecryptionError -if TYPE_CHECKING: - from telegram import Bot - @no_type_check def decrypt(secret, hash, data): @@ -233,15 +230,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Credentials": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["secure_data"] = de_json_optional(data.get("secure_data"), SecureData, bot) - - return super().de_json(data=data, bot=bot) - class SecureData(TelegramObject): """ @@ -340,31 +328,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "SecureData": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["temporary_registration"] = de_json_optional( - data.get("temporary_registration"), SecureValue, bot - ) - data["passport_registration"] = de_json_optional( - data.get("passport_registration"), SecureValue, bot - ) - data["rental_agreement"] = de_json_optional(data.get("rental_agreement"), SecureValue, bot) - data["bank_statement"] = de_json_optional(data.get("bank_statement"), SecureValue, bot) - data["utility_bill"] = de_json_optional(data.get("utility_bill"), SecureValue, bot) - data["address"] = de_json_optional(data.get("address"), SecureValue, bot) - data["identity_card"] = de_json_optional(data.get("identity_card"), SecureValue, bot) - data["driver_license"] = de_json_optional(data.get("driver_license"), SecureValue, bot) - data["internal_passport"] = de_json_optional( - data.get("internal_passport"), SecureValue, bot - ) - data["passport"] = de_json_optional(data.get("passport"), SecureValue, bot) - data["personal_details"] = de_json_optional(data.get("personal_details"), SecureValue, bot) - - return super().de_json(data=data, bot=bot) - class SecureValue(TelegramObject): """ @@ -445,20 +408,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "SecureValue": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["data"] = de_json_optional(data.get("data"), DataCredentials, bot) - data["front_side"] = de_json_optional(data.get("front_side"), FileCredentials, bot) - data["reverse_side"] = de_json_optional(data.get("reverse_side"), FileCredentials, bot) - data["selfie"] = de_json_optional(data.get("selfie"), FileCredentials, bot) - data["files"] = de_list_optional(data.get("files"), FileCredentials, bot) - data["translation"] = de_list_optional(data.get("translation"), FileCredentials, bot) - - return super().de_json(data=data, bot=bot) - class _CredentialsBase(TelegramObject): """Base class for DataCredentials and FileCredentials.""" diff --git a/src/telegram/_passport/encryptedpassportelement.py b/src/telegram/_passport/encryptedpassportelement.py index 8b4bdfbd77e..c8669cb036a 100644 --- a/src/telegram/_passport/encryptedpassportelement.py +++ b/src/telegram/_passport/encryptedpassportelement.py @@ -27,9 +27,7 @@ from telegram._telegramobject import TelegramObject from telegram._utils.argumentparsing import ( de_json_decrypted_optional, - de_json_optional, de_list_decrypted_optional, - de_list_optional, parse_sequence_arg, ) from telegram._utils.types import JSONDict @@ -195,19 +193,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "EncryptedPassportElement": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["files"] = de_list_optional(data.get("files"), PassportFile, bot) or None - data["front_side"] = de_json_optional(data.get("front_side"), PassportFile, bot) - data["reverse_side"] = de_json_optional(data.get("reverse_side"), PassportFile, bot) - data["selfie"] = de_json_optional(data.get("selfie"), PassportFile, bot) - data["translation"] = de_list_optional(data.get("translation"), PassportFile, bot) or None - - return super().de_json(data=data, bot=bot) - @classmethod def de_json_decrypted( cls, data: JSONDict, bot: "Bot | None", credentials: "Credentials" diff --git a/src/telegram/_passport/passportdata.py b/src/telegram/_passport/passportdata.py index deae7196f3a..73bf54976ba 100644 --- a/src/telegram/_passport/passportdata.py +++ b/src/telegram/_passport/passportdata.py @@ -24,11 +24,11 @@ from telegram._passport.credentials import EncryptedCredentials from telegram._passport.encryptedpassportelement import EncryptedPassportElement from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot, Credentials + from telegram import Credentials class PassportData(TelegramObject): @@ -82,16 +82,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PassportData": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["data"] = de_list_optional(data.get("data"), EncryptedPassportElement, bot) - data["credentials"] = de_json_optional(data.get("credentials"), EncryptedCredentials, bot) - - return super().de_json(data=data, bot=bot) - @property def decrypted_data(self) -> tuple[EncryptedPassportElement, ...]: """ diff --git a/src/telegram/_passport/passportfile.py b/src/telegram/_passport/passportfile.py index 724fb51ed1c..fd4c8a8189c 100644 --- a/src/telegram/_passport/passportfile.py +++ b/src/telegram/_passport/passportfile.py @@ -22,7 +22,6 @@ from typing import TYPE_CHECKING from telegram._telegramobject import TelegramObject -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput @@ -98,17 +97,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PassportFile": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["file_date"] = from_timestamp(data.get("file_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) - @classmethod def de_json_decrypted( cls, data: JSONDict, bot: "Bot | None", credentials: "FileCredentials" diff --git a/src/telegram/_payment/orderinfo.py b/src/telegram/_payment/orderinfo.py index c3f8a798d80..87997199b6c 100644 --- a/src/telegram/_payment/orderinfo.py +++ b/src/telegram/_payment/orderinfo.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram OrderInfo.""" -from typing import TYPE_CHECKING - from telegram._payment.shippingaddress import ShippingAddress from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class OrderInfo(TelegramObject): """This object represents information about an order. @@ -70,14 +64,3 @@ def __init__( self._id_attrs = (self.name, self.phone_number, self.email, self.shipping_address) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "OrderInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["shipping_address"] = de_json_optional( - data.get("shipping_address"), ShippingAddress, bot - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_payment/precheckoutquery.py b/src/telegram/_payment/precheckoutquery.py index 94acae703cb..096e3954d7d 100644 --- a/src/telegram/_payment/precheckoutquery.py +++ b/src/telegram/_payment/precheckoutquery.py @@ -18,18 +18,12 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram PreCheckoutQuery.""" -from typing import TYPE_CHECKING - from telegram._payment.orderinfo import OrderInfo from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput -if TYPE_CHECKING: - from telegram import Bot - class PreCheckoutQuery(TelegramObject): """This object contains information about an incoming pre-checkout query. @@ -110,16 +104,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PreCheckoutQuery": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["order_info"] = de_json_optional(data.get("order_info"), OrderInfo, bot) - - return super().de_json(data=data, bot=bot) - async def answer( self, ok: bool, diff --git a/src/telegram/_payment/shippingquery.py b/src/telegram/_payment/shippingquery.py index 2b3f92b3a16..fcbd45fad13 100644 --- a/src/telegram/_payment/shippingquery.py +++ b/src/telegram/_payment/shippingquery.py @@ -24,12 +24,10 @@ from telegram._payment.shippingaddress import ShippingAddress from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput if TYPE_CHECKING: - from telegram import Bot from telegram._payment.shippingoption import ShippingOption @@ -78,18 +76,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ShippingQuery": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["from_user"] = de_json_optional(data.pop("from", None), User, bot) - data["shipping_address"] = de_json_optional( - data.get("shipping_address"), ShippingAddress, bot - ) - - return super().de_json(data=data, bot=bot) - async def answer( self, ok: bool, diff --git a/src/telegram/_payment/stars/affiliateinfo.py b/src/telegram/_payment/stars/affiliateinfo.py index 5b178f74483..862e3584705 100644 --- a/src/telegram/_payment/stars/affiliateinfo.py +++ b/src/telegram/_payment/stars/affiliateinfo.py @@ -20,14 +20,12 @@ from typing import TYPE_CHECKING -from telegram._chat import Chat from telegram._telegramobject import TelegramObject -from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot + from telegram._chat import Chat + from telegram._user import User class AffiliateInfo(TelegramObject): @@ -105,13 +103,3 @@ def __init__( self.nanostar_amount, ) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "AffiliateInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["affiliate_user"] = de_json_optional(data.get("affiliate_user"), User, bot) - data["affiliate_chat"] = de_json_optional(data.get("affiliate_chat"), Chat, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_payment/stars/revenuewithdrawalstate.py b/src/telegram/_payment/stars/revenuewithdrawalstate.py index beb0e7de74f..a53e49f530f 100644 --- a/src/telegram/_payment/stars/revenuewithdrawalstate.py +++ b/src/telegram/_payment/stars/revenuewithdrawalstate.py @@ -20,17 +20,13 @@ """This module contains the classes for Telegram Stars Revenue Withdrawals.""" import datetime as dtm -from typing import TYPE_CHECKING, Final +from typing import ClassVar, Final from telegram import constants from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class RevenueWithdrawalState(TelegramObject): """This object describes the state of a revenue withdrawal operation. Currently, it can be one @@ -54,6 +50,15 @@ class RevenueWithdrawalState(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "pending": "RevenueWithdrawalStatePending", + "succeeded": "RevenueWithdrawalStateSucceeded", + "failed": "RevenueWithdrawalStateFailed", + }, + ) + PENDING: Final[str] = constants.RevenueWithdrawalStateType.PENDING """:const:`telegram.constants.RevenueWithdrawalStateType.PENDING`""" SUCCEEDED: Final[str] = constants.RevenueWithdrawalStateType.SUCCEEDED @@ -68,32 +73,6 @@ def __init__(self, type: str, *, api_kwargs: JSONDict | None = None) -> None: self._id_attrs = (self.type,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "RevenueWithdrawalState": - """Converts JSON data to the appropriate :class:`RevenueWithdrawalState` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with this object. - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[RevenueWithdrawalState]] = { - cls.PENDING: RevenueWithdrawalStatePending, - cls.SUCCEEDED: RevenueWithdrawalStateSucceeded, - cls.FAILED: RevenueWithdrawalStateFailed, - } - - if cls is RevenueWithdrawalState and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - return super().de_json(data=data, bot=bot) - class RevenueWithdrawalStatePending(RevenueWithdrawalState): """The withdrawal is in progress. @@ -150,19 +129,6 @@ def __init__( self.date, ) - @classmethod - def de_json( - cls, data: JSONDict, bot: "Bot | None" = None - ) -> "RevenueWithdrawalStateSucceeded": - """See :meth:`telegram.RevenueWithdrawalState.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class RevenueWithdrawalStateFailed(RevenueWithdrawalState): """The withdrawal failed and the transaction was refunded. diff --git a/src/telegram/_payment/stars/startransactions.py b/src/telegram/_payment/stars/startransactions.py index b8502fd8fd7..d47c48291de 100644 --- a/src/telegram/_payment/stars/startransactions.py +++ b/src/telegram/_payment/stars/startransactions.py @@ -21,18 +21,13 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict from .transactionpartner import TransactionPartner -if TYPE_CHECKING: - from telegram import Bot - class StarTransaction(TelegramObject): """Describes a Telegram Star transaction. @@ -114,20 +109,6 @@ def __init__( ) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "StarTransaction": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo) - - data["source"] = de_json_optional(data.get("source"), TransactionPartner, bot) - data["receiver"] = de_json_optional(data.get("receiver"), TransactionPartner, bot) - - return super().de_json(data=data, bot=bot) - class StarTransactions(TelegramObject): """ @@ -155,11 +136,3 @@ def __init__( self._id_attrs = (self.transactions,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "StarTransactions": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["transactions"] = de_list_optional(data.get("transactions"), StarTransaction, bot) - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_payment/stars/transactionpartner.py b/src/telegram/_payment/stars/transactionpartner.py index 09d96c159da..d6069204f5e 100644 --- a/src/telegram/_payment/stars/transactionpartner.py +++ b/src/telegram/_payment/stars/transactionpartner.py @@ -20,30 +20,28 @@ """This module contains the classes for Telegram Stars transaction partners.""" from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import TYPE_CHECKING, ClassVar, Final from telegram import constants from telegram._chat import Chat from telegram._gifts import Gift from telegram._paidmedia import PaidMedia from telegram._telegramobject import TelegramObject -from telegram._user import User from telegram._utils import enum from telegram._utils.argumentparsing import ( - de_json_optional, - de_list_optional, parse_sequence_arg, to_timedelta, ) from telegram._utils.types import JSONDict, TimePeriod from .affiliateinfo import AffiliateInfo -from .revenuewithdrawalstate import RevenueWithdrawalState if TYPE_CHECKING: import datetime as dtm - from telegram import Bot + from telegram._user import User + + from .revenuewithdrawalstate import RevenueWithdrawalState class TransactionPartner(TelegramObject): @@ -75,6 +73,19 @@ class TransactionPartner(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "affiliate_program": "TransactionPartnerAffiliateProgram", + "chat": "TransactionPartnerChat", + "fragment": "TransactionPartnerFragment", + "other": "TransactionPartnerOther", + "telegram_ads": "TransactionPartnerTelegramAds", + "telegram_api": "TransactionPartnerTelegramApi", + "user": "TransactionPartnerUser", + }, + ) + AFFILIATE_PROGRAM: Final[str] = constants.TransactionPartnerType.AFFILIATE_PROGRAM """:const:`telegram.constants.TransactionPartnerType.AFFILIATE_PROGRAM` @@ -103,36 +114,6 @@ def __init__(self, type: str, *, api_kwargs: JSONDict | None = None) -> None: self._id_attrs = (self.type,) self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "TransactionPartner": - """Converts JSON data to the appropriate :class:`TransactionPartner` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with this object. - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - _class_mapping: dict[str, type[TransactionPartner]] = { - cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram, - cls.CHAT: TransactionPartnerChat, - cls.FRAGMENT: TransactionPartnerFragment, - cls.USER: TransactionPartnerUser, - cls.TELEGRAM_ADS: TransactionPartnerTelegramAds, - cls.TELEGRAM_API: TransactionPartnerTelegramApi, - cls.OTHER: TransactionPartnerOther, - } - - if cls is TransactionPartner and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) - - return super().de_json(data=data, bot=bot) - class TransactionPartnerAffiliateProgram(TransactionPartner): """Describes the affiliate program that issued the affiliate commission received via this @@ -177,17 +158,6 @@ def __init__( self.commission_per_mille, ) - @classmethod - def de_json( - cls, data: JSONDict, bot: "Bot | None" = None - ) -> "TransactionPartnerAffiliateProgram": - """See :meth:`telegram.TransactionPartner.de_json`.""" - data = cls._parse_data(data) - - data["sponsor_user"] = de_json_optional(data.get("sponsor_user"), User, bot) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class TransactionPartnerChat(TransactionPartner): """Describes a transaction with a chat. @@ -232,16 +202,6 @@ def __init__( self.chat, ) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "TransactionPartnerChat": - """See :meth:`telegram.TransactionPartner.de_json`.""" - data = cls._parse_data(data) - - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["gift"] = de_json_optional(data.get("gift"), Gift, bot) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class TransactionPartnerFragment(TransactionPartner): """Describes a withdrawal transaction with Fragment. @@ -272,17 +232,6 @@ def __init__( with self._unfrozen(): self.withdrawal_state: RevenueWithdrawalState | None = withdrawal_state - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "TransactionPartnerFragment": - """See :meth:`telegram.TransactionPartner.de_json`.""" - data = cls._parse_data(data) - - data["withdrawal_state"] = de_json_optional( - data.get("withdrawal_state"), RevenueWithdrawalState, bot - ) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class TransactionPartnerUser(TransactionPartner): """Describes a transaction with a user. @@ -448,18 +397,6 @@ def __init__( self.transaction_type, ) - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "TransactionPartnerUser": - """See :meth:`telegram.TransactionPartner.de_json`.""" - data = cls._parse_data(data) - - data["user"] = de_json_optional(data.get("user"), User, bot) - data["affiliate"] = de_json_optional(data.get("affiliate"), AffiliateInfo, bot) - data["paid_media"] = de_list_optional(data.get("paid_media"), PaidMedia, bot) - data["gift"] = de_json_optional(data.get("gift"), Gift, bot) - - return super().de_json(data=data, bot=bot) # type: ignore[return-value] - class TransactionPartnerOther(TransactionPartner): """Describes a transaction with an unknown partner. diff --git a/src/telegram/_payment/successfulpayment.py b/src/telegram/_payment/successfulpayment.py index 7fb284814c6..8bf354330e4 100644 --- a/src/telegram/_payment/successfulpayment.py +++ b/src/telegram/_payment/successfulpayment.py @@ -19,17 +19,11 @@ """This module contains an object that represents a Telegram SuccessfulPayment.""" import datetime as dtm -from typing import TYPE_CHECKING from telegram._payment.orderinfo import OrderInfo from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class SuccessfulPayment(TelegramObject): """This object contains basic information about a successful payment. @@ -140,19 +134,3 @@ def __init__( self._id_attrs = (self.telegram_payment_charge_id, self.provider_payment_charge_id) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "SuccessfulPayment": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["order_info"] = de_json_optional(data.get("order_info"), OrderInfo, bot) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["subscription_expiration_date"] = from_timestamp( - data.get("subscription_expiration_date"), tzinfo=loc_tzinfo - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index 744edd22eff..3fdf0733c10 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -20,7 +20,7 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import Final from telegram import constants from telegram._chat import Chat @@ -29,23 +29,14 @@ from telegram._user import User from telegram._utils import enum from telegram._utils.argumentparsing import ( - de_json_optional, - de_list_optional, parse_sequence_arg, to_timedelta, ) -from telegram._utils.datetime import ( - extract_tzinfo_from_defaults, - from_timestamp, - get_timedelta_value, -) +from telegram._utils.datetime import get_timedelta_value from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.types import JSONDict, ODVInput, TimePeriod -if TYPE_CHECKING: - from telegram import Bot - class InputPollOption(TelegramObject): """ @@ -100,15 +91,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InputPollOption": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot) - - return super().de_json(data=data, bot=bot) - class PollOption(TelegramObject): """ @@ -161,15 +143,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOption": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot) - - return super().de_json(data=data, bot=bot) - def parse_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`text` from a given :class:`telegram.MessageEntity` of :attr:`text_entities`. @@ -305,16 +278,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollAnswer": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["user"] = de_json_optional(data.get("user"), User, bot) - data["voter_chat"] = de_json_optional(data.get("voter_chat"), Chat, bot) - - return super().de_json(data=data, bot=bot) - class Poll(TelegramObject): """ @@ -478,25 +441,6 @@ def __init__( def open_period(self) -> int | dtm.timedelta | None: return get_timedelta_value(self._open_period, attribute="open_period") - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Poll": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["options"] = de_list_optional(data.get("options"), PollOption, bot) - data["explanation_entities"] = de_list_optional( - data.get("explanation_entities"), MessageEntity, bot - ) - data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo) - data["question_entities"] = de_list_optional( - data.get("question_entities"), MessageEntity, bot - ) - - return super().de_json(data=data, bot=bot) - def parse_explanation_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`explanation` from a given :class:`telegram.MessageEntity` of :attr:`explanation_entities`. diff --git a/src/telegram/_proximityalerttriggered.py b/src/telegram/_proximityalerttriggered.py index 8f07d9aede4..4907172ab71 100644 --- a/src/telegram/_proximityalerttriggered.py +++ b/src/telegram/_proximityalerttriggered.py @@ -18,16 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Proximity Alert.""" -from typing import TYPE_CHECKING - from telegram._telegramobject import TelegramObject from telegram._user import User -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ProximityAlertTriggered(TelegramObject): """ @@ -67,13 +61,3 @@ def __init__( self._id_attrs = (self.traveler, self.watcher, self.distance) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ProximityAlertTriggered": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["traveler"] = de_json_optional(data.get("traveler"), User, bot) - data["watcher"] = de_json_optional(data.get("watcher"), User, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_reaction.py b/src/telegram/_reaction.py index 2dbcd462e72..1ae97580e62 100644 --- a/src/telegram/_reaction.py +++ b/src/telegram/_reaction.py @@ -19,17 +19,13 @@ # pylint: disable=redefined-builtin """This module contains objects that represents a Telegram ReactionType.""" -from typing import TYPE_CHECKING, Final, Literal +from typing import ClassVar, Final, Literal from telegram import constants from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class ReactionType(TelegramObject): """Base class for Telegram ReactionType Objects. @@ -54,6 +50,15 @@ class ReactionType(TelegramObject): __slots__ = ("type",) + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = ( + "type", + { + "emoji": "ReactionTypeEmoji", + "custom_emoji": "ReactionTypeCustomEmoji", + "paid": "ReactionTypePaid", + }, + ) + EMOJI: Final[constants.ReactionType] = constants.ReactionType.EMOJI """:const:`telegram.constants.ReactionType.EMOJI`""" CUSTOM_EMOJI: Final[constants.ReactionType] = constants.ReactionType.CUSTOM_EMOJI @@ -76,22 +81,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ReactionType": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - _class_mapping: dict[str, type[ReactionType]] = { - cls.EMOJI: ReactionTypeEmoji, - cls.CUSTOM_EMOJI: ReactionTypeCustomEmoji, - cls.PAID: ReactionTypePaid, - } - - if cls is ReactionType and data.get("type") in _class_mapping: - return _class_mapping[data.pop("type")].de_json(data, bot) - - return super().de_json(data=data, bot=bot) - class ReactionTypeEmoji(ReactionType): """ @@ -220,12 +209,3 @@ def __init__( self.total_count, ) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ReactionCount": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["type"] = de_json_optional(data.get("type"), ReactionType, bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_reply.py b/src/telegram/_reply.py index 367d5aad7a0..c9ff0121945 100644 --- a/src/telegram/_reply.py +++ b/src/telegram/_reply.py @@ -26,9 +26,7 @@ from telegram._dice import Dice from telegram._files.animation import Animation from telegram._files.audio import Audio -from telegram._files.contact import Contact from telegram._files.document import Document -from telegram._files.location import Location from telegram._files.photosize import PhotoSize from telegram._files.sticker import Sticker from telegram._files.venue import Venue @@ -45,12 +43,13 @@ from telegram._poll import Poll from telegram._story import Story from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput if TYPE_CHECKING: - from telegram import Bot + from telegram._files.contact import Contact + from telegram._files.location import Location class ExternalReplyInfo(TelegramObject): @@ -258,41 +257,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ExternalReplyInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["origin"] = de_json_optional(data.get("origin"), MessageOrigin, bot) - data["chat"] = de_json_optional(data.get("chat"), Chat, bot) - data["link_preview_options"] = de_json_optional( - data.get("link_preview_options"), LinkPreviewOptions, bot - ) - data["animation"] = de_json_optional(data.get("animation"), Animation, bot) - data["audio"] = de_json_optional(data.get("audio"), Audio, bot) - data["document"] = de_json_optional(data.get("document"), Document, bot) - data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) - data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) - data["story"] = de_json_optional(data.get("story"), Story, bot) - data["video"] = de_json_optional(data.get("video"), Video, bot) - data["video_note"] = de_json_optional(data.get("video_note"), VideoNote, bot) - data["voice"] = de_json_optional(data.get("voice"), Voice, bot) - data["contact"] = de_json_optional(data.get("contact"), Contact, bot) - data["dice"] = de_json_optional(data.get("dice"), Dice, bot) - data["game"] = de_json_optional(data.get("game"), Game, bot) - data["giveaway"] = de_json_optional(data.get("giveaway"), Giveaway, bot) - data["giveaway_winners"] = de_json_optional( - data.get("giveaway_winners"), GiveawayWinners, bot - ) - data["invoice"] = de_json_optional(data.get("invoice"), Invoice, bot) - data["location"] = de_json_optional(data.get("location"), Location, bot) - data["poll"] = de_json_optional(data.get("poll"), Poll, bot) - data["venue"] = de_json_optional(data.get("venue"), Venue, bot) - data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot) - data["checklist"] = de_json_optional(data.get("checklist"), Checklist, bot) - - return super().de_json(data=data, bot=bot) - class TextQuote(TelegramObject): """ @@ -358,15 +322,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "TextQuote": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot) - - return super().de_json(data=data, bot=bot) - class ReplyParameters(TelegramObject): """ @@ -477,14 +432,3 @@ def __init__( self._id_attrs = (self.message_id,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ReplyParameters": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["quote_entities"] = tuple( - de_list_optional(data.get("quote_entities"), MessageEntity, bot) - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_shared.py b/src/telegram/_shared.py index 2de07529c62..66c095b11aa 100644 --- a/src/telegram/_shared.py +++ b/src/telegram/_shared.py @@ -19,17 +19,13 @@ """This module contains two objects used for request chats/users service messages.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._files.photosize import PhotoSize from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict from telegram._utils.usernames import get_full_name, get_link, get_name -if TYPE_CHECKING: - from telegram._bot import Bot - class UsersShared(TelegramObject): """ @@ -70,6 +66,12 @@ class UsersShared(TelegramObject): __slots__ = ("request_id", "users") + __REMOVED_API_FIELDS__ = frozenset( + { + "user_ids", + } + ) + def __init__( self, request_id: int, @@ -85,21 +87,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UsersShared": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["users"] = de_list_optional(data.get("users"), SharedUser, bot) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if user_ids := data.get("user_ids"): - api_kwargs = {"user_ids": user_ids} - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) - class ChatShared(TelegramObject): """ @@ -171,14 +158,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatShared": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) - return super().de_json(data=data, bot=bot) - @property def link(self) -> str | None: """:obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link @@ -281,11 +260,3 @@ def link(self) -> str | None: .. versionadded:: 22.4 """ return get_link(self) - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "SharedUser": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_story.py b/src/telegram/_story.py index 680e7206230..85d17f8935d 100644 --- a/src/telegram/_story.py +++ b/src/telegram/_story.py @@ -18,16 +18,11 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object related to a Telegram Story.""" -from typing import TYPE_CHECKING - from telegram._chat import Chat from telegram._telegramobject import TelegramObject from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import JSONDict, ODVInput, TimePeriod -if TYPE_CHECKING: - from telegram import Bot - class Story(TelegramObject): """ @@ -71,14 +66,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Story": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["chat"] = Chat.de_json(data.get("chat", {}), bot) - return super().de_json(data=data, bot=bot) - async def repost( self, business_connection_id: str, diff --git a/src/telegram/_suggestedpost.py b/src/telegram/_suggestedpost.py index b820706685a..19e66bf92eb 100644 --- a/src/telegram/_suggestedpost.py +++ b/src/telegram/_suggestedpost.py @@ -19,20 +19,15 @@ """This module contains objects related to Telegram suggested posts.""" import datetime as dtm -from typing import TYPE_CHECKING, Final, Optional +from typing import Final from telegram import constants from telegram._message import Message from telegram._payment.stars.staramount import StarAmount from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class SuggestedPostPrice(TelegramObject): """ @@ -141,20 +136,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostParameters": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["price"] = de_json_optional(data.get("price"), SuggestedPostPrice, bot) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["send_date"] = from_timestamp(data.get("send_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) - class SuggestedPostInfo(TelegramObject): """ @@ -223,19 +204,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["price"] = de_json_optional(data.get("price"), SuggestedPostPrice, bot) - data["send_date"] = from_timestamp(data.get("send_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) - class SuggestedPostDeclined(TelegramObject): """ @@ -281,17 +249,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostDeclined": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["suggested_post_message"] = de_json_optional( - data.get("suggested_post_message"), Message, bot - ) - - return super().de_json(data=data, bot=bot) - class SuggestedPostPaid(TelegramObject): """ @@ -363,18 +320,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostPaid": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["suggested_post_message"] = de_json_optional( - data.get("suggested_post_message"), Message, bot - ) - data["star_amount"] = de_json_optional(data.get("star_amount"), StarAmount, bot) - - return super().de_json(data=data, bot=bot) - class SuggestedPostRefunded(TelegramObject): """ @@ -430,17 +375,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostRefunded": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["suggested_post_message"] = de_json_optional( - data.get("suggested_post_message"), Message, bot - ) - - return super().de_json(data=data, bot=bot) - class SuggestedPostApproved(TelegramObject): """ @@ -496,22 +430,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostApproved": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["send_date"] = from_timestamp(data.get("send_date"), tzinfo=loc_tzinfo) - data["price"] = de_json_optional(data.get("price"), SuggestedPostPrice, bot) - data["suggested_post_message"] = de_json_optional( - data.get("suggested_post_message"), Message, bot - ) - - return super().de_json(data=data, bot=bot) - class SuggestedPostApprovalFailed(TelegramObject): """ @@ -559,15 +477,3 @@ def __init__( self._id_attrs = (self.price, self.suggested_post_message) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuggestedPostApprovalFailed": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["price"] = de_json_optional(data.get("price"), SuggestedPostPrice, bot) - data["suggested_post_message"] = de_json_optional( - data.get("suggested_post_message"), Message, bot - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_telegramobject.py b/src/telegram/_telegramobject.py index 4098c75c37e..073d5dd5626 100644 --- a/src/telegram/_telegramobject.py +++ b/src/telegram/_telegramobject.py @@ -20,25 +20,81 @@ import contextlib import datetime as dtm +import importlib import inspect import json -from collections.abc import Iterator, Mapping, Sized +import types as _types +from collections.abc import Iterator, Mapping, Sequence, Sized from contextlib import contextmanager from copy import deepcopy from itertools import chain from types import MappingProxyType -from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast, get_args, get_origin -from telegram._utils.datetime import to_timestamp +from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp, to_timestamp from telegram._utils.defaultvalue import DefaultValue from telegram._utils.types import JSONDict from telegram._utils.warnings import warn if TYPE_CHECKING: + from collections.abc import Callable + from telegram import Bot Tele_co = TypeVar("Tele_co", bound="TelegramObject", covariant=True) Tele = TypeVar("Tele", bound="TelegramObject") +_DATETIME_FIELD = object() # sentinel value to mark datetime fields in the de_json plan + + +def _telegram_ns() -> dict[str, object]: + """Return the full ``telegram`` package namespace for annotation resolution.""" + return vars(importlib.import_module("telegram")) + + +def _unwrap_optional(ann: object) -> object: + """``X | None`` → ``X``. Any other annotation is returned unchanged.""" + if isinstance(ann, _types.UnionType): + non_none = [a for a in ann.__args__ if a is not type(None)] # pylint: disable=unidiomatic-typecheck + if len(non_none) == 1: + return non_none[0] + return ann + + +def _make_seq_transform( + item_type: object, + globalns: dict[str, object], + tg_ns: dict[str, object], +) -> "Callable[[object, Bot | None], object] | None": + """Recursively build a ``(value, bot) → transformed_value`` lambda for a Sequence item type. + + Args: + item_type: The unwrapped type annotation of the items in the Sequence. + globalns: The global namespace for evaluating string annotations. + tg_ns: The telegram namespace for evaluating string annotations. + + Returns: + - :obj:`None` if the item type does not require any transformation. + - a lambda (value, bot) -> transformation + """ + # inspect.signature does not resolve forward refs in Sequence's despite eval_str=True for some + # reason: + if isinstance(item_type, str): + item_type = eval(item_type, globalns, tg_ns) # pylint: disable=eval-used # noqa: S307 + + item_origin = get_origin(item_type) + if item_origin is Sequence: + inner_args = get_args(item_type) + if not inner_args: + return None + inner_fn = _make_seq_transform(inner_args[0], globalns, tg_ns) + if inner_fn is None: + return None + return lambda v, b, _f=inner_fn: [_f(row, b) for row in v] if isinstance(v, list) else v # type: ignore[misc] # pylint: disable=line-too-long + + if isinstance(item_type, type) and issubclass(item_type, TelegramObject): + return lambda v, b, _c=item_type: _c.de_list(v, b) if isinstance(v, list) else v # type: ignore[misc] # pylint: disable=line-too-long + + return None class TelegramObject: @@ -88,6 +144,31 @@ class TelegramObject: # unless it's overridden __INIT_PARAMS_CHECK: type["TelegramObject"] | None = None + # Per-class de_json plan built once by _build_plan(). + # Maps parameter name → transform target: + # _DATETIME_FIELD sentinel → from_timestamp(value, tzinfo) + # TelegramObject subclass → cls.de_json(value, bot) + # callable(value, bot) → Sequence transform (from _make_seq_transform) + __DE_JSON_PLAN__: ClassVar[dict[str, Any]] = {} + + # Forward-compatibility: names of Sequence-typed __init__ params. + # If the Telegram API stops sending a field that PTB's __init__ still + # requires (i.e. the API made it optional), _de_json defaults sequences + # to () and everything else to None so the library doesn't crash. + __DE_JSON_COMPAT__: ClassVar[frozenset[str]] = frozenset() + + # Subclasses may declare field names that Telegram still returns for backwards + # compatibility but that are no longer part of the PTB model. Those fields will + # be intercepted by de_json and forwarded into api_kwargs before construction. + __REMOVED_API_FIELDS__: ClassVar[frozenset[str]] = frozenset() + + # Delegator base classes (e.g. TransactionPartner) may define a dispatch mapping to route + # de_json to the correct subclass. + # Format: (dispatch_key, {value: "ClassName", ...}). + # The dispatch_key is the JSON field name (e.g. "type", "source", "status"). + # The values are class name strings, e.g. "TransactionPartnerChat". + __DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = None + def __init__(self, *, api_kwargs: JSONDict | None = None) -> None: # Setting _frozen to `False` here means that classes without arguments still need to # implement __init__. However, with `True` would mean increased usage of @@ -385,6 +466,74 @@ def _parse_data(data: JSONDict) -> JSONDict: """ return data.copy() + @classmethod + def _build_plan(cls) -> dict[str, Any]: + """Build the de_json transformation plan from ``__init__`` type annotations. + + Called once per class on the first :meth:`de_json` invocation. By that time + every module is fully loaded so forward-reference resolution always succeeds. + + The resulting ``dict`` maps parameter names to their transform targets. + Four kinds of transforms are recognised: + + * ``datetime`` fields → :func:`~telegram._utils.datetime.from_timestamp` + * ``TelegramObject`` fields → ``TargetCls.de_json(...)`` + * ``Sequence[TelegramObject]`` fields → ``TargetCls.de_list(...)`` + * ``Sequence[Sequence[TelegramObject]]`` fields → nested de_list + """ + init_fn = cls.__dict__.get("__init__") + if init_fn is None: + # No own __init__: inherit the nearest ancestor's plan. This is true for e.g. Chat + parent = cast("type[TelegramObject]", cls.__mro__[1]) + if "__DE_JSON_PLAN__" not in parent.__dict__: + parent._build_plan() # pylint: disable=protected-access, no-member + cls.__DE_JSON_PLAN__ = parent.__DE_JSON_PLAN__ # pylint: disable=no-member + cls.__DE_JSON_COMPAT__ = parent.__DE_JSON_COMPAT__ # pylint: disable=no-member + return cls.__DE_JSON_PLAN__ + + plan: dict[str, Any] = {} + seq_fields: set[str] = set() + globalns: dict[str, object] = getattr(init_fn, "__globals__", {}) + tg_ns = _telegram_ns() + sig = inspect.signature(init_fn, eval_str=True, globals=globalns, locals=tg_ns) + + for name, param in sig.parameters.items(): + if name in ("self", "api_kwargs") or param.kind in ( + param.VAR_POSITIONAL, + param.VAR_KEYWORD, + ): + continue + + ann = param.annotation + inner = _unwrap_optional(ann) + origin = get_origin(inner) + + if inner is dtm.datetime: + plan[name] = _DATETIME_FIELD + elif isinstance(inner, type) and issubclass(inner, TelegramObject): + plan[name] = inner + elif origin is Sequence: + args = get_args(inner) + if not args: + continue + fn = _make_seq_transform(args[0], globalns, tg_ns) + if fn is not None: + plan[name] = fn + seq_fields.add(name) + + cls.__DE_JSON_PLAN__ = plan + cls.__DE_JSON_COMPAT__ = frozenset(seq_fields) + + # Pre-resolve any string class names in the dispatch mapping so that + # de_json() can look up the target class directly without string checks. + if cls.__DE_JSON_DISPATCH__: + _, dispatch_mapping = cls.__DE_JSON_DISPATCH__ + for key, value in dispatch_mapping.items(): + if isinstance(value, str): + dispatch_mapping[key] = tg_ns[value] # type: ignore[assignment] + + return plan + @classmethod def _de_json( cls: type[Tele_co], @@ -396,12 +545,15 @@ def _de_json( try: obj = cls(**data, api_kwargs=api_kwargs) except TypeError as exc: - if "__init__() got an unexpected keyword argument" not in str(exc): + exc_str = str(exc) + if ( + "unexpected keyword argument" not in exc_str + and "required positional argument" not in exc_str + ): raise if cls.__INIT_PARAMS_CHECK is not cls: - signature = inspect.signature(cls) - cls.__INIT_PARAMS = set(signature.parameters.keys()) + cls.__INIT_PARAMS = set(inspect.signature(cls).parameters.keys()) cls.__INIT_PARAMS_CHECK = cls api_kwargs = api_kwargs or {} @@ -409,6 +561,12 @@ def _de_json( for key, value in data.items(): (existing_kwargs if key in cls.__INIT_PARAMS else api_kwargs)[key] = value + # Forward-compat: if the API stopped sending a field PTB requires, + # default sequences to () and everything else to None. + compat = cls.__dict__.get("__DE_JSON_COMPAT__", frozenset()) + for key in cls.__INIT_PARAMS - existing_kwargs.keys() - {"self", "api_kwargs"}: + existing_kwargs[key] = () if key in compat else None + obj = cls(api_kwargs=api_kwargs, **existing_kwargs) obj.set_bot(bot=bot) @@ -430,7 +588,55 @@ def de_json(cls: type[Tele_co], data: JSONDict, bot: "Bot | None" = None) -> Tel The Telegram object. """ - return cls._de_json(data=data, bot=bot) + # Build the plan lazily (once per class). + if "__DE_JSON_PLAN__" not in cls.__dict__: + cls._build_plan() + plan = cls.__DE_JSON_PLAN__ + + # Fast path: no dispatch, no removed fields, empty plan → skip data.copy() + if not plan and not cls.__DE_JSON_DISPATCH__ and not cls.__REMOVED_API_FIELDS__: + return cls._de_json(data=data, bot=bot) + + # We'll mutate data below (rename, pop, transform), so copy first. + data = cls._parse_data(data) + + # Dispatch to subclass for delegator classes (e.g. TransactionPartner, ChatMember). + if cls.__DE_JSON_DISPATCH__: + dispatch_key, dispatch_mapping = cls.__DE_JSON_DISPATCH__ + target_cls: Tele_co = dispatch_mapping.get(data.get(dispatch_key)) # type: ignore[assignment, arg-type] # pylint: disable=line-too-long + if target_cls is not None: + data.pop(dispatch_key) + return target_cls.de_json(data=data, bot=bot) + + # Move removed/legacy API fields into api_kwargs + api_kwargs: JSONDict | None = None + if cls.__REMOVED_API_FIELDS__: + removed = {f: data.pop(f) for f in cls.__REMOVED_API_FIELDS__ if f in data} + if removed: + api_kwargs = removed + + # Rename "from" → "from_user" before the transform loop. + if "from_user" in plan and "from" in data: + data["from_user"] = data.pop("from") + + # Let's finally apply the transformations: + if plan: + # Compute tzinfo once for all datetime fields, if any + tz = extract_tzinfo_from_defaults(bot) + for key in data: # Only loop through keys returned by the API + if key in plan and data[key] is not None: # Should we transform this field? + target = plan[key] # The transform target for this field + if target is _DATETIME_FIELD: # timestamp → datetime + if not isinstance(data[key], dtm.datetime): # Avoid retransformations + data[key] = from_timestamp(data[key], tzinfo=tz) + elif isinstance(target, type): # Target is a TelegramObject subclass → de_json + if not isinstance(data[key], target): # Avoid retransformations + data[key] = target.de_json(data[key], bot) # type: ignore[attr-defined] + else: + # Sequence transform callable (e.g. de_list) + data[key] = target(data[key], bot) + + return cls._de_json(data=data, bot=bot, api_kwargs=api_kwargs) @classmethod def de_list( diff --git a/src/telegram/_uniquegift.py b/src/telegram/_uniquegift.py index 33f35aae28e..56a3a7ed90f 100644 --- a/src/telegram/_uniquegift.py +++ b/src/telegram/_uniquegift.py @@ -21,20 +21,16 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING, Final +from typing import Final from telegram import constants from telegram._chat import Chat from telegram._files.sticker import Sticker from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import de_json_optional, parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp +from telegram._utils.argumentparsing import parse_sequence_arg from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class UniqueGiftColors(TelegramObject): """This object contains information about the color scheme for a user's name, message replies @@ -173,15 +169,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGiftModel": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) - - return super().de_json(data=data, bot=bot) - class UniqueGiftSymbol(TelegramObject): """This object describes the symbol shown on the pattern of a unique gift. @@ -228,15 +215,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGiftSymbol": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) - - return super().de_json(data=data, bot=bot) - class UniqueGiftBackdropColors(TelegramObject): """This object describes the colors of the backdrop of a unique gift. @@ -333,15 +311,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGiftBackdrop": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["colors"] = de_json_optional(data.get("colors"), UniqueGiftBackdropColors, bot) - - return super().de_json(data=data, bot=bot) - class UniqueGift(TelegramObject): """This object describes a unique gift that was upgraded from a regular gift. @@ -483,19 +452,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGift": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["model"] = de_json_optional(data.get("model"), UniqueGiftModel, bot) - data["symbol"] = de_json_optional(data.get("symbol"), UniqueGiftSymbol, bot) - data["backdrop"] = de_json_optional(data.get("backdrop"), UniqueGiftBackdrop, bot) - data["publisher_chat"] = de_json_optional(data.get("publisher_chat"), Chat, bot) - data["colors"] = de_json_optional(data.get("colors"), UniqueGiftColors, bot) - - return super().de_json(data=data, bot=bot) - class UniqueGiftInfo(TelegramObject): """Describes a service message about a unique gift that was sent or received. @@ -627,16 +583,3 @@ def __init__( self._id_attrs = (self.gift, self.origin) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGiftInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - loc_tzinfo = extract_tzinfo_from_defaults(bot) - data["gift"] = de_json_optional(data.get("gift"), UniqueGift, bot) - data["next_transfer_date"] = from_timestamp( - data.get("next_transfer_date"), tzinfo=loc_tzinfo - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_update.py b/src/telegram/_update.py index 9e018e4cb0f..335c081d4af 100644 --- a/src/telegram/_update.py +++ b/src/telegram/_update.py @@ -35,12 +35,11 @@ from telegram._payment.shippingquery import ShippingQuery from telegram._poll import Poll, PollAnswer from telegram._telegramobject import TelegramObject -from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict from telegram._utils.warnings import warn if TYPE_CHECKING: - from telegram import Bot, Chat, User + from telegram import Chat, User class Update(TelegramObject): @@ -754,58 +753,3 @@ def effective_message(self) -> Message | None: self._effective_message = message return message - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Update": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["message"] = de_json_optional(data.get("message"), Message, bot) - data["edited_message"] = de_json_optional(data.get("edited_message"), Message, bot) - data["inline_query"] = de_json_optional(data.get("inline_query"), InlineQuery, bot) - data["chosen_inline_result"] = de_json_optional( - data.get("chosen_inline_result"), ChosenInlineResult, bot - ) - data["callback_query"] = de_json_optional(data.get("callback_query"), CallbackQuery, bot) - data["shipping_query"] = de_json_optional(data.get("shipping_query"), ShippingQuery, bot) - data["pre_checkout_query"] = de_json_optional( - data.get("pre_checkout_query"), PreCheckoutQuery, bot - ) - data["channel_post"] = de_json_optional(data.get("channel_post"), Message, bot) - data["edited_channel_post"] = de_json_optional( - data.get("edited_channel_post"), Message, bot - ) - data["poll"] = de_json_optional(data.get("poll"), Poll, bot) - data["poll_answer"] = de_json_optional(data.get("poll_answer"), PollAnswer, bot) - data["my_chat_member"] = de_json_optional( - data.get("my_chat_member"), ChatMemberUpdated, bot - ) - data["chat_member"] = de_json_optional(data.get("chat_member"), ChatMemberUpdated, bot) - data["chat_join_request"] = de_json_optional( - data.get("chat_join_request"), ChatJoinRequest, bot - ) - data["chat_boost"] = de_json_optional(data.get("chat_boost"), ChatBoostUpdated, bot) - data["removed_chat_boost"] = de_json_optional( - data.get("removed_chat_boost"), ChatBoostRemoved, bot - ) - data["message_reaction"] = de_json_optional( - data.get("message_reaction"), MessageReactionUpdated, bot - ) - data["message_reaction_count"] = de_json_optional( - data.get("message_reaction_count"), MessageReactionCountUpdated, bot - ) - data["business_connection"] = de_json_optional( - data.get("business_connection"), BusinessConnection, bot - ) - data["business_message"] = de_json_optional(data.get("business_message"), Message, bot) - data["edited_business_message"] = de_json_optional( - data.get("edited_business_message"), Message, bot - ) - data["deleted_business_messages"] = de_json_optional( - data.get("deleted_business_messages"), BusinessMessagesDeleted, bot - ) - data["purchased_paid_media"] = de_json_optional( - data.get("purchased_paid_media"), PaidMediaPurchased, bot - ) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_userprofileaudios.py b/src/telegram/_userprofileaudios.py index 769298f0b83..dfe1362fb4e 100644 --- a/src/telegram/_userprofileaudios.py +++ b/src/telegram/_userprofileaudios.py @@ -19,15 +19,11 @@ """This module contains an object that represents a Telegram UserProfileAudios.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._files.audio import Audio from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class UserProfileAudios(TelegramObject): """ @@ -65,12 +61,3 @@ def __init__( self._id_attrs = (self.total_count, self.audios) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UserProfileAudios": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["audios"] = Audio.de_list(data.get("audios", []), bot) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_userprofilephotos.py b/src/telegram/_userprofilephotos.py index 59cebae1835..e5518a2081a 100644 --- a/src/telegram/_userprofilephotos.py +++ b/src/telegram/_userprofilephotos.py @@ -19,15 +19,11 @@ """This module contains an object that represents a Telegram UserProfilePhotos.""" from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._files.photosize import PhotoSize from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class UserProfilePhotos(TelegramObject): """This object represents a user's profile pictures. @@ -70,12 +66,3 @@ def __init__( self._id_attrs = (self.total_count, self.photos) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UserProfilePhotos": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["photos"] = [PhotoSize.de_list(photo, bot) for photo in data["photos"]] - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_videochat.py b/src/telegram/_videochat.py index b7f75dfc590..c355fe607b9 100644 --- a/src/telegram/_videochat.py +++ b/src/telegram/_videochat.py @@ -20,21 +20,13 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils.argumentparsing import parse_sequence_arg, to_timedelta -from telegram._utils.datetime import ( - extract_tzinfo_from_defaults, - from_timestamp, - get_timedelta_value, -) +from telegram._utils.datetime import get_timedelta_value from telegram._utils.types import JSONDict, TimePeriod -if TYPE_CHECKING: - from telegram import Bot - class VideoChatStarted(TelegramObject): """ @@ -146,14 +138,6 @@ def __init__( self._freeze() - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "VideoChatParticipantsInvited": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - data["users"] = User.de_list(data.get("users", []), bot) - return super().de_json(data=data, bot=bot) - class VideoChatScheduled(TelegramObject): """This object represents a service message about a video chat scheduled in the chat. @@ -193,15 +177,3 @@ def __init__( self._id_attrs = (self.start_date,) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "VideoChatScheduled": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["start_date"] = from_timestamp(data.get("start_date"), tzinfo=loc_tzinfo) - - return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_webhookinfo.py b/src/telegram/_webhookinfo.py index 94f6f7bf22e..7bfd6413c08 100644 --- a/src/telegram/_webhookinfo.py +++ b/src/telegram/_webhookinfo.py @@ -20,16 +20,11 @@ import datetime as dtm from collections.abc import Sequence -from typing import TYPE_CHECKING from telegram._telegramobject import TelegramObject from telegram._utils.argumentparsing import parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict -if TYPE_CHECKING: - from telegram import Bot - class WebhookInfo(TelegramObject): """This object represents a Telegram WebhookInfo. @@ -162,18 +157,3 @@ def __init__( ) self._freeze() - - @classmethod - def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "WebhookInfo": - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["last_error_date"] = from_timestamp(data.get("last_error_date"), tzinfo=loc_tzinfo) - data["last_synchronization_error_date"] = from_timestamp( - data.get("last_synchronization_error_date"), tzinfo=loc_tzinfo - ) - - return super().de_json(data=data, bot=bot) diff --git a/tests/_files/test_sticker.py b/tests/_files/test_sticker.py index b3ee5178a0b..d76ce821e0f 100644 --- a/tests/_files/test_sticker.py +++ b/tests/_files/test_sticker.py @@ -69,11 +69,35 @@ class StickerTestBase: type = Sticker.REGULAR custom_emoji_id = "ThisIsSuchACustomEmojiID" needs_repainting = True + mask_position = MaskPosition(point="forehead", x_shift=0.1, y_shift=-0.1, scale=1.0) + thumbnail = PhotoSize( + file_id="1", + file_unique_id="1", + width=thumb_width, + height=thumb_height, + file_size=thumb_file_size, + ) sticker_file_id = "5a3128a4d2a04750b5b58397f3b5e812" sticker_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" premium_animation = File("this_is_an_id", "this_is_an_unique_id") + sticker = Sticker( + file_id=sticker_file_id, + file_unique_id=sticker_file_unique_id, + width=width, + height=height, + is_animated=is_animated, + is_video=is_video, + type=type, + emoji=emoji, + file_size=file_size, + mask_position=mask_position, + premium_animation=premium_animation, + custom_emoji_id=custom_emoji_id, + thumbnail=thumbnail, + needs_repainting=needs_repainting, + ) class TestStickerWithoutRequest(StickerTestBase): @@ -125,22 +149,8 @@ def test_to_dict(self, sticker): assert sticker_dict["type"] == sticker.type assert sticker_dict["needs_repainting"] == sticker.needs_repainting - def test_de_json(self, offline_bot, sticker): - json_dict = { - "file_id": self.sticker_file_id, - "file_unique_id": self.sticker_file_unique_id, - "width": self.width, - "height": self.height, - "is_animated": self.is_animated, - "is_video": self.is_video, - "thumbnail": sticker.thumbnail.to_dict(), - "emoji": self.emoji, - "file_size": self.file_size, - "premium_animation": self.premium_animation.to_dict(), - "type": self.type, - "custom_emoji_id": self.custom_emoji_id, - "needs_repainting": self.needs_repainting, - } + def test_de_json(self, offline_bot): + json_dict = self.sticker.to_dict() json_sticker = Sticker.de_json(json_dict, offline_bot) assert json_sticker.api_kwargs == {} @@ -152,7 +162,7 @@ def test_de_json(self, offline_bot, sticker): assert json_sticker.is_video == self.is_video assert json_sticker.emoji == self.emoji assert json_sticker.file_size == self.file_size - assert json_sticker.thumbnail == sticker.thumbnail + assert json_sticker.thumbnail == self.thumbnail assert json_sticker.premium_animation == self.premium_animation assert json_sticker.type == self.type assert json_sticker.custom_emoji_id == self.custom_emoji_id @@ -494,6 +504,13 @@ class StickerSetTestBase: sticker_type = Sticker.REGULAR contains_masks = True thumbnail = PhotoSize("thumb_file_id", "thumb_file_un_id", 100, 100, False) + sticker_set = StickerSet( + name, + title=title, + stickers=stickers, + sticker_type=sticker_type, + thumbnail=thumbnail, + ) class TestStickerSetWithoutRequest(StickerSetTestBase): @@ -503,22 +520,15 @@ def test_slot_behaviour(self): assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" - def test_de_json(self, offline_bot, sticker): - name = f"test_by_{offline_bot.username}" - json_dict = { - "name": name, - "title": self.title, - "stickers": [x.to_dict() for x in self.stickers], - "thumbnail": sticker.thumbnail.to_dict(), - "sticker_type": self.sticker_type, - "contains_masks": self.contains_masks, - } + def test_de_json(self, offline_bot): + json_dict = self.sticker_set.to_dict() + json_dict["contains_masks"] = self.contains_masks sticker_set = StickerSet.de_json(json_dict, offline_bot) - assert sticker_set.name == name + assert sticker_set.name == self.name assert sticker_set.title == self.title assert sticker_set.stickers == tuple(self.stickers) - assert sticker_set.thumbnail == sticker.thumbnail + assert sticker_set.thumbnail == self.thumbnail assert sticker_set.sticker_type == self.sticker_type assert sticker_set.api_kwargs == {"contains_masks": self.contains_masks}