Extend rich comparison of objects (#1724)

* Make most objects comparable

* ID attrs for PollAnswer

* fix test_game

* fix test_userprofilephotos

* update for API 4.7

* Warn on meaningless comparisons

* Update for API 4.8

* Address review

* Get started on docs, update Message._id_attrs

* Change PollOption & InputLocation

* Some more changes

* Even more changes
This commit is contained in:
Bibo-Joshi 2020-07-14 21:33:56 +02:00
parent 2381724b7c
commit 2d4d48b89d
80 changed files with 934 additions and 21 deletions

View file

@ -23,6 +23,8 @@ try:
except ImportError:
import json
import warnings
class TelegramObject:
"""Base class for most telegram objects."""
@ -73,6 +75,12 @@ class TelegramObject:
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

View file

@ -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.
@ -38,6 +41,8 @@ class BotCommand(TelegramObject):
self.command = command
self.description = description
self._id_attrs = (self.command, self.description)
@classmethod
def de_json(cls, data, bot):
if not data:

View file

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

View file

@ -26,6 +26,9 @@ from .chatpermissions import ChatPermissions
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.

View file

@ -25,6 +25,9 @@ from telegram.utils.helpers import to_timestamp, from_timestamp
class ChatMember(TelegramObject):
"""This object contains information about one member of a 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.

View file

@ -24,6 +24,11 @@ from telegram import TelegramObject
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 official docs, Telegram changes not only the
permissions that are set, but also sets all the others to :obj:`False`. However, since not
@ -84,6 +89,17 @@ class ChatPermissions(TelegramObject):
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:

View file

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

View file

@ -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
@ -48,6 +51,8 @@ class Dice(TelegramObject):
self.value = value
self.emoji = emoji
self._id_attrs = (self.value, self.emoji)
@classmethod
def de_json(cls, data, bot):
if not data:

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
class Animation(TelegramObject):
"""This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound).
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

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject, PhotoSize
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -23,6 +23,10 @@ from telegram import TelegramObject
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

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
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.

View file

@ -25,6 +25,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`): File identifier.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -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 :meth:`telegram.Bot.get_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.
Note:
Maximum file size to download is 20 MB.

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
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.

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -24,6 +24,9 @@ from telegram import PhotoSize, TelegramObject
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
@ -135,6 +138,9 @@ class Sticker(TelegramObject):
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.
@ -190,6 +196,10 @@ class StickerSet(TelegramObject):
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.
One of ``'forehead'``, ``'eyes'``, ``'mouth'``, or ``'chin'``.
@ -230,6 +240,8 @@ class MaskPosition(TelegramObject):
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:

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject, Location
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.

View file

@ -24,6 +24,9 @@ from telegram import PhotoSize, TelegramObject
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -24,6 +24,9 @@ from telegram import PhotoSize, TelegramObject
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -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, as if they manually selected
the bot's message and tapped 'Reply'.
@ -50,3 +53,5 @@ class ForceReply(ReplyMarkup):
self.force_reply = bool(force_reply)
# Optionals
self.selective = bool(selective)
self._id_attrs = (self.selective,)

View file

