diff --git a/.gitignore b/.gitignore index a2e9366dd..85a61e2b5 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,6 @@ telegram.jpg # Exclude .exrc file for Vim .exrc + +# virtual env +venv* diff --git a/telegram/bot.py b/telegram/bot.py index 04aaa49ac..828e476b9 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -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`""" diff --git a/tests/test_bot.py b/tests/test_bot.py index 8777495b3..20c72b3bb 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -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) diff --git a/tests/test_updater.py b/tests/test_updater.py index 98ae0685f..e246496c3 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -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'