Rich Comparison for Bot (#2320)

* Make telegram.Bot comparable

Signed-off-by: starry69 <starry369126@outlook.com>

* Address review

Signed-off-by: starry69 <starry369126@outlook.com>

* Enhance tests & add docstring about comparison

Signed-off-by: starry69 <starry369126@outlook.com>

* Minor doc fix

* Extend tests

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
This commit is contained in:
Stɑrry Shivɑm 2021-01-17 13:53:36 +05:30 committed by GitHub
parent 7a3fd83570
commit b43a599e53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 41 deletions

3
.gitignore vendored
View file

@ -84,3 +84,6 @@ telegram.jpg
# Exclude .exrc file for Vim
.exrc
# virtual env
venv*

View file

@ -110,22 +110,6 @@ if TYPE_CHECKING:
RT = TypeVar('RT')
def info(func: Callable[..., RT]) -> Callable[..., RT]:
# pylint: disable=W0212
@functools.wraps(func)
def decorator(self: 'Bot', *args: Any, **kwargs: Any) -> RT:
if not self.bot:
self.get_me()
if self._commands is None:
self.get_my_commands()
result = func(self, *args, **kwargs)
return result
return decorator
def log(
func: Callable[..., RT], *args: Any, **kwargs: Any # pylint: disable=W0613
) -> Callable[..., RT]:
@ -144,6 +128,10 @@ def log(
class Bot(TelegramObject):
"""This object represents a Telegram Bot.
.. versionadded:: 13.2
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`bot` is equal.
Note:
Most bot methods have the argument ``api_kwargs`` which allows to pass arbitrary keywords
to the Telegram API. This can be used to access new features of the API before they were
@ -217,7 +205,7 @@ class Bot(TelegramObject):
self.base_url = str(base_url) + str(self.token)
self.base_file_url = str(base_file_url) + str(self.token)
self.bot: Optional[User] = None
self._bot: Optional[User] = None
self._commands: Optional[List[BotCommand]] = None
self._request = request or Request()
self.logger = logging.getLogger(__name__)
@ -302,68 +290,69 @@ class Bot(TelegramObject):
return token
@property # type: ignore
@info
@property
def bot(self) -> User:
""":class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`."""
if self._bot is None:
self._bot = self.get_me()
return self._bot
@property
def id(self) -> int:
""":obj:`int`: Unique identifier for this bot."""
return self.bot.id # type: ignore
return self.bot.id
@property # type: ignore
@info
@property
def first_name(self) -> str:
""":obj:`str`: Bot's first name."""
return self.bot.first_name # type: ignore
return self.bot.first_name
@property # type: ignore
@info
@property
def last_name(self) -> str:
""":obj:`str`: Optional. Bot's last name."""
return self.bot.last_name # type: ignore
@property # type: ignore
@info
@property
def username(self) -> str:
""":obj:`str`: Bot's username."""
return self.bot.username # type: ignore
@property # type: ignore
@info
@property
def link(self) -> str:
""":obj:`str`: Convenience property. Returns the t.me link of the bot."""
return f"https://t.me/{self.username}"
@property # type: ignore
@info
@property
def can_join_groups(self) -> bool:
""":obj:`bool`: Bot's can_join_groups attribute."""
return self.bot.can_join_groups # type: ignore
@property # type: ignore
@info
@property
def can_read_all_group_messages(self) -> bool:
""":obj:`bool`: Bot's can_read_all_group_messages attribute."""
return self.bot.can_read_all_group_messages # type: ignore
@property # type: ignore
@info
@property
def supports_inline_queries(self) -> bool:
""":obj:`bool`: Bot's supports_inline_queries attribute."""
return self.bot.supports_inline_queries # type: ignore
@property # type: ignore
@info
@property
def commands(self) -> List[BotCommand]:
"""List[:class:`BotCommand`]: Bot's commands."""
return self._commands or []
if self._commands is None:
self._commands = self.get_my_commands()
return self._commands
@property
def name(self) -> str:
@ -392,9 +381,9 @@ class Bot(TelegramObject):
"""
result = self._post('getMe', timeout=timeout, api_kwargs=api_kwargs)
self.bot = User.de_json(result, self) # type: ignore
self._bot = User.de_json(result, self) # type: ignore
return self.bot # type: ignore[return-value]
return self._bot # type: ignore[return-value]
@log
def send_message(
@ -4814,6 +4803,12 @@ class Bot(TelegramObject):
return data
def __eq__(self, other: object) -> bool:
return self.bot == other
def __hash__(self) -> int:
return hash(self.bot)
# camelCase aliases
getMe = get_me
"""Alias for :attr:`get_me`"""

View file

@ -48,6 +48,8 @@ from telegram.constants import MAX_INLINE_QUERY_RESULTS
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
from telegram.utils.helpers import from_timestamp, escape_markdown, to_timestamp
from tests.conftest import expect_bad_request
from tests.bots import FALLBACKS
BASE_TIME = time.time()
HIGHSCORE_DELTA = 1450000000
@ -144,6 +146,24 @@ class TestBot:
assert get_me_bot.supports_inline_queries == bot.supports_inline_queries
assert f'https://t.me/{get_me_bot.username}' == bot.link
assert commands == bot.commands
bot._commands = None
assert commands == bot.commands
def test_equality(self):
a = Bot(FALLBACKS[0]["token"])
b = Bot(FALLBACKS[0]["token"])
c = Bot(FALLBACKS[1]["token"])
d = Update(123456789)
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)
@flaky(3, 1)
@pytest.mark.timeout(10)

View file

@ -208,7 +208,7 @@ class TestUpdater:
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
monkeypatch.setattr(updater.bot, 'bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_commands', [])
ip = '127.0.0.1'