27
27
28
28
import httpx
29
29
30
+ from ..exceptions import APIError
31
+
30
32
_LOGGER = logging .getLogger (__name__ )
31
33
32
34
if TYPE_CHECKING :
36
38
_AUTH_URL = "authenticate"
37
39
38
40
41
+ def raise_for_status (response : httpx .Response ):
42
+ """
43
+ https://github.com/encode/httpx/discussions/2224#discussioncomment-2732372
44
+
45
+ When raising for status from certain places, if response is unread, the stream is
46
+ automatically closed, and we then cannot read the response in later error handling.
47
+ We thus need to check if the response is 4/500 and read it preemptively if so.
48
+ """
49
+ if response .status_code // 100 in (4 , 5 ):
50
+ response .read ()
51
+ response .raise_for_status ()
52
+
53
+
39
54
class TokenAuth (httpx .Auth ):
40
55
"""
41
56
Token-based authentication for an httpx session.
@@ -80,7 +95,7 @@ def token(self) -> str | None:
80
95
json = data ,
81
96
auth = None , # type: ignore
82
97
) # auth=None works but is missing from .post's type hint
83
- response_raise (response )
98
+ raise_for_status (response )
84
99
self ._token = response .json ()
85
100
return self ._token
86
101
@@ -111,7 +126,7 @@ def auth_flow(
111
126
request .headers ["Authorization" ] = f"Bearer { self .token } "
112
127
response = yield request
113
128
114
- response_raise (response )
129
+ raise_for_status (response )
115
130
116
131
def logout (self , clear_all_sessions = False ) -> bool :
117
132
"""
@@ -131,29 +146,39 @@ def auth_flow(
131
146
self , request : httpx .Request
132
147
) -> Generator [httpx .Request , httpx .Response , None ]:
133
148
response = yield request
134
- response_raise (response )
149
+ raise_for_status (response )
135
150
136
151
137
- def response_raise (response : httpx .Response ) -> None :
138
- """
139
- Raise an exception if the response has an HTTP status error.
140
- Replace the useless link to httpstatuses.com with error description.
152
+ class CustomClient (httpx .Client ):
153
+ _ERROR_PREFIX = {4 : "Client error - " , 5 : "Server error - " }
141
154
142
- :param response: The response object to check.
143
- :raises httpx.HTTPStatusError: If the response has an HTTP status error.
144
- """
145
- try :
146
- response .raise_for_status ()
147
- except httpx .HTTPStatusError as error :
148
- error .response .read ()
155
+ def __init__ (self , * args , ** kwargs ):
156
+ super ().__init__ (* args , ** kwargs )
157
+ self ._original_request = self .request
158
+ self .request = self ._request
159
+
160
+ def _request (self , * args , ** kwargs ):
161
+ """
162
+ httpx.Client.request modified to raise an exception if the response
163
+ has an HTTP status error and replace the useless link
164
+ to httpstatuses.com with error description.
165
+
166
+ :raises APIError: If the response has an HTTP status error.
167
+ """
149
168
try :
150
- error_msg = json .loads (error .response .text )["description" ]
151
- except (json .JSONDecodeError , IndexError , TypeError ):
152
- error_msg = error .response .text
153
- error_text : str = error .args [0 ].split ("\n " )[0 ]
154
- error_text += "\n " + error_msg
155
- error .args = (error_text ,)
156
- raise error
169
+ return self ._original_request (* args , ** kwargs )
170
+ except httpx .HTTPStatusError as error :
171
+ try :
172
+ error_detail = json .loads (error .response .text )["description" ]
173
+ except (json .JSONDecodeError , IndexError , TypeError ):
174
+ error_detail = error .response .text
175
+ prefix = self ._ERROR_PREFIX .get (error .response .status_code // 100 , "" )
176
+ api_error = APIError (
177
+ f"{ prefix } { error_detail or error } " ,
178
+ request = error .request ,
179
+ response = error .response ,
180
+ )
181
+ raise api_error from None
157
182
158
183
159
184
def make_session (base_url : str , ssl_verify : bool = True ) -> httpx .Client :
@@ -168,7 +193,7 @@ def make_session(base_url: str, ssl_verify: bool = True) -> httpx.Client:
168
193
:param ssl_verify: Whether to perform SSL verification.
169
194
:returns: The created httpx Client object.
170
195
"""
171
- return httpx . Client (
196
+ return CustomClient (
172
197
base_url = base_url ,
173
198
verify = ssl_verify ,
174
199
auth = BlankAuth (),
0 commit comments