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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy