Skip to content

Make (most) objects comparable #1724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions telegram/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
except ImportError:
import json

import warnings


class TelegramObject:
"""Base class for most telegram objects."""
Expand Down Expand Up @@ -73,6 +75,12 @@ def to_dict(self):

def __eq__(self, other):
if isinstance(other, self.__class__):
if self._id_attrs == ():
warnings.warn("Objects of type {} can not be meaningfully tested for "
"equivalence.".format(self.__class__.__name__))
if other._id_attrs == ():
warnings.warn("Objects of type {} can not be meaningfully tested for "
"equivalence.".format(other.__class__.__name__))
return self._id_attrs == other._id_attrs
return super().__eq__(other) # pylint: disable=no-member

Expand Down
5 changes: 5 additions & 0 deletions telegram/botcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class BotCommand(TelegramObject):
"""
This object represents a bot command.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`command` and :attr:`description` are equal.

Attributes:
command (:obj:`str`): Text of the command.
description (:obj:`str`): Description of the command.
Expand All @@ -38,6 +41,8 @@ def __init__(self, command, description, **kwargs):
self.command = command
self.description = description

self._id_attrs = (self.command, self.description)

@classmethod
def de_json(cls, data, bot):
if not data:
Expand Down
3 changes: 3 additions & 0 deletions telegram/callbackquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class CallbackQuery(TelegramObject):
:attr:`message` will be present. If the button was attached to a message sent via the bot (in
inline mode), the field :attr:`inline_message_id` will be present.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.

Note:
* In Python `from` is a reserved word, use `from_user` instead.
* Exactly one of the fields :attr:`data` or :attr:`game_short_name` will be present.
Expand Down
3 changes: 3 additions & 0 deletions telegram/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
class Chat(TelegramObject):
"""This object represents a chat.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.

Attributes:
id (:obj:`int`): Unique identifier for this chat.
type (:obj:`str`): Type of chat.
Expand Down
3 changes: 3 additions & 0 deletions telegram/chatmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
class ChatMember(TelegramObject):
"""This object contains information about one member of the chat.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user` and :attr:`status` are equal.

Attributes:
user (:class:`telegram.User`): Information about the user.
status (:obj:`str`): The member's status in the chat.
Expand Down
16 changes: 16 additions & 0 deletions telegram/chatpermissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
class ChatPermissions(TelegramObject):
"""Describes actions that a non-administrator user is allowed to take in a chat.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`can_send_messages`, :attr:`can_send_media_messages`,
:attr:`can_send_polls`, :attr:`can_send_other_messages`, :attr:`can_add_web_page_previews`,
:attr:`can_change_info`, :attr:`can_invite_users` and :attr:`can_pin_message` are equal.

