Skip to content
46 changes: 36 additions & 10 deletions src/apify/_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
_ensure_context = ensure_context('_active')


def _warn_max_items_deprecated() -> None:
warnings.warn(
'`max_items` is deprecated and will be removed in version 5.0.0. It works only with legacy pay-per-result '
'Actors. Use `max_total_charge_usd` instead.',
FutureWarning,
# This helper, the SDK method, and its `ensure_context` wrapper sit between the warning and the caller.
stacklevel=4,
)


@docs_name('Actor')
@docs_group('Actor')
class _ActorType:
Expand Down Expand Up @@ -956,8 +966,8 @@ async def start(
content_type: The content type of the input.
build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
the run uses the build specified in the default run configuration for the Actor (typically latest).
max_items: Maximum number of dataset items you are charged for, for pay-per-result Actors. It caps the
charge, not the output, so the run can return fewer or more items than this.
max_items: Deprecated, use `max_total_charge_usd` instead. Will be removed in version 5.0.0. Works only with
legacy pay-per-result Actors.
max_total_charge_usd: A limit on the total charged amount, in USD. Once the run exceeds it, the platform
aborts the run, which takes a few seconds, so the final charge can slightly overshoot the limit.
restart_on_error: If true, the Actor run process will be restarted whenever it exits with
Expand All @@ -976,6 +986,9 @@ async def start(
Returns:
Info about the started Actor run
"""
if max_items is not None:
_warn_max_items_deprecated()

client = self.new_client(token=token) if token else self.apify_client

actor_client = client.actor(actor_id)
Expand Down Expand Up @@ -1057,8 +1070,9 @@ async def resurrect(
timeout: New timeout for the resurrected run. By default, the resurrected run uses the same timeout as
before. Using `inherit` will set timeout of the resurrected run to the time remaining from this Actor
timeout.
max_items: Maximum number of items that the resurrected pay-per-result run will return. By default, the
resurrected run uses the same limit as before. The limit can only be increased.
max_items: Deprecated, use `max_total_charge_usd` instead. Will be removed in version 5.0.0. Works only with
legacy pay-per-result Actors. By default, the resurrected run uses the same limit as before. The limit
can only be increased.
max_total_charge_usd: A limit on the total charged amount of the resurrected run, in USD. Once the run
exceeds it, the platform aborts the run, which takes a few seconds, so the final charge can slightly
overshoot the limit. By default, the resurrected run uses the same limit as before. The limit can only
Expand All @@ -1069,6 +1083,9 @@ async def resurrect(
Returns:
Info about the resurrected Actor run.
"""
if max_items is not None:
_warn_max_items_deprecated()

client = self.new_client(token=token) if token else self.apify_client

return await client.run(run_id).resurrect(
Expand Down Expand Up @@ -1110,8 +1127,8 @@ async def call(
content_type: The content type of the input.
build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
the run uses the build specified in the default run configuration for the Actor (typically latest).
max_items: Maximum number of dataset items you are charged for, for pay-per-result Actors. It caps the
charge, not the output, so the run can return fewer or more items than this.
max_items: Deprecated, use `max_total_charge_usd` instead. Will be removed in version 5.0.0. Works only with
legacy pay-per-result Actors.
max_total_charge_usd: A limit on the total charged amount, in USD. Once the run exceeds it, the platform
aborts the run, which takes a few seconds, so the final charge can slightly overshoot the limit.
restart_on_error: If true, the Actor run process will be restarted whenever it exits with
Expand All @@ -1134,6 +1151,9 @@ async def call(
Returns:
Info about the started Actor run.
"""
if max_items is not None:
_warn_max_items_deprecated()

client = self.new_client(token=token) if token else self.apify_client

actor_client = client.actor(actor_id)
Expand Down Expand Up @@ -1186,8 +1206,8 @@ async def start_task(
token: The Apify API token to use for this request (defaults to the `APIFY_TOKEN` environment variable).
build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
the run uses the build specified in the default run configuration for the Actor (typically latest).
max_items: Maximum number of dataset items you are charged for, for pay-per-result Actors. It caps the
charge, not the output, so the run can return fewer or more items than this.
max_items: Deprecated, use `max_total_charge_usd` instead. Will be removed in version 5.0.0. Works only with
legacy pay-per-result Actors.
max_total_charge_usd: A limit on the total charged amount, in USD. Once the run exceeds it, the platform
aborts the run, which takes a few seconds, so the final charge can slightly overshoot the limit.
restart_on_error: If true, the Task run process will be restarted whenever it exits with
Expand All @@ -1204,6 +1224,9 @@ async def start_task(
Returns:
Info about the started Actor run.
"""
if max_items is not None:
_warn_max_items_deprecated()

client = self.new_client(token=token) if token else self.apify_client
task_client = client.task(task_id)
return await task_client.start(
Expand Down Expand Up @@ -1246,8 +1269,8 @@ async def call_task(
token: The Apify API token to use for this request (defaults to the `APIFY_TOKEN` environment variable).
build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
the run uses the build specified in the default run configuration for the Actor (typically latest).
max_items: Maximum number of dataset items you are charged for, for pay-per-result Actors. It caps the
charge, not the output, so the run can return fewer or more items than this.
max_items: Deprecated, use `max_total_charge_usd` instead. Will be removed in version 5.0.0. Works only with
legacy pay-per-result Actors.
max_total_charge_usd: A limit on the total charged amount, in USD. Once the run exceeds it, the platform
aborts the run, which takes a few seconds, so the final charge can slightly overshoot the limit.
restart_on_error: If true, the Task run process will be restarted whenever it exits with
Expand All @@ -1265,6 +1288,9 @@ async def call_task(
Returns:
Info about the started Actor run.
"""
if max_items is not None:
_warn_max_items_deprecated()

client = self.new_client(token=token) if token else self.apify_client

task_client = client.task(task_id)
Expand Down
77 changes: 67 additions & 10 deletions tests/unit/actor/test_actor_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import logging
import warnings
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -153,11 +154,66 @@ async def test_max_items_forwarded_to_client(
apify_client_async_patcher.patch(client_resource, client_method, return_value=fake_actor_run)

async with Actor:
await getattr(Actor, sdk_method)('some-id', max_items=42)
with pytest.warns(FutureWarning):
await getattr(Actor, sdk_method)('some-id', max_items=42)

assert apify_client_async_patcher.calls[client_resource][client_method][0][1]['max_items'] == 42


@pytest.mark.parametrize(
('client_resource', 'client_method', 'sdk_method'),
[
pytest.param('actor', 'start', 'start', id='start'),
pytest.param('actor', 'call', 'call', id='call'),
pytest.param('task', 'start', 'start_task', id='start_task'),
pytest.param('task', 'call', 'call_task', id='call_task'),
pytest.param('run', 'resurrect', 'resurrect', id='resurrect'),
],
)
async def test_max_items_warns_deprecated(
apify_client_async_patcher: ApifyClientAsyncPatcher,
fake_actor_run: Run,
client_resource: str,
client_method: str,
sdk_method: str,
) -> None:
"""Passing `max_items` emits a `FutureWarning` that recommends `max_total_charge_usd` and points at the caller."""
apify_client_async_patcher.patch(client_resource, client_method, return_value=fake_actor_run)

async with Actor:
with pytest.warns(FutureWarning, match='`max_total_charge_usd`') as record:
await getattr(Actor, sdk_method)('some-id', max_items=42)

assert len(record) == 1
assert record[0].filename == __file__


@pytest.mark.parametrize(
('client_resource', 'client_method', 'sdk_method'),
[
pytest.param('actor', 'start', 'start', id='start'),
pytest.param('actor', 'call', 'call', id='call'),
pytest.param('task', 'start', 'start_task', id='start_task'),
pytest.param('task', 'call', 'call_task', id='call_task'),
pytest.param('run', 'resurrect', 'resurrect', id='resurrect'),
],
)
async def test_no_max_items_warning_without_max_items(
apify_client_async_patcher: ApifyClientAsyncPatcher,
fake_actor_run: Run,
client_resource: str,
client_method: str,
sdk_method: str,
) -> None:
"""The `max_items` deprecation warning is not emitted when `max_items` is not passed."""
apify_client_async_patcher.patch(client_resource, client_method, return_value=fake_actor_run)

async with Actor:
with warnings.catch_warnings():
warnings.simplefilter('error', FutureWarning)
await getattr(Actor, sdk_method)('some-id')


@pytest.mark.parametrize(
('client_resource', 'client_method', 'sdk_method'),
[
Expand Down Expand Up @@ -201,15 +257,16 @@ async def test_resurrect_actor_run(apify_client_async_patcher: ApifyClientAsyncP
run_id = 'some-run-id'

async with Actor:
run = await Actor.resurrect(
run_id,
build='beta',
memory_mbytes=1024,
timeout=timedelta(seconds=120),
max_items=100,
max_total_charge_usd=Decimal('2.5'),
restart_on_error=True,
)
with pytest.warns(FutureWarning):
run = await Actor.resurrect(
run_id,
build='beta',
memory_mbytes=1024,
timeout=timedelta(seconds=120),
max_items=100,
max_total_charge_usd=Decimal('2.5'),
restart_on_error=True,
)

assert run is fake_actor_run
calls = apify_client_async_patcher.calls['run']['resurrect']
Expand Down
Loading