From 3ff3ff21dd45957c9e143cd500291959bb15f690 Mon Sep 17 00:00:00 2001 From: Thomas Dehghani Date: Wed, 31 Jan 2024 17:07:35 +0100 Subject: [PATCH] Fix #6628 - JSONDecodeError are not deserializable requests.exceptions.JSONDecodeError are not deserializable: calling `pickle.dumps` followed by `pickle.loads` will trigger an error. This is particularly a problem in a process pool, as an attempt to decode json on an invalid json document will result in the entire process pool crashing. This is due to the MRO of the `requests.exceptions.JSONDecodeError` class: the `__reduce__` method called when pickling an instance is not the one from the JSON library parent: two out of three args expected for instantiation will be dropped, and the instance can't be deserialised. By specifying in the class which parent `__reduce__` method should be called, the bug is fixed as all args are carried over in the resulting pickled bytes. --- src/requests/exceptions.py | 10 ++++++++++ tests/test_requests.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/requests/exceptions.py b/src/requests/exceptions.py index e1cedf883d..83986b4898 100644 --- a/src/requests/exceptions.py +++ b/src/requests/exceptions.py @@ -41,6 +41,16 @@ def __init__(self, *args, **kwargs): CompatJSONDecodeError.__init__(self, *args) InvalidJSONError.__init__(self, *self.args, **kwargs) + def __reduce__(self): + """ + The __reduce__ method called when pickling the object must + be the one from the JSONDecodeError (be it json/simplejson) + as it expects all the arguments for instantiation, not just + one like the IOError, and the MRO would by default call the + __reduce__ method from the IOError due to the inheritance order. + """ + return CompatJSONDecodeError.__reduce__(self) + class HTTPError(RequestException): """An HTTP error occurred.""" diff --git a/tests/test_requests.py b/tests/test_requests.py index 34796dc7ec..77aac3fecb 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -2810,3 +2810,13 @@ def test_status_code_425(self): assert r4 == 425 assert r5 == 425 assert r6 == 425 + + +def test_json_decode_errors_are_serializable_deserializable(): + json_decode_error = requests.exceptions.JSONDecodeError( + "Extra data", + '{"responseCode":["706"],"data":null}{"responseCode":["706"],"data":null}', + 36, + ) + deserialized_error = pickle.loads(pickle.dumps(json_decode_error)) + assert repr(json_decode_error) == repr(deserialized_error)