From d19af21d621d2eb1082235e3338b70dbb809dc89 Mon Sep 17 00:00:00 2001 From: JosXa Date: Fri, 6 Oct 2017 18:06:46 +0200 Subject: [PATCH 01/18] Initial version of autowire --- telegram/ext/callbackqueryhandler.py | 15 ++++++++- telegram/ext/commandhandler.py | 9 ++++++ telegram/ext/handler.py | 42 +++++++++++++++++++------ telegram/ext/inlinequeryhandler.py | 9 ++++++ telegram/ext/messagehandler.py | 4 +++ telegram/ext/precheckoutqueryhandler.py | 9 ++++++ telegram/ext/regexhandler.py | 9 ++++++ telegram/ext/shippingqueryhandler.py | 9 ++++++ telegram/utils/inspection.py | 12 +++++++ 9 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 telegram/utils/inspection.py diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index 626b3875dcb..fcabf3d93a0 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -72,7 +72,18 @@ class CallbackQueryHandler(Handler): pass_groups (:obj:`bool`, optional): If the callback should be passed the result of ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. Default is ``False`` - pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of + pass_groupdict (:obj:`bool`, import inspect + +try: + def inspect_arguments(func): + args, _, _, defaults = inspect.getargspec(func) + # Filter out positional arguments + kwargs = args[:-len(defaults)] + return kwargs +except Warning: # `getargspec()` is deprecated in Python3 + def inspect_arguments(func): + _, varargs, _, _, _, _, _ = inspect.getfullargspec(func) + return varargsoptional): If the callback should be passed the result of ``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``. Default is ``False`` pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called @@ -84,6 +95,7 @@ class CallbackQueryHandler(Handler): def __init__(self, callback, + autowire=False, pass_update_queue=False, pass_job_queue=False, pattern=None, @@ -93,6 +105,7 @@ def __init__(self, pass_chat_data=False): super(CallbackQueryHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index b33dc7959f4..5722577dacb 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -39,6 +39,8 @@ class CommandHandler(Handler): Filters. allow_edited (:obj:`bool`): Optional. Determines Whether the handler should also accept edited messages. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_args (:obj:`bool`): Optional. Determines whether the handler should be passed ``args``. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be @@ -68,6 +70,11 @@ class CommandHandler(Handler): operators (& for and, | for or, ~ for not). allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept edited messages. Default is ``False``. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the arguments passed to the command as a keyword argument called ``args``. It will contain a list of strings, which is the text following the command split on single or @@ -92,6 +99,7 @@ def __init__(self, callback, filters=None, allow_edited=False, + autowire=False, pass_args=False, pass_update_queue=False, pass_job_queue=False, @@ -99,6 +107,7 @@ def __init__(self, pass_chat_data=False): super(CommandHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 99b89109def..21cc506506a 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -17,6 +17,9 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the base class for handlers as used by the Dispatcher.""" +import warnings + +from telegram.utils.inspection import get_positional_arguments class Handler(object): @@ -24,6 +27,8 @@ class Handler(object): Attributes: callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -43,6 +48,11 @@ class Handler(object): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -60,11 +70,15 @@ class Handler(object): def __init__(self, callback, + autowire=False, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, pass_chat_data=False): self.callback = callback + self.autowire = autowire + if self.autowire and any((pass_update_queue, pass_job_queue, pass_user_data, pass_chat_data)): + warnings.warn('If `autowire` is set to `True`, it is unnecessary to provide any `pass_*` flags.') self.pass_update_queue = pass_update_queue self.pass_job_queue = pass_job_queue self.pass_user_data = pass_user_data @@ -108,18 +122,28 @@ def collect_optional_args(self, dispatcher, update=None): """ optional_args = dict() - if self.pass_update_queue: - optional_args['update_queue'] = dispatcher.update_queue - if self.pass_job_queue: - optional_args['job_queue'] = dispatcher.job_queue - if self.pass_user_data or self.pass_chat_data: - chat = update.effective_chat - user = update.effective_user - + if self.autowire: + callback_args = get_positional_arguments(self.callback) + if 'update_queue' in callback_args: + optional_args['update_queue'] = dispatcher.update_queue + if 'job_queue' in callback_args: + optional_args['job_queue'] = dispatcher.job_queue + if 'user_data' in callback_args: + user = update.effective_user + optional_args['user_data'] = dispatcher.user_data[user.id if user else None] + if 'chat_data' in callback_args: + chat = update.effective_chat + optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None] + else: + if self.pass_update_queue: + optional_args['update_queue'] = dispatcher.update_queue + if self.pass_job_queue: + optional_args['job_queue'] = dispatcher.job_queue if self.pass_user_data: + user = update.effective_user optional_args['user_data'] = dispatcher.user_data[user.id if user else None] - if self.pass_chat_data: + chat = update.effective_chat optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None] return optional_args diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index 3e5ec7a0566..09759b6196f 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -33,6 +33,8 @@ class InlineQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -58,6 +60,11 @@ class InlineQueryHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -83,6 +90,7 @@ class InlineQueryHandler(Handler): def __init__(self, callback, + autowire=False, pass_update_queue=False, pass_job_queue=False, pattern=None, @@ -92,6 +100,7 @@ def __init__(self, pass_chat_data=False): super(InlineQueryHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 11c10803ceb..6c95919f42d 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -31,6 +31,8 @@ class MessageHandler(Handler): filters (:obj:`Filter`): Only allow updates with these Filters. See :mod:`telegram.ext.filters` for a full list of all available filters. callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -92,6 +94,7 @@ def __init__(self, filters, callback, allow_edited=False, + autowire=False, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, @@ -108,6 +111,7 @@ def __init__(self, super(MessageHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/ext/precheckoutqueryhandler.py b/telegram/ext/precheckoutqueryhandler.py index 5cccfdd54eb..c9f10b63b04 100644 --- a/telegram/ext/precheckoutqueryhandler.py +++ b/telegram/ext/precheckoutqueryhandler.py @@ -27,6 +27,8 @@ class PreCheckoutQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -46,6 +48,11 @@ class PreCheckoutQueryHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -63,12 +70,14 @@ class PreCheckoutQueryHandler(Handler): def __init__(self, callback, + autowire=False, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, pass_chat_data=False): super(PreCheckoutQueryHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index 9397ff3720e..b894679b6ff 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -38,6 +38,8 @@ class RegexHandler(Handler): Attributes: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the callback function. pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to @@ -62,6 +64,11 @@ class RegexHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_groups (:obj:`bool`, optional): If the callback should be passed the result of ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. Default is ``False`` @@ -97,6 +104,7 @@ class RegexHandler(Handler): def __init__(self, pattern, callback, + autowire=False, pass_groups=False, pass_groupdict=False, pass_update_queue=False, @@ -117,6 +125,7 @@ def __init__(self, super(RegexHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/ext/shippingqueryhandler.py b/telegram/ext/shippingqueryhandler.py index ab21197d306..05b2a3d9373 100644 --- a/telegram/ext/shippingqueryhandler.py +++ b/telegram/ext/shippingqueryhandler.py @@ -27,6 +27,8 @@ class ShippingQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -46,6 +48,11 @@ class ShippingQueryHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -63,12 +70,14 @@ class ShippingQueryHandler(Handler): def __init__(self, callback, + autowire=False, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, pass_chat_data=False): super(ShippingQueryHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, diff --git a/telegram/utils/inspection.py b/telegram/utils/inspection.py new file mode 100644 index 00000000000..b413536b8be --- /dev/null +++ b/telegram/utils/inspection.py @@ -0,0 +1,12 @@ +import inspect + +try: + def get_positional_arguments(func): + args, _, _, defaults = inspect.getargspec(func) + # Filter out positional arguments + kwargs = args[:-len(defaults)] + return kwargs +except Warning: # `getargspec()` is deprecated in Python3 + def get_positional_arguments(func): + _, varargs, _, _, _, _, _ = inspect.getfullargspec(func) + return varargs From bf15f54c95319786fbd9262bdbd4d046d2972595 Mon Sep 17 00:00:00 2001 From: JosXa Date: Fri, 6 Oct 2017 18:20:38 +0200 Subject: [PATCH 02/18] Changed order of keyword arguments in the hope that no user has treated pass_update_queue as a positional argument. --- telegram/ext/inlinequeryhandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index 09759b6196f..b08621a5a0e 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -91,9 +91,9 @@ class InlineQueryHandler(Handler): def __init__(self, callback, autowire=False, + pattern=None, pass_update_queue=False, pass_job_queue=False, - pattern=None, pass_groups=False, pass_groupdict=False, pass_user_data=False, From 1243680f6a68d74a9dac95f1a4fe79de692a7eaa Mon Sep 17 00:00:00 2001 From: JosXa Date: Fri, 6 Oct 2017 21:47:09 +0200 Subject: [PATCH 03/18] Stable autowiring functionality --- telegram/ext/callbackqueryhandler.py | 4 + telegram/ext/choseninlineresulthandler.py | 10 ++ telegram/ext/commandhandler.py | 13 ++- telegram/ext/handler.py | 126 +++++++++++++++++----- telegram/ext/inlinequeryhandler.py | 3 + telegram/ext/messagehandler.py | 11 +- telegram/ext/precheckoutqueryhandler.py | 2 + telegram/ext/regexhandler.py | 2 + telegram/ext/shippingqueryhandler.py | 2 + telegram/ext/stringcommandhandler.py | 3 + telegram/ext/stringregexhandler.py | 8 +- telegram/ext/typehandler.py | 14 ++- telegram/utils/inspection.py | 14 ++- 13 files changed, 169 insertions(+), 43 deletions(-) diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index fcabf3d93a0..ddbb0c5bcfc 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -118,6 +118,9 @@ def __init__(self, self.pass_groups = pass_groups self.pass_groupdict = pass_groupdict + if self.autowire: + self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', 'chat_data'}) + def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. @@ -145,6 +148,7 @@ def handle_update(self, update, dispatcher): """ optional_args = self.collect_optional_args(dispatcher, update) + if self.pattern: match = re.match(self.pattern, update.callback_query.data) diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py index 59d0ec0e640..c2262f41e10 100644 --- a/telegram/ext/choseninlineresulthandler.py +++ b/telegram/ext/choseninlineresulthandler.py @@ -28,6 +28,8 @@ class ChosenInlineResultHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -47,6 +49,10 @@ class ChosenInlineResultHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -64,16 +70,20 @@ class ChosenInlineResultHandler(Handler): def __init__(self, callback, + autowire=False, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, pass_chat_data=False): super(ChosenInlineResultHandler, self).__init__( callback, + autowire=autowire, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, pass_chat_data=pass_chat_data) + if self.autowire: + self.set_autowired_flags() def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index 5722577dacb..5dfa06aac2d 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -21,8 +21,8 @@ from future.utils import string_types -from .handler import Handler from telegram import Update +from .handler import Handler class CommandHandler(Handler): @@ -73,8 +73,7 @@ class CommandHandler(Handler): autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be inspected for positional arguments and pass objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with - ``autowire`` will yield - a warning. + ``autowire`` will yield a warning. pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the arguments passed to the command as a keyword argument called ``args``. It will contain a list of strings, which is the text following the command split on single or @@ -113,13 +112,16 @@ def __init__(self, pass_user_data=pass_user_data, pass_chat_data=pass_chat_data) + self.pass_args = pass_args + if self.autowire: + self.set_autowired_flags({'update_queue', 'job_queue', 'user_data', 'chat_data', 'args'}) + if isinstance(command, string_types): self.command = [command.lower()] else: self.command = [x.lower() for x in command] self.filters = filters self.allow_edited = allow_edited - self.pass_args = pass_args # We put this up here instead of with the rest of checking code # in check_update since we don't wanna spam a ton @@ -139,7 +141,7 @@ def check_update(self, update): """ if (isinstance(update, Update) - and (update.message or update.edited_message and self.allow_edited)): + and (update.message or update.edited_message and self.allow_edited)): message = update.message or update.edited_message if message.text: @@ -170,6 +172,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + self.set_autowired_flags({'args', 'update_queue', 'job_queue', 'user_data', 'chat_data'}) optional_args = self.collect_optional_args(dispatcher, update) message = update.message or update.edited_message diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 21cc506506a..11551cfb417 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -19,7 +19,7 @@ """This module contains the base class for handlers as used by the Dispatcher.""" import warnings -from telegram.utils.inspection import get_positional_arguments +from telegram.utils.inspection import inspect_arguments class Handler(object): @@ -77,12 +77,12 @@ def __init__(self, pass_chat_data=False): self.callback = callback self.autowire = autowire - if self.autowire and any((pass_update_queue, pass_job_queue, pass_user_data, pass_chat_data)): - warnings.warn('If `autowire` is set to `True`, it is unnecessary to provide any `pass_*` flags.') self.pass_update_queue = pass_update_queue self.pass_job_queue = pass_job_queue self.pass_user_data = pass_user_data self.pass_chat_data = pass_chat_data + self._autowire_initialized = False + self._callback_args = None def check_update(self, update): """ @@ -113,8 +113,77 @@ def handle_update(self, update, dispatcher): """ raise NotImplementedError + def __get_available_pass_flags(self): + """ + Used to provide warnings if the user decides to use `autowire` in conjunction with + `pass_*` flags, and to recalculate all flags. + + Getting objects dynamically is better than hard-coding all passable objects and setting + them to False in here, because the base class should not know about the existence of + passable objects that are only relevant to subclasses (e.g. args, groups, groupdict). + """ + return [f for f in dir(self) if f.startswith('pass_')] + + def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data', 'chat_data'}): + """ + + Make the passable arguments explicit as opposed to dynamically generated to be absolutely + safe that no arguments will be passed that are not allowed. + """ + + if not self.autowire: + raise ValueError("This handler is not autowired.") + + if self._autowire_initialized: + # In case that users decide to change their callback signatures at runtime, give the + # possibility to recalculate all flags. + for flag in self.__get_available_pass_flags(): + setattr(self, flag, False) + + all_passable_objects = {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args', 'groups', 'groupdict'} + + self._callback_args = inspect_arguments(self.callback) + + def should_pass_obj(name): + """ + Utility to determine whether a passable object is part of + the user handler's signature, makes sense in this context, + and is not explicitly set to `False`. + """ + is_requested = name in all_passable_objects and name in self._callback_args + if is_requested and name not in passable: + warnings.warn("The argument `{}` cannot be autowired since it is not available " + "on `{}s`.".format(name, type(self).__name__)) + return False + return is_requested + + # Check whether the user has set any `pass_*` flag to True in addition to `autowire` + for flag in self.__get_available_pass_flags(): + to_pass = bool(getattr(self, flag)) + if to_pass is True: + warnings.warn('If `autowire` is set to `True`, it is unnecessary ' + 'to provide the `{}` flag.'.format(flag)) + + if should_pass_obj('update_queue'): + self.pass_update_queue = True + if should_pass_obj('job_queue'): + self.pass_job_queue = True + if should_pass_obj('user_data'): + self.pass_user_data = True + if should_pass_obj('chat_data'): + self.pass_chat_data = True + if should_pass_obj('args'): + self.pass_args = True + if should_pass_obj('groups'): + self.pass_groups = True + if should_pass_obj('groupdict'): + self.pass_groupdict = True + + self._autowire_initialized = True + def collect_optional_args(self, dispatcher, update=None): - """Prepares the optional arguments that are the same for all types of handlers. + """ + Prepares the optional arguments that are the same for all types of handlers. Args: dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher. @@ -123,27 +192,32 @@ def collect_optional_args(self, dispatcher, update=None): optional_args = dict() if self.autowire: - callback_args = get_positional_arguments(self.callback) - if 'update_queue' in callback_args: - optional_args['update_queue'] = dispatcher.update_queue - if 'job_queue' in callback_args: - optional_args['job_queue'] = dispatcher.job_queue - if 'user_data' in callback_args: - user = update.effective_user - optional_args['user_data'] = dispatcher.user_data[user.id if user else None] - if 'chat_data' in callback_args: - chat = update.effective_chat - optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None] - else: - if self.pass_update_queue: - optional_args['update_queue'] = dispatcher.update_queue - if self.pass_job_queue: - optional_args['job_queue'] = dispatcher.job_queue - if self.pass_user_data: - user = update.effective_user - optional_args['user_data'] = dispatcher.user_data[user.id if user else None] - if self.pass_chat_data: - chat = update.effective_chat - optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None] + # Subclasses are responsible for calling `set_autowired_flags` in their __init__ + assert self._autowire_initialized + + if self.pass_update_queue: + optional_args['update_queue'] = dispatcher.update_queue + if self.pass_job_queue: + optional_args['job_queue'] = dispatcher.job_queue + if self.pass_user_data: + user = update.effective_user + optional_args['user_data'] = dispatcher.user_data[user.id if user else None] + if self.pass_chat_data: + chat = update.effective_chat + optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None] return optional_args + + def collect_bot_update_args(self, dispatcher, update): + if self.autowire: + # Subclasses are responsible for calling `set_autowired_flags` in their __init__ + assert self._autowire_initialized + + positional_args = [] + if 'bot' in self._callback_args: + positional_args.append(dispatcher.bot) + if 'update' in self._callback_args: + positional_args.append(update) + return positional_args + else: + return (dispatcher.bot, update) diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index b08621a5a0e..6f59646c111 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -112,6 +112,8 @@ def __init__(self, self.pattern = pattern self.pass_groups = pass_groups self.pass_groupdict = pass_groupdict + if self.autowire: + self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', 'chat_data'}) def check_update(self, update): """ @@ -142,6 +144,7 @@ def handle_update(self, update, dispatcher): """ optional_args = self.collect_optional_args(dispatcher, update) + if self.pattern: match = re.match(self.pattern, update.inline_query.query) diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 6c95919f42d..69cdd604cf7 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -64,6 +64,11 @@ class MessageHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and pass objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield + a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -121,6 +126,9 @@ def __init__(self, self.channel_post_updates = channel_post_updates self.edited_updates = edited_updates + if self.autowire: + self.set_autowired_flags() + # We put this up here instead of with the rest of checking code # in check_update since we don't wanna spam a ton if isinstance(self.filters, list): @@ -168,6 +176,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/precheckoutqueryhandler.py b/telegram/ext/precheckoutqueryhandler.py index c9f10b63b04..88e65cf6aad 100644 --- a/telegram/ext/precheckoutqueryhandler.py +++ b/telegram/ext/precheckoutqueryhandler.py @@ -82,6 +82,8 @@ def __init__(self, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, pass_chat_data=pass_chat_data) + if self.autowire: + self.set_autowired_flags() def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index b894679b6ff..1a3a7329833 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -137,6 +137,8 @@ def __init__(self, self.pattern = pattern self.pass_groups = pass_groups self.pass_groupdict = pass_groupdict + if self.autowire: + self.set_autowired_flags({'groups', 'groupdict', 'update_queue', 'job_queue', 'user_data', 'chat_data'}) self.allow_edited = allow_edited self.message_updates = message_updates self.channel_post_updates = channel_post_updates diff --git a/telegram/ext/shippingqueryhandler.py b/telegram/ext/shippingqueryhandler.py index 05b2a3d9373..b673ad628b8 100644 --- a/telegram/ext/shippingqueryhandler.py +++ b/telegram/ext/shippingqueryhandler.py @@ -82,6 +82,8 @@ def __init__(self, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, pass_chat_data=pass_chat_data) + if self.autowire: + self.set_autowired_flags() def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index ca77d1b4fd7..ee14e47cb0e 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -64,6 +64,7 @@ class StringCommandHandler(Handler): def __init__(self, command, callback, + autowire=False, pass_args=False, pass_update_queue=False, pass_job_queue=False): @@ -71,6 +72,8 @@ def __init__(self, callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) self.command = command self.pass_args = pass_args + if self.autowire: + self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', 'chat_data', 'args'}) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index 18e17bc1715..b9c8b10c2ae 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -72,12 +72,16 @@ class StringRegexHandler(Handler): def __init__(self, pattern, callback, + autowire=False, pass_groups=False, pass_groupdict=False, pass_update_queue=False, pass_job_queue=False): super(StringRegexHandler, self).__init__( - callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) + callback, + autowire=autowire, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) if isinstance(pattern, string_types): pattern = re.compile(pattern) @@ -85,6 +89,8 @@ def __init__(self, self.pattern = pattern self.pass_groups = pass_groups self.pass_groupdict = pass_groupdict + if self.autowire: + self.set_autowired_flags({'groups', 'groupdict', 'update_queue', 'job_queue'}) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/typehandler.py b/telegram/ext/typehandler.py index 5b4d1d33acb..ca6195fbc48 100644 --- a/telegram/ext/typehandler.py +++ b/telegram/ext/typehandler.py @@ -53,12 +53,22 @@ class TypeHandler(Handler): """ - def __init__(self, type, callback, strict=False, pass_update_queue=False, + def __init__(self, + type, + callback, + strict=False, + autowire=False, + pass_update_queue=False, pass_job_queue=False): super(TypeHandler, self).__init__( - callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) + callback, + autowire=autowire, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.type = type self.strict = strict + if self.autowire: + self.set_autowired_flags({'update_queue', 'job_queue'}) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/utils/inspection.py b/telegram/utils/inspection.py index b413536b8be..a6a6b7a845c 100644 --- a/telegram/utils/inspection.py +++ b/telegram/utils/inspection.py @@ -1,12 +1,10 @@ import inspect try: - def get_positional_arguments(func): - args, _, _, defaults = inspect.getargspec(func) - # Filter out positional arguments - kwargs = args[:-len(defaults)] - return kwargs + def inspect_arguments(func): + args, _, _, _ = inspect.getargspec(func) + return args except Warning: # `getargspec()` is deprecated in Python3 - def get_positional_arguments(func): - _, varargs, _, _, _, _, _ = inspect.getfullargspec(func) - return varargs + def inspect_arguments(func): + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations = inspect.getfullargspec(func) + return args From cafd9f88e36744c86b8ddf48f075757835d2d54c Mon Sep 17 00:00:00 2001 From: JosXa Date: Fri, 6 Oct 2017 22:21:04 +0200 Subject: [PATCH 04/18] Added autowiring example --- examples/autowiring.py | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 examples/autowiring.py diff --git a/examples/autowiring.py b/examples/autowiring.py new file mode 100644 index 00000000000..febd1678aaf --- /dev/null +++ b/examples/autowiring.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Bot that displays the autowiring functionality +# This program is dedicated to the public domain under the CC0 license. +""" +This bot shows how to use `autowire=True` in Handler definitions to save a lot of effort typing +the explicit pass_* flags. + +Usage: +Autowiring example: Try sending /start, "test", /data or something random. +Press Ctrl-C on the command line or send a signal to the process to stop the +bot. +""" + +import logging + +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters + +# Enable logging +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +def error(bot, update, error): + logger.warning('Update "%s" caused error "%s"' % (update, error)) + + +def start(bot, update, args): + query = ' '.join(args) # `args` is magically defined + if query: + update.message.reply_text(query) + else: + update.message.reply_text("Example: /start here I am") + + +def simple_update_only(update): + """ + A simple handler that only needs an `update` object. + Useful e.g. for /help commands that need to do nothing but reply with some text. + """ + update.message.reply_text("This should have produced an error " + "for the MessageHandler in group=1.") + + +def callback_with_data(bot, update, chat_data, user_data): + msg = 'Adding something to chat_data...\n' + chat_data['value'] = "I'm a chat_data value" + msg += chat_data['value'] + + msg += '\n\n' + + msg += 'Adding something to user_data...\n' + user_data['value'] = "I'm a user_data value" + msg += user_data['value'] + + update.message.reply_text(msg, quote=True) + + +def main(): + # Create the EventHandler and pass it your bot's token. + updater = Updater("TOKEN") + + # Get the dispatcher to register handlers + dp = updater.dispatcher + + # Inject the `args` parameter automagically + dp.add_handler(CommandHandler("start", start, autowire=True)) + + # This will raise an error because the bot argument is missing... + dp.add_handler(MessageHandler(Filters.text, simple_update_only), group=1) + # ... but with the autowiring capability, you can have callbacks with only an `update` argument. + dp.add_handler(MessageHandler(Filters.text, simple_update_only, autowire=True), group=2) + + # Passing `chat_data` and `user_data` explicitly... + dp.add_handler(CommandHandler("data", callback_with_data, + pass_chat_data=True, + pass_user_data=True)) + # ... is equivalent to passing them automagically. + dp.add_handler(CommandHandler("data", callback_with_data, autowire=True)) + + dp.add_error_handler(error) + updater.start_polling() + + # Run the bot until you press Ctrl-C or the process receives SIGINT, + # SIGTERM or SIGABRT. This should be used most of the time, since + # start_polling() is non-blocking and will stop the bot gracefully. + updater.idle() + + +if __name__ == '__main__': + main() From 10e4aee791c2a7ddfa01937575d7d4a5803e48b6 Mon Sep 17 00:00:00 2001 From: JosXa Date: Fri, 6 Oct 2017 22:35:09 +0200 Subject: [PATCH 05/18] Extended autowiring example --- examples/autowiring.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/autowiring.py b/examples/autowiring.py index febd1678aaf..56a226894dd 100644 --- a/examples/autowiring.py +++ b/examples/autowiring.py @@ -8,14 +8,14 @@ the explicit pass_* flags. Usage: -Autowiring example: Try sending /start, "test", /data or something random. +Autowiring example: Try sending /start, /data, "My name is Leandro", or some random text. Press Ctrl-C on the command line or send a signal to the process to stop the bot. """ import logging -from telegram.ext import Updater, CommandHandler, MessageHandler, Filters +from telegram.ext import Updater, CommandHandler, RegexHandler # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -39,7 +39,7 @@ def start(bot, update, args): def simple_update_only(update): """ A simple handler that only needs an `update` object. - Useful e.g. for /help commands that need to do nothing but reply with some text. + Useful e.g. for basic commands like /help that need to do nothing but reply with some text. """ update.message.reply_text("This should have produced an error " "for the MessageHandler in group=1.") @@ -59,6 +59,11 @@ def callback_with_data(bot, update, chat_data, user_data): update.message.reply_text(msg, quote=True) +def regex_with_groups(bot, update, groups, groupdict): + update.message.reply_text("Nice, your {} is {}.".format(groups[0], groups[1])) + update.message.reply_text('Groupdict: {}'.format(groupdict)) + + def main(): # Create the EventHandler and pass it your bot's token. updater = Updater("TOKEN") @@ -69,10 +74,16 @@ def main(): # Inject the `args` parameter automagically dp.add_handler(CommandHandler("start", start, autowire=True)) + # A RegexHandler example where `groups` and `groupdict` are passed automagically + # Examples: Send "My name is Leandro" or "My cat is blue". + dp.add_handler(RegexHandler(r'[Mm]y (?P.*) is (?P.*)', + regex_with_groups, + autowire=True)) + # This will raise an error because the bot argument is missing... - dp.add_handler(MessageHandler(Filters.text, simple_update_only), group=1) + dp.add_handler(CommandHandler('help', simple_update_only), group=1) # ... but with the autowiring capability, you can have callbacks with only an `update` argument. - dp.add_handler(MessageHandler(Filters.text, simple_update_only, autowire=True), group=2) + dp.add_handler(CommandHandler('help', simple_update_only, autowire=True), group=2) # Passing `chat_data` and `user_data` explicitly... dp.add_handler(CommandHandler("data", callback_with_data, From 44b922e05960dab21f5cd65edbfba842a15e6502 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 00:05:08 +0200 Subject: [PATCH 06/18] Added tests and more elaborate documentation for autowire --- telegram/ext/callbackqueryhandler.py | 26 ++++++-------- telegram/ext/choseninlineresulthandler.py | 5 +-- telegram/ext/commandhandler.py | 6 ++-- telegram/ext/handler.py | 43 ++++++++++++++++++----- telegram/ext/inlinequeryhandler.py | 21 +++++------ telegram/ext/messagehandler.py | 2 +- telegram/ext/precheckoutqueryhandler.py | 6 ++-- telegram/ext/regexhandler.py | 5 +-- telegram/ext/shippingqueryhandler.py | 5 +-- telegram/ext/stringcommandhandler.py | 10 ++++-- telegram/ext/stringregexhandler.py | 3 +- telegram/ext/typehandler.py | 9 ++++- tests/test_callbackqueryhandler.py | 32 +++++++++++++++++ tests/test_choseninlineresulthandler.py | 11 ++++++ tests/test_commandhandler.py | 12 +++++++ tests/test_inlinequeryhandler.py | 30 ++++++++++++++++ tests/test_messagehandler.py | 11 ++++++ tests/test_precheckoutqueryhandler.py | 11 ++++++ tests/test_regexhandler.py | 28 +++++++++++++++ tests/test_shippingqueryhandler.py | 11 ++++++ tests/test_stringcommandhandler.py | 26 ++++++++++++++ tests/test_stringregexhandler.py | 27 ++++++++++++++ tests/test_typehandler.py | 15 ++++++++ 23 files changed, 305 insertions(+), 50 deletions(-) diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index ddbb0c5bcfc..081611d4078 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -33,6 +33,8 @@ class CallbackQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -58,6 +60,10 @@ class CallbackQueryHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and be passed objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -72,25 +78,13 @@ class CallbackQueryHandler(Handler): pass_groups (:obj:`bool`, optional): If the callback should be passed the result of ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. Default is ``False`` - pass_groupdict (:obj:`bool`, import inspect - -try: - def inspect_arguments(func): - args, _, _, defaults = inspect.getargspec(func) - # Filter out positional arguments - kwargs = args[:-len(defaults)] - return kwargs -except Warning: # `getargspec()` is deprecated in Python3 - def inspect_arguments(func): - _, varargs, _, _, _, _, _ = inspect.getfullargspec(func) - return varargsoptional): If the callback should be passed the result of + pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of ``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``. Default is ``False`` pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. - """ def __init__(self, @@ -119,7 +113,8 @@ def __init__(self, self.pass_groupdict = pass_groupdict if self.autowire: - self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', 'chat_data'}) + self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', + 'chat_data', 'update_queue', 'job_queue'}) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. @@ -147,6 +142,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) if self.pattern: @@ -157,4 +153,4 @@ def handle_update(self, update, dispatcher): if self.pass_groupdict: optional_args['groupdict'] = match.groupdict() - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py index c2262f41e10..1180ba78ed2 100644 --- a/telegram/ext/choseninlineresulthandler.py +++ b/telegram/ext/choseninlineresulthandler.py @@ -50,7 +50,7 @@ class ChosenInlineResultHandler(Handler): It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called @@ -105,9 +105,10 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) # old non-PEP8 Handler methods m = "telegram.ChosenInlineResultHandler." diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index 5dfa06aac2d..8e84b4bb059 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -71,7 +71,7 @@ class CommandHandler(Handler): allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept edited messages. Default is ``False``. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the @@ -172,7 +172,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ - self.set_autowired_flags({'args', 'update_queue', 'job_queue', 'user_data', 'chat_data'}) + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) message = update.message or update.edited_message @@ -180,4 +180,4 @@ def handle_update(self, update, dispatcher): if self.pass_args: optional_args['args'] = message.text.split()[1:] - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 11551cfb417..bb104473328 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -23,7 +23,12 @@ class Handler(object): - """The base class for all update handlers. Create custom handlers by inheriting from it. + """ + The base class for all update handlers. Create custom handlers by inheriting from it. + + If your subclass needs the *autowiring* functionality, make sure to call ``set_autowired_flags`` + **after** initializing the ``pass_*`` members. The ``passable`` argument to this method denotes + all the flags your Handler supports, e.g. ``{'update_queue', 'job_queue', 'args'}``. Attributes: callback (:obj:`callable`): The callback function for this handler. @@ -49,10 +54,9 @@ class Handler(object): It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with - ``autowire`` will yield - a warning. + ``autowire`` will yield a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -116,7 +120,7 @@ def handle_update(self, update, dispatcher): def __get_available_pass_flags(self): """ Used to provide warnings if the user decides to use `autowire` in conjunction with - `pass_*` flags, and to recalculate all flags. + ``pass_*`` flags, and to recalculate all flags. Getting objects dynamically is better than hard-coding all passable objects and setting them to False in here, because the base class should not know about the existence of @@ -126,9 +130,15 @@ def __get_available_pass_flags(self): def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data', 'chat_data'}): """ + This method inspects the callback handler for used arguments. If it finds arguments that + are ``passable``, i.e. types that can also be passed by the various ``pass_*`` flags, + it sets the according flags to true. + + If the handler signature is prone to change at runtime for whatever reason, you can call + this method again to recalculate the flags to use. - Make the passable arguments explicit as opposed to dynamically generated to be absolutely - safe that no arguments will be passed that are not allowed. + The ``passable`` arguments are required to be explicit as opposed to dynamically generated + to be absolutely safe that no arguments will be passed that are not allowed. """ if not self.autowire: @@ -192,7 +202,8 @@ def collect_optional_args(self, dispatcher, update=None): optional_args = dict() if self.autowire: - # Subclasses are responsible for calling `set_autowired_flags` in their __init__ + # Subclasses are responsible for calling `set_autowired_flags` + # at the end of their __init__ assert self._autowire_initialized if self.pass_update_queue: @@ -209,6 +220,20 @@ def collect_optional_args(self, dispatcher, update=None): return optional_args def collect_bot_update_args(self, dispatcher, update): + """ + Prepares the positional arguments ``bot`` and/or ``update`` that are required for every + python-telegram-bot handler that is not **autowired**. If ``autowire`` is set to ``True``, + this method uses the inspected callback arguments to decide whether bot or update, + respectively, need to be passed. The order is always (bot, update). + + + Args: + dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher. + update (:class:`telegram.Update`): The update. + + Returns: + A tuple of bot, update, or both + """ if self.autowire: # Subclasses are responsible for calling `set_autowired_flags` in their __init__ assert self._autowire_initialized @@ -218,6 +243,6 @@ def collect_bot_update_args(self, dispatcher, update): positional_args.append(dispatcher.bot) if 'update' in self._callback_args: positional_args.append(update) - return positional_args + return tuple(positional_args) else: return (dispatcher.bot, update) diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index 6f59646c111..4aeb6bbd091 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -33,14 +33,14 @@ class InlineQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. + pattern (:obj:`str` | :obj:`Pattern`): Optional. Regex pattern to test + :attr:`telegram.InlineQuery.query` against. autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to the callback function. - pattern (:obj:`str` | :obj:`Pattern`): Optional. Regex pattern to test - :attr:`telegram.InlineQuery.query` against. pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the callback function. pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to @@ -60,8 +60,11 @@ class InlineQueryHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. + pattern (:obj:`str` | :obj:`Pattern`, optional): Regex pattern. If not ``None``, + ``re.match`` is used on :attr:`telegram.InlineQuery.query` to determine if an update + should be handled by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. @@ -73,9 +76,6 @@ class InlineQueryHandler(Handler): ``job_queue`` will be passed to the callback function. It will be a :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` which can be used to schedule new jobs. Default is ``False``. - pattern (:obj:`str` | :obj:`Pattern`, optional): Regex pattern. If not ``None``, - ``re.match`` is used on :attr:`telegram.InlineQuery.query` to determine if an update - should be handled by this handler. pass_groups (:obj:`bool`, optional): If the callback should be passed the result of ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. Default is ``False`` @@ -90,8 +90,8 @@ class InlineQueryHandler(Handler): def __init__(self, callback, - autowire=False, pattern=None, + autowire=False, pass_update_queue=False, pass_job_queue=False, pass_groups=False, @@ -113,7 +113,8 @@ def __init__(self, self.pass_groups = pass_groups self.pass_groupdict = pass_groupdict if self.autowire: - self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', 'chat_data'}) + self.set_autowired_flags(passable={'update_queue', 'job_queue', 'user_data', + 'chat_data', 'groups', 'groupdict'}) def check_update(self, update): """ @@ -142,7 +143,7 @@ def handle_update(self, update, dispatcher): update (:class:`telegram.Update`): Incoming telegram update. dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ - + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) if self.pattern: @@ -153,7 +154,7 @@ def handle_update(self, update, dispatcher): if self.pass_groupdict: optional_args['groupdict'] = match.groupdict() - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) # old non-PEP8 Handler methods m = "telegram.InlineQueryHandler." diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 69cdd604cf7..9fd4da2699d 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -65,7 +65,7 @@ class MessageHandler(Handler): It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. diff --git a/telegram/ext/precheckoutqueryhandler.py b/telegram/ext/precheckoutqueryhandler.py index 88e65cf6aad..20cc6f349e0 100644 --- a/telegram/ext/precheckoutqueryhandler.py +++ b/telegram/ext/precheckoutqueryhandler.py @@ -49,7 +49,7 @@ class PreCheckoutQueryHandler(Handler): It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. @@ -105,5 +105,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) - return self.callback(dispatcher.bot, update, **optional_args) + + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index 1a3a7329833..616a97aff14 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -65,7 +65,7 @@ class RegexHandler(Handler): It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. @@ -173,6 +173,7 @@ def handle_update(self, update, dispatcher): """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) match = re.match(self.pattern, update.effective_message.text) @@ -181,4 +182,4 @@ def handle_update(self, update, dispatcher): if self.pass_groupdict: optional_args['groupdict'] = match.groupdict() - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/shippingqueryhandler.py b/telegram/ext/shippingqueryhandler.py index b673ad628b8..3b173dce295 100644 --- a/telegram/ext/shippingqueryhandler.py +++ b/telegram/ext/shippingqueryhandler.py @@ -49,7 +49,7 @@ class ShippingQueryHandler(Handler): It will be called when the :attr:`check_update` has determined that an update should be processed by this handler. autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be - inspected for positional arguments and pass objects whose names match any of the + inspected for positional arguments and be passed objects whose names match any of the ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with ``autowire`` will yield a warning. @@ -105,5 +105,6 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher, update) - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index ee14e47cb0e..de0be61bc31 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -33,6 +33,8 @@ class StringCommandHandler(Handler): Attributes: command (:obj:`str`): The command this handler should listen for. callback (:obj:`callable`): The callback function for this handler. + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_args (:obj:`bool`): Optional. Determines whether the handler should be passed ``args``. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be @@ -46,6 +48,10 @@ class StringCommandHandler(Handler): callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. It will be called when the :attr:`check_update` has determined that a command should be processed by this handler. + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and be passed objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield a warning. pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the arguments passed to the command as a keyword argument called ``args``. It will contain a list of strings, which is the text following the command split on single or @@ -97,10 +103,10 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the command. """ - + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher) if self.pass_args: optional_args['args'] = update.split()[1:] - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index b9c8b10c2ae..d666d755f7d 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -112,6 +112,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the command. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher) match = re.match(self.pattern, update) @@ -120,4 +121,4 @@ def handle_update(self, update, dispatcher): if self.pass_groupdict: optional_args['groupdict'] = match.groupdict() - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/telegram/ext/typehandler.py b/telegram/ext/typehandler.py index ca6195fbc48..051fd7c632f 100644 --- a/telegram/ext/typehandler.py +++ b/telegram/ext/typehandler.py @@ -29,6 +29,8 @@ class TypeHandler(Handler): callback (:obj:`callable`): The callback function for this handler. strict (:obj:`bool`): Optional. Use ``type`` instead of ``isinstance``. Default is ``False`` + autowire (:obj:`bool`): Optional. Determines whether objects will be passed to the + callback function automatically. pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to @@ -42,6 +44,10 @@ class TypeHandler(Handler): processed by this handler. strict (:obj:`bool`, optional): Use ``type`` instead of ``isinstance``. Default is ``False`` + autowire (:obj:`bool`, optional): If set to ``True``, your callback handler will be + inspected for positional arguments and be passed objects whose names match any of the + ``pass_*`` flags of this Handler. Using any ``pass_*`` argument in conjunction with + ``autowire`` will yield a warning. pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``update_queue`` will be passed to the callback function. It will be the ``Queue`` instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` @@ -94,6 +100,7 @@ def handle_update(self, update, dispatcher): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ + positional_args = self.collect_bot_update_args(dispatcher, update) optional_args = self.collect_optional_args(dispatcher) - return self.callback(dispatcher.bot, update, **optional_args) + return self.callback(*positional_args, **optional_args) diff --git a/tests/test_callbackqueryhandler.py b/tests/test_callbackqueryhandler.py index 8160bcf9345..39d5b9637a5 100644 --- a/tests/test_callbackqueryhandler.py +++ b/tests/test_callbackqueryhandler.py @@ -74,6 +74,13 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def autowire_callback_1(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + + def autowire_callback_2(self, bot, update, job_queue): + self.test_flag = all(x is not None for x in (bot, update, job_queue)) + def callback_group(self, bot, update, groups=None, groupdict=None): if groups is not None: self.test_flag = groups == ('t', ' data') @@ -164,6 +171,31 @@ def test_pass_job_or_update_queue(self, dp, callback_query): dp.process_update(callback_query) assert self.test_flag + def test_autowire(self, dp, callback_query): + handler = CallbackQueryHandler(self.autowire_callback_1, autowire=True) + dp.add_handler(handler) + + dp.process_update(callback_query) + assert self.test_flag + + dp.remove_handler(handler) + handler = CallbackQueryHandler(self.autowire_callback_2, autowire=True) + dp.add_handler(handler) + + self.test_flag = False + dp.process_update(callback_query) + assert self.test_flag + + dp.remove_handler(handler) + handler = CallbackQueryHandler(self.callback_group, + pattern='(?P.*)est(?P.*)', + pass_groups=True) + dp.add_handler(handler) + + dp.process_update(callback_query) + assert self.test_flag + + def test_other_update_types(self, false_update): handler = CallbackQueryHandler(self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_choseninlineresulthandler.py b/tests/test_choseninlineresulthandler.py index 2606c536e7e..54f1d73e43c 100644 --- a/tests/test_choseninlineresulthandler.py +++ b/tests/test_choseninlineresulthandler.py @@ -78,6 +78,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def autowire_callback(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + def test_basic(self, dp, chosen_inline_result): handler = ChosenInlineResultHandler(self.callback_basic) dp.add_handler(handler) @@ -134,6 +138,13 @@ def test_pass_job_or_update_queue(self, dp, chosen_inline_result): dp.process_update(chosen_inline_result) assert self.test_flag + def test_autowire(self, dp, chosen_inline_result): + handler = ChosenInlineResultHandler(self.autowire_callback, autowire=True) + dp.add_handler(handler) + + dp.process_update(chosen_inline_result) + assert self.test_flag + def test_other_update_types(self, false_update): handler = ChosenInlineResultHandler(self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_commandhandler.py b/tests/test_commandhandler.py index fb1aafa1fcf..a08f5e5ce27 100644 --- a/tests/test_commandhandler.py +++ b/tests/test_commandhandler.py @@ -75,6 +75,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def autowire_callback(self, update, args, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, args, job_queue, + update_queue, chat_data, user_data)) + def ch_callback_args(self, bot, update, args): if update.message.text == '/test': self.test_flag = len(args) == 0 @@ -215,6 +219,14 @@ def test_pass_job_or_update_queue(self, dp, message): dp.process_update(Update(0, message=message)) assert self.test_flag + def test_autowire(self, dp, message): + handler = CommandHandler('test', self.autowire_callback, autowire=True) + dp.add_handler(handler) + + message.text = '/test abc' + dp.process_update(Update(0, message=message)) + assert self.test_flag + def test_other_update_types(self, false_update): handler = CommandHandler('test', self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_inlinequeryhandler.py b/tests/test_inlinequeryhandler.py index 3a370845bcb..ae8d706343a 100644 --- a/tests/test_inlinequeryhandler.py +++ b/tests/test_inlinequeryhandler.py @@ -78,6 +78,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def callback_autowire(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + def callback_group(self, bot, update, groups=None, groupdict=None): if groups is not None: self.test_flag = groups == ('t', ' query') @@ -168,6 +172,32 @@ def test_pass_job_or_update_queue(self, dp, inline_query): dp.process_update(inline_query) assert self.test_flag + def test_autowire(self, dp, inline_query): + handler = InlineQueryHandler(self.callback_autowire, autowire=True) + dp.add_handler(handler) + + dp.process_update(inline_query) + assert self.test_flag + + def test_autowire_group(self, dp, inline_query): + handler = InlineQueryHandler(self.callback_group, + pattern='(?P.*)est(?P.*)', + autowire=True) + dp.add_handler(handler) + + dp.process_update(inline_query) + assert self.test_flag + + dp.remove_handler(handler) + handler = InlineQueryHandler(self.callback_group, + pattern='(?P.*)est(?P.*)', + autowire=True) + dp.add_handler(handler) + + self.test_flag = False + dp.process_update(inline_query) + assert self.test_flag + def test_other_update_types(self, false_update): handler = InlineQueryHandler(self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_messagehandler.py b/tests/test_messagehandler.py index 114d03ed6ea..193d6ddec13 100644 --- a/tests/test_messagehandler.py +++ b/tests/test_messagehandler.py @@ -72,6 +72,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def callback_autowire(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + def test_basic(self, dp, message): handler = MessageHandler(None, self.callback_basic) dp.add_handler(handler) @@ -179,6 +183,13 @@ def test_pass_job_or_update_queue(self, dp, message): dp.process_update(Update(0, message=message)) assert self.test_flag + def test_autowire(self, dp, message): + handler = MessageHandler(None, self.callback_autowire, autowire=True) + dp.add_handler(handler) + + dp.process_update(Update(0, message=message)) + assert self.test_flag + def test_other_update_types(self, false_update): handler = MessageHandler(None, self.callback_basic, edited_updates=True) assert not handler.check_update(false_update) diff --git a/tests/test_precheckoutqueryhandler.py b/tests/test_precheckoutqueryhandler.py index b6a9c3a60b8..135c437361c 100644 --- a/tests/test_precheckoutqueryhandler.py +++ b/tests/test_precheckoutqueryhandler.py @@ -77,6 +77,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def callback_autowire(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + def test_basic(self, dp, pre_checkout_query): handler = PreCheckoutQueryHandler(self.callback_basic) dp.add_handler(handler) @@ -133,6 +137,13 @@ def test_pass_job_or_update_queue(self, dp, pre_checkout_query): dp.process_update(pre_checkout_query) assert self.test_flag + def test_autowire(self, dp, pre_checkout_query): + handler = PreCheckoutQueryHandler(self.callback_autowire, autowire=True) + dp.add_handler(handler) + + dp.process_update(pre_checkout_query) + assert self.test_flag + def test_other_update_types(self, false_update): handler = PreCheckoutQueryHandler(self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_regexhandler.py b/tests/test_regexhandler.py index bd87bc705e4..9e018fbb1ad 100644 --- a/tests/test_regexhandler.py +++ b/tests/test_regexhandler.py @@ -72,6 +72,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def callback_autowire(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + def callback_group(self, bot, update, groups=None, groupdict=None): if groups is not None: self.test_flag = groups == ('t', ' message') @@ -201,6 +205,30 @@ def test_pass_job_or_update_queue(self, dp, message): dp.process_update(Update(0, message=message)) assert self.test_flag + def test_autowire(self, dp, message): + handler = RegexHandler('.*', self.callback_autowire, autowire=True) + dp.add_handler(handler) + + dp.process_update(Update(0, message=message)) + assert self.test_flag + + def test_autowire_group_dict(self, dp, message): + handler = RegexHandler('(?P.*)est(?P.*)', self.callback_group, + autowire=True) + dp.add_handler(handler) + + dp.process_update(Update(0, message)) + assert self.test_flag + + dp.remove_handler(handler) + handler = RegexHandler('(?P.*)est(?P.*)', self.callback_group, + autowire=True) + dp.add_handler(handler) + + self.test_flag = False + dp.process_update(Update(0, message)) + assert self.test_flag + def test_other_update_types(self, false_update): handler = RegexHandler('.*', self.callback_basic, edited_updates=True) assert not handler.check_update(false_update) diff --git a/tests/test_shippingqueryhandler.py b/tests/test_shippingqueryhandler.py index 47e6975b1be..43cd71a407a 100644 --- a/tests/test_shippingqueryhandler.py +++ b/tests/test_shippingqueryhandler.py @@ -78,6 +78,10 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def callback_autowire(self, update, job_queue, update_queue, chat_data, user_data): + self.test_flag = all(x is not None for x in (update, job_queue, + update_queue, chat_data, user_data)) + def test_basic(self, dp, shiping_query): handler = ShippingQueryHandler(self.callback_basic) dp.add_handler(handler) @@ -134,6 +138,13 @@ def test_pass_job_or_update_queue(self, dp, shiping_query): dp.process_update(shiping_query) assert self.test_flag + def test_autowire(self, dp, shiping_query): + handler = ShippingQueryHandler(self.callback_autowire, autowire=True) + dp.add_handler(handler) + + dp.process_update(shiping_query) + assert self.test_flag + def test_other_update_types(self, false_update): handler = ShippingQueryHandler(self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_stringcommandhandler.py b/tests/test_stringcommandhandler.py index 60fa786cb23..3e837577dd8 100644 --- a/tests/test_stringcommandhandler.py +++ b/tests/test_stringcommandhandler.py @@ -118,6 +118,32 @@ def test_pass_job_or_update_queue(self, dp): dp.process_update('/test') assert self.test_flag + def test_autowire(self, dp): + handler = StringCommandHandler('test', self.callback_queue_1, autowire=True) + dp.add_handler(handler) + + dp.process_update('/test') + assert self.test_flag + + dp.remove_handler(handler) + handler = StringCommandHandler('test', self.callback_queue_2, autowire=True) + dp.add_handler(handler) + + self.test_flag = False + dp.process_update('/test') + assert self.test_flag + + dp.remove_handler(handler) + handler = StringCommandHandler('test', self.sch_callback_args, autowire=True) + dp.add_handler(handler) + + dp.process_update('/test') + assert self.test_flag + + self.test_flag = False + dp.process_update('/test one two') + assert self.test_flag + def test_other_update_types(self, false_update): handler = StringCommandHandler('test', self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_stringregexhandler.py b/tests/test_stringregexhandler.py index 9d0ed7829f8..45d7367ab2f 100644 --- a/tests/test_stringregexhandler.py +++ b/tests/test_stringregexhandler.py @@ -65,6 +65,9 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None): def callback_queue_2(self, bot, update, job_queue=None, update_queue=None): self.test_flag = (job_queue is not None) and (update_queue is not None) + def callback_autowire(self, update, job_queue, update_queue): + self.test_flag = all(x is not None for x in (update, job_queue, update_queue)) + def callback_group(self, bot, update, groups=None, groupdict=None): if groups is not None: self.test_flag = groups == ('t', ' message') @@ -122,6 +125,30 @@ def test_pass_job_or_update_queue(self, dp): dp.process_update('test') assert self.test_flag + def test_autowire(self, dp): + handler = StringRegexHandler('test', self.callback_autowire, autowire=True) + dp.add_handler(handler) + + dp.process_update('test') + assert self.test_flag + + def test_autowire_groups_and_groupdict(self, dp): + handler = StringRegexHandler('(?P.*)est(?P.*)', self.callback_group, + autowire=True) + dp.add_handler(handler) + + dp.process_update('test message') + assert self.test_flag + + dp.remove_handler(handler) + handler = StringRegexHandler('(?P.*)est(?P.*)', self.callback_group, + autowire=True) + dp.add_handler(handler) + + self.test_flag = False + dp.process_update('test message') + assert self.test_flag + def test_other_update_types(self, false_update): handler = StringRegexHandler('test', self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_typehandler.py b/tests/test_typehandler.py index 43119123628..59b5edd0e5a 100644 --- a/tests/test_typehandler.py +++ b/tests/test_typehandler.py @@ -80,3 +80,18 @@ def test_pass_job_or_update_queue(self, dp): self.test_flag = False dp.process_update({'a': 1, 'b': 2}) assert self.test_flag + + def autowire_job_update(self, dp): + handler = TypeHandler(dict, self.callback_queue_1, autowire=True) + dp.add_handler(handler) + + dp.process_update({'a': 1, 'b': 2}) + assert self.test_flag + + dp.remove_handler(handler) + handler = TypeHandler(dict, self.callback_queue_2, autowire=True) + dp.add_handler(handler) + + self.test_flag = False + dp.process_update({'a': 1, 'b': 2}) + assert self.test_flag From 9c6efb8b6ba2b0d9c0da5ca59c48714e805d5cdc Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 00:13:46 +0200 Subject: [PATCH 07/18] Fixed StringCommandHandler pass-flags --- telegram/ext/stringcommandhandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index de0be61bc31..672faa1f65e 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -79,7 +79,7 @@ def __init__(self, self.command = command self.pass_args = pass_args if self.autowire: - self.set_autowired_flags(passable={'groups', 'groupdict', 'user_data', 'chat_data', 'args'}) + self.set_autowired_flags(passable={'update_queue', 'job_queue', 'args'}) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. From 6f02143451e3a38a3533a5be4c0f9f7ba81d44e4 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 00:41:47 +0200 Subject: [PATCH 08/18] Comment changed --- examples/autowiring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/autowiring.py b/examples/autowiring.py index 56a226894dd..ed4151dfecc 100644 --- a/examples/autowiring.py +++ b/examples/autowiring.py @@ -65,7 +65,7 @@ def regex_with_groups(bot, update, groups, groupdict): def main(): - # Create the EventHandler and pass it your bot's token. + # Create the Updater and pass it your bot's token. updater = Updater("TOKEN") # Get the dispatcher to register handlers From 284e6068f62f87e4c69338d41007c2496d17dbfc Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 00:42:03 +0200 Subject: [PATCH 09/18] Fixing tests that only fail on Travis --- telegram/ext/stringcommandhandler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index 672faa1f65e..22633b9f701 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -75,7 +75,10 @@ def __init__(self, pass_update_queue=False, pass_job_queue=False): super(StringCommandHandler, self).__init__( - callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) + callback, + autowire=autowire, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.command = command self.pass_args = pass_args if self.autowire: From cd4aded091a127d37a5ad91e190559445e0cf00b Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 00:47:25 +0200 Subject: [PATCH 10/18] Reduced method complexity --- telegram/ext/handler.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index bb104473328..0b7e521ad0d 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -117,6 +117,14 @@ def handle_update(self, update, dispatcher): """ raise NotImplementedError + def __warn_autowire(self): + """ Warn if the user has set any `pass_*` flags to True in addition to `autowire` """ + for flag in self.__get_available_pass_flags(): + to_pass = bool(getattr(self, flag)) + if to_pass is True: + warnings.warn('If `autowire` is set to `True`, it is unnecessary ' + 'to provide the `{}` flag.'.format(flag)) + def __get_available_pass_flags(self): """ Used to provide warnings if the user decides to use `autowire` in conjunction with @@ -143,6 +151,7 @@ def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data' if not self.autowire: raise ValueError("This handler is not autowired.") + self.__warn_autowire() if self._autowire_initialized: # In case that users decide to change their callback signatures at runtime, give the @@ -167,13 +176,6 @@ def should_pass_obj(name): return False return is_requested - # Check whether the user has set any `pass_*` flag to True in addition to `autowire` - for flag in self.__get_available_pass_flags(): - to_pass = bool(getattr(self, flag)) - if to_pass is True: - warnings.warn('If `autowire` is set to `True`, it is unnecessary ' - 'to provide the `{}` flag.'.format(flag)) - if should_pass_obj('update_queue'): self.pass_update_queue = True if should_pass_obj('job_queue'): From 05c16a19a52a09aeb78c29e9c0300da53151a92d Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 01:02:39 +0200 Subject: [PATCH 11/18] Fixed small test problems --- tests/test_stringcommandhandler.py | 7 ------- tests/test_typehandler.py | 8 -------- 2 files changed, 15 deletions(-) diff --git a/tests/test_stringcommandhandler.py b/tests/test_stringcommandhandler.py index 3e837577dd8..d36d065ca34 100644 --- a/tests/test_stringcommandhandler.py +++ b/tests/test_stringcommandhandler.py @@ -119,13 +119,6 @@ def test_pass_job_or_update_queue(self, dp): assert self.test_flag def test_autowire(self, dp): - handler = StringCommandHandler('test', self.callback_queue_1, autowire=True) - dp.add_handler(handler) - - dp.process_update('/test') - assert self.test_flag - - dp.remove_handler(handler) handler = StringCommandHandler('test', self.callback_queue_2, autowire=True) dp.add_handler(handler) diff --git a/tests/test_typehandler.py b/tests/test_typehandler.py index 59b5edd0e5a..4f34ea84dcd 100644 --- a/tests/test_typehandler.py +++ b/tests/test_typehandler.py @@ -82,16 +82,8 @@ def test_pass_job_or_update_queue(self, dp): assert self.test_flag def autowire_job_update(self, dp): - handler = TypeHandler(dict, self.callback_queue_1, autowire=True) - dp.add_handler(handler) - - dp.process_update({'a': 1, 'b': 2}) - assert self.test_flag - - dp.remove_handler(handler) handler = TypeHandler(dict, self.callback_queue_2, autowire=True) dp.add_handler(handler) - self.test_flag = False dp.process_update({'a': 1, 'b': 2}) assert self.test_flag From b388d1a8f1eaa28a6b538f916141dcfe38b7b3a4 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 01:46:14 +0200 Subject: [PATCH 12/18] Implemented test for base class `Handler` and fixed order of instructions in handler.py --- telegram/ext/handler.py | 52 +++++++++-------- tests/test_handler.py | 122 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 tests/test_handler.py diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 0b7e521ad0d..7467b5ee912 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -87,6 +87,7 @@ def __init__(self, self.pass_chat_data = pass_chat_data self._autowire_initialized = False self._callback_args = None + self._passable = None def check_update(self, update): """ @@ -119,13 +120,13 @@ def handle_update(self, update, dispatcher): def __warn_autowire(self): """ Warn if the user has set any `pass_*` flags to True in addition to `autowire` """ - for flag in self.__get_available_pass_flags(): + for flag in self._get_available_pass_flags(): to_pass = bool(getattr(self, flag)) if to_pass is True: warnings.warn('If `autowire` is set to `True`, it is unnecessary ' 'to provide the `{}` flag.'.format(flag)) - def __get_available_pass_flags(self): + def _get_available_pass_flags(self): """ Used to provide warnings if the user decides to use `autowire` in conjunction with ``pass_*`` flags, and to recalculate all flags. @@ -136,6 +137,20 @@ def __get_available_pass_flags(self): """ return [f for f in dir(self) if f.startswith('pass_')] + def __should_pass_obj(self, name): + """ + Utility to determine whether a passable object is part of + the user handler's signature, makes sense in this context, + and is not explicitly set to `False`. + """ + all_passable_objects = {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args', 'groups', 'groupdict'} + is_requested = name in all_passable_objects and name in self._callback_args + if is_requested and name not in self._passable: + warnings.warn("The argument `{}` cannot be autowired since it is not available " + "on `{}s`.".format(name, type(self).__name__)) + return False + return is_requested + def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data', 'chat_data'}): """ This method inspects the callback handler for used arguments. If it finds arguments that @@ -148,47 +163,34 @@ def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data' The ``passable`` arguments are required to be explicit as opposed to dynamically generated to be absolutely safe that no arguments will be passed that are not allowed. """ + self._passable = passable if not self.autowire: raise ValueError("This handler is not autowired.") - self.__warn_autowire() if self._autowire_initialized: # In case that users decide to change their callback signatures at runtime, give the # possibility to recalculate all flags. - for flag in self.__get_available_pass_flags(): + for flag in self._get_available_pass_flags(): setattr(self, flag, False) - all_passable_objects = {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args', 'groups', 'groupdict'} + self.__warn_autowire() self._callback_args = inspect_arguments(self.callback) - def should_pass_obj(name): - """ - Utility to determine whether a passable object is part of - the user handler's signature, makes sense in this context, - and is not explicitly set to `False`. - """ - is_requested = name in all_passable_objects and name in self._callback_args - if is_requested and name not in passable: - warnings.warn("The argument `{}` cannot be autowired since it is not available " - "on `{}s`.".format(name, type(self).__name__)) - return False - return is_requested - - if should_pass_obj('update_queue'): + if self.__should_pass_obj('update_queue'): self.pass_update_queue = True - if should_pass_obj('job_queue'): + if self.__should_pass_obj('job_queue'): self.pass_job_queue = True - if should_pass_obj('user_data'): + if self.__should_pass_obj('user_data'): self.pass_user_data = True - if should_pass_obj('chat_data'): + if self.__should_pass_obj('chat_data'): self.pass_chat_data = True - if should_pass_obj('args'): + if self.__should_pass_obj('args'): self.pass_args = True - if should_pass_obj('groups'): + if self.__should_pass_obj('groups'): self.pass_groups = True - if should_pass_obj('groupdict'): + if self.__should_pass_obj('groupdict'): self.pass_groupdict = True self._autowire_initialized = True diff --git a/tests/test_handler.py b/tests/test_handler.py new file mode 100644 index 00000000000..342d11accd3 --- /dev/null +++ b/tests/test_handler.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2017 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +import pytest + +from telegram.ext import Handler + + +class TestHandler(object): + test_flag = False + + @pytest.fixture(autouse=True) + def reset(self): + self.test_flag = False + + def callback_basic(self, bot, update): + pass + + def callback_some_passable(self, bot, update, update_queue, chat_data): + pass + + def callback_all_passable(self, bot, update, update_queue, job_queue, chat_data, user_data): + pass + + def test_set_autowired_flags_all(self): + handler = Handler(self.callback_all_passable, autowire=True) + assert handler._autowire_initialized is False + assert handler.pass_update_queue is False + assert handler.pass_job_queue is False + assert handler.pass_chat_data is False + assert handler.pass_user_data is False + + handler.set_autowired_flags() + + assert handler._autowire_initialized is True + assert handler.pass_update_queue is True + assert handler.pass_job_queue is True + assert handler.pass_chat_data is True + assert handler.pass_user_data is True + + def test_set_autowired_flags_some(self): + handler = Handler(self.callback_some_passable, autowire=True) + assert handler.pass_update_queue is False + assert handler.pass_chat_data is False + + handler.set_autowired_flags() + + assert handler._autowire_initialized is True + assert handler.pass_update_queue is True + assert handler.pass_chat_data is True + + def test_set_autowired_flags_wrong(self): + handler = Handler(self.callback_all_passable, autowire=True) + with pytest.raises(UserWarning): + handler.set_autowired_flags({'kektus'}) + with pytest.raises(UserWarning): + handler.set_autowired_flags({'chat_data', 'kektus'}) + with pytest.raises(UserWarning): + handler.set_autowired_flags({'bot', 'update'}) + + def test_autowire_and_pass(self): + handler = Handler(self.callback_all_passable, autowire=True, pass_chat_data=True) + with pytest.raises(UserWarning): + handler.set_autowired_flags() + + def test_not_autowired_set_flags(self): + handler = Handler(self.callback_all_passable, autowire=False) + with pytest.raises(ValueError): + handler.set_autowired_flags() + + def test_autowire_reinitialize(self): + handler = Handler(self.callback_all_passable, autowire=True) + assert handler._autowire_initialized is False + assert handler.pass_update_queue is False + assert handler.pass_job_queue is False + assert handler.pass_chat_data is False + assert handler.pass_user_data is False + + handler.set_autowired_flags() + + assert handler._autowire_initialized is True + assert handler.pass_update_queue is True + assert handler.pass_job_queue is True + assert handler.pass_chat_data is True + assert handler.pass_user_data is True + + handler.callback = self.callback_some_passable + handler.set_autowired_flags() + + assert handler._autowire_initialized is True + assert handler.pass_update_queue is True + assert handler.pass_job_queue is False + assert handler.pass_chat_data is True + assert handler.pass_user_data is False + + def test_get_available_pass_flags(self): + handler = Handler(self.callback_all_passable, autowire=True) + assert handler.pass_update_queue is False + assert handler.pass_job_queue is False + assert handler.pass_chat_data is False + assert handler.pass_user_data is False + + handler.set_autowired_flags() + + assert set(handler._get_available_pass_flags()) == {'pass_update_queue', 'pass_job_queue', 'pass_chat_data', + 'pass_user_data'} From 5da1bb1ca88fb108e35346b39bcae80df2e74555 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 02:15:24 +0200 Subject: [PATCH 13/18] Switched to setattr() to reduce cyclomatic complexity --- telegram/ext/handler.py | 26 ++++++++++---------------- tests/test_handler.py | 1 - 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 7467b5ee912..749c757a9a5 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -72,6 +72,8 @@ class Handler(object): """ + PASSABLE_OBJECTS = {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args', 'groups', 'groupdict'} + def __init__(self, callback, autowire=False, @@ -143,8 +145,7 @@ def __should_pass_obj(self, name): the user handler's signature, makes sense in this context, and is not explicitly set to `False`. """ - all_passable_objects = {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args', 'groups', 'groupdict'} - is_requested = name in all_passable_objects and name in self._callback_args + is_requested = name in self.PASSABLE_OBJECTS and name in self._callback_args if is_requested and name not in self._passable: warnings.warn("The argument `{}` cannot be autowired since it is not available " "on `{}s`.".format(name, type(self).__name__)) @@ -162,6 +163,9 @@ def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data' The ``passable`` arguments are required to be explicit as opposed to dynamically generated to be absolutely safe that no arguments will be passed that are not allowed. + + Args: + passable: An iterable that contains the allowed flags for this handler """ self._passable = passable @@ -178,20 +182,10 @@ def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data' self._callback_args = inspect_arguments(self.callback) - if self.__should_pass_obj('update_queue'): - self.pass_update_queue = True - if self.__should_pass_obj('job_queue'): - self.pass_job_queue = True - if self.__should_pass_obj('user_data'): - self.pass_user_data = True - if self.__should_pass_obj('chat_data'): - self.pass_chat_data = True - if self.__should_pass_obj('args'): - self.pass_args = True - if self.__should_pass_obj('groups'): - self.pass_groups = True - if self.__should_pass_obj('groupdict'): - self.pass_groupdict = True + # Actually set `pass_*` flags to True + for to_pass in self.PASSABLE_OBJECTS: + if self.__should_pass_obj(to_pass): + setattr(self, 'pass_' + to_pass, True) self._autowire_initialized = True diff --git a/tests/test_handler.py b/tests/test_handler.py index 342d11accd3..4dd1e0898d3 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -21,7 +21,6 @@ from telegram.ext import Handler - class TestHandler(object): test_flag = False From 1216fa32ae1477828791a6ef9272217b052f0a60 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 02:18:45 +0200 Subject: [PATCH 14/18] Making flake8 happy --- telegram/ext/handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 749c757a9a5..2efff95ee77 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -72,7 +72,8 @@ class Handler(object): """ - PASSABLE_OBJECTS = {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args', 'groups', 'groupdict'} + PASSABLE_OBJECTS = {'update_queue', 'job_queue', 'user_data', 'chat_data', + 'args', 'groups', 'groupdict'} def __init__(self, callback, From 6f308e80446a21854e4a24bf5bfb8c89f1fdefde Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 03:23:34 +0200 Subject: [PATCH 15/18] Making flake8 happy --- telegram/ext/commandhandler.py | 7 ++++--- telegram/ext/handler.py | 10 ++++++---- telegram/ext/regexhandler.py | 3 ++- tests/test_callbackqueryhandler.py | 1 - tests/test_handler.py | 4 +++- tests/test_precheckoutqueryhandler.py | 3 ++- tests/test_shippingqueryhandler.py | 7 ++++--- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index 8e84b4bb059..914a6a60f2b 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -114,7 +114,8 @@ def __init__(self, self.pass_args = pass_args if self.autowire: - self.set_autowired_flags({'update_queue', 'job_queue', 'user_data', 'chat_data', 'args'}) + self.set_autowired_flags( + {'update_queue', 'job_queue', 'user_data', 'chat_data', 'args'}) if isinstance(command, string_types): self.command = [command.lower()] @@ -140,8 +141,8 @@ def check_update(self, update): :obj:`bool` """ - if (isinstance(update, Update) - and (update.message or update.edited_message and self.allow_edited)): + if (isinstance(update, Update) and + (update.message or update.edited_message and self.allow_edited)): message = update.message or update.edited_message if message.text: diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 2efff95ee77..7b00958da7e 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -26,9 +26,10 @@ class Handler(object): """ The base class for all update handlers. Create custom handlers by inheriting from it. - If your subclass needs the *autowiring* functionality, make sure to call ``set_autowired_flags`` - **after** initializing the ``pass_*`` members. The ``passable`` argument to this method denotes - all the flags your Handler supports, e.g. ``{'update_queue', 'job_queue', 'args'}``. + If your subclass needs the *autowiring* functionality, make sure to call + ``set_autowired_flags`` **after** initializing the ``pass_*`` members. The ``passable`` + argument to this method denotes all the flags your Handler supports, e.g. + ``{'update_queue', 'job_queue', 'args'}``. Attributes: callback (:obj:`callable`): The callback function for this handler. @@ -153,7 +154,8 @@ def __should_pass_obj(self, name): return False return is_requested - def set_autowired_flags(self, passable={'update_queue', 'job_queue', 'user_data', 'chat_data'}): + def set_autowired_flags(self, + passable={'update_queue', 'job_queue', 'user_data', 'chat_data'}): """ This method inspects the callback handler for used arguments. If it finds arguments that are ``passable``, i.e. types that can also be passed by the various ``pass_*`` flags, diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index 616a97aff14..c7dfd2a713d 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -138,7 +138,8 @@ def __init__(self, self.pass_groups = pass_groups self.pass_groupdict = pass_groupdict if self.autowire: - self.set_autowired_flags({'groups', 'groupdict', 'update_queue', 'job_queue', 'user_data', 'chat_data'}) + self.set_autowired_flags( + {'groups', 'groupdict', 'update_queue', 'job_queue', 'user_data', 'chat_data'}) self.allow_edited = allow_edited self.message_updates = message_updates self.channel_post_updates = channel_post_updates diff --git a/tests/test_callbackqueryhandler.py b/tests/test_callbackqueryhandler.py index 39d5b9637a5..202127afb7e 100644 --- a/tests/test_callbackqueryhandler.py +++ b/tests/test_callbackqueryhandler.py @@ -195,7 +195,6 @@ def test_autowire(self, dp, callback_query): dp.process_update(callback_query) assert self.test_flag - def test_other_update_types(self, false_update): handler = CallbackQueryHandler(self.callback_basic) assert not handler.check_update(false_update) diff --git a/tests/test_handler.py b/tests/test_handler.py index 4dd1e0898d3..61ab05fa120 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -21,6 +21,7 @@ from telegram.ext import Handler + class TestHandler(object): test_flag = False @@ -117,5 +118,6 @@ def test_get_available_pass_flags(self): handler.set_autowired_flags() - assert set(handler._get_available_pass_flags()) == {'pass_update_queue', 'pass_job_queue', 'pass_chat_data', + assert set(handler._get_available_pass_flags()) == {'pass_update_queue', 'pass_job_queue', + 'pass_chat_data', 'pass_user_data'} diff --git a/tests/test_precheckoutqueryhandler.py b/tests/test_precheckoutqueryhandler.py index 135c437361c..500572ea683 100644 --- a/tests/test_precheckoutqueryhandler.py +++ b/tests/test_precheckoutqueryhandler.py @@ -49,7 +49,8 @@ def false_update(request): @pytest.fixture(scope='class') def pre_checkout_query(): - return Update(1, pre_checkout_query=PreCheckoutQuery('id', User(1, 'test user', False), 'EUR', 223, + return Update(1, pre_checkout_query=PreCheckoutQuery('id', User(1, 'test user', False), 'EUR', + 223, 'invoice_payload')) diff --git a/tests/test_shippingqueryhandler.py b/tests/test_shippingqueryhandler.py index 43cd71a407a..edd57762c41 100644 --- a/tests/test_shippingqueryhandler.py +++ b/tests/test_shippingqueryhandler.py @@ -49,9 +49,10 @@ def false_update(request): @pytest.fixture(scope='class') def shiping_query(): - return Update(1, shipping_query=ShippingQuery(42, User(1, 'test user', False), 'invoice_payload', - ShippingAddress('EN', 'my_state', 'my_city', - 'steer_1', '', 'post_code'))) + return Update(1, + shipping_query=ShippingQuery(42, User(1, 'test user', False), 'invoice_payload', + ShippingAddress('EN', 'my_state', 'my_city', + 'steer_1', '', 'post_code'))) class TestShippingQueryHandler(object): From 94031138a3986b327190e64311335318bae289d0 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 13:04:36 +0200 Subject: [PATCH 16/18] Removed unneeded tuple return values --- telegram/utils/inspection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/telegram/utils/inspection.py b/telegram/utils/inspection.py index a6a6b7a845c..090ab7e1467 100644 --- a/telegram/utils/inspection.py +++ b/telegram/utils/inspection.py @@ -1,10 +1,13 @@ import inspect +""" +Reflects on a function or method to retrieve all positional and keyword arguments available. +""" try: def inspect_arguments(func): args, _, _, _ = inspect.getargspec(func) return args except Warning: # `getargspec()` is deprecated in Python3 def inspect_arguments(func): - args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations = inspect.getfullargspec(func) + args, _, _, _, _, _, _ = inspect.getfullargspec(func) return args From 233960675beefd1fa1389d7265843946a07f6792 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 13:39:03 +0200 Subject: [PATCH 17/18] Added erroneous parameter definition callback --- examples/autowiring.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/autowiring.py b/examples/autowiring.py index ed4151dfecc..ad28205fbf3 100644 --- a/examples/autowiring.py +++ b/examples/autowiring.py @@ -64,9 +64,13 @@ def regex_with_groups(bot, update, groups, groupdict): update.message.reply_text('Groupdict: {}'.format(groupdict)) +def callback_undefined_arguments(bot, update, chat_data, groups): + pass + + def main(): # Create the Updater and pass it your bot's token. - updater = Updater("TOKEN") + updater = Updater("324133401:AAHVjjXotCDXC_kIIkfM0O6bm9-l7BfJw-I") # Get the dispatcher to register handlers dp = updater.dispatcher @@ -92,6 +96,10 @@ def main(): # ... is equivalent to passing them automagically. dp.add_handler(CommandHandler("data", callback_with_data, autowire=True)) + # An example of using the `groups` parameter which is not defined for a CommandHandler. + # Uncomment the line below and you will see a warning. + # dp.add_handler(CommandHandler("erroneous", callback_undefined_arguments, autowire=True)) + dp.add_error_handler(error) updater.start_polling() From 287007fb17eacd2358d0f67345ccc3f4afa1ab98 Mon Sep 17 00:00:00 2001 From: JosXa Date: Sat, 7 Oct 2017 13:41:49 +0200 Subject: [PATCH 18/18] Added erroneous parameter definition callback --- examples/autowiring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/autowiring.py b/examples/autowiring.py index ad28205fbf3..382e8ba1a41 100644 --- a/examples/autowiring.py +++ b/examples/autowiring.py @@ -70,7 +70,7 @@ def callback_undefined_arguments(bot, update, chat_data, groups): def main(): # Create the Updater and pass it your bot's token. - updater = Updater("324133401:AAHVjjXotCDXC_kIIkfM0O6bm9-l7BfJw-I") + updater = Updater("TOKEN") # Get the dispatcher to register handlers dp = updater.dispatcher