@ -28,6 +28,9 @@ class Game(TelegramObject):
This object represents a game. Use `BotFather <https://t.me/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.
@ -67,13 +70,17 @@ class Game(TelegramObject):
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:
@ -149,3 +156,6 @@ class Game(TelegramObject):
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)))

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject, User
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.
@ -41,6 +44,8 @@ class GameHighScore(TelegramObject):
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:

View file

@ -24,6 +24,11 @@ from telegram import TelegramObject
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.
@ -95,6 +100,17 @@ class InlineKeyboardButton(TelegramObject):
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:

View file

@ -25,6 +25,9 @@ class InlineKeyboardMarkup(ReplyMarkup):
"""
This object represents an inline keyboard that appears right next to the message it belongs to.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their the size of :attr:`inline_keyboard` and all the buttons are equal.
Attributes:
inline_keyboard (List[List[:class:`telegram.InlineKeyboardButton`]]): List of button rows,
each represented by a list of InlineKeyboardButton objects.
@ -109,3 +112,19 @@ class InlineKeyboardMarkup(ReplyMarkup):
"""
button_grid = [[button] for button in button_column]
return cls(button_grid, **kwargs)
def __eq__(self, other):
if isinstance(other, self.__class__):
if len(self.inline_keyboard) != len(other.inline_keyboard):
return False
for idx, row in enumerate(self.inline_keyboard):
if len(row) != len(other.inline_keyboard[idx]):
return False
for jdx, button in enumerate(row):
if button != other.inline_keyboard[idx][jdx]:
return False
return True
return super(InlineKeyboardMarkup, self).__eq__(other) # pylint: disable=no-member
def __hash__(self):
return hash(tuple(tuple(button for button in row) for row in self.inline_keyboard))

View file

@ -27,6 +27,9 @@ class InlineQuery(TelegramObject):
This object represents an incoming inline query. When the user sends an empty query, your bot
could return some default or trending results.
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.

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
class InlineQueryResult(TelegramObject):
"""Baseclass for the InlineQueryResult* classes.
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:
type (:obj:`str`): Type of the result.
id (:obj:`str`): Unique identifier for this result, 1-64 Bytes.

View file

@ -24,6 +24,9 @@ from telegram import InputMessageContent
class InputContactMessageContent(InputMessageContent):
"""Represents the content of a contact message to be sent as the result of an inline query.
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.
@ -48,3 +51,5 @@ class InputContactMessageContent(InputMessageContent):
# Optionals
self.last_name = last_name
self.vcard = vcard
self._id_attrs = (self.phone_number,)

View file

@ -25,11 +25,15 @@ class InputLocationMessageContent(InputMessageContent):
"""
Represents the content of a location message to be sent as the result of an inline query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`latitude` and :attr:`longitude` are equal.
Attributes:
latitude (:obj:`float`): Latitude of the location in degrees.
longitude (:obj:`float`): Longitude of the location in degrees.
live_period (:obj:`int`): Optional. Period in seconds for which the location can be
updated, should be between 60 and 86400.
updated.
Args:
latitude (:obj:`float`): Latitude of the location in degrees.
longitude (:obj:`float`): Longitude of the location in degrees.
@ -44,3 +48,5 @@ class InputLocationMessageContent(InputMessageContent):
self.latitude = latitude
self.longitude = longitude
self.live_period = live_period
self._id_attrs = (self.latitude, self.longitude)

View file

@ -26,6 +26,9 @@ class InputTextMessageContent(InputMessageContent):
"""
Represents the content of a text message to be sent as the result of an inline query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`message_text` is equal.
Attributes:
message_text (:obj:`str`): Text of the message to be sent, 1-4096 characters after entities
parsing.
@ -57,3 +60,5 @@ class InputTextMessageContent(InputMessageContent):
# Optionals
self.parse_mode = parse_mode
self.disable_web_page_preview = disable_web_page_preview
self._id_attrs = (self.message_text,)

View file

@ -24,6 +24,10 @@ from telegram import InputMessageContent
class InputVenueMessageContent(InputMessageContent):
"""Represents the content of a venue message to be sent as the result of an inline query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`latitude`, :attr:`longitude` and :attr:`title`
are equal.
Attributes:
latitude (:obj:`float`): Latitude of the location in degrees.
longitude (:obj:`float`): Longitude of the location in degrees.
@ -57,3 +61,9 @@ class InputVenueMessageContent(InputMessageContent):
# Optionals
self.foursquare_id = foursquare_id
self.foursquare_type = foursquare_type
self._id_attrs = (
self.latitude,
self.longitude,
self.title,
)

View file

@ -26,6 +26,10 @@ class KeyboardButton(TelegramObject):
This object represents one button of the reply keyboard. For simple text buttons String can be
used instead of this object to specify text of the button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text`, :attr:`request_contact`, :attr:`request_location` and
:attr:`request_poll` are equal.
Note:
Optional fields are mutually exclusive.
@ -63,3 +67,6 @@ class KeyboardButton(TelegramObject):
self.request_contact = request_contact
self.request_location = request_location
self.request_poll = request_poll
self._id_attrs = (self.text, self.request_contact, self.request_location,
self.request_poll)

View file

@ -25,6 +25,9 @@ class KeyboardButtonPollType(TelegramObject):
"""This object represents type of a poll, which is allowed to be created
and sent when the corresponding button is pressed.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
Attributes:
type (:obj:`str`): Optional. If :attr:`telegram.Poll.QUIZ` is passed, the user will be
allowed to create only polls in the quiz mode. If :attr:`telegram.Poll.REGULAR` is

View file

@ -29,6 +29,9 @@ class LoginUrl(TelegramObject):
Sample bot: `@discussbot <https://t.me/dicussbot>`_
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`url` is equal.
Attributes:
url (:obj:`str`): An HTTP URL to be opened with user authorization data.
forward_text (:obj:`str`): Optional. New text of the button in forwarded messages.

View file

@ -33,6 +33,9 @@ _UNDEFINED = object()
class Message(TelegramObject):
"""This object represents a message.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`message_id` and :attr:`chat` are equal.
Note:
* In Python `from` is a reserved word, use `from_user` instead.
@ -345,7 +348,7 @@ class Message(TelegramObject):
self.bot = bot
self.default_quote = default_quote
self._id_attrs = (self.message_id,)
self._id_attrs = (self.message_id, self.chat)
@property
def chat_id(self):

View file

