From 5a54e2279fecc3bbfd44694ad68a8fdb52593a6a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 26 Feb 2018 14:12:21 +0100 Subject: [PATCH] Avoid relying on .__iter__ to tell iterators apart .send_file() would fail with stream objects (those from open()) since they are iterable, and asserting that they weren't bytes or str was not enough. --- telethon/events/__init__.py | 2 +- telethon/session.py | 6 +++--- telethon/telegram_client.py | 6 +++--- telethon/utils.py | 12 ++++++++++++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/telethon/events/__init__.py b/telethon/events/__init__.py index 1f3b15f2..48b26004 100644 --- a/telethon/events/__init__.py +++ b/telethon/events/__init__.py @@ -14,7 +14,7 @@ def _into_id_set(client, chats): if chats is None: return None - if not hasattr(chats, '__iter__') or isinstance(chats, str): + if not utils.is_list_like(chats): chats = (chats,) result = set() diff --git a/telethon/session.py b/telethon/session.py index 4658df2b..faa1516f 100644 --- a/telethon/session.py +++ b/telethon/session.py @@ -355,14 +355,14 @@ class Session: if not self.save_entities: return - if not isinstance(tlo, TLObject) and hasattr(tlo, '__iter__'): + if not isinstance(tlo, TLObject) and utils.is_list_like(tlo): # This may be a list of users already for instance entities = tlo else: entities = [] - if hasattr(tlo, 'chats') and hasattr(tlo.chats, '__iter__'): + if hasattr(tlo, 'chats') and utils.is_list_like(tlo.chats): entities.extend(tlo.chats) - if hasattr(tlo, 'users') and hasattr(tlo.users, '__iter__'): + if hasattr(tlo, 'users') and utils.is_list_like(tlo.users): entities.extend(tlo.users) if not entities: return diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index a2bf5e85..1dad2716 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -975,7 +975,7 @@ class TelegramClient(TelegramBareClient): """ if max_id is None: if message: - if hasattr(message, '__iter__'): + if utils.is_list_like(message): max_id = max(msg.id for msg in message) else: max_id = message.id @@ -1140,7 +1140,7 @@ class TelegramClient(TelegramBareClient): """ # First check if the user passed an iterable, in which case # we may want to send as an album if all are photo files. - if hasattr(file, '__iter__') and not isinstance(file, (str, bytes)): + if utils.is_list_like(file): # Convert to tuple so we can iterate several times file = tuple(x for x in file) if all(utils.is_image(x) for x in file): @@ -1960,7 +1960,7 @@ class TelegramClient(TelegramBareClient): ``User``, ``Chat`` or ``Channel`` corresponding to the input entity. """ - if hasattr(entity, '__iter__') and not isinstance(entity, str): + if utils.is_list_like(entity): single = False else: single = True diff --git a/telethon/utils.py b/telethon/utils.py index fdadbd1c..8f38563a 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -5,6 +5,7 @@ to convert between an entity like an User, Chat, etc. into its Input version) import math import mimetypes import re +import types from mimetypes import add_type, guess_extension from .tl.types import ( @@ -341,6 +342,17 @@ def is_video(file): (mimetypes.guess_type(file)[0] or '').startswith('video/')) +def is_list_like(obj): + """ + Returns True if the given object looks like a list. + + Checking if hasattr(obj, '__iter__') and ignoring str/bytes is not + enough. Things like open() are also iterable (and probably many + other things), so just support the commonly known list-like objects. + """ + return isinstance(obj, (list, tuple, set, dict, types.GeneratorType)) + + def parse_phone(phone): """Parses the given phone, or returns None if it's invalid""" if isinstance(phone, int):