Read-Only CallbackDataCache (#3266)

This commit is contained in:
Bibo-Joshi 2022-10-07 10:18:08 +02:00 committed by GitHub
parent 870a20e834
commit fb87418473
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 158 additions and 87 deletions

View file

@ -3,4 +3,4 @@ telegram.ext.ExtBot
.. autoclass:: telegram.ext.ExtBot
:show-inheritance:
:members: insert_callback_data, defaults, rate_limiter, initialize, shutdown
:members: insert_callback_data, defaults, rate_limiter, initialize, shutdown, callback_data_cache

View file

@ -57,7 +57,7 @@ class CallbackQuery(TelegramObject):
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
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
of :class:`telegram.ext.InvalidCallbackData`. This will be the case, if the data
associated with the button triggering the :class:`telegram.CallbackQuery` was already

View file

@ -54,7 +54,6 @@ from telegram._utils.types import DVInput, ODVInput
from telegram._utils.warnings import warn
from telegram.error import TelegramError
from telegram.ext._basepersistence import BasePersistence
from telegram.ext._callbackdatacache import CallbackDataCache
from telegram.ext._contexttypes import ContextTypes
from telegram.ext._extbot import ExtBot
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")
# Mypy doesn't know that persistence.set_bot (see above) already checks that
# self.bot is an instance of ExtBot if callback_data should be stored ...
self.bot.callback_data_cache = CallbackDataCache( # type: ignore[attr-defined]
self.bot, # type: ignore[arg-type]
self.bot.callback_data_cache.maxsize, # type: ignore[attr-defined]
persistent_data=persistent_data,
self.bot.callback_data_cache.load_persistence_data( # type: ignore[attr-defined]
persistent_data
)
@staticmethod

View file

@ -174,10 +174,16 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
Raises:
: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):
raise TypeError("callback_data can only be stored when using telegram.ext.ExtBot.")
if self.store_data.callback_data and (
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

View file

@ -235,7 +235,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
callback data.
"""
if isinstance(self.bot, ExtBot):
if not self.bot.arbitrary_callback_data:
if self.bot.callback_data_cache is None:
raise RuntimeError(
"This telegram.ext.ExtBot instance does not use arbitrary callback data."
)

View file

@ -113,11 +113,10 @@ class CallbackDataCache:
Attributes:
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__(
self,
@ -128,18 +127,43 @@ class CallbackDataCache:
self.logger = logging.getLogger(__name__)
self.bot = bot
self.maxsize = maxsize
self._maxsize = maxsize
self._keyboard_data: MutableMapping[str, _KeyboardData] = LRUCache(maxsize=maxsize)
self._callback_queries: MutableMapping[str, str] = LRUCache(maxsize=maxsize)
if persistent_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
)
self.load_persistence_data(persistent_data)
def load_persistence_data(self, persistent_data: CDCData) -> None:
"""Loads data into the cache.
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
def persistence_data(self) -> CDCData:

View file

@ -124,6 +124,11 @@ class ExtBot(Bot, Generic[RLARGS]):
.. 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:
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
be used if not set explicitly in the bot methods.
@ -137,16 +142,9 @@ class ExtBot(Bot, Generic[RLARGS]):
.. 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
__RL_KEY = uuid4().hex
@ -210,15 +208,30 @@ class ExtBot(Bot, Generic[RLARGS]):
)
self._defaults = defaults
self._rate_limiter = rate_limiter
self._callback_data_cache: Optional[CallbackDataCache] = None
# set up callback_data
if arbitrary_callback_data is False:
return
if not isinstance(arbitrary_callback_data, bool):
maxsize = cast(int, arbitrary_callback_data)
self.arbitrary_callback_data = True
else:
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:
"""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]:
# 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
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 reply_markup
@ -405,7 +418,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self._insert_callback_data(update.effective_message)
def _insert_callback_data(self, obj: HandledTypes) -> HandledTypes:
if not self.arbitrary_callback_data:
if self.callback_data_cache is None:
return obj
if isinstance(obj, CallbackQuery):
@ -514,7 +527,7 @@ class ExtBot(Bot, Generic[RLARGS]):
)
# Process arbitrary callback
if not self.arbitrary_callback_data:
if self.callback_data_cache is None:
return effective_results, next_offset
results = []
for result in effective_results:

View file

@ -158,6 +158,13 @@ async def bot(bot_info):
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")
async def raw_bot(bot_info):
"""Makes an regular Bot instance with the given bot_info"""

View file

@ -26,6 +26,7 @@ from telegram.ext import (
AIORateLimiter,
Application,
ApplicationBuilder,
CallbackDataCache,
ContextTypes,
Defaults,
ExtBot,
@ -81,7 +82,7 @@ class TestApplicationBuilder:
assert "api.telegram.org" 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.arbitrary_callback_data is False
assert app.bot.callback_data_cache is None
assert app.bot.defaults is None
assert app.bot.rate_limiter is None
assert app.bot.local_mode is False
@ -346,6 +347,7 @@ class TestApplicationBuilder:
.concurrent_updates(concurrent_updates)
.post_init(post_init)
.post_shutdown(post_shutdown)
.arbitrary_callback_data(True)
).build()
assert app.job_queue is job_queue
assert app.job_queue.application is app
@ -358,6 +360,7 @@ class TestApplicationBuilder:
assert app.concurrent_updates == concurrent_updates
assert app.post_init is post_init
assert app.post_shutdown is post_shutdown
assert isinstance(app.bot.callback_data_cache, CallbackDataCache)
updater = Updater(bot=bot, update_queue=update_queue)
app = ApplicationBuilder().updater(updater).build()

View file

@ -38,6 +38,7 @@ from telegram.ext import (
BasePersistence,
CallbackContext,
ConversationHandler,
ExtBot,
MessageHandler,
PersistenceInput,
filters,
@ -390,6 +391,9 @@ class TestBasePersistence:
with pytest.raises(TypeError, match="when using telegram.ext.ExtBot"):
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):
class MyPersistence:
def __init__(self):
@ -1024,7 +1028,13 @@ class TestBasePersistence:
test_flag.append(str(context.error) == "PersistenceError")
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:
app.add_error_handler(error)

View file

@ -284,13 +284,15 @@ class TestBot:
assert caplog.records[-1].getMessage().startswith("Exiting: get_me")
@pytest.mark.parametrize(
"acd_in,maxsize,acd",
[(True, 1024, True), (False, 1024, False), (0, 0, True), (None, None, True)],
"acd_in,maxsize",
[(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:
assert acd_bot.arbitrary_callback_data == acd
assert acd_bot.callback_data_cache.maxsize == maxsize
if acd_in is not False:
assert acd_bot.callback_data_cache.maxsize == maxsize
else:
assert acd_bot.callback_data_cache is None
async def test_no_token_passed(self):
with pytest.raises(InvalidToken, match="You must pass the token"):
@ -1542,7 +1544,9 @@ class TestBot:
if updates:
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):
return [
Update(
@ -1563,7 +1567,6 @@ class TestBot:
).to_dict()
]
bot.arbitrary_callback_data = True
try:
await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed
monkeypatch.setattr(BaseRequest, "post", post)
@ -1575,7 +1578,8 @@ class TestBot:
finally:
# 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)
@pytest.mark.parametrize("use_ip", [True, False])
@ -2618,9 +2622,10 @@ class TestBot:
else:
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:
bot.arbitrary_callback_data = True
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
no_replace_button = InlineKeyboardButton(
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]
assert data == "replace_test"
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
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"])
try:
bot.arbitrary_callback_data = True
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
no_replace_button = InlineKeyboardButton(
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]
assert data == "replace_test"
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
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
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")
try:
bot.arbitrary_callback_data = True
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
no_replace_button = InlineKeyboardButton(
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]
assert data == "replace_test"
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
# 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
async def make_assertion(
endpoint,
@ -2732,7 +2738,6 @@ class TestBot:
return assertion_1 and assertion_2 and assertion_3 and assertion_4
try:
bot.arbitrary_callback_data = True
replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test")
no_replace_button = InlineKeyboardButton(
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)
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
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:
bot.arbitrary_callback_data = True
reply_markup = InlineKeyboardMarkup.from_button(
InlineKeyboardButton(text="text", callback_data="callback_data")
)
@ -2784,7 +2789,6 @@ class TestBot:
assert chat.pinned_message == message
assert chat.pinned_message.reply_markup == reply_markup
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
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
# here we test more extensively.
async def test_arbitrary_callback_data_no_insert(self, monkeypatch, bot):
"""Updates that don't need insertion shouldn.t fail obviously"""
async def test_arbitrary_callback_data_no_insert(self, monkeypatch, cdc_bot):
"""Updates that don't need insertion shouldn't fail obviously"""
bot = cdc_bot
async def post(*args, **kwargs):
update = Update(
@ -2813,7 +2818,6 @@ class TestBot:
return [update.to_dict()]
try:
bot.arbitrary_callback_data = True
monkeypatch.setattr(BaseRequest, "post", post)
await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed
updates = await bot.get_updates(timeout=1)
@ -2822,15 +2826,17 @@ class TestBot:
assert updates[0].update_id == 17
assert updates[0].poll.id == "42"
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
@pytest.mark.parametrize(
"message_type", ["channel_post", "edited_channel_post", "message", "edited_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(
InlineKeyboardButton(text="text", callback_data="callback_data")
)
@ -2880,12 +2886,13 @@ class TestBot:
)
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
async def test_arbitrary_callback_data_get_chat_no_pinned_message(self, super_group_id, bot):
bot.arbitrary_callback_data = True
async def test_arbitrary_callback_data_get_chat_no_pinned_message(
self, super_group_id, cdc_bot
):
bot = cdc_bot
await bot.unpin_all_chat_messages(super_group_id)
try:
@ -2895,16 +2902,17 @@ class TestBot:
assert int(chat.id) == int(super_group_id)
assert chat.pinned_message is None
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
@pytest.mark.parametrize(
"message_type", ["channel_post", "edited_channel_post", "message", "edited_message"]
)
@pytest.mark.parametrize("self_sender", [True, False])
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(
InlineKeyboardButton(text="text", callback_data="callback_data")
)
@ -2938,7 +2946,6 @@ class TestBot:
== reply_markup.inline_keyboard[0][0].callback_data
)
finally:
bot.arbitrary_callback_data = False
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()

View file

@ -74,6 +74,7 @@ class TestCallbackDataCache:
assert cdc.bot is bot
def test_init_and_access__persistent_data(self, bot):
"""This also tests CDC.load_persistent_data."""
keyboard_data = _KeyboardData("123", 456, {"button": 678})
persistent_data = ([keyboard_data.to_tuple()], {"id": "123"})
cdc = CallbackDataCache(bot, persistent_data=persistent_data)

View file

@ -24,11 +24,10 @@ from telegram.ext import DictPersistence
@pytest.fixture(autouse=True)
def reset_callback_data_cache(bot):
def reset_callback_data_cache(cdc_bot):
yield
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
bot.arbitrary_callback_data = False
cdc_bot.callback_data_cache.clear_callback_data()
cdc_bot.callback_data_cache.clear_callback_queries()
@pytest.fixture(scope="function")

View file

@ -41,11 +41,10 @@ def change_directory(tmp_path: Path):
@pytest.fixture(autouse=True)
def reset_callback_data_cache(bot):
def reset_callback_data_cache(cdc_bot):
yield
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()
bot.arbitrary_callback_data = False
cdc_bot.callback_data_cache.clear_callback_data()
cdc_bot.callback_data_cache.clear_callback_queries()
@pytest.fixture(scope="function")
@ -850,10 +849,14 @@ class TestPicklePersistence:
assert conversations_test["name1"] == conversation1
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
data_with_bot = {"current_bot": update.message}
data_with_bot = {"current_bot": update}
await pickle_persistence.update_chat_data(
12345, data_with_bot
) # also calls BotPickler.dumps()
@ -887,8 +890,10 @@ class TestPicklePersistence:
assert (await pp.get_chat_data())[12345]["unknown_bot_in_user"]._bot is None
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)
slot_s = self.SlotsSub("new_var", "private_var")
regular = self.NormalClass(12)

View file

@ -693,11 +693,11 @@ class TestUpdater:
@pytest.mark.parametrize("invalid_data", [True, False], ids=("invalid data", "valid 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
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):
return True
@ -743,7 +743,6 @@ class TestUpdater:
await updater.stop()
finally:
updater.bot.arbitrary_callback_data = False
updater.bot.callback_data_cache.clear_callback_data()
updater.bot.callback_data_cache.clear_callback_queries()