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
30 changes: 28 additions & 2 deletions telegram/ext/basepersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class BasePersistence(object):

All relevant methods must be overwritten. This means:

* If :attr:`store_bot_data` is ``True`` you must overwrite :meth:`get_bot_data` and
:meth:`update_bot_data`.
* If :attr:`store_chat_data` is ``True`` you must overwrite :meth:`get_chat_data` and
:meth:`update_chat_data`.
* If :attr:`store_user_data` is ``True`` you must overwrite :meth:`get_user_data` and
Expand All @@ -38,17 +40,22 @@ class BasePersistence(object):
persistence class.
store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this
persistence class.
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
persistence class.

Args:
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
persistence class. Default is ``True`` .
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
persistence class. Default is ``True`` .
"""

def __init__(self, store_user_data=True, store_chat_data=True):
def __init__(self, store_user_data=True, store_chat_data=True, store_bot_data=True):
self.store_user_data = store_user_data
self.store_chat_data = store_chat_data
self.store_bot_data = store_bot_data

def get_user_data(self):
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
Expand All @@ -70,6 +77,16 @@ def get_chat_data(self):
"""
raise NotImplementedError

def get_bot_data(self):
""""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
``dict``.

Returns:
:obj:`defaultdict`: The restored bot data.
"""
raise NotImplementedError

def get_conversations(self, name):
""""Will be called by :class:`telegram.ext.Dispatcher` when a
:class:`telegram.ext.ConversationHandler` is added if
Expand Down Expand Up @@ -111,7 +128,16 @@ def update_chat_data(self, chat_id, data):

Args:
chat_id (:obj:`int`): The chat the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [user_id].
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
"""
raise NotImplementedError