Note:
Though not stated explicitly in the offical docs, Telegram changes not only the permissions
that are set, but also sets all the others to :obj:`False`. However, since not documented,
Expand Down Expand Up @@ -84,6 +89,17 @@ def __init__(self, can_send_messages=None, can_send_media_messages=None, can_sen
self.can_invite_users = can_invite_users
self.can_pin_messages = can_pin_messages

self._id_attrs = (
self.can_send_messages,
self.can_send_media_messages,
self.can_send_polls,
self.can_send_other_messages,
self.can_add_web_page_previews,
self.can_change_info,
self.can_invite_users,
self.can_pin_messages
)

@classmethod
def de_json(cls, data, bot):
if not data:
Expand Down
3 changes: 3 additions & 0 deletions telegram/choseninlineresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class ChosenInlineResult(TelegramObject):
Represents a result of an inline query that was chosen by the user and sent to their chat
partner.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`result_id` is equal.

Note:
In Python `from` is a reserved word, use `from_user` instead.

Expand Down
5 changes: 5 additions & 0 deletions telegram/dice.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class Dice(TelegramObject):
emoji. (The singular form of "dice" is "die". However, PTB mimics the Telegram API, which uses
the term "dice".)

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`value` and :attr:`emoji` are equal.

Note:
If :attr:`emoji` is "🎯", a value of 6 currently represents a bullseye, while a value of 1
indicates that the dartboard was missed. However, this behaviour is undocumented and might
Expand All @@ -48,6 +51,8 @@ def __init__(self, value, emoji, **kwargs):
self.value = value
self.emoji = emoji

self._id_attrs = (self.value, self.emoji)

@classmethod
def de_json(cls, data, bot):
if not data:
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Animation(TelegramObject):
"""This object represents an animation file to be displayed in the message containing a game.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): File identifier.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Audio(TelegramObject):
"""This object represents an audio file to be treated as music by the Telegram clients.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
4 changes: 4 additions & 0 deletions telegram/files/chatphoto.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
class ChatPhoto(TelegramObject):
"""This object represents a chat photo.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`small_file_unique_id` and :attr:`big_file_unique_id` are
equal.

Attributes:
small_file_id (:obj:`str`): File identifier of small (160x160) chat photo.
This file_id can be used only for photo download and only for as long
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Contact(TelegramObject):
"""This object represents a phone contact.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`phone_number` is equal.

Attributes:
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Document(TelegramObject):
"""This object represents a general file (as opposed to photos, voice messages and audio files).

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique file identifier.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class File(TelegramObject):
:attr:`download`. It is guaranteed that the link will be valid for at least 1 hour. When the
link expires, a new one can be requested by calling getFile.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Note:
Maximum file size to download is 20 MB

Expand Down
3 changes: 3 additions & 0 deletions telegram/files/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Location(TelegramObject):
"""This object represents a point on the map.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`longitute` and :attr:`latitude` are equal.

Attributes:
longitude (:obj:`float`): Longitude as defined by sender.
latitude (:obj:`float`): Latitude as defined by sender.
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/photosize.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class PhotoSize(TelegramObject):
"""This object represents one size of a photo or a file/sticker thumbnail.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
12 changes: 12 additions & 0 deletions telegram/files/sticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Sticker(TelegramObject):
"""This object represents a sticker.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down Expand Up @@ -133,6 +136,9 @@ def get_file(self, timeout=None, api_kwargs=None):
class StickerSet(TelegramObject):
"""This object represents a sticker set.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`name` is equal.

Attributes:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
Expand Down Expand Up @@ -188,6 +194,10 @@ def to_dict(self):
class MaskPosition(TelegramObject):
"""This object describes the position on faces where a mask should be placed by default.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`point`, :attr:`x_shift`, :attr:`y_shift` and, :attr:`scale`
are equal.

Attributes:
point (:obj:`str`): The part of the face relative to which the mask should be placed.
x_shift (:obj:`float`): Shift by X-axis measured in widths of the mask scaled to the face
Expand Down Expand Up @@ -226,6 +236,8 @@ def __init__(self, point, x_shift, y_shift, scale, **kwargs):
self.y_shift = y_shift
self.scale = scale

self._id_attrs = (self.point, self.x_shift, self.y_shift, self.scale)

@classmethod
def de_json(cls, data, bot):
if data is None:
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/venue.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Venue(TelegramObject):
"""This object represents a venue.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`location` and :attr:`title`are equal.

Attributes:
location (:class:`telegram.Location`): Venue location.
title (:obj:`str`): Name of the venue.
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Video(TelegramObject):
"""This object represents a video file.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/videonote.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class VideoNote(TelegramObject):
"""This object represents a video message (available in Telegram apps as of v.4.0).

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
3 changes: 3 additions & 0 deletions telegram/files/voice.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class Voice(TelegramObject):
"""This object represents a voice note.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.

Attributes:
file_id (:obj:`str`): Unique identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
Expand Down
5 changes: 5 additions & 0 deletions telegram/forcereply.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class ForceReply(ReplyMarkup):
extremely useful if you want to create user-friendly step-by-step interfaces without having
to sacrifice privacy mode.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`selective` is equal.

Attributes:
force_reply (:obj:`True`): Shows reply interface to the user.
selective (:obj:`bool`): Optional. Force reply from specific users only.
Expand All @@ -49,3 +52,5 @@ def __init__(self, force_reply=True, selective=False, **kwargs):
self.force_reply = bool(force_reply)
# Optionals
self.selective = bool(selective)

self._id_attrs = (self.selective,)
10 changes: 10 additions & 0 deletions telegram/games/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class Game(TelegramObject):
This object represents a game. Use BotFather to create and edit games, their short names will
act as unique identifiers.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`title`, :attr:`description` and :attr:`photo` are equal.

Attributes:
title (:obj:`str`): Title of the game.
description (:obj:`str`): Description of the game.
Expand Down Expand Up @@ -65,13 +68,17 @@ def __init__(self,
text_entities=None,
animation=None,
**kwargs):
# Required
self.title = title
self.description = description
self.photo = photo
# Optionals
self.text = text
self.text_entities = text_entities or list()
self.animation = animation

self._id_attrs = (self.title, self.description, self.photo)

@classmethod
def de_json(cls, data, bot):
if not data:
Expand Down Expand Up @@ -147,3 +154,6 @@ def parse_text_entities(self, types=None):
entity: self.parse_text_entity(entity)
for entity in self.text_entities if entity.type in types
}

def __hash__(self):
return hash((self.title, self.description, tuple(p for p in self.photo)))
5 changes: 5 additions & 0 deletions telegram/games/gamehighscore.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
class GameHighScore(TelegramObject):
"""This object represents one row of the high scores table for a game.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`position`, :attr:`user` and :attr:`score` are equal.

Attributes:
position (:obj:`int`): Position in high score table for the game.
user (:class:`telegram.User`): User.
Expand All @@ -41,6 +44,8 @@ def __init__(self, position, user, score):
self.user = user
self.score = score

self._id_attrs = (self.position, self.user, self.score)

@classmethod
def de_json(cls, data, bot):
if not data:
Expand Down
16 changes: 16 additions & 0 deletions telegram/inline/inlinekeyboardbutton.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
class InlineKeyboardButton(TelegramObject):
"""This object represents one button of an inline keyboard.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text`, :attr:`url`, :attr:`login_url`, :attr:`callback_data`,
:attr:`switch_inline_query`, :attr:`switch_inline_query_current_chat`, :attr:`callback_game`
and :attr:`pay` are equal.

Note:
You must use exactly one of the optional fields. Mind that :attr:`callback_game` is not
working as expected. Putting a game short name in it might, but is not guaranteed to work.
Expand Down Expand Up @@ -95,6 +100,17 @@ def __init__(self,
self.callback_game = callback_game
self.pay = pay

self._id_attrs = (
self.text,
self.url,
self.login_url,
self.callback_data,
self.switch_inline_query,
self.switch_inline_query_current_chat,
self.callback_game,
self.pay,
)

@classmethod
def de_json(cls, data, bot):
if not data:
Expand Down
Loading