Python: improve .env handling and observability samples (#4032)

* Python: improve .env precedence and observability samples

- Switch load_settings to explicit precedence: overrides -> explicit .env -> environment -> defaults\n- Raise when env_file_path is provided but missing\n- Update settings docs and tests for new behavior\n- Refresh observability samples and README guidance for env loading options\n\nCloses #3864\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fixed some imports

* Fix load_settings CI regressions

Allow explicit env_file_path values that exist but are not regular files (for example /dev/null) by checking path existence before dotenv parsing, and restore a dict accumulator with typed return cast to satisfy mypy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Avoid implicit dotenv in observability

Only load dotenv in observability helpers when env_file_path is explicitly provided, and remove test os.devnull workarounds that are no longer necessary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-02-18 11:18:52 +00:00
committed by GitHub
co-authored by Copilot
parent f900febb6f
commit 534e5f5bf7
41 changed files with 250 additions and 221 deletions
@@ -196,9 +196,9 @@ FINISH_REASON_MAP: dict[str, FinishReasonLiteral] = {
class AnthropicSettings(TypedDict, total=False):
"""Anthropic Project settings.
The settings are first loaded from environment variables with the prefix 'ANTHROPIC_'.
If the environment variables are not found, the settings can be loaded from a .env file
with the encoding 'utf-8'.
Settings are resolved in this order: explicit keyword arguments, values from an
explicitly provided .env file, then environment variables with the prefix
'ANTHROPIC_'.
Keys:
api_key: The Anthropic API key.
@@ -50,7 +50,6 @@ def create_test_anthropic_client(
env_prefix="ANTHROPIC_",
api_key="test-api-key-12345",
chat_model_id="claude-3-5-sonnet-20241022",
env_file_path="test.env",
)
# Create client instance directly
@@ -72,7 +71,7 @@ def create_test_anthropic_client(
def test_anthropic_settings_init(anthropic_unit_test_env: dict[str, str]) -> None:
"""Test AnthropicSettings initialization."""
settings = load_settings(AnthropicSettings, env_prefix="ANTHROPIC_", env_file_path="test.env")
settings = load_settings(AnthropicSettings, env_prefix="ANTHROPIC_")
assert settings["api_key"] is not None
assert settings["api_key"].get_secret_value() == anthropic_unit_test_env["ANTHROPIC_API_KEY"]
@@ -86,7 +85,6 @@ def test_anthropic_settings_init_with_explicit_values() -> None:
env_prefix="ANTHROPIC_",
api_key="custom-api-key",
chat_model_id="claude-3-opus-20240229",
env_file_path="test.env",
)
assert settings["api_key"] is not None
@@ -97,7 +95,7 @@ def test_anthropic_settings_init_with_explicit_values() -> None:
@pytest.mark.parametrize("exclude_list", [["ANTHROPIC_API_KEY"]], indirect=True)
def test_anthropic_settings_missing_api_key(anthropic_unit_test_env: dict[str, str]) -> None:
"""Test AnthropicSettings when API key is missing."""
settings = load_settings(AnthropicSettings, env_prefix="ANTHROPIC_", env_file_path="test.env")
settings = load_settings(AnthropicSettings, env_prefix="ANTHROPIC_")
assert settings["api_key"] is None
assert settings["chat_model_id"] == anthropic_unit_test_env["ANTHROPIC_CHAT_MODEL_ID"]
@@ -119,7 +117,6 @@ def test_anthropic_client_init_auto_create_client(anthropic_unit_test_env: dict[
client = AnthropicClient(
api_key=anthropic_unit_test_env["ANTHROPIC_API_KEY"],
model_id=anthropic_unit_test_env["ANTHROPIC_CHAT_MODEL_ID"],
env_file_path="test.env",
)
assert client.anthropic_client is not None