Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions telegram/ext/_basepersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ def get_callback_data(self) -> Optional[CDCData]:

Returns:
Optional[Tuple[List[Tuple[:obj:`str`, :obj:`float`, \
Dict[:obj:`str`, :obj:`Any`]]], Dict[:obj:`str`, :obj:`str`]]:
Dict[:obj:`str`, :obj:`Any`]]], Dict[:obj:`str`, :obj:`str`]]]:
The restored meta data or :obj:`None`, if no data was stored.
"""

Expand Down Expand Up @@ -520,6 +520,44 @@ def update_bot_data(self, data: BD) -> None:
The :attr:`telegram.ext.Dispatcher.bot_data`.
"""

@abstractmethod
def update_callback_data(self, data: CDCData) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.

.. versionadded:: 13.6

.. versionchanged:: 14.0
Changed this method into an ``@abstractmethod``.

Args:
data (Optional[Tuple[List[Tuple[:obj:`str`, :obj:`float`, \
Dict[:obj:`str`, :obj:`Any`]]], Dict[:obj:`str`, :obj:`str`]]]):
The relevant data to restore :class:`telegram.ext.CallbackDataCache`.
"""

@abstractmethod
def drop_chat_data(self, chat_id: int) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher`, when using
:meth:`~telegram.ext.Dispatcher.drop_chat_data`.

.. versionadded:: 14.0

Args:
chat_id (:obj:`int`): The chat id to delete from the persistence.
"""

@abstractmethod
def drop_user_data(self, user_id: int) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher`, when using
:meth:`~telegram.ext.Dispatcher.drop_user_data`.

.. versionadded:: 14.0

Args:
user_id (:obj:`int`): The user id to delete from the persistence.
"""

@abstractmethod
def refresh_user_data(self, user_id: int, user_data: UD) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher` before passing the
Expand Down Expand Up @@ -570,22 +608,6 @@ def refresh_bot_data(self, bot_data: BD) -> None:
The ``bot_data``.
"""

@abstractmethod
def update_callback_data(self, data: CDCData) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.

.. versionadded:: 13.6

.. versionchanged:: 14.0
Changed this method into an ``@abstractmethod``.

Args:
data (Optional[Tuple[List[Tuple[:obj:`str`, :obj:`float`, \
Dict[:obj:`str`, :obj:`Any`]]], Dict[:obj:`str`, :obj:`str`]]):
The relevant data to restore :class:`telegram.ext.CallbackDataCache`.
"""

@abstractmethod
def flush(self) -> None:
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
Expand Down
26 changes: 26 additions & 0 deletions telegram/ext/_dictpersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,32 @@ def update_callback_data(self, data: CDCData) -> None:
self._callback_data = (data[0], data[1].copy())
self._callback_data_json = None

def drop_chat_data(self, chat_id: int) -> None:
"""Will delete the specified key from the :attr:`chat_data`.

.. versionadded:: 14.0

Args:
chat_id (:obj:`int`): The chat id to delete from the persistence.
"""
if self._chat_data is None:
return
self._chat_data.pop(chat_id, None)
self._chat_data_json = None

def drop_user_data(self, user_id: int) -> None:
"""Will delete the specified key from the :attr:`user_data`.

.. versionadded:: 14.0

Args:
user_id (:obj:`int`): The user id to delete from the persistence.
"""
if self._user_data is None:
return
self._user_data.pop(user_id, None)
self._user_data_json = None

def refresh_user_data(self, user_id: int, user_data: Dict) -> None:
"""Does nothing.

Expand Down
83 changes: 66 additions & 17 deletions telegram/ext/_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
TypeVar,
TYPE_CHECKING,
Tuple,
Mapping,
)
from types import MappingProxyType
from uuid import uuid4

from telegram import Update
Expand Down Expand Up @@ -78,11 +80,11 @@ def callback(update, context):
Note:
Has no effect, if the handler or error handler is run asynchronously.

Attributes:
state (:obj:`object`): Optional. The next state of the conversation.

Args:
state (:obj:`object`, optional): The next state of the conversation.

Attributes:
state (:obj:`object`): Optional. The next state of the conversation.
"""

