Skip to content
Open
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
8 changes: 5 additions & 3 deletions docs/02_concepts/10_configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Customize Actor behavior through the Configuration class or environ
import RunnableCodeBlock from '@site/src/components/RunnableCodeBlock';

import ConfigExample from '!!raw-loader!roa-loader!./code/10_config.py';
import GetEnvExample from '!!raw-loader!roa-loader!./code/10_get_env.py';
import RuntimeEnvironmentExample from '!!raw-loader!roa-loader!./code/10_runtime_environment.py';
import PlatformDetectionExample from '!!raw-loader!roa-loader!./code/10_platform_detection.py';
import ApiLink from '@theme/ApiLink';

Expand Down Expand Up @@ -52,10 +52,12 @@ The following table lists a few options you are most likely to set yourself. Whe

## Reading the runtime environment

The <ApiLink to="class/Actor#get_env">`Actor.get_env`</ApiLink> method returns a dictionary with all `APIFY_*` environment variables parsed into their typed values. This is useful for inspecting the Actor's runtime context, such as the Actor ID, run ID, or default storage IDs. Variables that are not set or are invalid will have a value of `None`.
The <ApiLink to="class/Actor#configuration">`Actor.configuration`</ApiLink> property exposes the `APIFY_*` and `ACTOR_*` environment variables parsed into their typed values. This is useful for inspecting the Actor's runtime context, such as the Actor ID, run ID, or default storage IDs. Variables that are not set are `None`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: not every unset variable is None. For example, input_key defaults to 'INPUT' and is_at_home to False.

Suggested change
The <ApiLink to="class/Actor#configuration">`Actor.configuration`</ApiLink> property exposes the `APIFY_*` and `ACTOR_*` environment variables parsed into their typed values. This is useful for inspecting the Actor's runtime context, such as the Actor ID, run ID, or default storage IDs. Variables that are not set are `None`.
The <ApiLink to="class/Actor#configuration">`Actor.configuration`</ApiLink> property exposes the `APIFY_*` and `ACTOR_*` environment variables parsed into their typed values. This is useful for inspecting the Actor's runtime context, such as the Actor ID, run ID, or default storage IDs. Variables that are not set take their default value, which is `None` for most of them.


Variables that `Configuration` does not expose are read from `os.environ`, using the `ActorEnvVars` and `ApifyEnvVars` enums exported from `apify` for their names.

<RunnableCodeBlock className="language-python" language="python">
{GetEnvExample}
{RuntimeEnvironmentExample}
</RunnableCodeBlock>

## Platform detection
Expand Down
17 changes: 0 additions & 17 deletions docs/02_concepts/code/10_get_env.py

This file was deleted.

22 changes: 22 additions & 0 deletions docs/02_concepts/code/10_runtime_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import asyncio
import os

from apify import Actor, ApifyEnvVars


async def main() -> None:
async with Actor:
config = Actor.configuration

Actor.log.info(f'Actor ID: {config.actor_id}')
Actor.log.info(f'Run ID: {config.actor_run_id}')
Actor.log.info(f'Default dataset ID: {config.default_dataset_id}')
Actor.log.info(f'Default KVS ID: {config.default_key_value_store_id}')

# Variables the configuration does not expose are read from the environment.
latest_sdk_version = os.environ.get(ApifyEnvVars.SDK_LATEST_VERSION)
Actor.log.info(f'Latest SDK version: {latest_sdk_version}')


