Skip to content

Commit e67b995

Browse files
DelganBibo-Joshi
andauthored
Make Errors picklable (python-telegram-bot#2106)
* Fix TypeError while unpickling TelegramError (and children) * Add more extensive unit tests for errors pickling * Move error pickling tests back to "test_error.py" * Add test making sure that new errors are covered by tests * Make meta test independent of sorting Co-authored-by: Hinrich Mahler <[email protected]>
1 parent 0d419ed commit e67b995

3 files changed

Lines changed: 72 additions & 3 deletions

File tree

telegram/error.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ def __init__(self, message):
5151
def __str__(self):
5252
return '%s' % (self.message)
5353

54+
def __reduce__(self):
55+
return self.__class__, (self.message,)
56+
5457

5558
class Unauthorized(TelegramError):
5659
pass
@@ -60,6 +63,9 @@ class InvalidToken(TelegramError):
6063
def __init__(self):
6164
super().__init__('Invalid token')
6265

66+
def __reduce__(self):
67+
return self.__class__, ()
68+
6369

6470
class NetworkError(TelegramError):
6571
pass
@@ -73,6 +79,9 @@ class TimedOut(NetworkError):
7379
def __init__(self):
7480
super().__init__('Timed out')
7581

82+
def __reduce__(self):
83+
return self.__class__, ()
84+
7685

7786
class ChatMigrated(TelegramError):
7887
"""
@@ -85,6 +94,9 @@ def __init__(self, new_chat_id):
8594
super().__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
8695
self.new_chat_id = new_chat_id
8796

97+
def __reduce__(self):
98+
return self.__class__, (self.new_chat_id,)
99+
88100

89101
class RetryAfter(TelegramError):
90102
"""
@@ -94,9 +106,12 @@ class RetryAfter(TelegramError):
94106
"""
95107

96108
def __init__(self, retry_after):
97-
super().__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
109+
super().__init__('Flood control exceeded. Retry in {} seconds'.format(float(retry_after)))
98110
self.retry_after = float(retry_after)
99111

112+
def __reduce__(self):
113+
return self.__class__, (self.retry_after,)
114+
100115

101116
class Conflict(TelegramError):
102117
"""
@@ -109,3 +124,6 @@ class Conflict(TelegramError):
109124

110125
def __init__(self, msg):
111126
super().__init__(msg)
127+
128+
def __reduce__(self):
129+
return self.__class__, (self.message,)

telegram/passport/credentials.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class TelegramDecryptionError(TelegramError):
3939

4040
def __init__(self, message):
4141
super().__init__("TelegramDecryptionError: {}".format(message))
42+
self._msg = message
43+
44+
def __reduce__(self):
45+
return self.__class__, (self._msg,)
4246

4347

4448
def decrypt(secret, hash, data):

tests/test_error.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
#
1717
# You should have received a copy of the GNU Lesser Public License
1818
# along with this program. If not, see [http://www.gnu.org/licenses/].
19+
import pickle
20+
from collections import defaultdict
21+
1922
import pytest
2023

21-
from telegram import TelegramError
24+
from telegram import TelegramError, TelegramDecryptionError
2225
from telegram.error import Unauthorized, InvalidToken, NetworkError, BadRequest, TimedOut, \
2326
ChatMigrated, RetryAfter, Conflict
2427

@@ -81,9 +84,53 @@ def test_chat_migrated(self):
8184
assert e.new_chat_id == 1234
8285

8386
def test_retry_after(self):
84-
with pytest.raises(RetryAfter, match="Flood control exceeded. Retry in 12 seconds"):
87+
with pytest.raises(RetryAfter, match="Flood control exceeded. Retry in 12.0 seconds"):
8588
raise RetryAfter(12)
8689

8790
def test_conflict(self):
8891
with pytest.raises(Conflict, match='Something something.'):
8992
raise Conflict('Something something.')
93+
94+
@pytest.mark.parametrize(
95+
"exception, attributes",
96+
[
97+
(TelegramError("test message"), ["message"]),
98+
(Unauthorized("test message"), ["message"]),
99+
(InvalidToken(), ["message"]),
100+
(NetworkError("test message"), ["message"]),
101+
(BadRequest("test message"), ["message"]),
102+
(TimedOut(), ["message"]),
103+
(ChatMigrated(1234), ["message", "new_chat_id"]),
104+
(RetryAfter(12), ["message", "retry_after"]),
105+
(Conflict("test message"), ["message"]),
106+
(TelegramDecryptionError("test message"), ["message"])
107+
],
108+
)
109+
def test_errors_pickling(self, exception, attributes):
110+
pickled = pickle.dumps(exception)
111+
unpickled = pickle.loads(pickled)
112+
assert type(unpickled) is type(exception)
113+
assert str(unpickled) == str(exception)
114+
115+
for attribute in attributes:
116+
assert getattr(unpickled, attribute) == getattr(exception, attribute)
117+
118+
def test_pickling_test_coverage(self):
119+
"""
120+
This test is only here to make sure that new errors will override __reduce__ properly.
121+
Add the new error class to the below covered_subclasses dict, if it's covered in the above
122+
test_errors_pickling test.
123+
"""
124+
def make_assertion(cls):
125+
assert {sc for sc in cls.__subclasses__()} == covered_subclasses[cls]
126+
for subcls in cls.__subclasses__():
127+
make_assertion(subcls)
128+
129+
covered_subclasses = defaultdict(set)
130+
covered_subclasses.update({
131+
TelegramError: {Unauthorized, InvalidToken, NetworkError, ChatMigrated, RetryAfter,
132+
Conflict, TelegramDecryptionError},
133+
NetworkError: {BadRequest, TimedOut}
134+
})
135+
136+
make_assertion(TelegramError)

0 commit comments

Comments
 (0)