Skip to content

Make telegram.Bot comparable #2320

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 5 commits into from
Jan 17, 2021
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ telegram.jpg

# Exclude .exrc file for Vim
.exrc

# virtual env
venv*
75 changes: 35 additions & 40 deletions telegram/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,6 @@
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]:
Expand All @@ -144,6 +128,10 @@ def decorator(self: 'Bot', *args: Any, **kwargs: Any) -> RT: # pylint: disable=
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
Expand Down Expand Up @@ -217,7 +205,7 @@ def __init__(

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__)
Expand Down Expand Up @@ -302,68 +290,69 @@ def _validate_token(token: str) -> str:

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:
Expand Down Expand Up @@ -392,9 +381,9 @@ def get_me(self, timeout: int = None, api_kwargs: JSONDict = None) -> User:
"""
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(
Expand Down Expand Up @@ -4814,6 +4803,12 @@ def to_dict(self) -> JSONDict:

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`"""
Expand Down
20 changes: 20 additions & 0 deletions tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
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
Expand Down Expand Up @@ -144,6 +146,24 @@ def test_get_me_and_properties(self, bot):
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)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def test_start_webhook_no_warning_or_error_logs(self, caplog, updater, monkeypat
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'
Expand Down