Skip to content

Commit 7d6dbb1

Browse files
committed
Adapt to apify-client v3
1 parent 5a41ea8 commit 7d6dbb1

21 files changed

+434
-235
lines changed

src/apify/_actor.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,8 @@ async def start(
885885
f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, `"RemainingTime"`, or a `timedelta`.'
886886
)
887887

888-
api_result = await client.actor(actor_id).start(
888+
actor_client = client.actor(actor_id)
889+
run = await actor_client.start(
889890
run_input=run_input,
890891
content_type=content_type,
891892
build=build,
@@ -895,7 +896,11 @@ async def start(
895896
webhooks=serialized_webhooks,
896897
)
897898

898-
return ActorRun.model_validate(api_result)
899+
if run is None:
900+
raise RuntimeError(f'Failed to start Actor with ID "{actor_id}".')
901+
902+
run_dict = run.model_dump(by_alias=True)
903+
return ActorRun.model_validate(run_dict)
899904

900905
async def abort(
901906
self,
@@ -923,13 +928,18 @@ async def abort(
923928
self._raise_if_not_initialized()
924929

925930
client = self.new_client(token=token) if token else self.apify_client
931+
run_client = client.run(run_id)
926932

927933
if status_message:
928-
await client.run(run_id).update(status_message=status_message)
934+
await run_client.update(status_message=status_message)
935+
936+
run = await run_client.abort(gracefully=gracefully)
929937

930-
api_result = await client.run(run_id).abort(gracefully=gracefully)
938+
if run is None:
939+
raise RuntimeError(f'Failed to abort Actor run with ID "{run_id}".')
931940

932-
return ActorRun.model_validate(api_result)
941+
run_dict = run.model_dump(by_alias=True)
942+
return ActorRun.model_validate(run_dict)
933943

934944
async def call(
935945
self,
@@ -1002,7 +1012,8 @@ async def call(
10021012
f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, `"RemainingTime"`, or a `timedelta`.'
10031013
)
10041014

1005-
api_result = await client.actor(actor_id).call(
1015+
actor_client = client.actor(actor_id)
1016+
run = await actor_client.call(
10061017
run_input=run_input,
10071018
content_type=content_type,
10081019
build=build,
@@ -1013,7 +1024,11 @@ async def call(
10131024
logger=logger,
10141025
)
10151026

1016-
return ActorRun.model_validate(api_result)
1027+
if run is None:
1028+
raise RuntimeError(f'Failed to call Actor with ID "{actor_id}".')
1029+
1030+
run_dict = run.model_dump(by_alias=True)
1031+
return ActorRun.model_validate(run_dict)
10171032

10181033
async def call_task(
10191034
self,
@@ -1075,7 +1090,8 @@ async def call_task(
10751090
else:
10761091
raise ValueError(f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, or a `timedelta`.')
10771092

1078-
api_result = await client.task(task_id).call(
1093+
task_client = client.task(task_id)
1094+
run = await task_client.call(
10791095
task_input=task_input,
10801096
build=build,
10811097
memory_mbytes=memory_mbytes,
@@ -1084,7 +1100,11 @@ async def call_task(
10841100
wait_secs=int(wait.total_seconds()) if wait is not None else None,
10851101
)
10861102

1087-
return ActorRun.model_validate(api_result)
1103+
if run is None:
1104+
raise RuntimeError(f'Failed to call Task with ID "{task_id}".')
1105+
1106+
run_dict = run.model_dump(by_alias=True)
1107+
return ActorRun.model_validate(run_dict)
10881108

10891109
async def metamorph(
10901110
self,
@@ -1261,11 +1281,19 @@ async def set_status_message(
12611281
if not self.configuration.actor_run_id:
12621282
raise RuntimeError('actor_run_id cannot be None when running on the Apify platform.')
12631283

1264-
api_result = await self.apify_client.run(self.configuration.actor_run_id).update(
1265-
status_message=status_message, is_status_message_terminal=is_terminal
1284+
run_client = self.apify_client.run(self.configuration.actor_run_id)
1285+
run = await run_client.update(
1286+
status_message=status_message,
1287+
is_status_message_terminal=is_terminal,
12661288
)
12671289

1268-
return ActorRun.model_validate(api_result)
1290+
if run is None:
1291+
raise RuntimeError(
1292+
f'Failed to set status message for Actor run with ID "{self.configuration.actor_run_id}".'
1293+
)
1294+
1295+
run_dict = run.model_dump(by_alias=True)
1296+
return ActorRun.model_validate(run_dict)
12691297

12701298
async def create_proxy_configuration(
12711299
self,

src/apify/_charging.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,14 +351,21 @@ async def _fetch_pricing_info(self) -> _FetchedPricingInfoDict:
351351
if self._actor_run_id is None:
352352
raise RuntimeError('Actor run ID not found even though the Actor is running on Apify')
353353

354-
run = run_validator.validate_python(await self._client.run(self._actor_run_id).get())
354+
run = await self._client.run(self._actor_run_id).get()
355+
355356
if run is None:
356357
raise RuntimeError('Actor run not found')
357358

359+
run_dict = run.model_dump(by_alias=True)
360+
actor_run = run_validator.validate_python(run_dict)
361+
362+
if actor_run is None:
363+
raise RuntimeError('Actor run not found')
364+
358365
return _FetchedPricingInfoDict(
359-
pricing_info=run.pricing_info,
360-
charged_event_counts=run.charged_event_counts or {},
361-
max_total_charge_usd=run.options.max_total_charge_usd or Decimal('inf'),
366+
pricing_info=actor_run.pricing_info,
367+
charged_event_counts=actor_run.charged_event_counts or {},
368+
max_total_charge_usd=actor_run.options.max_total_charge_usd or Decimal('inf'),
362369
)
363370

364371
# Local development without environment variables

src/apify/_models.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,32 +94,82 @@ class ActorRunUsage(BaseModel):
9494

9595
@docs_group('Actor')
9696
class ActorRun(BaseModel):
97+
"""Represents an Actor run and its associated data."""
98+
9799
__model_config__ = ConfigDict(populate_by_name=True)
98100

99101
id: Annotated[str, Field(alias='id')]
102+
"""Unique identifier of the Actor run."""
103+
100104
act_id: Annotated[str, Field(alias='actId')]
105+
"""ID of the Actor that was run."""
106+
101107
user_id: Annotated[str, Field(alias='userId')]
108+
"""ID of the user who started the run."""
109+
102110
actor_task_id: Annotated[str | None, Field(alias='actorTaskId')] = None
111+
"""ID of the Actor task, if the run was started from a task."""
112+
103113
started_at: Annotated[datetime, Field(alias='startedAt')]
114+
"""Time when the Actor run started."""
115+
104116
finished_at: Annotated[datetime | None, Field(alias='finishedAt')] = None
117+
"""Time when the Actor run finished."""
118+
105119
status: Annotated[ActorJobStatus, Field(alias='status')]
120+
"""Current status of the Actor run."""
121+
106122
status_message: Annotated[str | None, Field(alias='statusMessage')] = None
123+
"""Detailed message about the run status."""
124+
107125
is_status_message_terminal: Annotated[bool | None, Field(alias='isStatusMessageTerminal')] = None
126+
"""Whether the status message is terminal (final)."""
127+
108128
meta: Annotated[ActorRunMeta, Field(alias='meta')]
129+
"""Metadata about the Actor run."""
130+
109131
stats: Annotated[ActorRunStats, Field(alias='stats')]
132+
"""Statistics of the Actor run."""
133+
110134
options: Annotated[ActorRunOptions, Field(alias='options')]
135+
"""Configuration options for the Actor run."""
136+
111137
build_id: Annotated[str, Field(alias='buildId')]
138+
"""ID of the Actor build used for this run."""
139+
112140
exit_code: Annotated[int | None, Field(alias='exitCode')] = None
141+
"""Exit code of the Actor run process."""
142+
113143
default_key_value_store_id: Annotated[str, Field(alias='defaultKeyValueStoreId')]
144+
"""ID of the default key-value store for this run."""
145+
114146
default_dataset_id: Annotated[str, Field(alias='defaultDatasetId')]
147+
"""ID of the default dataset for this run."""
148+
115149
default_request_queue_id: Annotated[str, Field(alias='defaultRequestQueueId')]
150+
"""ID of the default request queue for this run."""
151+
116152
build_number: Annotated[str | None, Field(alias='buildNumber')] = None
153+
"""Build number of the Actor build used for this run."""
154+
117155
container_url: Annotated[str, Field(alias='containerUrl')]
156+
"""URL of the container running the Actor."""
157+
118158
is_container_server_ready: Annotated[bool | None, Field(alias='isContainerServerReady')] = None
159+
"""Whether the container's HTTP server is ready to accept requests."""
160+
119161
git_branch_name: Annotated[str | None, Field(alias='gitBranchName')] = None
162+
"""Name of the git branch used for the Actor build."""
163+
120164
usage: Annotated[ActorRunUsage | None, Field(alias='usage')] = None
165+
"""Resource usage statistics for the run."""
166+
121167
usage_total_usd: Annotated[float | None, Field(alias='usageTotalUsd')] = None
168+
"""Total cost of the run in USD."""
169+
122170
usage_usd: Annotated[ActorRunUsage | None, Field(alias='usageUsd')] = None
171+
"""Resource usage costs in USD."""
172+
123173
pricing_info: Annotated[
124174
FreeActorPricingInfo
125175
| FlatPricePerMonthActorPricingInfo
@@ -128,10 +178,13 @@ class ActorRun(BaseModel):
128178
| None,
129179
Field(alias='pricingInfo', discriminator='pricing_model'),
130180
] = None
181+
"""Pricing information for the Actor."""
182+
131183
charged_event_counts: Annotated[
132184
dict[str, int] | None,
133185
Field(alias='chargedEventCounts'),
134186
] = None
187+
"""Count of charged events for pay-per-event pricing model."""
135188

136189

137190
class FreeActorPricingInfo(BaseModel):

src/apify/storage_clients/_apify/_alias_resolving.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from collections.abc import Callable
1515
from types import TracebackType
1616

17-
from apify_client.clients import (
17+
from apify_client._resource_clients import (
1818
DatasetClientAsync,
1919
DatasetCollectionClientAsync,
2020
KeyValueStoreClientAsync,
@@ -105,8 +105,8 @@ async def open_by_alias(
105105
# Create new unnamed storage and store alias mapping
106106
raw_metadata = await collection_client.get_or_create()
107107

108-
await alias_resolver.store_mapping(storage_id=raw_metadata['id'])
109-
return get_resource_client_by_id(raw_metadata['id'])
108+
await alias_resolver.store_mapping(storage_id=raw_metadata.id)
109+
return get_resource_client_by_id(raw_metadata.id)
110110

111111

112112
class AliasResolver:

src/apify/storage_clients/_apify/_api_client_creation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from apify.storage_clients._apify._alias_resolving import open_by_alias
99

1010
if TYPE_CHECKING:
11-
from apify_client.clients import DatasetClientAsync, KeyValueStoreClientAsync, RequestQueueClientAsync
11+
from apify_client._resource_clients import DatasetClientAsync, KeyValueStoreClientAsync, RequestQueueClientAsync
1212

1313
from apify._configuration import Configuration
1414

@@ -137,13 +137,13 @@ def get_resource_client(storage_id: str) -> DatasetClientAsync:
137137
# Default storage does not exist. Create a new one.
138138
if not raw_metadata:
139139
raw_metadata = await collection_client.get_or_create()
140-
resource_client = get_resource_client(raw_metadata['id'])
140+
resource_client = get_resource_client(raw_metadata.id)
141141
return resource_client
142142

143143
# Open by name.
144144
case (None, str(), None, _):
145145
raw_metadata = await collection_client.get_or_create(name=name)
146-
return get_resource_client(raw_metadata['id'])
146+
return get_resource_client(raw_metadata.id)
147147

148148
# Open by ID.
149149
case (None, None, str(), _):

src/apify/storage_clients/_apify/_dataset_client.py

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

33
import asyncio
44
import warnings
5+
from datetime import datetime
56
from logging import getLogger
67
from typing import TYPE_CHECKING, Any
78

@@ -17,7 +18,7 @@
1718
if TYPE_CHECKING:
1819
from collections.abc import AsyncIterator
1920

20-
from apify_client.clients import DatasetClientAsync
21+
from apify_client._resource_clients import DatasetClientAsync
2122
from crawlee._types import JsonSerializable
2223

2324
from apify import Configuration
@@ -65,7 +66,18 @@ def __init__(
6566
@override
6667
async def get_metadata(self) -> DatasetMetadata:
6768
metadata = await self._api_client.get()
68-
return DatasetMetadata.model_validate(metadata)
69+
70+
if metadata is None:
71+
raise ValueError('Failed to retrieve dataset metadata.')
72+
73+
return DatasetMetadata(
74+
id=metadata.id,
75+
name=metadata.name,
76+
created_at=datetime.fromisoformat(metadata.created_at.replace('Z', '+00:00')),
77+
modified_at=datetime.fromisoformat(metadata.modified_at.replace('Z', '+00:00')),
78+
accessed_at=datetime.fromisoformat(metadata.accessed_at.replace('Z', '+00:00')),
79+
item_count=int(metadata.item_count),
80+
)
6981

7082
@classmethod
7183
async def open(

src/apify/storage_clients/_apify/_key_value_store_client.py

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

33
import asyncio
44
import warnings
5+
from datetime import datetime
56
from logging import getLogger
67
from typing import TYPE_CHECKING, Any
78

@@ -11,12 +12,12 @@
1112
from crawlee.storage_clients.models import KeyValueStoreRecord, KeyValueStoreRecordMetadata
1213

1314
from ._api_client_creation import create_storage_api_client
14-
from ._models import ApifyKeyValueStoreMetadata, KeyValueStoreListKeysPage
15+
from ._models import ApifyKeyValueStoreMetadata
1516

1617
if TYPE_CHECKING:
1718
from collections.abc import AsyncIterator
1819

19-
from apify_client.clients import KeyValueStoreClientAsync
20+
from apify_client._resource_clients import KeyValueStoreClientAsync
2021

2122
from apify import Configuration
2223

@@ -54,7 +55,18 @@ def __init__(
5455
@override
5556
async def get_metadata(self) -> ApifyKeyValueStoreMetadata:
5657
metadata = await self._api_client.get()
57-
return ApifyKeyValueStoreMetadata.model_validate(metadata)
58+
59+
if metadata is None:
60+
raise ValueError('Failed to retrieve dataset metadata.')
61+
62+
return ApifyKeyValueStoreMetadata(
63+
id=metadata.id,
64+
name=metadata.name,
65+
created_at=datetime.fromisoformat(metadata.created_at.replace('Z', '+00:00')),
66+
modified_at=datetime.fromisoformat(metadata.modified_at.replace('Z', '+00:00')),
67+
accessed_at=datetime.fromisoformat(metadata.accessed_at.replace('Z', '+00:00')),
68+
url_signing_secret_key=metadata.url_signing_secret_key,
69+
)
5870

5971
@classmethod
6072
async def open(
@@ -143,14 +155,13 @@ async def iterate_keys(
143155
count = 0
144156

145157
while True:
146-
response = await self._api_client.list_keys(exclusive_start_key=exclusive_start_key)
147-
list_key_page = KeyValueStoreListKeysPage.model_validate(response)
158+
list_key_page = await self._api_client.list_keys(exclusive_start_key=exclusive_start_key)
148159

149160
for item in list_key_page.items:
150161
# Convert KeyValueStoreKeyInfo to KeyValueStoreRecordMetadata
151162
record_metadata = KeyValueStoreRecordMetadata(
152163
key=item.key,
153-
size=item.size,
164+
size=int(item.size),
154165
content_type='application/octet-stream', # Content type not available from list_keys
155166
)
156167
yield record_metadata

0 commit comments

Comments
 (0)