88from http import HTTPStatus
99from importlib import metadata
1010from typing import TYPE_CHECKING , Any
11+ from urllib .parse import urlencode
1112
12- import httpx
13+ import impit
1314from apify_shared .utils import ignore_docs , is_content_type_json , is_content_type_text , is_content_type_xml
1415
1516from apify_client ._errors import ApifyApiError , InvalidResponseBodyError , is_retryable_error
@@ -59,13 +60,13 @@ def __init__(
5960 if token is not None :
6061 headers ['Authorization' ] = f'Bearer { token } '
6162
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 )
6465
6566 self .stats = stats or Statistics ()
6667
6768 @staticmethod
68- def _maybe_parse_response (response : httpx .Response ) -> Any :
69+ def _maybe_parse_response (response : impit .Response ) -> Any :
6970 if response .status_code == HTTPStatus .NO_CONTENT :
7071 return None
7172
@@ -75,7 +76,7 @@ def _maybe_parse_response(response: httpx.Response) -> Any:
7576
7677 try :
7778 if is_content_type_json (content_type ):
78- return response . json ( )
79+ return jsonlib . loads ( response . text )
7980 elif is_content_type_xml (content_type ) or is_content_type_text (content_type ): # noqa: RET505
8081 return response .text
8182 else :
@@ -131,6 +132,21 @@ def _prepare_request_call(
131132 data ,
132133 )
133134
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+
134150
135151class HTTPClient (_BaseHTTPClient ):
136152 def call (
@@ -145,7 +161,7 @@ def call(
145161 stream : bool | None = None ,
146162 parse_response : bool | None = True ,
147163 timeout_secs : int | None = None ,
148- ) -> httpx .Response :
164+ ) -> impit .Response :
149165 log_context .method .set (method )
150166 log_context .url .set (url )
151167
@@ -156,34 +172,26 @@ def call(
156172
157173 headers , params , content = self ._prepare_request_call (headers , params , data , json )
158174
159- httpx_client = self .httpx_client
175+ impit_client = self .impit_client
160176
161- def _make_request (stop_retrying : Callable , attempt : int ) -> httpx .Response :
177+ def _make_request (stop_retrying : Callable , attempt : int ) -> impit .Response :
162178 log_context .attempt .set (attempt )
163179 logger .debug ('Sending request' )
164180
165181 self .stats .requests += 1
166182
167183 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 (
169190 method = method ,
170- url = url ,
191+ url = url_with_params ,
171192 headers = headers ,
172- params = params ,
173193 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 ,
187195 stream = stream or False ,
188196 )
189197
@@ -217,7 +225,7 @@ def _make_request(stop_retrying: Callable, attempt: int) -> httpx.Response:
217225
218226 # Read the response in case it is a stream, so we can raise the error properly
219227 response .read ()
220- raise ApifyApiError (response , attempt )
228+ raise ApifyApiError (response , attempt , method = method )
221229
222230 return retry_with_exp_backoff (
223231 _make_request ,
@@ -241,7 +249,7 @@ async def call(
241249 stream : bool | None = None ,
242250 parse_response : bool | None = True ,
243251 timeout_secs : int | None = None ,
244- ) -> httpx .Response :
252+ ) -> impit .Response :
245253 log_context .method .set (method )
246254 log_context .url .set (url )
247255
@@ -252,31 +260,23 @@ async def call(
252260
253261 headers , params , content = self ._prepare_request_call (headers , params , data , json )
254262
255- httpx_async_client = self .httpx_async_client
263+ impit_async_client = self .impit_async_client
256264
257- async def _make_request (stop_retrying : Callable , attempt : int ) -> httpx .Response :
265+ async def _make_request (stop_retrying : Callable , attempt : int ) -> impit .Response :
258266 log_context .attempt .set (attempt )
259267 logger .debug ('Sending request' )
260268 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 (
262275 method = method ,
263- url = url ,
276+ url = url_with_params ,
264277 headers = headers ,
265- params = params ,
266278 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 ,
280280 stream = stream or False ,
281281 )
282282
@@ -310,7 +310,7 @@ async def _make_request(stop_retrying: Callable, attempt: int) -> httpx.Response
310310
311311 # Read the response in case it is a stream, so we can raise the error properly
312312 await response .aread ()
313- raise ApifyApiError (response , attempt )
313+ raise ApifyApiError (response , attempt , method = method )
314314
315315 return await retry_with_exp_backoff_async (
316316 _make_request ,
0 commit comments