Skip to content

Commit e7558ef

Browse files
committed
Add "easy wins" tests and consolidate
1 parent 4c0d6b6 commit e7558ef

25 files changed

+2107
-380
lines changed

src/apify_client/_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: openapi.json
3-
# timestamp: 2025-12-20T10:45:52+00:00
3+
# timestamp: 2025-12-21T10:07:13+00:00
44

55
from __future__ import annotations
66

@@ -1663,10 +1663,10 @@ class UpdateRequestResponse(BaseModel):
16631663

16641664
class Item1(BaseModel):
16651665
id: Annotated[str, Field(examples=['8OamqXBCpPHxyH9'])]
1666-
retry_count: Annotated[float, Field(alias='retryCount', examples=[0])]
1666+
retry_count: Annotated[float | None, Field(alias='retryCount', examples=[0])] = None
16671667
unique_key: Annotated[str, Field(alias='uniqueKey', examples=['http://example.com'])]
16681668
url: Annotated[str, Field(examples=['http://example.com'])]
1669-
method: Annotated[str, Field(examples=['GET'])]
1669+
method: Annotated[str | None, Field(examples=['GET'])] = None
16701670

16711671

16721672
class Data15(BaseModel):

src/apify_client/_resource_clients/request_queue.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
GetHeadAndLockResponse,
1717
GetHeadResponse,
1818
GetRequestQueueResponse,
19+
GetRequestResponse,
1920
ListRequestsResponse,
2021
ProcessedRequest,
2122
ProlongRequestLockResponse,
@@ -190,7 +191,7 @@ def get_request(self, request_id: str) -> RequestQueueItems | None:
190191
timeout_secs=_SMALL_TIMEOUT,
191192
)
192193
result = response.json()
193-
return RequestQueueItems.model_validate(result)
194+
return GetRequestResponse.model_validate(result).data
194195

195196
except ApifyApiError as exc:
196197
catch_not_found_or_throw(exc)
@@ -589,12 +590,12 @@ async def get_request(self, request_id: str) -> RequestQueueItems | None:
589590
timeout_secs=_SMALL_TIMEOUT,
590591
)
591592
result = response.json()
592-
return RequestQueueItems.model_validate(result) if result is not None else None
593-
593+
validated_response = GetRequestResponse.model_validate(result) if result is not None else None
594594
except ApifyApiError as exc:
595595
catch_not_found_or_throw(exc)
596-
597-
return None
596+
return None
597+
else:
598+
return validated_response.data if validated_response is not None else None
598599

