diff --git a/telegram/_bot.py b/telegram/_bot.py index cb4e00c6b..a2929da28 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -8205,7 +8205,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. api_kwargs=api_kwargs, ) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: # skipcq: PYL-W0613 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} diff --git a/telegram/_chatinvitelink.py b/telegram/_chatinvitelink.py index 3701b1d0a..f0ff6543c 100644 --- a/telegram/_chatinvitelink.py +++ b/telegram/_chatinvitelink.py @@ -151,9 +151,9 @@ class ChatInviteLink(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["expire_date"] = to_timestamp(self.expire_date) diff --git a/telegram/_chatjoinrequest.py b/telegram/_chatjoinrequest.py index 4edb03a90..2869df129 100644 --- a/telegram/_chatjoinrequest.py +++ b/telegram/_chatjoinrequest.py @@ -103,9 +103,9 @@ class ChatJoinRequest(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["date"] = to_timestamp(self.date) diff --git a/telegram/_chatmember.py b/telegram/_chatmember.py index 7d8f24ced..01b40f7e1 100644 --- a/telegram/_chatmember.py +++ b/telegram/_chatmember.py @@ -123,9 +123,9 @@ class ChatMember(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) if data.get("until_date", False): data["until_date"] = to_timestamp(data["until_date"]) diff --git a/telegram/_chatmemberupdated.py b/telegram/_chatmemberupdated.py index cceb469c7..e0abc5f03 100644 --- a/telegram/_chatmemberupdated.py +++ b/telegram/_chatmemberupdated.py @@ -122,9 +122,9 @@ class ChatMemberUpdated(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) # Required data["date"] = to_timestamp(self.date) diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index 1452135b6..3174aca58 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -90,9 +90,9 @@ class InputMedia(TelegramObject): self.caption_entities = caption_entities self.parse_mode = parse_mode - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) if self.caption_entities: data["caption_entities"] = [ce.to_dict() for ce in self.caption_entities] diff --git a/telegram/_files/sticker.py b/telegram/_files/sticker.py index dfd23b6ab..64e42c340 100644 --- a/telegram/_files/sticker.py +++ b/telegram/_files/sticker.py @@ -282,9 +282,9 @@ class StickerSet(TelegramObject): return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["stickers"] = [s.to_dict() for s in data.get("stickers")] # type: ignore[union-attr] diff --git a/telegram/_games/game.py b/telegram/_games/game.py index 6fc191fda..91d31b17e 100644 --- a/telegram/_games/game.py +++ b/telegram/_games/game.py @@ -117,9 +117,9 @@ class Game(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["photo"] = [p.to_dict() for p in self.photo] if self.text_entities: diff --git a/telegram/_inline/inlinekeyboardmarkup.py b/telegram/_inline/inlinekeyboardmarkup.py index 39b96da96..52c398288 100644 --- a/telegram/_inline/inlinekeyboardmarkup.py +++ b/telegram/_inline/inlinekeyboardmarkup.py @@ -68,9 +68,9 @@ class InlineKeyboardMarkup(TelegramObject): self._id_attrs = (self.inline_keyboard,) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["inline_keyboard"] = [] for inline_keyboard in self.inline_keyboard: diff --git a/telegram/_inline/inlinequeryresult.py b/telegram/_inline/inlinequeryresult.py index 60da54ad5..7ccf3c00b 100644 --- a/telegram/_inline/inlinequeryresult.py +++ b/telegram/_inline/inlinequeryresult.py @@ -54,9 +54,9 @@ class InlineQueryResult(TelegramObject): self._id_attrs = (self.id,) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) # pylint: disable=no-member if ( diff --git a/telegram/_inline/inputinvoicemessagecontent.py b/telegram/_inline/inputinvoicemessagecontent.py index a39952ed8..fbc7f3c33 100644 --- a/telegram/_inline/inputinvoicemessagecontent.py +++ b/telegram/_inline/inputinvoicemessagecontent.py @@ -228,9 +228,9 @@ class InputInvoiceMessageContent(InputMessageContent): ) ) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["prices"] = [price.to_dict() for price in self.prices] diff --git a/telegram/_inline/inputtextmessagecontent.py b/telegram/_inline/inputtextmessagecontent.py index aac335ca1..71c220830 100644 --- a/telegram/_inline/inputtextmessagecontent.py +++ b/telegram/_inline/inputtextmessagecontent.py @@ -84,9 +84,9 @@ class InputTextMessageContent(InputMessageContent): self._id_attrs = (self.message_text,) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) if self.entities: data["entities"] = [ce.to_dict() for ce in self.entities] diff --git a/telegram/_menubutton.py b/telegram/_menubutton.py index 0ecd6df51..2c28ed742 100644 --- a/telegram/_menubutton.py +++ b/telegram/_menubutton.py @@ -161,9 +161,9 @@ class MenuButtonWebApp(MenuButton): return super().de_json(data=data, bot=bot) # type: ignore[return-value] - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["web_app"] = self.web_app.to_dict() return data diff --git a/telegram/_message.py b/telegram/_message.py index e47aed4bd..e0c215f84 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -719,9 +719,9 @@ class Message(TelegramObject): return self._effective_attachment # type: ignore[return-value] - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) # Required data["date"] = to_timestamp(self.date) diff --git a/telegram/_passport/credentials.py b/telegram/_passport/credentials.py index 2c655c628..f7432ec19 100644 --- a/telegram/_passport/credentials.py +++ b/telegram/_passport/credentials.py @@ -404,9 +404,9 @@ class SecureValue(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["files"] = [p.to_dict() for p in self.files] # type: ignore[union-attr] data["translation"] = [p.to_dict() for p in self.translation] # type: ignore[union-attr] @@ -450,9 +450,9 @@ class DataCredentials(_CredentialsBase): def __init__(self, data_hash: str, secret: str, *, api_kwargs: JSONDict = None): super().__init__(hash=data_hash, secret=secret, api_kwargs=api_kwargs) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) del data["file_hash"] del data["hash"] @@ -479,9 +479,9 @@ class FileCredentials(_CredentialsBase): def __init__(self, file_hash: str, secret: str, *, api_kwargs: JSONDict = None): super().__init__(hash=file_hash, secret=secret, api_kwargs=api_kwargs) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) del data["data_hash"] del data["hash"] diff --git a/telegram/_passport/encryptedpassportelement.py b/telegram/_passport/encryptedpassportelement.py index 7ba7f057a..54281b7b8 100644 --- a/telegram/_passport/encryptedpassportelement.py +++ b/telegram/_passport/encryptedpassportelement.py @@ -245,9 +245,9 @@ class EncryptedPassportElement(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) if self.files: data["files"] = [p.to_dict() for p in self.files] diff --git a/telegram/_passport/passportdata.py b/telegram/_passport/passportdata.py index d67055d07..fbfafce21 100644 --- a/telegram/_passport/passportdata.py +++ b/telegram/_passport/passportdata.py @@ -81,9 +81,9 @@ class PassportData(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["data"] = [e.to_dict() for e in self.data] diff --git a/telegram/_payment/shippingoption.py b/telegram/_payment/shippingoption.py index 36dc8bf3e..c21c4d534 100644 --- a/telegram/_payment/shippingoption.py +++ b/telegram/_payment/shippingoption.py @@ -65,9 +65,9 @@ class ShippingOption(TelegramObject): self._id_attrs = (self.id,) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["prices"] = [p.to_dict() for p in self.prices] diff --git a/telegram/_poll.py b/telegram/_poll.py index 43c62652c..5ed9a1b1c 100644 --- a/telegram/_poll.py +++ b/telegram/_poll.py @@ -228,9 +228,9 @@ class Poll(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["options"] = [x.to_dict() for x in self.options] if self.explanation_entities: diff --git a/telegram/_replykeyboardmarkup.py b/telegram/_replykeyboardmarkup.py index 5e78bd5a1..c122670ee 100644 --- a/telegram/_replykeyboardmarkup.py +++ b/telegram/_replykeyboardmarkup.py @@ -119,9 +119,9 @@ class ReplyKeyboardMarkup(TelegramObject): self._id_attrs = (self.keyboard,) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["keyboard"] = [] for row in self.keyboard: diff --git a/telegram/_telegramobject.py b/telegram/_telegramobject.py index 34e9ba08a..a0bb9ebe1 100644 --- a/telegram/_telegramobject.py +++ b/telegram/_telegramobject.py @@ -20,6 +20,7 @@ import inspect import json from copy import deepcopy +from itertools import chain from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union from telegram._utils.types import JSONDict @@ -160,8 +161,8 @@ class TelegramObject: Args: include_private (:obj:`bool`): Whether the result should include private variables. - recursive (:obj:`bool`): If :obj:`True`, will convert any TelegramObjects (if found) in - the attributes to a dictionary. Else, preserves it as an object itself. + recursive (:obj:`bool`): If :obj:`True`, will convert any ``TelegramObjects`` (if + found) in the attributes to a dictionary. Else, preserves it as an object itself. remove_bot (:obj:`bool`): Whether the bot should be included in the result. Returns: @@ -169,28 +170,23 @@ class TelegramObject: """ data = {} - if not recursive: - try: - # __dict__ has attrs from superclasses, so no need to put in the for loop below - data.update(self.__dict__) - except AttributeError: - pass # We want to get all attributes for the class, using self.__slots__ only includes the # attributes used by that class itself, and not its superclass(es). Hence, we get its MRO # and then get their attributes. The `[:-1]` slice excludes the `object` class - for cls in self.__class__.__mro__[:-1]: - for key in cls.__slots__: # type: ignore[attr-defined] - if not include_private and key.startswith("_"): - continue + all_slots = (s for c in self.__class__.__mro__[:-1] for s in c.__slots__) # type: ignore + # chain the class's slots with the user defined subclass __dict__ (class has no slots) + for key in chain(self.__dict__, all_slots) if hasattr(self, "__dict__") else all_slots: + if not include_private and key.startswith("_"): + continue - value = getattr(self, key, None) - if value is not None: - if recursive and hasattr(value, "to_dict"): - data[key] = value.to_dict() # pylint: disable=no-member - else: - data[key] = value - elif not recursive: + value = getattr(self, key, None) + if value is not None: + if recursive and hasattr(value, "to_dict"): + data[key] = value.to_dict(recursive=True) # pylint: disable=no-member + else: data[key] = value + elif not recursive: + data[key] = value if recursive and data.get("from_user"): data["from"] = data.pop("from_user", None) @@ -279,16 +275,23 @@ class TelegramObject: """ return json.dumps(self.to_dict()) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """Gives representation of object as :obj:`dict`. .. versionchanged:: 20.0 Now includes all entries of :attr:`api_kwargs`. + Args: + recursive (:obj:`bool`, optional): If :obj:`True`, will convert any TelegramObjects + (if found) in the attributes to a dictionary. Else, preserves it as an object + itself. Defaults to :obj:`True`. + + .. versionadded:: 20.0 + Returns: :obj:`dict` """ - out = self._get_attrs(recursive=True) + out = self._get_attrs(recursive=recursive) out.update(out.pop("api_kwargs", {})) # type: ignore[call-overload] return out diff --git a/telegram/_userprofilephotos.py b/telegram/_userprofilephotos.py index 7a5136903..ee0e8ad35 100644 --- a/telegram/_userprofilephotos.py +++ b/telegram/_userprofilephotos.py @@ -69,9 +69,9 @@ class UserProfilePhotos(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) data["photos"] = [] for photo in self.photos: diff --git a/telegram/_videochat.py b/telegram/_videochat.py index afd750751..41bc25c64 100644 --- a/telegram/_videochat.py +++ b/telegram/_videochat.py @@ -121,9 +121,9 @@ class VideoChatParticipantsInvited(TelegramObject): data["users"] = User.de_list(data.get("users", []), bot) return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) if self.users is not None: data["users"] = [u.to_dict() for u in self.users] @@ -176,9 +176,9 @@ class VideoChatScheduled(TelegramObject): return super().de_json(data=data, bot=bot) - def to_dict(self) -> JSONDict: + def to_dict(self, recursive: bool = True) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() + data = super().to_dict(recursive=recursive) # Required data["start_date"] = to_timestamp(self.start_date) diff --git a/tests/test_telegramobject.py b/tests/test_telegramobject.py index 35596be11..5de88e6d3 100644 --- a/tests/test_telegramobject.py +++ b/tests/test_telegramobject.py @@ -138,6 +138,30 @@ class TestTelegramObject: to = TelegramObject(api_kwargs={"foo": "bar"}) assert to.to_dict() == {"foo": "bar"} + def test_to_dict_recursion(self): + class Recursive(TelegramObject): + __slots__ = ("recursive",) + + def __init__(self): + super().__init__() + self.recursive = "recursive" + + class SubClass(TelegramObject): + """This class doesn't have `__slots__`, so has `__dict__` instead.""" + + def __init__(self): + super().__init__() + self.subclass = Recursive() + + to = SubClass() + to_dict_no_recurse = to.to_dict(recursive=False) + assert to_dict_no_recurse + assert isinstance(to_dict_no_recurse["subclass"], Recursive) + to_dict_recurse = to.to_dict(recursive=True) + assert to_dict_recurse + assert isinstance(to_dict_recurse["subclass"], dict) + assert to_dict_recurse["subclass"]["recursive"] == "recursive" + def test_slot_behaviour(self, mro_slots): inst = TelegramObject() for attr in inst.__slots__: