Skip to content

Commit 51a92a3

Browse files
authored
feat: Add ownership parameter to storage collection listing methods (#696)
### Description Adds an `ownership` query parameter (`ownedByMe | sharedWithMe`) to the `list()` method on `DatasetCollectionClient`, `KeyValueStoreCollectionClient`, and `RequestQueueCollectionClient` (both sync and async variants). This allows filtering listed storages by whether they are owned by the user or shared with the user. ### Testing Includes unit tests verifying the parameter is correctly passed through to the API request for all three storage types.
1 parent 64ee8ab commit 51a92a3

File tree

5 files changed

+156
-11
lines changed

5 files changed

+156
-11
lines changed

src/apify_client/_resource_clients/dataset_collection.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync
88

99
if TYPE_CHECKING:
10-
from apify_client._types import Timeout
10+
from apify_client._types import StorageOwnership, Timeout
1111

1212

1313
@docs_group('Resource clients')
@@ -36,6 +36,7 @@ def list(
3636
limit: int | None = None,
3737
offset: int | None = None,
3838
desc: bool | None = None,
39+
ownership: StorageOwnership | None = None,
3940
timeout: Timeout = 'medium',
4041
) -> ListOfDatasets:
4142
"""List the available datasets.
@@ -47,12 +48,16 @@ def list(
4748
limit: How many datasets to retrieve.
4849
offset: What dataset to include as first when retrieving the list.
4950
desc: Whether to sort the datasets in descending order based on their modification date.
51+
ownership: Filter by ownership. 'ownedByMe' returns only user's own datasets,
52+
'sharedWithMe' returns only datasets shared with the user.
5053
timeout: Timeout for the API HTTP request.
5154
5255
Returns:
5356
The list of available datasets matching the specified filters.
5457
"""
55-
result = self._list(timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc)
58+
result = self._list(
59+
timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc, ownership=ownership
60+
)
5661
return ListOfDatasetsResponse.model_validate(result).data
5762

5863
def get_or_create(
@@ -104,6 +109,7 @@ async def list(
104109
limit: int | None = None,
105110
offset: int | None = None,
106111
desc: bool | None = None,
112+
ownership: StorageOwnership | None = None,
107113
timeout: Timeout = 'medium',
108114
) -> ListOfDatasets:
109115
"""List the available datasets.
@@ -115,12 +121,16 @@ async def list(
115121
limit: How many datasets to retrieve.
116122
offset: What dataset to include as first when retrieving the list.
117123
desc: Whether to sort the datasets in descending order based on their modification date.
124+
ownership: Filter by ownership. 'ownedByMe' returns only user's own datasets,
125+
'sharedWithMe' returns only datasets shared with the user.
118126
timeout: Timeout for the API HTTP request.
119127
120128
Returns:
121129
The list of available datasets matching the specified filters.
122130
"""
123-
result = await self._list(timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc)
131+
result = await self._list(
132+
timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc, ownership=ownership
133+
)
124134
return ListOfDatasetsResponse.model_validate(result).data
125135

126136
async def get_or_create(

src/apify_client/_resource_clients/key_value_store_collection.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync
1313

1414
if TYPE_CHECKING:
15-
from apify_client._types import Timeout
15+
from apify_client._types import StorageOwnership, Timeout
1616

1717

1818
@docs_group('Resource clients')
@@ -41,6 +41,7 @@ def list(
4141
limit: int | None = None,
4242
offset: int | None = None,
4343
desc: bool | None = None,
44+
ownership: StorageOwnership | None = None,
4445
timeout: Timeout = 'medium',
4546
) -> ListOfKeyValueStores:
4647
"""List the available key-value stores.
@@ -52,12 +53,16 @@ def list(
5253
limit: How many key-value stores to retrieve.
5354
offset: What key-value store to include as first when retrieving the list.
5455
desc: Whether to sort the key-value stores in descending order based on their modification date.
56+
ownership: Filter by ownership. 'ownedByMe' returns only user's own key-value stores,
57+
'sharedWithMe' returns only key-value stores shared with the user.
5558
timeout: Timeout for the API HTTP request.
5659
5760
Returns:
5861
The list of available key-value stores matching the specified filters.
5962
"""
60-
result = self._list(timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc)
63+
result = self._list(
64+
timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc, ownership=ownership
65+
)
6166
return ListOfKeyValueStoresResponse.model_validate(result).data
6267

6368
def get_or_create(
@@ -109,6 +114,7 @@ async def list(
109114
limit: int | None = None,
110115
offset: int | None = None,
111116
desc: bool | None = None,
117+
ownership: StorageOwnership | None = None,
112118
timeout: Timeout = 'medium',
113119
) -> ListOfKeyValueStores:
114120
"""List the available key-value stores.
@@ -120,12 +126,16 @@ async def list(
120126
limit: How many key-value stores to retrieve.
121127
offset: What key-value store to include as first when retrieving the list.
122128
desc: Whether to sort the key-value stores in descending order based on their modification date.
129+
ownership: Filter by ownership. 'ownedByMe' returns only user's own key-value stores,
130+
'sharedWithMe' returns only key-value stores shared with the user.
123131
timeout: Timeout for the API HTTP request.
124132
125133
Returns:
126134
The list of available key-value stores matching the specified filters.
127135
"""
128-
result = await self._list(timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc)
136+
result = await self._list(
137+
timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc, ownership=ownership
138+
)
129139
return ListOfKeyValueStoresResponse.model_validate(result).data
130140

131141
async def get_or_create(

src/apify_client/_resource_clients/request_queue_collection.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync
1313

1414
if TYPE_CHECKING:
15-
from apify_client._types import Timeout
15+
from apify_client._types import StorageOwnership, Timeout
1616

1717

1818
@docs_group('Resource clients')
@@ -41,6 +41,7 @@ def list(
4141
limit: int | None = None,
4242
offset: int | None = None,
4343
desc: bool | None = None,
44+
ownership: StorageOwnership | None = None,
4445
timeout: Timeout = 'medium',
4546
) -> ListOfRequestQueues:
4647
"""List the available request queues.
@@ -51,13 +52,17 @@ def list(
5152
unnamed: Whether to include unnamed request queues in the list.
5253
limit: How many request queues to retrieve.
5354
offset: What request queue to include as first when retrieving the list.
54-
desc: Whether to sort therequest queues in descending order based on their modification date.
55+
desc: Whether to sort the request queues in descending order based on their modification date.
56+
ownership: Filter by ownership. 'ownedByMe' returns only user's own request queues,
57+
'sharedWithMe' returns only request queues shared with the user.
5558
timeout: Timeout for the API HTTP request.
5659
5760
Returns:
5861
The list of available request queues matching the specified filters.
5962
"""
60-
result = self._list(timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc)
63+
result = self._list(
64+
timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc, ownership=ownership
65+
)
6166
return ListOfRequestQueuesResponse.model_validate(result).data
6267

6368
def get_or_create(
@@ -107,6 +112,7 @@ async def list(
107112
limit: int | None = None,
108113
offset: int | None = None,
109114
desc: bool | None = None,
115+
ownership: StorageOwnership | None = None,
110116
timeout: Timeout = 'medium',
111117
) -> ListOfRequestQueues:
112118
"""List the available request queues.
@@ -117,13 +123,17 @@ async def list(
117123
unnamed: Whether to include unnamed request queues in the list.
118124
limit: How many request queues to retrieve.
119125
offset: What request queue to include as first when retrieving the list.
120-
desc: Whether to sort therequest queues in descending order based on their modification date.
126+
desc: Whether to sort the request queues in descending order based on their modification date.
127+
ownership: Filter by ownership. 'ownedByMe' returns only user's own request queues,
128+
'sharedWithMe' returns only request queues shared with the user.
121129
timeout: Timeout for the API HTTP request.
122130
123131
Returns:
124132
The list of available request queues matching the specified filters.
125133
"""
126-
result = await self._list(timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc)
134+
result = await self._list(
135+
timeout=timeout, unnamed=unnamed, limit=limit, offset=offset, desc=desc, ownership=ownership
136+
)
127137
return ListOfRequestQueuesResponse.model_validate(result).data
128138

129139
async def get_or_create(

src/apify_client/_types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
from apify_client._models import ActorJobStatus, WebhookCreate # noqa: TC001
1111

12+
StorageOwnership = Literal['ownedByMe', 'sharedWithMe']
13+
"""Filter for storage listing methods to return only storages owned by the user or shared with the user."""
14+
1215
Timeout = timedelta | Literal['no_timeout', 'short', 'medium', 'long']
1316
"""Type for the `timeout` parameter on resource client methods.
1417
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from typing import TYPE_CHECKING
5+
6+
import pytest
7+
from werkzeug.wrappers import Response
8+
9+
from apify_client import ApifyClient, ApifyClientAsync
10+
11+
if TYPE_CHECKING:
12+
from collections.abc import Callable
13+
14+
from pytest_httpserver import HTTPServer
15+
from werkzeug.wrappers import Request
16+
17+
_MOCK_LIST_RESPONSE = json.dumps(
18+
{
19+
'data': {
20+
'items': [],
21+
'count': 0,
22+
'offset': 0,
23+
'limit': 100,
24+
'total': 0,
25+
'desc': False,
26+
}
27+
}
28+
)
29+
30+
31+
def _make_handler(captured: dict) -> Callable[[Request], Response]:
32+
def handler(request: Request) -> Response:
33+
captured['args'] = dict(request.args)
34+
return Response(_MOCK_LIST_RESPONSE, content_type='application/json')
35+
36+
return handler
37+
38+
39+
@pytest.fixture
40+
def client_urls(httpserver: HTTPServer) -> dict:
41+
server_url = httpserver.url_for('/').removesuffix('/')
42+
return {'api_url': server_url, 'api_public_url': server_url}
43+
44+
45+
def test_dataset_collection_list_ownership_sync(httpserver: HTTPServer, client_urls: dict) -> None:
46+
captured: dict = {}
47+
httpserver.expect_oneshot_request('/v2/datasets', method='GET').respond_with_handler(_make_handler(captured))
48+
49+
client = ApifyClient(token='placeholder_token', **client_urls)
50+
result = client.datasets().list(ownership='ownedByMe')
51+
52+
assert result.total == 0
53+
assert captured['args']['ownership'] == 'ownedByMe'
54+
55+
56+
async def test_dataset_collection_list_ownership_async(httpserver: HTTPServer, client_urls: dict) -> None:
57+
captured: dict = {}
58+
httpserver.expect_oneshot_request('/v2/datasets', method='GET').respond_with_handler(_make_handler(captured))
59+
60+
client = ApifyClientAsync(token='placeholder_token', **client_urls)
61+
result = await client.datasets().list(ownership='sharedWithMe')
62+
63+
assert result.total == 0
64+
assert captured['args']['ownership'] == 'sharedWithMe'
65+
66+
67+
def test_key_value_store_collection_list_ownership_sync(httpserver: HTTPServer, client_urls: dict) -> None:
68+
captured: dict = {}
69+
httpserver.expect_oneshot_request('/v2/key-value-stores', method='GET').respond_with_handler(
70+
_make_handler(captured)
71+
)
72+
73+
client = ApifyClient(token='placeholder_token', **client_urls)
74+
result = client.key_value_stores().list(ownership='ownedByMe')
75+
76+
assert result.total == 0
77+
assert captured['args']['ownership'] == 'ownedByMe'
78+
79+
80+
async def test_key_value_store_collection_list_ownership_async(httpserver: HTTPServer, client_urls: dict) -> None:
81+
captured: dict = {}
82+
httpserver.expect_oneshot_request('/v2/key-value-stores', method='GET').respond_with_handler(
83+
_make_handler(captured)
84+
)
85+
86+
client = ApifyClientAsync(token='placeholder_token', **client_urls)
87+
result = await client.key_value_stores().list(ownership='sharedWithMe')
88+
89+
assert result.total == 0
90+
assert captured['args']['ownership'] == 'sharedWithMe'
91+
92+
93+
def test_request_queue_collection_list_ownership_sync(httpserver: HTTPServer, client_urls: dict) -> None:
94+
captured: dict = {}
95+
httpserver.expect_oneshot_request('/v2/request-queues', method='GET').respond_with_handler(_make_handler(captured))
96+
97+
client = ApifyClient(token='placeholder_token', **client_urls)
98+
result = client.request_queues().list(ownership='ownedByMe')
99+
100+
assert result.total == 0
101+
assert captured['args']['ownership'] == 'ownedByMe'
102+
103+
104+
async def test_request_queue_collection_list_ownership_async(httpserver: HTTPServer, client_urls: dict) -> None:
105+
captured: dict = {}
106+
httpserver.expect_oneshot_request('/v2/request-queues', method='GET').respond_with_handler(_make_handler(captured))
107+
108+
client = ApifyClientAsync(token='placeholder_token', **client_urls)
109+
result = await client.request_queues().list(ownership='sharedWithMe')
110+
111+
assert result.total == 0
112+
assert captured['args']['ownership'] == 'sharedWithMe'

0 commit comments

Comments
 (0)