From 39db13f6689fb8ad326dda53633b5418702787ab Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Mon, 7 Jan 2019 19:03:18 +0100 Subject: [PATCH 1/3] Add bot_data as global memory --- telegram/ext/basepersistence.py | 32 ++++++++++- telegram/ext/callbackqueryhandler.py | 20 +++++-- telegram/ext/choseninlineresulthandler.py | 20 +++++-- telegram/ext/commandhandler.py | 19 +++++-- telegram/ext/dictpersistence.py | 68 ++++++++++++++++++++++- telegram/ext/dispatcher.py | 38 +++++++++---- telegram/ext/handler.py | 20 +++++-- telegram/ext/inlinequeryhandler.py | 20 +++++-- telegram/ext/messagehandler.py | 18 ++++-- telegram/ext/picklepersistence.py | 59 ++++++++++++++++++-- telegram/ext/precheckoutqueryhandler.py | 20 +++++-- telegram/ext/regexhandler.py | 18 ++++-- telegram/ext/shippingqueryhandler.py | 20 +++++-- telegram/ext/stringcommandhandler.py | 16 +++++- telegram/ext/stringregexhandler.py | 17 +++++- telegram/ext/typehandler.py | 23 +++++++- 16 files changed, 349 insertions(+), 79 deletions(-) diff --git a/telegram/ext/basepersistence.py b/telegram/ext/basepersistence.py index f62491ee903..c7d5e249e31 100644 --- a/telegram/ext/basepersistence.py +++ b/telegram/ext/basepersistence.py @@ -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 @@ -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 @@ -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 + ``defaultdict(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 @@ -101,7 +118,7 @@ def update_user_data(self, user_id, data): Args: user_id (:obj:`int`): The user the data might have been changed for. - data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data`[user_id]. + data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id]. """ raise NotImplementedError @@ -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 diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index 3c792e51f07..6b575c68495 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -47,12 +47,16 @@ class CallbackQueryHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. @@ -79,6 +83,8 @@ class CallbackQueryHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -90,13 +96,15 @@ def __init__(self, pass_groups=False, pass_groupdict=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): super(CallbackQueryHandler, self).__init__( callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) if isinstance(pattern, string_types): pattern = re.compile(pattern) diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py index 0b02456d388..2e1d53325f2 100644 --- a/telegram/ext/choseninlineresulthandler.py +++ b/telegram/ext/choseninlineresulthandler.py @@ -36,12 +36,16 @@ class ChosenInlineResultHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. @@ -59,6 +63,8 @@ class ChosenInlineResultHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -67,13 +73,15 @@ def __init__(self, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): super(ChosenInlineResultHandler, self).__init__( callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index f38f9a3aa04..545a7425903 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -49,11 +49,16 @@ class CommandHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``.nt in. For each update from the same user or in the same chat, it will be the same ``dict``. Args: @@ -84,6 +89,8 @@ class CommandHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -96,13 +103,15 @@ def __init__(self, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): super(CommandHandler, self).__init__( callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) if isinstance(command, string_types): self.command = [command.lower()] diff --git a/telegram/ext/dictpersistence.py b/telegram/ext/dictpersistence.py index 28ca4727331..5824ec8a623 100644 --- a/telegram/ext/dictpersistence.py +++ b/telegram/ext/dictpersistence.py @@ -29,36 +29,56 @@ 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`): 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 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 ``""``. + + Note: + When using :class:DictPersistence:, use strings as keys for ``user_data``, ``chat_data`` + and ``bot_data``. Other objects used as keys will be serialized as string and not be + cerrectly restored when calling ``json.loads``. """ - def __init__(self, store_user_data=True, store_chat_data=True, user_data_json='', - chat_data_json='', conversations_json=''): + 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=''): self.store_user_data = store_user_data self.store_chat_data = store_chat_data + self.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: @@ -72,6 +92,12 @@ 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 conversations_json: try: @@ -106,6 +132,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""" @@ -143,6 +182,18 @@ def get_chat_data(self): self._chat_data = defaultdict(dict) return self.chat_data.copy() + def get_bot_data(self): + """Returns the bot_data created from the ``bot_data_json`` or an empty defaultdict. + + Returns: + :obj:`defaultdict`: The restored user data. + """ + if self.bot_data: + pass + else: + self._bot_data = {} + return self.bot_data.copy() + def get_conversations(self, name): """Returns the conversations created from the ``conversations_json`` or an empty defaultdict. @@ -192,3 +243,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 diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index 2ee47f11d4f..6a4a25cbdf8 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -74,6 +74,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:`defaultdict`): A dictionary handlers can user to store data for the bot. persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to store data that should be persistent over restarts @@ -101,6 +102,7 @@ def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue self.workers = workers self.user_data = defaultdict(dict) self.chat_data = defaultdict(dict) + self.bot_data = defaultdict(dict) if persistence: if not isinstance(persistence, BasePersistence): raise TypeError("persistence should be based on telegram.ext.BasePersistence") @@ -113,6 +115,10 @@ def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue 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 @@ -299,19 +305,29 @@ def process_update(self, update): try: for handler in (x for x in self.handlers[group] if x.check_update(update)): handler.handle_update(update, self) - if self.persistence and isinstance(update, Update): - if self.persistence.store_chat_data and update.effective_chat: - chat_id = update.effective_chat.id + if self.persistence: + if self.persistence.store_bot_data: try: - self.persistence.update_chat_data(chat_id, self.chat_data[chat_id]) + self.persistence.update_bot_data(self.bot_data) except Exception: - self.logger.exception('Saving chat data raised an error') - if self.persistence.store_user_data and update.effective_user: - user_id = update.effective_user.id - try: - self.persistence.update_user_data(user_id, self.user_data[user_id]) - except Exception: - self.logger.exception('Saving user data raised an error') + self.logger.exception('Saving bot data raised an error') + + # user_data and chat_data are only available for telegram.Updates + if isinstance(update, Update): + if self.persistence.store_chat_data and update.effective_chat: + chat_id = update.effective_chat.id + try: + self.persistence.update_chat_data(chat_id, + self.chat_data[chat_id]) + except Exception: + self.logger.exception('Saving chat data raised an error') + if self.persistence.store_user_data and update.effective_user: + user_id = update.effective_user.id + try: + self.persistence.update_user_data(user_id, + self.user_data[user_id]) + except Exception: + self.logger.exception('Saving user data raised an error') break # Stop processing with any other handler. diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 5a3b6dc3606..73d476f9cba 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -32,12 +32,16 @@ class Handler(object): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. @@ -55,6 +59,8 @@ class Handler(object): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -63,12 +69,14 @@ def __init__(self, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): self.callback = callback self.pass_update_queue = pass_update_queue self.pass_job_queue = pass_job_queue self.pass_user_data = pass_user_data self.pass_chat_data = pass_chat_data + self.pass_bot_data = pass_bot_data def check_update(self, update): """ @@ -121,5 +129,7 @@ def collect_optional_args(self, dispatcher, update=None): if self.pass_chat_data: optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None] + if self.pass_bot_data: + optional_args['bot_data'] = dispatcher.bot_data return optional_args diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index 772cdf90fdc..1a48d3a2910 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -47,12 +47,16 @@ class InlineQueryHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. @@ -79,6 +83,8 @@ class InlineQueryHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ def __init__(self, @@ -89,13 +95,15 @@ def __init__(self, pass_groups=False, pass_groupdict=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): super(InlineQueryHandler, self).__init__( callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) if isinstance(pattern, string_types): pattern = re.compile(pattern) diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index dadbee55c47..6ebe3408c89 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -39,6 +39,8 @@ class MessageHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. message_updates (:obj:`bool`): Optional. Should "normal" message updates be handled? Default is ``True``. channel_post_updates (:obj:`bool`): Optional. Should channel posts updates be handled? @@ -49,10 +51,12 @@ class MessageHandler(Handler): Default is ``False`` - Deprecated. use edited_updates instead. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from @@ -74,6 +78,8 @@ class MessageHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. message_updates (:obj:`bool`, optional): Should "normal" message updates be handled? Default is ``True``. channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled? @@ -96,6 +102,7 @@ def __init__(self, pass_job_queue=False, pass_user_data=False, pass_chat_data=False, + pass_bot_data=False, message_updates=True, channel_post_updates=True, edited_updates=False): @@ -111,7 +118,8 @@ def __init__(self, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) self.filters = filters self.message_updates = message_updates self.channel_post_updates = channel_post_updates diff --git a/telegram/ext/picklepersistence.py b/telegram/ext/picklepersistence.py index ed3c06cf900..942fc1f4a06 100644 --- a/telegram/ext/picklepersistence.py +++ b/telegram/ext/picklepersistence.py @@ -33,6 +33,8 @@ class PicklePersistence(BasePersistence): persistence class. store_chat_data (:obj:`bool`): Optional. Whether user_data should be saved by this persistence class. + store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this + persistence class. single_file (:obj:`bool`): Optional. When ``False`` will store 3 sperate files of `filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is ``True``. @@ -47,6 +49,8 @@ class PicklePersistence(BasePersistence): 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`` . single_file (:obj:`bool`, optional): When ``False`` will store 3 sperate files of `filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is ``True``. @@ -55,15 +59,21 @@ class PicklePersistence(BasePersistence): transaction. Default is ``False``. """ - def __init__(self, filename, store_user_data=True, store_chat_data=True, singe_file=True, + def __init__(self, filename, + store_user_data=True, + store_chat_data=True, + store_bot_data=True, + singe_file=True, on_flush=False): self.filename = filename self.store_user_data = store_user_data self.store_chat_data = store_chat_data + self.store_bot_data = store_bot_data self.single_file = singe_file self.on_flush = on_flush self.user_data = None self.chat_data = None + self.bot_data = None self.conversations = None def load_singlefile(self): @@ -73,11 +83,13 @@ def load_singlefile(self): all = pickle.load(f) self.user_data = defaultdict(dict, all['user_data']) self.chat_data = defaultdict(dict, all['chat_data']) + self.bot_data = all['bot_data'] self.conversations = all['conversations'] except IOError: self.conversations = {} self.user_data = defaultdict(dict) self.chat_data = defaultdict(dict) + self.bot_data = {} except pickle.UnpicklingError: raise TypeError("File {} does not contain valid pickle data".format(filename)) except Exception: @@ -97,7 +109,7 @@ def load_file(self, filename): def dump_singlefile(self): with open(self.filename, "wb") as f: all = {'conversations': self.conversations, 'user_data': self.user_data, - 'chat_data': self.chat_data} + 'chat_data': self.chat_data, 'bot_data': self.bot_data} pickle.dump(all, f) def dump_file(self, filename, data): @@ -144,6 +156,24 @@ def get_chat_data(self): self.load_singlefile() return self.chat_data.copy() + def get_bot_data(self): + """Returns the bot_data from the pickle file if it exsists or an empty defaultdict. + + Returns: + :obj:`defaultdict`: The restored bot data. + """ + if self.bot_data: + pass + elif not self.single_file: + filename = "{}_bot_data".format(self.filename) + data = self.load_file(filename) + if not data: + data = {} + self.bot_data = data + else: + self.load_singlefile() + return self.bot_data.copy() + def get_conversations(self, name): """Returns the conversations from the pickle file if it exsists or an empty defaultdict. @@ -190,11 +220,12 @@ def update_user_data(self, user_id, data): Args: user_id (:obj:`int`): The user the data might have been changed for. - data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data`[user_id]. + data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id]. """ + print('same ' + str(self.user_data.get(user_id) is data)) if self.user_data.get(user_id) == data: return - self.user_data[user_id] = data + self.user_data[user_id] = data.copy() if not self.on_flush: if not self.single_file: filename = "{}_user_data".format(self.filename) @@ -208,7 +239,7 @@ 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`[chat_id]. + data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id]. """ if self.chat_data.get(chat_id) == data: return @@ -220,6 +251,23 @@ def update_chat_data(self, chat_id, data): else: self.dump_singlefile() + def update_bot_data(self, data): + """Will update the bot_data (if changed) and depending on :attr:`on_flush` save the + pickle file. + + Args: + data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`. + """ + if self.bot_data == data: + return + self.bot_data = data.copy() + if not self.on_flush: + if not self.single_file: + filename = "{}_bot_data".format(self.filename) + self.dump_file(filename, self.bot_data) + else: + self.dump_singlefile() + def flush(self): """If :attr:`on_flush` is set to ``True``. Will save all data in memory to pickle file(s). If it's ``False`` will just pass. @@ -232,4 +280,5 @@ def flush(self): else: self.dump_file("{}_user_data".format(self.filename), self.user_data) self.dump_file("{}_chat_data".format(self.filename), self.chat_data) + self.dump_file("{}_bot_data".format(self.filename), self.bot_data) self.dump_file("{}_conversations".format(self.filename), self.conversations) diff --git a/telegram/ext/precheckoutqueryhandler.py b/telegram/ext/precheckoutqueryhandler.py index e3bbe7c8141..30771f0df06 100644 --- a/telegram/ext/precheckoutqueryhandler.py +++ b/telegram/ext/precheckoutqueryhandler.py @@ -35,12 +35,16 @@ class PreCheckoutQueryHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. @@ -58,6 +62,8 @@ class PreCheckoutQueryHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -66,13 +72,15 @@ def __init__(self, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): super(PreCheckoutQueryHandler, self).__init__( callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index 934930dadde..f3b3b0c400e 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -50,12 +50,16 @@ class RegexHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. @@ -80,6 +84,8 @@ class RegexHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. message_updates (:obj:`bool`, optional): Should "normal" message updates be handled? Default is ``True``. channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled? @@ -103,6 +109,7 @@ def __init__(self, pass_job_queue=False, pass_user_data=False, pass_chat_data=False, + pass_bot_data=False, allow_edited=False, message_updates=True, channel_post_updates=False, @@ -120,7 +127,8 @@ def __init__(self, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) if isinstance(pattern, string_types): pattern = re.compile(pattern) diff --git a/telegram/ext/shippingqueryhandler.py b/telegram/ext/shippingqueryhandler.py index 663a3f725d0..4da3d9ac96b 100644 --- a/telegram/ext/shippingqueryhandler.py +++ b/telegram/ext/shippingqueryhandler.py @@ -35,12 +35,16 @@ class ShippingQueryHandler(Handler): the callback function. pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. + :attr:`pass_user_data`, :attr:`pass_chat_data` and :attr:`pass_bot_data` determine whether + a ``dict`` you can use to keep any data in will be sent to the :attr:`callback` function. + :attr:`pass_user_data` and :attr:`pass_chat_data` are related to either the user or the + chat that the update was sent in. For each update from the same user or in the same chat, + it will be the same ``dict``. :attr:`bot_data` is available independent of updates and will + always be the same ``dict``. Args: callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments. @@ -58,6 +62,8 @@ class ShippingQueryHandler(Handler): ``user_data`` will be passed to the callback function. Default is ``False``. pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called ``chat_data`` will be passed to the callback function. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -66,13 +72,15 @@ def __init__(self, pass_update_queue=False, pass_job_queue=False, pass_user_data=False, - pass_chat_data=False): + pass_chat_data=False, + pass_bot_data=False): super(ShippingQueryHandler, self).__init__( callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue, pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data) + pass_chat_data=pass_chat_data, + pass_bot_data=pass_bot_data) def check_update(self, update): """Determines whether an update should be passed to this handlers :attr:`callback`. diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index 9c76ecc4e4a..0316f7ea6a5 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -39,7 +39,13 @@ class StringCommandHandler(Handler): passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. + Note: + :attr:`pass_bot_data` determines whether a ``dict`` you can use to keep any data in will be + sent to the :attr:`callback` function. :attr:`bot_data` is available independent of updates + and will always be the same ``dict``. Args: command (:obj:`str`): The command this handler should listen for. @@ -58,6 +64,8 @@ class StringCommandHandler(Handler): ``job_queue`` will be passed to the callback function. It will be a class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` which can be used to schedule new jobs. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -66,9 +74,13 @@ def __init__(self, callback, pass_args=False, pass_update_queue=False, - pass_job_queue=False): + pass_job_queue=False, + pass_bot_data=False): super(StringCommandHandler, self).__init__( - callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) + callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue, + pass_bot_data=pass_bot_data) self.command = command self.pass_args = pass_args diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index f62b3c574d4..d412ad754a5 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -46,6 +46,13 @@ class StringRegexHandler(Handler): passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. + + Note: + :attr:`pass_bot_data` determines whether a ``dict`` you can use to keep any data in will be + sent to the :attr:`callback` function. :attr:`bot_data` is available independent of updates + and will always be the same ``dict``. Args: pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. @@ -66,6 +73,8 @@ class StringRegexHandler(Handler): ``job_queue`` will be passed to the callback function. It will be a :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` which can be used to schedule new jobs. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ @@ -75,9 +84,13 @@ def __init__(self, pass_groups=False, pass_groupdict=False, pass_update_queue=False, - pass_job_queue=False): + pass_job_queue=False, + pass_bot_data=False): super(StringRegexHandler, self).__init__( - callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) + callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue, + pass_bot_data=pass_bot_data) if isinstance(pattern, string_types): pattern = re.compile(pattern) diff --git a/telegram/ext/typehandler.py b/telegram/ext/typehandler.py index bb388e428b2..2e1ee275424 100644 --- a/telegram/ext/typehandler.py +++ b/telegram/ext/typehandler.py @@ -33,6 +33,13 @@ class TypeHandler(Handler): passed to the callback function. pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to the callback function. + pass_bot_data (:obj:`bool`): Optional. Determines wether ``bot_data`` will be passed to + the callback function. + + Note: + :attr:`pass_bot_data` determines whether a ``dict`` you can use to keep any data in will be + sent to the :attr:`callback` function. :attr:`bot_data` is available independent of updates + and will always be the same ``dict``. Args: type (:obj:`type`): The ``type`` of updates this handler should process, as @@ -50,13 +57,23 @@ class TypeHandler(Handler): ``job_queue`` will be passed to the callback function. It will be a :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` which can be used to schedule new jobs. Default is ``False``. + pass_bot_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called + ``bot_data`` will be passed to the callback function. Default is ``False``. """ - def __init__(self, type, callback, strict=False, pass_update_queue=False, - pass_job_queue=False): + def __init__(self, + type, + callback, + strict=False, + pass_update_queue=False, + pass_job_queue=False, + pass_bot_data=False): super(TypeHandler, self).__init__( - callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) + callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue, + pass_bot_data=pass_bot_data) self.type = type self.strict = strict From 2cd9b4008f122d761bfed937931441fdf7732724 Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Mon, 7 Jan 2019 19:10:54 +0100 Subject: [PATCH 2/3] Fix bot_data initialization by Dispatcher --- telegram/ext/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index 6a4a25cbdf8..9a2dad05d3e 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -102,7 +102,7 @@ def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue self.workers = workers self.user_data = defaultdict(dict) self.chat_data = defaultdict(dict) - self.bot_data = defaultdict(dict) + self.bot_data = {} if persistence: if not isinstance(persistence, BasePersistence): raise TypeError("persistence should be based on telegram.ext.BasePersistence") From cf9448509b9da83fd4df1350cff0105c5804401e Mon Sep 17 00:00:00 2001 From: Bibo-Joshi Date: Mon, 7 Jan 2019 19:16:05 +0100 Subject: [PATCH 3/3] Undo change in PicklePersistence --- telegram/ext/picklepersistence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/picklepersistence.py b/telegram/ext/picklepersistence.py index 942fc1f4a06..5cafacc2357 100644 --- a/telegram/ext/picklepersistence.py +++ b/telegram/ext/picklepersistence.py @@ -225,7 +225,7 @@ def update_user_data(self, user_id, data): print('same ' + str(self.user_data.get(user_id) is data)) if self.user_data.get(user_id) == data: return - self.user_data[user_id] = data.copy() + self.user_data[user_id] = data if not self.on_flush: if not self.single_file: filename = "{}_user_data".format(self.filename)