Skip to content

Commit 5f77774

Browse files
committed
Support pickling HTTPStatusError
1 parent 37593c1 commit 5f77774

File tree

2 files changed

+34
-0
lines changed

2 files changed

+34
-0
lines changed

httpx/_exceptions.py

+17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from __future__ import annotations
3535

3636
import contextlib
37+
import functools
3738
import typing
3839

3940
if typing.TYPE_CHECKING:
@@ -267,6 +268,22 @@ def __init__(self, message: str, *, request: Request, response: Response) -> Non
267268
self.request = request
268269
self.response = response
269270

271+
def __reduce__(self):
272+
# BaseException has a custom __reduce__ that can't handle subclasses with
273+
# required keyword-only arguments.
274+
return (
275+
functools.partial(
276+
self.__class__, request=self.request, response=self.response
277+
),
278+
self.args,
279+
# In case user code adds attributes to the exception instance.
280+
{
281+
k: v
282+
for k,v in self.__dict__.items()
283+
if k not in ('_request', 'response')
284+
},
285+
)
286+
270287

271288
class InvalidURL(Exception):
272289
"""

tests/test_exceptions.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import pickle
34
import typing
45

56
import httpcore
@@ -61,3 +62,19 @@ def test_request_attribute() -> None:
6162
request = httpx.Request("GET", "https://www.example.com")
6263
exc = httpx.ReadTimeout("Read operation timed out", request=request)
6364
assert exc.request == request
65+
66+
67+
def test_pickle_error(server):
68+
with httpx.Client() as client:
69+
response = client.request("GET", server.url.copy_with(path="/status/404"))
70+
with pytest.raises(httpx.HTTPStatusError) as exc_info:
71+
response.raise_for_status()
72+
error = exc_info.value
73+
assert isinstance(error, httpx.HTTPStatusError)
74+
pickled_error = pickle.dumps(error)
75+
unpickled_error = pickle.loads(pickled_error)
76+
# Note that the unpickled error will not be equal to the original error
77+
# because requests and responses are compared by identity.
78+
assert str(unpickled_error) == str(error)
79+
assert unpickled_error.request is not None
80+
assert unpickled_error.response is not None

0 commit comments

Comments
 (0)