From b566e59036f91739961a0f2e5bb200d32ac03d16 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 16 Oct 2021 11:53:29 +0200 Subject: [PATCH] Add stringify back to custom Message --- readthedocs/misc/v2-migration-guide.rst | 33 +++++++++++ telethon/_misc/helpers.py | 67 ++++++++++++++++++++++ telethon/_misc/tlobject.py | 75 ++----------------------- telethon/types/_custom/message.py | 47 +++++++++++++++- 4 files changed, 152 insertions(+), 70 deletions(-) diff --git a/readthedocs/misc/v2-migration-guide.rst b/readthedocs/misc/v2-migration-guide.rst index c16ed02f..0b8bbc22 100644 --- a/readthedocs/misc/v2-migration-guide.rst +++ b/readthedocs/misc/v2-migration-guide.rst @@ -526,6 +526,39 @@ If you still want the old behaviour, wrap the list inside another list: #+ +Changes to the string and to_dict representation +------------------------------------------------ + +The string representation of raw API objects will now have its "printing depth" limited, meaning +very large and nested objects will be easier to read. + +If you want to see the full object's representation, you should instead use Python's builtin +``repr`` method. + +The ``.stringify`` method remains unchanged. + +Here's a comparison table for a convenient overview: + ++-------------------+---------------------------------------------+---------------------------------------------+ +| | Telethon v1.x | Telethon v2.x | ++-------------------+-------------+--------------+----------------+-------------+--------------+----------------+ +| | ``__str__`` | ``__repr__`` | ``.stringify`` | ``__str__`` | ``__repr__`` | ``.stringify`` | ++-------------------+-------------+--------------+----------------+-------------+--------------+----------------+ +| Useful? | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ++-------------------+-------------+--------------+----------------+-------------+--------------+----------------+ +| Multiline? | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ++-------------------+-------------+--------------+----------------+-------------+--------------+----------------+ +| Shows everything? | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ++-------------------+-------------+--------------+----------------+-------------+--------------+----------------+ + +Both of the string representations may still change in the future without warning, as Telegram +adds, changes or removes fields. It should only be used for debugging. If you need a persistent +string representation, it is your job to decide which fields you care about and their format. + +The ``Message`` representation now contains different properties, which should be more useful and +less confusing. + + Changes on how to configure a different connection mode ------------------------------------------------------- diff --git a/telethon/_misc/helpers.py b/telethon/_misc/helpers.py index f9297816..a3480007 100644 --- a/telethon/_misc/helpers.py +++ b/telethon/_misc/helpers.py @@ -200,6 +200,73 @@ def _entity_type(entity): # 'Empty' in name or not found, we don't care, not a valid entity. raise TypeError('{} does not have any entity type'.format(entity)) + +def pretty_print(obj, indent=None, max_depth=float('inf')): + max_depth -= 1 + if max_depth < 0: + return '...' + + to_d = getattr(obj, '_to_dict', None) or getattr(obj, 'to_dict', None) + if callable(to_d): + obj = to_d() + + if indent is None: + if isinstance(obj, dict): + return '{}({})'.format(obj.get('_', 'dict'), ', '.join( + '{}={}'.format(k, pretty_print(v, indent, max_depth)) + for k, v in obj.items() if k != '_' + )) + elif isinstance(obj, str) or isinstance(obj, bytes): + return repr(obj) + elif hasattr(obj, '__iter__'): + return '[{}]'.format( + ', '.join(pretty_print(x, indent, max_depth) for x in obj) + ) + else: + return repr(obj) + else: + result = [] + + if isinstance(obj, dict): + result.append(obj.get('_', 'dict')) + result.append('(') + if obj: + result.append('\n') + indent += 1 + for k, v in obj.items(): + if k == '_': + continue + result.append('\t' * indent) + result.append(k) + result.append('=') + result.append(pretty_print(v, indent, max_depth)) + result.append(',\n') + result.pop() # last ',\n' + indent -= 1 + result.append('\n') + result.append('\t' * indent) + result.append(')') + + elif isinstance(obj, str) or isinstance(obj, bytes): + result.append(repr(obj)) + + elif hasattr(obj, '__iter__'): + result.append('[\n') + indent += 1 + for x in obj: + result.append('\t' * indent) + result.append(pretty_print(x, indent, max_depth)) + result.append(',\n') + indent -= 1 + result.append('\t' * indent) + result.append(']') + + else: + result.append(repr(obj)) + + return ''.join(result) + + # endregion # region Cryptographic related utils diff --git a/telethon/_misc/tlobject.py b/telethon/_misc/tlobject.py index da2ed6d6..4045e3ed 100644 --- a/telethon/_misc/tlobject.py +++ b/telethon/_misc/tlobject.py @@ -3,6 +3,7 @@ import json import struct from datetime import datetime, date, timedelta, timezone import time +from .helpers import pretty_print _EPOCH_NAIVE = datetime(*time.gmtime(0)[:6]) _EPOCH_NAIVE_LOCAL = datetime(*time.localtime(0)[:6]) @@ -36,73 +37,6 @@ class TLObject: CONSTRUCTOR_ID = None SUBCLASS_OF_ID = None - @staticmethod - def pretty_format(obj, indent=None): - """ - Pretty formats the given object as a string which is returned. - If indent is None, a single line will be returned. - """ - if indent is None: - if isinstance(obj, TLObject): - obj = obj.to_dict() - - if isinstance(obj, dict): - return '{}({})'.format(obj.get('_', 'dict'), ', '.join( - '{}={}'.format(k, TLObject.pretty_format(v)) - for k, v in obj.items() if k != '_' - )) - elif isinstance(obj, str) or isinstance(obj, bytes): - return repr(obj) - elif hasattr(obj, '__iter__'): - return '[{}]'.format( - ', '.join(TLObject.pretty_format(x) for x in obj) - ) - else: - return repr(obj) - else: - result = [] - if isinstance(obj, TLObject): - obj = obj.to_dict() - - if isinstance(obj, dict): - result.append(obj.get('_', 'dict')) - result.append('(') - if obj: - result.append('\n') - indent += 1 - for k, v in obj.items(): - if k == '_': - continue - result.append('\t' * indent) - result.append(k) - result.append('=') - result.append(TLObject.pretty_format(v, indent)) - result.append(',\n') - result.pop() # last ',\n' - indent -= 1 - result.append('\n') - result.append('\t' * indent) - result.append(')') - - elif isinstance(obj, str) or isinstance(obj, bytes): - result.append(repr(obj)) - - elif hasattr(obj, '__iter__'): - result.append('[\n') - indent += 1 - for x in obj: - result.append('\t' * indent) - result.append(TLObject.pretty_format(x, indent)) - result.append(',\n') - indent -= 1 - result.append('\t' * indent) - result.append(']') - - else: - result.append(repr(obj)) - - return ''.join(result) - @staticmethod def serialize_bytes(data): """Write bytes by using Telegram guidelines""" @@ -164,11 +98,14 @@ class TLObject: def __ne__(self, o): return not isinstance(o, type(self)) or self.to_dict() != o.to_dict() + def __repr__(self): + return pretty_print(self) + def __str__(self): - return TLObject.pretty_format(self) + return pretty_print(self, max_depth=2) def stringify(self): - return TLObject.pretty_format(self, indent=0) + return pretty_print(self, indent=0) def to_dict(self): res = {} diff --git a/telethon/types/_custom/message.py b/telethon/types/_custom/message.py index 103da65b..bd0b61ee 100644 --- a/telethon/types/_custom/message.py +++ b/telethon/types/_custom/message.py @@ -9,7 +9,7 @@ from .file import File from .inputfile import InputFile from .inputmessage import InputMessage from .button import build_reply_markup -from ..._misc import utils, tlobject +from ..._misc import utils, helpers, tlobject from ... import _tl, _misc @@ -1366,5 +1366,50 @@ class Message(ChatGetter, SenderGetter): # endregion Private Methods + def to_dict(self): + return self._message.to_dict() + + def _to_dict(self): + return { + '_': 'Message', + 'id': self.id, + 'out': self.out, + 'date': self.date, + 'text': self.text, + 'sender': self.sender, + 'chat': self.chat, + 'mentioned': self.mentioned, + 'media_unread': self.media_unread, + 'silent': self.silent, + 'post': self.post, + 'from_scheduled': self.from_scheduled, + 'legacy': self.legacy, + 'edit_hide': self.edit_hide, + 'pinned': self.pinned, + 'forward': self.forward, + 'via_bot': self.via_bot, + 'reply_to': self.reply_to, + 'reply_markup': self.reply_markup, + 'views': self.views, + 'forwards': self.forwards, + 'replies': self.replies, + 'edit_date': self.edit_date, + 'post_author': self.post_author, + 'grouped_id': self.grouped_id, + 'ttl_period': self.ttl_period, + 'action': self.action, + 'media': self.media, + 'action_entities': self.action_entities, + } + + def __repr__(self): + return helpers.pretty_print(self) + + def __str__(self): + return helpers.pretty_print(self, max_depth=2) + + def stringify(self): + return helpers.pretty_print(self, indent=0) + # TODO set md by default if commonmark is installed else nothing