mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 22:45:09 +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:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
- repo: https://github.com/PyCQA/pylint
|
- repo: https://github.com/PyCQA/pylint
|
||||||
rev: v2.8.3
|
rev: v2.10.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
files: ^(telegram|examples)/.*\.py$
|
files: ^(telegram|examples)/.*\.py$
|
||||||
|
@ -27,12 +27,17 @@ repos:
|
||||||
- cachetools==4.2.2
|
- cachetools==4.2.2
|
||||||
- . # this basically does `pip install -e .`
|
- . # this basically does `pip install -e .`
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.812
|
rev: v0.910
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
name: mypy-ptb
|
name: mypy-ptb
|
||||||
files: ^telegram/.*\.py$
|
files: ^telegram/.*\.py$
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
|
- types-ujson
|
||||||
|
- types-pytz
|
||||||
|
- types-cryptography
|
||||||
|
- types-certifi
|
||||||
|
- types-cachetools
|
||||||
- certifi
|
- certifi
|
||||||
- tornado>=6.1
|
- tornado>=6.1
|
||||||
- APScheduler==3.6.3
|
- APScheduler==3.6.3
|
||||||
|
@ -51,7 +56,7 @@ repos:
|
||||||
- cachetools==4.2.2
|
- cachetools==4.2.2
|
||||||
- . # this basically does `pip install -e .`
|
- . # this basically does `pip install -e .`
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.19.1
|
rev: v2.24.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
files: ^(telegram|examples|tests)/.*\.py$
|
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::
|
.. toctree::
|
||||||
|
|
||||||
telegram.ext.extbot
|
telegram.ext.extbot
|
||||||
|
telegram.ext.updaterbuilder
|
||||||
telegram.ext.updater
|
telegram.ext.updater
|
||||||
|
telegram.ext.dispatcherbuilder
|
||||||
telegram.ext.dispatcher
|
telegram.ext.dispatcher
|
||||||
telegram.ext.dispatcherhandlerstop
|
telegram.ext.dispatcherhandlerstop
|
||||||
telegram.ext.callbackcontext
|
telegram.ext.callbackcontext
|
||||||
|
@ -61,4 +63,5 @@ utils
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
telegram.ext.utils.promise
|
telegram.ext.utils.promise
|
||||||
|
telegram.ext.utils.stack
|
||||||
telegram.ext.utils.types
|
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 import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
CallbackQueryHandler,
|
CallbackQueryHandler,
|
||||||
CallbackContext,
|
|
||||||
InvalidCallbackData,
|
InvalidCallbackData,
|
||||||
PicklePersistence,
|
PicklePersistence,
|
||||||
|
Updater,
|
||||||
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
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."""
|
"""Sends a message with 5 inline buttons attached."""
|
||||||
number_list: List[int] = []
|
number_list: List[int] = []
|
||||||
update.message.reply_text('Please choose:', reply_markup=build_keyboard(number_list))
|
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."""
|
"""Displays info on how to use the bot."""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
"Use /start to test this bot. Use /clear to clear the stored data so that you can see "
|
"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"""
|
"""Clears the callback data cache"""
|
||||||
context.bot.callback_data_cache.clear_callback_data() # type: ignore[attr-defined]
|
context.bot.callback_data_cache.clear_callback_data()
|
||||||
context.bot.callback_data_cache.clear_callback_queries() # type: ignore[attr-defined]
|
context.bot.callback_data_cache.clear_callback_queries()
|
||||||
update.effective_message.reply_text('All clear!')
|
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."""
|
"""Parses the CallbackQuery and updates the message text."""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
query.answer()
|
query.answer()
|
||||||
|
@ -73,7 +75,7 @@ def list_button(update: Update, context: CallbackContext) -> None:
|
||||||
context.drop_callback_data(query)
|
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."""
|
"""Informs the user that the button is no longer available."""
|
||||||
update.callback_query.answer()
|
update.callback_query.answer()
|
||||||
update.effective_message.edit_text(
|
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
|
# We use persistence to demonstrate how buttons can still work after the bot was restarted
|
||||||
persistence = PicklePersistence(filepath='arbitrarycallbackdatabot')
|
persistence = PicklePersistence(filepath='arbitrarycallbackdatabot')
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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('start', start))
|
||||||
updater.dispatcher.add_handler(CommandHandler('help', help_command))
|
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 import Update, Chat, ChatMember, ParseMode, ChatMemberUpdated
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
CallbackContext,
|
|
||||||
ChatMemberHandler,
|
ChatMemberHandler,
|
||||||
|
Updater,
|
||||||
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
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
|
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."""
|
"""Tracks the chats the bot is in."""
|
||||||
result = extract_status_change(update.my_chat_member)
|
result = extract_status_change(update.my_chat_member)
|
||||||
if result is None:
|
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)
|
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"""
|
"""Shows which chats the bot is in"""
|
||||||
user_ids = ", ".join(str(uid) for uid in context.bot_data.setdefault("user_ids", set()))
|
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()))
|
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)
|
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"""
|
"""Greets new users in chats and announces when someone leaves"""
|
||||||
result = extract_status_change(update.chat_member)
|
result = extract_status_change(update.chat_member)
|
||||||
if result is None:
|
if result is None:
|
||||||
|
@ -139,7 +140,7 @@ def greet_chat_members(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Start the bot."""
|
"""Start the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -15,13 +15,14 @@ from typing import DefaultDict, Optional, Set
|
||||||
|
|
||||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
ContextTypes,
|
ContextTypes,
|
||||||
CallbackQueryHandler,
|
CallbackQueryHandler,
|
||||||
TypeHandler,
|
TypeHandler,
|
||||||
Dispatcher,
|
Dispatcher,
|
||||||
|
ExtBot,
|
||||||
|
Updater,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +33,8 @@ class ChatData:
|
||||||
self.clicks_per_message: DefaultDict[int, int] = defaultdict(int)
|
self.clicks_per_message: DefaultDict[int, int] = defaultdict(int)
|
||||||
|
|
||||||
|
|
||||||
# The [dict, ChatData, dict] is for type checkers like mypy
|
# The [ExtBot, dict, ChatData, dict] is for type checkers like mypy
|
||||||
class CustomContext(CallbackContext[dict, ChatData, dict]):
|
class CustomContext(CallbackContext[ExtBot, dict, ChatData, dict]):
|
||||||
"""Custom class for context."""
|
"""Custom class for context."""
|
||||||
|
|
||||||
def __init__(self, dispatcher: Dispatcher):
|
def __init__(self, dispatcher: Dispatcher):
|
||||||
|
@ -113,7 +114,7 @@ def track_users(update: Update, context: CustomContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
context_types = ContextTypes(context=CustomContext, chat_data=ChatData)
|
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
|
dispatcher = updater.dispatcher
|
||||||
# run track_users in its own group to not interfere with the user handlers
|
# 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 import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
GENDER, PHOTO, LOCATION, BIO = range(4)
|
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."""
|
"""Starts the conversation and asks the user about their gender."""
|
||||||
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
||||||
return GENDER
|
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."""
|
"""Stores the selected gender and asks for a photo."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
logger.info("Gender of %s: %s", user.first_name, update.message.text)
|
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
|
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."""
|
"""Stores the photo and asks for a location."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
photo_file = update.message.photo[-1].get_file()
|
photo_file = update.message.photo[-1].get_file()
|
||||||
|
@ -78,7 +78,7 @@ def photo(update: Update, context: CallbackContext) -> int:
|
||||||
return LOCATION
|
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."""
|
"""Skips the photo and asks for a location."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
logger.info("User %s did not send a photo.", user.first_name)
|
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
|
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."""
|
"""Stores the location and asks for some info about the user."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
user_location = update.message.location
|
user_location = update.message.location
|
||||||
|
@ -103,7 +103,7 @@ def location(update: Update, context: CallbackContext) -> int:
|
||||||
return BIO
|
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."""
|
"""Skips the location and asks for info about the user."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
logger.info("User %s did not send a location.", user.first_name)
|
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
|
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."""
|
"""Stores the info about the user and ends the conversation."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
logger.info("Bio of %s: %s", user.first_name, update.message.text)
|
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
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
def cancel(update: Update, context: CallbackContext) -> int:
|
def cancel(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""Cancels and ends the conversation."""
|
"""Cancels and ends the conversation."""
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
logger.info("User %s canceled the conversation.", user.first_name)
|
logger.info("User %s canceled the conversation.", user.first_name)
|
||||||
|
@ -137,7 +137,7 @@ def cancel(update: Update, context: CallbackContext) -> int:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -19,19 +19,19 @@ from typing import Dict
|
||||||
|
|
||||||
from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove
|
from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
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'])
|
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."""
|
"""Start the conversation and ask user for input."""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
"Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
|
"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
|
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."""
|
"""Ask the user for info about the selected predefined choice."""
|
||||||
text = update.message.text
|
text = update.message.text
|
||||||
context.user_data['choice'] = text
|
context.user_data['choice'] = text
|
||||||
|
@ -70,7 +70,7 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||||
return TYPING_REPLY
|
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."""
|
"""Ask the user for a description of a custom category."""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
'Alright, please send me the category first, for example "Most impressive skill"'
|
'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
|
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."""
|
"""Store info provided by user and ask for the next category."""
|
||||||
user_data = context.user_data
|
user_data = context.user_data
|
||||||
text = update.message.text
|
text = update.message.text
|
||||||
|
@ -97,7 +97,7 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
||||||
return CHOOSING
|
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."""
|
"""Display the gathered info and end the conversation."""
|
||||||
user_data = context.user_data
|
user_data = context.user_data
|
||||||
if 'choice' in user_data:
|
if 'choice' in user_data:
|
||||||
|
@ -115,7 +115,7 @@ def done(update: Update, context: CallbackContext) -> int:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -22,10 +22,10 @@ import logging
|
||||||
|
|
||||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton, Update, helpers
|
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton, Update, helpers
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
CallbackQueryHandler,
|
CallbackQueryHandler,
|
||||||
Filters,
|
Filters,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ SO_COOL = "so-cool"
|
||||||
KEYBOARD_CALLBACKDATA = "keyboard-callback-data"
|
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."""
|
"""Send a deep-linked URL when the command /start is issued."""
|
||||||
bot = context.bot
|
bot = context.bot
|
||||||
url = helpers.create_deep_linked_url(bot.username, CHECK_THIS_OUT, group=True)
|
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)
|
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"""
|
"""Reached through the CHECK_THIS_OUT payload"""
|
||||||
bot = context.bot
|
bot = context.bot
|
||||||
url = helpers.create_deep_linked_url(bot.username, SO_COOL)
|
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)
|
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"""
|
"""Reached through the SO_COOL payload"""
|
||||||
bot = context.bot
|
bot = context.bot
|
||||||
url = helpers.create_deep_linked_url(bot.username, USING_ENTITIES)
|
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)
|
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"""
|
"""Reached through the USING_ENTITIES payload"""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
"It is also possible to make deep-linking using InlineKeyboardButtons.",
|
"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."""
|
"""Answers CallbackQuery with deeplinking url."""
|
||||||
bot = context.bot
|
bot = context.bot
|
||||||
url = helpers.create_deep_linked_url(bot.username, USING_KEYBOARD)
|
url = helpers.create_deep_linked_url(bot.username, USING_KEYBOARD)
|
||||||
update.callback_query.answer(url=url)
|
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"""
|
"""Reached through the USING_KEYBOARD payload"""
|
||||||
payload = context.args
|
payload = context.args
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
|
@ -104,7 +104,7 @@ def deep_linked_level_4(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Start the bot."""
|
"""Start the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -18,19 +18,25 @@ bot.
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from telegram import Update, ForceReply
|
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
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Define a few command handlers. These usually take the two arguments update and
|
# Define a few command handlers. These usually take the two arguments update and
|
||||||
# context.
|
# 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."""
|
"""Send a message when the command /start is issued."""
|
||||||
user = update.effective_user
|
user = update.effective_user
|
||||||
update.message.reply_markdown_v2(
|
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."""
|
"""Send a message when the command /help is issued."""
|
||||||
update.message.reply_text('Help!')
|
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."""
|
"""Echo the user message."""
|
||||||
update.message.reply_text(update.message.text)
|
update.message.reply_text(update.message.text)
|
||||||
|
|
||||||
|
@ -52,7 +58,7 @@ def echo(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Start the bot."""
|
"""Start the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -9,12 +9,12 @@ import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from telegram import Update, ParseMode
|
from telegram import Update, ParseMode
|
||||||
from telegram.ext import Updater, CallbackContext, CommandHandler
|
from telegram.ext import CommandHandler, Updater, CallbackContext
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# The token you got from @botfather when you created the bot
|
# The token you got from @botfather when you created the bot
|
||||||
|
@ -25,7 +25,7 @@ BOT_TOKEN = "TOKEN"
|
||||||
DEVELOPER_CHAT_ID = 123456789
|
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 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.
|
# 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)
|
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)
|
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."""
|
"""Raise an error to trigger the error handler."""
|
||||||
context.bot.wrong_method_name() # type: ignore[attr-defined]
|
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."""
|
"""Displays info on how to trigger an error."""
|
||||||
update.effective_message.reply_html(
|
update.effective_message.reply_html(
|
||||||
'Use /bad_command to cause an error.\n'
|
'Use /bad_command to cause an error.\n'
|
||||||
|
@ -67,7 +67,7 @@ def start(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -23,23 +23,22 @@ from telegram.ext import Updater, InlineQueryHandler, CommandHandler, CallbackCo
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Define a few command handlers. These usually take the two arguments update and
|
# Define a few command handlers. These usually take the two arguments update and
|
||||||
# context. Error handlers also receive the raised TelegramError object in error.
|
# 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."""
|
"""Send a message when the command /start is issued."""
|
||||||
update.message.reply_text('Hi!')
|
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."""
|
"""Send a message when the command /help is issued."""
|
||||||
update.message.reply_text('Help!')
|
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."""
|
"""Handle the inline query."""
|
||||||
query = update.inline_query.query
|
query = update.inline_query.query
|
||||||
|
|
||||||
|
@ -74,7 +73,7 @@ def inlinequery(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -9,15 +9,22 @@ Basic example for a bot that uses inline keyboards. For an in-depth explanation,
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
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(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
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."""
|
"""Sends a message with three inline buttons attached."""
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[
|
[
|
||||||
|
@ -32,7 +39,7 @@ def start(update: Update, context: CallbackContext) -> None:
|
||||||
update.message.reply_text('Please choose:', reply_markup=reply_markup)
|
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."""
|
"""Parses the CallbackQuery and updates the message text."""
|
||||||
query = update.callback_query
|
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}")
|
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."""
|
"""Displays info on how to use the bot."""
|
||||||
update.message.reply_text("Use /start to test this 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:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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(CommandHandler('start', start))
|
||||||
updater.dispatcher.add_handler(CallbackQueryHandler(button))
|
updater.dispatcher.add_handler(CallbackQueryHandler(button))
|
||||||
|
|
|
@ -17,18 +17,18 @@ Press Ctrl-C on the command line to stop the bot.
|
||||||
import logging
|
import logging
|
||||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
CallbackQueryHandler,
|
CallbackQueryHandler,
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Stages
|
# Stages
|
||||||
|
@ -37,7 +37,7 @@ FIRST, SECOND = range(2)
|
||||||
ONE, TWO, THREE, FOUR = range(4)
|
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`."""
|
"""Send message on `/start`."""
|
||||||
# Get user that sent /start and log his name
|
# Get user that sent /start and log his name
|
||||||
user = update.message.from_user
|
user = update.message.from_user
|
||||||
|
@ -59,7 +59,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
||||||
return FIRST
|
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"""
|
"""Prompt same text & keyboard as `start` does but not as new message"""
|
||||||
# Get CallbackQuery from Update
|
# Get CallbackQuery from Update
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
|
@ -80,7 +80,7 @@ def start_over(update: Update, context: CallbackContext) -> int:
|
||||||
return FIRST
|
return FIRST
|
||||||
|
|
||||||
|
|
||||||
def one(update: Update, context: CallbackContext) -> int:
|
def one(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""Show new choice of buttons"""
|
"""Show new choice of buttons"""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
query.answer()
|
query.answer()
|
||||||
|
@ -97,7 +97,7 @@ def one(update: Update, context: CallbackContext) -> int:
|
||||||
return FIRST
|
return FIRST
|
||||||
|
|
||||||
|
|
||||||
def two(update: Update, context: CallbackContext) -> int:
|
def two(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""Show new choice of buttons"""
|
"""Show new choice of buttons"""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
query.answer()
|
query.answer()
|
||||||
|
@ -114,7 +114,7 @@ def two(update: Update, context: CallbackContext) -> int:
|
||||||
return FIRST
|
return FIRST
|
||||||
|
|
||||||
|
|
||||||
def three(update: Update, context: CallbackContext) -> int:
|
def three(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""Show new choice of buttons"""
|
"""Show new choice of buttons"""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
query.answer()
|
query.answer()
|
||||||
|
@ -132,7 +132,7 @@ def three(update: Update, context: CallbackContext) -> int:
|
||||||
return SECOND
|
return SECOND
|
||||||
|
|
||||||
|
|
||||||
def four(update: Update, context: CallbackContext) -> int:
|
def four(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""Show new choice of buttons"""
|
"""Show new choice of buttons"""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
query.answer()
|
query.answer()
|
||||||
|
@ -149,7 +149,7 @@ def four(update: Update, context: CallbackContext) -> int:
|
||||||
return FIRST
|
return FIRST
|
||||||
|
|
||||||
|
|
||||||
def end(update: Update, context: CallbackContext) -> int:
|
def end(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""Returns `ConversationHandler.END`, which tells the
|
"""Returns `ConversationHandler.END`, which tells the
|
||||||
ConversationHandler that the conversation is over.
|
ConversationHandler that the conversation is over.
|
||||||
"""
|
"""
|
||||||
|
@ -162,7 +162,7 @@ def end(update: Update, context: CallbackContext) -> int:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -19,20 +19,20 @@ from typing import Tuple, Dict, Any
|
||||||
|
|
||||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
|
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
CallbackQueryHandler,
|
CallbackQueryHandler,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# State definitions for top level conversation
|
# State definitions for top level conversation
|
||||||
|
@ -71,7 +71,7 @@ def _name_switcher(level: str) -> Tuple[str, str]:
|
||||||
|
|
||||||
|
|
||||||
# Top level conversation callbacks
|
# 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."""
|
"""Select an action: Adding parent/child or show data."""
|
||||||
text = (
|
text = (
|
||||||
"You may choose to add a family member, yourself, show the gathered data, or end the "
|
"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
|
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."""
|
"""Add information about yourself."""
|
||||||
context.user_data[CURRENT_LEVEL] = SELF
|
context.user_data[CURRENT_LEVEL] = SELF
|
||||||
text = 'Okay, please tell me about yourself.'
|
text = 'Okay, please tell me about yourself.'
|
||||||
|
@ -117,7 +117,7 @@ def adding_self(update: Update, context: CallbackContext) -> str:
|
||||||
return DESCRIBING_SELF
|
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."""
|
"""Pretty print gathered data."""
|
||||||
|
|
||||||
def prettyprint(user_data: Dict[str, Any], level: str) -> str:
|
def prettyprint(user_data: Dict[str, Any], level: str) -> str:
|
||||||
|
@ -152,14 +152,14 @@ def show_data(update: Update, context: CallbackContext) -> str:
|
||||||
return SHOWING
|
return SHOWING
|
||||||
|
|
||||||
|
|
||||||
def stop(update: Update, context: CallbackContext) -> int:
|
def stop(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""End Conversation by command."""
|
"""End Conversation by command."""
|
||||||
update.message.reply_text('Okay, bye.')
|
update.message.reply_text('Okay, bye.')
|
||||||
|
|
||||||
return END
|
return END
|
||||||
|
|
||||||
|
|
||||||
def end(update: Update, context: CallbackContext) -> int:
|
def end(update: Update, context: CallbackContext.DEFAULT_TYPE) -> int:
|
||||||
"""End conversation from InlineKeyboardButton."""
|
"""End conversation from InlineKeyboardButton."""
|
||||||
update.callback_query.answer()
|
update.callback_query.answer()
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ def end(update: Update, context: CallbackContext) -> int:
|
||||||
|
|
||||||
|
|
||||||
# Second level conversation callbacks
|
# 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."""
|
"""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.'
|
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
|
||||||
buttons = [
|
buttons = [
|
||||||
|
@ -191,7 +191,7 @@ def select_level(update: Update, context: CallbackContext) -> str:
|
||||||
return SELECTING_LEVEL
|
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."""
|
"""Choose to add mother or father."""
|
||||||
level = update.callback_query.data
|
level = update.callback_query.data
|
||||||
context.user_data[CURRENT_LEVEL] = level
|
context.user_data[CURRENT_LEVEL] = level
|
||||||
|
@ -218,7 +218,7 @@ def select_gender(update: Update, context: CallbackContext) -> str:
|
||||||
return SELECTING_GENDER
|
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."""
|
"""Return to top level conversation."""
|
||||||
context.user_data[START_OVER] = True
|
context.user_data[START_OVER] = True
|
||||||
start(update, context)
|
start(update, context)
|
||||||
|
@ -227,7 +227,7 @@ def end_second_level(update: Update, context: CallbackContext) -> int:
|
||||||
|
|
||||||
|
|
||||||
# Third level callbacks
|
# 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."""
|
"""Select a feature to update for the person."""
|
||||||
buttons = [
|
buttons = [
|
||||||
[
|
[
|
||||||
|
@ -254,7 +254,7 @@ def select_feature(update: Update, context: CallbackContext) -> str:
|
||||||
return SELECTING_FEATURE
|
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."""
|
"""Prompt user to input data for selected feature."""
|
||||||
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
||||||
text = 'Okay, tell me.'
|
text = 'Okay, tell me.'
|
||||||
|
@ -265,7 +265,7 @@ def ask_for_input(update: Update, context: CallbackContext) -> str:
|
||||||
return TYPING
|
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."""
|
"""Save input for feature and return to feature selection."""
|
||||||
user_data = context.user_data
|
user_data = context.user_data
|
||||||
user_data[FEATURES][user_data[CURRENT_FEATURE]] = update.message.text
|
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)
|
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."""
|
"""End gathering of features and return to parent conversation."""
|
||||||
user_data = context.user_data
|
user_data = context.user_data
|
||||||
level = user_data[CURRENT_LEVEL]
|
level = user_data[CURRENT_LEVEL]
|
||||||
|
@ -293,7 +293,7 @@ def end_describing(update: Update, context: CallbackContext) -> int:
|
||||||
return END
|
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."""
|
"""Completely end conversation from within nested conversation."""
|
||||||
update.message.reply_text('Okay, bye.')
|
update.message.reply_text('Okay, bye.')
|
||||||
|
|
||||||
|
@ -303,7 +303,7 @@ def stop_nested(update: Update, context: CallbackContext) -> str:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -14,9 +14,10 @@ import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from telegram import Update
|
from telegram import Update
|
||||||
from telegram.ext import Updater, MessageHandler, Filters, CallbackContext
|
from telegram.ext import MessageHandler, Filters, Updater, CallbackContext
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG
|
||||||
)
|
)
|
||||||
|
@ -24,7 +25,7 @@ logging.basicConfig(
|
||||||
logger = logging.getLogger(__name__)
|
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."""
|
"""Downloads and prints the received passport data."""
|
||||||
# Retrieve passport data
|
# Retrieve passport data
|
||||||
passport_data = update.message.passport_data
|
passport_data = update.message.passport_data
|
||||||
|
@ -102,7 +103,8 @@ def msg(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Start the bot."""
|
"""Start the bot."""
|
||||||
# Create the Updater and pass it your token and private key
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -8,24 +8,24 @@ import logging
|
||||||
|
|
||||||
from telegram import LabeledPrice, ShippingOption, Update
|
from telegram import LabeledPrice, ShippingOption, Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
PreCheckoutQueryHandler,
|
PreCheckoutQueryHandler,
|
||||||
ShippingQueryHandler,
|
ShippingQueryHandler,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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."""
|
"""Displays info on how to use the bot."""
|
||||||
msg = (
|
msg = (
|
||||||
"Use /shipping to get an invoice for shipping-payment, or /noshipping for an "
|
"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)
|
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."""
|
"""Sends an invoice with shipping-payment."""
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
title = "Payment Example"
|
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."""
|
"""Sends an invoice without shipping-payment."""
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
title = "Payment Example"
|
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"""
|
"""Answers the ShippingQuery with ShippingOptions"""
|
||||||
query = update.shipping_query
|
query = update.shipping_query
|
||||||
# check the payload, is this from your bot?
|
# 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
|
# 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"""
|
"""Answers the PreQecheckoutQuery"""
|
||||||
query = update.pre_checkout_query
|
query = update.pre_checkout_query
|
||||||
# check the payload, is this from your bot?
|
# 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...
|
# 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."""
|
"""Confirms the successful payment."""
|
||||||
# do something after successfully receiving payment?
|
# do something after successfully receiving payment?
|
||||||
update.message.reply_text("Thank you for your 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:
|
def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -19,20 +19,20 @@ from typing import Dict
|
||||||
|
|
||||||
from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove
|
from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
PicklePersistence,
|
PicklePersistence,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
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'])
|
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."""
|
"""Start the conversation, display any stored data and ask user for input."""
|
||||||
reply_text = "Hi! My name is Doctor Botter."
|
reply_text = "Hi! My name is Doctor Botter."
|
||||||
if context.user_data:
|
if context.user_data:
|
||||||
|
@ -69,7 +69,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
||||||
return CHOOSING
|
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."""
|
"""Ask the user for info about the selected predefined choice."""
|
||||||
text = update.message.text.lower()
|
text = update.message.text.lower()
|
||||||
context.user_data['choice'] = text
|
context.user_data['choice'] = text
|
||||||
|
@ -84,7 +84,7 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||||
return TYPING_REPLY
|
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."""
|
"""Ask the user for a description of a custom category."""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
'Alright, please send me the category first, for example "Most impressive skill"'
|
'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
|
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."""
|
"""Store info provided by user and ask for the next category."""
|
||||||
text = update.message.text
|
text = update.message.text
|
||||||
category = context.user_data['choice']
|
category = context.user_data['choice']
|
||||||
|
@ -110,14 +110,14 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
||||||
return CHOOSING
|
return CHOOSING
|
||||||
|
|
||||||
|
|
||||||
def show_data(update: Update, context: CallbackContext) -> None:
|
def show_data(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
|
||||||
"""Display the gathered info."""
|
"""Display the gathered info."""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
f"This is what you already told me: {facts_to_str(context.user_data)}"
|
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."""
|
"""Display the gathered info and end the conversation."""
|
||||||
if 'choice' in context.user_data:
|
if 'choice' in context.user_data:
|
||||||
del context.user_data['choice']
|
del context.user_data['choice']
|
||||||
|
@ -133,7 +133,7 @@ def main() -> None:
|
||||||
"""Run the bot."""
|
"""Run the bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# Create the Updater and pass it your bot's token.
|
||||||
persistence = PicklePersistence(filepath='conversationbot')
|
persistence = PicklePersistence(filepath='conversationbot')
|
||||||
updater = Updater("TOKEN", persistence=persistence)
|
updater = Updater.builder().token("TOKEN").persistence(persistence).build()
|
||||||
|
|
||||||
# Get the dispatcher to register handlers
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -19,22 +19,24 @@ from telegram import (
|
||||||
Update,
|
Update,
|
||||||
)
|
)
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
PollAnswerHandler,
|
PollAnswerHandler,
|
||||||
PollHandler,
|
PollHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
|
Updater,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
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"""
|
"""Inform user about what this bot can do"""
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
'Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
|
'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"""
|
"""Sends a predefined poll"""
|
||||||
questions = ["Good", "Really good", "Fantastic", "Great"]
|
questions = ["Good", "Really good", "Fantastic", "Great"]
|
||||||
message = context.bot.send_poll(
|
message = context.bot.send_poll(
|
||||||
|
@ -64,7 +66,7 @@ def poll(update: Update, context: CallbackContext) -> None:
|
||||||
context.bot_data.update(payload)
|
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"""
|
"""Summarize a users poll vote"""
|
||||||
answer = update.poll_answer
|
answer = update.poll_answer
|
||||||
poll_id = answer.poll_id
|
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"""
|
"""Send a predefined poll"""
|
||||||
questions = ["1", "2", "4", "20"]
|
questions = ["1", "2", "4", "20"]
|
||||||
message = update.effective_message.reply_poll(
|
message = update.effective_message.reply_poll(
|
||||||
|
@ -106,7 +108,7 @@ def quiz(update: Update, context: CallbackContext) -> None:
|
||||||
context.bot_data.update(payload)
|
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"""
|
"""Close quiz after three participants took it"""
|
||||||
# the bot can receive closed poll updates we don't care about
|
# the bot can receive closed poll updates we don't care about
|
||||||
if update.poll.is_closed:
|
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"])
|
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"""
|
"""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)
|
# using this without a type lets the user chooses what he wants (quiz or poll)
|
||||||
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
|
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"""
|
"""On receiving polls, reply to it by a closed poll copying the received poll"""
|
||||||
actual_poll = update.effective_message.poll
|
actual_poll = update.effective_message.poll
|
||||||
# Only need to set the question and options, since all other parameters don't matter for
|
# 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"""
|
"""Display a help message"""
|
||||||
update.message.reply_text("Use /quiz, /poll or /preview to test this bot.")
|
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:
|
def main() -> None:
|
||||||
"""Run bot."""
|
"""Run bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# Create the Updater and pass it your bot's token.
|
||||||
updater = Updater("TOKEN")
|
updater = Updater.builder().token("TOKEN").build()
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
dispatcher.add_handler(CommandHandler('start', start))
|
dispatcher.add_handler(CommandHandler('start', start))
|
||||||
dispatcher.add_handler(CommandHandler('poll', poll))
|
dispatcher.add_handler(CommandHandler('poll', poll))
|
||||||
|
|
|
@ -21,13 +21,12 @@ bot.
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from telegram import Update
|
from telegram import Update
|
||||||
from telegram.ext import Updater, CommandHandler, CallbackContext
|
from telegram.ext import CommandHandler, Updater, CallbackContext
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,18 +36,18 @@ logger = logging.getLogger(__name__)
|
||||||
# since context is an unused local variable.
|
# since context is an unused local variable.
|
||||||
# This being an example and not having context present confusing beginners,
|
# This being an example and not having context present confusing beginners,
|
||||||
# we decided to have it present as context.
|
# 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."""
|
"""Sends explanation on how to use the bot."""
|
||||||
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
|
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."""
|
"""Send the alarm message."""
|
||||||
job = context.job
|
job = context.job
|
||||||
context.bot.send_message(job.context, text='Beep!')
|
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."""
|
"""Remove job with given name. Returns whether job was removed."""
|
||||||
current_jobs = context.job_queue.get_jobs_by_name(name)
|
current_jobs = context.job_queue.get_jobs_by_name(name)
|
||||||
if not current_jobs:
|
if not current_jobs:
|
||||||
|
@ -58,7 +57,7 @@ def remove_job_if_exists(name: str, context: CallbackContext) -> bool:
|
||||||
return True
|
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."""
|
"""Add a job to the queue."""
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
try:
|
try:
|
||||||
|
@ -80,7 +79,7 @@ def set_timer(update: Update, context: CallbackContext) -> None:
|
||||||
update.message.reply_text('Usage: /set <seconds>')
|
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."""
|
"""Remove the job if the user changed their mind."""
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
job_removed = remove_job_if_exists(str(chat_id), context)
|
job_removed = remove_job_if_exists(str(chat_id), context)
|
||||||
|
@ -91,7 +90,7 @@ def unset(update: Update, context: CallbackContext) -> None:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run bot."""
|
"""Run bot."""
|
||||||
# Create the Updater and pass it your bot's token.
|
# 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
|
# Get the dispatcher to register handlers
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
@ -5,9 +5,9 @@ pre-commit
|
||||||
# Make sure that the versions specified here match the pre-commit settings!
|
# Make sure that the versions specified here match the pre-commit settings!
|
||||||
black==20.8b1
|
black==20.8b1
|
||||||
flake8==3.9.2
|
flake8==3.9.2
|
||||||
pylint==2.8.3
|
pylint==2.10.2
|
||||||
mypy==0.812
|
mypy==0.910
|
||||||
pyupgrade==2.19.1
|
pyupgrade==2.24.0
|
||||||
|
|
||||||
pytest==6.2.4
|
pytest==6.2.4
|
||||||
|
|
||||||
|
|
|
@ -158,22 +158,16 @@ class Bot(TelegramObject):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
token: str,
|
token: str,
|
||||||
base_url: str = None,
|
base_url: str = 'https://api.telegram.org/bot',
|
||||||
base_file_url: str = None,
|
base_file_url: str = 'https://api.telegram.org/file/bot',
|
||||||
request: 'Request' = None,
|
request: 'Request' = None,
|
||||||
private_key: bytes = None,
|
private_key: bytes = None,
|
||||||
private_key_password: bytes = None,
|
private_key_password: bytes = None,
|
||||||
):
|
):
|
||||||
self.token = self._validate_token(token)
|
self.token = self._validate_token(token)
|
||||||
|
|
||||||
if base_url is None:
|
self.base_url = base_url + self.token
|
||||||
base_url = 'https://api.telegram.org/bot'
|
self.base_file_url = base_file_url + self.token
|
||||||
|
|
||||||
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._bot: Optional[User] = None
|
self._bot: Optional[User] = None
|
||||||
self._request = request or Request()
|
self._request = request or Request()
|
||||||
self.private_key = None
|
self.private_key = None
|
||||||
|
@ -2796,8 +2790,8 @@ class Bot(TelegramObject):
|
||||||
Telegram API.
|
Telegram API.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:class:`telegram.Message`: On success, if the edited message is not an inline message
|
:class:`telegram.Message`: On success, if edited message is not an inline message, the
|
||||||
, the edited Message is returned, otherwise :obj:`True` is returned.
|
edited Message is returned, otherwise :obj:`True` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`telegram.error.TelegramError`
|
:class:`telegram.error.TelegramError`
|
||||||
|
|
|
@ -47,6 +47,7 @@ from .chatmemberhandler import ChatMemberHandler
|
||||||
from .chatjoinrequesthandler import ChatJoinRequestHandler
|
from .chatjoinrequesthandler import ChatJoinRequestHandler
|
||||||
from .defaults import Defaults
|
from .defaults import Defaults
|
||||||
from .callbackdatacache import CallbackDataCache, InvalidCallbackData
|
from .callbackdatacache import CallbackDataCache, InvalidCallbackData
|
||||||
|
from .builders import DispatcherBuilder, UpdaterBuilder
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BaseFilter',
|
'BaseFilter',
|
||||||
|
@ -63,6 +64,7 @@ __all__ = (
|
||||||
'Defaults',
|
'Defaults',
|
||||||
'DictPersistence',
|
'DictPersistence',
|
||||||
'Dispatcher',
|
'Dispatcher',
|
||||||
|
'DispatcherBuilder',
|
||||||
'DispatcherHandlerStop',
|
'DispatcherHandlerStop',
|
||||||
'ExtBot',
|
'ExtBot',
|
||||||
'Filters',
|
'Filters',
|
||||||
|
@ -85,4 +87,5 @@ __all__ = (
|
||||||
'TypeHandler',
|
'TypeHandler',
|
||||||
'UpdateFilter',
|
'UpdateFilter',
|
||||||
'Updater',
|
'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 import Update, CallbackQuery
|
||||||
from telegram.ext import ExtBot
|
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:
|
if TYPE_CHECKING:
|
||||||
from telegram import Bot
|
|
||||||
from telegram.ext import Dispatcher, Job, JobQueue
|
from telegram.ext import Dispatcher, Job, JobQueue
|
||||||
from telegram.ext.utils.types import CCT
|
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`
|
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
|
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__ = (
|
__slots__ = (
|
||||||
'_dispatcher',
|
'_dispatcher',
|
||||||
'_chat_id_and_data',
|
'_chat_id_and_data',
|
||||||
|
@ -107,7 +126,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
||||||
'__dict__',
|
'__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:
|
Args:
|
||||||
dispatcher (:class:`telegram.ext.Dispatcher`):
|
dispatcher (:class:`telegram.ext.Dispatcher`):
|
||||||
|
@ -123,7 +142,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
||||||
self.async_kwargs: Optional[Dict[str, object]] = None
|
self.async_kwargs: Optional[Dict[str, object]] = None
|
||||||
|
|
||||||
@property
|
@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."""
|
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
|
||||||
return self._dispatcher
|
return self._dispatcher
|
||||||
|
|
||||||
|
@ -232,7 +251,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
||||||
cls: Type['CCT'],
|
cls: Type['CCT'],
|
||||||
update: object,
|
update: object,
|
||||||
error: Exception,
|
error: Exception,
|
||||||
dispatcher: 'Dispatcher[CCT, UD, CD, BD]',
|
dispatcher: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]',
|
||||||
async_args: Union[List, Tuple] = None,
|
async_args: Union[List, Tuple] = None,
|
||||||
async_kwargs: Dict[str, object] = None,
|
async_kwargs: Dict[str, object] = None,
|
||||||
job: 'Job' = None,
|
job: 'Job' = None,
|
||||||
|
@ -271,7 +290,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_update(
|
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':
|
) -> 'CCT':
|
||||||
"""
|
"""
|
||||||
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the
|
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
|
return self
|
||||||
|
|
||||||
@classmethod
|
@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
|
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to a
|
||||||
job callback.
|
job callback.
|
||||||
|
@ -335,7 +356,7 @@ class CallbackContext(Generic[UD, CD, BD]):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bot(self) -> 'Bot':
|
def bot(self) -> BT:
|
||||||
""":class:`telegram.Bot`: The bot associated with this context."""
|
""":class:`telegram.Bot`: The bot associated with this context."""
|
||||||
return self._dispatcher.bot
|
return self._dispatcher.bot
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
from typing import Type, Generic, overload, Dict # pylint: disable=unused-import
|
from typing import Type, Generic, overload, Dict # pylint: disable=unused-import
|
||||||
|
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
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
|
from telegram.ext.utils.types import CCT, UD, CD, BD
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
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
|
@overload
|
||||||
def __init__(
|
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
|
@overload
|
||||||
def __init__(
|
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
|
@overload
|
||||||
def __init__(
|
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
|
@overload
|
||||||
def __init__(
|
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],
|
user_data: Type[UD],
|
||||||
chat_data: Type[CD],
|
chat_data: Type[CD],
|
||||||
):
|
):
|
||||||
|
@ -108,7 +112,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
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],
|
user_data: Type[UD],
|
||||||
bot_data: Type[BD],
|
bot_data: Type[BD],
|
||||||
):
|
):
|
||||||
|
@ -116,7 +120,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
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],
|
chat_data: Type[CD],
|
||||||
bot_data: Type[BD],
|
bot_data: Type[BD],
|
||||||
):
|
):
|
||||||
|
@ -151,7 +155,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
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],
|
user_data: Type[UD],
|
||||||
chat_data: Type[CD],
|
chat_data: Type[CD],
|
||||||
bot_data: Type[BD],
|
bot_data: Type[BD],
|
||||||
|
|
|
@ -23,7 +23,18 @@ import logging
|
||||||
import functools
|
import functools
|
||||||
import datetime
|
import datetime
|
||||||
from threading import Lock
|
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 import Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
|
@ -41,7 +52,7 @@ from telegram.ext.utils.types import CCT
|
||||||
from telegram.utils.warnings import warn
|
from telegram.utils.warnings import warn
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from telegram.ext import Dispatcher, Job
|
from telegram.ext import Dispatcher, Job, JobQueue
|
||||||
CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
|
CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +63,7 @@ class _ConversationTimeoutContext:
|
||||||
self,
|
self,
|
||||||
conversation_key: Tuple[int, ...],
|
conversation_key: Tuple[int, ...],
|
||||||
update: Update,
|
update: Update,
|
||||||
dispatcher: 'Dispatcher',
|
dispatcher: 'Dispatcher[Any, CCT, Any, Any, Any, JobQueue, Any]',
|
||||||
callback_context: CallbackContext,
|
callback_context: CallbackContext,
|
||||||
):
|
):
|
||||||
self.conversation_key = conversation_key
|
self.conversation_key = conversation_key
|
||||||
|
@ -489,7 +500,7 @@ class ConversationHandler(Handler[Update, CCT]):
|
||||||
def _schedule_job(
|
def _schedule_job(
|
||||||
self,
|
self,
|
||||||
new_state: object,
|
new_state: object,
|
||||||
dispatcher: 'Dispatcher',
|
dispatcher: 'Dispatcher[Any, CCT, Any, Any, Any, JobQueue, Any]',
|
||||||
update: Update,
|
update: Update,
|
||||||
context: CallbackContext,
|
context: CallbackContext,
|
||||||
conversation_key: Tuple[int, ...],
|
conversation_key: Tuple[int, ...],
|
||||||
|
@ -498,7 +509,7 @@ class ConversationHandler(Handler[Update, CCT]):
|
||||||
try:
|
try:
|
||||||
# both job_queue & conversation_timeout are checked before calling _schedule_job
|
# both job_queue & conversation_timeout are checked before calling _schedule_job
|
||||||
j_queue = dispatcher.job_queue
|
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._trigger_timeout,
|
||||||
self.conversation_timeout, # type: ignore[arg-type]
|
self.conversation_timeout, # type: ignore[arg-type]
|
||||||
context=_ConversationTimeoutContext(
|
context=_ConversationTimeoutContext(
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
# You should have received a copy of the GNU Lesser Public License
|
# You should have received a copy of the GNU Lesser Public License
|
||||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||||
"""This module contains the Dispatcher class."""
|
"""This module contains the Dispatcher class."""
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import weakref
|
import weakref
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
|
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
|
||||||
Callable,
|
Callable,
|
||||||
DefaultDict,
|
DefaultDict,
|
||||||
Dict,
|
Dict,
|
||||||
|
@ -35,8 +35,7 @@ from typing import (
|
||||||
Union,
|
Union,
|
||||||
Generic,
|
Generic,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
overload,
|
TYPE_CHECKING,
|
||||||
cast,
|
|
||||||
)
|
)
|
||||||
from uuid import uuid4
|
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.defaultvalue import DefaultValue, DEFAULT_FALSE
|
||||||
from telegram.utils.warnings import warn
|
from telegram.utils.warnings import warn
|
||||||
from telegram.ext.utils.promise import Promise
|
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:
|
if TYPE_CHECKING:
|
||||||
from telegram import Bot
|
from .jobqueue import Job
|
||||||
from telegram.ext import JobQueue, Job, CallbackContext
|
from .builders import InitDispatcherBuilder
|
||||||
|
|
||||||
DEFAULT_GROUP: int = 0
|
DEFAULT_GROUP: int = 0
|
||||||
|
|
||||||
|
@ -90,24 +90,15 @@ class DispatcherHandlerStop(Exception):
|
||||||
self.state = state
|
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.
|
"""This class dispatches all kinds of updates to its registered handlers.
|
||||||
|
|
||||||
Args:
|
Note:
|
||||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
This class may not be initialized directly. Use :class:`telegram.ext.DispatcherBuilder` or
|
||||||
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
|
:meth:`builder` (for convenience).
|
||||||
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.
|
|
||||||
|
|
||||||
.. versionadded:: 13.6
|
.. versionchanged:: 14.0
|
||||||
|
Initialization is now done through the :class:`telegram.ext.DispatcherBuilder`.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
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.
|
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
|
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||||
store data that should be persistent over restarts.
|
store data that should be persistent over restarts.
|
||||||
context_types (:class:`telegram.ext.ContextTypes`): Container for the types used
|
exception_event (:class:`threading.Event`): When this event is set, the dispatcher will
|
||||||
in the ``context`` interface.
|
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',
|
'error_handlers',
|
||||||
'running',
|
'running',
|
||||||
'__stop_event',
|
'__stop_event',
|
||||||
'__exception_event',
|
'exception_event',
|
||||||
'__async_queue',
|
'__async_queue',
|
||||||
'__async_threads',
|
'__async_threads',
|
||||||
'bot',
|
'bot',
|
||||||
|
@ -156,51 +166,37 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
__singleton = None
|
__singleton = None
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self: 'Dispatcher[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]',
|
self: 'Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]',
|
||||||
bot: 'Bot',
|
*,
|
||||||
|
bot: BT,
|
||||||
update_queue: Queue,
|
update_queue: Queue,
|
||||||
workers: int = 4,
|
job_queue: JQ,
|
||||||
exception_event: Event = None,
|
workers: int,
|
||||||
job_queue: 'JobQueue' = None,
|
persistence: PT,
|
||||||
persistence: BasePersistence = None,
|
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.bot = bot
|
||||||
self.update_queue = update_queue
|
self.update_queue = update_queue
|
||||||
self.job_queue = job_queue
|
self.job_queue = job_queue
|
||||||
self.workers = workers
|
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:
|
if self.workers < 1:
|
||||||
warn(
|
warn(
|
||||||
'Asynchronous callbacks can not be processed without at least one worker thread.',
|
'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)
|
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 persistence:
|
||||||
if not isinstance(persistence, BasePersistence):
|
if not isinstance(persistence, BasePersistence):
|
||||||
raise TypeError("persistence must be based on telegram.ext.BasePersistence")
|
raise TypeError("persistence must be based on telegram.ext.BasePersistence")
|
||||||
|
|
||||||
self.persistence = persistence
|
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)
|
self.persistence.set_bot(self.bot)
|
||||||
|
|
||||||
if self.persistence.store_data.user_data:
|
if self.persistence.store_data.user_data:
|
||||||
self.user_data = self.persistence.get_user_data()
|
self.user_data = self.persistence.get_user_data()
|
||||||
if not isinstance(self.user_data, defaultdict):
|
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__}"
|
f"bot_data must be of type {self.context_types.bot_data.__name__}"
|
||||||
)
|
)
|
||||||
if self.persistence.store_data.callback_data:
|
if self.persistence.store_data.callback_data:
|
||||||
self.bot = cast(ExtBot, self.bot)
|
|
||||||
persistent_data = self.persistence.get_callback_data()
|
persistent_data = self.persistence.get_callback_data()
|
||||||
if persistent_data is not None:
|
if persistent_data is not None:
|
||||||
if not isinstance(persistent_data, tuple) and len(persistent_data) != 2:
|
if not isinstance(persistent_data, tuple) and len(persistent_data) != 2:
|
||||||
raise ValueError('callback_data must be a 2-tuple')
|
raise ValueError('callback_data must be a 2-tuple')
|
||||||
self.bot.callback_data_cache = CallbackDataCache(
|
# Mypy doesn't know that persistence.set_bot (see above) already checks that
|
||||||
self.bot,
|
# self.bot is an instance of ExtBot if callback_data should be stored ...
|
||||||
self.bot.callback_data_cache.maxsize,
|
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,
|
persistent_data=persistent_data,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.persistence = None
|
self.persistence = None
|
||||||
|
|
||||||
self.handlers: Dict[int, List[Handler]] = {}
|
self.handlers: Dict[int, List[Handler]] = {}
|
||||||
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
|
||||||
self.groups: List[int] = []
|
self.groups: List[int] = []
|
||||||
"""List[:obj:`int`]: A list with all groups."""
|
|
||||||
self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {}
|
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
|
self.running = False
|
||||||
""":obj:`bool`: Indicates if this dispatcher is running."""
|
|
||||||
self.__stop_event = Event()
|
self.__stop_event = Event()
|
||||||
self.__exception_event = exception_event or Event()
|
|
||||||
self.__async_queue: Queue = Queue()
|
self.__async_queue: Queue = Queue()
|
||||||
self.__async_threads: Set[Thread] = set()
|
self.__async_threads: Set[Thread] = set()
|
||||||
|
|
||||||
|
@ -265,9 +260,16 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
else:
|
else:
|
||||||
self._set_singleton(None)
|
self._set_singleton(None)
|
||||||
|
|
||||||
@property
|
@staticmethod
|
||||||
def exception_event(self) -> Event: # skipcq: PY-D0003
|
def builder() -> 'InitDispatcherBuilder':
|
||||||
return self.__exception_event
|
"""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:
|
def _init_async_threads(self, base_name: str, workers: int) -> None:
|
||||||
base_name = f'{base_name}_' if base_name else ''
|
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:
|
def start(self, ready: Event = None) -> None:
|
||||||
"""Thread target of thread 'dispatcher'.
|
"""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:
|
Args:
|
||||||
ready (:obj:`threading.Event`, optional): If specified, the event will be set once the
|
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()
|
ready.set()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.__exception_event.is_set():
|
if self.exception_event.is_set():
|
||||||
msg = 'reusing dispatcher after exception event is forbidden'
|
msg = 'reusing dispatcher after exception event is forbidden'
|
||||||
self.logger.error(msg)
|
self.logger.error(msg)
|
||||||
raise TelegramError(msg)
|
raise TelegramError(msg)
|
||||||
|
|
||||||
|
if self.job_queue:
|
||||||
|
self.job_queue.start()
|
||||||
self._init_async_threads(str(uuid4()), self.workers)
|
self._init_async_threads(str(uuid4()), self.workers)
|
||||||
self.running = True
|
self.running = True
|
||||||
self.logger.debug('Dispatcher started')
|
self.logger.debug('Dispatcher started')
|
||||||
|
@ -401,7 +405,7 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
if self.__stop_event.is_set():
|
if self.__stop_event.is_set():
|
||||||
self.logger.debug('orderly stopping')
|
self.logger.debug('orderly stopping')
|
||||||
break
|
break
|
||||||
if self.__exception_event.is_set():
|
if self.exception_event.is_set():
|
||||||
self.logger.critical('stopping due to exception in another thread')
|
self.logger.critical('stopping due to exception in another thread')
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
|
@ -414,7 +418,10 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
self.logger.debug('Dispatcher thread stopped')
|
self.logger.debug('Dispatcher thread stopped')
|
||||||
|
|
||||||
def stop(self) -> None:
|
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:
|
if self.running:
|
||||||
self.__stop_event.set()
|
self.__stop_event.set()
|
||||||
while self.running:
|
while self.running:
|
||||||
|
@ -436,6 +443,17 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
self.__async_threads.remove(thr)
|
self.__async_threads.remove(thr)
|
||||||
self.logger.debug('async thread %s/%s has ended', i + 1, total)
|
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
|
@property
|
||||||
def has_running_threads(self) -> bool: # skipcq: PY-D0003
|
def has_running_threads(self) -> bool: # skipcq: PY-D0003
|
||||||
return self.running or bool(self.__async_threads)
|
return self.running or bool(self.__async_threads)
|
||||||
|
@ -602,10 +620,11 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
user_ids = []
|
user_ids = []
|
||||||
|
|
||||||
if self.persistence.store_data.callback_data:
|
if self.persistence.store_data.callback_data:
|
||||||
self.bot = cast(ExtBot, self.bot)
|
|
||||||
try:
|
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.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:
|
except Exception as exc:
|
||||||
self.dispatch_error(update, exc)
|
self.dispatch_error(update, exc)
|
||||||
|
@ -641,10 +660,7 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
callback (:obj:`callable`): The callback function for this error handler. Will be
|
callback (:obj:`callable`): The callback function for this error handler. Will be
|
||||||
called when an error is raised.
|
called when an error is raised. Callback signature:
|
||||||
Callback signature:
|
|
||||||
|
|
||||||
|
|
||||||
``def callback(update: Update, context: CallbackContext)``
|
``def callback(update: Update, context: CallbackContext)``
|
||||||
|
|
||||||
The error that happened will be present in context.error.
|
The error that happened will be present in context.error.
|
||||||
|
|
|
@ -94,8 +94,8 @@ class ExtBot(telegram.bot.Bot):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
token: str,
|
token: str,
|
||||||
base_url: str = None,
|
base_url: str = 'https://api.telegram.org/bot',
|
||||||
base_file_url: str = None,
|
base_file_url: str = 'https://api.telegram.org/file/bot',
|
||||||
request: 'Request' = None,
|
request: 'Request' = None,
|
||||||
private_key: bytes = None,
|
private_key: bytes = None,
|
||||||
private_key_password: bytes = None,
|
private_key_password: bytes = None,
|
||||||
|
|
|
@ -1166,23 +1166,23 @@ officedocument.wordprocessingml.document")``.
|
||||||
|
|
||||||
name = 'Filters.status_update'
|
name = 'Filters.status_update'
|
||||||
|
|
||||||
def filter(self, message: Update) -> bool:
|
def filter(self, update: Update) -> bool:
|
||||||
return bool(
|
return bool(
|
||||||
self.new_chat_members(message)
|
self.new_chat_members(update)
|
||||||
or self.left_chat_member(message)
|
or self.left_chat_member(update)
|
||||||
or self.new_chat_title(message)
|
or self.new_chat_title(update)
|
||||||
or self.new_chat_photo(message)
|
or self.new_chat_photo(update)
|
||||||
or self.delete_chat_photo(message)
|
or self.delete_chat_photo(update)
|
||||||
or self.chat_created(message)
|
or self.chat_created(update)
|
||||||
or self.message_auto_delete_timer_changed(message)
|
or self.message_auto_delete_timer_changed(update)
|
||||||
or self.migrate(message)
|
or self.migrate(update)
|
||||||
or self.pinned_message(message)
|
or self.pinned_message(update)
|
||||||
or self.connected_website(message)
|
or self.connected_website(update)
|
||||||
or self.proximity_alert_triggered(message)
|
or self.proximity_alert_triggered(update)
|
||||||
or self.voice_chat_scheduled(message)
|
or self.voice_chat_scheduled(update)
|
||||||
or self.voice_chat_started(message)
|
or self.voice_chat_started(update)
|
||||||
or self.voice_chat_ended(message)
|
or self.voice_chat_ended(update)
|
||||||
or self.voice_chat_participants_invited(message)
|
or self.voice_chat_participants_invited(update)
|
||||||
)
|
)
|
||||||
|
|
||||||
status_update = _StatusUpdate()
|
status_update = _StatusUpdate()
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"""This module contains the classes JobQueue and Job."""
|
"""This module contains the classes JobQueue and Job."""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import weakref
|
||||||
from typing import TYPE_CHECKING, Callable, Optional, Tuple, Union, cast, overload
|
from typing import TYPE_CHECKING, Callable, Optional, Tuple, Union, cast, overload
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
@ -45,7 +46,7 @@ class JobQueue:
|
||||||
__slots__ = ('_dispatcher', 'scheduler')
|
__slots__ = ('_dispatcher', 'scheduler')
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
|
self._dispatcher: 'Optional[weakref.ReferenceType[Dispatcher]]' = None
|
||||||
self.scheduler = BackgroundScheduler(timezone=pytz.utc)
|
self.scheduler = BackgroundScheduler(timezone=pytz.utc)
|
||||||
|
|
||||||
def _tz_now(self) -> datetime.datetime:
|
def _tz_now(self) -> datetime.datetime:
|
||||||
|
@ -93,10 +94,20 @@ class JobQueue:
|
||||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
|
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._dispatcher = dispatcher
|
self._dispatcher = weakref.ref(dispatcher)
|
||||||
if isinstance(dispatcher.bot, ExtBot) and dispatcher.bot.defaults:
|
if isinstance(dispatcher.bot, ExtBot) and dispatcher.bot.defaults:
|
||||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
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(
|
def run_once(
|
||||||
self,
|
self,
|
||||||
callback: Callable[['CallbackContext'], None],
|
callback: Callable[['CallbackContext'], None],
|
||||||
|
@ -151,7 +162,7 @@ class JobQueue:
|
||||||
name=name,
|
name=name,
|
||||||
trigger='date',
|
trigger='date',
|
||||||
run_date=date_time,
|
run_date=date_time,
|
||||||
args=(self._dispatcher,),
|
args=(self.dispatcher,),
|
||||||
timezone=date_time.tzinfo or self.scheduler.timezone,
|
timezone=date_time.tzinfo or self.scheduler.timezone,
|
||||||
**job_kwargs,
|
**job_kwargs,
|
||||||
)
|
)
|
||||||
|
@ -241,7 +252,7 @@ class JobQueue:
|
||||||
j = self.scheduler.add_job(
|
j = self.scheduler.add_job(
|
||||||
job,
|
job,
|
||||||
trigger='interval',
|
trigger='interval',
|
||||||
args=(self._dispatcher,),
|
args=(self.dispatcher,),
|
||||||
start_date=dt_first,
|
start_date=dt_first,
|
||||||
end_date=dt_last,
|
end_date=dt_last,
|
||||||
seconds=interval,
|
seconds=interval,
|
||||||
|
@ -297,7 +308,7 @@ class JobQueue:
|
||||||
j = self.scheduler.add_job(
|
j = self.scheduler.add_job(
|
||||||
job,
|
job,
|
||||||
trigger='cron',
|
trigger='cron',
|
||||||
args=(self._dispatcher,),
|
args=(self.dispatcher,),
|
||||||
name=name,
|
name=name,
|
||||||
day='last' if day == -1 else day,
|
day='last' if day == -1 else day,
|
||||||
hour=when.hour,
|
hour=when.hour,
|
||||||
|
@ -354,7 +365,7 @@ class JobQueue:
|
||||||
j = self.scheduler.add_job(
|
j = self.scheduler.add_job(
|
||||||
job,
|
job,
|
||||||
name=name,
|
name=name,
|
||||||
args=(self._dispatcher,),
|
args=(self.dispatcher,),
|
||||||
trigger='cron',
|
trigger='cron',
|
||||||
day_of_week=','.join([str(d) for d in days]),
|
day_of_week=','.join([str(d) for d in days]),
|
||||||
hour=time.hour,
|
hour=time.hour,
|
||||||
|
@ -394,7 +405,7 @@ class JobQueue:
|
||||||
name = name or callback.__name__
|
name = name or callback.__name__
|
||||||
job = Job(callback, context, 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
|
job.job = j
|
||||||
return job
|
return job
|
||||||
|
|
|
@ -17,42 +17,42 @@
|
||||||
# You should have received a copy of the GNU Lesser Public License
|
# You should have received a copy of the GNU Lesser Public License
|
||||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
# 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."""
|
"""This module contains the class Updater, which tries to make creating Telegram bots intuitive."""
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import ssl
|
import ssl
|
||||||
import signal
|
import signal
|
||||||
|
from pathlib import Path
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Event, Lock, Thread, current_thread
|
from threading import Event, Lock, Thread, current_thread
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Dict,
|
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
no_type_check,
|
no_type_check,
|
||||||
Generic,
|
Generic,
|
||||||
overload,
|
TypeVar,
|
||||||
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
|
||||||
from telegram import Bot
|
|
||||||
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized, TelegramError
|
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized, TelegramError
|
||||||
from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot
|
from telegram.ext import Dispatcher
|
||||||
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.utils.webhookhandler import WebhookAppClass, WebhookServer
|
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:
|
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
|
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
|
: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.
|
WebhookHandler classes.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
* You must supply either a :attr:`bot` or a :attr:`token` argument.
|
This class may not be initialized directly. Use :class:`telegram.ext.UpdaterBuilder` or
|
||||||
* If you supply a :attr:`bot`, you will need to pass :attr:`arbitrary_callback_data`,
|
:meth:`builder` (for convenience).
|
||||||
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.
|
|
||||||
|
|
||||||
|
.. 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:
|
Attributes:
|
||||||
bot (:class:`telegram.Bot`): The bot used with this Updater.
|
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.
|
received.
|
||||||
|
|
||||||
|
.. versionchanged:: 14.0
|
||||||
|
Renamed ``user_sig_handler`` to ``user_signal_handler``.
|
||||||
update_queue (:obj:`Queue`): Queue for the updates.
|
update_queue (:obj:`Queue`): Queue for the updates.
|
||||||
job_queue (:class:`telegram.ext.JobQueue`): Jobqueue for the updater.
|
dispatcher (:class:`telegram.ext.Dispatcher`): Optional. Dispatcher that handles the
|
||||||
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and
|
updates and dispatches them to the handlers.
|
||||||
dispatches them to the handlers.
|
|
||||||
running (:obj:`bool`): Indicates if the updater is running.
|
running (:obj:`bool`): Indicates if the updater is running.
|
||||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
exception_event (:class:`threading.Event`): When an unhandled exception happens while
|
||||||
store data that should be persistent over restarts.
|
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__ = (
|
__slots__ = (
|
||||||
'persistence',
|
|
||||||
'dispatcher',
|
'dispatcher',
|
||||||
'user_sig_handler',
|
'user_signal_handler',
|
||||||
'bot',
|
'bot',
|
||||||
'logger',
|
'logger',
|
||||||
'update_queue',
|
'update_queue',
|
||||||
'job_queue',
|
'exception_event',
|
||||||
'__exception_event',
|
|
||||||
'last_update_id',
|
'last_update_id',
|
||||||
'running',
|
'running',
|
||||||
'_request',
|
|
||||||
'is_idle',
|
'is_idle',
|
||||||
'httpd',
|
'httpd',
|
||||||
'__lock',
|
'__lock',
|
||||||
'__threads',
|
'__threads',
|
||||||
)
|
)
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self: 'Updater[CallbackContext, dict, dict, dict]',
|
self: 'Updater[BT, DT]',
|
||||||
token: str = None,
|
*,
|
||||||
base_url: str = None,
|
user_signal_handler: Callable[[int, object], Any] = None,
|
||||||
workers: int = 4,
|
dispatcher: DT = None,
|
||||||
bot: Bot = None,
|
bot: BT = None,
|
||||||
private_key: bytes = None,
|
update_queue: Queue = None,
|
||||||
private_key_password: bytes = None,
|
exception_event: Event = 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,
|
|
||||||
):
|
):
|
||||||
...
|
if not was_called_by(
|
||||||
|
inspect.currentframe(), Path(__file__).parent.resolve() / 'builders.py'
|
||||||
@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:
|
|
||||||
warn(
|
warn(
|
||||||
'Passing defaults to an Updater has no effect when a Bot is passed '
|
'`Updater` instances should be built via the `UpdaterBuilder`.',
|
||||||
'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.',
|
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
if dispatcher is None:
|
self.user_signal_handler = user_signal_handler
|
||||||
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')
|
|
||||||
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.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.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:
|
||||||
|
self.bot = bot
|
||||||
|
self.update_queue = update_queue
|
||||||
|
self.exception_event = exception_event
|
||||||
|
|
||||||
self.user_sig_handler = user_sig_handler
|
|
||||||
self.last_update_id = 0
|
self.last_update_id = 0
|
||||||
self.running = False
|
self.running = False
|
||||||
self.is_idle = False
|
self.is_idle = False
|
||||||
self.httpd = None
|
self.httpd = None
|
||||||
self.__lock = Lock()
|
self.__lock = Lock()
|
||||||
self.__threads: List[Thread] = []
|
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:
|
def _init_thread(self, target: Callable, name: str, *args: object, **kwargs: object) -> None:
|
||||||
thr = Thread(
|
thr = Thread(
|
||||||
|
@ -335,7 +170,7 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
try:
|
try:
|
||||||
target(*args, **kwargs)
|
target(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.__exception_event.set()
|
self.exception_event.set()
|
||||||
self.logger.exception('unhandled exception in %s', thr_name)
|
self.logger.exception('unhandled exception in %s', thr_name)
|
||||||
raise
|
raise
|
||||||
self.logger.debug('%s - ended', thr_name)
|
self.logger.debug('%s - ended', thr_name)
|
||||||
|
@ -384,9 +219,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
self.running = True
|
self.running = True
|
||||||
|
|
||||||
# Create & start threads
|
# Create & start threads
|
||||||
self.job_queue.start()
|
|
||||||
dispatcher_ready = Event()
|
dispatcher_ready = Event()
|
||||||
polling_ready = Event()
|
polling_ready = Event()
|
||||||
|
|
||||||
|
if self.dispatcher:
|
||||||
self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
|
self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
|
||||||
self._init_thread(
|
self._init_thread(
|
||||||
self._start_polling,
|
self._start_polling,
|
||||||
|
@ -400,9 +236,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
ready=polling_ready,
|
ready=polling_ready,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug('Waiting for Dispatcher and polling to start')
|
self.logger.debug('Waiting for polling to start')
|
||||||
dispatcher_ready.wait()
|
|
||||||
polling_ready.wait()
|
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 the update queue so the main thread can insert updates
|
||||||
return self.update_queue
|
return self.update_queue
|
||||||
|
@ -478,7 +316,8 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
# Create & start threads
|
# Create & start threads
|
||||||
webhook_ready = Event()
|
webhook_ready = Event()
|
||||||
dispatcher_ready = Event()
|
dispatcher_ready = Event()
|
||||||
self.job_queue.start()
|
|
||||||
|
if self.dispatcher:
|
||||||
self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready)
|
self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready)
|
||||||
self._init_thread(
|
self._init_thread(
|
||||||
self._start_webhook,
|
self._start_webhook,
|
||||||
|
@ -497,8 +336,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
max_connections=max_connections,
|
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()
|
webhook_ready.wait()
|
||||||
|
if self.dispatcher:
|
||||||
|
self.logger.debug('Waiting for Dispatcher to start')
|
||||||
dispatcher_ready.wait()
|
dispatcher_ready.wait()
|
||||||
|
|
||||||
# Return the update queue so the main thread can insert updates
|
# Return the update queue so the main thread can insert updates
|
||||||
|
@ -661,18 +502,26 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
||||||
|
|
||||||
# We pass along the cert to the webhook if present.
|
# We pass along the cert to the webhook if present.
|
||||||
cert_file = open(cert, 'rb') if cert is not None else None
|
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(
|
self._bootstrap(
|
||||||
max_retries=bootstrap_retries,
|
max_retries=bootstrap_retries,
|
||||||
drop_pending_updates=drop_pending_updates,
|
drop_pending_updates=drop_pending_updates,
|
||||||
webhook_url=webhook_url,
|
webhook_url=webhook_url,
|
||||||
allowed_updates=allowed_updates,
|
allowed_updates=allowed_updates,
|
||||||
cert=cert_file,
|
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
max_connections=max_connections,
|
max_connections=max_connections,
|
||||||
)
|
)
|
||||||
if cert_file is not None:
|
|
||||||
cert_file.close()
|
|
||||||
|
|
||||||
self.httpd.serve_forever(ready=ready)
|
self.httpd.serve_forever(ready=ready)
|
||||||
|
|
||||||
|
@ -750,10 +599,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stops the polling/webhook thread, the dispatcher and the job queue."""
|
"""Stops the polling/webhook thread, the dispatcher and the job queue."""
|
||||||
self.job_queue.stop()
|
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
if self.running or self.dispatcher.has_running_threads:
|
if self.running or (self.dispatcher and self.dispatcher.has_running_threads):
|
||||||
self.logger.debug('Stopping Updater and Dispatcher...')
|
self.logger.debug(
|
||||||
|
'Stopping Updater %s...', 'and Dispatcher ' if self.dispatcher else ''
|
||||||
|
)
|
||||||
|
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
|
@ -761,9 +611,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
self._stop_dispatcher()
|
self._stop_dispatcher()
|
||||||
self._join_threads()
|
self._join_threads()
|
||||||
|
|
||||||
# Stop the Request instance only if it was created by the Updater
|
# Clear the connection pool only if the bot is managed by the Updater
|
||||||
if self._request:
|
# Otherwise `dispatcher.stop()` already does that
|
||||||
self._request.stop()
|
if not self.dispatcher:
|
||||||
|
self.bot.request.stop()
|
||||||
|
|
||||||
@no_type_check
|
@no_type_check
|
||||||
def _stop_httpd(self) -> None:
|
def _stop_httpd(self) -> None:
|
||||||
|
@ -778,6 +629,7 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
|
|
||||||
@no_type_check
|
@no_type_check
|
||||||
def _stop_dispatcher(self) -> None:
|
def _stop_dispatcher(self) -> None:
|
||||||
|
if self.dispatcher:
|
||||||
self.logger.debug('Requesting Dispatcher to stop...')
|
self.logger.debug('Requesting Dispatcher to stop...')
|
||||||
self.dispatcher.stop()
|
self.dispatcher.stop()
|
||||||
|
|
||||||
|
@ -801,13 +653,9 @@ class Updater(Generic[CCT, UD, CD, BD]):
|
||||||
# https://bugs.python.org/issue28206
|
# https://bugs.python.org/issue28206
|
||||||
signal.Signals(signum), # pylint: disable=no-member
|
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()
|
self.stop()
|
||||||
if self.user_sig_handler:
|
if self.user_signal_handler:
|
||||||
self.user_sig_handler(signum, frame)
|
self.user_signal_handler(signum, frame)
|
||||||
else:
|
else:
|
||||||
self.logger.warning('Exiting immediately!')
|
self.logger.warning('Exiting immediately!')
|
||||||
# pylint: disable=import-outside-toplevel, protected-access
|
# 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
|
user. Changes to this module are not considered breaking changes and may not be documented in
|
||||||
the changelog.
|
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:
|
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]]
|
ConversationDict = Dict[Tuple[int, ...], Optional[object]]
|
||||||
|
@ -50,6 +51,11 @@ CCT = TypeVar('CCT', bound='CallbackContext')
|
||||||
|
|
||||||
.. versionadded:: 13.6
|
.. versionadded:: 13.6
|
||||||
"""
|
"""
|
||||||
|
BT = TypeVar('BT', bound='Bot')
|
||||||
|
"""Type of the bot.
|
||||||
|
|
||||||
|
.. versionadded:: 14.0
|
||||||
|
"""
|
||||||
UD = TypeVar('UD')
|
UD = TypeVar('UD')
|
||||||
"""Type of the user data for a single user.
|
"""Type of the user data for a single user.
|
||||||
|
|
||||||
|
@ -65,3 +71,11 @@ BD = TypeVar('BD')
|
||||||
|
|
||||||
.. versionadded:: 13.6
|
.. 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
|
import certifi
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import telegram.vendor.ptb_urllib3.urllib3 as urllib3
|
from telegram.vendor.ptb_urllib3 import urllib3
|
||||||
import telegram.vendor.ptb_urllib3.urllib3.contrib.appengine as appengine
|
from telegram.vendor.ptb_urllib3.urllib3.contrib import appengine
|
||||||
from telegram.vendor.ptb_urllib3.urllib3.connection import HTTPConnection
|
from telegram.vendor.ptb_urllib3.urllib3.connection import HTTPConnection
|
||||||
from telegram.vendor.ptb_urllib3.urllib3.fields import RequestField
|
from telegram.vendor.ptb_urllib3.urllib3.fields import RequestField
|
||||||
from telegram.vendor.ptb_urllib3.urllib3.util.timeout import Timeout
|
from telegram.vendor.ptb_urllib3.urllib3.util.timeout import Timeout
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
try:
|
try:
|
||||||
import urllib3 # type: ignore[no-redef]
|
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.connection import HTTPConnection # type: ignore[no-redef]
|
||||||
from urllib3.fields import RequestField # type: ignore[no-redef]
|
from urllib3.fields import RequestField # type: ignore[no-redef]
|
||||||
from urllib3.util.timeout import Timeout # 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 (
|
from telegram.ext import (
|
||||||
Dispatcher,
|
Dispatcher,
|
||||||
JobQueue,
|
|
||||||
Updater,
|
|
||||||
MessageFilter,
|
MessageFilter,
|
||||||
Defaults,
|
Defaults,
|
||||||
UpdateFilter,
|
UpdateFilter,
|
||||||
ExtBot,
|
ExtBot,
|
||||||
|
DispatcherBuilder,
|
||||||
|
UpdaterBuilder,
|
||||||
)
|
)
|
||||||
from telegram.error import BadRequest
|
from telegram.error import BadRequest
|
||||||
from telegram.utils.defaultvalue import DefaultValue, DEFAULT_NONE
|
from telegram.utils.defaultvalue import DefaultValue, DEFAULT_NONE
|
||||||
|
@ -173,8 +173,7 @@ def provider_token(bot_info):
|
||||||
def create_dp(bot):
|
def create_dp(bot):
|
||||||
# Dispatcher is heavy to init (due to many threads and such) so we have a single session
|
# 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)
|
# scoped one here, but before each test, reset it (dp fixture below)
|
||||||
dispatcher = DictDispatcher(bot, Queue(), job_queue=JobQueue(), workers=2)
|
dispatcher = DispatcherBuilder().bot(bot).workers(2).dispatcher_class(DictDispatcher).build()
|
||||||
dispatcher.job_queue.set_dispatcher(dispatcher)
|
|
||||||
thr = Thread(target=dispatcher.start)
|
thr = Thread(target=dispatcher.start)
|
||||||
thr.start()
|
thr.start()
|
||||||
sleep(2)
|
sleep(2)
|
||||||
|
@ -202,7 +201,7 @@ def dp(_dp):
|
||||||
_dp.handlers = {}
|
_dp.handlers = {}
|
||||||
_dp.groups = []
|
_dp.groups = []
|
||||||
_dp.error_handlers = {}
|
_dp.error_handlers = {}
|
||||||
_dp.__exception_event = Event()
|
_dp.exception_event = Event()
|
||||||
_dp.__stop_event = Event()
|
_dp.__stop_event = Event()
|
||||||
_dp.__async_queue = Queue()
|
_dp.__async_queue = Queue()
|
||||||
_dp.__async_threads = set()
|
_dp.__async_threads = set()
|
||||||
|
@ -212,7 +211,7 @@ def dp(_dp):
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def updater(bot):
|
def updater(bot):
|
||||||
up = Updater(bot=bot, workers=2)
|
up = UpdaterBuilder().bot(bot).workers(2).build()
|
||||||
yield up
|
yield up
|
||||||
if up.running:
|
if up.running:
|
||||||
up.stop()
|
up.stop()
|
||||||
|
|
|
@ -184,7 +184,7 @@ class TestBot:
|
||||||
|
|
||||||
@flaky(3, 1)
|
@flaky(3, 1)
|
||||||
def test_invalid_token_server_response(self, monkeypatch):
|
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')
|
bot = Bot('12')
|
||||||
with pytest.raises(InvalidToken):
|
with pytest.raises(InvalidToken):
|
||||||
bot.get_me()
|
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,
|
JobQueue,
|
||||||
BasePersistence,
|
BasePersistence,
|
||||||
ContextTypes,
|
ContextTypes,
|
||||||
|
DispatcherBuilder,
|
||||||
|
UpdaterBuilder,
|
||||||
)
|
)
|
||||||
from telegram.ext import PersistenceInput
|
from telegram.ext import PersistenceInput
|
||||||
from telegram.ext.dispatcher import Dispatcher, DispatcherHandlerStop
|
from telegram.ext.dispatcher import Dispatcher, DispatcherHandlerStop
|
||||||
|
@ -98,14 +100,36 @@ class TestDispatcher:
|
||||||
self.received = context.error.message
|
self.received = context.error.message
|
||||||
|
|
||||||
def test_slot_behaviour(self, bot, mro_slots):
|
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__:
|
for at in dp.__slots__:
|
||||||
at = f"_Dispatcher{at}" if at.startswith('__') and not at.endswith('__') else at
|
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 getattr(dp, at, 'err') != 'err', f"got extra slot '{at}'"
|
||||||
assert len(mro_slots(dp)) == len(set(mro_slots(dp))), "duplicate slot"
|
assert len(mro_slots(dp)) == len(set(mro_slots(dp))), "duplicate slot"
|
||||||
|
|
||||||
def test_less_than_one_worker_warning(self, dp, recwarn):
|
def test_manual_init_warning(self, recwarn):
|
||||||
Dispatcher(dp.bot, dp.update_queue, job_queue=dp.job_queue, workers=0)
|
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 len(recwarn) == 1
|
||||||
assert (
|
assert (
|
||||||
str(recwarn[0].message)
|
str(recwarn[0].message)
|
||||||
|
@ -113,6 +137,18 @@ class TestDispatcher:
|
||||||
)
|
)
|
||||||
assert recwarn[0].filename == __file__, "stacklevel is incorrect!"
|
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 test_one_context_per_update(self, dp):
|
||||||
def one(update, context):
|
def one(update, context):
|
||||||
if update.message.text == 'test':
|
if update.message.text == 'test':
|
||||||
|
@ -163,7 +199,7 @@ class TestDispatcher:
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
TypeError, match='persistence must be based on telegram.ext.BasePersistence'
|
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):
|
def test_error_handler_that_raises_errors(self, dp):
|
||||||
"""
|
"""
|
||||||
|
@ -580,7 +616,7 @@ class TestDispatcher:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
my_persistence = OwnPersistence()
|
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_handler(CommandHandler('start', start1))
|
||||||
dp.add_error_handler(error)
|
dp.add_error_handler(error)
|
||||||
dp.process_update(update)
|
dp.process_update(update)
|
||||||
|
@ -885,7 +921,7 @@ class TestDispatcher:
|
||||||
bot_data=complex,
|
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.user_data[1], int)
|
||||||
assert isinstance(dispatcher.chat_data[1], float)
|
assert isinstance(dispatcher.chat_data[1], float)
|
||||||
|
@ -900,12 +936,15 @@ class TestDispatcher:
|
||||||
type(context.bot_data),
|
type(context.bot_data),
|
||||||
)
|
)
|
||||||
|
|
||||||
dispatcher = Dispatcher(
|
dispatcher = (
|
||||||
bot,
|
DispatcherBuilder()
|
||||||
Queue(),
|
.bot(bot)
|
||||||
context_types=ContextTypes(
|
.context_types(
|
||||||
|
ContextTypes(
|
||||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||||
),
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
)
|
)
|
||||||
dispatcher.add_error_handler(error_handler)
|
dispatcher.add_error_handler(error_handler)
|
||||||
dispatcher.add_handler(MessageHandler(Filters.all, self.callback_raise_error))
|
dispatcher.add_handler(MessageHandler(Filters.all, self.callback_raise_error))
|
||||||
|
@ -923,12 +962,15 @@ class TestDispatcher:
|
||||||
type(context.bot_data),
|
type(context.bot_data),
|
||||||
)
|
)
|
||||||
|
|
||||||
dispatcher = Dispatcher(
|
dispatcher = (
|
||||||
bot,
|
DispatcherBuilder()
|
||||||
Queue(),
|
.bot(bot)
|
||||||
context_types=ContextTypes(
|
.context_types(
|
||||||
|
ContextTypes(
|
||||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||||
),
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
)
|
)
|
||||||
dispatcher.add_handler(MessageHandler(Filters.all, callback))
|
dispatcher.add_handler(MessageHandler(Filters.all, callback))
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,13 @@ import pytest
|
||||||
import pytz
|
import pytz
|
||||||
from apscheduler.schedulers import SchedulerNotRunningError
|
from apscheduler.schedulers import SchedulerNotRunningError
|
||||||
from flaky import flaky
|
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):
|
class CustomContext(CallbackContext):
|
||||||
|
@ -55,11 +61,6 @@ class TestJobQueue:
|
||||||
job_time = 0
|
job_time = 0
|
||||||
received_error = None
|
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)
|
@pytest.fixture(autouse=True)
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.result = 0
|
self.result = 0
|
||||||
|
@ -100,6 +101,22 @@ class TestJobQueue:
|
||||||
def error_handler_raise_error(self, *args):
|
def error_handler_raise_error(self, *args):
|
||||||
raise Exception('Failing bigly')
|
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):
|
def test_run_once(self, job_queue):
|
||||||
job_queue.run_once(self.job_run_once, 0.01)
|
job_queue.run_once(self.job_run_once, 0.01)
|
||||||
sleep(0.02)
|
sleep(0.02)
|
||||||
|
@ -228,19 +245,19 @@ class TestJobQueue:
|
||||||
sleep(0.03)
|
sleep(0.03)
|
||||||
assert self.result == 1
|
assert self.result == 1
|
||||||
|
|
||||||
def test_in_updater(self, bot):
|
def test_in_dispatcher(self, bot):
|
||||||
u = Updater(bot=bot)
|
dispatcher = DispatcherBuilder().bot(bot).build()
|
||||||
u.job_queue.start()
|
dispatcher.job_queue.start()
|
||||||
try:
|
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)
|
sleep(0.03)
|
||||||
assert self.result == 1
|
assert self.result == 1
|
||||||
u.stop()
|
dispatcher.stop()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
assert self.result == 1
|
assert self.result == 1
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
u.stop()
|
dispatcher.stop()
|
||||||
except SchedulerNotRunningError:
|
except SchedulerNotRunningError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -479,12 +496,15 @@ class TestJobQueue:
|
||||||
assert 'No error handlers are registered' in rec.getMessage()
|
assert 'No error handlers are registered' in rec.getMessage()
|
||||||
|
|
||||||
def test_custom_context(self, bot, job_queue):
|
def test_custom_context(self, bot, job_queue):
|
||||||
dispatcher = Dispatcher(
|
dispatcher = (
|
||||||
bot,
|
DispatcherBuilder()
|
||||||
Queue(),
|
.bot(bot)
|
||||||
context_types=ContextTypes(
|
.context_types(
|
||||||
|
ContextTypes(
|
||||||
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
context=CustomContext, bot_data=int, user_data=float, chat_data=complex
|
||||||
),
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
)
|
)
|
||||||
job_queue.set_dispatcher(dispatcher)
|
job_queue.set_dispatcher(dispatcher)
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
from telegram.ext import PersistenceInput
|
from telegram.ext import PersistenceInput, UpdaterBuilder
|
||||||
from telegram.ext.callbackdatacache import CallbackDataCache
|
from telegram.ext.callbackdatacache import CallbackDataCache
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -41,7 +41,6 @@ import pytest
|
||||||
from telegram import Update, Message, User, Chat, MessageEntity, Bot
|
from telegram import Update, Message, User, Chat, MessageEntity, Bot
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
BasePersistence,
|
BasePersistence,
|
||||||
Updater,
|
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
Filters,
|
Filters,
|
||||||
|
@ -215,7 +214,7 @@ def conversations():
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def updater(bot, base_persistence):
|
def updater(bot, base_persistence):
|
||||||
base_persistence.store_data = PersistenceInput(False, False, False, False)
|
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()
|
base_persistence.store_data = PersistenceInput()
|
||||||
return u
|
return u
|
||||||
|
|
||||||
|
@ -304,34 +303,36 @@ class TestBasePersistence:
|
||||||
base_persistence.get_callback_data = get_callback_data
|
base_persistence.get_callback_data = get_callback_data
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="user_data must be of type defaultdict"):
|
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():
|
def get_user_data():
|
||||||
return user_data
|
return user_data
|
||||||
|
|
||||||
base_persistence.get_user_data = get_user_data
|
base_persistence.get_user_data = get_user_data
|
||||||
with pytest.raises(ValueError, match="chat_data must be of type defaultdict"):
|
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():
|
def get_chat_data():
|
||||||
return chat_data
|
return chat_data
|
||||||
|
|
||||||
base_persistence.get_chat_data = get_chat_data
|
base_persistence.get_chat_data = get_chat_data
|
||||||
with pytest.raises(ValueError, match="bot_data must be of type dict"):
|
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():
|
def get_bot_data():
|
||||||
return bot_data
|
return bot_data
|
||||||
|
|
||||||
base_persistence.get_bot_data = get_bot_data
|
base_persistence.get_bot_data = get_bot_data
|
||||||
with pytest.raises(ValueError, match="callback_data must be a 2-tuple"):
|
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():
|
def get_callback_data():
|
||||||
return callback_data
|
return callback_data
|
||||||
|
|
||||||
|
base_persistence.bot = None
|
||||||
base_persistence.get_callback_data = get_callback_data
|
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.bot_data == bot_data
|
||||||
assert u.dispatcher.chat_data == chat_data
|
assert u.dispatcher.chat_data == chat_data
|
||||||
assert u.dispatcher.user_data == user_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_bot_data = lambda x: x
|
||||||
base_persistence.refresh_chat_data = lambda x, y: x
|
base_persistence.refresh_chat_data = lambda x, y: x
|
||||||
base_persistence.refresh_user_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
|
dp = updater.dispatcher
|
||||||
|
|
||||||
def callback_known_user(update, context):
|
def callback_known_user(update, context):
|
||||||
|
@ -1622,7 +1623,7 @@ class TestPicklePersistence:
|
||||||
assert conversations_test['name1'] == conversation1
|
assert conversations_test['name1'] == conversation1
|
||||||
|
|
||||||
def test_with_handler(self, bot, update, bot_data, pickle_persistence, good_pickle_files):
|
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
|
dp = u.dispatcher
|
||||||
bot.callback_data_cache.clear_callback_data()
|
bot.callback_data_cache.clear_callback_data()
|
||||||
bot.callback_data_cache.clear_callback_queries()
|
bot.callback_data_cache.clear_callback_queries()
|
||||||
|
@ -1660,13 +1661,13 @@ class TestPicklePersistence:
|
||||||
single_file=False,
|
single_file=False,
|
||||||
on_flush=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 = u.dispatcher
|
||||||
dp.add_handler(h2)
|
dp.add_handler(h2)
|
||||||
dp.process_update(update)
|
dp.process_update(update)
|
||||||
|
|
||||||
def test_flush_on_stop(self, bot, update, pickle_persistence):
|
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
|
dp = u.dispatcher
|
||||||
u.running = True
|
u.running = True
|
||||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||||
|
@ -1686,7 +1687,7 @@ class TestPicklePersistence:
|
||||||
assert data['test'] == 'Working4!'
|
assert data['test'] == 'Working4!'
|
||||||
|
|
||||||
def test_flush_on_stop_only_bot(self, bot, update, pickle_persistence_only_bot):
|
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
|
dp = u.dispatcher
|
||||||
u.running = True
|
u.running = True
|
||||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||||
|
@ -1706,7 +1707,7 @@ class TestPicklePersistence:
|
||||||
assert pickle_persistence_2.get_callback_data() is None
|
assert pickle_persistence_2.get_callback_data() is None
|
||||||
|
|
||||||
def test_flush_on_stop_only_chat(self, bot, update, pickle_persistence_only_chat):
|
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
|
dp = u.dispatcher
|
||||||
u.running = True
|
u.running = True
|
||||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||||
|
@ -1726,7 +1727,7 @@ class TestPicklePersistence:
|
||||||
assert pickle_persistence_2.get_callback_data() is None
|
assert pickle_persistence_2.get_callback_data() is None
|
||||||
|
|
||||||
def test_flush_on_stop_only_user(self, bot, update, pickle_persistence_only_user):
|
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
|
dp = u.dispatcher
|
||||||
u.running = True
|
u.running = True
|
||||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||||
|
@ -1746,7 +1747,7 @@ class TestPicklePersistence:
|
||||||
assert pickle_persistence_2.get_callback_data() is None
|
assert pickle_persistence_2.get_callback_data() is None
|
||||||
|
|
||||||
def test_flush_on_stop_only_callback(self, bot, update, pickle_persistence_only_callback):
|
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
|
dp = u.dispatcher
|
||||||
u.running = True
|
u.running = True
|
||||||
dp.user_data[4242424242]['my_test'] = 'Working!'
|
dp.user_data[4242424242]['my_test'] = 'Working!'
|
||||||
|
@ -2194,7 +2195,7 @@ class TestDictPersistence:
|
||||||
|
|
||||||
def test_with_handler(self, bot, update):
|
def test_with_handler(self, bot, update):
|
||||||
dict_persistence = DictPersistence()
|
dict_persistence = DictPersistence()
|
||||||
u = Updater(bot=bot, persistence=dict_persistence)
|
u = UpdaterBuilder().bot(bot).persistence(dict_persistence).build()
|
||||||
dp = u.dispatcher
|
dp = u.dispatcher
|
||||||
|
|
||||||
def first(update, context):
|
def first(update, context):
|
||||||
|
@ -2236,7 +2237,7 @@ class TestDictPersistence:
|
||||||
callback_data_json=callback_data,
|
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 = u.dispatcher
|
||||||
dp.add_handler(h2)
|
dp.add_handler(h2)
|
||||||
dp.process_update(update)
|
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.error import Unauthorized, InvalidToken, TimedOut, RetryAfter, TelegramError
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Updater,
|
|
||||||
Dispatcher,
|
|
||||||
DictPersistence,
|
|
||||||
Defaults,
|
|
||||||
InvalidCallbackData,
|
InvalidCallbackData,
|
||||||
ExtBot,
|
ExtBot,
|
||||||
|
Updater,
|
||||||
|
UpdaterBuilder,
|
||||||
|
DispatcherBuilder,
|
||||||
)
|
)
|
||||||
from telegram.warnings import PTBDeprecationWarning
|
|
||||||
from telegram.ext.utils.webhookhandler import WebhookServer
|
from telegram.ext.utils.webhookhandler import WebhookServer
|
||||||
|
|
||||||
signalskip = pytest.mark.skipif(
|
signalskip = pytest.mark.skipif(
|
||||||
|
@ -90,12 +88,6 @@ class TestUpdater:
|
||||||
offset = 0
|
offset = 0
|
||||||
test_flag = False
|
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)
|
@pytest.fixture(autouse=True)
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.message_count = 0
|
self.message_count = 0
|
||||||
|
@ -113,18 +105,49 @@ class TestUpdater:
|
||||||
self.received = update.message.text
|
self.received = update.message.text
|
||||||
self.cb_handler_called.set()
|
self.cb_handler_called.set()
|
||||||
|
|
||||||
def test_warn_arbitrary_callback_data(self, bot, recwarn):
|
def test_slot_behaviour(self, updater, mro_slots):
|
||||||
Updater(bot=bot, arbitrary_callback_data=True)
|
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 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):
|
def test_warn_con_pool(self, bot, recwarn, dp):
|
||||||
dp = Dispatcher(bot, Queue(), workers=5)
|
DispatcherBuilder().bot(bot).workers(5).build()
|
||||||
Updater(bot=bot, workers=8)
|
UpdaterBuilder().bot(bot).workers(8).build()
|
||||||
Updater(dispatcher=dp, workers=None)
|
UpdaterBuilder().bot(bot).workers(2).build()
|
||||||
assert len(recwarn) == 2
|
assert len(recwarn) == 2
|
||||||
for idx, value in enumerate((12, 9)):
|
for idx, value in enumerate((9, 12)):
|
||||||
warning = f'Connection pool of Request object is smaller than optimal value {value}'
|
warning = (
|
||||||
|
'The Connection pool of Request object is smaller (8) than the '
|
||||||
|
f'recommended value of {value}.'
|
||||||
|
)
|
||||||
assert str(recwarn[idx].message) == warning
|
assert str(recwarn[idx].message) == warning
|
||||||
assert recwarn[idx].filename == __file__, "wrong stacklevel!"
|
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_data()
|
||||||
updater.bot.callback_data_cache.clear_callback_queries()
|
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, 'set_webhook', lambda *args, **kwargs: True)
|
||||||
monkeypatch.setattr(updater.bot, 'delete_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
|
# 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))
|
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.start_webhook(ip, port)
|
||||||
updater.stop()
|
updater.stop()
|
||||||
assert not caplog.records
|
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):
|
def test_webhook_ssl(self, monkeypatch, updater):
|
||||||
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
||||||
|
@ -606,7 +643,7 @@ class TestUpdater:
|
||||||
def user_signal_inc(signum, frame):
|
def user_signal_inc(signum, frame):
|
||||||
temp_var['a'] = 1
|
temp_var['a'] = 1
|
||||||
|
|
||||||
updater.user_sig_handler = user_signal_inc
|
updater.user_signal_handler = user_signal_inc
|
||||||
updater.start_polling(0.01)
|
updater.start_polling(0.01)
|
||||||
Thread(target=partial(self.signal_sender, updater=updater)).start()
|
Thread(target=partial(self.signal_sender, updater=updater)).start()
|
||||||
updater.idle()
|
updater.idle()
|
||||||
|
@ -614,47 +651,3 @@ class TestUpdater:
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
assert updater.running is False
|
assert updater.running is False
|
||||||
assert temp_var['a'] != 0
|
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