Skip to content

Commit 840d51a

Browse files
authored
refactor!: Remove parse_response arg from the call method (#462)
- Remove the `parse_response` parameter for the `call` method. It was only used to create the private attribute `_maybe_parsed_body` in `Response`. - The `_maybe_parsed_body` attribute has been removed; now the utility is called directly in Key-Value storage client, where it was used. ### Issues - Closes: #166
1 parent b880231 commit 840d51a

File tree

6 files changed

+45
-60
lines changed

6 files changed

+45
-60
lines changed

src/apify_client/_http_client.py

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
from urllib.parse import urlencode
1212

1313
import impit
14-
from apify_shared.utils import ignore_docs, is_content_type_json, is_content_type_text, is_content_type_xml
14+
from apify_shared.utils import ignore_docs
1515

1616
from apify_client._logging import log_context, logger_name
1717
from apify_client._statistics import Statistics
1818
from apify_client._utils import is_retryable_error, retry_with_exp_backoff, retry_with_exp_backoff_async
19-
from apify_client.errors import ApifyApiError, InvalidResponseBodyError
19+
from apify_client.errors import ApifyApiError
2020

2121
if TYPE_CHECKING:
2222
from collections.abc import Callable
@@ -65,25 +65,6 @@ def __init__(
6565

6666
self.stats = stats or Statistics()
6767

68-
@staticmethod
69-
def _maybe_parse_response(response: impit.Response) -> Any:
70-
if response.status_code == HTTPStatus.NO_CONTENT:
71-
return None
72-
73-
content_type = ''
74-
if 'content-type' in response.headers:
75-
content_type = response.headers['content-type'].split(';')[0].strip()
76-
77-
try:
78-
if is_content_type_json(content_type):
79-
return jsonlib.loads(response.text)
80-
elif is_content_type_xml(content_type) or is_content_type_text(content_type): # noqa: RET505
81-
return response.text
82-
else:
83-
return response.content
84-
except ValueError as err:
85-
raise InvalidResponseBodyError(response) from err
86-
8768
@staticmethod
8869
def _parse_params(params: dict | None) -> dict | None:
8970
if params is None:
@@ -159,17 +140,13 @@ def call(
159140
data: Any = None,
160141
json: JSONSerializable | None = None,
161142
stream: bool | None = None,
162-
parse_response: bool | None = True,
163143
timeout_secs: int | None = None,
164144
) -> impit.Response:
165145
log_context.method.set(method)
166146
log_context.url.set(url)
167147

168148
self.stats.calls += 1
169149

170-
if stream and parse_response:
171-
raise ValueError('Cannot stream response and parse it at the same time!')
172-
173150
headers, params, content = self._prepare_request_call(headers, params, data, json)
174151

175152
impit_client = self.impit_client
@@ -198,11 +175,6 @@ def _make_request(stop_retrying: Callable, attempt: int) -> impit.Response:
198175
# If response status is < 300, the request was successful, and we can return the result
199176
if response.status_code < 300: # noqa: PLR2004
200177
logger.debug('Request successful', extra={'status_code': response.status_code})
201-
if not stream:
202-
_maybe_parsed_body = (
203-
self._maybe_parse_response(response) if parse_response else response.content
204-
)
205-
setattr(response, '_maybe_parsed_body', _maybe_parsed_body) # noqa: B010
206178

207179
return response
208180

@@ -247,17 +219,13 @@ async def call(
247219
data: Any = None,
248220
json: JSONSerializable | None = None,
249221
stream: bool | None = None,
250-
parse_response: bool | None = True,
251222
timeout_secs: int | None = None,
252223
) -> impit.Response:
253224
log_context.method.set(method)
254225
log_context.url.set(url)
255226

256227
self.stats.calls += 1
257228

258-
if stream and parse_response:
259-
raise ValueError('Cannot stream response and parse it at the same time!')
260-
261229
headers, params, content = self._prepare_request_call(headers, params, data, json)
262230

263231
impit_async_client = self.impit_async_client
@@ -283,11 +251,6 @@ async def _make_request(stop_retrying: Callable, attempt: int) -> impit.Response
283251
# If response status is < 300, the request was successful, and we can return the result
284252
if response.status_code < 300: # noqa: PLR2004
285253
logger.debug('Request successful', extra={'status_code': response.status_code})
286-
if not stream:
287-
_maybe_parsed_body = (
288-
self._maybe_parse_response(response) if parse_response else response.content
289-
)
290-
setattr(response, '_maybe_parsed_body', _maybe_parsed_body) # noqa: B010
291254

292255
return response
293256

src/apify_client/_utils.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@
22

33
import asyncio
44
import base64
5-
import json
5+
import json as jsonlib
66
import random
77
import time
88
from collections.abc import Callable
99
from http import HTTPStatus
1010
from typing import TYPE_CHECKING, Any, TypeVar, cast
1111

1212
import impit
13-
from apify_shared.utils import is_file_or_bytes, maybe_extract_enum_member_value
13+
from apify_shared.utils import (
14+
is_content_type_json,
15+
is_content_type_text,
16+
is_content_type_xml,
17+
is_file_or_bytes,
18+
maybe_extract_enum_member_value,
19+
)
1420

1521
from apify_client.errors import InvalidResponseBodyError
1622

1723
if TYPE_CHECKING:
1824
from collections.abc import Awaitable
1925

26+
from impit import Response
27+
2028
from apify_client.errors import ApifyApiError
2129

2230
PARSE_DATE_FIELDS_MAX_DEPTH = 3
@@ -136,7 +144,7 @@ def encode_webhook_list_to_base64(webhooks: list[dict]) -> str:
136144
webhook_representation['headersTemplate'] = webhook['headers_template']
137145
data.append(webhook_representation)
138146

139-
return base64.b64encode(json.dumps(data).encode('utf-8')).decode('ascii')
147+
return base64.b64encode(jsonlib.dumps(data).encode('utf-8')).decode('ascii')
140148

141149

142150
def encode_key_value_store_record_value(value: Any, content_type: str | None = None) -> tuple[Any, str]:
@@ -149,11 +157,32 @@ def encode_key_value_store_record_value(value: Any, content_type: str | None = N
149157
content_type = 'application/json; charset=utf-8'
150158

151159
if 'application/json' in content_type and not is_file_or_bytes(value) and not isinstance(value, str):
152-
value = json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False, default=str).encode('utf-8')
160+
value = jsonlib.dumps(value, ensure_ascii=False, indent=2, allow_nan=False, default=str).encode('utf-8')
153161

154162
return (value, content_type)
155163

156164

165+
def maybe_parse_response(response: Response) -> Any:
166+
if response.status_code == HTTPStatus.NO_CONTENT:
167+
return None
168+
169+
content_type = ''
170+
if 'content-type' in response.headers:
171+
content_type = response.headers['content-type'].split(';')[0].strip()
172+
173+
try:
174+
if is_content_type_json(content_type):
175+
response_data = jsonlib.loads(response.text)
176+
elif is_content_type_xml(content_type) or is_content_type_text(content_type):
177+
response_data = response.text
178+
else:
179+
response_data = response.content
180+
except ValueError as err:
181+
raise InvalidResponseBodyError(response) from err
182+
else:
183+
return response_data
184+
185+
157186
def is_retryable_error(exc: Exception) -> bool:
158187
"""Check if the given error is retryable."""
159188
return isinstance(

src/apify_client/clients/resource_clients/dataset.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ def get_items_as_bytes(
413413
url=self._url('items'),
414414
method='GET',
415415
params=request_params,
416-
parse_response=False,
417416
)
418417

419418
return response.content
@@ -507,7 +506,6 @@ def stream_items(
507506
method='GET',
508507
params=request_params,
509508
stream=True,
510-
parse_response=False,
511509
)
512510
yield response
513511
finally:
@@ -862,7 +860,6 @@ async def get_items_as_bytes(
862860
url=self._url('items'),
863861
method='GET',
864862
params=request_params,
865-
parse_response=False,
866863
)
867864

868865
return response.content
@@ -956,7 +953,6 @@ async def stream_items(
956953
method='GET',
957954
params=request_params,
958955
stream=True,
959-
parse_response=False,
960956
)
961957
yield response
962958
finally:

src/apify_client/clients/resource_clients/key_value_store.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
from apify_shared.utils import filter_out_none_values_recursively, ignore_docs, parse_date_fields
99

10-
from apify_client._utils import catch_not_found_or_throw, encode_key_value_store_record_value, pluck_data
10+
from apify_client._utils import (
11+
catch_not_found_or_throw,
12+
encode_key_value_store_record_value,
13+
maybe_parse_response,
14+
pluck_data,
15+
)
1116
from apify_client.clients.base import ResourceClient, ResourceClientAsync
1217
from apify_client.errors import ApifyApiError
1318

@@ -121,7 +126,7 @@ def get_record(self, key: str) -> dict | None:
121126

122127
return {
123128
'key': key,
124-
'value': response._maybe_parsed_body, # type: ignore[attr-defined] # noqa: SLF001
129+
'value': maybe_parse_response(response),
125130
'content_type': response.headers['content-type'],
126131
}
127132

@@ -171,7 +176,6 @@ def get_record_as_bytes(self, key: str) -> dict | None:
171176
url=self._url(f'records/{key}'),
172177
method='GET',
173178
params=self._params(),
174-
parse_response=False,
175179
)
176180

177181
return {
@@ -203,7 +207,6 @@ def stream_record(self, key: str) -> Iterator[dict | None]:
203207
url=self._url(f'records/{key}'),
204208
method='GET',
205209
params=self._params(),
206-
parse_response=False,
207210
stream=True,
208211
)
209212

@@ -364,7 +367,7 @@ async def get_record(self, key: str) -> dict | None:
364367

365368
return {
366369
'key': key,
367-
'value': response._maybe_parsed_body, # type: ignore[attr-defined] # noqa: SLF001
370+
'value': maybe_parse_response(response),
368371
'content_type': response.headers['content-type'],
369372
}
370373

@@ -414,7 +417,6 @@ async def get_record_as_bytes(self, key: str) -> dict | None:
414417
url=self._url(f'records/{key}'),
415418
method='GET',
416419
params=self._params(),
417-
parse_response=False,
418420
)
419421

420422
return {
@@ -446,7 +448,6 @@ async def stream_record(self, key: str) -> AsyncIterator[dict | None]:
446448
url=self._url(f'records/{key}'),
447449
method='GET',
448450
params=self._params(),
449-
parse_response=False,
450451
stream=True,
451452
)
452453

src/apify_client/clients/resource_clients/log.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ def get_as_bytes(self, *, raw: bool = False) -> bytes | None:
7676
url=self.url,
7777
method='GET',
7878
params=self._params(raw=raw),
79-
parse_response=False,
8079
)
8180

8281
return response.content # noqa: TRY300
@@ -105,7 +104,6 @@ def stream(self, *, raw: bool = False) -> Iterator[impit.Response | None]:
105104
method='GET',
106105
params=self._params(stream=True, raw=raw),
107106
stream=True,
108-
parse_response=False,
109107
)
110108

111109
yield response
@@ -166,7 +164,6 @@ async def get_as_bytes(self, *, raw: bool = False) -> bytes | None:
166164
url=self.url,
167165
method='GET',
168166
params=self._params(raw=raw),
169-
parse_response=False,
170167
)
171168

172169
return response.content # noqa: TRY300
@@ -195,7 +192,6 @@ async def stream(self, *, raw: bool = False) -> AsyncIterator[impit.Response | N
195192
method='GET',
196193
params=self._params(stream=True, raw=raw),
197194
stream=True,
198-
parse_response=False,
199195
)
200196

201197
yield response

tests/unit/test_client_errors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def test_client_apify_api_error_streamed(httpserver: HTTPServer) -> None:
9292
httpserver.expect_request('/stream_error').respond_with_handler(streaming_handler)
9393

9494
with pytest.raises(ApifyApiError) as e:
95-
client.call(method='GET', url=httpserver.url_for('/stream_error'), stream=True, parse_response=False)
95+
client.call(method='GET', url=httpserver.url_for('/stream_error'), stream=True)
9696

9797
assert e.value.message == error['error']['message']
9898
assert e.value.type == error['error']['type']
@@ -108,7 +108,7 @@ async def test_async_client_apify_api_error_streamed(httpserver: HTTPServer) ->
108108
httpserver.expect_request('/stream_error').respond_with_handler(streaming_handler)
109109

110110
with pytest.raises(ApifyApiError) as e:
111-
await client.call(method='GET', url=httpserver.url_for('/stream_error'), stream=True, parse_response=False)
111+
await client.call(method='GET', url=httpserver.url_for('/stream_error'), stream=True)
112112

113113
assert e.value.message == error['error']['message']
114114
assert e.value.type == error['error']['type']

0 commit comments

Comments
 (0)