From 883c6b590102a615be639e95740e5b2152b3f09e Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Sun, 26 Jan 2020 20:59:47 +0100 Subject: [PATCH 01/18] Add dispatcher argument to Updater (#1484) --- telegram/ext/dispatcher.py | 6 +- telegram/ext/updater.py | 129 +++++++++++++++++++++++-------------- tests/test_updater.py | 27 +++++++- 3 files changed, 109 insertions(+), 53 deletions(-) diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index 5e255fbed..1c64c4b5a 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -138,8 +138,6 @@ class Dispatcher(object): else: self.persistence = None - self.job_queue = job_queue - self.handlers = {} """Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group.""" self.groups = [] @@ -162,6 +160,10 @@ class Dispatcher(object): else: self._set_singleton(None) + @property + def exception_event(self): + return self.__exception_event + def _init_async_threads(self, base_name, workers): base_name = '{}_'.format(base_name) if base_name else '' diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index 8b6af3684..1032d343d 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -63,24 +63,29 @@ class Updater(object): token (:obj:`str`, optional): The bot's token given by the @BotFather. base_url (:obj:`str`, optional): Base_url for the bot. workers (:obj:`int`, optional): Amount of threads in the thread pool for functions - decorated with ``@run_async``. - bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance. If a pre-initialized - bot is used, it is the user's responsibility to create it using a `Request` - instance with a large enough connection pool. + decorated with ``@run_async`` (ignored if `dispatcher` argument is used). + bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance (ignored if + `dispatcher` argument is used). If a pre-initialized bot is used, it is the user's + responsibility to create it using a `Request` instance with a large enough connection + pool. + dispatcher (:class:`telegram.ext.Dispatcher`, optional): A pre-initialized dispatcher + instance. If a pre-initialized dispatcher is used, it is the user's responsibility to + create it with proper arguments. private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data. private_key_password (:obj:`bytes`, optional): Password for above private key. user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional arguments. This will be called when a signal is received, defaults are (SIGINT, SIGTERM, SIGABRT) setable with :attr:`idle`. request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a - `telegram.utils.request.Request` object (ignored if `bot` argument is used). The - request_kwargs are very useful for the advanced users who would like to control the - default timeouts and/or control the proxy used for http communication. - use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API. - During the deprecation period of the old API the default is ``False``. **New users**: - set this to ``True``. + `telegram.utils.request.Request` object (ignored if `bot` or `dispatcher` argument is + used). The request_kwargs are very useful for the advanced users who would like to + control the default timeouts and/or control the proxy used for http communication. + use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API + (ignored if `dispatcher` argument is used). During the deprecation period of the old + API the default is ``False``. **New users**: set this to ``True``. persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to - store data that should be persistent over restarts. + store data that should be persistent over restarts (ignored if `dispatcher` argument is + used). Note: You must supply either a :attr:`bot` or a :attr:`token` argument. @@ -102,53 +107,79 @@ class Updater(object): user_sig_handler=None, request_kwargs=None, persistence=None, - use_context=False): + use_context=False, + dispatcher=None): - if (token is None) and (bot is None): - raise ValueError('`token` or `bot` must be passed') - if (token is not None) and (bot is not None): - raise ValueError('`token` and `bot` are mutually exclusive') - if (private_key is not None) and (bot is not None): - raise ValueError('`bot` and `private_key` are mutually exclusive') + if dispatcher is None: + if (token is None) and (bot is None): + raise ValueError('`token` or `bot` must be passed') + if (token is not None) and (bot is not None): + raise ValueError('`token` and `bot` are mutually exclusive') + if (private_key is not None) and (bot is not None): + raise ValueError('`bot` and `private_key` are mutually exclusive') + else: + if bot is not None: + raise ValueError('`dispatcher` and `bot` are mutually exclusive') + if persistence is not None: + raise ValueError('`dispatcher` and `persistence` are mutually exclusive') + if workers is not None: + raise ValueError('`dispatcher` and `workers` are mutually exclusive') + if use_context != dispatcher.use_context: + raise ValueError('`dispatcher` and `use_context` are mutually exclusive') self.logger = logging.getLogger(__name__) - con_pool_size = workers + 4 + if dispatcher is None: + con_pool_size = workers + 4 - if bot is not None: - self.bot = bot - if bot.request.con_pool_size < con_pool_size: + if bot is not None: + self.bot = bot + if bot.request.con_pool_size < con_pool_size: + self.logger.warning( + 'Connection pool of Request object is smaller than optimal value (%s)', + con_pool_size) + else: + # we need a connection pool the size of: + # * for each of the workers + # * 1 for Dispatcher + # * 1 for polling Updater (even if webhook is used, we can spare a connection) + # * 1 for JobQueue + # * 1 for main thread + if request_kwargs is None: + request_kwargs = {} + if 'con_pool_size' not in request_kwargs: + request_kwargs['con_pool_size'] = con_pool_size + self._request = Request(**request_kwargs) + self.bot = Bot(token, base_url, request=self._request, private_key=private_key, + private_key_password=private_key_password) + self.update_queue = Queue() + self.job_queue = JobQueue() + self.__exception_event = Event() + self.persistence = persistence + self.dispatcher = Dispatcher( + self.bot, + self.update_queue, + job_queue=self.job_queue, + workers=workers, + exception_event=self.__exception_event, + persistence=persistence, + use_context=use_context) + self.job_queue.set_dispatcher(self.dispatcher) + else: + con_pool_size = dispatcher.workers + 4 + + self.bot = dispatcher.bot + if self.bot.request.con_pool_size < con_pool_size: self.logger.warning( 'Connection pool of Request object is smaller than optimal value (%s)', con_pool_size) - else: - # we need a connection pool the size of: - # * for each of the workers - # * 1 for Dispatcher - # * 1 for polling Updater (even if webhook is used, we can spare a connection) - # * 1 for JobQueue - # * 1 for main thread - if request_kwargs is None: - request_kwargs = {} - if 'con_pool_size' not in request_kwargs: - request_kwargs['con_pool_size'] = con_pool_size - self._request = Request(**request_kwargs) - self.bot = Bot(token, base_url, request=self._request, private_key=private_key, - private_key_password=private_key_password) + self.update_queue = dispatcher.update_queue + self.__exception_event = dispatcher.exception_event + self.persistence = dispatcher.persistence + self.job_queue = dispatcher.job_queue + self.dispatcher = dispatcher + self.user_sig_handler = user_sig_handler - self.update_queue = Queue() - self.job_queue = JobQueue() - self.__exception_event = Event() - self.persistence = persistence - self.dispatcher = Dispatcher( - self.bot, - self.update_queue, - job_queue=self.job_queue, - workers=workers, - exception_event=self.__exception_event, - persistence=persistence, - use_context=use_context) - self.job_queue.set_dispatcher(self.dispatcher) self.last_update_id = 0 self.running = False self.is_idle = False diff --git a/tests/test_updater.py b/tests/test_updater.py index 1da48b89c..b0ea47c36 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -39,7 +39,7 @@ from future.builtins import bytes from telegram import TelegramError, Message, User, Chat, Update, Bot from telegram.error import Unauthorized, InvalidToken, TimedOut, RetryAfter -from telegram.ext import Updater +from telegram.ext import Updater, Dispatcher, BasePersistence signalskip = pytest.mark.skipif(sys.platform == 'win32', reason='Can\'t send signals without stopping ' @@ -369,7 +369,7 @@ class TestUpdater(object): with pytest.raises(ValueError): Updater(token='123:abcd', bot=bot) - def test_no_token_or_bot(self): + def test_no_token_or_bot_or_dispatcher(self): with pytest.raises(ValueError): Updater() @@ -377,3 +377,26 @@ class TestUpdater(object): bot = Bot('123:zyxw') with pytest.raises(ValueError): Updater(bot=bot, private_key=b'key') + + def test_mutual_exclude_bot_dispatcher(self): + dispatcher = Dispatcher(None, None) + bot = Bot('123:zyxw') + with pytest.raises(ValueError): + Updater(bot=bot, dispatcher=dispatcher) + + def test_mutual_exclude_persistence_dispatcher(self): + dispatcher = Dispatcher(None, None) + persistence = BasePersistence() + with pytest.raises(ValueError): + Updater(dispatcher=dispatcher, persistence=persistence) + + def test_mutual_exclude_workers_dispatcher(self): + dispatcher = Dispatcher(None, None) + with pytest.raises(ValueError): + Updater(dispatcher=dispatcher, workers=8) + + def test_mutual_exclude_use_context_dispatcher(self): + dispatcher = Dispatcher(None, None) + use_context = not dispatcher.use_context + with pytest.raises(ValueError): + Updater(dispatcher=dispatcher, use_context=use_context) From d2466f1e6ef6fbb8f7e6dd392ec772d83ab61e16 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sun, 26 Jan 2020 12:07:25 -0800 Subject: [PATCH 02/18] CI: Fix running on master after push + official can fail (#1716) allowing official to fail is only temporary until we'll add the latest bot api support --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 230c65bb6..28670c694 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,9 @@ on: - master schedule: - cron: 7 3 * * * + push: + branches: + - master jobs: pytest: @@ -87,6 +90,7 @@ jobs: run: | pytest -v tests/test_official.py exit $? + continue-on-error: True env: TEST_OFFICIAL: "true" shell: bash --noprofile --norc {0} From 4b5ba15d31fb2a7989986a79b737900bf6b0cdd1 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sun, 26 Jan 2020 12:16:19 -0800 Subject: [PATCH 03/18] both google python style links have been moved (#1624) I choose https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html, based on https://web.archive.org/web/20160322212330/http://www.sphinx-doc.org/en/stable/ext/example_google.html and http://google.github.io/styleguide/pyguide.html based on https://web.archive.org/web/20160304111857/https://google.github.io/styleguide/pyguide.html --- .github/CONTRIBUTING.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index 05bd22e02..cbf25cc1a 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -238,6 +238,6 @@ break the API classes. For example: .. _`developers' mailing list`: mailto:devs@python-telegram-bot.org .. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/ .. _`sphinx`: http://sphinx-doc.org -.. _`Google Python Style Guide`: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html -.. _`Google Python Style Docstrings`: http://sphinx-doc.org/latest/ext/example_google.html +.. _`Google Python Style Guide`: http://google.github.io/styleguide/pyguide.html +.. _`Google Python Style Docstrings`: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html .. _AUTHORS.rst: ../AUTHORS.rst From 33280a7fe0380e0dfc9a024fb9da24bf82499a4e Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Sun, 26 Jan 2020 21:18:29 +0100 Subject: [PATCH 04/18] Add missing name for Filters.update classes (#1632) --- telegram/ext/filters.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 8e4eac801..013e6b27f 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -987,8 +987,10 @@ officedocument.wordprocessingml.document")``- class _UpdateType(BaseFilter): update_filter = True + name = 'Filters.update' class _Message(BaseFilter): + name = 'Filters.update.message' update_filter = True def filter(self, update): @@ -997,6 +999,7 @@ officedocument.wordprocessingml.document")``- message = _Message() class _EditedMessage(BaseFilter): + name = 'Filters.update.edited_message' update_filter = True def filter(self, update): @@ -1005,6 +1008,7 @@ officedocument.wordprocessingml.document")``- edited_message = _EditedMessage() class _Messages(BaseFilter): + name = 'Filters.update.messages' update_filter = True def filter(self, update): @@ -1013,6 +1017,7 @@ officedocument.wordprocessingml.document")``- messages = _Messages() class _ChannelPost(BaseFilter): + name = 'Filters.update.channel_post' update_filter = True def filter(self, update): @@ -1022,6 +1027,7 @@ officedocument.wordprocessingml.document")``- class _EditedChannelPost(BaseFilter): update_filter = True + name = 'Filters.update.edited_channel_post' def filter(self, update): return update.edited_channel_post is not None @@ -1030,6 +1036,7 @@ officedocument.wordprocessingml.document")``- class _ChannelPosts(BaseFilter): update_filter = True + name = 'Filters.update.channel_posts' def filter(self, update): return update.channel_post is not None or update.edited_channel_post is not None From 1d92f52c6a83aeb980375be9b725286a2c3b89dc Mon Sep 17 00:00:00 2001 From: compSciKai Date: Sun, 26 Jan 2020 12:19:59 -0800 Subject: [PATCH 05/18] Minor documentation fix (#1647) --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index a3cd8b17d..4c7e5a570 100644 --- a/README.rst +++ b/README.rst @@ -137,9 +137,9 @@ Other references: Learning by example ------------------- -We believe that the best way to learn and understand this simple package is by example. So here -are some examples for you to review. Even if it's not your approach for learning, please take a -look at ``echobot2``, it is de facto the base for most of the bots out there. Best of all, +We believe that the best way to learn this package is by example. Here +are some examples for you to review. Even if it is not your approach for learning, please take a +look at ``echobot2``, it is the de facto base for most of the bots out there. Best of all, the code for these examples are released to the public domain, so you can start by grabbing the code and building on top of it. From e3c8466e41596289b8a414522fef2b5ecb3e3079 Mon Sep 17 00:00:00 2001 From: rizlas Date: Sun, 26 Jan 2020 21:24:00 +0100 Subject: [PATCH 06/18] Rename enocde_conversations_to_json() -> enocde_conversations_to_json() (#1661) Fixes #1660 --- AUTHORS.rst | 1 + telegram/ext/dictpersistence.py | 4 ++-- telegram/utils/helpers.py | 2 +- tests/test_persistence.py | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 63cd5e0bd..300892358 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -67,6 +67,7 @@ The following wonderful people contributed directly or indirectly to this projec - `Pieter Schutz `_ - `Poolitzer `_ - `Rahiel Kasim `_ +- `Rizlas `_ - `Sahil Sharma `_ - `Sascha `_ - `Shelomentsev D `_ diff --git a/telegram/ext/dictpersistence.py b/telegram/ext/dictpersistence.py index 419603f17..431fb82f6 100644 --- a/telegram/ext/dictpersistence.py +++ b/telegram/ext/dictpersistence.py @@ -20,7 +20,7 @@ from copy import deepcopy from telegram.utils.helpers import decode_user_chat_data_from_json,\ - decode_conversations_from_json, enocde_conversations_to_json + decode_conversations_from_json, encode_conversations_to_json try: import ujson as json @@ -119,7 +119,7 @@ class DictPersistence(BasePersistence): if self._conversations_json: return self._conversations_json else: - return enocde_conversations_to_json(self.conversations) + return encode_conversations_to_json(self.conversations) def get_user_data(self): """Returns the user_data created from the ``user_data_json`` or an empty defaultdict. diff --git a/telegram/utils/helpers.py b/telegram/utils/helpers.py index e56eb11e4..5eacd10ba 100644 --- a/telegram/utils/helpers.py +++ b/telegram/utils/helpers.py @@ -300,7 +300,7 @@ def create_deep_linked_url(bot_username, payload=None, group=False): ) -def enocde_conversations_to_json(conversations): +def encode_conversations_to_json(conversations): """Helper method to encode a conversations dict (that uses tuples as keys) to a JSON-serializable way. Use :attr:`_decode_conversations_from_json` to decode. diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 6797c0d02..c9b01d109 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import signal -from telegram.utils.helpers import enocde_conversations_to_json +from telegram.utils.helpers import encode_conversations_to_json try: import ujson as json @@ -774,7 +774,7 @@ class TestDictPersistence(object): dict_persistence.update_conversation('name3', (1, 2), 3) assert dict_persistence.conversations == conversations_two assert dict_persistence.conversations_json != conversations_json - assert dict_persistence.conversations_json == enocde_conversations_to_json( + assert dict_persistence.conversations_json == encode_conversations_to_json( conversations_two) def test_with_handler(self, bot, update): From 3d59b2f5812de23a04ed547c93be9224fda76326 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Sun, 26 Jan 2020 23:30:26 +0300 Subject: [PATCH 07/18] Docstring fix: thumb limits were changed with Bot API 4.2 (#1669) --- telegram/bot.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/telegram/bot.py b/telegram/bot.py index bb9b3ecf5..becf69ead 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -455,10 +455,10 @@ class Bot(TelegramObject): reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - thumb (`filelike object`, optional): Thumbnail of the - file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail's width and height should not exceed 90. Ignored if the file is not - is passed as a string or file_id. + thumb (`filelike object`, optional): Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 kB in size. + A thumbnail's width and height should not exceed 320. + Ignored if the file is passed as a string or file_id. timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). **kwargs (:obj:`dict`): Arbitrary keyword arguments. @@ -538,10 +538,10 @@ class Bot(TelegramObject): reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - thumb (`filelike object`, optional): Thumbnail of the - file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail's width and height should not exceed 90. Ignored if the file is not - is passed as a string or file_id. + thumb (`filelike object`, optional): Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 kB in size. + A thumbnail's width and height should not exceed 320. + Ignored if the file is passed as a string or file_id. timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). **kwargs (:obj:`dict`): Arbitrary keyword arguments. @@ -676,10 +676,10 @@ class Bot(TelegramObject): reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - thumb (`filelike object`, optional): Thumbnail of the - file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail's width and height should not exceed 90. Ignored if the file is not - is passed as a string or file_id. + thumb (`filelike object`, optional): Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 kB in size. + A thumbnail's width and height should not exceed 320. + Ignored if the file is passed as a string or file_id. timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). **kwargs (:obj:`dict`): Arbitrary keyword arguments. @@ -755,10 +755,10 @@ class Bot(TelegramObject): reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - thumb (`filelike object`, optional): Thumbnail of the - file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail's width and height should not exceed 90. Ignored if the file is not - is passed as a string or file_id. + thumb (`filelike object`, optional): Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 kB in size. + A thumbnail's width and height should not exceed 320. + Ignored if the file is passed as a string or file_id. timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). **kwargs (:obj:`dict`): Arbitrary keyword arguments. @@ -820,10 +820,10 @@ class Bot(TelegramObject): duration (:obj:`int`, optional): Duration of sent animation in seconds. width (:obj:`int`, optional): Animation width. height (:obj:`int`, optional): Animation height. - thumb (`filelike object`, optional): Thumbnail of the - file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail's width and height should not exceed 90. Ignored if the file is not - is passed as a string or file_id. + thumb (`filelike object`, optional): Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 kB in size. + A thumbnail's width and height should not exceed 320. + Ignored if the file is passed as a string or file_id. caption (:obj:`str`, optional): Animation caption (may also be used when resending animations by file_id), 0-1024 characters. parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to From 38b9f4b9bc3e1b2d96a5655ad478bd9e5e4d6651 Mon Sep 17 00:00:00 2001 From: Mayur Newase Date: Mon, 27 Jan 2020 02:04:25 +0530 Subject: [PATCH 08/18] Dispatcher.__init__: Remove double assignmed to self.job_queue (#1698) Co-authored-by: mayur741 From 08bbeca8ecfc5739418d8f169fd92229802fcc2a Mon Sep 17 00:00:00 2001 From: David Auer Date: Sun, 26 Jan 2020 21:35:34 +0100 Subject: [PATCH 09/18] Fix typo in example text (#1703) --- examples/persistentconversationbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/persistentconversationbot.py b/examples/persistentconversationbot.py index 1099ecce7..708d79bd2 100644 --- a/examples/persistentconversationbot.py +++ b/examples/persistentconversationbot.py @@ -47,7 +47,7 @@ def start(update, context): reply_text = "Hi! My name is Doctor Botter." if context.user_data: reply_text += " You already told me your {}. Why don't you tell me something more " \ - "about yourself? Or change enything I " \ + "about yourself? Or change anything I " \ "already know.".format(", ".join(context.user_data.keys())) else: reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \ From 62f514f068a36316ea790c1f8c40a4f45acef0db Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Sun, 26 Jan 2020 21:55:00 +0100 Subject: [PATCH 10/18] CallbackContext: Expose dispatcher as a property (#1684) Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> --- telegram/ext/callbackcontext.py | 14 +++++++++++++- tests/test_callbackcontext.py | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/telegram/ext/callbackcontext.py b/telegram/ext/callbackcontext.py index aeb3eb964..83dd3912a 100644 --- a/telegram/ext/callbackcontext.py +++ b/telegram/ext/callbackcontext.py @@ -44,7 +44,14 @@ class CallbackContext(object): Attributes: chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each - update from the same chat it will be the same ``dict``. + update from the same chat id it will be the same ``dict``. + + Warning: + When a group chat migrates to a supergroup, its chat id will change and the + ``chat_data`` needs to be transferred. For details see our `wiki page + `_. + user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each update from the same user it will be the same ``dict``. matches (List[:obj:`re match object`], optional): If the associated update originated from @@ -80,6 +87,11 @@ class CallbackContext(object): self.error = None self.job = None + @property + def dispatcher(self): + """:class:`telegram.ext.Dispatcher`: The dispatcher associated with this context.""" + return self._dispatcher + @property def chat_data(self): return self._chat_data diff --git a/tests/test_callbackcontext.py b/tests/test_callbackcontext.py index 2eefddaf8..5607fda0d 100644 --- a/tests/test_callbackcontext.py +++ b/tests/test_callbackcontext.py @@ -117,3 +117,7 @@ class TestCallbackContext(object): callback_context.user_data = {} with pytest.raises(AttributeError): callback_context.chat_data = "test" + + def test_dispatcher_attribute(self, cdp): + callback_context = CallbackContext(cdp) + assert callback_context.dispatcher == cdp From d9d65cc2acdd00d21de22b8d24a3122e6621d006 Mon Sep 17 00:00:00 2001 From: Eana Hufwe Date: Mon, 27 Jan 2020 04:57:48 +0800 Subject: [PATCH 11/18] Add poll messages support to filters (#1673) --- AUTHORS.rst | 1 + telegram/ext/filters.py | 9 +++++++++ tests/test_filters.py | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 300892358..3de882279 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -26,6 +26,7 @@ The following wonderful people contributed directly or indirectly to this projec - `d-qoi `_ - `daimajia `_ - `Daniel Reed `_ +- `Eana Hufwe `_ - `Ehsan Online `_ - `Eli Gao `_ - `Emilio Molinari `_ diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 013e6b27f..b375bc9f0 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -956,6 +956,15 @@ officedocument.wordprocessingml.document")``- passport_data = _PassportData() """Messages that contain a :class:`telegram.PassportData`""" + class _Poll(BaseFilter): + name = 'Filters.poll' + + def filter(self, message): + return bool(message.poll) + + poll = _Poll() + """Messages that contain a :class:`telegram.Poll`.""" + class language(BaseFilter): """Filters messages to only allow those which are from users with a certain language code. diff --git a/tests/test_filters.py b/tests/test_filters.py index b1525b87e..a48ba2d55 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -599,6 +599,11 @@ class TestFilters(object): update.message.passport_data = 'test' assert Filters.passport_data(update) + def test_filters_poll(self, update): + assert not Filters.poll(update) + update.message.poll = 'test' + assert Filters.poll(update) + def test_language_filter_single(self, update): update.message.from_user.language_code = 'en_US' assert (Filters.language('en_US'))(update) From fbb7e0e645810a6bbe6cfe468888fe9e7508b1bf Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sun, 26 Jan 2020 13:01:29 -0800 Subject: [PATCH 12/18] No more unitests for py2.7 (#1731) Still not removing any py2.7 specific code, but no reason to waste precious CPU and unitest time on py2.7. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28670c694..0aa16b1e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7] + python-version: [3.5, 3.6, 3.7] os: [ubuntu-latest, windows-latest] include: - os: ubuntu-latest From cb9af36937adf2b15da1947d841f6a15b801bb86 Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Sun, 26 Jan 2020 22:07:17 +0100 Subject: [PATCH 13/18] Fix None check in JobQueue._put() (#1707) fixes #1701 --- telegram/ext/jobqueue.py | 3 ++- tests/test_jobqueue.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 5a04d4ea5..28c9a1a87 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -95,7 +95,8 @@ class JobQueue(object): """ # get time at which to run: - time_spec = time_spec or job.interval + if time_spec is None: + time_spec = job.interval if time_spec is None: raise ValueError("no time specification given for scheduling non-repeating job") next_t = to_float_timestamp(time_spec, reference_timestamp=previous_t) diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 4b5d8d108..664254f4b 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -119,6 +119,11 @@ class TestJobQueue(object): sleep(0.07) assert self.result == 1 + def test_run_repeating_first_immediate(self, job_queue): + job_queue.run_repeating(self.job_run_once, 0.1, first=0) + sleep(0.05) + assert self.result == 1 + def test_run_repeating_first_timezone(self, job_queue, timezone): """Test correct scheduling of job when passing a timezone-aware datetime as ``first``""" first = (dtm.datetime.utcnow() + timezone.utcoffset(None)).replace(tzinfo=timezone) From 0df526d3900664b806bc28a40660803b3fe0174a Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Sun, 26 Jan 2020 22:08:33 +0100 Subject: [PATCH 14/18] jobqueue: Log datetimes correctly (minor change) (#1714) --- telegram/ext/jobqueue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 28c9a1a87..50c96014d 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -102,7 +102,7 @@ class JobQueue(object): next_t = to_float_timestamp(time_spec, reference_timestamp=previous_t) # enqueue: - self.logger.debug('Putting job %s with t=%f', job.name, time_spec) + self.logger.debug('Putting job %s with t=%s', job.name, time_spec) self._queue.put((next_t, job)) # Wake up the loop if this job should be executed next From 0b87f4b27470bb947cb042a18f945517a980aac1 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sun, 26 Jan 2020 13:15:42 -0800 Subject: [PATCH 15/18] contibuting guide: warning about adding requirements (#1718) --- .github/CONTRIBUTING.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index cbf25cc1a..0e6b1e60e 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -43,6 +43,8 @@ If you have an idea for something to do, first check if it's already been filed Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `developers' mailing list`_, and we'll help you get started. +That being said, we want to mention that we are very hesistant about adding new requirements to our projects. If you intend to do this, please state this in an issue and get a verification from one of the maintainers. + Instructions for making a code change ##################################### From 3eb2cef600c2bd8a8897ee48a7ee7fdad6f4bbea Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Sun, 26 Jan 2020 22:19:38 +0100 Subject: [PATCH 16/18] Make Filters.text accept leading slash (#1680) Fixes #1678 --- telegram/ext/filters.py | 4 ++-- tests/test_filters.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index b375bc9f0..1e2708159 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -243,7 +243,7 @@ class Filters(object): self.name = 'Filters.text({})'.format(iterable) def filter(self, message): - if message.text and not message.text.startswith('/'): + if message.text: return message.text in self.iterable return False @@ -257,7 +257,7 @@ class Filters(object): return self._TextIterable(update) def filter(self, message): - return bool(message.text and not message.text.startswith('/')) + return bool(message.text) text = _Text() """Text Messages. If an iterable of strings is passed, it filters messages to only allow those diff --git a/tests/test_filters.py b/tests/test_filters.py index a48ba2d55..1fc101e32 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -45,11 +45,11 @@ class TestFilters(object): update.message.text = 'test' assert (Filters.text)(update) update.message.text = '/test' - assert not (Filters.text)(update) + assert (Filters.text)(update) def test_filters_text_iterable(self, update): - update.message.text = 'test' - assert Filters.text({'test', 'test1'})(update) + update.message.text = '/test' + assert Filters.text({'/test', 'test1'})(update) assert not Filters.text(['test1', 'test2'])(update) def test_filters_caption(self, update): @@ -630,7 +630,7 @@ class TestFilters(object): update.message.forward_date = datetime.datetime.utcnow() assert (Filters.text & Filters.forwarded)(update) update.message.text = '/test' - assert not (Filters.text & Filters.forwarded)(update) + assert (Filters.text & Filters.forwarded)(update) update.message.text = 'test' update.message.forward_date = None assert not (Filters.text & Filters.forwarded)(update) From d96d233dc5d74a21b800ac970d6d2ef431cc58cb Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sun, 26 Jan 2020 13:47:52 -0800 Subject: [PATCH 17/18] dropping 2.7, 3.3, 3.4 support from setup.py and README.rst (#1734) Fixes #1720 --- README.rst | 2 +- setup.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 4c7e5a570..918601bef 100644 --- a/README.rst +++ b/README.rst @@ -83,7 +83,7 @@ Introduction This library provides a pure Python interface for the `Telegram Bot API `_. -It's compatible with Python versions 2.7, 3.3+ and `PyPy `_. +It's compatible with Python versions 3.5+ and `PyPy `_. In addition to the pure API implementation, this library features a number of high-level classes to make the development of bots easy and straightforward. These classes are contained in the diff --git a/setup.py b/setup.py index 2c99bbe98..0381bf4ef 100644 --- a/setup.py +++ b/setup.py @@ -50,10 +50,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd: 'Topic :: Communications :: Chat', 'Topic :: Internet', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7' From 408062dd432e60e1ed09abf8e15532998c0435bf Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Wed, 29 Jan 2020 21:43:57 +0100 Subject: [PATCH 18/18] Add note on how to run test_official to contrib guide (#1740) --- .github/CONTRIBUTING.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index 0e6b1e60e..40332fc3f 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -113,6 +113,14 @@ Here's how to make a one-off code change. $ pytest -v + To run ``test_official`` (particularly useful if you made API changes), run + + .. code-block:: + + $ export TEST_OFFICIAL=True + + prior to running the tests. + - To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically): .. code-block:: bash