19
19
"""This module contains the BasePersistence class."""
20
20
21
21
from abc import ABC , abstractmethod
22
+ from collections import defaultdict
23
+ from copy import copy
24
+
25
+ from telegram import Bot
22
26
23
27
24
28
class BasePersistence (ABC ):
@@ -37,6 +41,18 @@ class BasePersistence(ABC):
37
41
must overwrite :meth:`get_conversations` and :meth:`update_conversation`.
38
42
* :meth:`flush` will be called when the bot is shutdown.
39
43
44
+ Warning:
45
+ Persistence will try to replace :class:`telegram.Bot` instances by :attr:`REPLACED_BOT` and
46
+ insert the bot set with :meth:`set_bot` upon loading of the data. This is to ensure that
47
+ changes to the bot apply to the saved objects, too. If you change the bots token, this may
48
+ lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see
49
+ :meth:`replace_bot` and :meth:`insert_bot`.
50
+
51
+ Note:
52
+ :meth:`replace_bot` and :meth:`insert_bot` are used *independently* of the implementation
53
+ of the :meth:`update/get_*` methods, i.e. you don't need to worry about it while
54
+ implementing a custom persistence subclass.
55
+
40
56
Attributes:
41
57
store_user_data (:obj:`bool`): Optional, Whether user_data should be saved by this
42
58
persistence class.
@@ -54,10 +70,128 @@ class BasePersistence(ABC):
54
70
persistence class. Default is :obj:`True` .
55
71
"""
56
72
73
+ def __new__ (cls , * args , ** kwargs ):
74
+ instance = super ().__new__ (cls )
75
+ get_user_data = instance .get_user_data
76
+ get_chat_data = instance .get_chat_data
77
+ get_bot_data = instance .get_bot_data
78
+ update_user_data = instance .update_user_data
79
+ update_chat_data = instance .update_chat_data
80
+ update_bot_data = instance .update_bot_data
81
+
82
+ def get_user_data_insert_bot ():
83
+ return instance .insert_bot (get_user_data ())
84
+
85
+ def get_chat_data_insert_bot ():
86
+ return instance .insert_bot (get_chat_data ())
87
+
88
+ def get_bot_data_insert_bot ():
89
+ return instance .insert_bot (get_bot_data ())
90
+
91
+ def update_user_data_replace_bot (user_id , data ):
92
+ return update_user_data (user_id , instance .replace_bot (data ))
93
+
94
+ def update_chat_data_replace_bot (chat_id , data ):
95
+ return update_chat_data (chat_id , instance .replace_bot (data ))
96
+
97
+ def update_bot_data_replace_bot (data ):
98
+ return update_bot_data (instance .replace_bot (data ))
99
+
100
+ instance .get_user_data = get_user_data_insert_bot
101
+ instance .get_chat_data = get_chat_data_insert_bot
102
+ instance .get_bot_data = get_bot_data_insert_bot
103
+ instance .update_user_data = update_user_data_replace_bot
104
+ instance .update_chat_data = update_chat_data_replace_bot
105
+ instance .update_bot_data = update_bot_data_replace_bot
106
+ return instance
107
+
57
108
def __init__ (self , store_user_data = True , store_chat_data = True , store_bot_data = True ):
58
109
self .store_user_data = store_user_data
59
110
self .store_chat_data = store_chat_data
60
111
self .store_bot_data = store_bot_data
112
+ self .bot = None
113
+
114
+ def set_bot (self , bot ):
115
+ """Set the Bot to be used by this persistence instance.
116
+
117
+ Args:
118
+ bot (:class:`telegram.Bot`): The bot.
119
+ """
120
+ self .bot = bot
121
+
122
+ @classmethod
123
+ def replace_bot (cls , obj ):
124
+ """
125
+ Replaces all instances of :class:`telegram.Bot` that occur within the passed object with
126
+ :attr:`REPLACED_BOT`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
127
+ ``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
128
+ ``__slot__`` attribute.
129
+
130
+ Args:
131
+ obj (:obj:`object`): The object
132
+
133
+ Returns:
134
+ :obj:`obj`: Copy of the object with Bot instances replaced.
135
+ """
136
+ if isinstance (obj , Bot ):
137
+ return cls .REPLACED_BOT
138
+ if isinstance (obj , (list , tuple , set , frozenset )):
139
+ return obj .__class__ (cls .replace_bot (item ) for item in obj )
140
+
141
+ new_obj = copy (obj )
142
+ if isinstance (obj , (dict , defaultdict )):
143
+ new_obj .clear ()
144
+ for k , v in obj .items ():
145
+ new_obj [cls .replace_bot (k )] = cls .replace_bot (v )
146
+ return new_obj
147
+ if hasattr (obj , '__dict__' ):
148
+ for attr_name , attr in new_obj .__dict__ .items ():
149
+ setattr (new_obj , attr_name , cls .replace_bot (attr ))
150
+ return new_obj
151
+ if hasattr (obj , '__slots__' ):
152
+ for attr_name in new_obj .__slots__ :
153
+ setattr (new_obj , attr_name ,
154
+ cls .replace_bot (cls .replace_bot (getattr (new_obj , attr_name ))))
155
+ return new_obj
156
+
157
+ return obj
158
+
159
+ def insert_bot (self , obj ):
160
+ """
161
+ Replaces all instances of :attr:`REPLACED_BOT` that occur within the passed object with
162
+ :attr:`bot`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
163
+ ``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
164
+ ``__slot__`` attribute.
165
+
166
+ Args:
167
+ obj (:obj:`object`): The object
168
+
169
+ Returns:
170
+ :obj:`obj`: Copy of the object with Bot instances inserted.
171
+ """
172
+ if isinstance (obj , Bot ):
173
+ return self .bot
174
+ if obj == self .REPLACED_BOT :
175
+ return self .bot
176
+ if isinstance (obj , (list , tuple , set , frozenset )):
177
+ return obj .__class__ (self .insert_bot (item ) for item in obj )
178
+
179
+ new_obj = copy (obj )
180
+ if isinstance (obj , (dict , defaultdict )):
181
+ new_obj .clear ()
182
+ for k , v in obj .items ():
183
+ new_obj [self .insert_bot (k )] = self .insert_bot (v )
184
+ return new_obj
185
+ if hasattr (obj , '__dict__' ):
186
+ for attr_name , attr in new_obj .__dict__ .items ():
187
+ setattr (new_obj , attr_name , self .insert_bot (attr ))
188
+ return new_obj
189
+ if hasattr (obj , '__slots__' ):
190
+ for attr_name in obj .__slots__ :
191
+ setattr (new_obj , attr_name ,
192
+ self .insert_bot (self .insert_bot (getattr (new_obj , attr_name ))))
193
+ return new_obj
194
+ return obj
61
195
62
196
@abstractmethod
63
197
def get_user_data (self ):
@@ -149,3 +283,6 @@ def flush(self):
149
283
is not of any importance just pass will be sufficient.
150
284
"""
151
285
pass
286
+
287
+ REPLACED_BOT = 'bot_instance_replaced_by_ptb_persistence'
288
+ """:obj:`str`: Placeholder for :class:`telegram.Bot` instances replaced in saved data."""
0 commit comments