Skip to content

Commit

Permalink
Return Plain Dicts from BasePersistence.get_*_data (#2873)
Browse files Browse the repository at this point in the history
  • Loading branch information
harshil21 committed Feb 1, 2022
1 parent 2d70838 commit 3b1a142
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 150 deletions.
37 changes: 20 additions & 17 deletions telegram/ext/_basepersistence.py
Expand Up @@ -19,7 +19,7 @@
"""This module contains the BasePersistence class."""
from abc import ABC, abstractmethod
from copy import copy
from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, DefaultDict, NamedTuple
from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, NamedTuple

from telegram import Bot
from telegram.ext import ExtBot
Expand Down Expand Up @@ -132,10 +132,10 @@ def __new__(
update_bot_data = instance.update_bot_data
update_callback_data = instance.update_callback_data

def get_user_data_insert_bot() -> DefaultDict[int, UD]:
def get_user_data_insert_bot() -> Dict[int, UD]:
return instance.insert_bot(get_user_data())

def get_chat_data_insert_bot() -> DefaultDict[int, CD]:
def get_chat_data_insert_bot() -> Dict[int, CD]:
return instance.insert_bot(get_chat_data())

def get_bot_data_insert_bot() -> BD:
Expand Down Expand Up @@ -171,10 +171,7 @@ def update_callback_data_replace_bot(data: CDCData) -> None:
setattr(instance, 'update_callback_data', update_callback_data_replace_bot)
return instance

def __init__(
self,
store_data: PersistenceInput = None,
):
def __init__(self, store_data: PersistenceInput = None):
self.store_data = store_data or PersistenceInput()

self.bot: Bot = None # type: ignore[assignment]
Expand Down Expand Up @@ -398,50 +395,56 @@ def _insert_bot(self, obj: object, memo: Dict[int, object]) -> object:
return obj

@abstractmethod
def get_user_data(self) -> DefaultDict[int, UD]:
def get_user_data(self) -> Dict[int, UD]:
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the ``user_data`` if stored, or an empty
:obj:`defaultdict`. In the latter case, the :obj:`defaultdict` should produce values
:obj:`dict`. In the latter case, the dictionary should produce values
corresponding to one of the following:
* :obj:`dict`
* The type from :attr:`telegram.ext.ContextTypes.user_data`
if :class:`telegram.ext.ContextTypes` are used.
if :class:`telegram.ext.ContextTypes` is used.
.. versionchanged:: 14.0
This method may now return a :obj:`dict` instead of a :obj:`collections.defaultdict`
Returns:
DefaultDict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.user_data`]:
Dict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.user_data`]:
The restored user data.
"""

@abstractmethod
def get_chat_data(self) -> DefaultDict[int, CD]:
def get_chat_data(self) -> Dict[int, CD]:
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the ``chat_data`` if stored, or an empty
:obj:`defaultdict`. In the latter case, the :obj:`defaultdict` should produce values
:obj:`dict`. In the latter case, the dictionary should produce values
corresponding to one of the following:
* :obj:`dict`
* The type from :attr:`telegram.ext.ContextTypes.chat_data`
if :class:`telegram.ext.ContextTypes` are used.
if :class:`telegram.ext.ContextTypes` is used.
.. versionchanged:: 14.0
This method may now return a :obj:`dict` instead of a :obj:`collections.defaultdict`
Returns:
DefaultDict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.chat_data`]:
Dict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.chat_data`]:
The restored chat data.
"""

@abstractmethod
def get_bot_data(self) -> BD:
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the ``bot_data`` if stored, or an empty
:obj:`defaultdict`. In the latter case, the :obj:`defaultdict` should produce values
:obj:`dict`. In the latter case, the :obj:`dict` should produce values
corresponding to one of the following:
* :obj:`dict`
* The type from :attr:`telegram.ext.ContextTypes.bot_data`
if :class:`telegram.ext.ContextTypes` are used.
Returns:
DefaultDict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.bot_data`]:
Dict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.bot_data`]:
The restored bot data.
"""

Expand Down
39 changes: 18 additions & 21 deletions telegram/ext/_dictpersistence.py
Expand Up @@ -18,8 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the DictPersistence class."""

from typing import DefaultDict, Dict, Optional, Tuple, cast
from collections import defaultdict
from typing import Dict, Optional, Tuple, cast

from telegram.ext import BasePersistence, PersistenceInput
from telegram._utils.types import JSONDict
Expand Down Expand Up @@ -166,7 +165,7 @@ def __init__(
) from exc

@property
def user_data(self) -> Optional[DefaultDict[int, Dict]]:
def user_data(self) -> Optional[Dict[int, Dict]]:
""":obj:`dict`: The user_data as a dict."""
return self._user_data

Expand All @@ -178,7 +177,7 @@ def user_data_json(self) -> str:
return json.dumps(self.user_data)

