diff --git a/src/apify/_actor.py b/src/apify/_actor.py index 11ebe78d..1c5a290f 100644 --- a/src/apify/_actor.py +++ b/src/apify/_actor.py @@ -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: @@ -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 @@ -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) @@ -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 @@ -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( @@ -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 @@ -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) @@ -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 @@ -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( @@ -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 @@ -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) diff --git a/tests/unit/actor/test_actor_helpers.py b/tests/unit/actor/test_actor_helpers.py index a30b2fb7..5ac9777a 100644 --- a/tests/unit/actor/test_actor_helpers.py +++ b/tests/unit/actor/test_actor_helpers.py @@ -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 @@ -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'), [ @@ -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']