diff --git a/CHANGES.rst b/CHANGES.rst index 1d5fe1f74f9..ccd939f1d6a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,26 @@ ======= Changes ======= +**2018-??-??** +*Released 11.0.0* + +Context based callbacks: +See https://git.io/vp113 for help. + +- Use of `pass_` in handlers is deprecated. +- Instead use `use_context=True` on `Updater` or `Dispatcher` and change callback from (bot, update, others...) to (update, context). +- This also applies to error handlers `Dispatcher.add_error_handler` and JobQueue jobs (change (bot, job) to (context) here). +- For users with custom handlers subclassing Handler, this is mostly backwards compatible, but to use the new context based callbacks you need to implement the new collect_additional_context method. +- Passing bot to JobQueue.__init__ is deprecated. Use JobQueue.set_dispatcher with a dispatcher instead. + +Other: +- Handlers should be faster due to deduped logic. + +Other removals: +- Remove the ability to use filter lists in handlers. +- Remove the last CamelCase CheckUpdate methods from the handlers we missed earlier. + + **2018-05-02** *Released 10.1.0* diff --git a/docs/source/telegram.ext.callbackcontext.rst b/docs/source/telegram.ext.callbackcontext.rst new file mode 100644 index 00000000000..36487931805 --- /dev/null +++ b/docs/source/telegram.ext.callbackcontext.rst @@ -0,0 +1,5 @@ +telegram.ext.CallbackContext +============================ + +.. autoclass:: telegram.ext.CallbackContext + :members: diff --git a/docs/source/telegram.ext.rst b/docs/source/telegram.ext.rst index d54e0542561..09647a1bdd6 100644 --- a/docs/source/telegram.ext.rst +++ b/docs/source/telegram.ext.rst @@ -10,6 +10,7 @@ telegram.ext package telegram.ext.jobqueue telegram.ext.messagequeue telegram.ext.delayqueue + telegram.ext.callbackcontext Handlers -------- diff --git a/examples/conversationbot.py b/examples/conversationbot.py index 06412ca354f..70f7f1cb0a3 100644 --- a/examples/conversationbot.py +++ b/examples/conversationbot.py @@ -17,12 +17,12 @@ bot. """ +import logging + from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove) from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler, ConversationHandler) -import logging - # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) @@ -32,7 +32,7 @@ GENDER, PHOTO, LOCATION, BIO = range(4) -def start(bot, update): +def start(update, context): reply_keyboard = [['Boy', 'Girl', 'Other']] update.message.reply_text( @@ -44,7 +44,7 @@ def start(bot, update): return GENDER -def gender(bot, update): +def gender(update, context): user = update.message.from_user logger.info("Gender of %s: %s", user.first_name, update.message.text) update.message.reply_text('I see! Please send me a photo of yourself, ' @@ -54,9 +54,9 @@ def gender(bot, update): return PHOTO -def photo(bot, update): +def photo(update, context): user = update.message.from_user - photo_file = bot.get_file(update.message.photo[-1].file_id) + photo_file = update.message.photo[-1].get_file() photo_file.download('user_photo.jpg') logger.info("Photo of %s: %s", user.first_name, 'user_photo.jpg') update.message.reply_text('Gorgeous! Now, send me your location please, ' @@ -65,7 +65,7 @@ def photo(bot, update): return LOCATION -def skip_photo(bot, update): +def skip_photo(update, context): user = update.message.from_user logger.info("User %s did not send a photo.", user.first_name) update.message.reply_text('I bet you look great! Now, send me your location please, ' @@ -74,7 +74,7 @@ def skip_photo(bot, update): return LOCATION -def location(bot, update): +def location(update, context): user = update.message.from_user user_location = update.message.location logger.info("Location of %s: %f / %f", user.first_name, user_location.latitude, @@ -85,7 +85,7 @@ def location(bot, update): return BIO -def skip_location(bot, update): +def skip_location(update, context): user = update.message.from_user logger.info("User %s did not send a location.", user.first_name) update.message.reply_text('You seem a bit paranoid! ' @@ -94,7 +94,7 @@ def skip_location(bot, update): return BIO -def bio(bot, update): +def bio(update, context): user = update.message.from_user logger.info("Bio of %s: %s", user.first_name, update.message.text) update.message.reply_text('Thank you! I hope we can talk again some day.') @@ -102,7 +102,7 @@ def bio(bot, update): return ConversationHandler.END -def cancel(bot, update): +def cancel(update, context): user = update.message.from_user logger.info("User %s canceled the conversation.", user.first_name) update.message.reply_text('Bye! I hope we can talk again some day.', @@ -111,14 +111,16 @@ def cancel(bot, update): return ConversationHandler.END -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + logger.warning('Update "%s" caused error "%s"', update, context.error) def main(): - # Create the EventHandler and pass it your bot's token. - updater = Updater("TOKEN") + # Create the Updater and pass it your bot's token. + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/examples/conversationbot2.py b/examples/conversationbot2.py index 34002792a07..59d0d751c9a 100644 --- a/examples/conversationbot2.py +++ b/examples/conversationbot2.py @@ -17,12 +17,12 @@ bot. """ +import logging + from telegram import ReplyKeyboardMarkup from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler, ConversationHandler) -import logging - # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) @@ -46,7 +46,7 @@ def facts_to_str(user_data): return "\n".join(facts).join(['\n', '\n']) -def start(bot, update): +def start(update, context): update.message.reply_text( "Hi! My name is Doctor Botter. I will hold a more complex conversation with you. " "Why don't you tell me something about yourself?", @@ -55,23 +55,24 @@ def start(bot, update): return CHOOSING -def regular_choice(bot, update, user_data): +def regular_choice(update, context): text = update.message.text - user_data['choice'] = text + context.user_data['choice'] = text update.message.reply_text( 'Your {}? Yes, I would love to hear about that!'.format(text.lower())) return TYPING_REPLY -def custom_choice(bot, update): +def custom_choice(update, context): update.message.reply_text('Alright, please send me the category first, ' 'for example "Most impressive skill"') return TYPING_CHOICE -def received_information(bot, update, user_data): +def received_information(update, context): + user_data = context.user_data text = update.message.text category = user_data['choice'] user_data[category] = text @@ -85,7 +86,8 @@ def received_information(bot, update, user_data): return CHOOSING -def done(bot, update, user_data): +def done(update, context): + user_data = context.user_data if 'choice' in user_data: del user_data['choice'] @@ -97,14 +99,16 @@ def done(bot, update, user_data): return ConversationHandler.END -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" logger.warning('Update "%s" caused error "%s"', update, error) def main(): # Create the Updater and pass it your bot's token. - updater = Updater("TOKEN") + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/examples/echobot2.py b/examples/echobot2.py index d6b102bcdfb..868a03f024b 100644 --- a/examples/echobot2.py +++ b/examples/echobot2.py @@ -17,9 +17,10 @@ bot. """ -from telegram.ext import Updater, CommandHandler, MessageHandler, Filters 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) @@ -29,30 +30,32 @@ # Define a few command handlers. These usually take the two arguments bot and # update. Error handlers also receive the raised TelegramError object in error. -def start(bot, update): +def start(update, context): """Send a message when the command /start is issued.""" update.message.reply_text('Hi!') -def help(bot, update): +def help(update, context): """Send a message when the command /help is issued.""" update.message.reply_text('Help!') -def echo(bot, update): +def echo(update, context): """Echo the user message.""" update.message.reply_text(update.message.text) -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + logger.warning('Update "%s" caused error "%s"', update, context.error) def main(): """Start the bot.""" - # Create the EventHandler and pass it your bot's token. - updater = Updater("TOKEN") + # Create the Updater and pass it your bot's token. + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/examples/inlinebot.py b/examples/inlinebot.py index 923e6bd7975..4c4c896891b 100644 --- a/examples/inlinebot.py +++ b/examples/inlinebot.py @@ -16,14 +16,13 @@ Press Ctrl-C on the command line or send a signal to the process to stop the bot. """ +import logging from uuid import uuid4 -from telegram.utils.helpers import escape_markdown - from telegram import InlineQueryResultArticle, ParseMode, \ InputTextMessageContent from telegram.ext import Updater, InlineQueryHandler, CommandHandler -import logging +from telegram.utils.helpers import escape_markdown # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -34,17 +33,17 @@ # Define a few command handlers. These usually take the two arguments bot and # update. Error handlers also receive the raised TelegramError object in error. -def start(bot, update): +def start(update, context): """Send a message when the command /start is issued.""" update.message.reply_text('Hi!') -def help(bot, update): +def help(update, context): """Send a message when the command /help is issued.""" update.message.reply_text('Help!') -def inlinequery(bot, update): +def inlinequery(update, context): """Handle the inline query.""" query = update.inline_query.query results = [ @@ -69,14 +68,16 @@ def inlinequery(bot, update): update.inline_query.answer(results) -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + logger.warning('Update "%s" caused error "%s"', update, context.error) def main(): # Create the Updater and pass it your bot's token. - updater = Updater("TOKEN") + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/examples/inlinekeyboard.py b/examples/inlinekeyboard.py index 84c0966adc0..8b699f932b3 100644 --- a/examples/inlinekeyboard.py +++ b/examples/inlinekeyboard.py @@ -5,6 +5,7 @@ # This program is dedicated to the public domain under the CC0 license. """ import logging + from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Updater, CommandHandler, CallbackQueryHandler @@ -13,7 +14,7 @@ logger = logging.getLogger(__name__) -def start(bot, update): +def start(update, context): keyboard = [[InlineKeyboardButton("Option 1", callback_data='1'), InlineKeyboardButton("Option 2", callback_data='2')], @@ -24,26 +25,26 @@ def start(bot, update): update.message.reply_text('Please choose:', reply_markup=reply_markup) -def button(bot, update): +def button(update, context): query = update.callback_query - bot.edit_message_text(text="Selected option: {}".format(query.data), - chat_id=query.message.chat_id, - message_id=query.message.message_id) + query.edit_message_text(text="Selected option: {}".format(query.data)) -def help(bot, update): +def help(update, context): update.message.reply_text("Use /start to test this bot.") -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + logger.warning('Update "%s" caused error "%s"', update, context.error) def main(): # Create the Updater and pass it your bot's token. - updater = Updater("TOKEN") + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) updater.dispatcher.add_handler(CommandHandler('start', start)) updater.dispatcher.add_handler(CallbackQueryHandler(button)) diff --git a/examples/paymentbot.py b/examples/paymentbot.py index a419034de94..f512bb344e7 100644 --- a/examples/paymentbot.py +++ b/examples/paymentbot.py @@ -6,10 +6,11 @@ This program is dedicated to the public domain under the CC0 license. """ +import logging + from telegram import (LabeledPrice, ShippingOption) from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, PreCheckoutQueryHandler, ShippingQueryHandler) -import logging # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -18,18 +19,18 @@ logger = logging.getLogger(__name__) -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + logger.warning('Update "%s" caused error "%s"', update, context.error) -def start_callback(bot, update): +def start_callback(update, context): msg = "Use /shipping to get an invoice for shipping-payment, " msg += "or /noshipping for an invoice without shipping." update.message.reply_text(msg) -def start_with_shipping_callback(bot, update): +def start_with_shipping_callback(update, context): chat_id = update.message.chat_id title = "Payment Example" description = "Payment Example using python-telegram-bot" @@ -47,13 +48,13 @@ def start_with_shipping_callback(bot, update): # optionally pass need_name=True, need_phone_number=True, # need_email=True, need_shipping_address=True, is_flexible=True - bot.sendInvoice(chat_id, title, description, payload, - provider_token, start_parameter, currency, prices, - need_name=True, need_phone_number=True, - need_email=True, need_shipping_address=True, is_flexible=True) + context.bot.send_invoice(chat_id, title, description, payload, + provider_token, start_parameter, currency, prices, + need_name=True, need_phone_number=True, + need_email=True, need_shipping_address=True, is_flexible=True) -def start_without_shipping_callback(bot, update): +def start_without_shipping_callback(update, context): chat_id = update.message.chat_id title = "Payment Example" description = "Payment Example using python-telegram-bot" @@ -70,17 +71,16 @@ def start_without_shipping_callback(bot, update): # optionally pass need_name=True, need_phone_number=True, # need_email=True, need_shipping_address=True, is_flexible=True - bot.sendInvoice(chat_id, title, description, payload, - provider_token, start_parameter, currency, prices) + context.bot.send_invoice(chat_id, title, description, payload, + provider_token, start_parameter, currency, prices) -def shipping_callback(bot, update): +def shipping_callback(update, context): query = update.shipping_query # check the payload, is this from your bot? if query.invoice_payload != 'Custom-Payload': # answer False pre_checkout_query - bot.answer_shipping_query(shipping_query_id=query.id, ok=False, - error_message="Something went wrong...") + query.answer(ok=False, error_message="Something went wrong...") return else: options = list() @@ -89,31 +89,31 @@ def shipping_callback(bot, update): # an array of LabeledPrice objects price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)] options.append(ShippingOption('2', 'Shipping Option B', price_list)) - bot.answer_shipping_query(shipping_query_id=query.id, ok=True, - shipping_options=options) + query.answer(ok=True, shipping_options=options) # after (optional) shipping, it's the pre-checkout -def precheckout_callback(bot, update): +def precheckout_callback(update, context): query = update.pre_checkout_query # check the payload, is this from your bot? if query.invoice_payload != 'Custom-Payload': # answer False pre_checkout_query - bot.answer_pre_checkout_query(pre_checkout_query_id=query.id, ok=False, - error_message="Something went wrong...") + query.answer(ok=False, error_message="Something went wrong...") else: - bot.answer_pre_checkout_query(pre_checkout_query_id=query.id, ok=True) + query.answer(ok=True) # finally, after contacting to the payment provider... -def successful_payment_callback(bot, update): +def successful_payment_callback(update, context): # do something after successful receive of payment? update.message.reply_text("Thank you for your payment!") def main(): - # Create the EventHandler and pass it your bot's token. - updater = Updater(token="BOT_TOKEN") + # Create the Updater and pass it your bot's token. + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/examples/timerbot.py b/examples/timerbot.py index 1a1c7ff9447..c21563a55d7 100644 --- a/examples/timerbot.py +++ b/examples/timerbot.py @@ -19,9 +19,10 @@ bot. """ -from telegram.ext import Updater, CommandHandler import logging +from telegram.ext import Updater, CommandHandler + # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) @@ -31,28 +32,29 @@ # Define a few command handlers. These usually take the two arguments bot and # update. Error handlers also receive the raised TelegramError object in error. -def start(bot, update): +def start(update, context): update.message.reply_text('Hi! Use /set to set a timer') -def alarm(bot, job): +def alarm(context): """Send the alarm message.""" - bot.send_message(job.context, text='Beep!') + job = context.job + context.bot.send_message(job.context, text='Beep!') -def set_timer(bot, update, args, job_queue, chat_data): +def set_timer(update, context): """Add a job to the queue.""" chat_id = update.message.chat_id try: # args[0] should contain the time for the timer in seconds - due = int(args[0]) + due = int(context.args[0]) if due < 0: update.message.reply_text('Sorry we can not go back to future!') return # Add job to queue - job = job_queue.run_once(alarm, due, context=chat_id) - chat_data['job'] = job + job = context.job_queue.run_once(alarm, due, context=chat_id) + context.chat_data['job'] = job update.message.reply_text('Timer successfully set!') @@ -60,27 +62,30 @@ def set_timer(bot, update, args, job_queue, chat_data): update.message.reply_text('Usage: /set ') -def unset(bot, update, chat_data): +def unset(update, context): """Remove the job if the user changed their mind.""" - if 'job' not in chat_data: + if 'job' not in context.chat_data: update.message.reply_text('You have no active timer') return - job = chat_data['job'] + job = context.chat_data['job'] job.schedule_removal() - del chat_data['job'] + del context.chat_data['job'] update.message.reply_text('Timer successfully unset!') -def error(bot, update, error): +def error(update, context): """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + logger.warning('Update "%s" caused error "%s"', update, context.error) def main(): """Run bot.""" - updater = Updater("TOKEN") + # Create the Updater and pass it your bot's token. + # Make sure to set use_context=True to use the new context based callbacks + # Post version 12 this will no longer be necessary + updater = Updater("TOKEN", use_context=True) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/setup.cfg b/setup.cfg index ae23f5e89a3..1ff7a5cfbd7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ addopts = --no-success-flaky-report -rsxX filterwarnings = error ignore::DeprecationWarning + ignore::telegram.utils.deprecate.TelegramDeprecationWarning [coverage:run] branch = True diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index 1c69d0b8333..4538567dcc1 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -18,13 +18,14 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """Extensions over the Telegram Bot API to facilitate bot making""" +from .handler import Handler +from .callbackcontext import CallbackContext from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async from .jobqueue import JobQueue, Job from .updater import Updater from .callbackqueryhandler import CallbackQueryHandler from .choseninlineresulthandler import ChosenInlineResultHandler from .commandhandler import CommandHandler -from .handler import Handler from .inlinequeryhandler import InlineQueryHandler from .messagehandler import MessageHandler from .filters import BaseFilter, Filters @@ -43,4 +44,4 @@ 'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler', 'ConversationHandler', 'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue', - 'DispatcherHandlerStop', 'run_async') + 'DispatcherHandlerStop', 'run_async', 'CallbackContext') diff --git a/telegram/ext/callbackcontext.py b/telegram/ext/callbackcontext.py new file mode 100644 index 00000000000..e36f877c4d1 --- /dev/null +++ b/telegram/ext/callbackcontext.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2018 +# 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/]. +"""This module contains the CallbackContext class.""" + +from telegram import Update + + +class CallbackContext(object): + """ + This is a context object passed to the callback called by :class:`telegram.ext.Handler` + or by the :class:`telegram.ext.Dispatcher` in an error handler added by + :attr:`telegram.ext.Dispatcher.add_error_handler` or to the callback of a + :class:`telegram.ext.Job`. + + Attributes: + chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each + update from the same chat it will be the same ``dict``. + user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each + update from the same user it will be the same ``dict``. + match (:obj:`re match object`, optional): If the associated update originated from a + regex-supported handler, this will contain the object returned from + ``re.match(pattern, string)``. + args (List[:obj:`str`], optional): Arguments passed to a command if the associated update + is handled by :class:`telegram.ext.CommandHandler` or + :class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the text + after the command, using any whitespace string as a delimiter. + error (:class:`telegram.TelegramError`, optional): The Telegram error that was raised. + Only present when passed to a error handler registered with + :attr:`telegram.ext.Dispatcher.add_error_handler`. + job (:class:`telegram.ext.Job`): The job that that originated this callback. + Only present when passed to the callback of :class:`telegram.ext.Job`. + + """ + + def __init__(self, dispatcher): + """ + Args: + dispatcher (:class:`telegram.ext.Dispatcher`): + """ + if not dispatcher.use_context: + raise ValueError('CallbackContext should not be used with a non context aware ' + 'dispatcher!') + self._dispatcher = dispatcher + self.chat_data = None + self.user_data = None + self.args = None + self.match = None + self.error = None + self.job = None + + @classmethod + def from_error(cls, update, error, dispatcher): + self = cls.from_update(update, dispatcher) + self.error = error + return self + + @classmethod + def from_update(cls, update, dispatcher): + self = cls(dispatcher) + if update is not None and isinstance(update, Update): + chat = update.effective_chat + user = update.effective_user + + if chat: + self.chat_data = dispatcher.chat_data[chat.id] + if user: + self.user_data = dispatcher.user_data[user.id] + return self + + @classmethod + def from_job(cls, job, dispatcher): + self = cls(dispatcher) + self.job = job + return self + + @property + def bot(self): + """:class:`telegram.Bot`: The bot associated with this context.""" + return self._dispatcher.bot + + @property + def job_queue(self): + """ + :class:`telegram.ext.JobQueue`: The ``JobQueue`` used by the + :class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater` + associated with this context. + + """ + return self._dispatcher.job_queue + + @property + def update_queue(self): + """ + :class:`queue.Queue`: The ``Queue`` instance used by the + :class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater` + associated with this context. + + """ + return self._dispatcher.update_queue diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index 08b37b93459..03135d8e991 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -33,19 +33,19 @@ class CallbackQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. pattern (:obj:`str` | `Pattern`): Optional. Regex pattern to test :attr:`telegram.CallbackQuery.data` against. - pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the + pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the callback function. - pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to + pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -54,31 +54,45 @@ class CallbackQueryHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not ``None``, ``re.match`` is used on :attr:`telegram.CallbackQuery.data` 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`` + DEPRECATED: Please switch to context based callbacks. 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`` + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. """ @@ -119,25 +133,22 @@ def check_update(self, update): if self.pattern: if update.callback_query.data: match = re.match(self.pattern, update.callback_query.data) - return bool(match) + if match: + return match else: return True - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher, update) + def collect_optional_args(self, dispatcher, update=None, check_result=None): + optional_args = super(CallbackQueryHandler, self).collect_optional_args(dispatcher, + update, + check_result) if self.pattern: - match = re.match(self.pattern, update.callback_query.data) - if self.pass_groups: - optional_args['groups'] = match.groups() + optional_args['groups'] = check_result.groups() if self.pass_groupdict: - optional_args['groupdict'] = match.groupdict() + optional_args['groupdict'] = check_result.groupdict() + return optional_args - return self.callback(dispatcher.bot, update, **optional_args) + def collect_additional_context(self, context, update, dispatcher, check_result): + if self.pattern: + context.match = check_result diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py index 34efea3578a..09e327410a9 100644 --- a/telegram/ext/choseninlineresulthandler.py +++ b/telegram/ext/choseninlineresulthandler.py @@ -18,9 +18,8 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the ChosenInlineResultHandler class.""" -from .handler import Handler from telegram import Update -from telegram.utils.deprecate import deprecate +from .handler import Handler class ChosenInlineResultHandler(Handler): @@ -28,13 +27,13 @@ class ChosenInlineResultHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -43,38 +42,37 @@ class ChosenInlineResultHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. """ - def __init__(self, - callback, - pass_update_queue=False, - pass_job_queue=False, - pass_user_data=False, - pass_chat_data=False): - super(ChosenInlineResultHandler, self).__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) - def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. @@ -86,20 +84,3 @@ def check_update(self, update): """ return isinstance(update, Update) and update.chosen_inline_result - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher, update) - - return self.callback(dispatcher.bot, update, **optional_args) - - # old non-PEP8 Handler methods - m = "telegram.ChosenInlineResultHandler." - checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update") - handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update") diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index 82e8a40b6cc..2f58ebf9a58 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -17,12 +17,10 @@ # 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 CommandHandler class.""" -import warnings - from future.utils import string_types -from .handler import Handler from telegram import Update +from .handler import Handler class CommandHandler(Handler): @@ -37,17 +35,17 @@ class CommandHandler(Handler): callback (:obj:`callable`): The callback function for this handler. filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these Filters. - allow_edited (:obj:`bool`): Optional. Determines Whether the handler should also accept + allow_edited (:obj:`bool`): Determines Whether the handler should also accept edited messages. - pass_args (:obj:`bool`): Optional. Determines whether the handler should be passed + pass_args (:obj:`bool`): Determines whether the handler should be passed ``args``. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -56,12 +54,20 @@ class CommandHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler should listen for. - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from :class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in :class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise @@ -72,18 +78,23 @@ class CommandHandler(Handler): 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 consecutive whitespace characters. Default is ``False`` + DEPRECATED: Please switch to context based callbacks. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. """ @@ -112,13 +123,6 @@ def __init__(self, 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 - if isinstance(self.filters, list): - warnings.warn('Using a list of filters in MessageHandler is getting ' - 'deprecated, please use bitwise operators (& and |) ' - 'instead. More info: https://git.io/vPTbc.') - def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. @@ -129,8 +133,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 and message.text.startswith('/') and len(message.text) > 1: @@ -142,32 +146,16 @@ def check_update(self, update): if not (command[0].lower() in self.command and command[1].lower() == message.bot.username.lower()): - return False - - if self.filters is None: - res = True - elif isinstance(self.filters, list): - res = any(func(message) for func in self.filters) - else: - res = self.filters(message) - - return res - - return False - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher, update) + return None - message = update.message or update.edited_message + if self.filters is None or self.filters(message): + return message.text.split()[1:] + def collect_optional_args(self, dispatcher, update=None, check_result=None): + optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update) if self.pass_args: - optional_args['args'] = message.text.split()[1:] + optional_args['args'] = check_result + return optional_args - return self.callback(dispatcher.bot, update, **optional_args) + def collect_additional_context(self, context, update, dispatcher, check_result): + context.args = check_result diff --git a/telegram/ext/conversationhandler.py b/telegram/ext/conversationhandler.py index 0cc6633f87f..87864bcfcab 100644 --- a/telegram/ext/conversationhandler.py +++ b/telegram/ext/conversationhandler.py @@ -66,15 +66,15 @@ class ConversationHandler(Handler): fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if the user is in a conversation, but every handler for their current state returned ``False`` on :attr:`check_update`. - allow_reentry (:obj:`bool`): Optional. Determines if a user can restart a conversation with + allow_reentry (:obj:`bool`): Determines if a user can restart a conversation with an entry point. run_async_timeout (:obj:`float`): Optional. The time-out for ``run_async`` decorated Handlers. timed_out_behavior (List[:class:`telegram.ext.Handler`]): Optional. A list of handlers that might be used if the wait for ``run_async`` timed out. - per_chat (:obj:`bool`): Optional. If the conversationkey should contain the Chat's ID. - per_user (:obj:`bool`): Optional. If the conversationkey should contain the User's ID. - per_message (:obj:`bool`): Optional. If the conversationkey should contain the Message's + per_chat (:obj:`bool`): If the conversationkey should contain the Chat's ID. + per_user (:obj:`bool`): If the conversationkey should contain the User's ID. + per_message (:obj:`bool`): If the conversationkey should contain the Message's ID. conversation_timeout (:obj:`float`|:obj:`datetime.timedelta`): Optional. When this handler is inactive more than this timeout (in seconds), it will be automatically ended. If @@ -147,8 +147,6 @@ def __init__(self, self.timeout_jobs = dict() self.conversations = dict() - self.current_conversation = None - self.current_handler = None self.logger = logging.getLogger(__name__) @@ -220,7 +218,7 @@ def check_update(self, update): self.per_chat and not update.effective_chat or self.per_message and not update.callback_query or update.callback_query and self.per_chat and not update.callback_query.message): - return False + return None key = self._get_key(update) state = self.conversations.get(key) @@ -244,15 +242,13 @@ def check_update(self, update): else: for candidate in (self.timed_out_behavior or []): - if candidate.check_update(update): + check = candidate.check_update(update) + if check is not None and check is not False: # Save the current user and the selected handler for handle_update - self.current_conversation = key - self.current_handler = candidate - - return True + return key, candidate, check else: - return False + return None self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state))) @@ -261,59 +257,61 @@ def check_update(self, update): # Search entry points for a match if state is None or self.allow_reentry: for entry_point in self.entry_points: - if entry_point.check_update(update): + check = entry_point.check_update(update) + if check is not None and check is not False: handler = entry_point break else: if state is None: - return False + return None # Get the handler list for current state, if we didn't find one yet and we're still here if state is not None and not handler: handlers = self.states.get(state) for candidate in (handlers or []): - if candidate.check_update(update): + check = candidate.check_update(update) + if check is not None and check is not False: handler = candidate break # Find a fallback handler if all other handlers fail else: for fallback in self.fallbacks: - if fallback.check_update(update): + check = fallback.check_update(update) + if check is not None and check is not False: handler = fallback break else: - return False - - # Save the current user and the selected handler for handle_update - self.current_conversation = key - self.current_handler = handler + return None - return True + return key, handler, check - def handle_update(self, update, dispatcher): + def handle_update(self, update, dispatcher, check_result): """Send the update to the callback for the current state and Handler Args: + check_result: The result from check_update. For this handler it's a tuple of key, + handler, and the handler's check result. update (:class:`telegram.Update`): Incoming telegram update. dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. """ - new_state = self.current_handler.handle_update(update, dispatcher) - timeout_job = self.timeout_jobs.pop(self.current_conversation, None) + conversation_key, handler, check_result = check_result + new_state = handler.handle_update(update, dispatcher, check_result) + timeout_job = self.timeout_jobs.pop(conversation_key, None) if timeout_job is not None: timeout_job.schedule_removal() if self.conversation_timeout and new_state != self.END: - self.timeout_jobs[self.current_conversation] = dispatcher.job_queue.run_once( + self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once( self._trigger_timeout, self.conversation_timeout, - context=self.current_conversation + context=conversation_key ) - self.update_state(new_state, self.current_conversation) + self.update_state(new_state, conversation_key) def update_state(self, new_state, key): if new_state == self.END: diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index 7756770b7d3..a7e988573f5 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -19,6 +19,7 @@ """This module contains the Dispatcher class.""" import logging +import warnings import weakref from functools import wraps from threading import Thread, Lock, Event, current_thread, BoundedSemaphore @@ -32,6 +33,8 @@ from telegram import TelegramError from telegram.ext.handler import Handler +from telegram.ext.callbackcontext import CallbackContext +from telegram.utils.deprecate import TelegramDeprecationWarning from telegram.utils.promise import Promise logging.getLogger(__name__).addHandler(logging.NullHandler()) @@ -78,6 +81,9 @@ class Dispatcher(object): instance to pass onto handler callbacks. workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the ``@run_async`` decorator. defaults to 4. + use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API. + During the deprecation period of the old API the default is ``False``. **New users**: + set this to ``True``. """ @@ -86,11 +92,22 @@ class Dispatcher(object): __singleton = None logger = logging.getLogger(__name__) - def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None): + def __init__(self, + bot, + update_queue, + workers=4, + exception_event=None, + job_queue=None, + use_context=False): self.bot = bot self.update_queue = update_queue self.job_queue = job_queue self.workers = workers + self.use_context = use_context + + if not use_context: + warnings.warn('Old Handler API is deprecated - see https://git.io/vp113 for details', + TelegramDeprecationWarning, stacklevel=3) self.user_data = defaultdict(dict) """:obj:`dict`: A dictionary handlers can use to store data for the user.""" @@ -275,9 +292,11 @@ def process_update(self, update): for group in self.groups: try: - for handler in (x for x in self.handlers[group] if x.check_update(update)): - handler.handle_update(update, self) - break + for handler in self.handlers[group]: + check = handler.check_update(update) + if check is not None and check is not False: + handler.handle_update(update, self, check) + break # Stop processing with any other handler. except DispatcherHandlerStop: @@ -355,9 +374,15 @@ def add_error_handler(self, callback): """Registers an error handler in the Dispatcher. Args: - callback (:obj:`callable`): A function that takes ``Bot, Update, TelegramError`` as - arguments. + callback (:obj:`callable`): The callback function for this error handler. Will be + called when an error is raised Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The error that happened will be present in context.error. + Note: + See https://git.io/vp113 for more info about switching to context based API. """ self.error_handlers.append(callback) @@ -381,7 +406,10 @@ def dispatch_error(self, update, error): """ if self.error_handlers: for callback in self.error_handlers: - callback(self.bot, update, error) + if self.use_context: + callback(update, CallbackContext.from_error(update, error, self)) + else: + callback(self.bot, update, error) else: self.logger.exception( diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index d529b612853..3000ba95ff5 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -17,6 +17,7 @@ # 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.""" +from telegram.ext.callbackcontext import CallbackContext class Handler(object): @@ -24,13 +25,13 @@ class Handler(object): Attributes: callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -39,22 +40,34 @@ class Handler(object): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. """ @@ -79,31 +92,59 @@ def check_update(self, update): update (:obj:`str` | :class:`telegram.Update`): The update to be tested. Returns: - :obj:`bool` + Either ``None`` or ``False`` if the update should not be handled. Otherwise an object + that will be passed to :attr:`handle_update` and :attr:`collect_additional_context` + when the update gets handled. """ raise NotImplementedError - def handle_update(self, update, dispatcher): + def handle_update(self, update, dispatcher, check_result): """ This method is called if it was determined that an update should indeed - be handled by this instance. It should also be overridden, but in most - cases call ``self.callback(dispatcher.bot, update)``, possibly along with - optional arguments. To work with the ``ConversationHandler``, this method should return the - value returned from ``self.callback`` + be handled by this instance. Calls :attr:`self.callback` along with its respectful + arguments. To work with the :class:`telegram.ext.ConversationHandler`, this method + returns the value returned from ``self.callback``. + Note that it can be overridden if needed by the subclassing handler. Args: update (:obj:`str` | :class:`telegram.Update`): The update to be handled. - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher to collect optional args. + dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher. + check_result: The result from :attr:`check_update`. """ - raise NotImplementedError + if dispatcher.use_context: + context = CallbackContext.from_update(update, dispatcher) + self.collect_additional_context(context, update, dispatcher, check_result) + return self.callback(update, context) + else: + optional_args = self.collect_optional_args(dispatcher, update, check_result) + return self.callback(dispatcher.bot, update, **optional_args) + + def collect_additional_context(self, context, update, dispatcher, check_result): + """Prepares additional arguments for the context. Override if needed. + + Args: + context (:class:`telegram.ext.CallbackContext`): The context object. + update (:class:`telegram.Update`): The update to gather chat/user id from. + dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher. + check_result: The result (return value) from :attr:`check_update`. + + """ + pass + + def collect_optional_args(self, dispatcher, update=None, check_result=None): + """ + Prepares the optional arguments. If the handler has additional optional args, + it should subclass this method, but remember to call this super method. - def collect_optional_args(self, dispatcher, update=None): - """Prepares the optional arguments that are the same for all types of handlers. + DEPRECATED: This method is being replaced by new context based callbacks. Please see + https://git.io/vp113 for more info. Args: dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher. + update (:class:`telegram.Update`): The update to gather chat/user id from. + check_result: The result from check_update """ optional_args = dict() diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index c5a313d83d3..19a90a10c36 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -22,7 +22,6 @@ from future.utils import string_types from telegram import Update -from telegram.utils.deprecate import deprecate from .handler import Handler @@ -33,19 +32,19 @@ class InlineQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): 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 + pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the callback function. - pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to + pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -54,31 +53,46 @@ class InlineQueryHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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`` + DEPRECATED: Please switch to context based callbacks. 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`` + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. + """ def __init__(self, @@ -113,37 +127,28 @@ def check_update(self, update): Returns: :obj:`bool` + """ if isinstance(update, Update) and update.inline_query: if self.pattern: if update.inline_query.query: match = re.match(self.pattern, update.inline_query.query) - return bool(match) + if match: + return match else: return True - def handle_update(self, update, dispatcher): - """ - Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - """ - - optional_args = self.collect_optional_args(dispatcher, update) + def collect_optional_args(self, dispatcher, update=None, check_result=None): + optional_args = super(InlineQueryHandler, self).collect_optional_args(dispatcher, + update, check_result) if self.pattern: - match = re.match(self.pattern, update.inline_query.query) - if self.pass_groups: - optional_args['groups'] = match.groups() + optional_args['groups'] = check_result.groups() if self.pass_groupdict: - optional_args['groupdict'] = match.groupdict() - - return self.callback(dispatcher.bot, update, **optional_args) + optional_args['groupdict'] = check_result.groupdict() + return optional_args - # old non-PEP8 Handler methods - m = "telegram.InlineQueryHandler." - checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update") - handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update") + def collect_additional_context(self, context, update, dispatcher, check_result): + if self.pattern: + context.match = check_result diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 1aa51b03fdd..2249b1ae708 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -18,13 +18,17 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes JobQueue and Job.""" +import datetime import logging import time -import datetime +import warnings import weakref from numbers import Number -from threading import Thread, Lock, Event from queue import PriorityQueue, Empty +from threading import Thread, Lock, Event + +from telegram.ext.callbackcontext import CallbackContext +from telegram.utils.deprecate import TelegramDeprecationWarning class Days(object): @@ -37,16 +41,24 @@ class JobQueue(object): Attributes: _queue (:obj:`PriorityQueue`): The queue that holds the Jobs. - bot (:class:`telegram.Bot`): Bot that's send to the handlers. - - Args: bot (:class:`telegram.Bot`): The bot instance that should be passed to the jobs. - + DEPRECATED: Use set_dispatcher instead. """ - def __init__(self, bot): + def __init__(self, bot=None): self._queue = PriorityQueue() - self.bot = bot + if bot: + warnings.warn("Passing bot to jobqueue is deprecated. Please use set_dispatcher " + "instead!", TelegramDeprecationWarning, stacklevel=2) + + class MockDispatcher(object): + def __init__(self): + self.bot = bot + self.use_context = False + + self._dispatcher = MockDispatcher() + else: + self._dispatcher = None self.logger = logging.getLogger(self.__class__.__name__) self.__start_lock = Lock() self.__next_peek_lock = Lock() # to protect self._next_peek & self.__tick @@ -55,6 +67,9 @@ def __init__(self, bot): self._next_peek = None self._running = False + def set_dispatcher(self, dispatcher): + self._dispatcher = dispatcher + def _put(self, job, next_t=None, last_t=None): if next_t is None: next_t = job.interval @@ -242,7 +257,7 @@ def tick(self): current_week_day = datetime.datetime.now().weekday() if any(day == current_week_day for day in job.days): self.logger.debug('Running job %s', job.name) - job.run(self.bot) + job.run(self._dispatcher) except Exception: self.logger.exception('An uncaught error was raised while executing job %s', @@ -367,9 +382,12 @@ def __init__(self, self._enabled = Event() self._enabled.set() - def run(self, bot): + def run(self, dispatcher): """Executes the callback function.""" - self.callback(bot, self) + if dispatcher.use_context: + self.callback(CallbackContext.from_job(self, dispatcher)) + else: + self.callback(dispatcher.bot, self) def schedule_removal(self): """ diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index e9babc31577..8cd33d82ef5 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -31,21 +31,21 @@ 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. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. - message_updates (:obj:`bool`): Optional. Should "normal" message updates be handled? + message_updates (:obj:`bool`): Should "normal" message updates be handled? Default is ``True``. - channel_post_updates (:obj:`bool`): Optional. Should channel posts updates be handled? + channel_post_updates (:obj:`bool`): Should channel posts updates be handled? Default is ``True``. - edited_updates (:obj:`bool`): Optional. Should "edited" message updates be handled? + edited_updates (:obj:`bool`): Should "edited" message updates be handled? Default is ``False``. - allow_edited (:obj:`bool`): Optional. If the handler should also accept edited messages. + allow_edited (:obj:`bool`): If the handler should also accept edited messages. Default is ``False`` - Deprecated. use edited_updates instead. Note: @@ -54,26 +54,38 @@ class MessageHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from :class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in :class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise operators (& for and, | for or, ~ for not). - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. message_updates (:obj:`bool`, optional): Should "normal" message updates be handled? Default is ``True``. channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled? @@ -117,13 +129,6 @@ def __init__(self, self.channel_post_updates = channel_post_updates self.edited_updates = edited_updates - # 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): - warnings.warn('Using a list of filters in MessageHandler is getting ' - 'deprecated, please use bitwise operators (& and |) ' - 'instead. More info: https://git.io/vPTbc.') - def _is_allowed_update(self, update): return any([self.message_updates and update.message, self.edited_updates and (update.edited_message or update.edited_channel_post), @@ -140,30 +145,7 @@ def check_update(self, update): """ if isinstance(update, Update) and self._is_allowed_update(update): - if not self.filters: - res = True - + return True else: - message = update.effective_message - if isinstance(self.filters, list): - res = any(func(message) for func in self.filters) - else: - res = self.filters(message) - - else: - res = False - - return res - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher, update) - - return self.callback(dispatcher.bot, update, **optional_args) + return self.filters(update.effective_message) diff --git a/telegram/ext/precheckoutqueryhandler.py b/telegram/ext/precheckoutqueryhandler.py index 6f8b3605442..07f6bef302b 100644 --- a/telegram/ext/precheckoutqueryhandler.py +++ b/telegram/ext/precheckoutqueryhandler.py @@ -27,13 +27,13 @@ class PreCheckoutQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -42,38 +42,37 @@ class PreCheckoutQueryHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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`` + DEPRECATED: Please switch to context based callbacks. instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` that contains new updates which can be used to insert updates. Default is ``False``. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. """ - def __init__(self, - callback, - pass_update_queue=False, - pass_job_queue=False, - pass_user_data=False, - pass_chat_data=False): - super(PreCheckoutQueryHandler, self).__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) - def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. @@ -85,14 +84,3 @@ def check_update(self, update): """ return isinstance(update, Update) and update.pre_checkout_query - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher, update) - return self.callback(dispatcher.bot, update, **optional_args) diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index 1e83a3eaab6..9dbbbac6541 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -38,17 +38,17 @@ class RegexHandler(Handler): Attributes: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. callback (:obj:`callable`): The callback function for this handler. - pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the + pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the callback function. - pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to + pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to the callback function. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -57,29 +57,43 @@ class RegexHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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`` + DEPRECATED: Please switch to context based callbacks. 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`` + DEPRECATED: Please switch to context based callbacks. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. message_updates (:obj:`bool`, optional): Should "normal" message updates be handled? Default is ``True``. channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled? @@ -106,8 +120,7 @@ def __init__(self, allow_edited=False, message_updates=True, channel_post_updates=False, - edited_updates=False - ): + edited_updates=False): if not message_updates and not channel_post_updates and not edited_updates: raise ValueError( 'message_updates, channel_post_updates and edited_updates are all False') @@ -144,30 +157,23 @@ def check_update(self, update): """ if not isinstance(update, Update) and not update.effective_message: - return False + return None if any([self.message_updates and update.message, self.edited_updates and (update.edited_message or update.edited_channel_post), self.channel_post_updates and update.channel_post]) and \ update.effective_message.text: match = re.match(self.pattern, update.effective_message.text) - return bool(match) - return False - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - - optional_args = self.collect_optional_args(dispatcher, update) - match = re.match(self.pattern, update.effective_message.text) + if match: + return match + def collect_optional_args(self, dispatcher, update=None, check_result=None): + optional_args = super(RegexHandler, self).collect_optional_args(dispatcher, update, + check_result) if self.pass_groups: - optional_args['groups'] = match.groups() + optional_args['groups'] = check_result.groups() if self.pass_groupdict: - optional_args['groupdict'] = match.groupdict() + optional_args['groupdict'] = check_result.groupdict() + return optional_args - return self.callback(dispatcher.bot, update, **optional_args) + def collect_additional_context(self, context, update, dispatcher, check_result): + context.match = check_result diff --git a/telegram/ext/shippingqueryhandler.py b/telegram/ext/shippingqueryhandler.py index bc374d3be3b..8e07d4f47be 100644 --- a/telegram/ext/shippingqueryhandler.py +++ b/telegram/ext/shippingqueryhandler.py @@ -27,13 +27,13 @@ class ShippingQueryHandler(Handler): Attributes: callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to + pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to the callback function. - pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to + pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to the callback function. Note: @@ -42,38 +42,37 @@ class ShippingQueryHandler(Handler): either the user or the chat that the update was sent in. For each update from the same user or in the same chat, it will be the same ``dict``. + Note that this is DEPRECATED, and you should use context based callbacks. See + https://git.io/vp113 for more info. + Args: - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. 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``. + DEPRECATED: Please switch to context based callbacks. """ - def __init__(self, - callback, - pass_update_queue=False, - pass_job_queue=False, - pass_user_data=False, - pass_chat_data=False): - super(ShippingQueryHandler, self).__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) - def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. @@ -85,14 +84,3 @@ def check_update(self, update): """ return isinstance(update, Update) and update.shipping_query - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher, update) - return self.callback(dispatcher.bot, update, **optional_args) diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index 9c76ecc4e4a..01e4936d592 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -33,31 +33,37 @@ class StringCommandHandler(Handler): Attributes: command (:obj:`str`): The command this handler should listen for. callback (:obj:`callable`): The callback function for this handler. - pass_args (:obj:`bool`): Optional. Determines whether the handler should be passed + pass_args (:obj:`bool`): Determines whether the handler should be passed ``args``. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. - Args: - command (:obj:`str`): The command this handler should listen for. - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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 consecutive whitespace characters. Default is ``False`` + DEPRECATED: Please switch to context based callbacks. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. """ @@ -68,7 +74,9 @@ 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, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.command = command self.pass_args = pass_args @@ -76,28 +84,24 @@ def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. Args: - update (:obj:`str`): An incomming command. + update (:obj:`str`): An incoming command. Returns: :obj:`bool` """ - - return (isinstance(update, string_types) and update.startswith('/') - and update[1:].split(' ')[0] == self.command) - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:obj:`str`): An incomming command. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the command. - - """ - - optional_args = self.collect_optional_args(dispatcher) - + if isinstance(update, string_types) and update.startswith('/'): + args = update[1:].split(' ') + if args[0] == self.command: + return args[1:] + + def collect_optional_args(self, dispatcher, update=None, check_result=None): + optional_args = super(StringCommandHandler, self).collect_optional_args(dispatcher, + update, + check_result) if self.pass_args: - optional_args['args'] = update.split()[1:] + optional_args['args'] = check_result + return optional_args - return self.callback(dispatcher.bot, update, **optional_args) + def collect_additional_context(self, context, update, dispatcher, check_result): + context.args = check_result diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index f62b3c574d4..ba52cce0a96 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -38,34 +38,43 @@ class StringRegexHandler(Handler): Attributes: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. callback (:obj:`callable`): The callback function for this handler. - pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the + pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the callback function. - pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to + pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to the callback function. - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. Args: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. 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`` + DEPRECATED: Please switch to context based callbacks. 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`` + DEPRECATED: Please switch to context based callbacks. 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. """ @@ -77,7 +86,9 @@ def __init__(self, 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, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) if isinstance(pattern, string_types): pattern = re.compile(pattern) @@ -90,28 +101,27 @@ def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. Args: - update (:obj:`str`): An incomming command. + update (:obj:`str`): An incoming command. Returns: :obj:`bool` """ - return isinstance(update, string_types) and bool(re.match(self.pattern, update)) - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:obj:`str`): An incomming command. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the command. - - """ - optional_args = self.collect_optional_args(dispatcher) - match = re.match(self.pattern, update) - - if self.pass_groups: - optional_args['groups'] = match.groups() - if self.pass_groupdict: - optional_args['groupdict'] = match.groupdict() - - return self.callback(dispatcher.bot, update, **optional_args) + if isinstance(update, string_types): + match = re.match(self.pattern, update) + if match: + return match + + def collect_optional_args(self, dispatcher, update=None, check_result=None): + optional_args = super(StringRegexHandler, self).collect_optional_args(dispatcher, + update, check_result) + if self.pattern: + if self.pass_groups: + optional_args['groups'] = check_result.groups() + if self.pass_groupdict: + optional_args['groupdict'] = check_result.groupdict() + return optional_args + + def collect_additional_context(self, context, update, dispatcher, check_result): + if self.pattern: + context.match = check_result diff --git a/telegram/ext/typehandler.py b/telegram/ext/typehandler.py index bb388e428b2..6d0e015f412 100644 --- a/telegram/ext/typehandler.py +++ b/telegram/ext/typehandler.py @@ -27,36 +27,48 @@ class TypeHandler(Handler): Attributes: type (:obj:`type`): The ``type`` of updates this handler should process. callback (:obj:`callable`): The callback function for this handler. - strict (:obj:`bool`): Optional. Use ``type`` instead of ``isinstance``. - Default is ``False`` - pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be + strict (:obj:`bool`): Use ``type`` instead of ``isinstance``. Default is ``False``. + pass_update_queue (:obj:`bool`): 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 + pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to the callback function. Args: type (:obj:`type`): The ``type`` of updates this handler should process, as determined by ``isinstance`` - 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. + callback (:obj:`callable`): The callback function for this handler. Will be called when + :attr:`check_update` has determined that an update should be processed by this handler. + Callback signature for context based API: + + ``def callback(update: Update, context: CallbackContext)`` + + The return value of the callback is usually ignored except for the special case of + :class:`telegram.ext.ConversationHandler`. strict (:obj:`bool`, optional): Use ``type`` instead of ``isinstance``. Default is ``False`` 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` that contains new updates which can be used to insert updates. Default is ``False``. + DEPRECATED: Please switch to context based callbacks. pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``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``. + DEPRECATED: Please switch to context based callbacks. """ - def __init__(self, type, callback, strict=False, pass_update_queue=False, + def __init__(self, + type, + callback, + strict=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, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.type = type self.strict = strict @@ -70,20 +82,7 @@ def check_update(self, update): :obj:`bool` """ - if not self.strict: return isinstance(update, self.type) else: return type(update) is self.type - - def handle_update(self, update, dispatcher): - """Send the update to the :attr:`callback`. - - Args: - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - - """ - optional_args = self.collect_optional_args(dispatcher) - - return self.callback(dispatcher.bot, update, **optional_args) diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index 77752ba17d0..89f9576d3d9 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -57,6 +57,7 @@ class Updater(object): dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and dispatches them to the handlers. running (:obj:`bool`): Indicates if the updater is running. + use_context (:obj:`bool`, optional): ``True`` if using context based callbacks. Args: token (:obj:`str`, optional): The bot's token given by the @BotFather. @@ -73,6 +74,9 @@ class Updater(object): `telegram.utils.request.Request` object (ignored if `bot` argument is used). The request_kwargs are very useful for the advanced users who would like to control the default timeouts and/or control the proxy used for http communication. + use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API. + During the deprecation period of the old API the default is ``False``. **New users**: + set this to ``True``. Note: You must supply either a :attr:`bot` or a :attr:`token` argument. @@ -90,7 +94,8 @@ def __init__(self, workers=4, bot=None, user_sig_handler=None, - request_kwargs=None): + request_kwargs=None, + use_context=False): if (token is None) and (bot is None): raise ValueError('`token` or `bot` must be passed') @@ -122,14 +127,16 @@ def __init__(self, self.bot = Bot(token, base_url, request=self._request) self.user_sig_handler = user_sig_handler self.update_queue = Queue() - self.job_queue = JobQueue(self.bot) + self.job_queue = JobQueue() self.__exception_event = Event() self.dispatcher = Dispatcher( self.bot, self.update_queue, job_queue=self.job_queue, workers=workers, - exception_event=self.__exception_event) + exception_event=self.__exception_event, + use_context=use_context) + self.job_queue.set_dispatcher(self.dispatcher) self.last_update_id = 0 self.running = False self.is_idle = False diff --git a/tests/conftest.py b/tests/conftest.py index cf500995c34..7796f6f6ae3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,7 +68,8 @@ def provider_token(bot_info): def create_dp(bot): # Dispatcher is heavy to init (due to many threads and such) so we have a single session # scoped one here, but before each test, reset it (dp fixture below) - dispatcher = Dispatcher(bot, Queue(), job_queue=JobQueue(bot), workers=2) + dispatcher = Dispatcher(bot, Queue(), job_queue=JobQueue(), workers=2, use_context=False) + dispatcher.job_queue.set_dispatcher(dispatcher) thr = Thread(target=dispatcher.start) thr.start() sleep(2) @@ -105,6 +106,13 @@ def dp(_dp): Dispatcher._Dispatcher__singleton_semaphore.release() +@pytest.fixture(scope='function') +def cdp(dp): + dp.use_context = True + yield dp + dp.use_context = False + + def pytest_configure(config): if sys.version_info >= (3,): config.addinivalue_line('filterwarnings', 'ignore::ResourceWarning') diff --git a/tests/test_callbackcontext.py b/tests/test_callbackcontext.py new file mode 100644 index 00000000000..70eb9bcbcf3 --- /dev/null +++ b/tests/test_callbackcontext.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2018 +# 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 import Update, Message, Chat, User, TelegramError +from telegram.ext import CallbackContext + + +class TestCallbackContext(object): + def test_non_context_dp(self, dp): + with pytest.raises(ValueError): + CallbackContext(dp) + + def test_from_job(self, cdp): + job = cdp.job_queue.run_once(lambda x: x, 10) + + callback_context = CallbackContext.from_job(job, cdp) + + assert callback_context.job is job + assert callback_context.chat_data is None + assert callback_context.user_data is None + assert callback_context.bot is cdp.bot + assert callback_context.job_queue is cdp.job_queue + assert callback_context.update_queue is cdp.update_queue + + def test_from_update(self, cdp): + update = Update(0, message=Message(0, User(1, 'user', False), None, Chat(1, 'chat'))) + + callback_context = CallbackContext.from_update(update, cdp) + + assert callback_context.chat_data == {} + assert callback_context.user_data == {} + assert callback_context.bot is cdp.bot + assert callback_context.job_queue is cdp.job_queue + assert callback_context.update_queue is cdp.update_queue + + callback_context_same_user_chat = CallbackContext.from_update(update, cdp) + + callback_context.chat_data['test'] = 'chat' + callback_context.user_data['test'] = 'user' + + assert callback_context_same_user_chat.chat_data is callback_context.chat_data + assert callback_context_same_user_chat.user_data is callback_context.user_data + + update_other_user_chat = Update(0, message=Message(0, User(2, 'user', False), + None, Chat(2, 'chat'))) + + callback_context_other_user_chat = CallbackContext.from_update(update_other_user_chat, cdp) + + assert callback_context_other_user_chat.chat_data is not callback_context.chat_data + assert callback_context_other_user_chat.user_data is not callback_context.user_data + + def test_from_update_not_update(self, cdp): + callback_context = CallbackContext.from_update(None, cdp) + + assert callback_context.chat_data is None + assert callback_context.user_data is None + assert callback_context.bot is cdp.bot + assert callback_context.job_queue is cdp.job_queue + assert callback_context.update_queue is cdp.update_queue + + callback_context = CallbackContext.from_update('', cdp) + + assert callback_context.chat_data is None + assert callback_context.user_data is None + assert callback_context.bot is cdp.bot + assert callback_context.job_queue is cdp.job_queue + assert callback_context.update_queue is cdp.update_queue + + def test_from_error(self, cdp): + error = TelegramError('test') + + update = Update(0, message=Message(0, User(1, 'user', False), None, Chat(1, 'chat'))) + + callback_context = CallbackContext.from_error(update, error, cdp) + + assert callback_context.error is error + assert callback_context.chat_data == {} + assert callback_context.user_data == {} + assert callback_context.bot is cdp.bot + assert callback_context.job_queue is cdp.job_queue + assert callback_context.update_queue is cdp.update_queue diff --git a/tests/test_callbackqueryhandler.py b/tests/test_callbackqueryhandler.py index 97b342f3335..ca6a81a47b4 100644 --- a/tests/test_callbackqueryhandler.py +++ b/tests/test_callbackqueryhandler.py @@ -16,11 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue + import pytest from telegram import (Update, CallbackQuery, Bot, Message, User, Chat, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery) -from telegram.ext import CallbackQueryHandler +from telegram.ext import CallbackQueryHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -47,7 +49,7 @@ def false_update(request): @pytest.fixture(scope='function') def callback_query(bot): - return Update(0, callback_query=CallbackQuery(2, None, None, data='test data')) + return Update(0, callback_query=CallbackQuery(2, User(1, '', False), None, data='test data')) class TestCallbackQueryHandler(object): @@ -80,6 +82,22 @@ def callback_group(self, bot, update, groups=None, groupdict=None): if groupdict is not None: self.test_flag = groupdict == {'begin': 't', 'end': ' data'} + def callback_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + context.chat_data is None and + isinstance(update.callback_query, CallbackQuery)) + + def callback_context_pattern(self, update, context): + if context.match.groups(): + self.test_flag = context.match.groups() == ('t', ' data') + if context.match.groupdict(): + self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' data'} + def test_basic(self, dp, callback_query): handler = CallbackQueryHandler(self.callback_basic) dp.add_handler(handler) @@ -117,14 +135,16 @@ def test_with_passing_group_dict(self, dp, callback_query): assert self.test_flag def test_pass_user_or_chat_data(self, dp, callback_query): - handler = CallbackQueryHandler(self.callback_data_1, pass_user_data=True) + handler = CallbackQueryHandler(self.callback_data_1, + pass_user_data=True) dp.add_handler(handler) dp.process_update(callback_query) assert self.test_flag dp.remove_handler(handler) - handler = CallbackQueryHandler(self.callback_data_1, pass_chat_data=True) + handler = CallbackQueryHandler(self.callback_data_1, + pass_chat_data=True) dp.add_handler(handler) self.test_flag = False @@ -132,7 +152,8 @@ def test_pass_user_or_chat_data(self, dp, callback_query): assert self.test_flag dp.remove_handler(handler) - handler = CallbackQueryHandler(self.callback_data_2, pass_chat_data=True, + handler = CallbackQueryHandler(self.callback_data_2, + pass_chat_data=True, pass_user_data=True) dp.add_handler(handler) @@ -141,14 +162,16 @@ def test_pass_user_or_chat_data(self, dp, callback_query): assert self.test_flag def test_pass_job_or_update_queue(self, dp, callback_query): - handler = CallbackQueryHandler(self.callback_queue_1, pass_job_queue=True) + handler = CallbackQueryHandler(self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update(callback_query) assert self.test_flag dp.remove_handler(handler) - handler = CallbackQueryHandler(self.callback_queue_1, pass_update_queue=True) + handler = CallbackQueryHandler(self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -156,7 +179,8 @@ def test_pass_job_or_update_queue(self, dp, callback_query): assert self.test_flag dp.remove_handler(handler) - handler = CallbackQueryHandler(self.callback_queue_2, pass_job_queue=True, + handler = CallbackQueryHandler(self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) @@ -167,3 +191,26 @@ def test_pass_job_or_update_queue(self, dp, callback_query): def test_other_update_types(self, false_update): handler = CallbackQueryHandler(self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp, callback_query): + handler = CallbackQueryHandler(self.callback_context) + cdp.add_handler(handler) + + cdp.process_update(callback_query) + assert self.test_flag + + def test_context_pattern(self, cdp, callback_query): + handler = CallbackQueryHandler(self.callback_context_pattern, + pattern=r'(?P.*)est(?P.*)') + cdp.add_handler(handler) + + cdp.process_update(callback_query) + assert self.test_flag + + cdp.remove_handler(handler) + handler = CallbackQueryHandler(self.callback_context_pattern, + pattern=r'(t)est(.*)') + cdp.add_handler(handler) + + cdp.process_update(callback_query) + assert self.test_flag diff --git a/tests/test_choseninlineresulthandler.py b/tests/test_choseninlineresulthandler.py index b0ca6b34394..06d8dac438b 100644 --- a/tests/test_choseninlineresulthandler.py +++ b/tests/test_choseninlineresulthandler.py @@ -16,12 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue import pytest from telegram import (Update, Chat, Bot, ChosenInlineResult, User, Message, CallbackQuery, InlineQuery, ShippingQuery, PreCheckoutQuery) -from telegram.ext import ChosenInlineResultHandler +from telegram.ext import ChosenInlineResultHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -78,6 +79,16 @@ 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_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + context.chat_data is None and + isinstance(update.chosen_inline_result, ChosenInlineResult)) + def test_basic(self, dp, chosen_inline_result): handler = ChosenInlineResultHandler(self.callback_basic) dp.add_handler(handler) @@ -87,14 +98,16 @@ def test_basic(self, dp, chosen_inline_result): assert self.test_flag def test_pass_user_or_chat_data(self, dp, chosen_inline_result): - handler = ChosenInlineResultHandler(self.callback_data_1, pass_user_data=True) + handler = ChosenInlineResultHandler(self.callback_data_1, + pass_user_data=True) dp.add_handler(handler) dp.process_update(chosen_inline_result) assert self.test_flag dp.remove_handler(handler) - handler = ChosenInlineResultHandler(self.callback_data_1, pass_chat_data=True) + handler = ChosenInlineResultHandler(self.callback_data_1, + pass_chat_data=True) dp.add_handler(handler) self.test_flag = False @@ -102,8 +115,8 @@ def test_pass_user_or_chat_data(self, dp, chosen_inline_result): assert self.test_flag dp.remove_handler(handler) - handler = ChosenInlineResultHandler(self.callback_data_2, pass_chat_data=True, - pass_user_data=True) + handler = ChosenInlineResultHandler(self.callback_data_2, + pass_chat_data=True, pass_user_data=True) dp.add_handler(handler) self.test_flag = False @@ -111,14 +124,16 @@ def test_pass_user_or_chat_data(self, dp, chosen_inline_result): assert self.test_flag def test_pass_job_or_update_queue(self, dp, chosen_inline_result): - handler = ChosenInlineResultHandler(self.callback_queue_1, pass_job_queue=True) + handler = ChosenInlineResultHandler(self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update(chosen_inline_result) assert self.test_flag dp.remove_handler(handler) - handler = ChosenInlineResultHandler(self.callback_queue_1, pass_update_queue=True) + handler = ChosenInlineResultHandler(self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -126,8 +141,8 @@ def test_pass_job_or_update_queue(self, dp, chosen_inline_result): assert self.test_flag dp.remove_handler(handler) - handler = ChosenInlineResultHandler(self.callback_queue_2, pass_job_queue=True, - pass_update_queue=True) + handler = ChosenInlineResultHandler(self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -137,3 +152,10 @@ def test_pass_job_or_update_queue(self, dp, chosen_inline_result): def test_other_update_types(self, false_update): handler = ChosenInlineResultHandler(self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp, chosen_inline_result): + handler = ChosenInlineResultHandler(self.callback_context) + cdp.add_handler(handler) + + cdp.process_update(chosen_inline_result) + assert self.test_flag diff --git a/tests/test_commandhandler.py b/tests/test_commandhandler.py index c8ff4a9569e..6264f9d77eb 100644 --- a/tests/test_commandhandler.py +++ b/tests/test_commandhandler.py @@ -16,12 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue import pytest from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery) -from telegram.ext import CommandHandler, Filters, BaseFilter +from telegram.ext import CommandHandler, Filters, BaseFilter, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='test') @@ -48,7 +49,7 @@ def false_update(request): @pytest.fixture(scope='function') def message(bot): - return Message(1, None, None, None, bot=bot) + return Message(1, User(1, '', False), None, Chat(1, ''), bot=bot) class TestCommandHandler(object): @@ -83,51 +84,73 @@ def ch_callback_args(self, bot, update, args): else: self.test_flag = args == ['one', 'two'] + def callback_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + isinstance(context.chat_data, dict) and + isinstance(update.message, Message)) + + def callback_context_args(self, update, context): + self.test_flag = context.args == ['one', 'two'] + def test_basic(self, dp, message): handler = CommandHandler('test', self.callback_basic) dp.add_handler(handler) message.text = '/test' - assert handler.check_update(Update(0, message)) dp.process_update(Update(0, message)) assert self.test_flag message.text = '/nottest' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False message.text = 'test' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False message.text = 'not /test at start' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False def test_command_list(self, message): handler = CommandHandler(['test', 'start'], self.callback_basic) message.text = '/test' - assert handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) message.text = '/start' - assert handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) message.text = '/stop' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False def test_edited(self, message): - handler = CommandHandler('test', self.callback_basic, allow_edited=False) + handler = CommandHandler('test', self.callback_basic, + allow_edited=False) message.text = '/test' - assert handler.check_update(Update(0, message)) - assert not handler.check_update(Update(0, edited_message=message)) + check = handler.check_update(Update(0, message)) + assert check is not None and check is not False + check = handler.check_update(Update(0, edited_message=message)) + assert check is None or check is False handler.allow_edited = True - assert handler.check_update(Update(0, message)) - assert handler.check_update(Update(0, edited_message=message)) + check = handler.check_update(Update(0, message)) + assert check is not None and check is not False + check = handler.check_update(Update(0, edited_message=message)) + assert check is not None and check is not False def test_directed_commands(self, message): handler = CommandHandler('test', self.callback_basic) message.text = '/test@{}'.format(message.bot.username) - assert handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is not None and check is not False message.text = '/test@otherbot' assert not handler.check_update(Update(0, message)) @@ -137,10 +160,12 @@ def test_with_filter(self, message): message.chat = Chat(-23, 'group') message.text = '/test' - assert handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is not None and check is not False message.chat = Chat(23, 'private') - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False def test_pass_args(self, dp, message): handler = CommandHandler('test', self.ch_callback_args, pass_args=True) @@ -170,7 +195,8 @@ def test_newline(self, dp, message): dp.add_handler(handler) message.text = '/test\nfoobar' - assert handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is not None and check is not False dp.process_update(Update(0, message)) assert self.test_flag @@ -180,7 +206,8 @@ def test_single_char(self, dp, message): dp.add_handler(handler) message.text = 'a' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False def test_single_slash(self, dp, message): # Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/871 @@ -188,13 +215,16 @@ def test_single_slash(self, dp, message): dp.add_handler(handler) message.text = '/' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False message.text = '/ test' - assert not handler.check_update(Update(0, message)) + check = handler.check_update(Update(0, message)) + assert check is None or check is False def test_pass_user_or_chat_data(self, dp, message): - handler = CommandHandler('test', self.callback_data_1, pass_user_data=True) + handler = CommandHandler('test', self.callback_data_1, + pass_user_data=True) dp.add_handler(handler) message.text = '/test' @@ -202,7 +232,8 @@ def test_pass_user_or_chat_data(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = CommandHandler('test', self.callback_data_1, pass_chat_data=True) + handler = CommandHandler('test', self.callback_data_1, + pass_chat_data=True) dp.add_handler(handler) self.test_flag = False @@ -210,7 +241,8 @@ def test_pass_user_or_chat_data(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = CommandHandler('test', self.callback_data_2, pass_chat_data=True, + handler = CommandHandler('test', self.callback_data_2, + pass_chat_data=True, pass_user_data=True) dp.add_handler(handler) @@ -219,7 +251,8 @@ def test_pass_user_or_chat_data(self, dp, message): assert self.test_flag def test_pass_job_or_update_queue(self, dp, message): - handler = CommandHandler('test', self.callback_queue_1, pass_job_queue=True) + handler = CommandHandler('test', self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) message.text = '/test' @@ -227,7 +260,8 @@ def test_pass_job_or_update_queue(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = CommandHandler('test', self.callback_queue_1, pass_update_queue=True) + handler = CommandHandler('test', self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -235,7 +269,8 @@ def test_pass_job_or_update_queue(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = CommandHandler('test', self.callback_queue_2, pass_job_queue=True, + handler = CommandHandler('test', self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) @@ -245,7 +280,8 @@ def test_pass_job_or_update_queue(self, dp, message): def test_other_update_types(self, false_update): handler = CommandHandler('test', self.callback_basic) - assert not handler.check_update(false_update) + check = handler.check_update(false_update) + assert check is None or check is False def test_filters_for_wrong_command(self, message): """Filters should not be executed if the command does not match the handler""" @@ -259,9 +295,31 @@ def filter(self, message): test_filter = TestFilter() - handler = CommandHandler('foo', self.callback_basic, filters=test_filter) + handler = CommandHandler('foo', self.callback_basic, + filters=test_filter) message.text = '/bar' - handler.check_update(Update(0, message=message)) + check = handler.check_update(Update(0, message=message)) + assert check is None or check is False assert not test_filter.tested + + def test_context(self, cdp, message): + handler = CommandHandler('test', self.callback_context) + cdp.add_handler(handler) + + message.text = '/test' + cdp.process_update(Update(0, message)) + assert self.test_flag + + def test_context_args(self, cdp, message): + handler = CommandHandler('test', self.callback_context_args) + cdp.add_handler(handler) + + message.text = '/test' + cdp.process_update(Update(0, message)) + assert not self.test_flag + + message.text = '/test one two' + cdp.process_update(Update(0, message)) + assert self.test_flag diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 7f531ff36ba..2cb2c22c6bc 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -52,7 +52,8 @@ def reset(self): self.current_state = dict() self.entry_points = [CommandHandler('start', self.start)] self.states = { - self.THIRSTY: [CommandHandler('brew', self.brew), CommandHandler('wait', self.start)], + self.THIRSTY: [CommandHandler('brew', self.brew), + CommandHandler('wait', self.start)], self.BREWING: [CommandHandler('pourCoffee', self.drink)], self.DRINKING: [CommandHandler('startCoding', self.code), @@ -260,7 +261,8 @@ def two(bot, update): def test_end_on_first_message(self, dp, bot, user1): handler = ConversationHandler( - entry_points=[CommandHandler('start', self.start_end)], states={}, fallbacks=[]) + entry_points=[CommandHandler('start', self.start_end)], states={}, + fallbacks=[]) dp.add_handler(handler) # User starts the state machine and immediately ends it. @@ -272,7 +274,8 @@ def test_end_on_first_message_async(self, dp, bot, user1): start_end_async = (lambda bot, update: dp.run_async(self.start_end, bot, update)) handler = ConversationHandler( - entry_points=[CommandHandler('start', start_end_async)], states={}, fallbacks=[]) + entry_points=[CommandHandler('start', start_end_async)], states={}, + fallbacks=[]) dp.add_handler(handler) # User starts the state machine with an async function that immediately ends the @@ -291,7 +294,8 @@ def test_end_on_first_message_async(self, dp, bot, user1): def test_per_chat_message_without_chat(self, bot, user1): handler = ConversationHandler( - entry_points=[CommandHandler('start', self.start_end)], states={}, fallbacks=[]) + entry_points=[CommandHandler('start', self.start_end)], states={}, + fallbacks=[]) cbq = CallbackQuery(0, user1, None, None, bot=bot) update = Update(0, callback_query=cbq) assert not handler.check_update(update) diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index 6dc6b236f4c..bb31e3f5a06 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -16,15 +16,17 @@ # # 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 sys from queue import Queue from threading import current_thread from time import sleep import pytest -from telegram import TelegramError, Message, User, Chat, Update -from telegram.ext import MessageHandler, Filters, CommandHandler +from telegram import TelegramError, Message, User, Chat, Update, Bot +from telegram.ext import MessageHandler, Filters, CommandHandler, CallbackContext, JobQueue from telegram.ext.dispatcher import run_async, Dispatcher, DispatcherHandlerStop +from telegram.utils.deprecate import TelegramDeprecationWarning from tests.conftest import create_dp @@ -67,6 +69,14 @@ def callback_if_not_update_queue(self, bot, update, update_queue=None): if update_queue is not None: self.received = update.message + def callback_context(self, update, context): + if (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.error, TelegramError)): + self.received = context.error.message + def test_error_handler(self, dp): dp.add_error_handler(self.error_handler) error = TelegramError('Unauthorized.') @@ -316,3 +326,17 @@ def error(b, u, e): dp.process_update(update) assert passed == ['start1', 'error', err] assert passed[2] is err + + def test_error_handler_context(self, cdp): + cdp.add_error_handler(self.callback_context) + + error = TelegramError('Unauthorized.') + cdp.update_queue.put(error) + sleep(.1) + assert self.received == 'Unauthorized.' + + @pytest.mark.skipif(sys.version_info < (3, 0), reason='pytest fails this for no reason') + def test_non_context_deprecation(self, dp): + with pytest.warns(TelegramDeprecationWarning): + Dispatcher(dp.bot, dp.update_queue, job_queue=dp.job_queue, workers=0, + use_context=False) diff --git a/tests/test_inlinequeryhandler.py b/tests/test_inlinequeryhandler.py index 1ff82889160..689f1ad10db 100644 --- a/tests/test_inlinequeryhandler.py +++ b/tests/test_inlinequeryhandler.py @@ -16,11 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue + import pytest from telegram import (Update, CallbackQuery, Bot, Message, User, Chat, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery, Location) -from telegram.ext import InlineQueryHandler +from telegram.ext import InlineQueryHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -84,6 +86,22 @@ def callback_group(self, bot, update, groups=None, groupdict=None): if groupdict is not None: self.test_flag = groupdict == {'begin': 't', 'end': ' query'} + def callback_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + context.chat_data is None and + isinstance(update.inline_query, InlineQuery)) + + def callback_context_pattern(self, update, context): + if context.match.groups(): + self.test_flag = context.match.groups() == ('t', ' query') + if context.match.groupdict(): + self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' query'} + def test_basic(self, dp, inline_query): handler = InlineQueryHandler(self.callback_basic) dp.add_handler(handler) @@ -94,7 +112,8 @@ def test_basic(self, dp, inline_query): assert self.test_flag def test_with_pattern(self, inline_query): - handler = InlineQueryHandler(self.callback_basic, pattern='(?P.*)est(?P.*)') + handler = InlineQueryHandler(self.callback_basic, + pattern='(?P.*)est(?P.*)') assert handler.check_update(inline_query) @@ -152,7 +171,8 @@ def test_pass_job_or_update_queue(self, dp, inline_query): assert self.test_flag dp.remove_handler(handler) - handler = InlineQueryHandler(self.callback_queue_1, pass_update_queue=True) + handler = InlineQueryHandler(self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -171,3 +191,26 @@ def test_pass_job_or_update_queue(self, dp, inline_query): def test_other_update_types(self, false_update): handler = InlineQueryHandler(self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp, inline_query): + handler = InlineQueryHandler(self.callback_context) + cdp.add_handler(handler) + + cdp.process_update(inline_query) + assert self.test_flag + + def test_context_pattern(self, cdp, inline_query): + handler = InlineQueryHandler(self.callback_context_pattern, + pattern=r'(?P.*)est(?P.*)') + cdp.add_handler(handler) + + cdp.process_update(inline_query) + assert self.test_flag + + cdp.remove_handler(handler) + handler = InlineQueryHandler(self.callback_context_pattern, + pattern=r'(t)est(.*)') + cdp.add_handler(handler) + + cdp.process_update(inline_query) + assert self.test_flag diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 083a3d1941a..e3ed8e6995d 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -18,18 +18,22 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import datetime import os +import sys import time +from queue import Queue from time import sleep import pytest from flaky import flaky -from telegram.ext import JobQueue, Updater, Job +from telegram.ext import JobQueue, Updater, Job, CallbackContext +from telegram.utils.deprecate import TelegramDeprecationWarning @pytest.fixture(scope='function') -def job_queue(bot): - jq = JobQueue(bot) +def job_queue(bot, _dp): + jq = JobQueue() + jq.set_dispatcher(_dp) jq.start() yield jq jq.stop() @@ -62,6 +66,16 @@ def job_run_once_with_context(self, bot, job): def job_datetime_tests(self, bot, job): self.job_time = time.time() + def job_context_based_callback(self, context): + if (isinstance(context, CallbackContext) and + isinstance(context.job, Job) and + isinstance(context.update_queue, Queue) and + context.job.context == 2 and + context.chat_data is None and + context.user_data is None and + context.job_queue is context.job.job_queue): + self.result += 1 + def test_run_once(self, job_queue): job_queue.run_once(self.job_run_once, 0.01) sleep(0.02) @@ -244,3 +258,15 @@ def test_get_jobs(self, job_queue): assert job_queue.jobs() == (job1, job2, job3) assert job_queue.get_jobs_by_name('name1') == (job1, job2) assert job_queue.get_jobs_by_name('name2') == (job3,) + + @pytest.mark.skipif(sys.version_info < (3, 0), reason='pytest fails this for no reason') + def test_bot_in_init_deprecation(self, bot): + with pytest.warns(TelegramDeprecationWarning): + JobQueue(bot) + + def test_context_based_callback(self, job_queue): + job_queue.run_once(self.job_context_based_callback, 0.01, context=2) + + sleep(0.03) + + assert self.result == 0 diff --git a/tests/test_messagehandler.py b/tests/test_messagehandler.py index 8ccac3335d8..1d1d6c8354f 100644 --- a/tests/test_messagehandler.py +++ b/tests/test_messagehandler.py @@ -16,12 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue import pytest from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery) -from telegram.ext import Filters, MessageHandler +from telegram.ext import Filters, MessageHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -45,7 +46,7 @@ def false_update(request): @pytest.fixture(scope='class') def message(bot): - return Message(1, None, None, None, bot=bot) + return Message(1, User(1, '', False), None, Chat(1, ''), bot=bot) class TestMessageHandler(object): @@ -72,6 +73,25 @@ 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_context(self, update, context): + self.test_flag = ( + isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.chat_data, dict) and + ( + (isinstance(context.user_data, dict) and + (isinstance(update.message, Message) or + isinstance(update.edited_message, Message))) + or + (context.user_data is None and + (isinstance(update.channel_post, Message) or + isinstance(update.edited_channel_post, Message))) + ) + ) + def test_basic(self, dp, message): handler = MessageHandler(None, self.callback_basic) dp.add_handler(handler) @@ -90,8 +110,9 @@ def test_edited(self, message): assert handler.check_update(Update(0, edited_channel_post=message)) def test_channel_post(self, message): - handler = MessageHandler(None, self.callback_basic, edited_updates=False, - message_updates=False, channel_post_updates=True) + handler = MessageHandler(None, self.callback_basic, + edited_updates=False, message_updates=False, + channel_post_updates=True) assert not handler.check_update(Update(0, edited_message=message)) assert not handler.check_update(Update(0, message=message)) @@ -109,8 +130,9 @@ def test_multiple_flags(self, message): def test_allow_edited(self, message): with pytest.warns(UserWarning): - handler = MessageHandler(None, self.callback_basic, message_updates=True, - allow_edited=True, channel_post_updates=False) + handler = MessageHandler(None, self.callback_basic, + message_updates=True, allow_edited=True, + channel_post_updates=False) assert handler.check_update(Update(0, edited_message=message)) assert handler.check_update(Update(0, message=message)) @@ -132,14 +154,16 @@ def test_with_filter(self, message): assert not handler.check_update(Update(0, message)) def test_pass_user_or_chat_data(self, dp, message): - handler = MessageHandler(None, self.callback_data_1, pass_user_data=True) + handler = MessageHandler(None, self.callback_data_1, + pass_user_data=True) dp.add_handler(handler) dp.process_update(Update(0, message=message)) assert self.test_flag dp.remove_handler(handler) - handler = MessageHandler(None, self.callback_data_1, pass_chat_data=True) + handler = MessageHandler(None, self.callback_data_1, + pass_chat_data=True) dp.add_handler(handler) self.test_flag = False @@ -147,8 +171,8 @@ def test_pass_user_or_chat_data(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = MessageHandler(None, self.callback_data_2, pass_chat_data=True, - pass_user_data=True) + handler = MessageHandler(None, self.callback_data_2, + pass_chat_data=True, pass_user_data=True) dp.add_handler(handler) self.test_flag = False @@ -156,14 +180,16 @@ def test_pass_user_or_chat_data(self, dp, message): assert self.test_flag def test_pass_job_or_update_queue(self, dp, message): - handler = MessageHandler(None, self.callback_queue_1, pass_job_queue=True) + handler = MessageHandler(None, self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update(Update(0, message=message)) assert self.test_flag dp.remove_handler(handler) - handler = MessageHandler(None, self.callback_queue_1, pass_update_queue=True) + handler = MessageHandler(None, self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -171,8 +197,8 @@ def test_pass_job_or_update_queue(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = MessageHandler(None, self.callback_queue_2, pass_job_queue=True, - pass_update_queue=True) + handler = MessageHandler(None, self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -182,3 +208,23 @@ def test_pass_job_or_update_queue(self, dp, message): def test_other_update_types(self, false_update): handler = MessageHandler(None, self.callback_basic, edited_updates=True) assert not handler.check_update(false_update) + + def test_context(self, cdp, message): + handler = MessageHandler(None, self.callback_context, + edited_updates=True, channel_post_updates=True) + cdp.add_handler(handler) + + cdp.process_update(Update(0, message=message)) + assert self.test_flag + + self.test_flag = False + cdp.process_update(Update(0, edited_message=message)) + assert self.test_flag + + self.test_flag = False + cdp.process_update(Update(0, channel_post=message)) + assert self.test_flag + + self.test_flag = False + cdp.process_update(Update(0, edited_channel_post=message)) + assert self.test_flag diff --git a/tests/test_precheckoutqueryhandler.py b/tests/test_precheckoutqueryhandler.py index 01f4aec89b3..a5cfba99d9f 100644 --- a/tests/test_precheckoutqueryhandler.py +++ b/tests/test_precheckoutqueryhandler.py @@ -16,12 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue import pytest from telegram import (Update, Chat, Bot, ChosenInlineResult, User, Message, CallbackQuery, InlineQuery, ShippingQuery, PreCheckoutQuery) -from telegram.ext import PreCheckoutQueryHandler +from telegram.ext import PreCheckoutQueryHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -78,6 +79,16 @@ 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_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + context.chat_data is None and + isinstance(update.pre_checkout_query, PreCheckoutQuery)) + def test_basic(self, dp, pre_checkout_query): handler = PreCheckoutQueryHandler(self.callback_basic) dp.add_handler(handler) @@ -87,14 +98,16 @@ def test_basic(self, dp, pre_checkout_query): assert self.test_flag def test_pass_user_or_chat_data(self, dp, pre_checkout_query): - handler = PreCheckoutQueryHandler(self.callback_data_1, pass_user_data=True) + handler = PreCheckoutQueryHandler(self.callback_data_1, + pass_user_data=True) dp.add_handler(handler) dp.process_update(pre_checkout_query) assert self.test_flag dp.remove_handler(handler) - handler = PreCheckoutQueryHandler(self.callback_data_1, pass_chat_data=True) + handler = PreCheckoutQueryHandler(self.callback_data_1, + pass_chat_data=True) dp.add_handler(handler) self.test_flag = False @@ -102,7 +115,8 @@ def test_pass_user_or_chat_data(self, dp, pre_checkout_query): assert self.test_flag dp.remove_handler(handler) - handler = PreCheckoutQueryHandler(self.callback_data_2, pass_chat_data=True, + handler = PreCheckoutQueryHandler(self.callback_data_2, + pass_chat_data=True, pass_user_data=True) dp.add_handler(handler) @@ -111,14 +125,16 @@ def test_pass_user_or_chat_data(self, dp, pre_checkout_query): assert self.test_flag def test_pass_job_or_update_queue(self, dp, pre_checkout_query): - handler = PreCheckoutQueryHandler(self.callback_queue_1, pass_job_queue=True) + handler = PreCheckoutQueryHandler(self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update(pre_checkout_query) assert self.test_flag dp.remove_handler(handler) - handler = PreCheckoutQueryHandler(self.callback_queue_1, pass_update_queue=True) + handler = PreCheckoutQueryHandler(self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -126,7 +142,8 @@ def test_pass_job_or_update_queue(self, dp, pre_checkout_query): assert self.test_flag dp.remove_handler(handler) - handler = PreCheckoutQueryHandler(self.callback_queue_2, pass_job_queue=True, + handler = PreCheckoutQueryHandler(self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) @@ -137,3 +154,10 @@ def test_pass_job_or_update_queue(self, dp, pre_checkout_query): def test_other_update_types(self, false_update): handler = PreCheckoutQueryHandler(self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp, pre_checkout_query): + handler = PreCheckoutQueryHandler(self.callback_context) + cdp.add_handler(handler) + + cdp.process_update(pre_checkout_query) + assert self.test_flag diff --git a/tests/test_regexhandler.py b/tests/test_regexhandler.py index 025a06b774f..6e3af9766b0 100644 --- a/tests/test_regexhandler.py +++ b/tests/test_regexhandler.py @@ -16,12 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue import pytest from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery) -from telegram.ext import RegexHandler +from telegram.ext import RegexHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -45,7 +46,7 @@ def false_update(request): @pytest.fixture(scope='class') def message(bot): - return Message(1, None, None, None, text='test message', bot=bot) + return Message(1, User(1, '', False), None, Chat(1, ''), text='test message', bot=bot) class TestRegexHandler(object): @@ -78,6 +79,22 @@ def callback_group(self, bot, update, groups=None, groupdict=None): if groupdict is not None: self.test_flag = groupdict == {'begin': 't', 'end': ' message'} + def callback_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + isinstance(context.chat_data, dict) and + isinstance(update.message, Message)) + + def callback_context_pattern(self, update, context): + if context.match.groups(): + self.test_flag = context.match.groups() == ('t', ' message') + if context.match.groupdict(): + self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' message'} + def test_basic(self, dp, message): handler = RegexHandler('.*', self.callback_basic) dp.add_handler(handler) @@ -140,7 +157,8 @@ def test_multiple_flags(self, message): def test_allow_edited(self, message): with pytest.warns(UserWarning): - handler = RegexHandler('.*', self.callback_basic, message_updates=True, + handler = RegexHandler('.*', self.callback_basic, + message_updates=True, allow_edited=True) assert handler.check_update(Update(0, edited_message=message)) @@ -185,7 +203,8 @@ def test_pass_job_or_update_queue(self, dp, message): assert self.test_flag dp.remove_handler(handler) - handler = RegexHandler('.*', self.callback_queue_1, pass_update_queue=True) + handler = RegexHandler('.*', self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -204,3 +223,24 @@ def test_pass_job_or_update_queue(self, dp, message): def test_other_update_types(self, false_update): handler = RegexHandler('.*', self.callback_basic, edited_updates=True) assert not handler.check_update(false_update) + + def test_context(self, cdp, message): + handler = RegexHandler(r'(t)est(.*)', self.callback_context) + cdp.add_handler(handler) + + cdp.process_update(Update(0, message=message)) + assert self.test_flag + + def test_context_pattern(self, cdp, message): + handler = RegexHandler(r'(t)est(.*)', self.callback_context_pattern) + cdp.add_handler(handler) + + cdp.process_update(Update(0, message=message)) + assert self.test_flag + + cdp.remove_handler(handler) + handler = RegexHandler(r'(t)est(.*)', self.callback_context_pattern) + cdp.add_handler(handler) + + cdp.process_update(Update(0, message=message)) + assert self.test_flag diff --git a/tests/test_shippingqueryhandler.py b/tests/test_shippingqueryhandler.py index d164dee11fd..64e70b69697 100644 --- a/tests/test_shippingqueryhandler.py +++ b/tests/test_shippingqueryhandler.py @@ -16,12 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue import pytest from telegram import (Update, Chat, Bot, ChosenInlineResult, User, Message, CallbackQuery, InlineQuery, ShippingQuery, PreCheckoutQuery, ShippingAddress) -from telegram.ext import ShippingQueryHandler +from telegram.ext import ShippingQueryHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -79,6 +80,16 @@ 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_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, Update) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + isinstance(context.user_data, dict) and + context.chat_data is None and + isinstance(update.shipping_query, ShippingQuery)) + def test_basic(self, dp, shiping_query): handler = ShippingQueryHandler(self.callback_basic) dp.add_handler(handler) @@ -88,14 +99,16 @@ def test_basic(self, dp, shiping_query): assert self.test_flag def test_pass_user_or_chat_data(self, dp, shiping_query): - handler = ShippingQueryHandler(self.callback_data_1, pass_user_data=True) + handler = ShippingQueryHandler(self.callback_data_1, + pass_user_data=True) dp.add_handler(handler) dp.process_update(shiping_query) assert self.test_flag dp.remove_handler(handler) - handler = ShippingQueryHandler(self.callback_data_1, pass_chat_data=True) + handler = ShippingQueryHandler(self.callback_data_1, + pass_chat_data=True) dp.add_handler(handler) self.test_flag = False @@ -103,7 +116,8 @@ def test_pass_user_or_chat_data(self, dp, shiping_query): assert self.test_flag dp.remove_handler(handler) - handler = ShippingQueryHandler(self.callback_data_2, pass_chat_data=True, + handler = ShippingQueryHandler(self.callback_data_2, + pass_chat_data=True, pass_user_data=True) dp.add_handler(handler) @@ -112,14 +126,16 @@ def test_pass_user_or_chat_data(self, dp, shiping_query): assert self.test_flag def test_pass_job_or_update_queue(self, dp, shiping_query): - handler = ShippingQueryHandler(self.callback_queue_1, pass_job_queue=True) + handler = ShippingQueryHandler(self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update(shiping_query) assert self.test_flag dp.remove_handler(handler) - handler = ShippingQueryHandler(self.callback_queue_1, pass_update_queue=True) + handler = ShippingQueryHandler(self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -127,7 +143,8 @@ def test_pass_job_or_update_queue(self, dp, shiping_query): assert self.test_flag dp.remove_handler(handler) - handler = ShippingQueryHandler(self.callback_queue_2, pass_job_queue=True, + handler = ShippingQueryHandler(self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) @@ -138,3 +155,10 @@ def test_pass_job_or_update_queue(self, dp, shiping_query): def test_other_update_types(self, false_update): handler = ShippingQueryHandler(self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp, shiping_query): + handler = ShippingQueryHandler(self.callback_context) + cdp.add_handler(handler) + + cdp.process_update(shiping_query) + assert self.test_flag diff --git a/tests/test_stringcommandhandler.py b/tests/test_stringcommandhandler.py index 382471ab475..52aa1ae23d4 100644 --- a/tests/test_stringcommandhandler.py +++ b/tests/test_stringcommandhandler.py @@ -16,11 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue + import pytest from telegram import (Bot, Update, Message, User, Chat, CallbackQuery, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery) -from telegram.ext import StringCommandHandler +from telegram.ext import StringCommandHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -71,20 +73,37 @@ def sch_callback_args(self, bot, update, args): else: self.test_flag = args == ['one', 'two'] + def callback_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, str) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + context.user_data is None and + context.chat_data is None) + + def callback_context_args(self, update, context): + self.test_flag = context.args == ['one', 'two'] + def test_basic(self, dp): handler = StringCommandHandler('test', self.callback_basic) dp.add_handler(handler) - assert handler.check_update('/test') + check = handler.check_update('/test') + assert check is not None and check is not False dp.process_update('/test') assert self.test_flag - assert not handler.check_update('/nottest') - assert not handler.check_update('not /test in front') - assert handler.check_update('/test followed by text') + check = handler.check_update('/nottest') + assert check is None or check is False + check = handler.check_update('not /test in front') + assert check is None or check is False + check = handler.check_update('/test followed by text') + assert check is not None and check is not False def test_pass_args(self, dp): - handler = StringCommandHandler('test', self.sch_callback_args, pass_args=True) + handler = StringCommandHandler('test', self.sch_callback_args, + pass_args=True) dp.add_handler(handler) dp.process_update('/test') @@ -95,14 +114,16 @@ def test_pass_args(self, dp): assert self.test_flag def test_pass_job_or_update_queue(self, dp): - handler = StringCommandHandler('test', self.callback_queue_1, pass_job_queue=True) + handler = StringCommandHandler('test', self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update('/test') assert self.test_flag dp.remove_handler(handler) - handler = StringCommandHandler('test', self.callback_queue_1, pass_update_queue=True) + handler = StringCommandHandler('test', self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -110,7 +131,8 @@ def test_pass_job_or_update_queue(self, dp): assert self.test_flag dp.remove_handler(handler) - handler = StringCommandHandler('test', self.callback_queue_2, pass_job_queue=True, + handler = StringCommandHandler('test', self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) @@ -121,3 +143,20 @@ def test_pass_job_or_update_queue(self, dp): def test_other_update_types(self, false_update): handler = StringCommandHandler('test', self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp): + handler = StringCommandHandler('test', self.callback_context) + cdp.add_handler(handler) + + cdp.process_update('/test') + assert self.test_flag + + def test_context_args(self, cdp): + handler = StringCommandHandler('test', self.callback_context_args) + cdp.add_handler(handler) + + cdp.process_update('/test') + assert not self.test_flag + + cdp.process_update('/test one two') + assert self.test_flag diff --git a/tests/test_stringregexhandler.py b/tests/test_stringregexhandler.py index e5ae8ffdc16..cc09350e37c 100644 --- a/tests/test_stringregexhandler.py +++ b/tests/test_stringregexhandler.py @@ -16,11 +16,13 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +from queue import Queue + import pytest from telegram import (Bot, Update, Message, User, Chat, CallbackQuery, InlineQuery, ChosenInlineResult, ShippingQuery, PreCheckoutQuery) -from telegram.ext import StringRegexHandler +from telegram.ext import StringRegexHandler, CallbackContext, JobQueue message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') @@ -71,6 +73,19 @@ def callback_group(self, bot, update, groups=None, groupdict=None): if groupdict is not None: self.test_flag = groupdict == {'begin': 't', 'end': ' message'} + def callback_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, str) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue)) + + def callback_context_pattern(self, update, context): + if context.match.groups(): + self.test_flag = context.match.groups() == ('t', ' message') + if context.match.groupdict(): + self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' message'} + def test_basic(self, dp): handler = StringRegexHandler('(?P.*)est(?P.*)', self.callback_basic) dp.add_handler(handler) @@ -99,14 +114,16 @@ def test_with_passing_group_dict(self, dp): assert self.test_flag def test_pass_job_or_update_queue(self, dp): - handler = StringRegexHandler('test', self.callback_queue_1, pass_job_queue=True) + handler = StringRegexHandler('test', self.callback_queue_1, + pass_job_queue=True) dp.add_handler(handler) dp.process_update('test') assert self.test_flag dp.remove_handler(handler) - handler = StringRegexHandler('test', self.callback_queue_1, pass_update_queue=True) + handler = StringRegexHandler('test', self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -114,8 +131,8 @@ def test_pass_job_or_update_queue(self, dp): assert self.test_flag dp.remove_handler(handler) - handler = StringRegexHandler('test', self.callback_queue_2, pass_job_queue=True, - pass_update_queue=True) + handler = StringRegexHandler('test', self.callback_queue_2, + pass_job_queue=True, pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -125,3 +142,24 @@ def test_pass_job_or_update_queue(self, dp): def test_other_update_types(self, false_update): handler = StringRegexHandler('test', self.callback_basic) assert not handler.check_update(false_update) + + def test_context(self, cdp): + handler = StringRegexHandler(r'(t)est(.*)', self.callback_context) + cdp.add_handler(handler) + + cdp.process_update('test message') + assert self.test_flag + + def test_context_pattern(self, cdp): + handler = StringRegexHandler(r'(t)est(.*)', self.callback_context_pattern) + cdp.add_handler(handler) + + cdp.process_update('test message') + assert self.test_flag + + cdp.remove_handler(handler) + handler = StringRegexHandler(r'(t)est(.*)', self.callback_context_pattern) + cdp.add_handler(handler) + + cdp.process_update('test message') + assert self.test_flag diff --git a/tests/test_typehandler.py b/tests/test_typehandler.py index 6dcc982d339..f0fc362a06b 100644 --- a/tests/test_typehandler.py +++ b/tests/test_typehandler.py @@ -17,11 +17,12 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. from collections import OrderedDict +from queue import Queue import pytest from telegram import Bot -from telegram.ext import TypeHandler +from telegram.ext import TypeHandler, CallbackContext, JobQueue class TestTypeHandler(object): @@ -42,6 +43,15 @@ 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_context(self, update, context): + self.test_flag = (isinstance(context, CallbackContext) and + isinstance(context.bot, Bot) and + isinstance(update, dict) and + isinstance(context.update_queue, Queue) and + isinstance(context.job_queue, JobQueue) and + context.user_data is None and + context.chat_data is None) + def test_basic(self, dp): handler = TypeHandler(dict, self.callback_basic) dp.add_handler(handler) @@ -65,7 +75,8 @@ def test_pass_job_or_update_queue(self, dp): assert self.test_flag dp.remove_handler(handler) - handler = TypeHandler(dict, self.callback_queue_1, pass_update_queue=True) + handler = TypeHandler(dict, self.callback_queue_1, + pass_update_queue=True) dp.add_handler(handler) self.test_flag = False @@ -80,3 +91,10 @@ 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 test_context(self, cdp): + handler = TypeHandler(dict, self.callback_context) + cdp.add_handler(handler) + + cdp.process_update({'a': 1, 'b': 2}) + assert self.test_flag 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