__slots__ = ('state',)
Expand Down Expand Up @@ -111,8 +113,24 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
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`.
user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user.
chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat.
chat_data (:obj:`types.MappingProxyType`): A dictionary handlers can use to store data for
the chat.

.. versionchanged:: 14.0
:attr:`chat_data` is now read-only

.. tip::
Manually modifying :attr:`chat_data` is almost never needed and unadvisable.

user_data (:obj:`types.MappingProxyType`): A dictionary handlers can use to store data for
the user.

.. versionchanged:: 14.0
:attr:`user_data` is now read-only

.. tip::
Manually modifying :attr:`user_data` is almost never needed and unadvisable.

bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
store data that should be persistent over restarts.
Expand Down Expand Up @@ -144,7 +162,9 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
'persistence',
'update_queue',
'job_queue',
'_user_data',
'user_data',
'_chat_data',
'chat_data',
'bot_data',
'_update_persistence_lock',
Expand Down Expand Up @@ -198,10 +218,15 @@ def __init__(
stacklevel=stack_level,
)

self.user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data)
self.chat_data: DefaultDict[int, CD] = defaultdict(self.context_types.chat_data)
self._user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data)
self._chat_data: DefaultDict[int, CD] = defaultdict(self.context_types.chat_data)
# Read only mapping-
self.user_data: Mapping[int, UD] = MappingProxyType(self._user_data)
self.chat_data: Mapping[int, CD] = MappingProxyType(self._chat_data)

self.bot_data = self.context_types.bot_data()
self.persistence: Optional[BasePersistence] = None

self.persistence: Optional[BasePersistence]
self._update_persistence_lock = Lock()
if persistence:
if not isinstance(persistence, BasePersistence):
Expand All @@ -213,13 +238,9 @@ def __init__(
self.persistence.set_bot(self.bot)

if self.persistence.store_data.user_data:
self.user_data = self.persistence.get_user_data()
if not isinstance(self.user_data, defaultdict):
raise ValueError("user_data must be of type defaultdict")
self._user_data.update(self.persistence.get_user_data())
if self.persistence.store_data.chat_data:
self.chat_data = self.persistence.get_chat_data()
if not isinstance(self.chat_data, defaultdict):
raise ValueError("chat_data must be of type defaultdict")
self._chat_data.update(self.persistence.get_chat_data())
if self.persistence.store_data.bot_data:
self.bot_data = self.persistence.get_bot_data()
if not isinstance(self.bot_data, self.context_types.bot_data):
Expand All @@ -230,7 +251,7 @@ def __init__(
persistent_data = self.persistence.get_callback_data()
if persistent_data is not None:
if not isinstance(persistent_data, tuple) and len(persistent_data) != 2:
raise ValueError('callback_data must be a 2-tuple')
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]
Expand Down Expand Up @@ -631,6 +652,34 @@ def remove_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
if not self.handlers[group]:
del self.handlers[group]

def drop_chat_data(self, chat_id: int) -> None:
"""Used for deleting a key from the :attr:`chat_data`.

.. versionadded:: 14.0

Args:
chat_id (:obj:`int`): The chat id to delete from the persistence. The entry
will be deleted even if it is not empty.
"""
self._chat_data.pop(chat_id, None) # type: ignore[arg-type]

if self.persistence:
self.persistence.drop_chat_data(chat_id)

def drop_user_data(self, user_id: int) -> None:
"""Used for deleting a key from the :attr:`user_data`.

.. versionadded:: 14.0

Args:
user_id (:obj:`int`): The user id to delete from the persistence. The entry
will be deleted even if it is not empty.
"""
self._user_data.pop(user_id, None) # type: ignore[arg-type]

if self.persistence:
self.persistence.drop_user_data(user_id)

def update_persistence(self, update: object = None) -> None:
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.

Expand All @@ -643,7 +692,7 @@ def update_persistence(self, update: object = None) -> None:

def __update_persistence(self, update: object = None) -> None:
if self.persistence:
# We use list() here in order to decouple chat_ids from self.chat_data, as dict view
# We use list() here in order to decouple chat_ids from self._chat_data, as dict view
# objects will change, when the dict does and we want to loop over chat_ids
chat_ids = list(self.chat_data.keys())
user_ids = list(self.user_data.keys())
Expand Down Expand Up @@ -758,7 +807,7 @@ def dispatch_error(

Returns:
:obj:`bool`: :obj:`True` if one of the error handlers raised
:class:`telegram.ext.DispatcherHandlerStop`. :obj:`False`, otherwise.
:class:`telegram.ext.DispatcherHandlerStop`. :obj:`False`, otherwise.
"""
async_args = None if not promise else promise.args
async_kwargs = None if not promise else promise.kwargs
Expand Down
38 changes: 38 additions & 0 deletions telegram/ext/_picklepersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,44 @@ def update_callback_data(self, data: CDCData) -> None:
else:
self._dump_singlefile()

def drop_chat_data(self, chat_id: int) -> None:
"""Will delete the specified key from the :attr:`chat_data` and depending on
:attr:`on_flush` save the pickle file.

.. versionadded:: 14.0

Args:
chat_id (:obj:`int`): The chat id to delete from the persistence.
"""
if self.chat_data is None:
return
self.chat_data.pop(chat_id, None) # type: ignore[arg-type]

if not self.on_flush:
if not self.single_file:
self._dump_file(Path(f"{self.filepath}_chat_data"), self.chat_data)
else:
self._dump_singlefile()

def drop_user_data(self, user_id: int) -> None:
"""Will delete the specified key from the :attr:`user_data` and depending on
:attr:`on_flush` save the pickle file.

.. versionadded:: 14.0

Args:
user_id (:obj:`int`): The user id to delete from the persistence.
"""
if self.user_data is None:
return
self.user_data.pop(user_id, None) # type: ignore[arg-type]

if not self.on_flush:
if not self.single_file:
self._dump_file(Path(f"{self.filepath}_user_data"), self.user_data)
else:
self._dump_singlefile()

def refresh_user_data(self, user_id: int, user_data: UD) -> None:
"""Does nothing.

Expand Down
8 changes: 5 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from threading import Thread, Event
from time import sleep
from typing import Callable, List, Iterable, Any
from types import MappingProxyType

import pytest
import pytz
Expand Down Expand Up @@ -194,10 +195,11 @@ def dp(_dp):
# Reset the dispatcher first
while not _dp.update_queue.empty():
_dp.update_queue.get(False)
_dp.chat_data = defaultdict(dict)
_dp.user_data = defaultdict(dict)
_dp._chat_data = defaultdict(dict)
_dp._user_data = defaultdict(dict)
_dp.chat_data = MappingProxyType(_dp._chat_data) # Rebuild the mapping so it updates
_dp.user_data = MappingProxyType(_dp._user_data)
_dp.bot_data = {}
_dp.persistence = None
_dp.handlers = {}
_dp.error_handlers = {}
_dp.exception_event = Event()
Expand Down
4 changes: 1 addition & 3 deletions tests/test_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ def test_creation(self, animation):
assert animation.file_unique_id != ''

def test_expected_values(self, animation):
assert animation.file_size == self.file_size
assert animation.mime_type == self.mime_type
assert animation.file_name == self.file_name
assert animation.file_name.startswith('game.gif') == self.file_name.startswith('game.gif')
assert isinstance(animation.thumb, PhotoSize)

@flaky(3, 1)
Expand Down Expand Up @@ -122,7 +121,6 @@ def make_assertion(url, data, **kwargs):
def test_get_and_download(self, bot, animation):
new_file = bot.get_file(animation.file_id)

assert new_file.file_size == self.file_size
assert new_file.file_id == animation.file_id
assert new_file.file_path.startswith('https://')

Expand Down
Loading