From 6be99e0dee72b14c5684a529eeea551e6e78e54a Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Fri, 29 Oct 2021 19:06:13 +0200 Subject: [PATCH 01/13] Feat: initial implementation of improved features --- telegram/ext/_conversationhandler.py | 62 +++++++++++++++++++++------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 2685a95d608..bd64e3ddf03 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -45,6 +45,12 @@ DispatcherHandlerStop, Handler, InlineQueryHandler, + StringCommandHandler, + StringRegexHandler, + ShippingQueryHandler, + PreCheckoutQueryHandler, + PollHandler, + PollAnswerHandler, ) from telegram._utils.warnings import warn from telegram.ext._utils.promise import Promise @@ -283,8 +289,45 @@ def __init__( for state_handlers in states.values(): all_handlers.extend(state_handlers) - if self.per_message: - for handler in all_handlers: + # this loop is going to warn the user about handlers which can work unexpected + # in conversations + for handler in all_handlers: + if isinstance(handler, (StringCommandHandler, StringRegexHandler)): + warn( + "The ConversationHandler does not work with non Telegram.Update type updates, " + "and you shouldn't use this handler for Telegram Update types.", + stacklevel=2, + ) + if isinstance(handler, PollHandler): + warn( + "PollHandler will never trigger in a Conversation since it has no information " + "about the chat or the user who voted in it. Do you mean the " + "'PollAnswerHandler'?", + stacklevel=2, + ) + # we can assume per_user is set. If its not, per_message has to be set, otherwise the + # user would have faced the ValueError that all per_* settings cant be false. If + # per_message is set, the user will get a warning later. + if ( + isinstance( + handler, + ( + ShippingQueryHandler, + InlineQueryHandler, + ChosenInlineResultHandler, + PreCheckoutQueryHandler, + PollAnswerHandler, + ), + ) + and self.per_chat + ): + warn( + "This Handler only has information about the user, so it wont ever be" + " triggered if 'per_chat=True'.", + stacklevel=2, + ) + + if self.per_message: if not isinstance(handler, CallbackQueryHandler): warn( "If 'per_message=True', all entry points, state handlers, and fallbacks" @@ -292,26 +335,13 @@ def __init__( "have a message context.", stacklevel=2, ) - break - else: - for handler in all_handlers: + else: if isinstance(handler, CallbackQueryHandler): warn( "If 'per_message=False', 'CallbackQueryHandler' will not be " "tracked for every message.", stacklevel=2, ) - break - - if self.per_chat: - for handler in all_handlers: - if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)): - warn( - "If 'per_chat=True', 'InlineQueryHandler' can not be used, " - "since inline queries have no chat context.", - stacklevel=2, - ) - break if self.conversation_timeout: for handler in all_handlers: From f661fe57ebc6aac1ebe5340dcc532b2819a3b3c8 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Fri, 29 Oct 2021 20:01:27 +0200 Subject: [PATCH 02/13] Fix: circular import also added a link to the _per* FAQ page --- telegram/ext/_conversationhandler.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index bd64e3ddf03..85ce5fed167 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -47,10 +47,6 @@ InlineQueryHandler, StringCommandHandler, StringRegexHandler, - ShippingQueryHandler, - PreCheckoutQueryHandler, - PollHandler, - PollAnswerHandler, ) from telegram._utils.warnings import warn from telegram.ext._utils.promise import Promise @@ -245,6 +241,14 @@ def __init__( map_to_parent: Dict[object, object] = None, run_async: bool = False, ): + # these imports need to be here because of circular import error otherwise + from telegram.ext import ( # pylint: disable=import-outside-toplevel + ShippingQueryHandler, + PreCheckoutQueryHandler, + PollHandler, + PollAnswerHandler, + ) + self.run_async = run_async self._entry_points = entry_points @@ -291,6 +295,12 @@ def __init__( # this loop is going to warn the user about handlers which can work unexpected # in conversations + + # this link will be added to all warnings tied to per_* setting + per_faq_link = ( + " Read this FAQ entry to learn more about the per_* settings https://git.io/JtcyU." + ) + for handler in all_handlers: if isinstance(handler, (StringCommandHandler, StringRegexHandler)): warn( @@ -323,7 +333,7 @@ def __init__( ): warn( "This Handler only has information about the user, so it wont ever be" - " triggered if 'per_chat=True'.", + " triggered if 'per_chat=True'." + per_faq_link, stacklevel=2, ) @@ -332,14 +342,14 @@ def __init__( warn( "If 'per_message=True', all entry points, state handlers, and fallbacks" " must be 'CallbackQueryHandler', since no other handlers " - "have a message context.", + "have a message context." + per_faq_link, stacklevel=2, ) else: if isinstance(handler, CallbackQueryHandler): warn( "If 'per_message=False', 'CallbackQueryHandler' will not be " - "tracked for every message.", + "tracked for every message." + per_faq_link, stacklevel=2, ) From c129180aa41f270a4b420f56714e305d0d7060ff Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Fri, 29 Oct 2021 21:06:40 +0200 Subject: [PATCH 03/13] Feat: updating test also improved one warning message and removed a break --- telegram/ext/_conversationhandler.py | 3 +- tests/test_conversationhandler.py | 160 ++++++++++++++------------- 2 files changed, 85 insertions(+), 78 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 85ce5fed167..bfe923eef5a 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -310,7 +310,7 @@ def __init__( ) if isinstance(handler, PollHandler): warn( - "PollHandler will never trigger in a Conversation since it has no information " + "PollHandler will never trigger in a conversation since it has no information " "about the chat or the user who voted in it. Do you mean the " "'PollAnswerHandler'?", stacklevel=2, @@ -362,7 +362,6 @@ def __init__( "differently from what you expect.", stacklevel=2, ) - break if self.run_async: for handler in all_handlers: diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 44edea95e71..0388021199a 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -18,6 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import logging from time import sleep +from warnings import filterwarnings import pytest from flaky import flaky @@ -45,7 +46,15 @@ DispatcherHandlerStop, TypeHandler, JobQueue, + StringCommandHandler, + StringRegexHandler, + PollHandler, + ShippingQueryHandler, + ChosenInlineResultHandler, + PreCheckoutQueryHandler, + PollAnswerHandler, ) +from telegram.warnings import PTBUserWarning @pytest.fixture(scope='class') @@ -1325,112 +1334,111 @@ def slowbrew(_update, context): assert handler.conversations.get((self.group.id, user1.id)) is None assert self.is_timeout - def test_conversation_timeout_warning_only_shown_once(self, recwarn): + @pytest.mark.dev + def test_handlers_generate_warning(self, recwarn): + # this function tests all handler + per_* setting combinations. + + # the warning message action needs to be set to always, + # otherwise only the first occurrence will be issued + filterwarnings(action="always", category=PTBUserWarning) + + # this conversation handler has the string, string_regex, and Pollhandler which should all + # generate a warning no matter the per_* setting. ConversationHandler( - entry_points=self.entry_points, + entry_points=[StringCommandHandler("code", self.code)], states={ - self.THIRSTY: [ - ConversationHandler( - entry_points=self.entry_points, - states={ - self.BREWING: [CommandHandler('pourCoffee', self.drink)], - }, - fallbacks=self.fallbacks, - ) - ], - self.DRINKING: [ - ConversationHandler( - entry_points=self.entry_points, - states={ - self.CODING: [CommandHandler('startCoding', self.code)], - }, - fallbacks=self.fallbacks, - ) - ], + self.BREWING: [StringRegexHandler("code", self.code), PollHandler(self.code)], }, - fallbacks=self.fallbacks, - conversation_timeout=100, - ) - assert len(recwarn) == 1 - assert str(recwarn[0].message) == ( - "Using `conversation_timeout` with nested conversations is currently not " - "supported. You can still try to use it, but it will likely behave " - "differently from what you expect." + fallbacks=[], ) - assert recwarn[0].filename == __file__, "incorrect stacklevel!" - def test_per_message_warning_is_only_shown_once(self, recwarn): + # these handlers should all raise a warning when per_chat is True ConversationHandler( - entry_points=self.entry_points, + entry_points=[ShippingQueryHandler(self.code)], states={ - self.THIRSTY: [CommandHandler('pourCoffee', self.drink)], - self.BREWING: [CommandHandler('startCoding', self.code)], + self.BREWING: [ + InlineQueryHandler(self.code), + PreCheckoutQueryHandler(self.code), + PollAnswerHandler(self.code), + ], }, - fallbacks=self.fallbacks, - per_message=True, - ) - assert len(recwarn) == 1 - assert str(recwarn[0].message) == ( - "If 'per_message=True', all entry points, state handlers, and fallbacks" - " must be 'CallbackQueryHandler', since no other handlers" - " have a message context." + fallbacks=[ChosenInlineResultHandler(self.code)], + per_chat=True, ) - assert recwarn[0].filename == __file__, "incorrect stacklevel!" - def test_per_message_but_not_per_chat_warning(self, recwarn): + # the CallbackQueryHandler should *not* raise when per_message is True, + # but any other one should ConversationHandler( - entry_points=[CallbackQueryHandler(self.code, "code")], + entry_points=[CallbackQueryHandler(self.code)], states={ - self.BREWING: [CallbackQueryHandler(self.code, "code")], + self.BREWING: [CommandHandler("code", self.code)], }, - fallbacks=[CallbackQueryHandler(self.code, "code")], + fallbacks=[CallbackQueryHandler(self.code)], per_message=True, - per_chat=False, - ) - assert len(recwarn) == 1 - assert str(recwarn[0].message) == ( - "If 'per_message=True' is used, 'per_chat=True' should also be used, " - "since message IDs are not globally unique." ) - assert recwarn[0].filename == __file__, "incorrect stacklevel!" - def test_per_message_false_warning_is_only_shown_once(self, recwarn): + # the CallbackQueryHandler should raise when per_message is False ConversationHandler( - entry_points=self.entry_points, + entry_points=[CommandHandler("code", self.code)], states={ - self.THIRSTY: [CallbackQueryHandler(self.drink)], - self.BREWING: [CallbackQueryHandler(self.code)], + self.BREWING: [CommandHandler("code", self.code)], }, - fallbacks=self.fallbacks, + fallbacks=[CallbackQueryHandler(self.code)], per_message=False, ) - assert len(recwarn) == 1 - assert str(recwarn[0].message) == ( - "If 'per_message=False', 'CallbackQueryHandler' will not be " - "tracked for every message." - ) - assert recwarn[0].filename == __file__, "incorrect stacklevel!" - def test_warnings_per_chat_is_only_shown_once(self, recwarn): - def hello(update, context): - return self.BREWING + # the overall handlers raising an error is 10 + assert len(recwarn) == 10 + # now we test the messages, they are raised in the order they are inserted + # into the conversation handler + assert str(recwarn[0].message) and str(recwarn[1].message) == ( + "The ConversationHandler does not work with non Telegram.Update type updates, " + "and you shouldn't use this handler for Telegram Update types." + ) + assert str(recwarn[2].message) == ( + "PollHandler will never trigger in a conversation since it has no information " + "about the chat or the user who voted in it. Do you mean the " + "'PollAnswerHandler'?" + ) - def bye(update, context): - return ConversationHandler.END + per_faq_link = ( + " Read this FAQ entry to learn more about the per_* settings https://git.io/JtcyU." + ) + assert ( + str(recwarn[3].message) + and str(recwarn[4].message) + and str(recwarn[5].message) + and str(recwarn[6].message) + and str(recwarn[7].message) + == ( + "This Handler only has information about the user, so it wont ever be triggered " + "if 'per_chat=True'." + per_faq_link + ) + ) + assert str(recwarn[8].message) == ( + "If 'per_message=True', all entry points, state handlers, and fallbacks must be " + "'CallbackQueryHandler', since no other handlers have a message context." + + per_faq_link + ) + assert str(recwarn[9].message) == ( + "If 'per_message=False', 'CallbackQueryHandler' will not be tracked for " + "every message." + per_faq_link + ) + def test_per_message_but_not_per_chat_warning(self, recwarn): ConversationHandler( - entry_points=self.entry_points, + entry_points=[CallbackQueryHandler(self.code, "code")], states={ - self.THIRSTY: [InlineQueryHandler(hello)], - self.BREWING: [InlineQueryHandler(bye)], + self.BREWING: [CallbackQueryHandler(self.code, "code")], }, - fallbacks=self.fallbacks, - per_chat=True, + fallbacks=[CallbackQueryHandler(self.code, "code")], + per_message=True, + per_chat=False, ) assert len(recwarn) == 1 assert str(recwarn[0].message) == ( - "If 'per_chat=True', 'InlineQueryHandler' can not be used," - " since inline queries have no chat context." + "If 'per_message=True' is used, 'per_chat=True' should also be used, " + "since message IDs are not globally unique." ) assert recwarn[0].filename == __file__, "incorrect stacklevel!" From 09fda28604c009d7e2d097422fafedf085b57fec Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Fri, 29 Oct 2021 21:12:48 +0200 Subject: [PATCH 04/13] Fix: removed the dev mark --- tests/test_conversationhandler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 0388021199a..88b33574abe 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -1334,7 +1334,6 @@ def slowbrew(_update, context): assert handler.conversations.get((self.group.id, user1.id)) is None assert self.is_timeout - @pytest.mark.dev def test_handlers_generate_warning(self, recwarn): # this function tests all handler + per_* setting combinations. From 96928cd853730032884dd3b879793390b9f15181 Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Wed, 3 Nov 2021 12:21:03 +0100 Subject: [PATCH 05/13] Apply suggestions from code review Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- telegram/ext/_conversationhandler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index bfe923eef5a..b1c6b8bd9af 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -304,15 +304,15 @@ def __init__( for handler in all_handlers: if isinstance(handler, (StringCommandHandler, StringRegexHandler)): warn( - "The ConversationHandler does not work with non Telegram.Update type updates, " - "and you shouldn't use this handler for Telegram Update types.", + "The `ConversationHandler` only handles updates of type `telegram.Update`. " + f"{handler.__class__.__name__} handles updates of type `str`.", stacklevel=2, ) if isinstance(handler, PollHandler): warn( "PollHandler will never trigger in a conversation since it has no information " "about the chat or the user who voted in it. Do you mean the " - "'PollAnswerHandler'?", + "`PollAnswerHandler`?", stacklevel=2, ) # we can assume per_user is set. If its not, per_message has to be set, otherwise the @@ -332,8 +332,8 @@ def __init__( and self.per_chat ): warn( - "This Handler only has information about the user, so it wont ever be" - " triggered if 'per_chat=True'." + per_faq_link, + f"Updates handled by {handler.__class__.__name__} only have information about the user, so this handler wont ever be" + " triggered if `per_chat=True`." + per_faq_link, stacklevel=2, ) From 5bf1d14e39e238ba99f2a04e866428a9b0db7480 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Wed, 3 Nov 2021 12:44:54 +0100 Subject: [PATCH 06/13] Feat: Add TypeHandler to check also added a checkmark to the PR template --- .github/pull_request_template.md | 1 + telegram/ext/_conversationhandler.py | 39 ++++++++++------ tests/test_conversationhandler.py | 69 +++++++++++++++++++--------- 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6122e7fb647..91c16804b5d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -28,6 +28,7 @@ Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to - [ ] Link new and existing constants in docstrings instead of hard coded number and strings - [ ] Add new message types to `Message.effective_attachment` - [ ] Added new handlers for new update types + - [ ] Add the handlers to the warning loop in the `ConversationHandler` - [ ] Added new filters for new message (sub)types - [ ] Added or updated documentation for the changed class(es) and/or method(s) - [ ] Updated the Bot API version number in all places: `README.rst` and `README_RAW.rst` (including the badge), as well as `telegram.constants.BOT_API_VERSION` diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index b1c6b8bd9af..50489b20a17 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -47,6 +47,7 @@ InlineQueryHandler, StringCommandHandler, StringRegexHandler, + TypeHandler, ) from telegram._utils.warnings import warn from telegram.ext._utils.promise import Promise @@ -308,6 +309,12 @@ def __init__( f"{handler.__class__.__name__} handles updates of type `str`.", stacklevel=2, ) + if isinstance(handler, TypeHandler) and handler.type is not Update: + warn( + "The `ConversationHandler` only handles updates of type `telegram.Update`." + f" The TypeHandler is set to handle {handler.type.__name__}.", + stacklevel=2, + ) if isinstance(handler, PollHandler): warn( "PollHandler will never trigger in a conversation since it has no information " @@ -315,25 +322,27 @@ def __init__( "`PollAnswerHandler`?", stacklevel=2, ) - # we can assume per_user is set. If its not, per_message has to be set, otherwise the - # user would have faced the ValueError that all per_* settings cant be false. If - # per_message is set, the user will get a warning later. + if ( - isinstance( - handler, - ( - ShippingQueryHandler, - InlineQueryHandler, - ChosenInlineResultHandler, - PreCheckoutQueryHandler, - PollAnswerHandler, - ), + self.per_chat + and self.per_user + and ( + isinstance( + handler, + ( + ShippingQueryHandler, + InlineQueryHandler, + ChosenInlineResultHandler, + PreCheckoutQueryHandler, + PollAnswerHandler, + ), + ) ) - and self.per_chat ): warn( - f"Updates handled by {handler.__class__.__name__} only have information about the user, so this handler wont ever be" - " triggered if `per_chat=True`." + per_faq_link, + f"Updates handled by {handler.__class__.__name__} only have information about " + f"the user, so this handler wont ever be triggered if `per_chat=True`." + + per_faq_link, stacklevel=2, ) diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 88b33574abe..d7d8492693a 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -1341,14 +1341,23 @@ def test_handlers_generate_warning(self, recwarn): # otherwise only the first occurrence will be issued filterwarnings(action="always", category=PTBUserWarning) - # this conversation handler has the string, string_regex, and Pollhandler which should all - # generate a warning no matter the per_* setting. + # this class doesn't do anything, its just not the Update class + class NotUpdate: + pass + + # this conversation handler has the string, string_regex, Pollhandler and TypeHandler + # which should all generate a warning no matter the per_* setting. TypeHandler should + # not when the class is Update ConversationHandler( entry_points=[StringCommandHandler("code", self.code)], states={ - self.BREWING: [StringRegexHandler("code", self.code), PollHandler(self.code)], + self.BREWING: [ + StringRegexHandler("code", self.code), + PollHandler(self.code), + TypeHandler(NotUpdate, self.code), + ], }, - fallbacks=[], + fallbacks=[TypeHandler(Update, self.code)], ) # these handlers should all raise a warning when per_chat is True @@ -1386,40 +1395,58 @@ def test_handlers_generate_warning(self, recwarn): per_message=False, ) - # the overall handlers raising an error is 10 - assert len(recwarn) == 10 + # the overall handlers raising an error is 11 + assert len(recwarn) == 11 # now we test the messages, they are raised in the order they are inserted # into the conversation handler - assert str(recwarn[0].message) and str(recwarn[1].message) == ( - "The ConversationHandler does not work with non Telegram.Update type updates, " - "and you shouldn't use this handler for Telegram Update types." + assert str(recwarn[0].message) == ( + "The `ConversationHandler` only handles updates of type `telegram.Update`. " + "StringCommandHandler handles updates of type `str`." + ) + assert str(recwarn[1].message) == ( + "The `ConversationHandler` only handles updates of type `telegram.Update`. " + "StringRegexHandler handles updates of type `str`." ) assert str(recwarn[2].message) == ( "PollHandler will never trigger in a conversation since it has no information " "about the chat or the user who voted in it. Do you mean the " - "'PollAnswerHandler'?" + "`PollAnswerHandler`?" + ) + assert str(recwarn[3].message) == ( + "The `ConversationHandler` only handles updates of type `telegram.Update`. " + "The TypeHandler is set to handle NotUpdate." ) per_faq_link = ( " Read this FAQ entry to learn more about the per_* settings https://git.io/JtcyU." ) - assert ( - str(recwarn[3].message) - and str(recwarn[4].message) - and str(recwarn[5].message) - and str(recwarn[6].message) - and str(recwarn[7].message) - == ( - "This Handler only has information about the user, so it wont ever be triggered " - "if 'per_chat=True'." + per_faq_link - ) + + assert str(recwarn[4].message) == ( + "Updates handled by ShippingQueryHandler only have information about the user," + " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + ) + assert str(recwarn[5].message) == ( + "Updates handled by ChosenInlineResultHandler only have information about the user," + " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + ) + assert str(recwarn[6].message) == ( + "Updates handled by InlineQueryHandler only have information about the user," + " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + ) + assert str(recwarn[7].message) == ( + "Updates handled by PreCheckoutQueryHandler only have information about the user," + " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link ) assert str(recwarn[8].message) == ( + "Updates handled by PollAnswerHandler only have information about the user," + " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + ) + assert str(recwarn[9].message) == ( "If 'per_message=True', all entry points, state handlers, and fallbacks must be " "'CallbackQueryHandler', since no other handlers have a message context." + per_faq_link ) - assert str(recwarn[9].message) == ( + assert str(recwarn[10].message) == ( "If 'per_message=False', 'CallbackQueryHandler' will not be tracked for " "every message." + per_faq_link ) From acf0c13d48bff803fe5e7bb491ae2987abbe1df4 Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Thu, 4 Nov 2021 14:23:54 +0100 Subject: [PATCH 07/13] Apply suggestions from code review Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- telegram/ext/_conversationhandler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 50489b20a17..9f5ee4fd7c9 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -299,7 +299,7 @@ def __init__( # this link will be added to all warnings tied to per_* setting per_faq_link = ( - " Read this FAQ entry to learn more about the per_* settings https://git.io/JtcyU." + " Read this FAQ entry to learn more about the per_* settings: https://git.io/JtcyU." ) for handler in all_handlers: @@ -309,7 +309,7 @@ def __init__( f"{handler.__class__.__name__} handles updates of type `str`.", stacklevel=2, ) - if isinstance(handler, TypeHandler) and handler.type is not Update: + if isinstance(handler, TypeHandler) and not issubclass(handler.type, Update): warn( "The `ConversationHandler` only handles updates of type `telegram.Update`." f" The TypeHandler is set to handle {handler.type.__name__}.", @@ -341,7 +341,7 @@ def __init__( ): warn( f"Updates handled by {handler.__class__.__name__} only have information about " - f"the user, so this handler wont ever be triggered if `per_chat=True`." + f"the user, so this handler won't ever be triggered if `per_chat=True`." + per_faq_link, stacklevel=2, ) @@ -351,7 +351,7 @@ def __init__( warn( "If 'per_message=True', all entry points, state handlers, and fallbacks" " must be 'CallbackQueryHandler', since no other handlers " - "have a message context." + per_faq_link, + f"have a message context.{per_faq_link}", stacklevel=2, ) else: From cec35fb81be1f3b1791898869c125b31e9035eac Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Thu, 4 Nov 2021 14:27:23 +0100 Subject: [PATCH 08/13] Fix: Small design fixes from code review --- telegram/ext/_conversationhandler.py | 56 +++++++++++++--------------- tests/test_conversationhandler.py | 4 +- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 9f5ee4fd7c9..e1b20a749a9 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -309,13 +309,13 @@ def __init__( f"{handler.__class__.__name__} handles updates of type `str`.", stacklevel=2, ) - if isinstance(handler, TypeHandler) and not issubclass(handler.type, Update): + elif isinstance(handler, TypeHandler) and not issubclass(handler.type, Update): warn( "The `ConversationHandler` only handles updates of type `telegram.Update`." f" The TypeHandler is set to handle {handler.type.__name__}.", stacklevel=2, ) - if isinstance(handler, PollHandler): + elif isinstance(handler, PollHandler): warn( "PollHandler will never trigger in a conversation since it has no information " "about the chat or the user who voted in it. Do you mean the " @@ -323,20 +323,16 @@ def __init__( stacklevel=2, ) - if ( - self.per_chat - and self.per_user - and ( - isinstance( - handler, - ( - ShippingQueryHandler, - InlineQueryHandler, - ChosenInlineResultHandler, - PreCheckoutQueryHandler, - PollAnswerHandler, - ), - ) + elif self.per_chat and ( + isinstance( + handler, + ( + ShippingQueryHandler, + InlineQueryHandler, + ChosenInlineResultHandler, + PreCheckoutQueryHandler, + PollAnswerHandler, + ), ) ): warn( @@ -346,21 +342,19 @@ def __init__( stacklevel=2, ) - if self.per_message: - if not isinstance(handler, CallbackQueryHandler): - warn( - "If 'per_message=True', all entry points, state handlers, and fallbacks" - " must be 'CallbackQueryHandler', since no other handlers " - f"have a message context.{per_faq_link}", - stacklevel=2, - ) - else: - if isinstance(handler, CallbackQueryHandler): - warn( - "If 'per_message=False', 'CallbackQueryHandler' will not be " - "tracked for every message." + per_faq_link, - stacklevel=2, - ) + elif self.per_message and not isinstance(handler, CallbackQueryHandler): + warn( + "If 'per_message=True', all entry points, state handlers, and fallbacks" + " must be 'CallbackQueryHandler', since no other handlers " + f"have a message context.{per_faq_link}", + stacklevel=2, + ) + elif isinstance(handler, CallbackQueryHandler): + warn( + "If 'per_message=False', 'CallbackQueryHandler' will not be " + "tracked for every message." + per_faq_link, + stacklevel=2, + ) if self.conversation_timeout: for handler in all_handlers: diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index d7d8492693a..4e81e29a4f7 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -1335,7 +1335,9 @@ def slowbrew(_update, context): assert self.is_timeout def test_handlers_generate_warning(self, recwarn): - # this function tests all handler + per_* setting combinations. + """ + this function tests all handler + per_* setting combinations. + """ # the warning message action needs to be set to always, # otherwise only the first occurrence will be issued From f2803aa6a7888b047516cfc08d0f0b12e0ef3f5e Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:12:32 +0100 Subject: [PATCH 09/13] Fix: Test + Small bug --- telegram/ext/_conversationhandler.py | 6 +++--- tests/test_conversationhandler.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index e1b20a749a9..e1b18f9356b 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -338,7 +338,7 @@ def __init__( warn( f"Updates handled by {handler.__class__.__name__} only have information about " f"the user, so this handler won't ever be triggered if `per_chat=True`." - + per_faq_link, + f"{per_faq_link}", stacklevel=2, ) @@ -349,10 +349,10 @@ def __init__( f"have a message context.{per_faq_link}", stacklevel=2, ) - elif isinstance(handler, CallbackQueryHandler): + elif not self.per_message and isinstance(handler, CallbackQueryHandler): warn( "If 'per_message=False', 'CallbackQueryHandler' will not be " - "tracked for every message." + per_faq_link, + f"tracked for every message.{per_faq_link}", stacklevel=2, ) diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 4e81e29a4f7..2351b2f13ce 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -1420,28 +1420,28 @@ class NotUpdate: ) per_faq_link = ( - " Read this FAQ entry to learn more about the per_* settings https://git.io/JtcyU." + " Read this FAQ entry to learn more about the per_* settings: https://git.io/JtcyU." ) assert str(recwarn[4].message) == ( "Updates handled by ShippingQueryHandler only have information about the user," - " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + " so this handler won't ever be triggered if `per_chat=True`." + per_faq_link ) assert str(recwarn[5].message) == ( "Updates handled by ChosenInlineResultHandler only have information about the user," - " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + " so this handler won't ever be triggered if `per_chat=True`." + per_faq_link ) assert str(recwarn[6].message) == ( "Updates handled by InlineQueryHandler only have information about the user," - " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + " so this handler won't ever be triggered if `per_chat=True`." + per_faq_link ) assert str(recwarn[7].message) == ( "Updates handled by PreCheckoutQueryHandler only have information about the user," - " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + " so this handler won't ever be triggered if `per_chat=True`." + per_faq_link ) assert str(recwarn[8].message) == ( "Updates handled by PollAnswerHandler only have information about the user," - " so this handler wont ever be triggered if `per_chat=True`." + per_faq_link + " so this handler won't ever be triggered if `per_chat=True`." + per_faq_link ) assert str(recwarn[9].message) == ( "If 'per_message=True', all entry points, state handlers, and fallbacks must be " From 93c341ba6d7f993774653d1a03966b2cba33877a Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 6 Nov 2021 13:23:27 +0100 Subject: [PATCH 10/13] Feat: Minimize loops --- telegram/ext/_conversationhandler.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index e1b18f9356b..10647a54c0f 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -356,8 +356,7 @@ def __init__( stacklevel=2, ) - if self.conversation_timeout: - for handler in all_handlers: + elif self.conversation_timeout: if isinstance(handler, self.__class__): warn( "Using `conversation_timeout` with nested conversations is currently not " @@ -366,8 +365,7 @@ def __init__( stacklevel=2, ) - if self.run_async: - for handler in all_handlers: + elif self.run_async: handler.run_async = True @property From a6ef6e95a88300373fbc2842916bc803bb7e15c9 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 6 Nov 2021 14:40:15 +0100 Subject: [PATCH 11/13] Feat: Add stacklevel test --- tests/test_conversationhandler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 2351b2f13ce..5dd6240c3b6 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -1453,6 +1453,9 @@ class NotUpdate: "every message." + per_faq_link ) + for warning in recwarn: + assert warning.filename == __file__, "incorrect stacklevel!" + def test_per_message_but_not_per_chat_warning(self, recwarn): ConversationHandler( entry_points=[CallbackQueryHandler(self.code, "code")], From 7c2d5cd01c41a066d2f3e1a2b06c3b816ddffa05 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 6 Nov 2021 14:50:05 +0100 Subject: [PATCH 12/13] Fix: remove elif also added test for the nested conv warn --- telegram/ext/_conversationhandler.py | 4 ++-- tests/test_conversationhandler.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 10647a54c0f..61795ab45a5 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -356,7 +356,7 @@ def __init__( stacklevel=2, ) - elif self.conversation_timeout: + if self.conversation_timeout: if isinstance(handler, self.__class__): warn( "Using `conversation_timeout` with nested conversations is currently not " @@ -365,7 +365,7 @@ def __init__( stacklevel=2, ) - elif self.run_async: + if self.run_async: handler.run_async = True @property diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 5dd6240c3b6..a28878161a4 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -1397,8 +1397,26 @@ class NotUpdate: per_message=False, ) - # the overall handlers raising an error is 11 - assert len(recwarn) == 11 + # adding a nested conv to a conversation with timeout should warn + child = ConversationHandler( + entry_points=[CommandHandler("code", self.code)], + states={ + self.BREWING: [CommandHandler("code", self.code)], + }, + fallbacks=[CommandHandler("code", self.code)], + ) + + ConversationHandler( + entry_points=[CommandHandler("code", self.code)], + states={ + self.BREWING: [child], + }, + fallbacks=[CommandHandler("code", self.code)], + conversation_timeout=42, + ) + + # the overall handlers raising an error is 12 + assert len(recwarn) == 12 # now we test the messages, they are raised in the order they are inserted # into the conversation handler assert str(recwarn[0].message) == ( @@ -1452,7 +1470,13 @@ class NotUpdate: "If 'per_message=False', 'CallbackQueryHandler' will not be tracked for " "every message." + per_faq_link ) + assert str(recwarn[11].message) == ( + "Using `conversation_timeout` with nested conversations is currently not " + "supported. You can still try to use it, but it will likely behave differently" + " from what you expect." + ) + # this for loop checks if the correct stacklevel is used when generating the warning for warning in recwarn: assert warning.filename == __file__, "incorrect stacklevel!" From 6b233391272458ac21f62db8dd6c5a828fa8c03a Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 6 Nov 2021 14:57:30 +0100 Subject: [PATCH 13/13] Fix: Making deepsource happy ghrmpf --- telegram/ext/_conversationhandler.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 61795ab45a5..9aa0e51fd40 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -356,14 +356,13 @@ def __init__( stacklevel=2, ) - if self.conversation_timeout: - if isinstance(handler, self.__class__): - warn( - "Using `conversation_timeout` with nested conversations is currently not " - "supported. You can still try to use it, but it will likely behave " - "differently from what you expect.", - stacklevel=2, - ) + if self.conversation_timeout and isinstance(handler, self.__class__): + warn( + "Using `conversation_timeout` with nested conversations is currently not " + "supported. You can still try to use it, but it will likely behave " + "differently from what you expect.", + stacklevel=2, + ) if self.run_async: handler.run_async = True