mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 14:35:00 +01:00
Overhaul String Representation of TelegramObject
(#3234)
This commit is contained in:
parent
07f8dd1cb1
commit
f68663af7e
3 changed files with 84 additions and 4 deletions
|
@ -4,3 +4,4 @@ telegram.TelegramObject
|
|||
.. autoclass:: telegram.TelegramObject
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members: __repr__
|
||||
|
|
|
@ -21,7 +21,7 @@ import inspect
|
|||
import json
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Sized, Tuple, Type, TypeVar, Union
|
||||
|
||||
from telegram._utils.types import JSONDict
|
||||
from telegram._utils.warnings import warn
|
||||
|
@ -52,6 +52,9 @@ class TelegramObject:
|
|||
* Removed argument and attribute ``bot`` for several subclasses. Use
|
||||
:meth:`set_bot` and :meth:`get_bot` instead.
|
||||
* Removed the possibility to pass arbitrary keyword arguments for several subclasses.
|
||||
* String representations objects of this type was overhauled. See :meth:`__repr__` for
|
||||
details. As this class doesn't implement :meth:`object.__str__`, the default
|
||||
implementation will be used, which is equivalent to :meth:`__repr__`.
|
||||
|
||||
Arguments:
|
||||
api_kwargs (Dict[:obj:`str`, any], optional): |toapikwargsarg|
|
||||
|
@ -100,8 +103,40 @@ class TelegramObject:
|
|||
if getattr(self, key, True) is None:
|
||||
setattr(self, key, self.api_kwargs.pop(key))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.to_dict())
|
||||
def __repr__(self) -> str:
|
||||
"""Gives a string representation of this object in the form
|
||||
``ClassName(attr_1=value_1, attr_2=value_2, ...)``, where attributes are omitted if they
|
||||
have the value :obj:`None` or empty instances of :class:`collections.abc.Sized` (e.g.
|
||||
:class:`list`, :class:`dict`, :class:`set`, :class:`str`, etc.).
|
||||
|
||||
As this class doesn't implement :meth:`object.__str__`, the default implementation
|
||||
will be used, which is equivalent to :meth:`__repr__`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`
|
||||
"""
|
||||
# * `__repr__` goal is to be unambiguous
|
||||
# * `__str__` goal is to be readable
|
||||
# * `str()` calls `__repr__`, if `__str__` is not defined
|
||||
# In our case "unambiguous" and "readable" largely coincide, so we can use the same logic.
|
||||
as_dict = self._get_attrs(recursive=False, include_private=False)
|
||||
|
||||
if not self.api_kwargs:
|
||||
# Drop api_kwargs from the representation, if empty
|
||||
as_dict.pop("api_kwargs", None)
|
||||
|
||||
contents = ", ".join(
|
||||
f"{k}={as_dict[k]!r}"
|
||||
for k in sorted(as_dict.keys())
|
||||
if (
|
||||
as_dict[k] is not None
|
||||
and not (
|
||||
isinstance(as_dict[k], Sized)
|
||||
and len(as_dict[k]) == 0 # type: ignore[arg-type]
|
||||
)
|
||||
)
|
||||
)
|
||||
return f"{self.__class__.__name__}({contents})"
|
||||
|
||||
def __getitem__(self, item: str) -> object:
|
||||
if item == "from":
|
||||
|
|
|
@ -24,7 +24,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from telegram import Bot, Chat, Message, PhotoSize, TelegramObject, User
|
||||
from telegram import Bot, BotCommand, Chat, Message, PhotoSize, TelegramObject, User
|
||||
|
||||
|
||||
def all_subclasses(cls):
|
||||
|
@ -284,3 +284,47 @@ class TestTelegramObject:
|
|||
assert d._private == s._private # Can't test for identity since two equal strings is True
|
||||
assert d._bot == s._bot and d._bot is s._bot
|
||||
assert d.normal == s.normal
|
||||
|
||||
def test_string_representation(self):
|
||||
class TGO(TelegramObject):
|
||||
def __init__(self, api_kwargs=None):
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
self.string_attr = "string"
|
||||
self.int_attr = 42
|
||||
self.to_attr = BotCommand("command", "description")
|
||||
self.list_attr = [
|
||||
BotCommand("command_1", "description_1"),
|
||||
BotCommand("command_2", "description_2"),
|
||||
]
|
||||
self.dict_attr = {
|
||||
BotCommand("command_1", "description_1"): BotCommand(
|
||||
"command_2", "description_2"
|
||||
)
|
||||
}
|
||||
self.empty_tuple_attrs = ()
|
||||
self.empty_str_attribute = ""
|
||||
# Should not be included in string representation
|
||||
self.none_attr = None
|
||||
|
||||
expected_without_api_kwargs = (
|
||||
"TGO(dict_attr={BotCommand(command='command_1', description='description_1'): "
|
||||
"BotCommand(command='command_2', description='description_2')}, int_attr=42, "
|
||||
"list_attr=[BotCommand(command='command_1', description='description_1'), "
|
||||
"BotCommand(command='command_2', description='description_2')], "
|
||||
"string_attr='string', to_attr=BotCommand(command='command', "
|
||||
"description='description'))"
|
||||
)
|
||||
assert str(TGO()) == expected_without_api_kwargs
|
||||
assert repr(TGO()) == expected_without_api_kwargs
|
||||
|
||||
expected_with_api_kwargs = (
|
||||
"TGO(api_kwargs={'foo': 'bar'}, dict_attr={BotCommand(command='command_1', "
|
||||
"description='description_1'): BotCommand(command='command_2', "
|
||||
"description='description_2')}, int_attr=42, "
|
||||
"list_attr=[BotCommand(command='command_1', description='description_1'), "
|
||||
"BotCommand(command='command_2', description='description_2')], "
|
||||
"string_attr='string', to_attr=BotCommand(command='command', "
|
||||
"description='description'))"
|
||||
)
|
||||
assert str(TGO(api_kwargs={"foo": "bar"})) == expected_with_api_kwargs
|
||||
assert repr(TGO(api_kwargs={"foo": "bar"})) == expected_with_api_kwargs
|
||||
|
|
Loading…
Reference in a new issue