@property
def chat_data(self) -> Optional[DefaultDict[int, Dict]]:
def chat_data(self) -> Optional[Dict[int, Dict]]:
""":obj:`dict`: The chat_data as a dict."""
return self._chat_data

Expand All @@ -204,15 +203,15 @@ def bot_data_json(self) -> str:
@property
def callback_data(self) -> Optional[CDCData]:
"""Tuple[List[Tuple[:obj:`str`, :obj:`float`, Dict[:obj:`str`, :obj:`Any`]]], \
Dict[:obj:`str`, :obj:`str`]]: The meta data on the stored callback data.
Dict[:obj:`str`, :obj:`str`]]: The metadata on the stored callback data.
.. versionadded:: 13.6
"""
return self._callback_data

@property
def callback_data_json(self) -> str:
""":obj:`str`: The meta data on the stored callback data as a JSON-string.
""":obj:`str`: The metadata on the stored callback data as a JSON-string.
.. versionadded:: 13.6
"""
Expand All @@ -232,26 +231,24 @@ def conversations_json(self) -> str:
return self._conversations_json
return self._encode_conversations_to_json(self.conversations) # type: ignore[arg-type]

def get_user_data(self) -> DefaultDict[int, Dict[object, object]]:
"""Returns the user_data created from the ``user_data_json`` or an empty
:obj:`defaultdict`.
def get_user_data(self) -> Dict[int, Dict[object, object]]:
"""Returns the user_data created from the ``user_data_json`` or an empty :obj:`dict`.
Returns:
:obj:`defaultdict`: The restored user data.
:obj:`dict`: The restored user data.
"""
if self.user_data is None:
self._user_data = defaultdict(dict)
self._user_data = {}
return self.user_data # type: ignore[return-value]

