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 # Exclude .exrc file for Vim
.exrc .exrc
# virtual env
venv*

View file

@ -110,22 +110,6 @@ if TYPE_CHECKING:
RT = TypeVar('RT') 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( def log(
func: Callable[..., RT], *args: Any, **kwargs: Any # pylint: disable=W0613 func: Callable[..., RT], *args: Any, **kwargs: Any # pylint: disable=W0613
) -> Callable[..., RT]: ) -> Callable[..., RT]:
@ -144,6 +128,10 @@ def log(
class Bot(TelegramObject): class Bot(TelegramObject):
"""This object represents a Telegram Bot. """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: Note:
Most bot methods have the argument ``api_kwargs`` which allows to pass arbitrary keywords 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 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_url = str(base_url) + str(self.token)
self.base_file_url = str(base_file_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._commands: Optional[List[BotCommand]] = None
self._request = request or Request() self._request = request or Request()
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
@ -302,68 +290,69 @@ class Bot(TelegramObject):
return token return token
@property # type: ignore @property
@info 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: def id(self) -> int:
""":obj:`int`: Unique identifier for this bot.""" """:obj:`int`: Unique identifier for this bot."""
return self.bot.id # type: ignore return self.bot.id
@property # type: ignore @property
@info
def first_name(self) -> str: def first_name(self) -> str:
""":obj:`str`: Bot's first name.""" """:obj:`str`: Bot's first name."""
return self.bot.first_name # type: ignore return self.bot.first_name
@property # type: ignore @property
@info
def last_name(self) -> str: def last_name(self) -> str:
""":obj:`str`: Optional. Bot's last name.""" """:obj:`str`: Optional. Bot's last name."""
return self.bot.last_name # type: ignore return self.bot.last_name # type: ignore
@property # type: ignore @property
@info
def username(self) -> str: def username(self) -> str:
""":obj:`str`: Bot's username.""" """:obj:`str`: Bot's username."""
return self.bot.username # type: ignore return self.bot.username # type: ignore
@property # type: ignore @property
@info
def link(self) -> str: def link(self) -> str:
""":obj:`str`: Convenience property. Returns the t.me link of the bot.""" """:obj:`str`: Convenience property. Returns the t.me link of the bot."""
return f"https://t.me/{self.username}" return f"https://t.me/{self.username}"
@property # type: ignore @property
@info
def can_join_groups(self) -> bool: def can_join_groups(self) -> bool:
""":obj:`bool`: Bot's can_join_groups attribute.""" """:obj:`bool`: Bot's can_join_groups attribute."""
return self.bot.can_join_groups # type: ignore return self.bot.can_join_groups # type: ignore
@property # type: ignore @property
@info
def can_read_all_group_messages(self) -> bool: def can_read_all_group_messages(self) -> bool:
""":obj:`bool`: Bot's can_read_all_group_messages attribute.""" """:obj:`bool`: Bot's can_read_all_group_messages attribute."""
return self.bot.can_read_all_group_messages # type: ignore return self.bot.can_read_all_group_messages # type: ignore
@property # type: ignore @property
@info
def supports_inline_queries(self) -> bool: def supports_inline_queries(self) -> bool:
""":obj:`bool`: Bot's supports_inline_queries attribute.""" """:obj:`bool`: Bot's supports_inline_queries attribute."""
return self.bot.supports_inline_queries # type: ignore return self.bot.supports_inline_queries # type: ignore
@property # type: ignore @property
@info
def commands(self) -> List[BotCommand]: def commands(self) -> List[BotCommand]:
"""List[:class:`BotCommand`]: Bot's commands.""" """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 @property
def name(self) -> str: def name(self) -> str:
@ -392,9 +381,9 @@ class Bot(TelegramObject):
""" """
result = self._post('getMe', timeout=timeout, api_kwargs=api_kwargs) 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 @log
def send_message( def send_message(
@ -4814,6 +4803,12 @@ class Bot(TelegramObject):
return data return data
def __eq__(self, other: object) -> bool:
return self.bot == other
def __hash__(self) -> int:
return hash(self.bot)
# camelCase aliases # camelCase aliases
getMe = get_me getMe = get_me
"""Alias for :attr:`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.error import BadRequest, InvalidToken, NetworkError, RetryAfter
from telegram.utils.helpers import from_timestamp, escape_markdown, to_timestamp from telegram.utils.helpers import from_timestamp, escape_markdown, to_timestamp
from tests.conftest import expect_bad_request from tests.conftest import expect_bad_request
from tests.bots import FALLBACKS
BASE_TIME = time.time() BASE_TIME = time.time()
HIGHSCORE_DELTA = 1450000000 HIGHSCORE_DELTA = 1450000000
@ -144,6 +146,24 @@ class TestBot:
assert get_me_bot.supports_inline_queries == bot.supports_inline_queries assert get_me_bot.supports_inline_queries == bot.supports_inline_queries
assert f'https://t.me/{get_me_bot.username}' == bot.link assert f'https://t.me/{get_me_bot.username}' == bot.link
assert commands == bot.commands 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) @flaky(3, 1)
@pytest.mark.timeout(10) @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, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_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 # 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', []) monkeypatch.setattr(updater.bot, '_commands', [])
ip = '127.0.0.1' ip = '127.0.0.1'