Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions src/apify/_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import sys
import warnings
from contextlib import suppress
from datetime import datetime, timedelta, timezone
from functools import cached_property
Expand Down Expand Up @@ -827,7 +828,7 @@ async def start(
content_type: str | None = None,
build: str | None = None,
memory_mbytes: int | None = None,
timeout: timedelta | None | Literal['RemainingTime'] = None,
timeout: timedelta | None | Literal['inherit', 'RemainingTime'] = None,
wait_for_finish: int | None = None,
webhooks: list[Webhook] | None = None,
) -> ActorRun:
Expand All @@ -845,8 +846,8 @@ async def start(
memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
in the default run configuration for the Actor.
timeout: Optional timeout for the run, in seconds. By default, the run uses timeout specified in
the default run configuration for the Actor. Using `RemainingTime` will set timeout of the other Actor
to the time remaining from this Actor timeout.
the default run configuration for the Actor. Using `inherit` or `RemainingTime` will set timeout of the
other Actor to the time remaining from this Actor timeout.
wait_for_finish: The maximum number of seconds the server waits for the run to finish. By default,
it is 0, the maximum value is 300.
webhooks: Optional ad-hoc webhooks (https://docs.apify.com/webhooks/ad-hoc-webhooks) associated with
Expand All @@ -867,14 +868,22 @@ async def start(
else:
serialized_webhooks = None

if timeout == 'RemainingTime':
if timeout in {'inherit', 'RemainingTime'}:
if timeout == 'RemainingTime':
warnings.warn(
'`RemainingTime` is deprecated and will be removed in version 4.0.0. Use `inherit` instead.',
DeprecationWarning,
stacklevel=2,
)
actor_start_timeout = self._get_remaining_time()
elif timeout is None:
actor_start_timeout = None
elif isinstance(timeout, timedelta):
actor_start_timeout = timeout
else:
raise ValueError(f'Invalid timeout {timeout!r}: expected `None`, `"RemainingTime"`, or a `timedelta`.')
raise ValueError(
f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, `"RemainingTime"`, or a `timedelta`.'
)

api_result = await client.actor(actor_id).start(
run_input=run_input,
Expand Down Expand Up @@ -931,7 +940,7 @@ async def call(
content_type: str | None = None,
build: str | None = None,
memory_mbytes: int | None = None,
timeout: timedelta | None | Literal['RemainingTime'] = None,
timeout: timedelta | None | Literal['inherit', 'RemainingTime'] = None,
webhooks: list[Webhook] | None = None,
wait: timedelta | None = None,
logger: logging.Logger | None | Literal['default'] = 'default',
Expand All @@ -950,8 +959,8 @@ async def call(
memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
in the default run configuration for the Actor.
timeout: Optional timeout for the run, in seconds. By default, the run uses timeout specified in
the default run configuration for the Actor. Using `RemainingTime` will set timeout of the other Actor
to the time remaining from this Actor timeout.
the default run configuration for the Actor. Using `inherit` or `RemainingTime` will set timeout of the
other Actor to the time remaining from this Actor timeout.
webhooks: Optional webhooks (https://docs.apify.com/webhooks) associated with the Actor run, which can
be used to receive a notification, e.g. when the Actor finished or failed. If you already have
a webhook set up for the Actor, you do not have to add it again here.
Expand All @@ -975,14 +984,23 @@ async def call(
else:
serialized_webhooks = None

if timeout == 'RemainingTime':
if timeout in {'inherit', 'RemainingTime'}:
if timeout == 'RemainingTime':
warnings.warn(
'`RemainingTime` is deprecated and will be removed in version 4.0.0. Use `inherit` instead.',
DeprecationWarning,
stacklevel=2,
)

actor_call_timeout = self._get_remaining_time()
elif timeout is None:
actor_call_timeout = None
elif isinstance(timeout, timedelta):
actor_call_timeout = timeout
else:
raise ValueError(f'Invalid timeout {timeout!r}: expected `None`, `"RemainingTime"`, or a `timedelta`.')
raise ValueError(
f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, `"RemainingTime"`, or a `timedelta`.'
)

api_result = await client.actor(actor_id).call(
run_input=run_input,
Expand All @@ -1004,7 +1022,7 @@ async def call_task(
*,
build: str | None = None,
memory_mbytes: int | None = None,
timeout: timedelta | None = None,
timeout: timedelta | None | Literal['inherit'] = None,
webhooks: list[Webhook] | None = None,
wait: timedelta | None = None,
token: str | None = None,
Expand All @@ -1026,7 +1044,8 @@ async def call_task(
memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
in the default run configuration for the Actor.
timeout: Optional timeout for the run, in seconds. By default, the run uses timeout specified in
the default run configuration for the Actor.
the default run configuration for the Actor. Using `inherit` will set timeout of the other Actor to the
time remaining from this Actor timeout.
webhooks: Optional webhooks (https://docs.apify.com/webhooks) associated with the Actor run, which can
be used to receive a notification, e.g. when the Actor finished or failed. If you already have
a webhook set up for the Actor, you do not have to add it again here.
Expand All @@ -1047,11 +1066,20 @@ async def call_task(
else:
serialized_webhooks = None

if timeout == 'inherit':
task_call_timeout = self._get_remaining_time()
elif timeout is None:
task_call_timeout = None
elif isinstance(timeout, timedelta):
task_call_timeout = timeout
else:
raise ValueError(f'Invalid timeout {timeout!r}: expected `None`, `"inherit"`, or a `timedelta`.')

api_result = await client.task(task_id).call(
task_input=task_input,
build=build,
memory_mbytes=memory_mbytes,
timeout_secs=int(timeout.total_seconds()) if timeout is not None else None,
timeout_secs=int(task_call_timeout.total_seconds()) if task_call_timeout is not None else None,
webhooks=serialized_webhooks,
wait_secs=int(wait.total_seconds()) if wait is not None else None,
)
Expand Down Expand Up @@ -1321,7 +1349,7 @@ def _get_remaining_time(self) -> timedelta | None:
return self.configuration.timeout_at - datetime.now(tz=timezone.utc)

self.log.warning(
'Returning `None` instead of remaining time. Using `RemainingTime` argument is only possible when the Actor'
'Using `inherit` or `RemainingTime` argument is only possible when the Actor'
' is running on the Apify platform and when the timeout for the Actor run is set. '
f'{self.is_at_home()=}, {self.configuration.timeout_at=}'
)
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/actor/test_actor_call_timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from .conftest import MakeActorFunction, RunActorFunction


async def test_actor_start_remaining_timeout(
async def test_actor_start_inherit_timeout(
make_actor: MakeActorFunction,
run_actor: RunActorFunction,
) -> None:
"""Test that correct timeout is set when using `RemainingTime` value for the `timeout` argument.
"""Test that correct timeout is set when using `inherit` value for the `timeout` argument.

In this test, one Actor starts itself again and checks that the timeout is correctly set on the second Actor run.
Timeout should be the remaining time of the first Actor run calculated at the moment of the other Actor start."""
Expand All @@ -32,7 +32,7 @@ async def main() -> None:
other_run_data = await Actor.call(
actor_id=Actor.configuration.actor_id or '',
run_input={'called_from_another_actor': True},
timeout='RemainingTime',
timeout='inherit',
)
assert other_run_data is not None
try:
Expand All @@ -50,17 +50,17 @@ async def main() -> None:
# Make sure the other actor run is aborted
await Actor.apify_client.run(other_run_data.id).abort()

actor = await make_actor(label='remaining-timeout', main_func=main)
actor = await make_actor(label='inherit-timeout', main_func=main)
run_result = await run_actor(actor)

assert run_result.status == 'SUCCEEDED'


async def test_actor_call_remaining_timeout(
async def test_actor_call_inherit_timeout(
make_actor: MakeActorFunction,
run_actor: RunActorFunction,
) -> None:
"""Test that correct timeout is set when using `RemainingTime` value for the `timeout` argument.
"""Test that correct timeout is set when using `inherit` value for the `timeout` argument.

In this test, one Actor starts itself again and checks that the timeout is correctly set on the second Actor run.
Timeout should be the remaining time of the first Actor run calculated at the moment of the other Actor call."""
Expand All @@ -79,7 +79,7 @@ async def main() -> None:
other_run_data = await Actor.call(
actor_id=Actor.configuration.actor_id or '',
run_input={'called_from_another_actor': True},
timeout='RemainingTime',
timeout='inherit',
)

assert other_run_data is not None
Expand Down