Skip to content

Commit eb4284b

Browse files
committed
Add ActorRun model
1 parent 91dd5e7 commit eb4284b

File tree

2 files changed

+129
-35
lines changed

2 files changed

+129
-35
lines changed

src/apify/_actor.py

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from apify._configuration import Configuration
2020
from apify._consts import EVENT_LISTENERS_TIMEOUT
2121
from apify._crypto import decrypt_input_secrets, load_private_key
22+
from apify._models import ActorRun
2223
from apify._platform_event_manager import EventManager, LocalEventManager, PlatformEventManager
2324
from apify._proxy_configuration import ProxyConfiguration
2425
from apify._utils import get_system_info, is_running_in_ipython
@@ -536,7 +537,7 @@ async def start(
536537
timeout: timedelta | None = None,
537538
wait_for_finish: int | None = None,
538539
webhooks: list[Webhook] | None = None,
539-
) -> dict:
540+
) -> ActorRun:
540541
"""Run an Actor on the Apify platform.
541542
542543
Unlike `Actor.call`, this method just starts the run without waiting for finish.
@@ -565,16 +566,20 @@ async def start(
565566

566567
client = self.new_client(token=token) if token else self._apify_client
567568

568-
return await client.actor(actor_id).start(
569-
run_input=run_input,
570-
content_type=content_type,
571-
build=build,
572-
memory_mbytes=memory_mbytes,
573-
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
574-
wait_for_finish=wait_for_finish,
575-
webhooks=[hook.model_dump(by_alias=True, exclude_unset=True, exclude_defaults=True) for hook in webhooks]
576-
if webhooks
577-
else None,
569+
return ActorRun.model_validate(
570+
await client.actor(actor_id).start(
571+
run_input=run_input,
572+
content_type=content_type,
573+
build=build,
574+
memory_mbytes=memory_mbytes,
575+
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
576+
wait_for_finish=wait_for_finish,
577+
webhooks=[
578+
hook.model_dump(by_alias=True, exclude_unset=True, exclude_defaults=True) for hook in webhooks
579+
]
580+
if webhooks
581+
else None,
582+
)
578583
)
579584

580585
async def abort(
@@ -584,7 +589,7 @@ async def abort(
584589
token: str | None = None,
585590
status_message: str | None = None,
586591
gracefully: bool | None = None,
587-
) -> dict:
592+
) -> ActorRun:
588593
"""Abort given Actor run on the Apify platform using the current user account.
589594
590595
The user account is determined by the `APIFY_TOKEN` environment variable.
@@ -607,7 +612,7 @@ async def abort(
607612
if status_message:
608613
await client.run(run_id).update(status_message=status_message)
609614

610-
return await client.run(run_id).abort(gracefully=gracefully)
615+
return ActorRun.model_validate(await client.run(run_id).abort(gracefully=gracefully))
611616

612617
async def call(
613618
self,
@@ -621,7 +626,7 @@ async def call(
621626
timeout: timedelta | None = None,
622627
webhooks: list[Webhook] | None = None,
623628
wait: timedelta | None = None,
624-
) -> dict | None:
629+
) -> ActorRun | None:
625630
"""Start an Actor on the Apify Platform and wait for it to finish before returning.
626631
627632
It waits indefinitely, unless the wait argument is provided.
@@ -650,16 +655,20 @@ async def call(
650655

651656
client = self.new_client(token=token) if token else self._apify_client
652657

653-
return await client.actor(actor_id).call(
654-
run_input=run_input,
655-
content_type=content_type,
656-
build=build,
657-
memory_mbytes=memory_mbytes,
658-
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
659-
webhooks=[hook.model_dump(by_alias=True, exclude_defaults=True, exclude_unset=True) for hook in webhooks]
660-
if webhooks
661-
else [],
662-
wait_secs=int(wait.total_seconds()) if wait is not None else None,
658+
return ActorRun.model_validate(
659+
await client.actor(actor_id).call(
660+
run_input=run_input,
661+
content_type=content_type,
662+
build=build,
663+
memory_mbytes=memory_mbytes,
664+
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
665+
webhooks=[
666+
hook.model_dump(by_alias=True, exclude_defaults=True, exclude_unset=True) for hook in webhooks
667+
]
668+
if webhooks
669+
else [],
670+
wait_secs=int(wait.total_seconds()) if wait is not None else None,
671+
)
663672
)
664673

665674
async def call_task(
@@ -673,7 +682,7 @@ async def call_task(
673682
webhooks: list[Webhook] | None = None,
674683
wait: timedelta | None = None,
675684
token: str | None = None,
676-
) -> dict | None:
685+
) -> ActorRun | None:
677686
"""Start an Actor task on the Apify Platform and wait for it to finish before returning.
678687
679688
It waits indefinitely, unless the wait argument is provided.
@@ -705,15 +714,19 @@ async def call_task(
705714

706715
client = self.new_client(token=token) if token else self._apify_client
707716

708-
return await client.task(task_id).call(
709-
task_input=task_input,
710-
build=build,
711-
memory_mbytes=memory_mbytes,
712-
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
713-
webhooks=[hook.model_dump(by_alias=True, exclude_defaults=True, exclude_unset=True) for hook in webhooks]
714-
if webhooks
715-
else [],
716-
wait_secs=int(wait.total_seconds()) if wait is not None else None,
717+
return ActorRun.model_validate(
718+
await client.task(task_id).call(
719+
task_input=task_input,
720+
build=build,
721+
memory_mbytes=memory_mbytes,
722+
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
723+
webhooks=[
724+
hook.model_dump(by_alias=True, exclude_defaults=True, exclude_unset=True) for hook in webhooks
725+
]
726+
if webhooks
727+
else [],
728+
wait_secs=int(wait.total_seconds()) if wait is not None else None,
729+
)
717730
)
718731

719732
async def metamorph(

src/apify/_models.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# ruff: noqa: TCH001 TCH002 TCH003 (Pydantic)
22
from __future__ import annotations
33

4+
from datetime import datetime, timedelta
45
from typing import Annotated
56

67
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
78

8-
from apify_shared.consts import WebhookEventType
9+
from apify_shared.consts import ActorJobStatus, MetaOrigin, WebhookEventType
10+
from crawlee._utils.models import timedelta_ms
911
from crawlee._utils.urls import validate_http_url
1012

1113

@@ -25,3 +27,82 @@ class Webhook(BaseModel):
2527
str | None,
2628
Field(description='Template for the payload sent by the webook'),
2729
] = None
30+
31+
32+
class ActorRunMeta(BaseModel):
33+
__model_config__ = ConfigDict(populate_by_name=True)
34+
35+
origin: Annotated[MetaOrigin, Field()]
36+
37+
38+
class ActorRunStats(BaseModel):
39+
__model_config__ = ConfigDict(populate_by_name=True)
40+
41+
input_body_len: Annotated[int, Field(alias='inputBodyLen')]
42+
restart_count: Annotated[int, Field(alias='restartCount')]
43+
resurrect_count: Annotated[int, Field(alias='resurrectCount')]
44+
mem_avg_bytes: Annotated[float, Field(alias='memAvgBytes')]
45+
mem_max_bytes: Annotated[int, Field(alias='memMaxBytes')]
46+
mem_current_bytes: Annotated[int, Field(alias='memCurrentBytes')]
47+
cpu_avg_usage: Annotated[float, Field(alias='cpuAvgUsage')]
48+
cpu_max_usage: Annotated[float, Field(alias='cpuMaxUsage')]
49+
cpu_current_usage: Annotated[float, Field(alias='cpuCurrentUsage')]
50+
net_rx_bytes: Annotated[int, Field(alias='netRxBytes')]
51+
net_tx_bytes: Annotated[int, Field(alias='netTxBytes')]
52+
duration: Annotated[timedelta_ms, Field(alias='durationMillis')]
53+
run_time: Annotated[timedelta, Field(alias='runTimeSecs')]
54+
metamorph: Annotated[int, Field(alias='metamorph')]
55+
compute_units: Annotated[float, Field(alias='computeUnits')]
56+
57+
58+
class ActorRunOptions(BaseModel):
59+
__model_config__ = ConfigDict(populate_by_name=True)
60+
build: str
61+
timeout: Annotated[timedelta, Field(alias='timeoutSecs')]
62+
memory_mbytes: Annotated[int, Field(alias='memoryMbytes')]
63+
disk_mbytes: Annotated[int, Field(alias='diskMbytes')]
64+
65+
66+
class ActorRunUsage(BaseModel):
67+
__model_config__ = ConfigDict(populate_by_name=True)
68+
actor_compute_units: Annotated[int | None, Field(alias='ACTOR_COMPUTE_UNITS')] = None
69+
dataset_reads: Annotated[int | None, Field(alias='DATASET_READS')] = None
70+
dataset_writes: Annotated[int | None, Field(alias='DATASET_WRITES')] = None
71+
key_value_store_reads: Annotated[int | None, Field(alias='KEY_VALUE_STORE_READS')] = None
72+
key_value_store_writes: Annotated[int | None, Field(alias='KEY_VALUE_STORE_WRITES')] = None
73+
key_value_store_lists: Annotated[int | None, Field(alias='KEY_VALUE_STORE_LISTS')] = None
74+
request_queue_reads: Annotated[int | None, Field(alias='REQUEST_QUEUE_READS')] = None
75+
request_queue_writes: Annotated[int | None, Field(alias='REQUEST_QUEUE_WRITES')] = None
76+
data_transfer_internal_gbytes: Annotated[int | None, Field(alias='DATA_TRANSFER_INTERNAL_GBYTES')] = None
77+
data_transfer_external_gbytes: Annotated[int | None, Field(alias='DATA_TRANSFER_EXTERNAL_GBYTES')] = None
78+
proxy_residential_transfer_gbytes: Annotated[int | None, Field(alias='PROXY_RESIDENTIAL_TRANSFER_GBYTES')] = None
79+
proxy_serps: Annotated[int | None, Field(alias='PROXY_SERPS')] = None
80+
81+
82+
class ActorRun(BaseModel):
83+
__model_config__ = ConfigDict(populate_by_name=True)
84+
85+
id: Annotated[str, Field(alias='id')]
86+
act_id: Annotated[str, Field(alias='actId')]
87+
user_id: Annotated[str, Field(alias='userId')]
88+
actor_task_id: Annotated[str | None, Field(alias='actorTaskId')] = None
89+
started_at: Annotated[datetime, Field(alias='startedAt')]
90+
finished_at: Annotated[datetime | None, Field(alias='finishedAt')] = None
91+
status: Annotated[ActorJobStatus, Field(alias='status')]
92+
status_message: Annotated[str | None, Field(alias='statusMessage')] = None
93+
is_status_message_terminal: Annotated[bool, Field(alias='isStatusMessageTerminal')] = False
94+
meta: Annotated[ActorRunMeta, Field(alias='meta')]
95+
stats: Annotated[ActorRunStats, Field(alias='stats')]
96+
options: Annotated[ActorRunOptions, Field(alias='options')]
97+
build_id: Annotated[str, Field(alias='buildId')]
98+
exit_code: Annotated[int | None, Field(alias='exitCode')] = None
99+
default_key_value_store_id: Annotated[str, Field(alias='defaultKeyValueStoreId')]
100+
default_dataset_id: Annotated[str, Field(alias='defaultDatasetId')]
101+
default_request_queue_id: Annotated[str, Field(alias='defaultRequestQueueId')]
102+
build_number: Annotated[str, Field(alias='buildNumber')]
103+
container_url: Annotated[str, Field(alias='containerUrl')]
104+
is_container_server_ready: Annotated[bool, Field(alias='isContainerServerReady')] = False
105+
git_branch_name: Annotated[str | None, Field(alias='gitBranchName')] = None
106+
usage: Annotated[ActorRunUsage | None, Field(alias='usage')] = None
107+
usage_total_usd: Annotated[float | None, Field(alias='usageTotalUsd')] = None
108+
usage_usd: Annotated[float | None, Field(alias='usageUsd')] = None

0 commit comments

Comments
 (0)