mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-02-16 18:31:45 +01:00
Read-Only CallbackDataCache
(#3266)
This commit is contained in:
parent
870a20e834
commit
fb87418473
15 changed files with 158 additions and 87 deletions
|
@ -3,4 +3,4 @@ telegram.ext.ExtBot
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ExtBot
|
.. autoclass:: telegram.ext.ExtBot
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members: insert_callback_data, defaults, rate_limiter, initialize, shutdown
|
:members: insert_callback_data, defaults, rate_limiter, initialize, shutdown, callback_data_cache
|
||||||
|
|
|
@ -57,7 +57,7 @@ class CallbackQuery(TelegramObject):
|
||||||
until you call :attr:`answer`. It is, therefore, necessary to react
|
until you call :attr:`answer`. It is, therefore, necessary to react
|
||||||
by calling :attr:`telegram.Bot.answer_callback_query` even if no notification to the user
|
by calling :attr:`telegram.Bot.answer_callback_query` even if no notification to the user
|
||||||
is needed (e.g., without specifying any of the optional parameters).
|
is needed (e.g., without specifying any of the optional parameters).
|
||||||
* If you're using :attr:`telegram.ext.ExtBot.arbitrary_callback_data`, :attr:`data` may be
|
* If you're using :attr:`telegram.ext.ExtBot.callback_data_cache`, :attr:`data` may be
|
||||||
an instance
|
an instance
|
||||||
of :class:`telegram.ext.InvalidCallbackData`. This will be the case, if the data
|
of :class:`telegram.ext.InvalidCallbackData`. This will be the case, if the data
|
||||||
associated with the button triggering the :class:`telegram.CallbackQuery` was already
|
associated with the button triggering the :class:`telegram.CallbackQuery` was already
|
||||||
|
|
|
@ -54,7 +54,6 @@ from telegram._utils.types import DVInput, ODVInput
|
||||||
from telegram._utils.warnings import warn
|
from telegram._utils.warnings import warn
|
||||||
from telegram.error import TelegramError
|
from telegram.error import TelegramError
|
||||||
from telegram.ext._basepersistence import BasePersistence
|
from telegram.ext._basepersistence import BasePersistence
|
||||||
from telegram.ext._callbackdatacache import CallbackDataCache
|
|
||||||
from telegram.ext._contexttypes import ContextTypes
|
from telegram.ext._contexttypes import ContextTypes
|
||||||
from telegram.ext._extbot import ExtBot
|
from telegram.ext._extbot import ExtBot
|
||||||
from telegram.ext._handler import BaseHandler
|
from telegram.ext._handler import BaseHandler
|
||||||
|
@ -440,10 +439,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
|
||||||
raise ValueError("callback_data must be a tuple of length 2")
|
raise ValueError("callback_data must be a tuple of length 2")
|
||||||
# Mypy doesn't know that persistence.set_bot (see above) already checks that
|
# Mypy doesn't know that persistence.set_bot (see above) already checks that
|
||||||
# self.bot is an instance of ExtBot if callback_data should be stored ...
|
# self.bot is an instance of ExtBot if callback_data should be stored ...
|
||||||
self.bot.callback_data_cache = CallbackDataCache( # type: ignore[attr-defined]
|
self.bot.callback_data_cache.load_persistence_data( # type: ignore[attr-defined]
|
||||||
self.bot, # type: ignore[arg-type]
|
persistent_data
|
||||||
self.bot.callback_data_cache.maxsize, # type: ignore[attr-defined]
|
|
||||||
persistent_data=persistent_data,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -174,10 +174,16 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:exc:`TypeError`: If :attr:`PersistenceInput.callback_data` is :obj:`True` and the
|
:exc:`TypeError`: If :attr:`PersistenceInput.callback_data` is :obj:`True` and the
|
||||||
:paramref:`bot` is not an instance of :class:`telegram.ext.ExtBot`.
|
:paramref:`bot` is not an instance of :class:`telegram.ext.ExtBot` or
|
||||||
|
:attr:`~telegram.ext.ExtBot.callback_data_cache` is :obj:`None`.
|
||||||
"""
|
"""
|
||||||
if self.store_data.callback_data and not isinstance(bot, ExtBot):
|
if self.store_data.callback_data and (
|
||||||
raise TypeError("callback_data can only be stored when using telegram.ext.ExtBot.")
|
not isinstance(bot, ExtBot) or bot.callback_data_cache is None
|
||||||
|
):
|
||||||
|
raise TypeError(
|
||||||
|
"callback_data can only be stored when using telegram.ext.ExtBot with arbitrary "
|
||||||
|
"callback_data enabled. "
|
||||||
|
)
|
||||||
|
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
|
|
|
@ -235,7 +235,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
|
||||||
callback data.
|
callback data.
|
||||||
"""
|
"""
|
||||||
if isinstance(self.bot, ExtBot):
|
if isinstance(self.bot, ExtBot):
|
||||||
if not self.bot.arbitrary_callback_data:
|
if self.bot.callback_data_cache is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"This telegram.ext.ExtBot instance does not use arbitrary callback data."
|
"This telegram.ext.ExtBot instance does not use arbitrary callback data."
|
||||||
)
|
)
|
||||||
|
|
|
@ -113,11 +113,10 @@ class CallbackDataCache:
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
bot (:class:`telegram.ext.ExtBot`): The bot this cache is for.
|
bot (:class:`telegram.ext.ExtBot`): The bot this cache is for.
|
||||||
maxsize (:obj:`int`): maximum size of the cache.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("bot", "maxsize", "_keyboard_data", "_callback_queries", "logger")
|
__slots__ = ("bot", "_maxsize", "_keyboard_data", "_callback_queries", "logger")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -128,18 +127,43 @@ class CallbackDataCache:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.maxsize = maxsize
|
self._maxsize = maxsize
|
||||||
self._keyboard_data: MutableMapping[str, _KeyboardData] = LRUCache(maxsize=maxsize)
|
self._keyboard_data: MutableMapping[str, _KeyboardData] = LRUCache(maxsize=maxsize)
|
||||||
self._callback_queries: MutableMapping[str, str] = LRUCache(maxsize=maxsize)
|
self._callback_queries: MutableMapping[str, str] = LRUCache(maxsize=maxsize)
|
||||||
|
|
||||||
if persistent_data:
|
if persistent_data:
|
||||||
keyboard_data, callback_queries = persistent_data
|
self.load_persistence_data(persistent_data)
|
||||||
for key, value in callback_queries.items():
|
|
||||||
self._callback_queries[key] = value
|
def load_persistence_data(self, persistent_data: CDCData) -> None:
|
||||||
for uuid, access_time, data in keyboard_data:
|
"""Loads data into the cache.
|
||||||
self._keyboard_data[uuid] = _KeyboardData(
|
|
||||||
keyboard_uuid=uuid, access_time=access_time, button_data=data
|
Warning:
|
||||||
)
|
This method is not intended to be called by users directly.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
|
||||||
|
Args:
|
||||||
|
persistent_data (Tuple[List[Tuple[:obj:`str`, :obj:`float`, \
|
||||||
|
Dict[:obj:`str`, :class:`object`]]], Dict[:obj:`str`, :obj:`str`]], optional): \
|
||||||
|
Data to load, as returned by \
|
||||||
|
:meth:`telegram.ext.BasePersistence.get_callback_data`.
|
||||||
|
"""
|
||||||
|
keyboard_data, callback_queries = persistent_data
|
||||||
|
for key, value in callback_queries.items():
|
||||||
|
self._callback_queries[key] = value
|
||||||
|
for uuid, access_time, data in keyboard_data:
|
||||||
|
self._keyboard_data[uuid] = _KeyboardData(
|
||||||
|
keyboard_uuid=uuid, access_time=access_time, button_data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def maxsize(self) -> int:
|
||||||
|
""":obj:`int`: The maximum size of the cache.
|
||||||
|
|
||||||
|
.. versionchanged:: 20.0
|
||||||
|
This property is now read-only.
|
||||||
|
"""
|
||||||
|
return self._maxsize
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def persistence_data(self) -> CDCData:
|
def persistence_data(self) -> CDCData:
|
||||||
|
|
|
@ -124,6 +124,11 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||||
|
|
||||||
.. versionadded:: 13.6
|
.. versionadded:: 13.6
|
||||||
|
|
||||||
|
.. versionchanged:: 20.0
|
||||||
|
Removed the attribute ``arbitrary_callback_data``. You can instead use
|
||||||
|
:attr:`bot.callback_data_cache.maxsize <telegram.ext.CallbackDataCache.maxsize>` to
|
||||||
|
access the size of the cache.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
||||||
be used if not set explicitly in the bot methods.
|
be used if not set explicitly in the bot methods.
|
||||||
|
@ -137,16 +142,9 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||||
|
|
||||||
.. versionadded:: 20.0
|
.. versionadded:: 20.0
|
||||||
|
|
||||||
Attributes:
|
|
||||||
arbitrary_callback_data (:obj:`bool` | :obj:`int`): Whether this bot instance
|
|
||||||
allows to use arbitrary objects as callback data for
|
|
||||||
:class:`telegram.InlineKeyboardButton`.
|
|
||||||
callback_data_cache (:class:`telegram.ext.CallbackDataCache`): The cache for objects passed
|
|
||||||
as callback data for :class:`telegram.InlineKeyboardButton`.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("arbitrary_callback_data", "callback_data_cache", "_defaults", "_rate_limiter")
|
__slots__ = ("_callback_data_cache", "_defaults", "_rate_limiter")
|
||||||
|
|
||||||
# using object() would be a tiny bit safer, but a string plays better with the typing setup
|
# using object() would be a tiny bit safer, but a string plays better with the typing setup
|
||||||
__RL_KEY = uuid4().hex
|
__RL_KEY = uuid4().hex
|
||||||
|
@ -210,15 +208,30 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||||
)
|
)
|
||||||
self._defaults = defaults
|
self._defaults = defaults
|
||||||
self._rate_limiter = rate_limiter
|
self._rate_limiter = rate_limiter
|
||||||
|
self._callback_data_cache: Optional[CallbackDataCache] = None
|
||||||
|
|
||||||
# set up callback_data
|
# set up callback_data
|
||||||
|
if arbitrary_callback_data is False:
|
||||||
|
return
|
||||||
|
|
||||||
if not isinstance(arbitrary_callback_data, bool):
|
if not isinstance(arbitrary_callback_data, bool):
|
||||||
maxsize = cast(int, arbitrary_callback_data)
|
maxsize = cast(int, arbitrary_callback_data)
|
||||||
self.arbitrary_callback_data = True
|
|
||||||
else:
|
else:
|
||||||
maxsize = 1024
|
maxsize = 1024
|
||||||
self.arbitrary_callback_data = arbitrary_callback_data
|
|
||||||
self.callback_data_cache: CallbackDataCache = CallbackDataCache(bot=self, maxsize=maxsize)
|
self._callback_data_cache = CallbackDataCache(bot=self, maxsize=maxsize)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def callback_data_cache(self) -> Optional[CallbackDataCache]:
|
||||||
|
""":class:`telegram.ext.CallbackDataCache`: Optional. The cache for
|
||||||
|
objects passed as callback data for :class:`telegram.InlineKeyboardButton`.
|
||||||
|
|
||||||
|
.. versionchanged:: 20.0
|
||||||
|
* This property is now read-only.
|
||||||
|
* This property is now optional and can be :obj:`None` if
|
||||||
|
:paramref:`~telegram.ext.ExtBot.arbitrary_callback_data` is set to :obj:`False`.
|
||||||
|
"""
|
||||||
|
return self._callback_data_cache
|
||||||
|
|
||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
"""See :meth:`telegram.Bot.initialize`. Also initializes the
|
"""See :meth:`telegram.Bot.initialize`. Also initializes the
|
||||||
|
@ -366,7 +379,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||||
def _replace_keyboard(self, reply_markup: Optional[ReplyMarkup]) -> Optional[ReplyMarkup]:
|
def _replace_keyboard(self, reply_markup: Optional[ReplyMarkup]) -> Optional[ReplyMarkup]:
|
||||||
# If the reply_markup is an inline keyboard and we allow arbitrary callback data, let the
|
# If the reply_markup is an inline keyboard and we allow arbitrary callback data, let the
|
||||||
# CallbackDataCache build a new keyboard with the data replaced. Otherwise return the input
|
# CallbackDataCache build a new keyboard with the data replaced. Otherwise return the input
|
||||||
if isinstance(reply_markup, InlineKeyboardMarkup) and self.arbitrary_callback_data:
|
if isinstance(reply_markup, InlineKeyboardMarkup) and self.callback_data_cache is not None:
|
||||||
return self.callback_data_cache.process_keyboard(reply_markup)
|
return self.callback_data_cache.process_keyboard(reply_markup)
|
||||||
|
|
||||||
return reply_markup
|
return reply_markup
|
||||||
|
@ -405,7 +418,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||||
self._insert_callback_data(update.effective_message)
|
self._insert_callback_data(update.effective_message)
|
||||||
|
|
||||||
def _insert_callback_data(self, obj: HandledTypes) -> HandledTypes:
|
def _insert_callback_data(self, obj: HandledTypes) -> HandledTypes:
|
||||||
if not self.arbitrary_callback_data:
|
if self.callback_data_cache is None:
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
if isinstance(obj, CallbackQuery):
|
if isinstance(obj, CallbackQuery):
|
||||||
|
@ -514,7 +527,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process arbitrary callback
|
# Process arbitrary callback
|
||||||
if not self.arbitrary_callback_data:
|
if self.callback_data_cache is None:
|
||||||
return effective_results, next_offset
|
return effective_results, next_offset
|
||||||
results = []
|
results = []
|
||||||
for result in effective_results:
|
for result in effective_results:
|
||||||
|
|
|
@ -158,6 +158,13 @@ async def bot(bot_info):
|
||||||
yield _bot
|
yield _bot
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
async def cdc_bot(bot_info):
|
||||||
|
"""Makes an ExtBot instance with the given bot_info that uses arbitrary callback_data"""
|
||||||
|
async with make_bot(bot_info, arbitrary_callback_data=True) as _bot:
|
||||||
|
yield _bot
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def raw_bot(bot_info):
|
async def raw_bot(bot_info):
|
||||||
"""Makes an regular Bot instance with the given bot_info"""
|
"""Makes an regular Bot instance with the given bot_info"""
|
||||||
|
|
|
@ -26,6 +26,7 @@ from telegram.ext import (
|
||||||
AIORateLimiter,
|
AIORateLimiter,
|
||||||
Application,
|
Application,
|
||||||
ApplicationBuilder,
|
ApplicationBuilder,
|
||||||
|
CallbackDataCache,
|
||||||
ContextTypes,
|
ContextTypes,
|
||||||
Defaults,
|
Defaults,
|
||||||
ExtBot,
|
ExtBot,
|
||||||
|
@ -81,7 +82,7 @@ class TestApplicationBuilder:
|
||||||
assert "api.telegram.org" in app.bot.base_file_url
|
assert "api.telegram.org" in app.bot.base_file_url
|
||||||
assert bot.token in app.bot.base_file_url
|
assert bot.token in app.bot.base_file_url
|
||||||
assert app.bot.private_key is None
|
assert app.bot.private_key is None
|
||||||
assert app.bot.arbitrary_callback_data is False
|
assert app.bot.callback_data_cache is None
|
||||||
assert app.bot.defaults is None
|
assert app.bot.defaults is None
|
||||||
assert app.bot.rate_limiter is None
|
assert app.bot.rate_limiter is None
|
||||||
assert app.bot.local_mode is False
|
assert app.bot.local_mode is False
|
||||||
|
@ -346,6 +347,7 @@ class TestApplicationBuilder:
|
||||||
.concurrent_updates(concurrent_updates)
|
.concurrent_updates(concurrent_updates)
|
||||||
.post_init(post_init)
|
.post_init(post_init)
|
||||||
.post_shutdown(post_shutdown)
|
.post_shutdown(post_shutdown)
|
||||||
|
.arbitrary_callback_data(True)
|
||||||
).build()
|
).build()
|
||||||
assert app.job_queue is job_queue
|
assert app.job_queue is job_queue
|
||||||
assert app.job_queue.application is app
|
assert app.job_queue.application is app
|
||||||
|
@ -358,6 +360,7 @@ class TestApplicationBuilder:
|
||||||
assert app.concurrent_updates == concurrent_updates
|
assert app.concurrent_updates == concurrent_updates
|
||||||
assert app.post_init is post_init
|
assert app.post_init is post_init
|
||||||
assert app.post_shutdown is post_shutdown
|
assert app.post_shutdown is post_shutdown
|
||||||
|
assert isinstance(app.bot.callback_data_cache, CallbackDataCache)
|
||||||
|
|
||||||
updater = Updater(bot=bot, update_queue=update_queue)
|
updater = Updater(bot=bot, update_queue=update_queue)
|
||||||
app = ApplicationBuilder().updater(updater).build()
|
app = ApplicationBuilder().updater(updater).build()
|
||||||
|
|
|
@ -38,6 +38,7 @@ from telegram.ext import (
|
||||||
BasePersistence,
|
BasePersistence,
|
||||||
CallbackContext,
|
CallbackContext,
|
||||||
ConversationHandler,
|
ConversationHandler,
|
||||||
|
ExtBot,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
PersistenceInput,
|
PersistenceInput,
|
||||||
filters,
|
filters,
|
||||||
|
@ -390,6 +391,9 @@ class TestBasePersistence:
|
||||||
with pytest.raises(TypeError, match="when using telegram.ext.ExtBot"):
|
with pytest.raises(TypeError, match="when using telegram.ext.ExtBot"):
|
||||||
papp.persistence.set_bot(Bot(papp.bot.token))
|
papp.persistence.set_bot(Bot(papp.bot.token))
|
||||||
|
|
||||||
|
with pytest.raises(TypeError, match="when using telegram.ext.ExtBot"):
|
||||||
|
papp.persistence.set_bot(ExtBot(papp.bot.token))
|
||||||
|
|
||||||
def test_construction_with_bad_persistence(self, caplog, bot):
|
def test_construction_with_bad_persistence(self, caplog, bot):
|
||||||
class MyPersistence:
|
class MyPersistence:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -1024,7 +1028,13 @@ class TestBasePersistence:
|
||||||
test_flag.append(str(context.error) == "PersistenceError")
|
test_flag.append(str(context.error) == "PersistenceError")
|
||||||
raise Exception("ErrorHandlingError")
|
raise Exception("ErrorHandlingError")
|
||||||
|
|
||||||
app = ApplicationBuilder().token(bot.token).persistence(ErrorPersistence()).build()
|
app = (
|
||||||
|
ApplicationBuilder()
|
||||||
|
.token(bot.token)
|
||||||
|
.arbitrary_callback_data(True)
|
||||||
|
.persistence(ErrorPersistence())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
async with app:
|
async with app:
|
||||||
app.add_error_handler(error)
|
app.add_error_handler(error)
|
||||||
|
|
|
@ -284,13 +284,15 @@ class TestBot:
|
||||||
assert caplog.records[-1].getMessage().startswith("Exiting: get_me")
|
assert caplog.records[-1].getMessage().startswith("Exiting: get_me")
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"acd_in,maxsize,acd",
|
"acd_in,maxsize",
|
||||||
[(True, 1024, True), (False, 1024, False), (0, 0, True), (None, None, True)],
|
[(True, 1024), (False, 1024), (0, 0), (None, None)],
|
||||||
)
|
)
|
||||||
async def test_callback_data_maxsize(self, bot, acd_in, maxsize, acd):
|
async def test_callback_data_maxsize(self, bot, acd_in, maxsize):
|
||||||
async with ExtBot(bot.token, arbitrary_callback_data=acd_in) as acd_bot:
|
async with ExtBot(bot.token, arbitrary_callback_data=acd_in) as acd_bot:
|
||||||
assert acd_bot.arbitrary_callback_data == acd
|
if acd_in is not False:
|
||||||
assert acd_bot.callback_data_cache.maxsize == maxsize
|
assert acd_bot.callback_data_cache.maxsize == maxsize
|
||||||
|
else:
|
||||||
|
assert acd_bot.callback_data_cache is None
|
||||||
|
|
||||||
async def test_no_token_passed(self):
|
async def test_no_token_passed(self):
|
||||||
with pytest.raises(InvalidToken, match="You must pass the token"):
|
with pytest.raises(InvalidToken, match="You must pass the token"):
|
||||||
|
@ -1542,7 +1544,9 @@ class TestBot:
|
||||||
if updates:
|
if updates:
|
||||||
assert isinstance(updates[0], Update)
|
assert isinstance(updates[0], Update)
|
||||||
|
|
||||||
async def test_get_updates_invalid_callback_data(self, bot, monkeypatch):
|
async def test_get_updates_invalid_callback_data(self, cdc_bot, monkeypatch):
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
async def post(*args, **kwargs):
|
async def post(*args, **kwargs):
|
||||||
return [
|
return [
|
||||||
Update(
|
Update(
|
||||||
|
@ -1563,7 +1567,6 @@ class TestBot:
|
||||||
).to_dict()
|
).to_dict()
|
||||||
]
|
]
|
||||||
|
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
try:
|
try:
|
||||||
await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed
|
await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed
|
||||||
monkeypatch.setattr(BaseRequest, "post", post)
|
monkeypatch.setattr(BaseRequest, "post", post)
|
||||||
|
@ -1575,7 +1578,8 @@ class TestBot:
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Reset b/c bots scope is session
|
# Reset b/c bots scope is session
|
||||||
bot.arbitrary_callback_data = False
|
bot.callback_data_cache.clear_callback_data()
|
||||||
|
bot.callback_data_cache.clear_callback_queries()
|
||||||
|
|
||||||
@flaky(3, 1)
|
@flaky(3, 1)
|
||||||
@pytest.mark.parametrize("use_ip", [True, False])
|
@pytest.mark.parametrize("use_ip", [True, False])
|
||||||
|
@ -2618,9 +2622,10 @@ class TestBot:
|
||||||
else:
|
else:
|
||||||
assert len(message.caption_entities) == 0
|
assert len(message.caption_entities) == 0
|
||||||
|
|
||||||
async def test_replace_callback_data_send_message(self, bot, chat_id):
|
async def test_replace_callback_data_send_message(self, cdc_bot, chat_id):
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
||||||
no_replace_button = InlineKeyboardButton(
|
no_replace_button = InlineKeyboardButton(
|
||||||
text="no_replace", url="http://python-telegram-bot.org/"
|
text="no_replace", url="http://python-telegram-bot.org/"
|
||||||
|
@ -2642,14 +2647,14 @@ class TestBot:
|
||||||
data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0]
|
data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0]
|
||||||
assert data == "replace_test"
|
assert data == "replace_test"
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
async def test_replace_callback_data_stop_poll_and_repl_to_message(self, bot, chat_id):
|
async def test_replace_callback_data_stop_poll_and_repl_to_message(self, cdc_bot, chat_id):
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
poll_message = await bot.send_poll(chat_id=chat_id, question="test", options=["1", "2"])
|
poll_message = await bot.send_poll(chat_id=chat_id, question="test", options=["1", "2"])
|
||||||
try:
|
try:
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
||||||
no_replace_button = InlineKeyboardButton(
|
no_replace_button = InlineKeyboardButton(
|
||||||
text="no_replace", url="http://python-telegram-bot.org/"
|
text="no_replace", url="http://python-telegram-bot.org/"
|
||||||
|
@ -2671,16 +2676,16 @@ class TestBot:
|
||||||
data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0]
|
data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0]
|
||||||
assert data == "replace_test"
|
assert data == "replace_test"
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
async def test_replace_callback_data_copy_message(self, bot, chat_id):
|
async def test_replace_callback_data_copy_message(self, cdc_bot, chat_id):
|
||||||
"""This also tests that data is inserted into the buttons of message.reply_to_message
|
"""This also tests that data is inserted into the buttons of message.reply_to_message
|
||||||
where message is the return value of a bot method"""
|
where message is the return value of a bot method"""
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
original_message = await bot.send_message(chat_id=chat_id, text="original")
|
original_message = await bot.send_message(chat_id=chat_id, text="original")
|
||||||
try:
|
try:
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
||||||
no_replace_button = InlineKeyboardButton(
|
no_replace_button = InlineKeyboardButton(
|
||||||
text="no_replace", url="http://python-telegram-bot.org/"
|
text="no_replace", url="http://python-telegram-bot.org/"
|
||||||
|
@ -2704,12 +2709,13 @@ class TestBot:
|
||||||
data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0]
|
data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0]
|
||||||
assert data == "replace_test"
|
assert data == "replace_test"
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
# TODO: Needs improvement. We need incoming inline query to test answer.
|
# TODO: Needs improvement. We need incoming inline query to test answer.
|
||||||
async def test_replace_callback_data_answer_inline_query(self, monkeypatch, bot, chat_id):
|
async def test_replace_callback_data_answer_inline_query(self, monkeypatch, cdc_bot, chat_id):
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
# For now just test that our internals pass the correct data
|
# For now just test that our internals pass the correct data
|
||||||
async def make_assertion(
|
async def make_assertion(
|
||||||
endpoint,
|
endpoint,
|
||||||
|
@ -2732,7 +2738,6 @@ class TestBot:
|
||||||
return assertion_1 and assertion_2 and assertion_3 and assertion_4
|
return assertion_1 and assertion_2 and assertion_3 and assertion_4
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
|
||||||
no_replace_button = InlineKeyboardButton(
|
no_replace_button = InlineKeyboardButton(
|
||||||
text="no_replace", url="http://python-telegram-bot.org/"
|
text="no_replace", url="http://python-telegram-bot.org/"
|
||||||
|
@ -2760,13 +2765,13 @@ class TestBot:
|
||||||
assert await bot.answer_inline_query(chat_id, results=results)
|
assert await bot.answer_inline_query(chat_id, results=results)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
async def test_get_chat_arbitrary_callback_data(self, super_group_id, bot):
|
async def test_get_chat_arbitrary_callback_data(self, super_group_id, cdc_bot):
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
reply_markup = InlineKeyboardMarkup.from_button(
|
reply_markup = InlineKeyboardMarkup.from_button(
|
||||||
InlineKeyboardButton(text="text", callback_data="callback_data")
|
InlineKeyboardButton(text="text", callback_data="callback_data")
|
||||||
)
|
)
|
||||||
|
@ -2784,7 +2789,6 @@ class TestBot:
|
||||||
assert chat.pinned_message == message
|
assert chat.pinned_message == message
|
||||||
assert chat.pinned_message.reply_markup == reply_markup
|
assert chat.pinned_message.reply_markup == reply_markup
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
await bot.unpin_all_chat_messages(super_group_id)
|
await bot.unpin_all_chat_messages(super_group_id)
|
||||||
|
@ -2793,8 +2797,9 @@ class TestBot:
|
||||||
# The same must be done in the webhook updater. This is tested over at test_updater.py, but
|
# The same must be done in the webhook updater. This is tested over at test_updater.py, but
|
||||||
# here we test more extensively.
|
# here we test more extensively.
|
||||||
|
|
||||||
async def test_arbitrary_callback_data_no_insert(self, monkeypatch, bot):
|
async def test_arbitrary_callback_data_no_insert(self, monkeypatch, cdc_bot):
|
||||||
"""Updates that don't need insertion shouldn.t fail obviously"""
|
"""Updates that don't need insertion shouldn't fail obviously"""
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
async def post(*args, **kwargs):
|
async def post(*args, **kwargs):
|
||||||
update = Update(
|
update = Update(
|
||||||
|
@ -2813,7 +2818,6 @@ class TestBot:
|
||||||
return [update.to_dict()]
|
return [update.to_dict()]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bot.arbitrary_callback_data = True
|
|
||||||
monkeypatch.setattr(BaseRequest, "post", post)
|
monkeypatch.setattr(BaseRequest, "post", post)
|
||||||
await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed
|
await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed
|
||||||
updates = await bot.get_updates(timeout=1)
|
updates = await bot.get_updates(timeout=1)
|
||||||
|
@ -2822,15 +2826,17 @@ class TestBot:
|
||||||
assert updates[0].update_id == 17
|
assert updates[0].update_id == 17
|
||||||
assert updates[0].poll.id == "42"
|
assert updates[0].poll.id == "42"
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
bot.callback_data_cache.clear_callback_data()
|
||||||
|
bot.callback_data_cache.clear_callback_queries()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"message_type", ["channel_post", "edited_channel_post", "message", "edited_message"]
|
"message_type", ["channel_post", "edited_channel_post", "message", "edited_message"]
|
||||||
)
|
)
|
||||||
async def test_arbitrary_callback_data_pinned_message_reply_to_message(
|
async def test_arbitrary_callback_data_pinned_message_reply_to_message(
|
||||||
self, super_group_id, bot, monkeypatch, message_type
|
self, super_group_id, cdc_bot, monkeypatch, message_type
|
||||||
):
|
):
|
||||||
bot.arbitrary_callback_data = True
|
bot = cdc_bot
|
||||||
|
|
||||||
reply_markup = InlineKeyboardMarkup.from_button(
|
reply_markup = InlineKeyboardMarkup.from_button(
|
||||||
InlineKeyboardButton(text="text", callback_data="callback_data")
|
InlineKeyboardButton(text="text", callback_data="callback_data")
|
||||||
)
|
)
|
||||||
|
@ -2880,12 +2886,13 @@ class TestBot:
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
async def test_arbitrary_callback_data_get_chat_no_pinned_message(self, super_group_id, bot):
|
async def test_arbitrary_callback_data_get_chat_no_pinned_message(
|
||||||
bot.arbitrary_callback_data = True
|
self, super_group_id, cdc_bot
|
||||||
|
):
|
||||||
|
bot = cdc_bot
|
||||||
await bot.unpin_all_chat_messages(super_group_id)
|
await bot.unpin_all_chat_messages(super_group_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -2895,16 +2902,17 @@ class TestBot:
|
||||||
assert int(chat.id) == int(super_group_id)
|
assert int(chat.id) == int(super_group_id)
|
||||||
assert chat.pinned_message is None
|
assert chat.pinned_message is None
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
bot.callback_data_cache.clear_callback_data()
|
||||||
|
bot.callback_data_cache.clear_callback_queries()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"message_type", ["channel_post", "edited_channel_post", "message", "edited_message"]
|
"message_type", ["channel_post", "edited_channel_post", "message", "edited_message"]
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("self_sender", [True, False])
|
@pytest.mark.parametrize("self_sender", [True, False])
|
||||||
async def test_arbitrary_callback_data_via_bot(
|
async def test_arbitrary_callback_data_via_bot(
|
||||||
self, super_group_id, bot, monkeypatch, self_sender, message_type
|
self, super_group_id, cdc_bot, monkeypatch, self_sender, message_type
|
||||||
):
|
):
|
||||||
bot.arbitrary_callback_data = True
|
bot = cdc_bot
|
||||||
reply_markup = InlineKeyboardMarkup.from_button(
|
reply_markup = InlineKeyboardMarkup.from_button(
|
||||||
InlineKeyboardButton(text="text", callback_data="callback_data")
|
InlineKeyboardButton(text="text", callback_data="callback_data")
|
||||||
)
|
)
|
||||||
|
@ -2938,7 +2946,6 @@ class TestBot:
|
||||||
== reply_markup.inline_keyboard[0][0].callback_data
|
== reply_markup.inline_keyboard[0][0].callback_data
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ class TestCallbackDataCache:
|
||||||
assert cdc.bot is bot
|
assert cdc.bot is bot
|
||||||
|
|
||||||
def test_init_and_access__persistent_data(self, bot):
|
def test_init_and_access__persistent_data(self, bot):
|
||||||
|
"""This also tests CDC.load_persistent_data."""
|
||||||
keyboard_data = _KeyboardData("123", 456, {"button": 678})
|
keyboard_data = _KeyboardData("123", 456, {"button": 678})
|
||||||
persistent_data = ([keyboard_data.to_tuple()], {"id": "123"})
|
persistent_data = ([keyboard_data.to_tuple()], {"id": "123"})
|
||||||
cdc = CallbackDataCache(bot, persistent_data=persistent_data)
|
cdc = CallbackDataCache(bot, persistent_data=persistent_data)
|
||||||
|
|
|
@ -24,11 +24,10 @@ from telegram.ext import DictPersistence
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def reset_callback_data_cache(bot):
|
def reset_callback_data_cache(cdc_bot):
|
||||||
yield
|
yield
|
||||||
bot.callback_data_cache.clear_callback_data()
|
cdc_bot.callback_data_cache.clear_callback_data()
|
||||||
bot.callback_data_cache.clear_callback_queries()
|
cdc_bot.callback_data_cache.clear_callback_queries()
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
|
|
@ -41,11 +41,10 @@ def change_directory(tmp_path: Path):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def reset_callback_data_cache(bot):
|
def reset_callback_data_cache(cdc_bot):
|
||||||
yield
|
yield
|
||||||
bot.callback_data_cache.clear_callback_data()
|
cdc_bot.callback_data_cache.clear_callback_data()
|
||||||
bot.callback_data_cache.clear_callback_queries()
|
cdc_bot.callback_data_cache.clear_callback_queries()
|
||||||
bot.arbitrary_callback_data = False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
@ -850,10 +849,14 @@ class TestPicklePersistence:
|
||||||
assert conversations_test["name1"] == conversation1
|
assert conversations_test["name1"] == conversation1
|
||||||
|
|
||||||
async def test_custom_pickler_unpickler_simple(
|
async def test_custom_pickler_unpickler_simple(
|
||||||
self, pickle_persistence, update, good_pickle_files, bot, recwarn
|
self, pickle_persistence, good_pickle_files, cdc_bot, recwarn
|
||||||
):
|
):
|
||||||
|
bot = cdc_bot
|
||||||
|
update = Update(1)
|
||||||
|
update.set_bot(bot)
|
||||||
|
|
||||||
pickle_persistence.set_bot(bot) # assign the current bot to the persistence
|
pickle_persistence.set_bot(bot) # assign the current bot to the persistence
|
||||||
data_with_bot = {"current_bot": update.message}
|
data_with_bot = {"current_bot": update}
|
||||||
await pickle_persistence.update_chat_data(
|
await pickle_persistence.update_chat_data(
|
||||||
12345, data_with_bot
|
12345, data_with_bot
|
||||||
) # also calls BotPickler.dumps()
|
) # also calls BotPickler.dumps()
|
||||||
|
@ -887,8 +890,10 @@ class TestPicklePersistence:
|
||||||
assert (await pp.get_chat_data())[12345]["unknown_bot_in_user"]._bot is None
|
assert (await pp.get_chat_data())[12345]["unknown_bot_in_user"]._bot is None
|
||||||
|
|
||||||
async def test_custom_pickler_unpickler_with_custom_objects(
|
async def test_custom_pickler_unpickler_with_custom_objects(
|
||||||
self, bot, pickle_persistence, good_pickle_files
|
self, cdc_bot, pickle_persistence, good_pickle_files
|
||||||
):
|
):
|
||||||
|
bot = cdc_bot
|
||||||
|
|
||||||
dict_s = self.DictSub("private", "normal", bot)
|
dict_s = self.DictSub("private", "normal", bot)
|
||||||
slot_s = self.SlotsSub("new_var", "private_var")
|
slot_s = self.SlotsSub("new_var", "private_var")
|
||||||
regular = self.NormalClass(12)
|
regular = self.NormalClass(12)
|
||||||
|
|
|
@ -693,11 +693,11 @@ class TestUpdater:
|
||||||
|
|
||||||
@pytest.mark.parametrize("invalid_data", [True, False], ids=("invalid data", "valid data"))
|
@pytest.mark.parametrize("invalid_data", [True, False], ids=("invalid data", "valid data"))
|
||||||
async def test_webhook_arbitrary_callback_data(
|
async def test_webhook_arbitrary_callback_data(
|
||||||
self, monkeypatch, updater, invalid_data, chat_id
|
self, monkeypatch, cdc_bot, invalid_data, chat_id
|
||||||
):
|
):
|
||||||
"""Here we only test one simple setup. telegram.ext.ExtBot.insert_callback_data is tested
|
"""Here we only test one simple setup. telegram.ext.ExtBot.insert_callback_data is tested
|
||||||
extensively in test_bot.py in conjunction with get_updates."""
|
extensively in test_bot.py in conjunction with get_updates."""
|
||||||
updater.bot.arbitrary_callback_data = True
|
updater = Updater(bot=cdc_bot, update_queue=asyncio.Queue())
|
||||||
|
|
||||||
async def return_true(*args, **kwargs):
|
async def return_true(*args, **kwargs):
|
||||||
return True
|
return True
|
||||||
|
@ -743,7 +743,6 @@ class TestUpdater:
|
||||||
|
|
||||||
await updater.stop()
|
await updater.stop()
|
||||||
finally:
|
finally:
|
||||||
updater.bot.arbitrary_callback_data = False
|
|
||||||
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()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue