Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Automatic pass_* for Handlers using reflection: Autowiring #859

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions examples/autowiring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/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, /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, RegexHandler

# 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 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.")


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 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 callback_undefined_arguments(bot, update, chat_data, groups):
pass


def main():
# Create the Updater 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))

# 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<object>.*) is (?P<value>.*)',
regex_with_groups,
autowire=True))

# This will raise an error because the bot argument is missing...
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(CommandHandler('help', 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))

# 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()

# 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()
17 changes: 15 additions & 2 deletions telegram/ext/callbackqueryhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`
Expand All @@ -79,11 +85,11 @@ class CallbackQueryHandler(Handler):
``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,
callback,
autowire=False,
pass_update_queue=False,
pass_job_queue=False,
pattern=None,
Expand All @@ -93,6 +99,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,
Expand All @@ -105,6 +112,10 @@ 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', 'update_queue', 'job_queue'})

def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.

Expand All @@ -131,7 +142,9 @@ 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:
match = re.match(self.pattern, update.callback_query.data)

Expand All @@ -140,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)
13 changes: 12 additions & 1 deletion telegram/ext/choseninlineresulthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 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`
Expand All @@ -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`.
Expand All @@ -95,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."
Expand Down
23 changes: 18 additions & 5 deletions telegram/ext/commandhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -68,6 +70,10 @@ 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 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
Expand All @@ -92,25 +98,31 @@ def __init__(self,
callback,
filters=None,
allow_edited=False,
autowire=False,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
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,
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
Expand All @@ -129,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:
Expand Down Expand Up @@ -161,11 +173,12 @@ 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)

message = update.message or update.edited_message

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)
Loading