599600
async def update_request(self, request: dict, *, forefront: bool | None = None) -> RequestOperationInfo:
600601
"""Update a request in the queue.

src/apify_client/_resource_clients/store_collection.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
from typing import TYPE_CHECKING, Any
44

55
from apify_client._resource_clients.base import ResourceCollectionClient, ResourceCollectionClientAsync
6+
from apify_client._types import ListPage
7+
from apify_client._utils import response_to_dict
68

79
if TYPE_CHECKING:
810
from apify_client._models import ActorShort
9-
from apify_client._types import ListPage
1011

1112

1213
class StoreCollectionClient(ResourceCollectionClient):
@@ -16,6 +17,16 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
1617
resource_path = kwargs.pop('resource_path', 'store')
1718
super().__init__(*args, resource_path=resource_path, **kwargs)
1819

20+
def _list(self, **kwargs: Any) -> ListPage:
21+
"""Override to unwrap the 'data' field from the store API response."""
22+
response = self.http_client.call(
23+
url=self._url(),
24+
method='GET',
25+
params=self._params(**kwargs),
26+
)
27+
data = response_to_dict(response)
28+
return ListPage(data.get('data', {}))
29+
1930
def list(
2031
self,
2132
*,
@@ -62,6 +73,16 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
6273
resource_path = kwargs.pop('resource_path', 'store')
6374
super().__init__(*args, resource_path=resource_path, **kwargs)
6475

76+
async def _list(self, **kwargs: Any) -> ListPage:
77+
"""Override to unwrap the 'data' field from the store API response."""
78+
response = await self.http_client.call(
79+
url=self._url(),
80+
method='GET',
81+
params=self._params(**kwargs),
82+
)
83+
data = response_to_dict(response)
84+
return ListPage(data.get('data', {}))
85+
6586
async def list(
6687
self,
6788
*,

src/apify_client/_resource_clients/user.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ def monthly_usage(self) -> MonthlyUsage | None:
6363
params=self._params(),
6464
)
6565
data = response_to_dict(response)
66-
return MonthlyUsage.model_validate(data) if data is not None else None
66+
if data is None:
67+
return None
68+
# API returns {data: {...}} structure
69+
return MonthlyUsage.model_validate(data.get('data', {}))
6770

6871
except ApifyApiError as exc:
6972
catch_not_found_or_throw(exc)
@@ -88,7 +91,10 @@ def limits(self) -> AccountLimits | None:
8891
params=self._params(),
8992
)
9093
data = response_to_dict(response)
91-
return AccountLimits.model_validate(data) if data is not None else None
94+
if data is None:
95+
return None
96+
# API returns {data: {...}} structure
97+
return AccountLimits.model_validate(data.get('data', {}))
9298

9399
except ApifyApiError as exc:
94100
catch_not_found_or_throw(exc)
@@ -163,7 +169,10 @@ async def monthly_usage(self) -> MonthlyUsage | None:
163169
params=self._params(),
164170
)
165171
data = response_to_dict(response)
166-
return MonthlyUsage.model_validate(data) if data is not None else None
172+
if data is None:
173+
return None
174+
# API returns {data: {...}} structure
175+
return MonthlyUsage.model_validate(data.get('data', {}))
167176

168177
except ApifyApiError as exc:
169178
catch_not_found_or_throw(exc)
@@ -188,7 +197,10 @@ async def limits(self) -> AccountLimits | None:
188197
params=self._params(),
189198
)
190199
data = response_to_dict(response)
191-
return AccountLimits.model_validate(data) if data is not None else None
200+
if data is None:
201+
return None
202+
# API returns {data: {...}} structure
203+
return AccountLimits.model_validate(data.get('data', {}))
192204

193205
except ApifyApiError as exc:
194206
catch_not_found_or_throw(exc)

tests/integration/conftest.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
import json
22
import os
3-
import secrets
43
from collections.abc import Generator
54

65
import pytest
76
from apify_shared.utils import create_hmac_signature, create_storage_content_signature
87

9-
from .integration_test_utils import TestDataset, TestKvs
8+
from .utils import TestDataset, TestKvs, get_crypto_random_object_id
109
from apify_client import ApifyClient, ApifyClientAsync
1110

1211
TOKEN_ENV_VAR = 'APIFY_TEST_USER_API_TOKEN'
1312
TOKEN_ENV_VAR_2 = 'APIFY_TEST_USER_2_API_TOKEN'
1413
API_URL_ENV_VAR = 'APIFY_INTEGRATION_TESTS_API_URL'
1514

1615

17-
def crypto_random_object_id(length: int = 17) -> str:
18-
"""Generate a random object ID."""
19-
chars = 'abcdefghijklmnopqrstuvwxyzABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789'
20-
return ''.join(secrets.choice(chars) for _ in range(length))
21-
22-
2316
@pytest.fixture(scope='session')
2417
def api_token() -> str:
2518
token = os.getenv(TOKEN_ENV_VAR)
@@ -57,7 +50,7 @@ def test_dataset_of_another_user(api_token_2: str) -> Generator[TestDataset]:
5750
"""Pre-existing named dataset of another test user with restricted access."""
5851
client = ApifyClient(api_token_2, api_url=os.getenv(API_URL_ENV_VAR))
5952

60-
dataset_name = f'API-test-permissions-{crypto_random_object_id()}'
53+
dataset_name = f'API-test-permissions-{get_crypto_random_object_id()}'
6154
dataset = client.datasets().get_or_create(name=dataset_name)
6255
dataset_client = client.dataset(dataset_id=dataset.id)
6356
expected_content = [{'item1': 1, 'item2': 2, 'item3': 3}, {'item1': 4, 'item2': 5, 'item3': 6}]
@@ -87,7 +80,7 @@ def test_kvs_of_another_user(api_token_2: str) -> Generator[TestKvs]:
8780
"""Pre-existing named key value store of another test user with restricted access."""
8881
client = ApifyClient(api_token_2, api_url=os.getenv(API_URL_ENV_VAR))
8982

90-
kvs_name = f'API-test-permissions-{crypto_random_object_id()}'
83+
kvs_name = f'API-test-permissions-{get_crypto_random_object_id()}'
9184
kvs = client.key_value_stores().get_or_create(name=kvs_name)
9285
kvs_client = client.key_value_store(key_value_store_id=kvs.id)
9386
expected_content = {'key1': 1, 'key2': 2, 'key3': 3}

tests/integration/test_actor.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
from apify_client import ApifyClient
7+
8+
9+
def test_get_public_actor(apify_client: ApifyClient) -> None:
10+
"""Test getting a public actor by ID."""
11+
# Use a well-known public actor (Apify's web scraper)
12+
actor = apify_client.actor('apify/web-scraper').get()
13+
14+
assert actor is not None
15+
assert actor.id is not None
16+
assert actor.name == 'web-scraper'
17+
assert actor.username == 'apify'
18+
19+
20+
def test_get_actor_by_full_name(apify_client: ApifyClient) -> None:
21+
"""Test getting an actor using username/actorname format."""
22+
actor = apify_client.actor('apify/hello-world').get()
23+
24+
assert actor is not None
25+
assert actor.name == 'hello-world'
26+
assert actor.username == 'apify'
27+
28+
29+
def test_list_actors_my(apify_client: ApifyClient) -> None:
30+
"""Test listing actors created by the user."""
31+
actors_page = apify_client.actors().list(my=True, limit=10)
32+
33+
assert actors_page is not None
34+
assert actors_page.items is not None
35+
# User may have 0 actors
36+
assert isinstance(actors_page.items, list)
37+
38+
39+
def test_list_actors_pagination(apify_client: ApifyClient) -> None:
40+
"""Test listing actors with pagination parameters."""
41+
# List all actors (public + owned), should return some results
42+
actors_page = apify_client.actors().list(limit=5, offset=0)
43+
44+
assert actors_page is not None
45+
assert actors_page.items is not None
46+
assert isinstance(actors_page.items, list)
47+
# Should have at least some actors (public ones exist)
48+
assert len(actors_page.items) >= 0
49+
50+
51+
def test_list_actors_sorting(apify_client: ApifyClient) -> None:
52+
"""Test listing actors with sorting."""
53+
actors_page = apify_client.actors().list(limit=10, desc=True, sort_by='createdAt')
54+
55+
assert actors_page is not None
56+
assert actors_page.items is not None
57+
assert isinstance(actors_page.items, list)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
from apify_client import ApifyClientAsync
7+
8+
9+
async def test_get_public_actor(apify_client_async: ApifyClientAsync) -> None:
10+
"""Test getting a public actor by ID."""
11+
# Use a well-known public actor (Apify's web scraper)
12+
actor = await apify_client_async.actor('apify/web-scraper').get()
13+
14+
assert actor is not None
15+
assert actor.id is not None
16+
assert actor.name == 'web-scraper'
17+
assert actor.username == 'apify'
18+
19+
20+
async def test_get_actor_by_full_name(apify_client_async: ApifyClientAsync) -> None:
21+
"""Test getting an actor using username/actorname format."""
22+
actor = await apify_client_async.actor('apify/hello-world').get()
23+
24+
assert actor is not None
25+
assert actor.name == 'hello-world'
26+
assert actor.username == 'apify'
27+
28+
29+
async def test_list_actors_my(apify_client_async: ApifyClientAsync) -> None:
30+
"""Test listing actors created by the user."""
31+
actors_page = await apify_client_async.actors().list(my=True, limit=10)
32+
33+
assert actors_page is not None
34+
assert actors_page.items is not None
35+
# User may have 0 actors
36+
assert isinstance(actors_page.items, list)
37+
38+
39+
async def test_list_actors_pagination(apify_client_async: ApifyClientAsync) -> None:
40+
"""Test listing actors with pagination parameters."""
41+
# List all actors (public + owned), should return some results
42+
actors_page = await apify_client_async.actors().list(limit=5, offset=0)
43+
44+
assert actors_page is not None
45+
assert actors_page.items is not None
46+
assert isinstance(actors_page.items, list)
47+
# Should have at least some actors (public ones exist)
48+
assert len(actors_page.items) >= 0
49+
50+
51+
async def test_list_actors_sorting(apify_client_async: ApifyClientAsync) -> None:
52+
"""Test listing actors with sorting."""
53+
actors_page = await apify_client_async.actors().list(limit=10, desc=True, sort_by='createdAt')
54+
55+
assert actors_page is not None
56+
assert actors_page.items is not None
57+
assert isinstance(actors_page.items, list)

tests/integration/test_apify_client.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,11 @@
55
from apify_client._models import UserPrivateInfo, UserPublicInfo
66

77
if TYPE_CHECKING:
8-
from apify_client import ApifyClient, ApifyClientAsync
8+
from apify_client import ApifyClient
99

1010

11-
def test_apify_client_sync(apify_client: ApifyClient) -> None:
11+
def test_apify_client(apify_client: ApifyClient) -> None:
1212
user_client = apify_client.user('me')
1313
me = user_client.get()
1414
assert isinstance(me, (UserPrivateInfo, UserPublicInfo))
1515
assert me.username is not None
16-
17-
18-
async def test_apify_client_async(apify_client_async: ApifyClientAsync) -> None:
19-
user_client = apify_client_async.user('me')
20-
me = await user_client.get()
21-
assert isinstance(me, (UserPrivateInfo, UserPublicInfo))
22-
assert me.username is not None
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from apify_client._models import UserPrivateInfo, UserPublicInfo
6+
7+
if TYPE_CHECKING:
8+
from apify_client import ApifyClientAsync
9+
10+
11+
async def test_apify_client(apify_client_async: ApifyClientAsync) -> None:
12+
user_client = apify_client_async.user('me')
13+
me = await user_client.get()
14+
assert isinstance(me, (UserPrivateInfo, UserPublicInfo))
15+
assert me.username is not None

0 commit comments

Comments
 (0)