Skip to content

Commit 60d6d76

Browse files
authored
fix: shallow copy issue of ConjureHTTPError (#167)
1 parent 71453ac commit 60d6d76

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

conjure_python_client/_http/requests_client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,31 @@ def __init__(self, http_error: HTTPError) -> None:
294294
message, request=http_error.request, response=http_error.response
295295
)
296296

297+
def __copy__(self):
298+
"""The fact that ConjureHTTPError is a BaseException but its __init__
299+
has a different signature causes a subtle issue for shallow copying.
300+
During copy.copy(), __init__ will be called with args defined by
301+
BaseException.__reduce_, which corresponds to default __init__. Since
302+
they're inconsistent, what http_error receives is actually message,
303+
hence an error.
304+
305+
By defining a __copy__ method, we give instructions to the intepreter
306+
on how to reconstruct a ConjureHTTPError instance. Alternatively, we
307+
could also fix it by changing the _init__ signature of this class.
308+
Although cleaner, unfortunately it will be a breaking change.
309+
"""
310+
311+
# Create a shell object without calling __init__
312+
new_obj = type(self).__new__(type(self))
313+
314+
for attr, value in self.__dict__.items():
315+
setattr(new_obj, attr, value)
316+
317+
# Exception args are not actually a part of __dict__...
318+
new_obj.args = self.args
319+
320+
return new_obj
321+
297322
@property
298323
def cause(self) -> Optional[HTTPError]:
299324
"""The wrapped ``HTTPError`` that was the direct cause of

test/_http/test_requests_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import copy
12
import pickle
23
import sys
34

45
from conjure_python_client._http.requests_client import (
6+
ConjureHTTPError,
57
SOCKET_KEEP_ALIVE,
68
SOCKET_KEEP_INTVL,
79
TransportAdapter,
810
)
11+
from requests.exceptions import HTTPError
912

1013
if sys.platform != "darwin":
1114
from conjure_python_client._http.requests_client import SOCKET_KEEP_IDLE
@@ -58,3 +61,19 @@ def test_transport_adapter_can_be_unpickled_from_old_pickle():
5861
}
5962
adapter = pickle.loads(pickle.dumps(TransportAdapter()))
6063
assert adapter._enable_keep_alive is False
64+
65+
def test_shallow_copying_conjure_http_error():
66+
class MockResponse:
67+
def __init__(self):
68+
self.headers = {"X-B3-TraceId": "test"}
69+
70+
def json(self):
71+
return {}
72+
73+
response = MockResponse()
74+
http_error = HTTPError("HTTP 500 Server Error", response=response)
75+
original_error = ConjureHTTPError(http_error)
76+
77+
copied_error = copy.copy(original_error)
78+
assert type(original_error) is type(copied_error)
79+
assert str(original_error) == str(copied_error)

0 commit comments

Comments
 (0)