Add recursive Parameter to TelegramObject.to_dict() (#3276)

This commit is contained in:
Harshil 2022-10-19 14:01:55 +05:30 committed by GitHub
parent 210f9afd66
commit 24d390e1aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 97 additions and 70 deletions

View file

@ -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}

View file

@ -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)

View file

@ -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)

View file

@ -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"])

View file

@ -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)

View file

@ -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]

View file

@ -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]

View file

@ -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:

View file

@ -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:

View file

@ -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 (

View file

@ -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]

View file

@ -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]

View file

@ -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

View file

@ -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)

View file

@ -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"]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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:

View file

@ -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:

View file

@ -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,24 +170,19 @@ 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]
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
data[key] = value.to_dict(recursive=True) # pylint: disable=no-member
else:
data[key] = value
elif not recursive:
@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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__: