Skip to content

Commit 6e5df35

Browse files
authored
refactor!: remove deprecated APIs (#799)
## Summary Removes deprecated APIs as part of the v3 major release: - `DatasetClient.download_items()` — deprecated alias of `get_items_as_bytes()`. - `max_unprocessed_requests_retries` and `min_delay_between_unprocessed_requests_retries` arguments of `batch_add_requests` (sync + async). The docstrings already said *"Will be removed in next major release."* - `exclusive_start_id` argument of `list_requests` (sync + async) — replaced by `cursor`. Also drops the now-unused `import warnings` from both resource client modules and the associated unit tests for the removed `cursor`/`exclusive_start_id` mutex check.
1 parent 6a36533 commit 6e5df35

4 files changed

Lines changed: 61 additions & 177 deletions

File tree

docs/04_upgrading/upgrading_to_v3.mdx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,61 @@ The difference matters only if your code inspects the function itself:
334334
- Type checkers see `def (...) -> AsyncIterator[T]` instead of `async def (...) -> AsyncIterator[T]`. Annotations on variables that hold the call's result may need to change from `AsyncGenerator[T, None]` to `AsyncIterator[T]`.
335335

336336
A new <ApiLink to="class/RequestQueueClientAsync#iterate_requests">`RequestQueueClientAsync.iterate_requests()`</ApiLink> helper is also introduced and follows the same `def ... -> AsyncIterator[T]` shape.
337+
338+
## Removal of deprecated APIs
339+
340+
Methods and arguments that had been deprecated in v2 are removed in v3.
341+
342+
### `DatasetClient.download_items()`
343+
344+
The deprecated alias has been removed. Use <ApiLink to="class/DatasetClient#get_items_as_bytes">`DatasetClient.get_items_as_bytes()`</ApiLink> instead — the signature and behavior are identical.
345+
346+
Before (v2):
347+
348+
```python
349+
raw = client.dataset('my-dataset').download_items(item_format='csv')
350+
```
351+
352+
After (v3):
353+
354+
```python
355+
raw = client.dataset('my-dataset').get_items_as_bytes(item_format='csv')
356+
```
357+
358+
### `batch_add_requests`: `max_unprocessed_requests_retries` and `min_delay_between_unprocessed_requests_retries`
359+
360+
Both arguments have been removed from <ApiLink to="class/RequestQueueClient#batch_add_requests">`RequestQueueClient.batch_add_requests()`</ApiLink> and <ApiLink to="class/RequestQueueClientAsync#batch_add_requests">`RequestQueueClientAsync.batch_add_requests()`</ApiLink>. They had no effect already in v2 — passing them only emitted a `DeprecationWarning`. Drop them from your call sites.
361+
362+
Before (v2):
363+
364+
```python
365+
rq_client.batch_add_requests(
366+
requests,
367+
max_unprocessed_requests_retries=3,
368+
min_delay_between_unprocessed_requests_retries=timedelta(seconds=1),
369+
)
370+
```
371+
372+
After (v3):
373+
374+
```python
375+
rq_client.batch_add_requests(requests)
376+
```
377+
378+
### `list_requests`: `exclusive_start_id`
379+
380+
The deprecated `exclusive_start_id` argument has been removed from <ApiLink to="class/RequestQueueClient#list_requests">`RequestQueueClient.list_requests()`</ApiLink> and <ApiLink to="class/RequestQueueClientAsync#list_requests">`RequestQueueClientAsync.list_requests()`</ApiLink>. Use the `cursor` argument together with `next_cursor` from the previous response (or <ApiLink to="class/RequestQueueClient#iterate_requests">`iterate_requests()`</ApiLink>, which handles pagination for you).
381+
382+
Before (v2):
383+
384+
```python
385+
page = rq_client.list_requests(limit=100)
386+
next_page = rq_client.list_requests(limit=100, exclusive_start_id=page.items[-1].id)
387+
```
388+
389+
After (v3):
390+
391+
```python
392+
page = rq_client.list_requests(limit=100)
393+
next_page = rq_client.list_requests(limit=100, cursor=page.next_cursor)
394+
```

src/apify_client/_resource_clients/dataset.py

Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import warnings
43
from contextlib import asynccontextmanager, contextmanager
54
from dataclasses import dataclass
65
from typing import TYPE_CHECKING, Any
@@ -289,104 +288,6 @@ def _callback(*, limit: int | None = None, offset: int | None = None) -> Dataset
289288

290289
return get_items_iterator(_callback, limit=limit, offset=offset, chunk_size=chunk_size or DEFAULT_CHUNK_SIZE)
291290

292-
def download_items(
293-
self,
294-
*,
295-
item_format: str = 'json',
296-
offset: int | None = None,
297-
limit: int | None = None,
298-
desc: bool | None = None,
299-
clean: bool | None = None,
300-
bom: bool | None = None,
301-
delimiter: str | None = None,
302-
fields: list[str] | None = None,
303-
omit: list[str] | None = None,
304-
unwind: list[str] | None = None,
305-
skip_empty: bool | None = None,
306-
skip_header_row: bool | None = None,
307-
skip_hidden: bool | None = None,
308-
xml_root: str | None = None,
309-
xml_row: str | None = None,
310-
flatten: list[str] | None = None,
311-
signature: str | None = None,
312-
timeout: Timeout = 'long',
313-
) -> bytes:
314-
"""Get the items in the dataset as raw bytes.
315-
316-
Deprecated: this function is a deprecated alias of `get_items_as_bytes`. It will be removed in
317-
a future version.
318-
319-
https://docs.apify.com/api/v2#/reference/datasets/item-collection/get-items
320-
321-
Args:
322-
item_format: Format of the results, possible values are: json, jsonl, csv, html, xlsx, xml and rss.
323-
The default value is json.
324-
offset: Number of items that should be skipped at the start. The default value is 0.
325-
limit: Maximum number of items to return. By default there is no limit.
326-
desc: By default, results are returned in the same order as they were stored. To reverse the order,
327-
set this parameter to True.
328-
clean: If True, returns only non-empty items and skips hidden fields (i.e. fields starting with
329-
the # character). The clean parameter is just a shortcut for skip_hidden=True and skip_empty=True
330-
parameters. Note that since some objects might be skipped from the output, that the result might
331-
contain less items than the limit value.
332-
bom: All text responses are encoded in UTF-8 encoding. By default, csv files are prefixed with
333-
the UTF-8 Byte Order Mark (BOM), while json, jsonl, xml, html and rss files are not. If you want
334-
to override this default behavior, specify bom=True query parameter to include the BOM or bom=False
335-
to skip it.
336-
delimiter: A delimiter character for CSV files. The default delimiter is a simple comma (,).
337-
fields: A list of fields which should be picked from the items, only these fields will remain in
338-
the resulting record objects. Note that the fields in the outputted items are sorted the same way
339-
as they are specified in the fields parameter. You can use this feature to effectively fix the
340-
output format.
341-
omit: A list of fields which should be omitted from the items.
342-
unwind: A list of fields which should be unwound, in order which they should be processed. Each field
343-
should be either an array or an object. If the field is an array then every element of the array
344-
will become a separate record and merged with parent object. If the unwound field is an object then
345-
it is merged with the parent object. If the unwound field is missing or its value is neither an array
346-
nor an object and therefore cannot be merged with a parent object, then the item gets preserved
347-
as it is. Note that the unwound items ignore the desc parameter.
348-
skip_empty: If True, then empty items are skipped from the output. Note that if used, the results might
349-
contain less items than the limit value.
350-
skip_header_row: If True, then header row in the csv format is skipped.
351-
skip_hidden: If True, then hidden fields are skipped from the output, i.e. fields starting with
352-
the # character.
353-
xml_root: Overrides default root element name of xml output. By default the root element is items.
354-
xml_row: Overrides default element name that wraps each page or page function result object in xml output.
355-
By default the element name is item.
356-
flatten: A list of fields that should be flattened.
357-
signature: Signature used to access the items.
358-
timeout: Timeout for the API HTTP request.
359-
360-
Returns:
361-
The dataset items as raw bytes.
362-
"""
363-
warnings.warn(
364-
'`DatasetClient.download_items()` is deprecated, use `DatasetClient.get_items_as_bytes()` instead.',
365-
DeprecationWarning,
366-
stacklevel=2,
367-
)
368-
369-
return self.get_items_as_bytes(
370-
item_format=item_format,
371-
offset=offset,
372-
limit=limit,
373-
desc=desc,
374-
clean=clean,
375-
bom=bom,
376-
delimiter=delimiter,
377-
fields=fields,
378-
omit=omit,
379-
unwind=unwind,
380-
skip_empty=skip_empty,
381-
skip_header_row=skip_header_row,
382-
skip_hidden=skip_hidden,
383-
xml_root=xml_root,
384-
xml_row=xml_row,
385-
flatten=flatten,
386-
signature=signature,
387-
timeout=timeout,
388-
)
389-
390291
def get_items_as_bytes(
391292
self,
392293
*,

src/apify_client/_resource_clients/request_queue.py

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import asyncio
44
import math
5-
import warnings
65
from collections.abc import Iterable
76
from queue import Queue
87
from typing import TYPE_CHECKING, Any, Literal
@@ -377,8 +376,6 @@ def batch_add_requests(
377376
*,
378377
forefront: bool = False,
379378
max_parallel: int = 1,
380-
max_unprocessed_requests_retries: int | None = None,
381-
min_delay_between_unprocessed_requests_retries: timedelta | None = None,
382379
timeout: Timeout = 'medium',
383380
) -> BatchAddResult:
384381
"""Add requests to the request queue in batches.
@@ -393,26 +390,11 @@ def batch_add_requests(
393390
max_parallel: Specifies the maximum number of parallel tasks for API calls. This is only applicable
394391
to the async client. For the sync client, this value must be set to 1, as parallel execution
395392
is not supported.
396-
max_unprocessed_requests_retries: Deprecated argument. Will be removed in next major release.
397-
min_delay_between_unprocessed_requests_retries: Deprecated argument. Will be removed in next major release.
398393
timeout: Timeout for the API HTTP request.
399394
400395
Returns:
401396
Result containing lists of processed and unprocessed requests.
402397
"""
403-
if max_unprocessed_requests_retries:
404-
warnings.warn(
405-
'`max_unprocessed_requests_retries` is deprecated and not used anymore.',
406-
DeprecationWarning,
407-
stacklevel=2,
408-
)
409-
if min_delay_between_unprocessed_requests_retries:
410-
warnings.warn(
411-
'`min_delay_between_unprocessed_requests_retries` is deprecated and not used anymore.',
412-
DeprecationWarning,
413-
stacklevel=2,
414-
)
415-
416398
if max_parallel != 1:
417399
raise NotImplementedError('max_parallel is only supported in async client')
418400

@@ -514,7 +496,6 @@ def list_requests(
514496
filter: list[Literal['pending', 'locked']] | None = None, # noqa: A002
515497
timeout: Timeout = 'medium',
516498
cursor: str | None = None,
517-
exclusive_start_id: str | None = None,
518499
) -> ListOfRequests:
519500
"""List requests in the queue.
520501
@@ -525,23 +506,11 @@ def list_requests(
525506
filter: List of request states to use as a filter. Multiple values mean union of the given filters.
526507
timeout: Timeout for the API HTTP request.
527508
cursor: A token returned in previous API response, to continue listing next page of requests
528-
exclusive_start_id: (deprecated) All requests up to this one (including) are skipped from the result.
529509
"""
530-
if exclusive_start_id and cursor:
531-
raise ValueError('Cannot use both `exclusive_start_id` and `cursor` for paginating requests.')
532-
533-
if exclusive_start_id is not None:
534-
warnings.warn(
535-
'`exclusive_start_id` is deprecated for paginating requests. Use pagination using `cursor` instead.',
536-
DeprecationWarning,
537-
stacklevel=2,
538-
)
539-
540510
request_params = self._build_params(
541511
limit=limit,
542512
filter=','.join(filter) if filter else None,
543513
clientKey=self.client_key,
544-
exclusiveStartId=exclusive_start_id,
545514
cursor=cursor,
546515
)
547516

@@ -979,8 +948,6 @@ async def batch_add_requests(
979948
*,
980949
forefront: bool = False,
981950
max_parallel: int = 5,
982-
max_unprocessed_requests_retries: int | None = None,
983-
min_delay_between_unprocessed_requests_retries: timedelta | None = None,
984951
timeout: Timeout = 'medium',
985952
) -> BatchAddResult:
986953
"""Add requests to the request queue in batches.
@@ -995,26 +962,11 @@ async def batch_add_requests(
995962
max_parallel: Specifies the maximum number of parallel tasks for API calls. This is only applicable
996963
to the async client. For the sync client, this value must be set to 1, as parallel execution
997964
is not supported.
998-
max_unprocessed_requests_retries: Deprecated argument. Will be removed in next major release.
999-
min_delay_between_unprocessed_requests_retries: Deprecated argument. Will be removed in next major release.
1000965
timeout: Timeout for the API HTTP request.
1001966
1002967
Returns:
1003968
Result containing lists of processed and unprocessed requests.
1004969
"""
1005-
if max_unprocessed_requests_retries:
1006-
warnings.warn(
1007-
'`max_unprocessed_requests_retries` is deprecated and not used anymore.',
1008-
DeprecationWarning,
1009-
stacklevel=2,
1010-
)
1011-
if min_delay_between_unprocessed_requests_retries:
1012-
warnings.warn(
1013-
'`min_delay_between_unprocessed_requests_retries` is deprecated and not used anymore.',
1014-
DeprecationWarning,
1015-
stacklevel=2,
1016-
)
1017-
1018970
requests_as_dicts = [
1019971
(
1020972
request
@@ -1126,7 +1078,6 @@ async def list_requests(
11261078
filter: list[Literal['pending', 'locked']] | None = None, # noqa: A002
11271079
timeout: Timeout = 'medium',
11281080
cursor: str | None = None,
1129-
exclusive_start_id: str | None = None,
11301081
) -> ListOfRequests:
11311082
"""List requests in the queue.
11321083
@@ -1137,23 +1088,11 @@ async def list_requests(
11371088
filter: List of request states to use as a filter. Multiple values mean union of the given filters.
11381089
timeout: Timeout for the API HTTP request.
11391090
cursor: A token returned in previous API response, to continue listing next page of requests
1140-
exclusive_start_id: (deprecated) All requests up to this one (including) are skipped from the result.
11411091
"""
1142-
if exclusive_start_id and cursor:
1143-
raise ValueError('Cannot use both `exclusive_start_id` and `cursor` for paginating requests.')
1144-
1145-
if exclusive_start_id is not None:
1146-
warnings.warn(
1147-
'`exclusive_start_id` is deprecated for paginating requests. Use pagination using `cursor` instead.',
1148-
DeprecationWarning,
1149-
stacklevel=2,
1150-
)
1151-
11521092
request_params = self._build_params(
11531093
limit=limit,
11541094
filter=','.join(filter) if filter else None,
11551095
clientKey=self.client_key,
1156-
exclusiveStartId=exclusive_start_id,
11571096
cursor=cursor,
11581097
)
11591098

tests/unit/test_client_pagination.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,14 @@ def _handle_cursor_pagination(request: Request) -> Response:
235235
"""Serve a cursor-paginated Apify API response for KVS keys and RQ requests.
236236
237237
Holds 2500 synthetic items whose integer `id` equals their position. Each page is capped at 1000 items. KVS uses
238-
`exclusiveStartKey`; RQ accepts either the deprecated `exclusiveStartId` on the initial call or the opaque `cursor`
239-
on subsequent calls. All three values encode the last-seen item id as a string — the next page starts at id + 1.
238+
`exclusiveStartKey`; RQ uses the opaque `cursor`. Both values encode the last-seen item id as a string — the
239+
next page starts at id + 1.
240240
"""
241241
params = request.args
242242
limit = _parse_int_param(params.get('limit'))
243243
assert limit >= 0, 'Invalid limit sent to API'
244244

245-
cursor_raw = params.get('exclusiveStartKey') or params.get('exclusiveStartId') or params.get('cursor')
245+
cursor_raw = params.get('exclusiveStartKey') or params.get('cursor')
246246

247247
total_items = NORMAL_ITEMS
248248
start = int(cursor_raw) + 1 if cursor_raw not in (None, '') else 0
@@ -617,17 +617,3 @@ async def test_rq_list_requests_iterable_async(
617617
client: RequestQueueClientAsync = _CLIENT_FACTORIES[client_name](_make_async_client(pagination_server))
618618
returned_items = [dict(item) async for item in client.iterate_requests(**inputs)]
619619
assert returned_items == expected_items
620-
621-
622-
def test_rq_list_requests_rejects_cursor_and_exclusive_start_id() -> None:
623-
"""Passing both `cursor` and `exclusive_start_id` is mutually exclusive and must error."""
624-
client = ApifyClient(token='').request_queue(ID_PLACEHOLDER)
625-
with pytest.raises(ValueError, match='Cannot use both'):
626-
client.list_requests(cursor='a', exclusive_start_id='b')
627-
628-
629-
async def test_rq_list_requests_rejects_cursor_and_exclusive_start_id_async() -> None:
630-
"""Async variant of the mutual-exclusion check."""
631-
client = ApifyClientAsync(token='').request_queue(ID_PLACEHOLDER)
632-
with pytest.raises(ValueError, match='Cannot use both'):
633-
await client.list_requests(cursor='a', exclusive_start_id='b')

0 commit comments

Comments
 (0)