8
8
from http import HTTPStatus
9
9
from importlib import metadata
10
10
from typing import TYPE_CHECKING , Any
11
+ from urllib .parse import urlencode
11
12
12
- import httpx
13
+ import impit
13
14
from apify_shared .utils import ignore_docs , is_content_type_json , is_content_type_text , is_content_type_xml
14
15
15
16
from apify_client ._errors import ApifyApiError , InvalidResponseBodyError , is_retryable_error
@@ -59,13 +60,13 @@ def __init__(
59
60
if token is not None :
60
61
headers ['Authorization' ] = f'Bearer { token } '
61
62
62
- self .httpx_client = httpx .Client (headers = headers , follow_redirects = True , timeout = timeout_secs )
63
- self .httpx_async_client = httpx .AsyncClient (headers = headers , follow_redirects = True , timeout = timeout_secs )
63
+ self .impit_client = impit .Client (headers = headers , follow_redirects = True , timeout = timeout_secs )
64
+ self .impit_async_client = impit .AsyncClient (headers = headers , follow_redirects = True , timeout = timeout_secs )
64
65
65
66
self .stats = stats or Statistics ()
66
67
67
68
@staticmethod
68
- def _maybe_parse_response (response : httpx .Response ) -> Any :
69
+ def _maybe_parse_response (response : impit .Response ) -> Any :
69
70
if response .status_code == HTTPStatus .NO_CONTENT :
70
71
return None
71
72
@@ -75,7 +76,7 @@ def _maybe_parse_response(response: httpx.Response) -> Any:
75
76
76
77
try :
77
78
if is_content_type_json (content_type ):
78
- return response . json ( )
79
+ return jsonlib . loads ( response . text )
79
80
elif is_content_type_xml (content_type ) or is_content_type_text (content_type ): # noqa: RET505
80
81
return response .text
81
82
else :
@@ -131,6 +132,21 @@ def _prepare_request_call(
131
132
data ,
132
133
)
133
134
135
+ def _build_url_with_params (self , url : str , params : dict | None = None ) -> str :
136
+ if not params :
137
+ return url
138
+
139
+ param_pairs : list [tuple [str , str ]] = []
140
+ for key , value in params .items ():
141
+ if isinstance (value , list ):
142
+ param_pairs .extend ((key , str (v )) for v in value )
143
+ else :
144
+ param_pairs .append ((key , str (value )))
145
+
146
+ query_string = urlencode (param_pairs )
147
+
148
+ return f'{ url } ?{ query_string } '
149
+
134
150
135
151
class HTTPClient (_BaseHTTPClient ):
136
152
def call (
@@ -145,7 +161,7 @@ def call(
145
161
stream : bool | None = None ,
146
162
parse_response : bool | None = True ,
147
163
timeout_secs : int | None = None ,
148
- ) -> httpx .Response :
164
+ ) -> impit .Response :
149
165
log_context .method .set (method )
150
166
log_context .url .set (url )
151
167
@@ -156,34 +172,26 @@ def call(
156
172
157
173
headers , params , content = self ._prepare_request_call (headers , params , data , json )
158
174
159
- httpx_client = self .httpx_client
175
+ impit_client = self .impit_client
160
176
161
- def _make_request (stop_retrying : Callable , attempt : int ) -> httpx .Response :
177
+ def _make_request (stop_retrying : Callable , attempt : int ) -> impit .Response :
162
178
log_context .attempt .set (attempt )
163
179
logger .debug ('Sending request' )
164
180
165
181
self .stats .requests += 1
166
182
167
183
try :
168
- request = httpx_client .build_request (
184
+ # Increase timeout with each attempt. Max timeout is bounded by the client timeout.
185
+ timeout = min (self .timeout_secs , (timeout_secs or self .timeout_secs ) * 2 ** (attempt - 1 ))
186
+
187
+ url_with_params = self ._build_url_with_params (url , params )
188
+
189
+ response = impit_client .request (
169
190
method = method ,
170
- url = url ,
191
+ url = url_with_params ,
171
192
headers = headers ,
172
- params = params ,
173
193
content = content ,
174
- )
175
-
176
- # Increase timeout with each attempt. Max timeout is bounded by the client timeout.
177
- timeout = min (self .timeout_secs , (timeout_secs or self .timeout_secs ) * 2 ** (attempt - 1 ))
178
- request .extensions ['timeout' ] = {
179
- 'connect' : timeout ,
180
- 'pool' : timeout ,
181
- 'read' : timeout ,
182
- 'write' : timeout ,
183
- }
184
-
185
- response = httpx_client .send (
186
- request = request ,
194
+ timeout = timeout ,
187
195
stream = stream or False ,
188
196
)
189
197
@@ -217,7 +225,7 @@ def _make_request(stop_retrying: Callable, attempt: int) -> httpx.Response:
217
225
218
226
# Read the response in case it is a stream, so we can raise the error properly
219
227
response .read ()
220
- raise ApifyApiError (response , attempt )
228
+ raise ApifyApiError (response , attempt , method = method )
221
229
222
230
return retry_with_exp_backoff (
223
231
_make_request ,
@@ -241,7 +249,7 @@ async def call(
241
249
stream : bool | None = None ,
242
250
parse_response : bool | None = True ,
243
251
timeout_secs : int | None = None ,
244
- ) -> httpx .Response :
252
+ ) -> impit .Response :
245
253
log_context .method .set (method )
246
254
log_context .url .set (url )
247
255
@@ -252,31 +260,23 @@ async def call(
252
260
253
261
headers , params , content = self ._prepare_request_call (headers , params , data , json )
254
262
255
- httpx_async_client = self .httpx_async_client
263
+ impit_async_client = self .impit_async_client
256
264
257
- async def _make_request (stop_retrying : Callable , attempt : int ) -> httpx .Response :
265
+ async def _make_request (stop_retrying : Callable , attempt : int ) -> impit .Response :
258
266
log_context .attempt .set (attempt )
259
267
logger .debug ('Sending request' )
260
268
try :
261
- request = httpx_async_client .build_request (
269
+ # Increase timeout with each attempt. Max timeout is bounded by the client timeout.
270
+ timeout = min (self .timeout_secs , (timeout_secs or self .timeout_secs ) * 2 ** (attempt - 1 ))
271
+
272
+ url_with_params = self ._build_url_with_params (url , params )
273
+
274
+ response = await impit_async_client .request (
262
275
method = method ,
263
- url = url ,
276
+ url = url_with_params ,
264
277
headers = headers ,
265
- params = params ,
266
278
content = content ,
267
- )
268
-
269
- # Increase timeout with each attempt. Max timeout is bounded by the client timeout.
270
- timeout = min (self .timeout_secs , (timeout_secs or self .timeout_secs ) * 2 ** (attempt - 1 ))
271
- request .extensions ['timeout' ] = {
272
- 'connect' : timeout ,
273
- 'pool' : timeout ,
274
- 'read' : timeout ,
275
- 'write' : timeout ,
276
- }
277
-
278
- response = await httpx_async_client .send (
279
- request = request ,
279
+ timeout = timeout ,
280
280
stream = stream or False ,
281
281
)
282
282
@@ -310,7 +310,7 @@ async def _make_request(stop_retrying: Callable, attempt: int) -> httpx.Response
310
310
311
311
# Read the response in case it is a stream, so we can raise the error properly
312
312
await response .aread ()
313
- raise ApifyApiError (response , attempt )
313
+ raise ApifyApiError (response , attempt , method = method )
314
314
315
315
return await retry_with_exp_backoff_async (
316
316
_make_request ,
0 commit comments