@ -26,6 +26,9 @@ class MessageEntity(TelegramObject):
This object represents one special entity in a text message. For example, hashtags,
usernames, URLs, etc.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type`, :attr:`offset` and :attr`length` are equal.
Attributes:
type (:obj:`str`): Type of the entity.
offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.

View file

@ -98,6 +98,9 @@ class EncryptedCredentials(TelegramObject):
Telegram Passport Documentation for a complete description of the data decryption and
authentication processes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`data`, :attr:`hash` and :attr:`secret` are equal.
Attributes:
data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's
nonce, data hashes and secrets used for EncryptedPassportElement decryption and

View file

@ -29,6 +29,10 @@ class EncryptedPassportElement(TelegramObject):
Contains information about documents or other Telegram Passport elements shared with the bot
by the user. The data has been automatically decrypted by python-telegram-bot.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type`, :attr:`data`, :attr:`phone_number`, :attr:`email`,
:attr:`files`, :attr:`front_side`, :attr:`reverse_side` and :attr:`selfie` are equal.
Attributes:
type (:obj:`str`): Element type. One of "personal_details", "passport", "driver_license",
"identity_card", "internal_passport", "address", "utility_bill", "bank_statement",

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
class PassportElementError(TelegramObject):
"""Baseclass for the PassportElementError* classes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source` and :attr:`type` are equal.
Attributes:
source (:obj:`str`): Error source.
type (:obj:`str`): The section of the user's Telegram Passport which has the error.
@ -50,6 +53,10 @@ class PassportElementErrorDataField(PassportElementError):
Represents an issue in one of the data fields that was provided by the user. The error is
considered resolved when the field's value changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`field_name`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the error, one of
"personal_details", "passport", "driver_license", "identity_card", "internal_passport",
@ -88,6 +95,10 @@ class PassportElementErrorFile(PassportElementError):
Represents an issue with a document scan. The error is considered resolved when the file with
the document scan changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
@ -122,11 +133,15 @@ class PassportElementErrorFiles(PassportElementError):
Represents an issue with a list of scans. The error is considered resolved when the file with
the document scan changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hashes`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
"temporary_registration".
file_hash (:obj:`str`): Base64-encoded file hash.
file_hashes (List[:obj:`str`]): List of base64-encoded file hashes.
message (:obj:`str`): Error message.
Args:
@ -157,6 +172,10 @@ class PassportElementErrorFrontSide(PassportElementError):
Represents an issue with the front side of a document. The error is considered resolved when
the file with the front side of the document changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
"passport", "driver_license", "identity_card", "internal_passport".
@ -191,6 +210,10 @@ class PassportElementErrorReverseSide(PassportElementError):
Represents an issue with the front side of a document. The error is considered resolved when
the file with the reverse side of the document changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
"passport", "driver_license", "identity_card", "internal_passport".
@ -225,6 +248,10 @@ class PassportElementErrorSelfie(PassportElementError):
Represents an issue with the selfie with a document. The error is considered resolved when
the file with the selfie changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
"passport", "driver_license", "identity_card", "internal_passport".
@ -257,6 +284,10 @@ class PassportElementErrorTranslationFile(PassportElementError):
Represents an issue with one of the files that constitute the translation of a document.
The error is considered resolved when the file changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of "passport", "driver_license", "identity_card", "internal_passport",
@ -293,12 +324,16 @@ class PassportElementErrorTranslationFiles(PassportElementError):
Represents an issue with the translated version of a document. The error is considered
resolved when a file with the document translation change.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hashes`, :attr:`data_hash`
and :attr:`message` are equal.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
"temporary_registration"
file_hash (:obj:`str`): Base64-encoded file hash.
file_hashes (List[:obj:`str`]): List of base64-encoded file hashes.
message (:obj:`str`): Error message.
Args:
@ -330,6 +365,10 @@ class PassportElementErrorUnspecified(PassportElementError):
Represents an issue in an unspecified place. The error is considered resolved when new
data is added.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source`, :attr:`type`, :attr:`element_hash`,
:attr:`data_hash` and :attr:`message` are equal.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue.
element_hash (:obj:`str`): Base64-encoded element hash.

View file

@ -26,6 +26,9 @@ class PassportFile(TelegramObject):
This object represents a file uploaded to Telegram Passport. Currently all Telegram Passport
files are in JPEG format when decrypted and don't exceed 10MB.
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`): Identifier for this file.
file_unique_id (:obj:`str`): Unique identifier for this file, which

View file

@ -24,6 +24,10 @@ from telegram import TelegramObject
class Invoice(TelegramObject):
"""This object contains basic information about an invoice.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`title`, :attr:`description`, :attr:`start_parameter`,
:attr:`currency` and :attr:`total_amount` are equal.
Attributes:
title (:obj:`str`): Product name.
description (:obj:`str`): Product description.
@ -54,6 +58,14 @@ class Invoice(TelegramObject):
self.currency = currency
self.total_amount = total_amount
self._id_attrs = (
self.title,
self.description,
self.start_parameter,
self.currency,
self.total_amount,
)
@classmethod
def de_json(cls, data, bot):
if not data:

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
class LabeledPrice(TelegramObject):
"""This object represents a portion of the price for goods or services.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`label` and :attr:`amount` are equal.
Attributes:
label (:obj:`str`): Portion label.
amount (:obj:`int`): Price of the product in the smallest units of the currency.
@ -43,3 +46,5 @@ class LabeledPrice(TelegramObject):
def __init__(self, label, amount, **kwargs):
self.label = label
self.amount = amount
self._id_attrs = (self.label, self.amount)

View file

@ -24,6 +24,10 @@ from telegram import TelegramObject, ShippingAddress
class OrderInfo(TelegramObject):
"""This object represents information about an order.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`name`, :attr:`phone_number`, :attr:`email` and
:attr:`shipping_address` are equal.
Attributes:
name (:obj:`str`): Optional. User name.
phone_number (:obj:`str`): Optional. User's phone number.
@ -45,6 +49,8 @@ class OrderInfo(TelegramObject):
self.email = email
self.shipping_address = shipping_address
self._id_attrs = (self.name, self.phone_number, self.email, self.shipping_address)
@classmethod
def de_json(cls, data, bot):
if not data:

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject, User, OrderInfo
class PreCheckoutQuery(TelegramObject):
"""This object contains information about an incoming pre-checkout query.
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.

View file

@ -24,6 +24,10 @@ from telegram import TelegramObject
class ShippingAddress(TelegramObject):
"""This object represents a Telegram ShippingAddress.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`country_code`, :attr:`state`, :attr:`city`,
:attr:`street_line1`, :attr:`street_line2` and :attr:`post_cod` are equal.
Attributes:
country_code (:obj:`str`): ISO 3166-1 alpha-2 country code.
state (:obj:`str`): State, if applicable.

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject
class ShippingOption(TelegramObject):
"""This object represents one shipping option.
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:`str`): Shipping option identifier.
title (:obj:`str`): Option title.

View file

@ -24,6 +24,9 @@ from telegram import TelegramObject, User, ShippingAddress
class ShippingQuery(TelegramObject):
"""This object contains information about an incoming shipping query.
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.