if __name__ == '__main__':
asyncio.run(main())
10 changes: 10 additions & 0 deletions src/apify/_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -981,10 +981,20 @@ def is_at_home(self) -> bool:
def get_env(self) -> dict:
"""Return a dictionary with information parsed from all the `APIFY_XXX` environment variables.

Deprecated. Read `Actor.configuration` instead, or `os.environ` for variables it does not expose.
Will be removed in version 5.0.0.

For a list of all the environment variables, see the
[Actor documentation](https://docs.apify.com/actors/development/environment-variables). If some variables
are not defined or are invalid, the corresponding value in the resulting dictionary will be None.
"""
warnings.warn(
'`Actor.get_env()` is deprecated and will be removed in version 5.0.0. Read `Actor.configuration` '
'instead, or `os.environ` for variables it does not expose.',
DeprecationWarning,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with the right stacklevel, DeprecationWarning stays hidden for Actor code run as python -m src, since that is not __main__. The max_items deprecation uses FutureWarning, which is shown by default. I would suggest to use the same here.

stacklevel=2,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: get_env is wrapped by _ensure_context, so stacklevel=2 points the warning at _utils.py, not at the caller. Python only shows a DeprecationWarning by default when it points at __main__, so users never see it. _warn_max_items_deprecated accounts for the wrapper the same way.

Suggested change
stacklevel=2,
# The `ensure_context` wrapper sits between this method and the caller.
stacklevel=3,

)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside this diff: the idempotency_key deprecation in Actor.add_webhook() also passes stacklevel=2 through the same _ensure_context wrapper, so it likely has the same problem as the comment below. Could you please resolve it here?

config = dict[str, Any]()
for field_name, field in Configuration.model_fields.items():
if field.deprecated:
Expand Down
20 changes: 10 additions & 10 deletions tests/e2e/test_actor_api_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ async def test_actor_retrieves_env_vars(
) -> None:
async def main() -> None:
async with Actor:
env_dict = Actor.get_env()
config = Actor.configuration

assert env_dict.get('is_at_home') is True
assert env_dict.get('token') is not None
assert env_dict.get('events_websocket_url') is not None
assert env_dict.get('input_key') is not None
assert config.is_at_home is True
assert config.token is not None
assert config.actor_events_ws_url is not None
assert config.input_key is not None

assert len(env_dict.get('id', '')) == 17
assert len(env_dict.get('build_id', '')) == 17
assert len(env_dict.get('run_id', '')) == 17
assert len(env_dict.get('user_id', '')) == 17
assert len(config.actor_id or '') == 17
assert len(config.actor_build_id or '') == 17
assert len(config.actor_run_id or '') == 17
assert len(config.user_id or '') == 17

actor = await make_actor(label='get-env', main_func=main)
actor = await make_actor(label='configuration', main_func=main)
run_result = await run_actor(actor)

assert run_result.status == 'SUCCEEDED'
Expand Down
14 changes: 11 additions & 3 deletions tests/unit/actor/test_actor_env_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Any

import pytest
from pydantic_core import TzInfo

from apify import Actor
Expand Down Expand Up @@ -82,8 +83,6 @@
if TYPE_CHECKING:
from pathlib import Path

import pytest


async def test_actor_is_not_at_home_when_local() -> None:
async with Actor as actor:
Expand Down Expand Up @@ -199,6 +198,15 @@ async def test_get_env_with_randomized_env_vars(monkeypatch: pytest.MonkeyPatch,
expected_get_env[ApifyEnvVars.CHARGED_ACTOR_EVENT_COUNTS.name.lower()] = None

await Actor.init()
assert Actor.get_env() == expected_get_env
with pytest.warns(DeprecationWarning, match=r'`Actor.get_env\(\)` is deprecated'):
env = Actor.get_env()
assert env == expected_get_env

await Actor.exit()


async def test_get_env_warns_about_deprecation() -> None:
"""`Actor.get_env()` emits a `DeprecationWarning` pointing at `Actor.configuration`."""
async with Actor:
with pytest.warns(DeprecationWarning, match=r'`Actor.get_env\(\)` is deprecated.*`Actor.configuration`'):
Actor.get_env()
Comment on lines +209 to +212

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: pytest.warns records the warning wherever it points, so this test passes with the wrong stacklevel. Asserting the filename locks it in, like test_max_items_warns_deprecated does.

Suggested change
"""`Actor.get_env()` emits a `DeprecationWarning` pointing at `Actor.configuration`."""
async with Actor:
with pytest.warns(DeprecationWarning, match=r'`Actor.get_env\(\)` is deprecated.*`Actor.configuration`'):
Actor.get_env()
"""`Actor.get_env()` emits a `DeprecationWarning` that recommends `Actor.configuration` and points at the caller."""
async with Actor:
with pytest.warns(DeprecationWarning, match=r'is deprecated.*`Actor.configuration`') as record:
Actor.get_env()
assert len(record) == 1
assert record[0].filename == __file__

Loading