diff --git a/src/apify/_actor.py b/src/apify/_actor.py index 150cf924..d854b800 100644 --- a/src/apify/_actor.py +++ b/src/apify/_actor.py @@ -128,7 +128,7 @@ def __init__( *, configure_logging: bool = True, exit_process: bool | None = None, - exit_code: int = 0, + exit_code: int | None = None, status_message: str | None = None, event_listeners_timeout: timedelta | None = EVENT_LISTENERS_TIMEOUT, cleanup_timeout: timedelta = timedelta(seconds=30), @@ -140,15 +140,35 @@ def __init__( configure_logging: Whether to set up the default logging configuration. exit_process: Whether the Actor should call `sys.exit` when the context manager exits. Defaults to True, except in IPython and Scrapy environments. - exit_code: The exit code the Actor should use when exiting. - status_message: Final status message to display upon Actor termination. + exit_code: Deprecated. Pass `exit_code` to `Actor.exit()` or `Actor.fail()`, or set `Actor.exit_code` + inside the context manager instead. Will be removed in version 5.0.0. + status_message: Deprecated. Pass `status_message` to `Actor.exit()` or `Actor.fail()`, or set + `Actor.status_message` inside the context manager instead. Will be removed in version 5.0.0. event_listeners_timeout: Maximum time to wait for Actor event listeners to complete before exiting. cleanup_timeout: Maximum time to wait for cleanup tasks to finish. """ + if exit_code is not None: + warnings.warn( + 'Passing `exit_code` to `Actor()` is deprecated and will be removed in version 5.0.0. ' + 'Pass it to `Actor.exit()` or `Actor.fail()`, or set `Actor.exit_code` inside the context manager ' + 'instead.', + DeprecationWarning, + stacklevel=3, + ) + + if status_message is not None: + warnings.warn( + 'Passing `status_message` to `Actor()` is deprecated and will be removed in version 5.0.0. ' + 'Pass it to `Actor.exit()` or `Actor.fail()`, or set `Actor.status_message` inside the context ' + 'manager instead.', + DeprecationWarning, + stacklevel=3, + ) + self._configuration = configuration self._configure_logging = configure_logging self._exit_process = self._get_default_exit_process() if exit_process is None else exit_process - self._exit_code = exit_code + self._exit_code = 0 if exit_code is None else exit_code self._status_message = status_message self._event_listeners_timeout = event_listeners_timeout self._cleanup_timeout = cleanup_timeout @@ -347,7 +367,7 @@ def __call__( *, configure_logging: bool = True, exit_process: bool | None = None, - exit_code: int = 0, + exit_code: int | None = None, event_listeners_timeout: timedelta | None = EVENT_LISTENERS_TIMEOUT, status_message: str | None = None, cleanup_timeout: timedelta = timedelta(seconds=30), diff --git a/tests/unit/actor/test_actor_lifecycle.py b/tests/unit/actor/test_actor_lifecycle.py index 19e4ea99..8966e333 100644 --- a/tests/unit/actor/test_actor_lifecycle.py +++ b/tests/unit/actor/test_actor_lifecycle.py @@ -5,6 +5,7 @@ import json import logging import sys +import warnings from datetime import UTC, datetime, timedelta from typing import TYPE_CHECKING, Any from unittest import mock @@ -494,3 +495,40 @@ async def test_actor_sequential_contexts(*, first_with_call: bool, second_with_c # The mock should have been called twice, once in each context. assert mock.call_count == 2 + + +@pytest.mark.parametrize( + 'exit_code', + [ + pytest.param(0, id='zero'), + pytest.param(42, id='non_zero'), + ], +) +def test_actor_constructor_exit_code_warns_deprecated(exit_code: int) -> None: + """Passing `exit_code` to `Actor()` emits a `DeprecationWarning` and points at the caller.""" + with pytest.warns(DeprecationWarning, match='Passing `exit_code` to `Actor\\(\\)` is deprecated') as record: + actor = Actor(exit_code=exit_code) + + assert len(record) == 1 + assert record[0].filename == __file__ + assert actor.exit_code == exit_code + + +def test_actor_constructor_status_message_warns_deprecated() -> None: + """Passing `status_message` to `Actor()` emits a `DeprecationWarning` and points at the caller.""" + with pytest.warns(DeprecationWarning, match='Passing `status_message` to `Actor\\(\\)` is deprecated') as record: + actor = Actor(status_message='Done') + + assert len(record) == 1 + assert record[0].filename == __file__ + assert actor.status_message == 'Done' + + +def test_actor_constructor_no_warning_without_deprecated_args() -> None: + """No deprecation warning is emitted when `exit_code` and `status_message` are omitted from `Actor()`.""" + with warnings.catch_warnings(): + warnings.simplefilter('error', DeprecationWarning) + actor = Actor() + + assert actor.exit_code == 0 + assert actor.status_message is None