View file

@ -24,6 +24,10 @@ from telegram import TelegramObject, OrderInfo
class SuccessfulPayment(TelegramObject):
"""This object contains basic information about a successful payment.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`telegram_payment_charge_id` and
:attr:`provider_payment_charge_id` are equal.
Attributes:
currency (:obj:`str`): Three-letter ISO 4217 currency code.
total_amount (:obj:`int`): Total price in the smallest units of the currency.

View file

@ -29,6 +29,9 @@ class PollOption(TelegramObject):
"""
This object contains information about one answer option in a poll.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` and :attr:`voter_count` are equal.
Attributes:
text (:obj:`str`): Option text, 1-100 characters.
voter_count (:obj:`int`): Number of users that voted for this option.
@ -43,6 +46,8 @@ class PollOption(TelegramObject):
self.text = text
self.voter_count = voter_count
self._id_attrs = (self.text, self.voter_count)
@classmethod
def de_json(cls, data, bot):
if not data:
@ -55,6 +60,9 @@ class PollAnswer(TelegramObject):
"""
This object represents an answer of a user in a non-anonymous poll.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`poll_id`, :attr:`user` and :attr:`options_ids` are equal.
Attributes:
poll_id (:obj:`str`): Unique poll identifier.
user (:class:`telegram.User`): The user, who changed the answer to the poll.
@ -72,6 +80,8 @@ class PollAnswer(TelegramObject):
self.user = user
self.option_ids = option_ids
self._id_attrs = (self.poll_id, self.user, tuple(self.option_ids))
@classmethod
def de_json(cls, data, bot):
if not data:
@ -88,6 +98,9 @@ class Poll(TelegramObject):
"""
This object contains information about a poll.
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:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.

View file

@ -19,11 +19,15 @@
"""This module contains an object that represents a Telegram ReplyKeyboardMarkup."""
from telegram import ReplyMarkup
from .keyboardbutton import KeyboardButton
class ReplyKeyboardMarkup(ReplyMarkup):
"""This object represents a custom keyboard with reply options.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their the size of :attr:`keyboard` and all the buttons are equal.
Attributes:
keyboard (List[List[:class:`telegram.KeyboardButton` | :obj:`str`]]): Array of button rows.
resize_keyboard (:obj:`bool`): Optional. Requests clients to resize the keyboard.
@ -66,7 +70,16 @@ class ReplyKeyboardMarkup(ReplyMarkup):
selective=False,
**kwargs):
# Required
self.keyboard = keyboard
self.keyboard = []
for row in keyboard:
r = []
for button in row:
if hasattr(button, 'to_dict'):
r.append(button) # telegram.KeyboardButton
else:
r.append(KeyboardButton(button)) # str
self.keyboard.append(r)
# Optionals
self.resize_keyboard = bool(resize_keyboard)
self.one_time_keyboard = bool(one_time_keyboard)
@ -211,3 +224,22 @@ class ReplyKeyboardMarkup(ReplyMarkup):
one_time_keyboard=one_time_keyboard,
selective=selective,
**kwargs)
def __eq__(self, other):
if isinstance(other, self.__class__):
if len(self.keyboard) != len(other.keyboard):
return False
for idx, row in enumerate(self.keyboard):
if len(row) != len(other.keyboard[idx]):
return False
for jdx, button in enumerate(row):
if button != other.keyboard[idx][jdx]:
return False
return True
return super(ReplyKeyboardMarkup, self).__eq__(other) # pylint: disable=no-member
def __hash__(self):
return hash((
tuple(tuple(button for button in row) for row in self.keyboard),
self.resize_keyboard, self.one_time_keyboard, self.selective
))

View file

@ -26,6 +26,9 @@ from telegram.poll import PollAnswer
class Update(TelegramObject):
"""This object represents an incoming update.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`update_id` is equal.
Note:
At most one of the optional parameters can be present in any given update.

View file

@ -27,6 +27,9 @@ from telegram.utils.helpers import mention_markdown as util_mention_markdown
class User(TelegramObject):
"""This object represents a Telegram user or bot.
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 user or bot.
is_bot (:obj:`bool`): :obj:`True`, if this user is a bot.

View file

@ -24,6 +24,9 @@ from telegram import PhotoSize, TelegramObject
class UserProfilePhotos(TelegramObject):
"""This object represent a user's profile pictures.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`total_count` and :attr:`photos` are equal.
Attributes:
total_count (:obj:`int`): Total number of profile pictures.
photos (List[List[:class:`telegram.PhotoSize`]]): Requested profile pictures.
@ -40,6 +43,8 @@ class UserProfilePhotos(TelegramObject):
self.total_count = int(total_count)
self.photos = photos
self._id_attrs = (self.total_count, self.photos)
@classmethod
def de_json(cls, data, bot):
if not data:
@ -59,3 +64,6 @@ class UserProfilePhotos(TelegramObject):
data['photos'].append([x.to_dict() for x in photo])
return data
def __hash__(self):
return hash(tuple(tuple(p for p in photo) for photo in self.photos))

View file

@ -26,6 +26,11 @@ class WebhookInfo(TelegramObject):
Contains information about the current status of a webhook.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`url`, :attr:`has_custom_certificate`,
:attr:`pending_update_count`, :attr:`last_error_date`, :attr:`last_error_message`,
:attr:`max_connections` and :attr:`allowed_updates` are equal.
Attributes:
url (:obj:`str`): Webhook URL.
has_custom_certificate (:obj:`bool`): If a custom certificate was provided for webhook.
@ -71,6 +76,16 @@ class WebhookInfo(TelegramObject):
self.max_connections = max_connections
self.allowed_updates = allowed_updates
self._id_attrs = (
self.url,
self.has_custom_certificate,
self.pending_update_count,
self.last_error_date,
self.last_error_message,
self.max_connections,
self.allowed_updates,
)
@classmethod
def de_json(cls, data, bot):
if not data:

View file

@ -19,7 +19,7 @@
import pytest
from telegram import BotCommand
from telegram import BotCommand, Dice
@pytest.fixture(scope="class")
@ -46,3 +46,22 @@ class TestBotCommand:
assert isinstance(bot_command_dict, dict)
assert bot_command_dict['command'] == bot_command.command
assert bot_command_dict['description'] == bot_command.description
def test_equality(self):
a = BotCommand('start', 'some description')
b = BotCommand('start', 'some description')
c = BotCommand('start', 'some other description')
d = BotCommand('hepl', 'some description')
e = Dice(4, 'emoji')
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import ChatPermissions
from telegram import ChatPermissions, User
@pytest.fixture(scope="class")
@ -77,3 +77,34 @@ class TestChatPermissions:
assert permissions_dict['can_change_info'] == chat_permissions.can_change_info
assert permissions_dict['can_invite_users'] == chat_permissions.can_invite_users
assert permissions_dict['can_pin_messages'] == chat_permissions.can_pin_messages
def test_equality(self):
a = ChatPermissions(
can_send_messages=True,
can_send_media_messages=True,
can_send_polls=True,
can_send_other_messages=False
)
b = ChatPermissions(
can_send_polls=True,
can_send_other_messages=False,
can_send_messages=True,
can_send_media_messages=True,
)
c = ChatPermissions(
can_send_messages=False,
can_send_media_messages=True,
can_send_polls=True,
can_send_other_messages=False
)
d = User(123, '', False)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import Dice
from telegram import Dice, BotCommand
@pytest.fixture(scope="class",
@ -46,3 +46,22 @@ class TestDice:
assert isinstance(dice_dict, dict)
assert dice_dict['value'] == dice.value
assert dice_dict['emoji'] == dice.emoji
def test_equality(self):
a = Dice(3, '🎯')
b = Dice(3, '🎯')
c = Dice(3, '🎲')
d = Dice(4, '🎯')
e = BotCommand('start', 'description')
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)

View file

@ -20,7 +20,7 @@
import pytest
from flaky import flaky
from telegram import ForceReply
from telegram import ForceReply, ReplyKeyboardRemove
@pytest.fixture(scope='class')
@ -49,3 +49,18 @@ class TestForceReply:
assert isinstance(force_reply_dict, dict)
assert force_reply_dict['force_reply'] == force_reply.force_reply
assert force_reply_dict['selective'] == force_reply.selective
def test_equality(self):
a = ForceReply(True, False)
b = ForceReply(False, False)
c = ForceReply(True, True)
d = ReplyKeyboardRemove()
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -95,3 +95,20 @@ class TestGame:
assert game.parse_text_entities(MessageEntity.URL) == {entity: 'http://google.com'}
assert game.parse_text_entities() == {entity: 'http://google.com', entity_2: 'h'}
def test_equality(self):
a = Game('title', 'description', [PhotoSize('Blah', 'unique_id', 640, 360, file_size=0)])
b = Game('title', 'description', [PhotoSize('Blah', 'unique_id', 640, 360, file_size=0)],
text='Here is a text')
c = Game('eltit', 'description', [PhotoSize('Blah', 'unique_id', 640, 360, file_size=0)],
animation=Animation('blah', 'unique_id', 320, 180, 1))
d = Animation('blah', 'unique_id', 320, 180, 1)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -51,3 +51,22 @@ class TestGameHighScore:
assert game_highscore_dict['position'] == game_highscore.position
assert game_highscore_dict['user'] == game_highscore.user.to_dict()
assert game_highscore_dict['score'] == game_highscore.score
def test_equality(self):
a = GameHighScore(1, User(2, 'test user', False), 42)
b = GameHighScore(1, User(2, 'test user', False), 42)
c = GameHighScore(2, User(2, 'test user', False), 42)
d = GameHighScore(1, User(3, 'test user', False), 42)
e = User(3, 'test user', False)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)

View file

@ -92,3 +92,26 @@ class TestInlineKeyboardButton:
== self.switch_inline_query_current_chat)
assert inline_keyboard_button.callback_game == self.callback_game
assert inline_keyboard_button.pay == self.pay
def test_equality(self):
a = InlineKeyboardButton('text', callback_data='data')
b = InlineKeyboardButton('text', callback_data='data')
c = InlineKeyboardButton('texts', callback_data='data')
d = InlineKeyboardButton('text', callback_data='info')
e = InlineKeyboardButton('text', url='http://google.com')
f = LoginUrl("http://google.com")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)

View file

@ -20,7 +20,7 @@
import pytest
from flaky import flaky
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyMarkup
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyMarkup, ReplyKeyboardMarkup
@pytest.fixture(scope='class')
@ -129,3 +129,52 @@ class TestInlineKeyboardMarkup:
assert keyboard[0][0].text == 'start'
assert keyboard[0][0].url == 'http://google.com'
def test_equality(self):
a = InlineKeyboardMarkup.from_column([
InlineKeyboardButton(label, callback_data='data')
for label in ['button1', 'button2', 'button3']
])
b = InlineKeyboardMarkup.from_column([
InlineKeyboardButton(label, callback_data='data')
for label in ['button1', 'button2', 'button3']
])
c = InlineKeyboardMarkup.from_column([
InlineKeyboardButton(label, callback_data='data')
for label in ['button1', 'button2']
])
d = InlineKeyboardMarkup.from_column([
InlineKeyboardButton(label, callback_data=label)
for label in ['button1', 'button2', 'button3']
])
e = InlineKeyboardMarkup.from_column([
InlineKeyboardButton(label, url=label)
for label in ['button1', 'button2', 'button3']
])
f = InlineKeyboardMarkup([
[InlineKeyboardButton(label, callback_data='data')
for label in ['button1', 'button2']],
[InlineKeyboardButton(label, callback_data='data')
for label in ['button1', 'button2']],
[InlineKeyboardButton(label, callback_data='data')
for label in ['button1', 'button2']]
])
g = ReplyKeyboardMarkup.from_column(['button1', 'button2', 'button3'])
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)
assert a != g
assert hash(a) != hash(g)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import InputContactMessageContent
from telegram import InputContactMessageContent, User
@pytest.fixture(scope='class')
@ -49,3 +49,18 @@ class TestInputContactMessageContent:
== input_contact_message_content.first_name)
assert (input_contact_message_content_dict['last_name']
== input_contact_message_content.last_name)
def test_equality(self):
a = InputContactMessageContent('phone', 'first', last_name='last')
b = InputContactMessageContent('phone', 'first_name', vcard='vcard')
c = InputContactMessageContent('phone_number', 'first', vcard='vcard')
d = User(123, 'first', False)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import InputLocationMessageContent
from telegram import InputLocationMessageContent, Location
@pytest.fixture(scope='class')
@ -49,3 +49,18 @@ class TestInputLocationMessageContent:
== input_location_message_content.longitude)
assert (input_location_message_content_dict['live_period']
== input_location_message_content.live_period)
def test_equality(self):
a = InputLocationMessageContent(123, 456, 70)
b = InputLocationMessageContent(123, 456, 90)
c = InputLocationMessageContent(123, 457, 70)
d = Location(123, 456)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -50,3 +50,18 @@ class TestInputTextMessageContent:
== input_text_message_content.parse_mode)
assert (input_text_message_content_dict['disable_web_page_preview']
== input_text_message_content.disable_web_page_preview)
def test_equality(self):
a = InputTextMessageContent('text')
b = InputTextMessageContent('text', parse_mode=ParseMode.HTML)
c = InputTextMessageContent('label')
d = ParseMode.HTML
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import InputVenueMessageContent
from telegram import InputVenueMessageContent, Location
@pytest.fixture(scope='class')
@ -62,3 +62,22 @@ class TestInputVenueMessageContent:
== input_venue_message_content.foursquare_id)
assert (input_venue_message_content_dict['foursquare_type']
== input_venue_message_content.foursquare_type)
def test_equality(self):
a = InputVenueMessageContent(123, 456, 'title', 'address')
b = InputVenueMessageContent(123, 456, 'title', '')
c = InputVenueMessageContent(123, 456, 'title', 'address', foursquare_id=123)
d = InputVenueMessageContent(456, 123, 'title', 'address', foursquare_id=123)
e = Location(123, 456)
assert a == b
assert hash(a) == hash(b)
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)

View file

@ -120,3 +120,18 @@ class TestInvoice:
assert bot.send_invoice(chat_id, self.title, self.description, self.payload,
provider_token, self.start_parameter, self.currency,
self.prices, provider_data={'test_data': 123456789})
def test_equality(self):
a = Invoice('invoice', 'desc', 'start', 'EUR', 7)
b = Invoice('invoice', 'desc', 'start', 'EUR', 7)
c = Invoice('invoices', 'description', 'stop', 'USD', 8)
d = LabeledPrice('label', 5)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import KeyboardButton
from telegram import KeyboardButton, InlineKeyboardButton
from telegram.keyboardbuttonpolltype import KeyboardButtonPollType
@ -51,3 +51,18 @@ class TestKeyboardButton:
assert keyboard_button_dict['request_location'] == keyboard_button.request_location
assert keyboard_button_dict['request_contact'] == keyboard_button.request_contact
assert keyboard_button_dict['request_poll'] == keyboard_button.request_poll.to_dict()
def test_equality(self):
a = KeyboardButton('test', request_contact=True)
b = KeyboardButton('test', request_contact=True)
c = KeyboardButton('Test', request_location=True)
d = InlineKeyboardButton('test', callback_data='test')
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -19,7 +19,7 @@
import pytest
from telegram import LabeledPrice
from telegram import LabeledPrice, Location
@pytest.fixture(scope='class')
@ -41,3 +41,18 @@ class TestLabeledPrice:
assert isinstance(labeled_price_dict, dict)
assert labeled_price_dict['label'] == labeled_price.label
assert labeled_price_dict['amount'] == labeled_price.amount
def test_equality(self):
a = LabeledPrice('label', 100)
b = LabeledPrice('label', 100)
c = LabeledPrice('Label', 101)
d = Location(123, 456)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -888,7 +888,7 @@ class TestMessage:
id_ = 1
a = Message(id_, self.from_user, self.date, self.chat)
b = Message(id_, self.from_user, self.date, self.chat)
c = Message(id_, User(0, '', False), self.date, self.chat)
c = Message(id_, self.from_user, self.date, Chat(123, Chat.GROUP))
d = Message(0, self.from_user, self.date, self.chat)
e = Update(id_)
@ -896,8 +896,8 @@ class TestMessage:
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -56,3 +56,26 @@ class TestOrderInfo:
assert order_info_dict['phone_number'] == order_info.phone_number
assert order_info_dict['email'] == order_info.email
assert order_info_dict['shipping_address'] == order_info.shipping_address.to_dict()
def test_equality(self):
a = OrderInfo('name', 'number', 'mail',
ShippingAddress('GB', '', 'London', '12 Grimmauld Place', '', 'WC1'))
b = OrderInfo('name', 'number', 'mail',
ShippingAddress('GB', '', 'London', '12 Grimmauld Place', '', 'WC1'))
c = OrderInfo('name', 'number', 'mail',
ShippingAddress('GB', '', 'London', '13 Grimmauld Place', '', 'WC1'))
d = OrderInfo('name', 'number', 'e-mail',
ShippingAddress('GB', '', 'London', '12 Grimmauld Place', '', 'WC1'))
e = ShippingAddress('GB', '', 'London', '12 Grimmauld Place', '', 'WC1')
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)

View file

@ -467,7 +467,7 @@ class TestBasePersistence:
if isinstance(other, CustomClass):
# print(self.__dict__)
# print(other.__dict__)
return (self.bot == other.bot
return (self.bot is other.bot
and self.slotted_object == other.slotted_object
and self.list_ == other.list_
and self.tuple_ == other.tuple_

View file

@ -51,6 +51,25 @@ class TestPollOption:
assert poll_option_dict['text'] == poll_option.text
assert poll_option_dict['voter_count'] == poll_option.voter_count
def test_equality(self):
a = PollOption('text', 1)
b = PollOption('text', 1)
c = PollOption('text_1', 1)
d = PollOption('text', 2)
e = Poll(123, 'question', ['O1', 'O2'], 1, False, True, Poll.REGULAR, True)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@pytest.fixture(scope="class")
def poll_answer():
@ -83,6 +102,25 @@ class TestPollAnswer:
assert poll_answer_dict['user'] == poll_answer.user.to_dict()
assert poll_answer_dict['option_ids'] == poll_answer.option_ids
def test_equality(self):
a = PollAnswer(123, self.user, [2])
b = PollAnswer(123, User(1, 'first', False), [2])
c = PollAnswer(123, self.user, [1, 2])
d = PollAnswer(456, self.user, [2])
e = PollOption('Text', 1)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@pytest.fixture(scope='class')
def poll():
@ -181,3 +219,18 @@ class TestPoll:
assert poll.parse_explanation_entities(MessageEntity.URL) == {entity: 'http://google.com'}
assert poll.parse_explanation_entities() == {entity: 'http://google.com', entity_2: 'h'}
def test_equality(self):
a = Poll(123, 'question', ['O1', 'O2'], 1, False, True, Poll.REGULAR, True)
b = Poll(123, 'question', ['o1', 'o2'], 1, True, False, Poll.REGULAR, True)
c = Poll(456, 'question', ['o1', 'o2'], 1, True, False, Poll.REGULAR, True)
d = PollOption('Text', 1)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -20,7 +20,7 @@
import pytest
from flaky import flaky
from telegram import ReplyKeyboardMarkup, KeyboardButton
from telegram import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup
@pytest.fixture(scope='class')
@ -106,3 +106,28 @@ class TestReplyKeyboardMarkup:
assert (reply_keyboard_markup_dict['one_time_keyboard']
== reply_keyboard_markup.one_time_keyboard)
assert reply_keyboard_markup_dict['selective'] == reply_keyboard_markup.selective
def test_equality(self):
a = ReplyKeyboardMarkup.from_column(['button1', 'button2', 'button3'])
b = ReplyKeyboardMarkup.from_column([
KeyboardButton(text) for text in ['button1', 'button2', 'button3']
])
c = ReplyKeyboardMarkup.from_column(['button1', 'button2'])
d = ReplyKeyboardMarkup.from_column(['button1', 'button2', 'button3.1'])
e = ReplyKeyboardMarkup([['button1', 'button1'], ['button2'], ['button3.1']])
f = InlineKeyboardMarkup.from_column(['button1', 'button2', 'button3'])
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)

View file

@ -50,7 +50,7 @@ class TestShippingQuery:
assert shipping_query.invoice_payload == self.invoice_payload
assert shipping_query.from_user == self.from_user
assert shipping_query.shipping_address == self.shipping_address
assert shipping_query.bot == bot
assert shipping_query.bot is bot
def test_to_dict(self, shipping_query):
shipping_query_dict = shipping_query.to_dict()

View file

@ -449,3 +449,23 @@ class TestMaskPosition:
assert mask_position_dict['x_shift'] == mask_position.x_shift
assert mask_position_dict['y_shift'] == mask_position.y_shift
assert mask_position_dict['scale'] == mask_position.scale
def test_equality(self):
a = MaskPosition(self.point, self.x_shift, self.y_shift, self.scale)
b = MaskPosition(self.point, self.x_shift, self.y_shift, self.scale)
c = MaskPosition(MaskPosition.FOREHEAD, self.x_shift, self.y_shift, self.scale)
d = MaskPosition(self.point, 0, 0, self.scale)
e = Audio('', '', 0, None, None)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)

View file

@ -83,3 +83,27 @@ class TestTelegramObject:
subclass_instance = TelegramObjectSubclass()
assert subclass_instance.to_dict() == {'a': 1}
def test_meaningless_comparison(self, recwarn):
expected_warning = "Objects of type TGO can not be meaningfully tested for equivalence."
class TGO(TelegramObject):
pass
a = TGO()
b = TGO()
assert a == b
assert len(recwarn) == 2
assert str(recwarn[0].message) == expected_warning
assert str(recwarn[1].message) == expected_warning
def test_meaningful_comparison(self, recwarn):
class TGO(TelegramObject):
_id_attrs = (1,)
a = TGO()
b = TGO()
assert a == b
assert len(recwarn) == 0
assert b == a
assert len(recwarn) == 0

View file

@ -48,3 +48,18 @@ class TestUserProfilePhotos:
for ix, x in enumerate(user_profile_photos_dict['photos']):
for iy, y in enumerate(x):
assert y == user_profile_photos.photos[ix][iy].to_dict()
def test_equality(self):
a = UserProfilePhotos(2, self.photos)
b = UserProfilePhotos(2, self.photos)
c = UserProfilePhotos(1, [self.photos[0]])
d = PhotoSize('file_id1', 'unique_id', 512, 512)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

88
tests/test_webhookinfo.py Normal file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
import time
from telegram import WebhookInfo, LoginUrl
@pytest.fixture(scope='class')
def webhook_info():
return WebhookInfo(
url=TestWebhookInfo.url,
has_custom_certificate=TestWebhookInfo.has_custom_certificate,
pending_update_count=TestWebhookInfo.pending_update_count,
last_error_date=TestWebhookInfo.last_error_date,
max_connections=TestWebhookInfo.max_connections,
allowed_updates=TestWebhookInfo.allowed_updates,
)
class TestWebhookInfo(object):
url = "http://www.google.com"
has_custom_certificate = False
pending_update_count = 5
last_error_date = time.time()
max_connections = 42
allowed_updates = ['type1', 'type2']
def test_to_dict(self, webhook_info):
webhook_info_dict = webhook_info.to_dict()
assert isinstance(webhook_info_dict, dict)
assert webhook_info_dict['url'] == self.url
assert webhook_info_dict['pending_update_count'] == self.pending_update_count
assert webhook_info_dict['last_error_date'] == self.last_error_date
assert webhook_info_dict['max_connections'] == self.max_connections
assert webhook_info_dict['allowed_updates'] == self.allowed_updates
def test_equality(self):
a = WebhookInfo(
url=self.url,
has_custom_certificate=self.has_custom_certificate,
pending_update_count=self.pending_update_count,
last_error_date=self.last_error_date,
max_connections=self.max_connections,
)
b = WebhookInfo(
url=self.url,
has_custom_certificate=self.has_custom_certificate,
pending_update_count=self.pending_update_count,
last_error_date=self.last_error_date,
max_connections=self.max_connections,
)
c = WebhookInfo(
url="http://github.com",
has_custom_certificate=True,
pending_update_count=78,
last_error_date=0,
max_connections=1,
)
d = LoginUrl("text.com")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)