def update_bot_data(self, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.

Args:
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data` .
"""
raise NotImplementedError

Expand Down
13 changes: 13 additions & 0 deletions telegram/ext/callbackcontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class CallbackContext(object):
that you think you added will not be present.

Attributes:
bot_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
update it will be the same ``dict``.
chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
update from the same chat id it will be the same ``dict``.

Expand Down Expand Up @@ -80,6 +82,7 @@ def __init__(self, dispatcher):
raise ValueError('CallbackContext should not be used with a non context aware '
'dispatcher!')
self._dispatcher = dispatcher
self._bot_data = dispatcher.bot_data
self._chat_data = None
self._user_data = None
self.args = None
Expand All @@ -92,6 +95,15 @@ def dispatcher(self):
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
return self._dispatcher

@property
def bot_data(self):
return self._bot_data

@bot_data.setter
def bot_data(self, value):
raise AttributeError("You can not assign a new value to bot_data, see "
"https://git.io/fjxKe")

@property
def chat_data(self):
return self._chat_data
Expand Down Expand Up @@ -119,6 +131,7 @@ def from_error(cls, update, error, dispatcher):
@classmethod
def from_update(cls, update, dispatcher):
self = cls(dispatcher)

if update is not None and isinstance(update, Update):
chat = update.effective_chat
user = update.effective_user
Expand Down
69 changes: 64 additions & 5 deletions telegram/ext/dictpersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,51 @@


class DictPersistence(BasePersistence):
"""Using python's dicts and json for making you bot persistent.
"""Using python's dicts and json for making your bot persistent.

Attributes:
store_user_data (:obj:`bool`): Whether user_data should be saved by this
persistence class.
store_chat_data (:obj:`bool`): Whether chat_data should be saved by this
persistence class.
store_bot_data (:obj:`bool`): Whether bot_data should be saved by this
persistence class.

Args:
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
persistence class. Default is ``True`` .
user_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
user_data on creating this persistence. Default is ``""``.
chat_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
chat_data on creating this persistence. Default is ``""``.
bot_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
bot_data on creating this persistence. Default is ``""``.
conversations_json (:obj:`str`, optional): Json string that will be used to reconstruct
conversation on creating this persistence. Default is ``""``.
"""

def __init__(self, store_user_data=True, store_chat_data=True, user_data_json='',
chat_data_json='', conversations_json=''):
self.store_user_data = store_user_data
self.store_chat_data = store_chat_data
def __init__(self,
store_user_data=True,
store_chat_data=True,
store_bot_data=True,
user_data_json='',
chat_data_json='',
bot_data_json='',
conversations_json=''):
super(DictPersistence, self).__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
self._user_data = None
self._chat_data = None
self._bot_data = None
self._conversations = None
self._user_data_json = None
self._chat_data_json = None
self._bot_data_json = None
self._conversations_json = None
if user_data_json:
try:
Expand All @@ -74,6 +89,14 @@ def __init__(self, store_user_data=True, store_chat_data=True, user_data_json=''
self._chat_data_json = chat_data_json
except (ValueError, AttributeError):
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON")
if bot_data_json:
try:
self._bot_data = json.loads(bot_data_json)
self._bot_data_json = bot_data_json
except (ValueError, AttributeError):
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON")
if not isinstance(self._bot_data, dict):
raise TypeError("bot_data_json must be serialized dict")

if conversations_json:
try:
Expand Down Expand Up @@ -108,6 +131,19 @@ def chat_data_json(self):
else:
return json.dumps(self.chat_data)

@property
def bot_data(self):
""":obj:`dict`: The bot_data as a dict"""
return self._bot_data

@property
def bot_data_json(self):
""":obj:`str`: The bot_data serialized as a JSON-string."""
if self._bot_data_json:
return self._bot_data_json
else:
return json.dumps(self.bot_data)

@property
def conversations(self):
""":obj:`dict`: The conversations as a dict"""
Expand Down Expand Up @@ -145,6 +181,18 @@ def get_chat_data(self):
self._chat_data = defaultdict(dict)
return deepcopy(self.chat_data)

def get_bot_data(self):
"""Returns the bot_data created from the ``bot_data_json`` or an empty dict.

Returns:
:obj:`defaultdict`: The restored user data.
"""
if self.bot_data:
pass
else:
self._bot_data = {}
return deepcopy(self.bot_data)

def get_conversations(self, name):
"""Returns the conversations created from the ``conversations_json`` or an empty
defaultdict.
Expand Down Expand Up @@ -194,3 +242,14 @@ def update_chat_data(self, chat_id, data):
return
self._chat_data[chat_id] = data
self._chat_data_json = None

def update_bot_data(self, data):
"""Will update the bot_data (if changed).

Args:
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`.
"""
if self._bot_data == data:
return
self._bot_data = data.copy()
self._bot_data_json = None
22 changes: 20 additions & 2 deletions telegram/ext/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class Dispatcher(object):
decorator.
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.
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 @@ -121,8 +122,8 @@ def __init__(self,
TelegramDeprecationWarning, stacklevel=3)

self.user_data = defaultdict(dict)
""":obj:`dict`: A dictionary handlers can use to store data for the user."""
self.chat_data = defaultdict(dict)
self.bot_data = {}
if persistence:
if not isinstance(persistence, BasePersistence):
raise TypeError("persistence should be based on telegram.ext.BasePersistence")
Expand All @@ -135,6 +136,10 @@ def __init__(self,
self.chat_data = self.persistence.get_chat_data()
if not isinstance(self.chat_data, defaultdict):
raise ValueError("chat_data must be of type defaultdict")
if self.persistence.store_bot_data:
self.bot_data = self.persistence.get_bot_data()
if not isinstance(self.bot_data, dict):
raise ValueError("bot_data must be of type dict")
else:
self.persistence = None

Expand Down Expand Up @@ -327,6 +332,17 @@ def persist_update(update):

"""
if self.persistence and isinstance(update, Update):
if self.persistence.store_bot_data:
try:
self.persistence.update_bot_data(self.bot_data)
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving bot data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
if self.persistence.store_chat_data and update.effective_chat:
chat_id = update.effective_chat.id
try:
Expand Down Expand Up @@ -455,9 +471,11 @@ def remove_handler(self, handler, group=DEFAULT_GROUP):
self.groups.remove(group)

def update_persistence(self):
"""Update :attr:`user_data` and :attr:`chat_data` in :attr:`persistence`.
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
"""
if self.persistence:
if self.persistence.store_bot_data:
self.persistence.update_bot_data(self.bot_data)
if self.persistence.store_chat_data:
for chat_id in self.chat_data:
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
Expand Down
Loading