Skip to content

Commit 2d70b86

Browse files
committed
feat: support abort_with_parent in Actor.call_task
1 parent 27a6ac7 commit 2d70b86

3 files changed

Lines changed: 36 additions & 11 deletions

File tree

‎src/apify/_actor.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,7 @@ async def call_task(
12861286
wait: timedelta | None = None,
12871287
token: str | None = None,
12881288
run_name: str | None = None,
1289+
abort_with_parent: bool = False,
12891290
) -> Run:
12901291
"""Start an Actor task on the Apify Platform and wait for it to finish before returning.
12911292
@@ -1317,10 +1318,16 @@ async def call_task(
13171318
resurrected, and a new run is started only when nothing is recorded under the name, or the recorded
13181319
run `FAILED` or no longer exists. The name is bound to `task_id` exactly as passed, so reusing it with
13191320
any other value, or for an Actor, raises a `ValueError`.
1321+
abort_with_parent: If true, the child run is gracefully aborted when this Actor run is gracefully
1322+
aborted. It requires `run_name`, and the value is recorded under it, replacing the one from an earlier
1323+
call. A hard abort, a timeout or a crash of this Actor run leaves the child running.
13201324
13211325
Returns:
13221326
Info about the started Actor run.
13231327
"""
1328+
if abort_with_parent and run_name is None:
1329+
raise ValueError('`abort_with_parent` requires `run_name`, since only named child runs are tracked.')
1330+
13241331
client = self.new_client(token=token) if token else self.apify_client
13251332

13261333
if timeout == 'inherit':
@@ -1363,7 +1370,7 @@ async def call_task(
13631370
restart_on_error=restart_on_error,
13641371
memory_mbytes=memory_mbytes,
13651372
run_timeout=task_call_timeout,
1366-
abort_with_parent=False,
1373+
abort_with_parent=abort_with_parent,
13671374
)
13681375
run = await self._wait_for_child_run(
13691376
client.run(started_run.id), started_run, wait=wait, logger=None, from_start=False

‎tests/e2e/test_actor_child_runs.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ async def main() -> None:
111111
return
112112

113113
actor_id = Actor.configuration.actor_id or ''
114-
await Actor.start(actor_id=actor_id, run_input={'is_child': True}, name='child', abort_with_parent=True)
114+
await Actor.start(actor_id=actor_id, run_input={'is_child': True}, run_name='child', abort_with_parent=True)
115115
await asyncio.sleep(300)
116116

117117
actor = await make_actor(label='child-run-abort-with-parent', main_func=main)

‎tests/unit/actor/test_actor_child_runs.py‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -521,21 +521,24 @@ async def test_child_runs_includes_run_started_in_this_attempt(
521521
[
522522
pytest.param('start', id='start'),
523523
pytest.param('call', id='call'),
524+
pytest.param('call_task', id='call task'),
524525
],
525526
)
526-
async def test_abort_with_parent_requires_name(
527+
async def test_abort_with_parent_requires_run_name(
527528
apify_client_async_patcher: ApifyClientAsyncPatcher, method: str
528529
) -> None:
529-
"""`abort_with_parent` without a `name` raises before any run is started."""
530-
apify_client_async_patcher.patch('actor', 'start', return_value=make_run('new-run', 'READY'))
531-
apify_client_async_patcher.patch('actor', 'call', return_value=make_run('new-run', 'SUCCEEDED'))
530+
"""`abort_with_parent` without a `run_name` raises before any run is started."""
531+
for resource in ('actor', 'task'):
532+
apify_client_async_patcher.patch(resource, 'start', return_value=make_run('new-run', 'READY'))
533+
apify_client_async_patcher.patch(resource, 'call', return_value=make_run('new-run', 'SUCCEEDED'))
532534

533535
async with Actor:
534536
with pytest.raises(ValueError, match='requires `run_name`'):
535-
await getattr(Actor, method)('some-actor', abort_with_parent=True)
537+
await getattr(Actor, method)('some-id', abort_with_parent=True)
536538

537-
assert apify_client_async_patcher.calls['actor']['start'] == []
538-
assert apify_client_async_patcher.calls['actor']['call'] == []
539+
for resource in ('actor', 'task'):
540+
assert apify_client_async_patcher.calls[resource]['start'] == []
541+
assert apify_client_async_patcher.calls[resource]['call'] == []
539542

540543

541544
async def test_named_start_records_abort_with_parent(apify_client_async_patcher: ApifyClientAsyncPatcher) -> None:
@@ -550,6 +553,21 @@ async def test_named_start_records_abort_with_parent(apify_client_async_patcher:
550553
assert stored['scrape-eu']['abortWithParent'] is True
551554

552555

556+
async def test_named_call_task_records_abort_with_parent(
557+
apify_client_async_patcher: ApifyClientAsyncPatcher,
558+
) -> None:
559+
"""A named task call with `abort_with_parent` records the flag with the run."""
560+
apify_client_async_patcher.patch('task', 'start', return_value=make_run('new-run', 'READY'))
561+
apify_client_async_patcher.patch('run', 'wait_for_finish', return_value=make_run('new-run', 'SUCCEEDED'))
562+
563+
async with Actor:
564+
await Actor.call_task('some-task', run_name='scrape-eu', abort_with_parent=True)
565+
kvs = await Actor.open_key_value_store()
566+
stored = await kvs.get_value(CHILD_RUNS_KEY)
567+
568+
assert stored['scrape-eu']['abortWithParent'] is True
569+
570+
553571
async def test_reattach_replaces_recorded_abort_with_parent(
554572
apify_client_async_patcher: ApifyClientAsyncPatcher,
555573
) -> None:
@@ -734,7 +752,7 @@ async def test_exit_removes_the_aborting_listener(
734752

735753
async with apify_event_manager:
736754
async with Actor:
737-
await Actor.start('some-actor', name='scrape-eu', abort_with_parent=True)
755+
await Actor.start('some-actor', run_name='scrape-eu', abort_with_parent=True)
738756
apify_event_manager.emit(event=Event.ABORTING, event_data=EventAbortingData())
739757
await apify_event_manager.wait_for_all_listeners_to_complete()
740758

@@ -750,7 +768,7 @@ async def test_removing_all_aborting_listeners_keeps_aborting_child_runs(
750768
apify_client_async_patcher.patch('run', 'abort', return_value=None)
751769

752770
async with Actor:
753-
await Actor.start('some-actor', name='scrape-eu', abort_with_parent=True)
771+
await Actor.start('some-actor', run_name='scrape-eu', abort_with_parent=True)
754772
apify_event_manager.off(event=Event.ABORTING)
755773
apify_event_manager.emit(event=Event.ABORTING, event_data=EventAbortingData())
756774
await apify_event_manager.wait_for_all_listeners_to_complete()

0 commit comments

Comments
 (0)