def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
"""Returns the chat_data created from the ``chat_data_json`` or an empty
:obj:`defaultdict`.
def get_chat_data(self) -> Dict[int, Dict[object, object]]:
"""Returns the chat_data created from the ``chat_data_json`` or an empty :obj:`dict`.
Returns:
:obj:`defaultdict`: The restored chat data.
:obj:`dict`: The restored chat data.
"""
if self.chat_data is None:
self._chat_data = defaultdict(dict)
self._chat_data = {}
return self.chat_data # type: ignore[return-value]

def get_bot_data(self) -> Dict[object, object]:
Expand All @@ -271,7 +268,7 @@ def get_callback_data(self) -> Optional[CDCData]:
Returns:
Tuple[List[Tuple[:obj:`str`, :obj:`float`, Dict[:obj:`str`, :obj:`Any`]]], \
Dict[:obj:`str`, :obj:`str`]]: The restored meta data or :obj:`None`, \
Dict[:obj:`str`, :obj:`str`]]: The restored metadata or :obj:`None`, \
if no data was stored.
"""
if self.callback_data is None:
Expand Down Expand Up @@ -315,7 +312,7 @@ def update_user_data(self, user_id: int, data: Dict) -> None:
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``.
"""
if self._user_data is None:
self._user_data = defaultdict(dict)
self._user_data = {}
if self._user_data.get(user_id) == data:
return
self._user_data[user_id] = data
Expand All @@ -329,7 +326,7 @@ def update_chat_data(self, chat_id: int, data: Dict) -> None:
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``.
"""
if self._chat_data is None:
self._chat_data = defaultdict(dict)
self._chat_data = {}
if self._chat_data.get(chat_id) == data:
return
self._chat_data[chat_id] = data
Expand Down Expand Up @@ -453,7 +450,7 @@ def _decode_conversations_from_json(json_string: str) -> Dict[str, Dict[Tuple, o
return conversations

@staticmethod
def _decode_user_chat_data_from_json(data: str) -> DefaultDict[int, Dict[object, object]]:
def _decode_user_chat_data_from_json(data: str) -> Dict[int, Dict[object, object]]:
"""Helper method to decode chat or user data (that uses ints as keys) from a
JSON-string.
Expand All @@ -463,7 +460,7 @@ def _decode_user_chat_data_from_json(data: str) -> DefaultDict[int, Dict[object,
Returns:
:obj:`dict`: The user/chat_data defaultdict after decoding
"""
tmp: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
tmp: Dict[int, Dict[object, object]] = {}
decoded_data = json.loads(data)
for user, user_data in decoded_data.items():
user = int(user)
Expand Down
2 changes: 1 addition & 1 deletion telegram/ext/_dispatcher.py
Expand Up @@ -739,7 +739,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
50 changes: 20 additions & 30 deletions telegram/ext/_picklepersistence.py
Expand Up @@ -18,7 +18,6 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the PicklePersistence class."""
import pickle
from collections import defaultdict
from pathlib import Path
from typing import (
Any,
Expand All @@ -27,7 +26,6 @@
Tuple,
overload,
cast,
DefaultDict,
)

from telegram._utils.types import FilePathInput
Expand Down Expand Up @@ -138,8 +136,8 @@ def __init__(
self.filepath = Path(filepath)
self.single_file = single_file
self.on_flush = on_flush
self.user_data: Optional[DefaultDict[int, UD]] = None
self.chat_data: Optional[DefaultDict[int, CD]] = None
self.user_data: Optional[Dict[int, UD]] = None
self.chat_data: Optional[Dict[int, CD]] = None
self.bot_data: Optional[BD] = None
self.callback_data: Optional[CDCData] = None
self.conversations: Optional[Dict[str, Dict[Tuple, object]]] = None
Expand All @@ -149,16 +147,16 @@ def _load_singlefile(self) -> None:
try:
with self.filepath.open("rb") as file:
data = pickle.load(file)
self.user_data = defaultdict(self.context_types.user_data, data['user_data'])
self.chat_data = defaultdict(self.context_types.chat_data, data['chat_data'])
self.user_data = data['user_data']
self.chat_data = data['chat_data']
# For backwards compatibility with files not containing bot data
self.bot_data = data.get('bot_data', self.context_types.bot_data())
self.callback_data = data.get('callback_data', {})
self.conversations = data['conversations']
except OSError:
self.conversations = {}
self.user_data = defaultdict(self.context_types.user_data)
self.chat_data = defaultdict(self.context_types.chat_data)
self.user_data = {}
self.chat_data = {}
self.bot_data = self.context_types.bot_data()
self.callback_data = None
except pickle.UnpicklingError as exc:
Expand Down Expand Up @@ -195,41 +193,35 @@ def _dump_file(filepath: Path, data: object) -> None:
with filepath.open("wb") as file:
pickle.dump(data, file)

def get_user_data(self) -> DefaultDict[int, UD]:
"""Returns the user_data from the pickle file if it exists or an empty :obj:`defaultdict`.
def get_user_data(self) -> Dict[int, UD]:
"""Returns the user_data from the pickle file if it exists or an empty :obj:`dict`.
Returns:
DefaultDict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.user_data`]:
The restored user data.
Dict[:obj:`int`, :obj:`dict`]: The restored user data.
"""
if self.user_data:
pass
elif not self.single_file:
data = self._load_file(Path(f"{self.filepath}_user_data"))
if not data:
data = defaultdict(self.context_types.user_data)
else:
data = defaultdict(self.context_types.user_data, data)
data = {}
self.user_data = data
else:
self._load_singlefile()
return self.user_data # type: ignore[return-value]

def get_chat_data(self) -> DefaultDict[int, CD]:
"""Returns the chat_data from the pickle file if it exists or an empty :obj:`defaultdict`.
def get_chat_data(self) -> Dict[int, CD]:
"""Returns the chat_data from the pickle file if it exists or an empty :obj:`dict`.
Returns:
DefaultDict[:obj:`int`, :obj:`dict` | :attr:`telegram.ext.ContextTypes.chat_data`]:
The restored chat data.
Dict[:obj:`int`, :obj:`dict`]: The restored chat data.
"""
if self.chat_data:
pass
elif not self.single_file:
data = self._load_file(Path(f"{self.filepath}_chat_data"))
if not data:
data = defaultdict(self.context_types.chat_data)
else:
data = defaultdict(self.context_types.chat_data, data)
data = {}
self.chat_data = data
else:
self._load_singlefile()
Expand Down Expand Up @@ -260,8 +252,8 @@ 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`]]:
The restored meta data or :obj:`None`, if no data was stored.
Dict[:obj:`str`, :obj:`Any`]]], Dict[:obj:`str`, :obj:`str`]]]:
The restored metadata or :obj:`None`, if no data was stored.
"""
if self.callback_data:
pass
Expand Down Expand Up @@ -323,11 +315,10 @@ def update_user_data(self, user_id: int, data: UD) -> None:
Args:
user_id (:obj:`int`): The user the data might have been changed for.
data (:obj:`dict` | :attr:`telegram.ext.ContextTypes.user_data`): The
:attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``.
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``.
"""
if self.user_data is None:
self.user_data = defaultdict(self.context_types.user_data)
self.user_data = {}
if self.user_data.get(user_id) == data:
return
self.user_data[user_id] = data
Expand All @@ -342,11 +333,10 @@ def update_chat_data(self, chat_id: int, data: CD) -> None:
Args:
chat_id (:obj:`int`): The chat the data might have been changed for.
data (:obj:`dict` | :attr:`telegram.ext.ContextTypes.chat_data`): The
:attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``.
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``.
"""
if self.chat_data is None:
self.chat_data = defaultdict(self.context_types.chat_data)
self.chat_data = {}
if self.chat_data.get(chat_id) == data:
return
self.chat_data[chat_id] = data
Expand Down

0 comments on commit 3b1a142

Please sign in to comment.