mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 14:35:00 +01:00
Introduce Builder Pattern for Updater and Dispatcher (#2646)
This commit is contained in:
parent
edb30cf342
commit
7afce46d9f
45 changed files with 2264 additions and 678 deletions
|
@ -14,7 +14,7 @@ repos:
|
|||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: v2.8.3
|
||||
rev: v2.10.2
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^(telegram|examples)/.*\.py$
|
||||
|
@ -27,12 +27,17 @@ repos:
|
|||
- cachetools==4.2.2
|
||||
- . # this basically does `pip install -e .`
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.812
|
||||
rev: v0.910
|
||||
hooks:
|
||||
- id: mypy
|
||||
name: mypy-ptb
|
||||
files: ^telegram/.*\.py$
|
||||
additional_dependencies:
|
||||
- types-ujson
|
||||
- types-pytz
|
||||
- types-cryptography
|
||||
- types-certifi
|
||||
- types-cachetools
|
||||
- certifi
|
||||
- tornado>=6.1
|
||||
- APScheduler==3.6.3
|
||||
|
@ -51,7 +56,7 @@ repos:
|
|||
- cachetools==4.2.2
|
||||
- . # this basically does `pip install -e .`
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.19.1
|
||||
rev: v2.24.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
files: ^(telegram|examples|tests)/.*\.py$
|
||||
|
|
7
docs/source/telegram.ext.dispatcherbuilder.rst
Normal file
7
docs/source/telegram.ext.dispatcherbuilder.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/builders.py
|
||||
|
||||
telegram.ext.DispatcherBuilder
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.ext.DispatcherBuilder
|
||||
:members:
|
|
@ -4,7 +4,9 @@ telegram.ext package
|
|||
.. toctree::
|
||||
|
||||
telegram.ext.extbot
|
||||
telegram.ext.updaterbuilder
|
||||
telegram.ext.updater
|
||||
telegram.ext.dispatcherbuilder
|
||||
telegram.ext.dispatcher
|
||||
telegram.ext.dispatcherhandlerstop
|
||||
telegram.ext.callbackcontext
|
||||
|
@ -61,4 +63,5 @@ utils
|
|||
.. toctree::
|
||||
|
||||
telegram.ext.utils.promise
|
||||
telegram.ext.utils.stack
|
||||
telegram.ext.utils.types
|
7
docs/source/telegram.ext.updaterbuilder.rst
Normal file
7
docs/source/telegram.ext.updaterbuilder.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/builders.py
|
||||
|
||||
telegram.ext.UpdaterBuilder
|
||||
===========================
|
||||
|
||||
.. autoclass:: telegram.ext.UpdaterBuilder
|
||||
:members:
|
8
docs/source/telegram.ext.utils.stack.rst
Normal file
8
docs/source/telegram.ext.utils.stack.rst
Normal file
|
@ -0,0 +1,8 @@
|
|||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/utils/stack.py
|
||||
|
||||
telegram.ext.utils.stack Module
|
||||
================================
|
||||
|
||||
.. automodule:: telegram.ext.utils.stack
|
||||
:members:
|
||||
:show-inheritance:
|
|
@ -11,27 +11,29 @@ from typing import List, Tuple, cast
|
|||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
CallbackContext,
|
||||
InvalidCallbackData,
|
||||
PicklePersistence,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Sends a message with 5 inline buttons attached."""
|
||||
number_list: List[int] = []
|
||||
update.message.reply_text('Please choose:', reply_markup=build_keyboard(number_list))
|
||||
|
||||
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Displays info on how to use the bot."""
|
||||
update.message.reply_text(
|
||||
"Use /start to test this bot. Use /clear to clear the stored data so that you can see "
|
||||
|
@ -39,10 +41,10 @@ def help_command(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def clear(update: Update, context: CallbackContext) -> None:
|
||||
def clear(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Clears the callback data cache"""
|
||||
context.bot.callback_data_cache.clear_callback_data() # type: ignore[attr-defined]
|
||||
context.bot.callback_data_cache.clear_callback_queries() # type: ignore[attr-defined]
|
||||
context.bot.callback_data_cache.clear_callback_data()
|
||||
context.bot.callback_data_cache.clear_callback_queries()
|
||||
update.effective_message.reply_text('All clear!')
|
||||
|
||||
|
||||
|
@ -53,7 +55,7 @@ def build_keyboard(current_list: List[int]) -> InlineKeyboardMarkup:
|
|||
)
|
||||
|
||||
|
||||
def list_button(update: Update, context: CallbackContext) -> None:
|
||||
def list_button(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Parses the CallbackQuery and updates the message text."""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
|
@ -73,7 +75,7 @@ def list_button(update: Update, context: CallbackContext) -> None:
|
|||
context.drop_callback_data(query)
|
||||
|
||||
|
||||
def handle_invalid_button(update: Update, context: CallbackContext) -> None:
|
||||
def handle_invalid_button(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Informs the user that the button is no longer available."""
|
||||
update.callback_query.answer()
|
||||
update.effective_message.edit_text(
|
||||
|
@ -86,7 +88,13 @@ def main() -> None:
|
|||
# We use persistence to demonstrate how buttons can still work after the bot was restarted
|
||||
persistence = PicklePersistence(filepath='arbitrarycallbackdatabot')
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN", persistence=persistence, arbitrary_callback_data=True)
|
||||
updater = (
|
||||
Updater.builder()
|
||||
.token("TOKEN")
|
||||
.persistence(persistence)
|
||||
.arbitrary_callback_data(True)
|
||||
.build()
|
||||
)
|
||||
|
||||
updater.dispatcher.add_handler(CommandHandler('start', start))
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help_command))
|
||||
|
|
|
@ -16,13 +16,14 @@ from typing import Tuple, Optional
|
|||
|
||||
from telegram import Update, Chat, ChatMember, ParseMode, ChatMemberUpdated
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackContext,
|
||||
ChatMemberHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||
)
|
||||
|
@ -66,7 +67,7 @@ def extract_status_change(
|
|||
return was_member, is_member
|
||||
|
||||
|
||||
def track_chats(update: Update, context: CallbackContext) -> None:
|
||||
def track_chats(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Tracks the chats the bot is in."""
|
||||
result = extract_status_change(update.my_chat_member)
|
||||
if result is None:
|
||||
|
@ -101,7 +102,7 @@ def track_chats(update: Update, context: CallbackContext) -> None:
|
|||
context.bot_data.setdefault("channel_ids", set()).discard(chat.id)
|
||||
|
||||
|
||||
def show_chats(update: Update, context: CallbackContext) -> None:
|
||||
def show_chats(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Shows which chats the bot is in"""
|
||||
user_ids = ", ".join(str(uid) for uid in context.bot_data.setdefault("user_ids", set()))
|
||||
group_ids = ", ".join(str(gid) for gid in context.bot_data.setdefault("group_ids", set()))
|
||||
|
@ -114,7 +115,7 @@ def show_chats(update: Update, context: CallbackContext) -> None:
|
|||
update.effective_message.reply_text(text)
|
||||
|
||||
|
||||
def greet_chat_members(update: Update, context: CallbackContext) -> None:
|
||||
def greet_chat_members(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Greets new users in chats and announces when someone leaves"""
|
||||
result = extract_status_change(update.chat_member)
|
||||
if result is None:
|
||||
|
@ -139,7 +140,7 @@ def greet_chat_members(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -15,13 +15,14 @@ from typing import DefaultDict, Optional, Set
|
|||
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackContext,
|
||||
ContextTypes,
|
||||
CallbackQueryHandler,
|
||||
TypeHandler,
|
||||
Dispatcher,
|
||||
ExtBot,
|
||||
Updater,
|
||||
)
|
||||
|
||||
|
||||
|
@ -32,8 +33,8 @@ class ChatData:
|
|||
self.clicks_per_message: DefaultDict[int, int] = defaultdict(int)
|
||||
|
||||
|
||||
# The [dict, ChatData, dict] is for type checkers like mypy
|
||||
class CustomContext(CallbackContext[dict, ChatData, dict]):
|
||||
# The [ExtBot, dict, ChatData, dict] is for type checkers like mypy
|
||||
class CustomContext(CallbackContext[ExtBot, dict, ChatData, dict]):
|
||||
"""Custom class for context."""
|
||||
|
||||
def __init__(self, dispatcher: Dispatcher):
|
||||
|
@ -113,7 +114,7 @@ def track_users(update: Update, context: CustomContext) -> None:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
context_types = ContextTypes(context=CustomContext, chat_data=ChatData)
|
||||
updater = Updater("TOKEN", context_types=context_types)
|
||||
updater = Updater.builder().token("TOKEN").context_types(context_types).build()
|
||||
|
||||
dispatcher = updater.dispatcher
|
||||
# run track_users in its own group to not interfere with the user handlers
|
||||
|
|
|
@ -18,25 +18,25 @@ import logging
|
|||
|
||||
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GENDER, PHOTO, LOCATION, BIO = range(4)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Starts the conversation and asks the user about their gender."""
|
||||
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
||||
|
||||
|
@ -52,7 +52,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
|||
return GENDER
|
||||
|
||||
|
||||
def gender(update: Update, context: CallbackContext) -> int:
|
||||
def gender(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Stores the selected gender and asks for a photo."""
|
||||
user = update.message.from_user
|
||||
logger.info("Gender of %s: %s", user.first_name, update.message.text)
|
||||
|
@ -65,7 +65,7 @@ def gender(update: Update, context: CallbackContext) -> int:
|
|||
return PHOTO
|
||||
|
||||
|
||||
def photo(update: Update, context: CallbackContext) -> int:
|
||||
def photo(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Stores the photo and asks for a location."""
|
||||
user = update.message.from_user
|
||||
photo_file = update.message.photo[-1].get_file()
|
||||
|
@ -78,7 +78,7 @@ def photo(update: Update, context: CallbackContext) -> int:
|
|||
return LOCATION
|
||||
|
||||
|
||||
def skip_photo(update: Update, context: CallbackContext) -> int:
|
||||
def skip_photo(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Skips the photo and asks for a location."""
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a photo.", user.first_name)
|
||||
|
@ -89,7 +89,7 @@ def skip_photo(update: Update, context: CallbackContext) -> int:
|
|||
return LOCATION
|
||||
|
||||
|
||||
def location(update: Update, context: CallbackContext) -> int:
|
||||
def location(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Stores the location and asks for some info about the user."""
|
||||
user = update.message.from_user
|
||||
user_location = update.message.location
|
||||
|
@ -103,7 +103,7 @@ def location(update: Update, context: CallbackContext) -> int:
|
|||
return BIO
|
||||
|
||||
|
||||
def skip_location(update: Update, context: CallbackContext) -> int:
|
||||
def skip_location(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Skips the location and asks for info about the user."""
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a location.", user.first_name)
|
||||
|
@ -114,7 +114,7 @@ def skip_location(update: Update, context: CallbackContext) -> int:
|
|||
return BIO
|
||||
|
||||
|
||||
def bio(update: Update, context: CallbackContext) -> int:
|
||||
def bio(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Stores the info about the user and ends the conversation."""
|
||||
user = update.message.from_user
|
||||
logger.info("Bio of %s: %s", user.first_name, update.message.text)
|
||||
|
@ -123,7 +123,7 @@ def bio(update: Update, context: CallbackContext) -> int:
|
|||
return ConversationHandler.END
|
||||
|
||||
|
||||
def cancel(update: Update, context: CallbackContext) -> int:
|
||||
def cancel(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Cancels and ends the conversation."""
|
||||
user = update.message.from_user
|
||||
logger.info("User %s canceled the conversation.", user.first_name)
|
||||
|
@ -137,7 +137,7 @@ def cancel(update: Update, context: CallbackContext) -> int:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -19,19 +19,19 @@ from typing import Dict
|
|||
|
||||
from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
||||
|
@ -50,7 +50,7 @@ def facts_to_str(user_data: Dict[str, str]) -> str:
|
|||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Start the conversation and ask user for input."""
|
||||
update.message.reply_text(
|
||||
"Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
|
||||
|
@ -61,7 +61,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
|||
return CHOOSING
|
||||
|
||||
|
||||
def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
def regular_choice(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Ask the user for info about the selected predefined choice."""
|
||||
text = update.message.text
|
||||
context.user_data['choice'] = text
|
||||
|
@ -70,7 +70,7 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
|
|||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update: Update, context: CallbackContext) -> int:
|
||||
def custom_choice(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Ask the user for a description of a custom category."""
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, for example "Most impressive skill"'
|
||||
|
@ -79,7 +79,7 @@ def custom_choice(update: Update, context: CallbackContext) -> int:
|
|||
return TYPING_CHOICE
|
||||
|
||||
|
||||
def received_information(update: Update, context: CallbackContext) -> int:
|
||||
def received_information(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Store info provided by user and ask for the next category."""
|
||||
user_data = context.user_data
|
||||
text = update.message.text
|
||||
|
@ -97,7 +97,7 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
|||
return CHOOSING
|
||||
|
||||
|
||||
def done(update: Update, context: CallbackContext) -> int:
|
||||
def done(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Display the gathered info and end the conversation."""
|
||||
user_data = context.user_data
|
||||
if 'choice' in user_data:
|
||||
|
@ -115,7 +115,7 @@ def done(update: Update, context: CallbackContext) -> int:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -22,10 +22,10 @@ import logging
|
|||
|
||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton, Update, helpers
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
Filters,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
@ -46,7 +46,7 @@ SO_COOL = "so-cool"
|
|||
KEYBOARD_CALLBACKDATA = "keyboard-callback-data"
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send a deep-linked URL when the command /start is issued."""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.username, CHECK_THIS_OUT, group=True)
|
||||
|
@ -54,7 +54,7 @@ def start(update: Update, context: CallbackContext) -> None:
|
|||
update.message.reply_text(text)
|
||||
|
||||
|
||||
def deep_linked_level_1(update: Update, context: CallbackContext) -> None:
|
||||
def deep_linked_level_1(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Reached through the CHECK_THIS_OUT payload"""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.username, SO_COOL)
|
||||
|
@ -68,7 +68,7 @@ def deep_linked_level_1(update: Update, context: CallbackContext) -> None:
|
|||
update.message.reply_text(text, reply_markup=keyboard)
|
||||
|
||||
|
||||
def deep_linked_level_2(update: Update, context: CallbackContext) -> None:
|
||||
def deep_linked_level_2(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Reached through the SO_COOL payload"""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.username, USING_ENTITIES)
|
||||
|
@ -76,7 +76,7 @@ def deep_linked_level_2(update: Update, context: CallbackContext) -> None:
|
|||
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
|
||||
|
||||
|
||||
def deep_linked_level_3(update: Update, context: CallbackContext) -> None:
|
||||
def deep_linked_level_3(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Reached through the USING_ENTITIES payload"""
|
||||
update.message.reply_text(
|
||||
"It is also possible to make deep-linking using InlineKeyboardButtons.",
|
||||
|
@ -86,14 +86,14 @@ def deep_linked_level_3(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def deep_link_level_3_callback(update: Update, context: CallbackContext) -> None:
|
||||
def deep_link_level_3_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Answers CallbackQuery with deeplinking url."""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.username, USING_KEYBOARD)
|
||||
update.callback_query.answer(url=url)
|
||||
|
||||
|
||||
def deep_linked_level_4(update: Update, context: CallbackContext) -> None:
|
||||
def deep_linked_level_4(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Reached through the USING_KEYBOARD payload"""
|
||||
payload = context.args
|
||||
update.message.reply_text(
|
||||
|
@ -104,7 +104,7 @@ def deep_linked_level_4(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -18,19 +18,25 @@ bot.
|
|||
import logging
|
||||
|
||||
from telegram import Update, ForceReply
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext
|
||||
from telegram.ext import (
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context.
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
user = update.effective_user
|
||||
update.message.reply_markdown_v2(
|
||||
|
@ -39,12 +45,12 @@ def start(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def echo(update: Update, context: CallbackContext) -> None:
|
||||
def echo(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Echo the user message."""
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
@ -52,7 +58,7 @@ def echo(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -9,12 +9,12 @@ import logging
|
|||
import traceback
|
||||
|
||||
from telegram import Update, ParseMode
|
||||
from telegram.ext import Updater, CallbackContext, CommandHandler
|
||||
from telegram.ext import CommandHandler, Updater, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# The token you got from @botfather when you created the bot
|
||||
|
@ -25,7 +25,7 @@ BOT_TOKEN = "TOKEN"
|
|||
DEVELOPER_CHAT_ID = 123456789
|
||||
|
||||
|
||||
def error_handler(update: object, context: CallbackContext) -> None:
|
||||
def error_handler(update: object, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Log the error and send a telegram message to notify the developer."""
|
||||
# Log the error before we do anything else, so we can see it even if something breaks.
|
||||
logger.error(msg="Exception while handling an update:", exc_info=context.error)
|
||||
|
@ -51,12 +51,12 @@ def error_handler(update: object, context: CallbackContext) -> None:
|
|||
context.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
def bad_command(update: Update, context: CallbackContext) -> None:
|
||||
def bad_command(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Raise an error to trigger the error handler."""
|
||||
context.bot.wrong_method_name() # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Displays info on how to trigger an error."""
|
||||
update.effective_message.reply_html(
|
||||
'Use /bad_command to cause an error.\n'
|
||||
|
@ -67,7 +67,7 @@ def start(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater(BOT_TOKEN)
|
||||
updater = Updater.builder().token(BOT_TOKEN).build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -23,23 +23,22 @@ from telegram.ext import Updater, InlineQueryHandler, CommandHandler, CallbackCo
|
|||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def inlinequery(update: Update, context: CallbackContext) -> None:
|
||||
def inlinequery(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Handle the inline query."""
|
||||
query = update.inline_query.query
|
||||
|
||||
|
@ -74,7 +73,7 @@ def inlinequery(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -9,15 +9,22 @@ Basic example for a bot that uses inline keyboards. For an in-depth explanation,
|
|||
import logging
|
||||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
|
||||
from telegram.ext import (
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Sends a message with three inline buttons attached."""
|
||||
keyboard = [
|
||||
[
|
||||
|
@ -32,7 +39,7 @@ def start(update: Update, context: CallbackContext) -> None:
|
|||
update.message.reply_text('Please choose:', reply_markup=reply_markup)
|
||||
|
||||
|
||||
def button(update: Update, context: CallbackContext) -> None:
|
||||
def button(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Parses the CallbackQuery and updates the message text."""
|
||||
query = update.callback_query
|
||||
|
||||
|
@ -43,7 +50,7 @@ def button(update: Update, context: CallbackContext) -> None:
|
|||
query.edit_message_text(text=f"Selected option: {query.data}")
|
||||
|
||||
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Displays info on how to use the bot."""
|
||||
update.message.reply_text("Use /start to test this bot.")
|
||||
|
||||
|
@ -51,7 +58,7 @@ def help_command(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
updater.dispatcher.add_handler(CommandHandler('start', start))
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(button))
|
||||
|
|
|
@ -17,18 +17,18 @@ Press Ctrl-C on the command line to stop the bot.
|
|||
import logging
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
ConversationHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Stages
|
||||
|
@ -37,7 +37,7 @@ FIRST, SECOND = range(2)
|
|||
ONE, TWO, THREE, FOUR = range(4)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Send message on `/start`."""
|
||||
# Get user that sent /start and log his name
|
||||
user = update.message.from_user
|
||||
|
@ -59,7 +59,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
|||
return FIRST
|
||||
|
||||
|
||||
def start_over(update: Update, context: CallbackContext) -> int:
|
||||
def start_over(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Prompt same text & keyboard as `start` does but not as new message"""
|
||||
# Get CallbackQuery from Update
|
||||
query = update.callback_query
|
||||
|
@ -80,7 +80,7 @@ def start_over(update: Update, context: CallbackContext) -> int:
|
|||
return FIRST
|
||||
|
||||
|
||||
def one(update: Update, context: CallbackContext) -> int:
|
||||
def one(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
|
@ -97,7 +97,7 @@ def one(update: Update, context: CallbackContext) -> int:
|
|||
return FIRST
|
||||
|
||||
|
||||
def two(update: Update, context: CallbackContext) -> int:
|
||||
def two(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
|
@ -114,7 +114,7 @@ def two(update: Update, context: CallbackContext) -> int:
|
|||
return FIRST
|
||||
|
||||
|
||||
def three(update: Update, context: CallbackContext) -> int:
|
||||
def three(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
|
@ -132,7 +132,7 @@ def three(update: Update, context: CallbackContext) -> int:
|
|||
return SECOND
|
||||
|
||||
|
||||
def four(update: Update, context: CallbackContext) -> int:
|
||||
def four(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
|
@ -149,7 +149,7 @@ def four(update: Update, context: CallbackContext) -> int:
|
|||
return FIRST
|
||||
|
||||
|
||||
def end(update: Update, context: CallbackContext) -> int:
|
||||
def end(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Returns `ConversationHandler.END`, which tells the
|
||||
ConversationHandler that the conversation is over.
|
||||
"""
|
||||
|
@ -162,7 +162,7 @@ def end(update: Update, context: CallbackContext) -> int:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -19,20 +19,20 @@ from typing import Tuple, Dict, Any
|
|||
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackQueryHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# State definitions for top level conversation
|
||||
|
@ -71,7 +71,7 @@ def _name_switcher(level: str) -> Tuple[str, str]:
|
|||
|
||||
|
||||
# Top level conversation callbacks
|
||||
def start(update: Update, context: CallbackContext) -> str:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Select an action: Adding parent/child or show data."""
|
||||
text = (
|
||||
"You may choose to add a family member, yourself, show the gathered data, or end the "
|
||||
|
@ -104,7 +104,7 @@ def start(update: Update, context: CallbackContext) -> str:
|
|||
return SELECTING_ACTION
|
||||
|
||||
|
||||
def adding_self(update: Update, context: CallbackContext) -> str:
|
||||
def adding_self(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Add information about yourself."""
|
||||
context.user_data[CURRENT_LEVEL] = SELF
|
||||
text = 'Okay, please tell me about yourself.'
|
||||
|
@ -117,7 +117,7 @@ def adding_self(update: Update, context: CallbackContext) -> str:
|
|||
return DESCRIBING_SELF
|
||||
|
||||
|
||||
def show_data(update: Update, context: CallbackContext) -> str:
|
||||
def show_data(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Pretty print gathered data."""
|
||||
|
||||
def prettyprint(user_data: Dict[str, Any], level: str) -> str:
|
||||
|
@ -152,14 +152,14 @@ def show_data(update: Update, context: CallbackContext) -> str:
|
|||
return SHOWING
|
||||
|
||||
|
||||
def stop(update: Update, context: CallbackContext) -> int:
|
||||
def stop(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""End Conversation by command."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
return END
|
||||
|
||||
|
||||
def end(update: Update, context: CallbackContext) -> int:
|
||||
def end(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""End conversation from InlineKeyboardButton."""
|
||||
update.callback_query.answer()
|
||||
|
||||
|
@ -170,7 +170,7 @@ def end(update: Update, context: CallbackContext) -> int:
|
|||
|
||||
|
||||
# Second level conversation callbacks
|
||||
def select_level(update: Update, context: CallbackContext) -> str:
|
||||
def select_level(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Choose to add a parent or a child."""
|
||||
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
|
||||
buttons = [
|
||||
|
@ -191,7 +191,7 @@ def select_level(update: Update, context: CallbackContext) -> str:
|
|||
return SELECTING_LEVEL
|
||||
|
||||
|
||||
def select_gender(update: Update, context: CallbackContext) -> str:
|
||||
def select_gender(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Choose to add mother or father."""
|
||||
level = update.callback_query.data
|
||||
context.user_data[CURRENT_LEVEL] = level
|
||||
|
@ -218,7 +218,7 @@ def select_gender(update: Update, context: CallbackContext) -> str:
|
|||
return SELECTING_GENDER
|
||||
|
||||
|
||||
def end_second_level(update: Update, context: CallbackContext) -> int:
|
||||
def end_second_level(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Return to top level conversation."""
|
||||
context.user_data[START_OVER] = True
|
||||
start(update, context)
|
||||
|
@ -227,7 +227,7 @@ def end_second_level(update: Update, context: CallbackContext) -> int:
|
|||
|
||||
|
||||
# Third level callbacks
|
||||
def select_feature(update: Update, context: CallbackContext) -> str:
|
||||
def select_feature(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Select a feature to update for the person."""
|
||||
buttons = [
|
||||
[
|
||||
|
@ -254,7 +254,7 @@ def select_feature(update: Update, context: CallbackContext) -> str:
|
|||
return SELECTING_FEATURE
|
||||
|
||||
|
||||
def ask_for_input(update: Update, context: CallbackContext) -> str:
|
||||
def ask_for_input(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Prompt user to input data for selected feature."""
|
||||
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
||||
text = 'Okay, tell me.'
|
||||
|
@ -265,7 +265,7 @@ def ask_for_input(update: Update, context: CallbackContext) -> str:
|
|||
return TYPING
|
||||
|
||||
|
||||
def save_input(update: Update, context: CallbackContext) -> str:
|
||||
def save_input(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Save input for feature and return to feature selection."""
|
||||
user_data = context.user_data
|
||||
user_data[FEATURES][user_data[CURRENT_FEATURE]] = update.message.text
|
||||
|
@ -275,7 +275,7 @@ def save_input(update: Update, context: CallbackContext) -> str:
|
|||
return select_feature(update, context)
|
||||
|
||||
|
||||
def end_describing(update: Update, context: CallbackContext) -> int:
|
||||
def end_describing(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""End gathering of features and return to parent conversation."""
|
||||
user_data = context.user_data
|
||||
level = user_data[CURRENT_LEVEL]
|
||||
|
@ -293,7 +293,7 @@ def end_describing(update: Update, context: CallbackContext) -> int:
|
|||
return END
|
||||
|
||||
|
||||
def stop_nested(update: Update, context: CallbackContext) -> str:
|
||||
def stop_nested(update: Update, context: CallbackContext.DEFAULT_TYPE) -> str:
|
||||
"""Completely end conversation from within nested conversation."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
|
@ -303,7 +303,7 @@ def stop_nested(update: Update, context: CallbackContext) -> str:
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -14,9 +14,10 @@ import logging
|
|||
from pathlib import Path
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, MessageHandler, Filters, CallbackContext
|
||||
from telegram.ext import MessageHandler, Filters, Updater, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG
|
||||
)
|
||||
|
@ -24,7 +25,7 @@ logging.basicConfig(
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def msg(update: Update, context: CallbackContext) -> None:
|
||||
def msg(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Downloads and prints the received passport data."""
|
||||
# Retrieve passport data
|
||||
passport_data = update.message.passport_data
|
||||
|
@ -102,7 +103,8 @@ def msg(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your token and private key
|
||||
updater = Updater("TOKEN", private_key=Path('private.key').read_bytes())
|
||||
private_key = Path('private.key')
|
||||
updater = Updater.builder().token("TOKEN").private_key(private_key.read_bytes()).build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -8,24 +8,24 @@ import logging
|
|||
|
||||
from telegram import LabeledPrice, ShippingOption, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
PreCheckoutQueryHandler,
|
||||
ShippingQueryHandler,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start_callback(update: Update, context: CallbackContext) -> None:
|
||||
def start_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Displays info on how to use the bot."""
|
||||
msg = (
|
||||
"Use /shipping to get an invoice for shipping-payment, or /noshipping for an "
|
||||
|
@ -35,7 +35,7 @@ def start_callback(update: Update, context: CallbackContext) -> None:
|
|||
update.message.reply_text(msg)
|
||||
|
||||
|
||||
def start_with_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
def start_with_shipping_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Sends an invoice with shipping-payment."""
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
|
@ -69,7 +69,7 @@ def start_with_shipping_callback(update: Update, context: CallbackContext) -> No
|
|||
)
|
||||
|
||||
|
||||
def start_without_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
def start_without_shipping_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Sends an invoice without shipping-payment."""
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
|
@ -91,7 +91,7 @@ def start_without_shipping_callback(update: Update, context: CallbackContext) ->
|
|||
)
|
||||
|
||||
|
||||
def shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
def shipping_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Answers the ShippingQuery with ShippingOptions"""
|
||||
query = update.shipping_query
|
||||
# check the payload, is this from your bot?
|
||||
|
@ -109,7 +109,7 @@ def shipping_callback(update: Update, context: CallbackContext) -> None:
|
|||
|
||||
|
||||
# after (optional) shipping, it's the pre-checkout
|
||||
def precheckout_callback(update: Update, context: CallbackContext) -> None:
|
||||
def precheckout_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Answers the PreQecheckoutQuery"""
|
||||
query = update.pre_checkout_query
|
||||
# check the payload, is this from your bot?
|
||||
|
@ -121,7 +121,7 @@ def precheckout_callback(update: Update, context: CallbackContext) -> None:
|
|||
|
||||
|
||||
# finally, after contacting the payment provider...
|
||||
def successful_payment_callback(update: Update, context: CallbackContext) -> None:
|
||||
def successful_payment_callback(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Confirms the successful payment."""
|
||||
# do something after successfully receiving payment?
|
||||
update.message.reply_text("Thank you for your payment!")
|
||||
|
@ -130,7 +130,7 @@ def successful_payment_callback(update: Update, context: CallbackContext) -> Non
|
|||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -19,20 +19,20 @@ from typing import Dict
|
|||
|
||||
from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
PicklePersistence,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
||||
|
@ -51,7 +51,7 @@ def facts_to_str(user_data: Dict[str, str]) -> str:
|
|||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Start the conversation, display any stored data and ask user for input."""
|
||||
reply_text = "Hi! My name is Doctor Botter."
|
||||
if context.user_data:
|
||||
|
@ -69,7 +69,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
|||
return CHOOSING
|
||||
|
||||
|
||||
def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
def regular_choice(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Ask the user for info about the selected predefined choice."""
|
||||
text = update.message.text.lower()
|
||||
context.user_data['choice'] = text
|
||||
|
@ -84,7 +84,7 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
|
|||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update: Update, context: CallbackContext) -> int:
|
||||
def custom_choice(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Ask the user for a description of a custom category."""
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, for example "Most impressive skill"'
|
||||
|
@ -93,7 +93,7 @@ def custom_choice(update: Update, context: CallbackContext) -> int:
|
|||
return TYPING_CHOICE
|
||||
|
||||
|
||||
def received_information(update: Update, context: CallbackContext) -> int:
|
||||
def received_information(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Store info provided by user and ask for the next category."""
|
||||
text = update.message.text
|
||||
category = context.user_data['choice']
|
||||
|
@ -110,14 +110,14 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
|||
return CHOOSING
|
||||
|
||||
|
||||
def show_data(update: Update, context: CallbackContext) -> None:
|
||||
def show_data(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Display the gathered info."""
|
||||
update.message.reply_text(
|
||||
f"This is what you already told me: {facts_to_str(context.user_data)}"
|
||||
)
|
||||
|
||||
|
||||
def done(update: Update, context: CallbackContext) -> int:
|
||||
def done(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||
"""Display the gathered info and end the conversation."""
|
||||
if 'choice' in context.user_data:
|
||||
del context.user_data['choice']
|
||||
|
@ -133,7 +133,7 @@ def main() -> None:
|
|||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
persistence = PicklePersistence(filepath='conversationbot')
|
||||
updater = Updater("TOKEN", persistence=persistence)
|
||||
updater = Updater.builder().token("TOKEN").persistence(persistence).build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -19,22 +19,24 @@ from telegram import (
|
|||
Update,
|
||||
)
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
PollAnswerHandler,
|
||||
PollHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
Updater,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Inform user about what this bot can do"""
|
||||
update.message.reply_text(
|
||||
'Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
|
||||
|
@ -42,7 +44,7 @@ def start(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def poll(update: Update, context: CallbackContext) -> None:
|
||||
def poll(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Sends a predefined poll"""
|
||||
questions = ["Good", "Really good", "Fantastic", "Great"]
|
||||
message = context.bot.send_poll(
|
||||
|
@ -64,7 +66,7 @@ def poll(update: Update, context: CallbackContext) -> None:
|
|||
context.bot_data.update(payload)
|
||||
|
||||
|
||||
def receive_poll_answer(update: Update, context: CallbackContext) -> None:
|
||||
def receive_poll_answer(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Summarize a users poll vote"""
|
||||
answer = update.poll_answer
|
||||
poll_id = answer.poll_id
|
||||
|
@ -93,7 +95,7 @@ def receive_poll_answer(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def quiz(update: Update, context: CallbackContext) -> None:
|
||||
def quiz(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send a predefined poll"""
|
||||
questions = ["1", "2", "4", "20"]
|
||||
message = update.effective_message.reply_poll(
|
||||
|
@ -106,7 +108,7 @@ def quiz(update: Update, context: CallbackContext) -> None:
|
|||
context.bot_data.update(payload)
|
||||
|
||||
|
||||
def receive_quiz_answer(update: Update, context: CallbackContext) -> None:
|
||||
def receive_quiz_answer(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Close quiz after three participants took it"""
|
||||
# the bot can receive closed poll updates we don't care about
|
||||
if update.poll.is_closed:
|
||||
|
@ -120,7 +122,7 @@ def receive_quiz_answer(update: Update, context: CallbackContext) -> None:
|
|||
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
|
||||
|
||||
|
||||
def preview(update: Update, context: CallbackContext) -> None:
|
||||
def preview(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Ask user to create a poll and display a preview of it"""
|
||||
# using this without a type lets the user chooses what he wants (quiz or poll)
|
||||
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
|
||||
|
@ -131,7 +133,7 @@ def preview(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def receive_poll(update: Update, context: CallbackContext) -> None:
|
||||
def receive_poll(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""On receiving polls, reply to it by a closed poll copying the received poll"""
|
||||
actual_poll = update.effective_message.poll
|
||||
# Only need to set the question and options, since all other parameters don't matter for
|
||||
|
@ -145,7 +147,7 @@ def receive_poll(update: Update, context: CallbackContext) -> None:
|
|||
)
|
||||
|
||||
|
||||
def help_handler(update: Update, context: CallbackContext) -> None:
|
||||
def help_handler(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Display a help message"""
|
||||
update.message.reply_text("Use /quiz, /poll or /preview to test this bot.")
|
||||
|
||||
|
@ -153,7 +155,7 @@ def help_handler(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Run bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
dispatcher = updater.dispatcher
|
||||
dispatcher.add_handler(CommandHandler('start', start))
|
||||
dispatcher.add_handler(CommandHandler('poll', poll))
|
||||
|
|
|
@ -21,13 +21,12 @@ bot.
|
|||
import logging
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, CommandHandler, CallbackContext
|
||||
from telegram.ext import CommandHandler, Updater, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -37,18 +36,18 @@ logger = logging.getLogger(__name__)
|
|||
# since context is an unused local variable.
|
||||
# This being an example and not having context present confusing beginners,
|
||||
# we decided to have it present as context.
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Sends explanation on how to use the bot."""
|
||||
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
|
||||
|
||||
|
||||
def alarm(context: CallbackContext) -> None:
|
||||
def alarm(context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Send the alarm message."""
|
||||
job = context.job
|
||||
context.bot.send_message(job.context, text='Beep!')
|
||||
|
||||
|
||||
def remove_job_if_exists(name: str, context: CallbackContext) -> bool:
|
||||
def remove_job_if_exists(name: str, context: CallbackContext.DEFAULT_TYPE) -> bool:
|
||||
"""Remove job with given name. Returns whether job was removed."""
|
||||
current_jobs = context.job_queue.get_jobs_by_name(name)
|
||||
if not current_jobs:
|
||||
|
@ -58,7 +57,7 @@ def remove_job_if_exists(name: str, context: CallbackContext) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def set_timer(update: Update, context: CallbackContext) -> None:
|
||||
def set_timer(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Add a job to the queue."""
|
||||
chat_id = update.message.chat_id
|
||||
try:
|
||||
|
@ -80,7 +79,7 @@ def set_timer(update: Update, context: CallbackContext) -> None:
|
|||
update.message.reply_text('Usage: /set <seconds>')
|
||||
|
||||
|
||||
def unset(update: Update, context: CallbackContext) -> None:
|
||||
def unset(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||
"""Remove the job if the user changed their mind."""
|
||||
chat_id = update.message.chat_id
|
||||
job_removed = remove_job_if_exists(str(chat_id), context)
|
||||
|
@ -91,7 +90,7 @@ def unset(update: Update, context: CallbackContext) -> None:
|
|||
def main() -> None:
|
||||
"""Run bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
updater = Updater.builder().token("TOKEN").build()
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
|
|
@ -5,9 +5,9 @@ pre-commit
|
|||
# Make sure that the versions specified here match the pre-commit settings!
|
||||
black==20.8b1
|
||||
flake8==3.9.2
|
||||
pylint==2.8.3
|
||||
mypy==0.812
|
||||
pyupgrade==2.19.1
|
||||
pylint==2.10.2
|
||||
mypy==0.910
|
||||
pyupgrade==2.24.0
|
||||
|
||||
pytest==6.2.4
|
||||
|
||||
|
|
|
@ -158,22 +158,16 @@ class Bot(TelegramObject):
|
|||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
base_url: str = None,
|
||||
base_file_url: str = None,
|
||||
base_url: str = 'https://api.telegram.org/bot',
|
||||
base_file_url: str = 'https://api.telegram.org/file/bot',
|
||||
request: 'Request' = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
):
|
||||
self.token = self._validate_token(token)
|
||||
|
||||
if base_url is None:
|
||||
base_url = 'https://api.telegram.org/bot'
|
||||
|
||||
if base_file_url is None:
|
||||
base_file_url = 'https://api.telegram.org/file/bot'
|
||||
|
||||
self.base_url = str(base_url) + str(self.token)
|
||||
self.base_file_url = str(base_file_url) + str(self.token)
|
||||
self.base_url = base_url + self.token
|
||||
self.base_file_url = base_file_url + self.token
|
||||
self._bot: Optional[User] = None
|
||||
self._request = request or Request()
|
||||
self.private_key = None
|
||||
|
@ -2796,8 +2790,8 @@ class Bot(TelegramObject):
|
|||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if the edited message is not an inline message
|
||||
, the edited Message is returned, otherwise :obj:`True` is returned.
|
||||
:class:`telegram.Message`: On success, if edited message is not an inline message, the
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
|
|
@ -47,6 +47,7 @@ from .chatmemberhandler import ChatMemberHandler
|
|||
from .chatjoinrequesthandler import ChatJoinRequestHandler
|
||||
from .defaults import Defaults
|
||||
from .callbackdatacache import CallbackDataCache, InvalidCallbackData
|
||||
from .builders import DispatcherBuilder, UpdaterBuilder
|
||||
|
||||
__all__ = (
|
||||
'BaseFilter',
|
||||
|
@ -63,6 +64,7 @@ __all__ = (
|
|||
'Defaults',
|
||||
'DictPersistence',
|
||||
'Dispatcher',
|
||||
'DispatcherBuilder',
|
||||
'DispatcherHandlerStop',
|
||||
'ExtBot',
|
||||
'Filters',
|
||||
|
@ -85,4 +87,5 @@ __all__ = (
|
|||
'TypeHandler',
|
||||
'UpdateFilter',
|
||||
'Updater',
|
||||
'UpdaterBuilder',
|
||||
)
|
||||
|
|
1206
telegram/ext/builders.py
Normal file
1206
telegram/ext/builders.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -34,15 +34,14 @@ from typing import (
|
|||
|
||||
from telegram import Update, CallbackQuery
|
||||
from telegram.ext import ExtBot
|
||||
from telegram.ext.utils.types import UD, CD, BD
|
||||
from telegram.ext.utils.types import UD, CD, BD, BT, JQ, PT # pylint: disable=unused-import
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher, Job, JobQueue
|
||||
from telegram.ext.utils.types import CCT
|
||||
|
||||
|
||||
class CallbackContext(Generic[UD, CD, BD]):
|
||||
class CallbackContext(Generic[BT, UD, CD, BD]):
|
||||
"""
|
||||
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
|
||||
|
@ -94,6 +93,26 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
DEFAULT_TYPE = CallbackContext[ # type: ignore[misc] # noqa: F821
|
||||
ExtBot, Dict, Dict, Dict
|
||||
]
|
||||
else:
|
||||
# Somewhat silly workaround so that accessing the attribute
|
||||
# doesn't only work while type checking
|
||||
DEFAULT_TYPE = 'CallbackContext[ExtBot, Dict, Dict, Dict]' # pylint: disable-all
|
||||
"""Shortcut for the type annotation for the `context` argument that's correct for the
|
||||
default settings, i.e. if :class:`telegram.ext.ContextTypes` is not used.
|
||||
|
||||
Example:
|
||||
.. code:: python
|
||||
|
||||
def callback(update: Update, context: CallbackContext.DEFAULT_TYPE):
|
||||
...
|
||||
|
||||
.. versionadded: 14.0
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_dispatcher',
|
||||
'_chat_id_and_data',
|
||||
|
@ -107,7 +126,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
'__dict__',
|
||||
)
|
||||
|
||||
def __init__(self: 'CCT', dispatcher: 'Dispatcher[CCT, UD, CD, BD]'):
|
||||
def __init__(self: 'CCT', dispatcher: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]'):
|
||||
"""
|
||||
Args:
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`):
|
||||
|
@ -123,7 +142,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
self.async_kwargs: Optional[Dict[str, object]] = None
|
||||
|
||||
@property
|
||||
def dispatcher(self) -> 'Dispatcher[CCT, UD, CD, BD]':
|
||||
def dispatcher(self) -> 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]':
|
||||
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
|
||||
return self._dispatcher
|
||||
|
||||
|
@ -232,7 +251,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
cls: Type['CCT'],
|
||||
update: object,
|
||||
error: Exception,
|
||||
dispatcher: 'Dispatcher[CCT, UD, CD, BD]',
|
||||
dispatcher: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]',
|
||||
async_args: Union[List, Tuple] = None,
|
||||
async_kwargs: Dict[str, object] = None,
|
||||
job: 'Job' = None,
|
||||
|
@ -271,7 +290,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
|
||||
@classmethod
|
||||
def from_update(
|
||||
cls: Type['CCT'], update: object, dispatcher: 'Dispatcher[CCT, UD, CD, BD]'
|
||||
cls: Type['CCT'], update: object, dispatcher: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]'
|
||||
) -> 'CCT':
|
||||
"""
|
||||
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the
|
||||
|
@ -306,7 +325,9 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
return self
|
||||
|
||||
@classmethod
|
||||
def from_job(cls: Type['CCT'], job: 'Job', dispatcher: 'Dispatcher[CCT, UD, CD, BD]') -> 'CCT':
|
||||
def from_job(
|
||||
cls: Type['CCT'], job: 'Job', dispatcher: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]'
|
||||
) -> 'CCT':
|
||||
"""
|
||||
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to a
|
||||
job callback.
|
||||
|
@ -335,7 +356,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
|||
setattr(self, key, value)
|
||||
|
||||
@property
|
||||
def bot(self) -> 'Bot':
|
||||
def bot(self) -> BT:
|
||||
""":class:`telegram.Bot`: The bot associated with this context."""
|
||||
return self._dispatcher.bot
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
from typing import Type, Generic, overload, Dict # pylint: disable=unused-import
|
||||
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.ext.extbot import ExtBot # pylint: disable=unused-import
|
||||
from telegram.ext.utils.types import CCT, UD, CD, BD
|
||||
|
||||
|
||||
|
@ -54,7 +55,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]',
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, Dict, Dict, Dict], Dict, Dict, Dict]',
|
||||
):
|
||||
...
|
||||
|
||||
|
@ -64,19 +65,22 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, Dict, Dict], UD, Dict, Dict]', user_data: Type[UD]
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, UD, Dict, Dict], UD, Dict, Dict]',
|
||||
user_data: Type[UD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, CD, Dict], Dict, CD, Dict]', chat_data: Type[CD]
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, Dict, CD, Dict], Dict, CD, Dict]',
|
||||
chat_data: Type[CD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, Dict, BD], Dict, Dict, BD]', bot_data: Type[BD]
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, Dict, Dict, BD], Dict, Dict, BD]',
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
|
@ -100,7 +104,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, CD, Dict], UD, CD, Dict]',
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, UD, CD, Dict], UD, CD, Dict]',
|
||||
user_data: Type[UD],
|
||||
chat_data: Type[CD],
|
||||
):
|
||||
|
@ -108,7 +112,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, Dict, BD], UD, Dict, BD]',
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, UD, Dict, BD], UD, Dict, BD]',
|
||||
user_data: Type[UD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
|
@ -116,7 +120,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, CD, BD], Dict, CD, BD]',
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, Dict, CD, BD], Dict, CD, BD]',
|
||||
chat_data: Type[CD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
|
@ -151,7 +155,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, CD, BD], UD, CD, BD]',
|
||||
self: 'ContextTypes[CallbackContext[ExtBot, UD, CD, BD], UD, CD, BD]',
|
||||
user_data: Type[UD],
|
||||
chat_data: Type[CD],
|
||||
bot_data: Type[BD],
|
||||
|
|
|
@ -23,7 +23,18 @@ import logging
|
|||
import functools
|
||||
import datetime
|
||||
from threading import Lock
|
||||
from typing import TYPE_CHECKING, Dict, List, NoReturn, Optional, Union, Tuple, cast, ClassVar
|
||||
from typing import ( # pylint: disable=unused-import # for the "Any" import
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
List,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Union,
|
||||
Tuple,
|
||||
cast,
|
||||
ClassVar,
|
||||
Any,
|
||||
)
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import (
|
||||
|
@ -41,7 +52,7 @@ from telegram.ext.utils.types import CCT
|
|||
from telegram.utils.warnings import warn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import Dispatcher, Job
|
||||
from telegram.ext import Dispatcher, Job, JobQueue
|
||||
CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
|
||||
|
||||
|
||||
|
@ -52,7 +63,7 @@ class _ConversationTimeoutContext:
|
|||
self,
|
||||
conversation_key: Tuple[int, ...],
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
dispatcher: 'Dispatcher[Any, CCT, Any, Any, Any, JobQueue, Any]',
|
||||
callback_context: CallbackContext,
|
||||
):
|
||||
self.conversation_key = conversation_key
|
||||
|
@ -489,7 +500,7 @@ class ConversationHandler(Handler[Update, CCT]):
|
|||
def _schedule_job(
|
||||
self,
|
||||
new_state: object,
|
||||
dispatcher: 'Dispatcher',
|
||||
dispatcher: 'Dispatcher[Any, CCT, Any, Any, Any, JobQueue, Any]',
|
||||
update: Update,
|
||||
context: CallbackContext,
|
||||
conversation_key: Tuple[int, ...],
|
||||
|
@ -498,7 +509,7 @@ class ConversationHandler(Handler[Update, CCT]):
|
|||
try:
|
||||
# both job_queue & conversation_timeout are checked before calling _schedule_job
|
||||
j_queue = dispatcher.job_queue
|
||||
self.timeout_jobs[conversation_key] = j_queue.run_once( # type: ignore[union-attr]
|
||||
self.timeout_jobs[conversation_key] = j_queue.run_once(
|
||||
self._trigger_timeout,
|
||||
self.conversation_timeout, # type: ignore[arg-type]
|
||||
context=_ConversationTimeoutContext(
|
||||
|
|
|
@ -17,15 +17,15 @@
|
|||
# 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 Dispatcher class."""
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import weakref
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from queue import Empty, Queue
|
||||
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
|
||||
from time import sleep
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
DefaultDict,
|
||||
Dict,
|
||||
|
@ -35,8 +35,7 @@ from typing import (
|
|||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
overload,
|
||||
cast,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
from uuid import uuid4
|
||||
|
||||
|
@ -48,11 +47,12 @@ from telegram.ext.callbackdatacache import CallbackDataCache
|
|||
from telegram.utils.defaultvalue import DefaultValue, DEFAULT_FALSE
|
||||
from telegram.utils.warnings import warn
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.ext.utils.types import CCT, UD, CD, BD
|
||||
from telegram.ext.utils.types import CCT, UD, CD, BD, BT, JQ, PT
|
||||
from .utils.stack import was_called_by
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import JobQueue, Job, CallbackContext
|
||||
from .jobqueue import Job
|
||||
from .builders import InitDispatcherBuilder
|
||||
|
||||
DEFAULT_GROUP: int = 0
|
||||
|
||||
|
@ -90,24 +90,15 @@ class DispatcherHandlerStop(Exception):
|
|||
self.state = state
|
||||
|
||||
|
||||
class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||
class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
|
||||
"""This class dispatches all kinds of updates to its registered handlers.
|
||||
|
||||
Args:
|
||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
||||
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
|
||||
job_queue (:class:`telegram.ext.JobQueue`, optional): The :class:`telegram.ext.JobQueue`
|
||||
instance to pass onto handler callbacks.
|
||||
workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the
|
||||
``@run_async`` decorator and :meth:`run_async`. Defaults to 4.
|
||||
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance
|
||||
of :class:`telegram.ext.ContextTypes` to customize the types used in the
|
||||
``context`` interface. If not passed, the defaults documented in
|
||||
:class:`telegram.ext.ContextTypes` will be used.
|
||||
Note:
|
||||
This class may not be initialized directly. Use :class:`telegram.ext.DispatcherBuilder` or
|
||||
:meth:`builder` (for convenience).
|
||||
|
||||
.. versionadded:: 13.6
|
||||
.. versionchanged:: 14.0
|
||||
Initialization is now done through the :class:`telegram.ext.DispatcherBuilder`.
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
||||
|
@ -121,10 +112,29 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
|
||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
context_types (:class:`telegram.ext.ContextTypes`): Container for the types used
|
||||
in the ``context`` interface.
|
||||
exception_event (:class:`threading.Event`): When this event is set, the dispatcher will
|
||||
stop processing updates. If this dispatcher is used together with an
|
||||
:class:`telegram.ext.Updater`, then this event will be the same object as
|
||||
:attr:`telegram.ext.Updater.exception_event`.
|
||||
handlers (Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]): A dictionary mapping each
|
||||
handler group to the list of handlers registered to that group.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
.. seealso::
|
||||
:meth:`add_handler`
|
||||
groups (List[:obj:`int`]): A list of all handler groups that have handlers registered.
|
||||
|
||||
.. seealso::
|
||||
:meth:`add_handler`
|
||||
error_handlers (Dict[:obj:`callable`, :obj:`bool`]): A dict, where the keys are error
|
||||
handlers and the values indicate whether they are to be run asynchronously via
|
||||
:meth:`run_async`.
|
||||
|
||||
.. seealso::
|
||||
:meth:`add_error_handler`
|
||||
running (:obj:`bool`): Indicates if this dispatcher is running.
|
||||
|
||||
.. seealso::
|
||||
:meth:`start`, :meth:`stop`
|
||||
|
||||
"""
|
||||
|
||||
|
@ -143,7 +153,7 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
'error_handlers',
|
||||
'running',
|
||||
'__stop_event',
|
||||
'__exception_event',
|
||||
'exception_event',
|
||||
'__async_queue',
|
||||
'__async_threads',
|
||||
'bot',
|
||||
|
@ -156,51 +166,37 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
__singleton = None
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Dispatcher[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]',
|
||||
bot: 'Bot',
|
||||
self: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]',
|
||||
*,
|
||||
bot: BT,
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
job_queue: JQ,
|
||||
workers: int,
|
||||
persistence: PT,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD],
|
||||
exception_event: Event,
|
||||
stack_level: int = 4,
|
||||
):
|
||||
...
|
||||
if not was_called_by(
|
||||
inspect.currentframe(), Path(__file__).parent.resolve() / 'builders.py'
|
||||
):
|
||||
warn(
|
||||
'`Dispatcher` instances should be built via the `DispatcherBuilder`.',
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Dispatcher[CCT, UD, CD, BD]',
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
...
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.job_queue = job_queue
|
||||
self.workers = workers
|
||||
self.context_types = cast(ContextTypes[CCT, UD, CD, BD], context_types or ContextTypes())
|
||||
self.context_types = context_types
|
||||
self.exception_event = exception_event
|
||||
|
||||
if self.workers < 1:
|
||||
warn(
|
||||
'Asynchronous callbacks can not be processed without at least one worker thread.',
|
||||
stacklevel=2,
|
||||
stacklevel=stack_level,
|
||||
)
|
||||
|
||||
self.user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data)
|
||||
|
@ -211,8 +207,12 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
if persistence:
|
||||
if not isinstance(persistence, BasePersistence):
|
||||
raise TypeError("persistence must be based on telegram.ext.BasePersistence")
|
||||
|
||||
self.persistence = persistence
|
||||
# This raises an exception if persistence.store_data.callback_data is True
|
||||
# but self.bot is not an instance of ExtBot - so no need to check that later on
|
||||
self.persistence.set_bot(self.bot)
|
||||
|
||||
if self.persistence.store_data.user_data:
|
||||
self.user_data = self.persistence.get_user_data()
|
||||
if not isinstance(self.user_data, defaultdict):
|
||||
|
@ -228,31 +228,26 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
f"bot_data must be of type {self.context_types.bot_data.__name__}"
|
||||
)
|
||||
if self.persistence.store_data.callback_data:
|
||||
self.bot = cast(ExtBot, self.bot)
|
||||
persistent_data = self.persistence.get_callback_data()
|
||||
if persistent_data is not None:
|
||||
if not isinstance(persistent_data, tuple) and len(persistent_data) != 2:
|
||||
raise ValueError('callback_data must be a 2-tuple')
|
||||
self.bot.callback_data_cache = CallbackDataCache(
|
||||
self.bot,
|
||||
self.bot.callback_data_cache.maxsize,
|
||||
# Mypy doesn't know that persistence.set_bot (see above) already checks that
|
||||
# self.bot is an instance of ExtBot if callback_data should be stored ...
|
||||
self.bot.callback_data_cache = CallbackDataCache( # type: ignore[attr-defined]
|
||||
self.bot, # type: ignore[arg-type]
|
||||
self.bot.callback_data_cache.maxsize, # type: ignore[attr-defined]
|
||||
persistent_data=persistent_data,
|
||||
)
|
||||
else:
|
||||
self.persistence = None
|
||||
|
||||
self.handlers: Dict[int, List[Handler]] = {}
|
||||
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
||||
self.groups: List[int] = []
|
||||
"""List[:obj:`int`]: A list with all groups."""
|
||||
self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {}
|
||||
"""Dict[:obj:`callable`, :obj:`bool`]: A dict, where the keys are error handlers and the
|
||||
values indicate whether they are to be run asynchronously."""
|
||||
|
||||
self.running = False
|
||||
""":obj:`bool`: Indicates if this dispatcher is running."""
|
||||
self.__stop_event = Event()
|
||||
self.__exception_event = exception_event or Event()
|
||||
self.__async_queue: Queue = Queue()
|
||||
self.__async_threads: Set[Thread] = set()
|
||||
|
||||
|
@ -265,9 +260,16 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
else:
|
||||
self._set_singleton(None)
|
||||
|
||||
@property
|
||||
def exception_event(self) -> Event: # skipcq: PY-D0003
|
||||
return self.__exception_event
|
||||
@staticmethod
|
||||
def builder() -> 'InitDispatcherBuilder':
|
||||
"""Convenience method. Returns a new :class:`telegram.ext.DispatcherBuilder`.
|
||||
|
||||
.. versionadded:: 14.0
|
||||
"""
|
||||
# Unfortunately this needs to be here due to cyclical imports
|
||||
from telegram.ext import DispatcherBuilder # pylint: disable=import-outside-toplevel
|
||||
|
||||
return DispatcherBuilder()
|
||||
|
||||
def _init_async_threads(self, base_name: str, workers: int) -> None:
|
||||
base_name = f'{base_name}_' if base_name else ''
|
||||
|
@ -368,7 +370,7 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
def start(self, ready: Event = None) -> None:
|
||||
"""Thread target of thread 'dispatcher'.
|
||||
|
||||
Runs in background and processes the update queue.
|
||||
Runs in background and processes the update queue. Also starts :attr:`job_queue`, if set.
|
||||
|
||||
Args:
|
||||
ready (:obj:`threading.Event`, optional): If specified, the event will be set once the
|
||||
|
@ -381,11 +383,13 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
ready.set()
|
||||
return
|
||||
|
||||
if self.__exception_event.is_set():
|
||||
if self.exception_event.is_set():
|
||||
msg = 'reusing dispatcher after exception event is forbidden'
|
||||
self.logger.error(msg)
|
||||
raise TelegramError(msg)
|
||||
|
||||
if self.job_queue:
|
||||
self.job_queue.start()
|
||||
self._init_async_threads(str(uuid4()), self.workers)
|
||||
self.running = True
|
||||
self.logger.debug('Dispatcher started')
|
||||
|
@ -401,7 +405,7 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
if self.__stop_event.is_set():
|
||||
self.logger.debug('orderly stopping')
|
||||
break
|
||||
if self.__exception_event.is_set():
|
||||
if self.exception_event.is_set():
|
||||
self.logger.critical('stopping due to exception in another thread')
|
||||
break
|
||||
continue
|
||||
|
@ -414,7 +418,10 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
self.logger.debug('Dispatcher thread stopped')
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stops the thread."""
|
||||
"""Stops the thread and :attr:`job_queue`, if set.
|
||||
Also calls :meth:`update_persistence` and :meth:`BasePersistence.flush` on
|
||||
:attr:`persistence`, if set.
|
||||
"""
|
||||
if self.running:
|
||||
self.__stop_event.set()
|
||||
while self.running:
|
||||
|
@ -436,6 +443,17 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
self.__async_threads.remove(thr)
|
||||
self.logger.debug('async thread %s/%s has ended', i + 1, total)
|
||||
|
||||
if self.job_queue:
|
||||
self.job_queue.stop()
|
||||
self.logger.debug('JobQueue was shut down.')
|
||||
|
||||
self.update_persistence()
|
||||
if self.persistence:
|
||||
self.persistence.flush()
|
||||
|
||||
# Clear the connection pool
|
||||
self.bot.request.stop()
|
||||
|
||||
@property
|
||||
def has_running_threads(self) -> bool: # skipcq: PY-D0003
|
||||
return self.running or bool(self.__async_threads)
|
||||
|
@ -602,10 +620,11 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
user_ids = []
|
||||
|
||||
if self.persistence.store_data.callback_data:
|
||||
self.bot = cast(ExtBot, self.bot)
|
||||
try:
|
||||
# Mypy doesn't know that persistence.set_bot (see above) already checks that
|
||||
# self.bot is an instance of ExtBot if callback_data should be stored ...
|
||||
self.persistence.update_callback_data(
|
||||
self.bot.callback_data_cache.persistence_data
|
||||
self.bot.callback_data_cache.persistence_data # type: ignore[attr-defined]
|
||||
)
|
||||
except Exception as exc:
|
||||
self.dispatch_error(update, exc)
|
||||
|
@ -641,11 +660,8 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this error handler. Will be
|
||||
called when an error is raised.
|
||||
Callback signature:
|
||||
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
called when an error is raised. Callback signature:
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The error that happened will be present in context.error.
|
||||
run_async (:obj:`bool`, optional): Whether this handlers callback should be run
|
||||
|
|
|
@ -94,8 +94,8 @@ class ExtBot(telegram.bot.Bot):
|
|||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
base_url: str = None,
|
||||
base_file_url: str = None,
|
||||
base_url: str = 'https://api.telegram.org/bot',
|
||||
base_file_url: str = 'https://api.telegram.org/file/bot',
|
||||
request: 'Request' = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
|
|
|
@ -1166,23 +1166,23 @@ officedocument.wordprocessingml.document")``.
|
|||
|
||||
name = 'Filters.status_update'
|
||||
|
||||
def filter(self, message: Update) -> bool:
|
||||
def filter(self, update: Update) -> bool:
|
||||
return bool(
|
||||
self.new_chat_members(message)
|
||||
or self.left_chat_member(message)
|
||||
or self.new_chat_title(message)
|
||||
or self.new_chat_photo(message)
|
||||
or self.delete_chat_photo(message)
|
||||
or self.chat_created(message)
|
||||
or self.message_auto_delete_timer_changed(message)
|
||||
or self.migrate(message)
|
||||
or self.pinned_message(message)
|
||||
or self.connected_website(message)
|
||||
or self.proximity_alert_triggered(message)
|
||||
or self.voice_chat_scheduled(message)
|
||||
or self.voice_chat_started(message)
|
||||
or self.voice_chat_ended(message)
|
||||
or self.voice_chat_participants_invited(message)
|
||||
self.new_chat_members(update)
|
||||
or self.left_chat_member(update)
|
||||
or self.new_chat_title(update)
|
||||
or self.new_chat_photo(update)
|
||||
or self.delete_chat_photo(update)
|
||||
or self.chat_created(update)
|
||||
or self.message_auto_delete_timer_changed(update)
|
||||
or self.migrate(update)
|
||||
or self.pinned_message(update)
|
||||
or self.connected_website(update)
|
||||
or self.proximity_alert_triggered(update)
|
||||
or self.voice_chat_scheduled(update)
|
||||
or self.voice_chat_started(update)
|
||||
or self.voice_chat_ended(update)
|
||||
or self.voice_chat_participants_invited(update)
|
||||
)
|
||||
|
||||
status_update = _StatusUpdate()
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"""This module contains the classes JobQueue and Job."""
|
||||
|
||||
import datetime
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING, Callable, Optional, Tuple, Union, cast, overload
|
||||
|
||||
import pytz
|
||||
|
@ -45,7 +46,7 @@ class JobQueue:
|
|||
__slots__ = ('_dispatcher', 'scheduler')
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
|
||||
self._dispatcher: 'Optional[weakref.ReferenceType[Dispatcher]]' = None
|
||||
self.scheduler = BackgroundScheduler(timezone=pytz.utc)
|
||||
|
||||
def _tz_now(self) -> datetime.datetime:
|
||||
|
@ -93,10 +94,20 @@ class JobQueue:
|
|||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
|
||||
|
||||
"""
|
||||
self._dispatcher = dispatcher
|
||||
self._dispatcher = weakref.ref(dispatcher)
|
||||
if isinstance(dispatcher.bot, ExtBot) and dispatcher.bot.defaults:
|
||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
||||
|
||||
@property
|
||||
def dispatcher(self) -> 'Dispatcher':
|
||||
"""The dispatcher this JobQueue is associated with."""
|
||||
if self._dispatcher is None:
|
||||
raise RuntimeError('No dispatcher was set for this JobQueue.')
|
||||
dispatcher = self._dispatcher()
|
||||
if dispatcher is not None:
|
||||
return dispatcher
|
||||
raise RuntimeError('The dispatcher instance is no longer alive.')
|
||||
|
||||
def run_once(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
|
@ -151,7 +162,7 @@ class JobQueue:
|
|||
name=name,
|
||||
trigger='date',
|
||||
run_date=date_time,
|
||||
args=(self._dispatcher,),
|
||||
args=(self.dispatcher,),
|
||||
timezone=date_time.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
@ -241,7 +252,7 @@ class JobQueue:
|
|||
j = self.scheduler.add_job(
|
||||
job,
|
||||
trigger='interval',
|
||||
args=(self._dispatcher,),
|
||||
args=(self.dispatcher,),
|
||||
start_date=dt_first,
|
||||
end_date=dt_last,
|
||||
seconds=interval,
|
||||
|
@ -297,7 +308,7 @@ class JobQueue:
|
|||
j = self.scheduler.add_job(
|
||||
job,
|
||||
trigger='cron',
|
||||
args=(self._dispatcher,),
|
||||
args=(self.dispatcher,),
|
||||
name=name,
|
||||
day='last' if day == -1 else day,
|
||||
hour=when.hour,
|
||||
|
@ -354,7 +365,7 @@ class JobQueue:
|
|||
j = self.scheduler.add_job(
|
||||
job,
|
||||
name=name,
|
||||
args=(self._dispatcher,),
|
||||
args=(self.dispatcher,),
|
||||
trigger='cron',
|
||||
day_of_week=','.join([str(d) for d in days]),
|
||||
hour=time.hour,
|
||||
|
@ -394,7 +405,7 @@ class JobQueue:
|
|||
name = name or callback.__name__
|
||||
job = Job(callback, context, name)
|
||||
|
||||
j = self.scheduler.add_job(job, args=(self._dispatcher,), name=name, **job_kwargs)
|
||||
j = self.scheduler.add_job(job, args=(self.dispatcher,), name=name, **job_kwargs)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
|
|
@ -17,42 +17,42 @@
|
|||
# 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 class Updater, which tries to make creating Telegram bots intuitive."""
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import ssl
|
||||
import signal
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from threading import Event, Lock, Thread, current_thread
|
||||
from time import sleep
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
no_type_check,
|
||||
Generic,
|
||||
overload,
|
||||
TypeVar,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from telegram import Bot
|
||||
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized, TelegramError
|
||||
from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot
|
||||
from telegram.warnings import PTBDeprecationWarning
|
||||
from telegram.request import Request
|
||||
from telegram.utils.defaultvalue import DEFAULT_FALSE, DefaultValue
|
||||
from telegram.utils.warnings import warn
|
||||
from telegram.ext.utils.types import CCT, UD, CD, BD
|
||||
from telegram.ext import Dispatcher
|
||||
from telegram.ext.utils.webhookhandler import WebhookAppClass, WebhookServer
|
||||
from .utils.stack import was_called_by
|
||||
from .utils.types import BT
|
||||
from ..utils.warnings import warn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import BasePersistence, Defaults, CallbackContext
|
||||
from .builders import InitUpdaterBuilder
|
||||
|
||||
|
||||
class Updater(Generic[CCT, UD, CD, BD]):
|
||||
DT = TypeVar('DT', bound=Union[None, Dispatcher])
|
||||
|
||||
|
||||
class Updater(Generic[BT, DT]):
|
||||
"""
|
||||
This class, which employs the :class:`telegram.ext.Dispatcher`, provides a frontend to
|
||||
:class:`telegram.Bot` to the programmer, so they can focus on coding the bot. Its purpose is to
|
||||
|
@ -64,260 +64,95 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
WebhookHandler classes.
|
||||
|
||||
Note:
|
||||
* You must supply either a :attr:`bot` or a :attr:`token` argument.
|
||||
* If you supply a :attr:`bot`, you will need to pass :attr:`arbitrary_callback_data`,
|
||||
and :attr:`defaults` to the bot instead of the :class:`telegram.ext.Updater`. In this
|
||||
case, you'll have to use the class :class:`telegram.ext.ExtBot`.
|
||||
|
||||
.. versionchanged:: 13.6
|
||||
|
||||
Args:
|
||||
token (:obj:`str`, optional): The bot's token given by the @BotFather.
|
||||
base_url (:obj:`str`, optional): Base_url for the bot.
|
||||
base_file_url (:obj:`str`, optional): Base_file_url for the bot.
|
||||
workers (:obj:`int`, optional): Amount of threads in the thread pool for functions
|
||||
decorated with ``@run_async`` (ignored if `dispatcher` argument is used).
|
||||
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance (ignored if
|
||||
`dispatcher` argument is used). If a pre-initialized bot is used, it is the user's
|
||||
responsibility to create it using a `Request` instance with a large enough connection
|
||||
pool.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`, optional): A pre-initialized dispatcher
|
||||
instance. If a pre-initialized dispatcher is used, it is the user's responsibility to
|
||||
create it with proper arguments.
|
||||
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
|
||||
private_key_password (:obj:`bytes`, optional): Password for above private key.
|
||||
user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
|
||||
arguments. This will be called when a signal is received, defaults are (SIGINT,
|
||||
SIGTERM, SIGABRT) settable with :attr:`idle`.
|
||||
request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a
|
||||
`telegram.request.Request` object (ignored if `bot` or `dispatcher` 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.
|
||||
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
||||
store data that should be persistent over restarts (ignored if `dispatcher` argument is
|
||||
used).
|
||||
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
||||
be used if not set explicitly in the bot methods.
|
||||
arbitrary_callback_data (:obj:`bool` | :obj:`int` | :obj:`None`, optional): Whether to
|
||||
allow arbitrary objects as callback data for :class:`telegram.InlineKeyboardButton`.
|
||||
Pass an integer to specify the maximum number of cached objects. For more details,
|
||||
please see our wiki. Defaults to :obj:`False`.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance
|
||||
of :class:`telegram.ext.ContextTypes` to customize the types used in the
|
||||
``context`` interface. If not passed, the defaults documented in
|
||||
:class:`telegram.ext.ContextTypes` will be used.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Raises:
|
||||
ValueError: If both :attr:`token` and :attr:`bot` are passed or none of them.
|
||||
This class may not be initialized directly. Use :class:`telegram.ext.UpdaterBuilder` or
|
||||
:meth:`builder` (for convenience).
|
||||
|
||||
.. versionchanged:: 14.0
|
||||
* Initialization is now done through the :class:`telegram.ext.UpdaterBuilder`.
|
||||
* Renamed ``user_sig_handler`` to :attr:`user_signal_handler`.
|
||||
* Removed the attributes ``job_queue``, and ``persistence`` - use the corresponding
|
||||
attributes of :attr:`dispatcher` instead.
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.Bot`): The bot used with this Updater.
|
||||
user_sig_handler (:obj:`function`): Optional. Function to be called when a signal is
|
||||
user_signal_handler (:obj:`function`): Optional. Function to be called when a signal is
|
||||
received.
|
||||
|
||||
.. versionchanged:: 14.0
|
||||
Renamed ``user_sig_handler`` to ``user_signal_handler``.
|
||||
update_queue (:obj:`Queue`): Queue for the updates.
|
||||
job_queue (:class:`telegram.ext.JobQueue`): Jobqueue for the updater.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and
|
||||
dispatches them to the handlers.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): Optional. Dispatcher that handles the
|
||||
updates and dispatches them to the handlers.
|
||||
running (:obj:`bool`): Indicates if the updater is running.
|
||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
exception_event (:class:`threading.Event`): When an unhandled exception happens while
|
||||
fetching updates, this event will be set. If :attr:`dispatcher` is not :obj:`None`, it
|
||||
is the same object as :attr:`telegram.ext.Dispatcher.exception_event`.
|
||||
|
||||
.. versionadded:: 14.0
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'persistence',
|
||||
'dispatcher',
|
||||
'user_sig_handler',
|
||||
'user_signal_handler',
|
||||
'bot',
|
||||
'logger',
|
||||
'update_queue',
|
||||
'job_queue',
|
||||
'__exception_event',
|
||||
'exception_event',
|
||||
'last_update_id',
|
||||
'running',
|
||||
'_request',
|
||||
'is_idle',
|
||||
'httpd',
|
||||
'__lock',
|
||||
'__threads',
|
||||
)
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Updater[CallbackContext, dict, dict, dict]',
|
||||
token: str = None,
|
||||
base_url: str = None,
|
||||
workers: int = 4,
|
||||
bot: Bot = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
user_sig_handler: Callable = None,
|
||||
request_kwargs: Dict[str, Any] = None,
|
||||
persistence: 'BasePersistence' = None, # pylint: disable=used-before-assignment
|
||||
defaults: 'Defaults' = None,
|
||||
base_file_url: str = None,
|
||||
arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE,
|
||||
self: 'Updater[BT, DT]',
|
||||
*,
|
||||
user_signal_handler: Callable[[int, object], Any] = None,
|
||||
dispatcher: DT = None,
|
||||
bot: BT = None,
|
||||
update_queue: Queue = None,
|
||||
exception_event: Event = None,
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Updater[CCT, UD, CD, BD]',
|
||||
token: str = None,
|
||||
base_url: str = None,
|
||||
workers: int = 4,
|
||||
bot: Bot = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
user_sig_handler: Callable = None,
|
||||
request_kwargs: Dict[str, Any] = None,
|
||||
persistence: 'BasePersistence' = None,
|
||||
defaults: 'Defaults' = None,
|
||||
base_file_url: str = None,
|
||||
arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Updater[CCT, UD, CD, BD]',
|
||||
user_sig_handler: Callable = None,
|
||||
dispatcher: Dispatcher[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
...
|
||||
|
||||
def __init__( # type: ignore[no-untyped-def,misc]
|
||||
self,
|
||||
token: str = None,
|
||||
base_url: str = None,
|
||||
workers: int = 4,
|
||||
bot: Bot = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
user_sig_handler: Callable = None,
|
||||
request_kwargs: Dict[str, Any] = None,
|
||||
persistence: 'BasePersistence' = None,
|
||||
defaults: 'Defaults' = None,
|
||||
dispatcher=None,
|
||||
base_file_url: str = None,
|
||||
arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
|
||||
if defaults and bot:
|
||||
if not was_called_by(
|
||||
inspect.currentframe(), Path(__file__).parent.resolve() / 'builders.py'
|
||||
):
|
||||
warn(
|
||||
'Passing defaults to an Updater has no effect when a Bot is passed '
|
||||
'as well. Pass them to the Bot instead.',
|
||||
PTBDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if arbitrary_callback_data is not DEFAULT_FALSE and bot:
|
||||
warn(
|
||||
'Passing arbitrary_callback_data to an Updater has no '
|
||||
'effect when a Bot is passed as well. Pass them to the Bot instead.',
|
||||
'`Updater` instances should be built via the `UpdaterBuilder`.',
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if dispatcher is None:
|
||||
if (token is None) and (bot is None):
|
||||
raise ValueError('`token` or `bot` must be passed')
|
||||
if (token is not None) and (bot is not None):
|
||||
raise ValueError('`token` and `bot` are mutually exclusive')
|
||||
if (private_key is not None) and (bot is not None):
|
||||
raise ValueError('`bot` and `private_key` are mutually exclusive')
|
||||
self.user_signal_handler = user_signal_handler
|
||||
self.dispatcher = dispatcher
|
||||
if self.dispatcher:
|
||||
self.bot = self.dispatcher.bot
|
||||
self.update_queue = self.dispatcher.update_queue
|
||||
self.exception_event = self.dispatcher.exception_event
|
||||
else:
|
||||
if bot is not None:
|
||||
raise ValueError('`dispatcher` and `bot` are mutually exclusive')
|
||||
if persistence is not None:
|
||||
raise ValueError('`dispatcher` and `persistence` are mutually exclusive')
|
||||
if context_types is not None:
|
||||
raise ValueError('`dispatcher` and `context_types` are mutually exclusive')
|
||||
if workers is not None:
|
||||
raise ValueError('`dispatcher` and `workers` are mutually exclusive')
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.exception_event = exception_event
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self._request = None
|
||||
|
||||
if dispatcher is None:
|
||||
con_pool_size = workers + 4
|
||||
|
||||
if bot is not None:
|
||||
self.bot = bot
|
||||
if bot.request.con_pool_size < con_pool_size:
|
||||
warn(
|
||||
f'Connection pool of Request object is smaller than optimal value '
|
||||
f'{con_pool_size}',
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
# we need a connection pool the size of:
|
||||
# * for each of the workers
|
||||
# * 1 for Dispatcher
|
||||
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
||||
# * 1 for JobQueue
|
||||
# * 1 for main thread
|
||||
if request_kwargs is None:
|
||||
request_kwargs = {}
|
||||
if 'con_pool_size' not in request_kwargs:
|
||||
request_kwargs['con_pool_size'] = con_pool_size
|
||||
self._request = Request(**request_kwargs)
|
||||
self.bot = ExtBot(
|
||||
token, # type: ignore[arg-type]
|
||||
base_url,
|
||||
base_file_url=base_file_url,
|
||||
request=self._request,
|
||||
private_key=private_key,
|
||||
private_key_password=private_key_password,
|
||||
defaults=defaults,
|
||||
arbitrary_callback_data=(
|
||||
False # type: ignore[arg-type]
|
||||
if arbitrary_callback_data is DEFAULT_FALSE
|
||||
else arbitrary_callback_data
|
||||
),
|
||||
)
|
||||
self.update_queue: Queue = Queue()
|
||||
self.job_queue = JobQueue()
|
||||
self.__exception_event = Event()
|
||||
self.persistence = persistence
|
||||
self.dispatcher = Dispatcher(
|
||||
self.bot,
|
||||
self.update_queue,
|
||||
job_queue=self.job_queue,
|
||||
workers=workers,
|
||||
exception_event=self.__exception_event,
|
||||
persistence=persistence,
|
||||
context_types=context_types,
|
||||
)
|
||||
self.job_queue.set_dispatcher(self.dispatcher)
|
||||
else:
|
||||
con_pool_size = dispatcher.workers + 4
|
||||
|
||||
self.bot = dispatcher.bot
|
||||
if self.bot.request.con_pool_size < con_pool_size:
|
||||
warn(
|
||||
f'Connection pool of Request object is smaller than optimal value '
|
||||
f'{con_pool_size}',
|
||||
stacklevel=2,
|
||||
)
|
||||
self.update_queue = dispatcher.update_queue
|
||||
self.__exception_event = dispatcher.exception_event
|
||||
self.persistence = dispatcher.persistence
|
||||
self.job_queue = dispatcher.job_queue
|
||||
self.dispatcher = dispatcher
|
||||
|
||||
self.user_sig_handler = user_sig_handler
|
||||
self.last_update_id = 0
|
||||
self.running = False
|
||||
self.is_idle = False
|
||||
self.httpd = None
|
||||
self.__lock = Lock()
|
||||
self.__threads: List[Thread] = []
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
@staticmethod
|
||||
def builder() -> 'InitUpdaterBuilder':
|
||||
"""Convenience method. Returns a new :class:`telegram.ext.UpdaterBuilder`.
|
||||
|
||||
.. versionadded:: 14.0
|
||||
"""
|
||||
# Unfortunately this needs to be here due to cyclical imports
|
||||
from telegram.ext import UpdaterBuilder # pylint: disable=import-outside-toplevel
|
||||
|
||||
return UpdaterBuilder()
|
||||
|
||||
def _init_thread(self, target: Callable, name: str, *args: object, **kwargs: object) -> None:
|
||||
thr = Thread(
|
||||
|
@ -335,7 +170,7 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
try:
|
||||
target(*args, **kwargs)
|
||||
except Exception:
|
||||
self.__exception_event.set()
|
||||
self.exception_event.set()
|
||||
self.logger.exception('unhandled exception in %s', thr_name)
|
||||
raise
|
||||
self.logger.debug('%s - ended', thr_name)
|
||||
|
@ -384,10 +219,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
self.running = True
|
||||
|
||||
# Create & start threads
|
||||
self.job_queue.start()
|
||||
dispatcher_ready = Event()
|
||||
polling_ready = Event()
|
||||
self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
|
||||
|
||||
if self.dispatcher:
|
||||
self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
|
||||
self._init_thread(
|
||||
self._start_polling,
|
||||
"updater",
|
||||
|
@ -400,9 +236,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
ready=polling_ready,
|
||||
)
|
||||
|
||||
self.logger.debug('Waiting for Dispatcher and polling to start')
|
||||
dispatcher_ready.wait()
|
||||
self.logger.debug('Waiting for polling to start')
|
||||
polling_ready.wait()
|
||||
if self.dispatcher:
|
||||
self.logger.debug('Waiting for Dispatcher to start')
|
||||
dispatcher_ready.wait()
|
||||
|
||||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
|
@ -478,8 +316,9 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
# Create & start threads
|
||||
webhook_ready = Event()
|
||||
dispatcher_ready = Event()
|
||||
self.job_queue.start()
|
||||
self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready)
|
||||
|
||||
if self.dispatcher:
|
||||
self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready)
|
||||
self._init_thread(
|
||||
self._start_webhook,
|
||||
"updater",
|
||||
|
@ -497,9 +336,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
max_connections=max_connections,
|
||||
)
|
||||
|
||||
self.logger.debug('Waiting for Dispatcher and Webhook to start')
|
||||
self.logger.debug('Waiting for webhook to start')
|
||||
webhook_ready.wait()
|
||||
dispatcher_ready.wait()
|
||||
if self.dispatcher:
|
||||
self.logger.debug('Waiting for Dispatcher to start')
|
||||
dispatcher_ready.wait()
|
||||
|
||||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
|
@ -661,18 +502,26 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
||||
|
||||
# We pass along the cert to the webhook if present.
|
||||
cert_file = open(cert, 'rb') if cert is not None else None
|
||||
self._bootstrap(
|
||||
max_retries=bootstrap_retries,
|
||||
drop_pending_updates=drop_pending_updates,
|
||||
webhook_url=webhook_url,
|
||||
allowed_updates=allowed_updates,
|
||||
cert=cert_file,
|
||||
ip_address=ip_address,
|
||||
max_connections=max_connections,
|
||||
)
|
||||
if cert_file is not None:
|
||||
cert_file.close()
|
||||
if cert is not None:
|
||||
with open(cert, 'rb') as cert_file:
|
||||
self._bootstrap(
|
||||
cert=cert_file,
|
||||
max_retries=bootstrap_retries,
|
||||
drop_pending_updates=drop_pending_updates,
|
||||
webhook_url=webhook_url,
|
||||
allowed_updates=allowed_updates,
|
||||
ip_address=ip_address,
|
||||
max_connections=max_connections,
|
||||
)
|
||||
else:
|
||||
self._bootstrap(
|
||||
max_retries=bootstrap_retries,
|
||||
drop_pending_updates=drop_pending_updates,
|
||||
webhook_url=webhook_url,
|
||||
allowed_updates=allowed_updates,
|
||||
ip_address=ip_address,
|
||||
max_connections=max_connections,
|
||||
)
|
||||
|
||||
self.httpd.serve_forever(ready=ready)
|
||||
|
||||
|
@ -750,10 +599,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
def stop(self) -> None:
|
||||
"""Stops the polling/webhook thread, the dispatcher and the job queue."""
|
||||
self.job_queue.stop()
|
||||
with self.__lock:
|
||||
if self.running or self.dispatcher.has_running_threads:
|
||||
self.logger.debug('Stopping Updater and Dispatcher...')
|
||||
if self.running or (self.dispatcher and self.dispatcher.has_running_threads):
|
||||
self.logger.debug(
|
||||
'Stopping Updater %s...', 'and Dispatcher ' if self.dispatcher else ''
|
||||
)
|
||||
|
||||
self.running = False
|
||||
|
||||
|
@ -761,9 +611,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
self._stop_dispatcher()
|
||||
self._join_threads()
|
||||
|
||||
# Stop the Request instance only if it was created by the Updater
|
||||
if self._request:
|
||||
self._request.stop()
|
||||
# Clear the connection pool only if the bot is managed by the Updater
|
||||
# Otherwise `dispatcher.stop()` already does that
|
||||
if not self.dispatcher:
|
||||
self.bot.request.stop()
|
||||
|
||||
@no_type_check
|
||||
def _stop_httpd(self) -> None:
|
||||
|
@ -778,8 +629,9 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
|
||||
@no_type_check
|
||||
def _stop_dispatcher(self) -> None:
|
||||
self.logger.debug('Requesting Dispatcher to stop...')
|
||||
self.dispatcher.stop()
|
||||
if self.dispatcher:
|
||||
self.logger.debug('Requesting Dispatcher to stop...')
|
||||
self.dispatcher.stop()
|
||||
|
||||
@no_type_check
|
||||
def _join_threads(self) -> None:
|
||||
|
@ -801,13 +653,9 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
|||
# https://bugs.python.org/issue28206
|
||||
signal.Signals(signum), # pylint: disable=no-member
|
||||
)
|
||||
if self.persistence:
|
||||
# Update user_data, chat_data and bot_data before flushing
|
||||
self.dispatcher.update_persistence()
|
||||
self.persistence.flush()
|
||||
self.stop()
|
||||
if self.user_sig_handler:
|
||||
self.user_sig_handler(signum, frame)
|
||||
if self.user_signal_handler:
|
||||
self.user_signal_handler(signum, frame)
|
||||
else:
|
||||
self.logger.warning('Exiting immediately!')
|
||||
# pylint: disable=import-outside-toplevel, protected-access
|
||||
|
|
61
telegram/ext/utils/stack.py
Normal file
61
telegram/ext/utils/stack.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# 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 helper functions related to inspecting the program stack.
|
||||
|
||||
.. versionadded:: 14.0
|
||||
|
||||
Warning:
|
||||
Contents of this module are intended to be used internally by the library and *not* by the
|
||||
user. Changes to this module are not considered breaking changes and may not be documented in
|
||||
the changelog.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from types import FrameType
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def was_called_by(frame: Optional[FrameType], caller: Path) -> bool:
|
||||
"""Checks if the passed frame was called by the specified file.
|
||||
|
||||
Example:
|
||||
.. code:: python
|
||||
|
||||
>>> was_called_by(inspect.currentframe(), Path(__file__))
|
||||
True
|
||||
|
||||
Arguments:
|
||||
frame (:obj:`FrameType`): The frame - usually the return value of
|
||||
``inspect.currentframe()``. If :obj:`None` is passed, the return value will be
|
||||
:obj:`False`.
|
||||
caller (:obj:`pathlib.Path`): File that should be the caller.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: Whether or not the frame was called by the specified file.
|
||||
"""
|
||||
if frame is None:
|
||||
return False
|
||||
|
||||
# https://stackoverflow.com/a/57712700/10606962
|
||||
if Path(frame.f_code.co_filename) == caller:
|
||||
return True
|
||||
while frame.f_back:
|
||||
frame = frame.f_back
|
||||
if Path(frame.f_code.co_filename) == caller:
|
||||
return True
|
||||
return False
|
|
@ -25,10 +25,11 @@ Warning:
|
|||
user. Changes to this module are not considered breaking changes and may not be documented in
|
||||
the changelog.
|
||||
"""
|
||||
from typing import TypeVar, TYPE_CHECKING, Tuple, List, Dict, Any, Optional
|
||||
from typing import TypeVar, TYPE_CHECKING, Tuple, List, Dict, Any, Optional, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext # noqa: F401
|
||||
from telegram.ext import CallbackContext, JobQueue, BasePersistence # noqa: F401
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
ConversationDict = Dict[Tuple[int, ...], Optional[object]]
|
||||
|
@ -50,6 +51,11 @@ CCT = TypeVar('CCT', bound='CallbackContext')
|
|||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
BT = TypeVar('BT', bound='Bot')
|
||||
"""Type of the bot.
|
||||
|
||||
.. versionadded:: 14.0
|
||||
"""
|
||||
UD = TypeVar('UD')
|
||||
"""Type of the user data for a single user.
|
||||
|
||||
|
@ -65,3 +71,11 @@ BD = TypeVar('BD')
|
|||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
JQ = TypeVar('JQ', bound=Union[None, 'JobQueue'])
|
||||
"""Type of the job queue.
|
||||
|
||||
.. versionadded:: 14.0"""
|
||||
PT = TypeVar('PT', bound=Union[None, 'BasePersistence'])
|
||||
"""Type of the persistence.
|
||||
|
||||
.. versionadded:: 14.0"""
|
||||
|
|
|
@ -36,15 +36,15 @@ from typing import Any, Union
|
|||
import certifi
|
||||
|
||||
try:
|
||||
import telegram.vendor.ptb_urllib3.urllib3 as urllib3
|
||||
import telegram.vendor.ptb_urllib3.urllib3.contrib.appengine as appengine
|
||||
from telegram.vendor.ptb_urllib3 import urllib3
|
||||
from telegram.vendor.ptb_urllib3.urllib3.contrib import appengine
|
||||
from telegram.vendor.ptb_urllib3.urllib3.connection import HTTPConnection
|
||||
from telegram.vendor.ptb_urllib3.urllib3.fields import RequestField
|
||||
from telegram.vendor.ptb_urllib3.urllib3.util.timeout import Timeout
|
||||
except ImportError: # pragma: no cover
|
||||
try:
|
||||
import urllib3 # type: ignore[no-redef]
|
||||
import urllib3.contrib.appengine as appengine # type: ignore[no-redef]
|
||||
from urllib3.contrib import appengine # type: ignore[no-redef]
|
||||
from urllib3.connection import HTTPConnection # type: ignore[no-redef]
|
||||
from urllib3.fields import RequestField # type: ignore[no-redef]
|
||||
from urllib3.util.timeout import Timeout # type: ignore[no-redef]
|
||||
|
|
|
@ -53,12 +53,12 @@ from telegram import (
|
|||
)
|
||||
from telegram.ext import (
|
||||
Dispatcher,
|
||||
JobQueue,
|
||||
Updater,
|
||||
MessageFilter,
|
||||
Defaults,
|
||||
UpdateFilter,
|
||||
ExtBot,
|
||||
DispatcherBuilder,
|
||||
UpdaterBuilder,
|
||||
)
|
||||
from telegram.error import BadRequest
|
||||
from telegram.utils.defaultvalue import DefaultValue, DEFAULT_NONE
|
||||
|
@ -173,8 +173,7 @@ 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 = DictDispatcher(bot, Queue(), job_queue=JobQueue(), workers=2)
|
||||
dispatcher.job_queue.set_dispatcher(dispatcher)
|
||||
dispatcher = DispatcherBuilder().bot(bot).workers(2).dispatcher_class(DictDispatcher).build()
|
||||
thr = Thread(target=dispatcher.start)
|
||||
thr.start()
|
||||
sleep(2)
|
||||
|
@ -202,7 +201,7 @@ def dp(_dp):
|
|||
_dp.handlers = {}
|
||||
_dp.groups = []
|
||||
_dp.error_handlers = {}
|
||||
_dp.__exception_event = Event()
|
||||
_dp.exception_event = Event()
|
||||
_dp.__stop_event = Event()
|
||||
_dp.__async_queue = Queue()
|
||||
_dp.__async_threads = set()
|
||||
|
@ -212,7 +211,7 @@ def dp(_dp):
|
|||
|
||||
@pytest.fixture(scope='function')
|
||||
def updater(bot):
|
||||
up = Updater(bot=bot, workers=2)
|
||||
up = UpdaterBuilder().bot(bot).workers(2).build()
|
||||
yield up
|
||||
if up.running:
|
||||
up.stop()
|
||||
|
|
|
@ -184,7 +184,7 @@ class TestBot:
|
|||
|
||||
@flaky(3, 1)
|
||||
def test_invalid_token_server_response(self, monkeypatch):
|
||||
monkeypatch.setattr('telegram.Bot._validate_token', lambda x, y: True)
|
||||
monkeypatch.setattr('telegram.Bot._validate_token', lambda x, y: '')
|
||||
bot = Bot('12')
|
||||
with pytest.raises(InvalidToken):
|
||||
bot.get_me()
|
||||
|
|
252
tests/test_builders.py
Normal file
252
tests/test_builders.py
Normal file
|
@ -0,0 +1,252 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# 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/].
|
||||
|
||||
"""
|
||||
We mainly test on UpdaterBuilder because it has all methods that DispatcherBuilder already has
|
||||
"""
|
||||
from random import randint
|
||||
from threading import Event
|
||||
|
||||
import pytest
|
||||
|
||||
from telegram.request import Request
|
||||
from .conftest import PRIVATE_KEY
|
||||
|
||||
from telegram.ext import (
|
||||
UpdaterBuilder,
|
||||
Defaults,
|
||||
JobQueue,
|
||||
PicklePersistence,
|
||||
ContextTypes,
|
||||
Dispatcher,
|
||||
Updater,
|
||||
)
|
||||
from telegram.ext.builders import _BOT_CHECKS, _DISPATCHER_CHECKS, DispatcherBuilder, _BaseBuilder
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
scope='function',
|
||||
params=[{'class': UpdaterBuilder}, {'class': DispatcherBuilder}],
|
||||
ids=['UpdaterBuilder', 'DispatcherBuilder'],
|
||||
)
|
||||
def builder(request):
|
||||
return request.param['class']()
|
||||
|
||||
|
||||
class TestBuilder:
|
||||
@pytest.mark.parametrize('workers', [randint(1, 100) for _ in range(10)])
|
||||
def test_get_connection_pool_size(self, workers):
|
||||
assert _BaseBuilder._get_connection_pool_size(workers) == workers + 4
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'method, description', _BOT_CHECKS, ids=[entry[0] for entry in _BOT_CHECKS]
|
||||
)
|
||||
def test_mutually_exclusive_for_bot(self, builder, method, description):
|
||||
if getattr(builder, method, None) is None:
|
||||
pytest.skip(f'{builder.__class__} has no method called {method}')
|
||||
|
||||
# First that e.g. `bot` can't be set if `request` was already set
|
||||
getattr(builder, method)(1)
|
||||
with pytest.raises(RuntimeError, match=f'`bot` may only be set, if no {description}'):
|
||||
builder.bot(None)
|
||||
|
||||
# Now test that `request` can't be set if `bot` was already set
|
||||
builder = builder.__class__()
|
||||
builder.bot(None)
|
||||
with pytest.raises(RuntimeError, match=f'`{method}` may only be set, if no bot instance'):
|
||||
getattr(builder, method)(None)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'method, description', _DISPATCHER_CHECKS, ids=[entry[0] for entry in _DISPATCHER_CHECKS]
|
||||
)
|
||||
def test_mutually_exclusive_for_dispatcher(self, builder, method, description):
|
||||
if isinstance(builder, DispatcherBuilder):
|
||||
pytest.skip('This test is only relevant for UpdaterBuilder')
|
||||
|
||||
if getattr(builder, method, None) is None:
|
||||
pytest.skip(f'{builder.__class__} has no method called {method}')
|
||||
|
||||
# First that e.g. `dispatcher` can't be set if `bot` was already set
|
||||
getattr(builder, method)(None)
|
||||
with pytest.raises(
|
||||
RuntimeError, match=f'`dispatcher` may only be set, if no {description}'
|
||||
):
|
||||
builder.dispatcher(None)
|
||||
|
||||
# Now test that `bot` can't be set if `dispatcher` was already set
|
||||
builder = builder.__class__()
|
||||
builder.dispatcher(1)
|
||||
with pytest.raises(
|
||||
RuntimeError, match=f'`{method}` may only be set, if no Dispatcher instance'
|
||||
):
|
||||
getattr(builder, method)(None)
|
||||
|
||||
# Finally test that `bot` *can* be set if `dispatcher` was set to None
|
||||
builder = builder.__class__()
|
||||
builder.dispatcher(None)
|
||||
if method != 'dispatcher_class':
|
||||
getattr(builder, method)(None)
|
||||
else:
|
||||
with pytest.raises(
|
||||
RuntimeError, match=f'`{method}` may only be set, if no Dispatcher instance'
|
||||
):
|
||||
getattr(builder, method)(None)
|
||||
|
||||
def test_mutually_exclusive_for_request(self, builder):
|
||||
builder.request(None)
|
||||
with pytest.raises(
|
||||
RuntimeError, match='`request_kwargs` may only be set, if no Request instance'
|
||||
):
|
||||
builder.request_kwargs(None)
|
||||
|
||||
builder = builder.__class__()
|
||||
builder.request_kwargs(None)
|
||||
with pytest.raises(RuntimeError, match='`request` may only be set, if no request_kwargs'):
|
||||
builder.request(None)
|
||||
|
||||
def test_build_without_token(self, builder):
|
||||
with pytest.raises(RuntimeError, match='No bot token was set.'):
|
||||
builder.build()
|
||||
|
||||
def test_build_custom_bot(self, builder, bot):
|
||||
builder.bot(bot)
|
||||
obj = builder.build()
|
||||
assert obj.bot is bot
|
||||
|
||||
if isinstance(obj, Updater):
|
||||
assert obj.dispatcher.bot is bot
|
||||
assert obj.dispatcher.job_queue.dispatcher is obj.dispatcher
|
||||
assert obj.exception_event is obj.dispatcher.exception_event
|
||||
|
||||
def test_build_custom_dispatcher(self, dp):
|
||||
updater = UpdaterBuilder().dispatcher(dp).build()
|
||||
assert updater.dispatcher is dp
|
||||
assert updater.bot is updater.dispatcher.bot
|
||||
assert updater.exception_event is dp.exception_event
|
||||
|
||||
def test_build_no_dispatcher(self, bot):
|
||||
updater = UpdaterBuilder().dispatcher(None).token(bot.token).build()
|
||||
assert updater.dispatcher is None
|
||||
assert updater.bot.token == bot.token
|
||||
assert updater.bot.request.con_pool_size == 8
|
||||
assert isinstance(updater.exception_event, Event)
|
||||
|
||||
def test_all_bot_args_custom(self, builder, bot):
|
||||
defaults = Defaults()
|
||||
request = Request(8)
|
||||
builder.token(bot.token).base_url('base_url').base_file_url('base_file_url').private_key(
|
||||
PRIVATE_KEY
|
||||
).defaults(defaults).arbitrary_callback_data(42).request(request)
|
||||
built_bot = builder.build().bot
|
||||
|
||||
assert built_bot.token == bot.token
|
||||
assert built_bot.base_url == 'base_url' + bot.token
|
||||
assert built_bot.base_file_url == 'base_file_url' + bot.token
|
||||
assert built_bot.defaults is defaults
|
||||
assert built_bot.request is request
|
||||
assert built_bot.callback_data_cache.maxsize == 42
|
||||
|
||||
builder = builder.__class__()
|
||||
builder.token(bot.token).request_kwargs({'connect_timeout': 42})
|
||||
built_bot = builder.build().bot
|
||||
|
||||
assert built_bot.token == bot.token
|
||||
assert built_bot.request._connect_timeout == 42
|
||||
|
||||
def test_all_dispatcher_args_custom(self, dp):
|
||||
builder = DispatcherBuilder()
|
||||
|
||||
job_queue = JobQueue()
|
||||
persistence = PicklePersistence('filename')
|
||||
context_types = ContextTypes()
|
||||
builder.bot(dp.bot).update_queue(dp.update_queue).exception_event(
|
||||
dp.exception_event
|
||||
).job_queue(job_queue).persistence(persistence).context_types(context_types).workers(3)
|
||||
dispatcher = builder.build()
|
||||
|
||||
assert dispatcher.bot is dp.bot
|
||||
assert dispatcher.update_queue is dp.update_queue
|
||||
assert dispatcher.exception_event is dp.exception_event
|
||||
assert dispatcher.job_queue is job_queue
|
||||
assert dispatcher.job_queue.dispatcher is dispatcher
|
||||
assert dispatcher.persistence is persistence
|
||||
assert dispatcher.context_types is context_types
|
||||
assert dispatcher.workers == 3
|
||||
|
||||
def test_all_updater_args_custom(self, dp):
|
||||
updater = (
|
||||
UpdaterBuilder()
|
||||
.dispatcher(None)
|
||||
.bot(dp.bot)
|
||||
.exception_event(dp.exception_event)
|
||||
.update_queue(dp.update_queue)
|
||||
.user_signal_handler(42)
|
||||
.build()
|
||||
)
|
||||
|
||||
assert updater.dispatcher is None
|
||||
assert updater.bot is dp.bot
|
||||
assert updater.exception_event is dp.exception_event
|
||||
assert updater.update_queue is dp.update_queue
|
||||
assert updater.user_signal_handler == 42
|
||||
|
||||
def test_connection_pool_size_with_workers(self, bot, builder):
|
||||
obj = builder.token(bot.token).workers(42).build()
|
||||
dispatcher = obj if isinstance(obj, Dispatcher) else obj.dispatcher
|
||||
assert dispatcher.workers == 42
|
||||
assert dispatcher.bot.request.con_pool_size == 46
|
||||
|
||||
def test_connection_pool_size_warning(self, bot, builder, recwarn):
|
||||
builder.token(bot.token).workers(42).request_kwargs({'con_pool_size': 1})
|
||||
obj = builder.build()
|
||||
dispatcher = obj if isinstance(obj, Dispatcher) else obj.dispatcher
|
||||
assert dispatcher.workers == 42
|
||||
assert dispatcher.bot.request.con_pool_size == 1
|
||||
|
||||
assert len(recwarn) == 1
|
||||
message = str(recwarn[-1].message)
|
||||
assert 'smaller (1)' in message
|
||||
assert 'recommended value of 46.' in message
|
||||
assert recwarn[-1].filename == __file__, "wrong stacklevel"
|
||||
|
||||
def test_custom_classes(self, bot, builder):
|
||||
class CustomDispatcher(Dispatcher):
|
||||
def __init__(self, arg, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.arg = arg
|
||||
|
||||
class CustomUpdater(Updater):
|
||||
def __init__(self, arg, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.arg = arg
|
||||
|
||||
builder.dispatcher_class(CustomDispatcher, kwargs={'arg': 2}).token(bot.token)
|
||||
if isinstance(builder, UpdaterBuilder):
|
||||
builder.updater_class(CustomUpdater, kwargs={'arg': 1})
|
||||
|
||||
obj = builder.build()
|
||||
|
||||
if isinstance(builder, UpdaterBuilder):
|
||||
assert isinstance(obj, CustomUpdater)
|
||||
assert obj.arg == 1
|
||||
assert isinstance(obj.dispatcher, CustomDispatcher)
|
||||
assert obj.dispatcher.arg == 2
|
||||
else:
|
||||
assert isinstance(obj, CustomDispatcher)
|
||||
assert obj.arg == 2
|
|
@ -33,6 +33,8 @@ from telegram.ext import (
|
|||
JobQueue,
|
||||
BasePersistence,
|
||||
ContextTypes,
|
||||
DispatcherBuilder,
|
||||
UpdaterBuilder,
|
||||
)
|
||||
from telegram.ext import PersistenceInput
|
||||
from telegram.ext.dispatcher import Dispatcher, DispatcherHandlerStop
|
||||
|
@ -98,14 +100,36 @@ class TestDispatcher:
|
|||
self.received = context.error.message
|
||||
|
||||
def test_slot_behaviour(self, bot, mro_slots):
|
||||
dp = Dispatcher(bot=bot, update_queue=None)
|
||||
dp = DispatcherBuilder().bot(bot).build()
|
||||
for at in dp.__slots__:
|
||||
at = f"_Dispatcher{at}" if at.startswith('__') and not at.endswith('__') else at
|
||||
assert getattr(dp, at, 'err') != 'err', f"got extra slot '{at}'"
|
||||
assert len(mro_slots(dp)) == len(set(mro_slots(dp))), "duplicate slot"
|
||||
|
||||
def test_less_than_one_worker_warning(self, dp, recwarn):
|
||||
Dispatcher(dp.bot, dp.update_queue, job_queue=dp.job_queue, workers=0)
|
||||
def test_manual_init_warning(self, recwarn):
|
||||
Dispatcher(
|
||||
bot=None,
|
||||
update_queue=None,
|
||||
workers=7,
|
||||
exception_event=None,
|
||||
job_queue=None,
|
||||
persistence=None,
|
||||
context_types=ContextTypes(),
|
||||
)
|
||||
assert len(recwarn) == 1
|
||||
assert (
|
||||
str(recwarn[-1].message)
|
||||
== '`Dispatcher` instances should be built via the `DispatcherBuilder`.'
|
||||
)
|
||||
assert recwarn[0].filename == __file__, "stacklevel is incorrect!"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'builder',
|
||||
(DispatcherBuilder(), UpdaterBuilder()),
|
||||
ids=('DispatcherBuilder', 'UpdaterBuilder'),
|
||||
)
|
||||
def test_less_than_one_worker_warning(self, dp, recwarn, builder):
|
||||
builder.bot(dp.bot).workers(0).build()
|
||||
assert len(recwarn) == 1
|
||||
assert (
|
||||
str(recwarn[0].message)
|
||||
|
@ -113,6 +137,18 @@ class TestDispatcher:
|
|||
)
|
||||
assert recwarn[0].filename == __file__, "stacklevel is incorrect!"
|
||||
|
||||
def test_builder(self, dp):
|
||||
builder_1 = dp.builder()
|
||||
builder_2 = dp.builder()
|
||||
assert isinstance(builder_1, DispatcherBuilder)
|
||||
assert isinstance(builder_2, DispatcherBuilder)
|
||||
assert builder_1 is not builder_2
|
||||
|
||||
# Make sure that setting a token doesn't raise an exception
|
||||
# i.e. check that the builders are "empty"/new
|
||||
builder_1.token(dp.bot.token)
|
||||
builder_2.token(dp.bot.token)
|
||||
|
||||
def test_one_context_per_update(self, dp):
|
||||
def one(update, context):
|
||||
if update.message.text == 'test':
|
||||
|
@ -163,7 +199,7 @@ class TestDispatcher:
|
|||
with pytest.raises(
|
||||
TypeError, match='persistence must be based on telegram.ext.BasePersistence'
|
||||
):
|
||||
Dispatcher(bot, None, persistence=my_per())
|
||||
DispatcherBuilder().bot(bot).persistence(my_per()).build()
|
||||
|
||||
def test_error_handler_that_raises_errors(self, dp):
|
||||
"""
|
||||
|
@ -580,7 +616,7 @@ class TestDispatcher:
|
|||
),
|
||||
)
|
||||
my_persistence = OwnPersistence()
|
||||
dp = Dispatcher(bot, None, persistence=my_persistence)
|
||||
dp = DispatcherBuilder().bot(bot).persistence(my_persistence).build()
|
||||
dp.add_handler(CommandHandler('start', start1))
|
||||
dp.add_error_handler(error)
|
||||
dp.process_update(update)
|
||||
|
@ -885,7 +921,7 @@ class TestDispatcher:
|
|||
bot_data=complex,
|
||||
)
|
||||
|
||||
dispatcher = Dispatcher(bot, Queue(), context_types=cc)
|
||||
dispatcher = DispatcherBuilder().bot(bot).context_types(cc).build()
|
||||
|
||||
assert isinstance(dispatcher.user_data[1], int)
|
||||
assert isinstance(dispatcher.chat_data[1], float)
|
||||
|
@ -900,12 +936,15 @@ class TestDispatcher:
|
|||
type(context.bot_data),
|
||||
)
|
||||
|
||||
dispatcher = Dispatcher(
|
||||
bot,
|
||||
Queue(),
|
||||
context_types=ContextTypes(
|
||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||
),
|
||||
dispatcher = (
|
||||
DispatcherBuilder()
|
||||
.bot(bot)
|
||||
.context_types(
|
||||
ContextTypes(
|
||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
dispatcher.add_error_handler(error_handler)
|
||||
dispatcher.add_handler(MessageHandler(Filters.all, self.callback_raise_error))
|
||||
|
@ -923,12 +962,15 @@ class TestDispatcher:
|
|||
type(context.bot_data),
|
||||
)
|
||||
|
||||
dispatcher = Dispatcher(
|
||||
bot,
|
||||
Queue(),
|
||||
context_types=ContextTypes(
|
||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||
),
|
||||
dispatcher = (
|
||||
DispatcherBuilder()
|
||||
.bot(bot)
|
||||
.context_types(
|
||||
ContextTypes(
|
||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
dispatcher.add_handler(MessageHandler(Filters.all, callback))
|
||||
|
||||
|
|
|
@ -29,7 +29,13 @@ import pytest
|
|||
import pytz
|
||||
from apscheduler.schedulers import SchedulerNotRunningError
|
||||
from flaky import flaky
|
||||
from telegram.ext import JobQueue, Updater, Job, CallbackContext, Dispatcher, ContextTypes
|
||||
from telegram.ext import (
|
||||
JobQueue,
|
||||
Job,
|
||||
CallbackContext,
|
||||
ContextTypes,
|
||||
DispatcherBuilder,
|
||||
)
|
||||
|
||||
|
||||
class CustomContext(CallbackContext):
|
||||
|
@ -55,11 +61,6 @@ class TestJobQueue:
|
|||
job_time = 0
|
||||
received_error = None
|
||||
|
||||
def test_slot_behaviour(self, job_queue, mro_slots, _dp):
|
||||
for attr in job_queue.__slots__:
|
||||
assert getattr(job_queue, attr, 'err') != 'err', f"got extra slot '{attr}'"
|
||||
assert len(mro_slots(job_queue)) == len(set(mro_slots(job_queue))), "duplicate slot"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset(self):
|
||||
self.result = 0
|
||||
|
@ -100,6 +101,22 @@ class TestJobQueue:
|
|||
def error_handler_raise_error(self, *args):
|
||||
raise Exception('Failing bigly')
|
||||
|
||||
def test_slot_behaviour(self, job_queue, mro_slots, _dp):
|
||||
for attr in job_queue.__slots__:
|
||||
assert getattr(job_queue, attr, 'err') != 'err', f"got extra slot '{attr}'"
|
||||
assert len(mro_slots(job_queue)) == len(set(mro_slots(job_queue))), "duplicate slot"
|
||||
|
||||
def test_dispatcher_weakref(self, bot):
|
||||
jq = JobQueue()
|
||||
dispatcher = DispatcherBuilder().bot(bot).job_queue(None).build()
|
||||
with pytest.raises(RuntimeError, match='No dispatcher was set'):
|
||||
jq.dispatcher
|
||||
jq.set_dispatcher(dispatcher)
|
||||
assert jq.dispatcher is dispatcher
|
||||
del dispatcher
|
||||
with pytest.raises(RuntimeError, match='no longer alive'):
|
||||
jq.dispatcher
|
||||
|
||||
def test_run_once(self, job_queue):
|
||||
job_queue.run_once(self.job_run_once, 0.01)
|
||||
sleep(0.02)
|
||||
|
@ -228,19 +245,19 @@ class TestJobQueue:
|
|||
sleep(0.03)
|
||||
assert self.result == 1
|
||||
|
||||
def test_in_updater(self, bot):
|
||||
u = Updater(bot=bot)
|
||||
u.job_queue.start()
|
||||
def test_in_dispatcher(self, bot):
|
||||
dispatcher = DispatcherBuilder().bot(bot).build()
|
||||
dispatcher.job_queue.start()
|
||||
try:
|
||||
u.job_queue.run_repeating(self.job_run_once, 0.02)
|
||||
dispatcher.job_queue.run_repeating(self.job_run_once, 0.02)
|
||||
sleep(0.03)
|
||||
assert self.result == 1
|
||||
u.stop()
|
||||
dispatcher.stop()
|
||||
sleep(1)
|
||||
assert self.result == 1
|
||||
finally:
|
||||
try:
|
||||
u.stop()
|
||||
dispatcher.stop()
|
||||
except SchedulerNotRunningError:
|
||||
pass
|
||||
|
||||
|
@ -479,12 +496,15 @@ class TestJobQueue:
|
|||
assert 'No error handlers are registered' in rec.getMessage()
|
||||
|
||||
def test_custom_context(self, bot, job_queue):
|
||||
dispatcher = Dispatcher(
|
||||
bot,
|
||||
Queue(),
|
||||
context_types=ContextTypes(
|
||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||
),
|
||||
dispatcher = (
|
||||
DispatcherBuilder()
|
||||
.bot(bot)
|
||||
.context_types(
|
||||
ContextTypes(
|
||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
job_queue.set_dispatcher(dispatcher)
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import uuid
|
|||
from pathlib import Path
|
||||
from threading import Lock
|
||||
|
||||
from telegram.ext import PersistenceInput
|
||||
from telegram.ext import PersistenceInput, UpdaterBuilder
|
||||
from telegram.ext.callbackdatacache import CallbackDataCache
|
||||
|
||||
try:
|
||||
|
@ -41,7 +41,6 @@ import pytest
|
|||
from telegram import Update, Message, User, Chat, MessageEntity, Bot
|
||||
from telegram.ext import (
|
||||
BasePersistence,
|
||||
Updater,
|
||||
ConversationHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
|
@ -215,7 +214,7 @@ def conversations():
|
|||
@pytest.fixture(scope="function")
|
||||
def updater(bot, base_persistence):
|
||||
base_persistence.store_data = PersistenceInput(False, False, False, False)
|
||||
u = Updater(bot=bot, persistence=base_persistence)
|
||||
u = UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
base_persistence.store_data = PersistenceInput()
|
||||
return u
|
||||
|
||||
|
@ -304,34 +303,36 @@ class TestBasePersistence:
|
|||
base_persistence.get_callback_data = get_callback_data
|
||||
|
||||
with pytest.raises(ValueError, match="user_data must be of type defaultdict"):
|
||||
u = Updater(bot=bot, persistence=base_persistence)
|
||||
UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
|
||||
def get_user_data():
|
||||
return user_data
|
||||
|
||||
base_persistence.get_user_data = get_user_data
|
||||
with pytest.raises(ValueError, match="chat_data must be of type defaultdict"):
|
||||
Updater(bot=bot, persistence=base_persistence)
|
||||
UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
|
||||
def get_chat_data():
|
||||
return chat_data
|
||||
|
||||
base_persistence.get_chat_data = get_chat_data
|
||||
with pytest.raises(ValueError, match="bot_data must be of type dict"):
|
||||
Updater(bot=bot, persistence=base_persistence)
|
||||
UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
|
||||
def get_bot_data():
|
||||
return bot_data
|
||||
|
||||
base_persistence.get_bot_data = get_bot_data
|
||||
with pytest.raises(ValueError, match="callback_data must be a 2-tuple"):
|
||||
Updater(bot=bot, persistence=base_persistence)
|
||||
UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
|
||||
def get_callback_data():
|
||||
return callback_data
|
||||
|
||||
base_persistence.bot = None
|
||||
base_persistence.get_callback_data = get_callback_data
|
||||
u = Updater(bot=bot, persistence=base_persistence)
|
||||
u = UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
assert u.dispatcher.bot is base_persistence.bot
|
||||
assert u.dispatcher.bot_data == bot_data
|
||||
assert u.dispatcher.chat_data == chat_data
|
||||
assert u.dispatcher.user_data == user_data
|
||||
|
@ -373,7 +374,7 @@ class TestBasePersistence:
|
|||
base_persistence.refresh_bot_data = lambda x: x
|
||||
base_persistence.refresh_chat_data = lambda x, y: x
|
||||
base_persistence.refresh_user_data = lambda x, y: x
|
||||
updater = Updater(bot=bot, persistence=base_persistence)
|
||||
updater = UpdaterBuilder().bot(bot).persistence(base_persistence).build()
|
||||
dp = updater.dispatcher
|
||||
|
||||
def callback_known_user(update, context):
|
||||
|
@ -1622,7 +1623,7 @@ class TestPicklePersistence:
|
|||
assert conversations_test['name1'] == conversation1
|
||||
|
||||
def test_with_handler(self, bot, update, bot_data, pickle_persistence, good_pickle_files):
|
||||
u = Updater(bot=bot, persistence=pickle_persistence)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence).build()
|
||||
dp = u.dispatcher
|
||||
bot.callback_data_cache.clear_callback_data()
|
||||
bot.callback_data_cache.clear_callback_queries()
|
||||
|
@ -1660,13 +1661,13 @@ class TestPicklePersistence:
|
|||
single_file=False,
|
||||
on_flush=False,
|
||||
)
|
||||
u = Updater(bot=bot, persistence=pickle_persistence_2)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence_2).build()
|
||||
dp = u.dispatcher
|
||||
dp.add_handler(h2)
|
||||
dp.process_update(update)
|
||||
|
||||
def test_flush_on_stop(self, bot, update, pickle_persistence):
|
||||
u = Updater(bot=bot, persistence=pickle_persistence)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence).build()
|
||||
dp = u.dispatcher
|
||||
u.running = True
|
||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||
|
@ -1686,7 +1687,7 @@ class TestPicklePersistence:
|
|||
assert data['test'] == 'Working4!'
|
||||
|
||||
def test_flush_on_stop_only_bot(self, bot, update, pickle_persistence_only_bot):
|
||||
u = Updater(bot=bot, persistence=pickle_persistence_only_bot)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence_only_bot).build()
|
||||
dp = u.dispatcher
|
||||
u.running = True
|
||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||
|
@ -1706,7 +1707,7 @@ class TestPicklePersistence:
|
|||
assert pickle_persistence_2.get_callback_data() is None
|
||||
|
||||
def test_flush_on_stop_only_chat(self, bot, update, pickle_persistence_only_chat):
|
||||
u = Updater(bot=bot, persistence=pickle_persistence_only_chat)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence_only_chat).build()
|
||||
dp = u.dispatcher
|
||||
u.running = True
|
||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||
|
@ -1726,7 +1727,7 @@ class TestPicklePersistence:
|
|||
assert pickle_persistence_2.get_callback_data() is None
|
||||
|
||||
def test_flush_on_stop_only_user(self, bot, update, pickle_persistence_only_user):
|
||||
u = Updater(bot=bot, persistence=pickle_persistence_only_user)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence_only_user).build()
|
||||
dp = u.dispatcher
|
||||
u.running = True
|
||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||
|
@ -1746,7 +1747,7 @@ class TestPicklePersistence:
|
|||
assert pickle_persistence_2.get_callback_data() is None
|
||||
|
||||
def test_flush_on_stop_only_callback(self, bot, update, pickle_persistence_only_callback):
|
||||
u = Updater(bot=bot, persistence=pickle_persistence_only_callback)
|
||||
u = UpdaterBuilder().bot(bot).persistence(pickle_persistence_only_callback).build()
|
||||
dp = u.dispatcher
|
||||
u.running = True
|
||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||
|
@ -2194,7 +2195,7 @@ class TestDictPersistence:
|
|||
|
||||
def test_with_handler(self, bot, update):
|
||||
dict_persistence = DictPersistence()
|
||||
u = Updater(bot=bot, persistence=dict_persistence)
|
||||
u = UpdaterBuilder().bot(bot).persistence(dict_persistence).build()
|
||||
dp = u.dispatcher
|
||||
|
||||
def first(update, context):
|
||||
|
@ -2236,7 +2237,7 @@ class TestDictPersistence:
|
|||
callback_data_json=callback_data,
|
||||
)
|
||||
|
||||
u = Updater(bot=bot, persistence=dict_persistence_2)
|
||||
u = UpdaterBuilder().bot(bot).persistence(dict_persistence_2).build()
|
||||
dp = u.dispatcher
|
||||
dp.add_handler(h2)
|
||||
dp.process_update(update)
|
||||
|
|
35
tests/test_stack.py
Normal file
35
tests/test_stack.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# 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 inspect
|
||||
from pathlib import Path
|
||||
|
||||
from telegram.ext.utils.stack import was_called_by
|
||||
|
||||
|
||||
class TestStack:
|
||||
def test_none_input(self):
|
||||
assert not was_called_by(None, None)
|
||||
|
||||
def test_called_by_current_file(self):
|
||||
frame = inspect.currentframe()
|
||||
file = Path(__file__)
|
||||
assert was_called_by(frame, file)
|
||||
|
||||
# Testing a call by a different file is somewhat hard but it's covered in
|
||||
# TestUpdater/Dispatcher.test_manual_init_warning
|
|
@ -48,14 +48,12 @@ from telegram import (
|
|||
)
|
||||
from telegram.error import Unauthorized, InvalidToken, TimedOut, RetryAfter, TelegramError
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
Dispatcher,
|
||||
DictPersistence,
|
||||
Defaults,
|
||||
InvalidCallbackData,
|
||||
ExtBot,
|
||||
Updater,
|
||||
UpdaterBuilder,
|
||||
DispatcherBuilder,
|
||||
)
|
||||
from telegram.warnings import PTBDeprecationWarning
|
||||
from telegram.ext.utils.webhookhandler import WebhookServer
|
||||
|
||||
signalskip = pytest.mark.skipif(
|
||||
|
@ -90,12 +88,6 @@ class TestUpdater:
|
|||
offset = 0
|
||||
test_flag = False
|
||||
|
||||
def test_slot_behaviour(self, updater, mro_slots):
|
||||
for at in updater.__slots__:
|
||||
at = f"_Updater{at}" if at.startswith('__') and not at.endswith('__') else at
|
||||
assert getattr(updater, at, 'err') != 'err', f"got extra slot '{at}'"
|
||||
assert len(mro_slots(updater)) == len(set(mro_slots(updater))), "duplicate slot"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset(self):
|
||||
self.message_count = 0
|
||||
|
@ -113,18 +105,49 @@ class TestUpdater:
|
|||
self.received = update.message.text
|
||||
self.cb_handler_called.set()
|
||||
|
||||
def test_warn_arbitrary_callback_data(self, bot, recwarn):
|
||||
Updater(bot=bot, arbitrary_callback_data=True)
|
||||
def test_slot_behaviour(self, updater, mro_slots):
|
||||
for at in updater.__slots__:
|
||||
at = f"_Updater{at}" if at.startswith('__') and not at.endswith('__') else at
|
||||
assert getattr(updater, at, 'err') != 'err', f"got extra slot '{at}'"
|
||||
assert len(mro_slots(updater)) == len(set(mro_slots(updater))), "duplicate slot"
|
||||
|
||||
def test_manual_init_warning(self, recwarn):
|
||||
Updater(
|
||||
bot=None,
|
||||
dispatcher=None,
|
||||
update_queue=None,
|
||||
exception_event=None,
|
||||
user_signal_handler=None,
|
||||
)
|
||||
assert len(recwarn) == 1
|
||||
assert 'Passing arbitrary_callback_data to an Updater' in str(recwarn[0].message)
|
||||
assert (
|
||||
str(recwarn[-1].message)
|
||||
== '`Updater` instances should be built via the `UpdaterBuilder`.'
|
||||
)
|
||||
assert recwarn[0].filename == __file__, "stacklevel is incorrect!"
|
||||
|
||||
def test_builder(self, updater):
|
||||
builder_1 = updater.builder()
|
||||
builder_2 = updater.builder()
|
||||
assert isinstance(builder_1, UpdaterBuilder)
|
||||
assert isinstance(builder_2, UpdaterBuilder)
|
||||
assert builder_1 is not builder_2
|
||||
|
||||
# Make sure that setting a token doesn't raise an exception
|
||||
# i.e. check that the builders are "empty"/new
|
||||
builder_1.token(updater.bot.token)
|
||||
builder_2.token(updater.bot.token)
|
||||
|
||||
def test_warn_con_pool(self, bot, recwarn, dp):
|
||||
dp = Dispatcher(bot, Queue(), workers=5)
|
||||
Updater(bot=bot, workers=8)
|
||||
Updater(dispatcher=dp, workers=None)
|
||||
DispatcherBuilder().bot(bot).workers(5).build()
|
||||
UpdaterBuilder().bot(bot).workers(8).build()
|
||||
UpdaterBuilder().bot(bot).workers(2).build()
|
||||
assert len(recwarn) == 2
|
||||
for idx, value in enumerate((12, 9)):
|
||||
warning = f'Connection pool of Request object is smaller than optimal value {value}'
|
||||
for idx, value in enumerate((9, 12)):
|
||||
warning = (
|
||||
'The Connection pool of Request object is smaller (8) than the '
|
||||
f'recommended value of {value}.'
|
||||
)
|
||||
assert str(recwarn[idx].message) == warning
|
||||
assert recwarn[idx].filename == __file__, "wrong stacklevel!"
|
||||
|
||||
|
@ -305,9 +328,21 @@ class TestUpdater:
|
|||
updater.bot.callback_data_cache.clear_callback_data()
|
||||
updater.bot.callback_data_cache.clear_callback_queries()
|
||||
|
||||
def test_start_webhook_no_warning_or_error_logs(self, caplog, updater, monkeypatch):
|
||||
@pytest.mark.parametrize('use_dispatcher', (True, False))
|
||||
def test_start_webhook_no_warning_or_error_logs(
|
||||
self, caplog, updater, monkeypatch, use_dispatcher
|
||||
):
|
||||
if not use_dispatcher:
|
||||
updater.dispatcher = None
|
||||
|
||||
self.test_flag = 0
|
||||
|
||||
def set_flag():
|
||||
self.test_flag += 1
|
||||
|
||||
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
||||
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
|
||||
monkeypatch.setattr(updater.bot._request, 'stop', lambda *args, **kwargs: set_flag())
|
||||
# prevent api calls from @info decorator when updater.bot.id is used in thread names
|
||||
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
|
||||
|
||||
|
@ -317,6 +352,8 @@ class TestUpdater:
|
|||
updater.start_webhook(ip, port)
|
||||
updater.stop()
|
||||
assert not caplog.records
|
||||
# Make sure that bot.request.stop() has been called exactly once
|
||||
assert self.test_flag == 1
|
||||
|
||||
def test_webhook_ssl(self, monkeypatch, updater):
|
||||
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
||||
|
@ -606,7 +643,7 @@ class TestUpdater:
|
|||
def user_signal_inc(signum, frame):
|
||||
temp_var['a'] = 1
|
||||
|
||||
updater.user_sig_handler = user_signal_inc
|
||||
updater.user_signal_handler = user_signal_inc
|
||||
updater.start_polling(0.01)
|
||||
Thread(target=partial(self.signal_sender, updater=updater)).start()
|
||||
updater.idle()
|
||||
|
@ -614,47 +651,3 @@ class TestUpdater:
|
|||
sleep(0.5)
|
||||
assert updater.running is False
|
||||
assert temp_var['a'] != 0
|
||||
|
||||
def test_create_bot(self):
|
||||
updater = Updater('123:abcd')
|
||||
assert updater.bot is not None
|
||||
|
||||
def test_mutual_exclude_token_bot(self):
|
||||
bot = Bot('123:zyxw')
|
||||
with pytest.raises(ValueError):
|
||||
Updater(token='123:abcd', bot=bot)
|
||||
|
||||
def test_no_token_or_bot_or_dispatcher(self):
|
||||
with pytest.raises(ValueError):
|
||||
Updater()
|
||||
|
||||
def test_mutual_exclude_bot_private_key(self):
|
||||
bot = Bot('123:zyxw')
|
||||
with pytest.raises(ValueError):
|
||||
Updater(bot=bot, private_key=b'key')
|
||||
|
||||
def test_mutual_exclude_bot_dispatcher(self, bot):
|
||||
dispatcher = Dispatcher(bot, None)
|
||||
bot = Bot('123:zyxw')
|
||||
with pytest.raises(ValueError):
|
||||
Updater(bot=bot, dispatcher=dispatcher)
|
||||
|
||||
def test_mutual_exclude_persistence_dispatcher(self, bot):
|
||||
dispatcher = Dispatcher(bot, None)
|
||||
persistence = DictPersistence()
|
||||
with pytest.raises(ValueError):
|
||||
Updater(dispatcher=dispatcher, persistence=persistence)
|
||||
|
||||
def test_mutual_exclude_workers_dispatcher(self, bot):
|
||||
dispatcher = Dispatcher(bot, None)
|
||||
with pytest.raises(ValueError):
|
||||
Updater(dispatcher=dispatcher, workers=8)
|
||||
|
||||
def test_mutual_exclude_custom_context_dispatcher(self):
|
||||
dispatcher = Dispatcher(None, None)
|
||||
with pytest.raises(ValueError):
|
||||
Updater(dispatcher=dispatcher, context_types=True)
|
||||
|
||||
def test_defaults_warning(self, bot):
|
||||
with pytest.warns(PTBDeprecationWarning, match='no effect when a Bot is passed'):
|
||||
Updater(bot=bot, defaults=Defaults())
|
||||
|
|
Loading…
Reference in a new issue