Files
comet-ml--opik/sdks/python/tests/unit/api_objects/test_environment.py
T
wehub-resource-sync 5a558eb09e
Backend Tests / discover-tests (push) Waiting to run
Backend Tests / ${{ matrix.name }} (push) Blocked by required conditions
Build and Publish SDK / build-and-publish (push) Waiting to run
Build Opik Docker Images / set-version (push) Waiting to run
Build Opik Docker Images / build-backend (push) Blocked by required conditions
Build Opik Docker Images / build-sandbox-executor-python (push) Blocked by required conditions
Build Opik Docker Images / build-python-backend (push) Blocked by required conditions
Build Opik Docker Images / build-frontend (push) Blocked by required conditions
Build Opik Docker Images / create-git-tag (push) Blocked by required conditions
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Waiting to run
Docs - Publish / run (push) Waiting to run
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Waiting to run
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Blocked by required conditions
Frontend Unit Tests / Test on Node 20 (push) Waiting to run
Guardrails E2E Tests / Select Python version matrix (push) Waiting to run
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
Guardrails E2E Tests / 📢 Slack Notification (push) Blocked by required conditions
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Waiting to run
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Blocked by required conditions
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Waiting to run
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Waiting to run
Lint Opik Helm Chart / unittest-helm-chart (push) Waiting to run
Lint Opik Helm Chart / render-equality (push) Waiting to run
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Waiting to run
Python BE E2E Tests / Python BE E2E (push) Waiting to run
Python Backend Tests / run-python-backend-tests (push) Waiting to run
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
SDK E2E Libraries Integration Tests / Check Secrets (push) Waiting to run
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / build-opik (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Blocked by required conditions
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Waiting to run
TypeScript SDK Build & Publish / build-and-publish (push) Waiting to run
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Waiting to run
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

336 lines
10 KiB
Python

"""Unit tests for the SDK ``environment`` plumbing.
Covers:
- ``Opik.trace(environment=...)`` is the only entry point that accepts an
explicit environment; the value flows into the emitted ``CreateTraceMessage``.
- Spans (``Trace.span``, ``Span.span``, ``@track``-created spans) inherit the
parent trace's environment unconditionally.
- A nested ``@track(environment=...)`` whose value differs from the enclosing
trace's environment logs a warning and is ignored — the parent trace's value
wins (mirrors how mismatched ``project_name`` is handled).
- Backwards compat: existing call sites without ``environment`` still work.
"""
from unittest.mock import MagicMock
import opik
from opik.api_objects import opik_client
from opik.message_processing import messages
from opik import dict_utils
def _capture_messages(client: opik_client.Opik) -> MagicMock:
mock_streamer = MagicMock()
client._streamer = mock_streamer
return mock_streamer
def _create_trace_messages(streamer: MagicMock):
return [
c.args[0]
for c in streamer.put.call_args_list
if isinstance(c.args[0], messages.CreateTraceMessage)
]
def _create_span_messages(streamer: MagicMock):
return [
c.args[0]
for c in streamer.put.call_args_list
if isinstance(c.args[0], messages.CreateSpanMessage)
]
def test_opik_client__no_environment_set__messages_have_none_environment_and_payload_omits_it():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
client.trace(name="t")
client.span(name="s")
trace_msg = _create_trace_messages(streamer)[0]
span_msg = _create_span_messages(streamer)[0]
assert trace_msg.environment is None
assert span_msg.environment is None
cleaned = dict_utils.remove_none_from_dict(trace_msg.as_payload_dict())
assert "environment" not in cleaned
def test_opik_client_trace__environment_kwarg_propagates_to_create_trace_message():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
client.trace(name="t", environment="staging")
assert _create_trace_messages(streamer)[0].environment == "staging"
def test_opik_client_trace__span_inherits_trace_environment():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
trace = client.trace(name="t", environment="staging")
trace.span(name="s")
assert _create_span_messages(streamer)[0].environment == "staging"
def test_opik_client_trace__nested_span_inherits_environment():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
trace = client.trace(name="t", environment="staging")
span = trace.span(name="parent-span")
span.span(name="child-span")
span_msgs = _create_span_messages(streamer)
assert len(span_msgs) == 2
assert all(m.environment == "staging" for m in span_msgs)
def test_track_decorator__environment_propagates_to_root_trace_and_spans():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
opik_client.set_global_client(client)
@opik.track(environment="staging")
def my_fn():
return 42
my_fn()
trace_msgs = _create_trace_messages(streamer)
span_msgs = _create_span_messages(streamer)
assert trace_msgs and span_msgs
assert all(m.environment == "staging" for m in trace_msgs)
assert all(m.environment == "staging" for m in span_msgs)
def test_track_decorator__nested_environment_mismatch_warns_and_inherits_parent(
monkeypatch,
):
from opik.api_objects import helpers as opik_helpers
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
opik_client.set_global_client(client)
warnings = []
monkeypatch.setattr(
opik_helpers.LOGGER,
"warning",
lambda msg, *a, **kw: warnings.append(msg % a if a else msg),
)
@opik.track(environment="this-should-be-ignored")
def inner():
return 1
@opik.track(environment="staging")
def outer():
return inner()
outer()
span_msgs = _create_span_messages(streamer)
assert span_msgs
assert all(m.environment == "staging" for m in span_msgs)
assert any("this-should-be-ignored" in w and "staging" in w for w in warnings), (
warnings
)
def test_create_trace_message__environment_field_default_is_none_for_backwards_compat():
msg = messages.CreateTraceMessage(
trace_id="t",
project_name="p",
name=None,
start_time=None, # type: ignore[arg-type]
end_time=None,
input=None,
output=None,
metadata=None,
tags=None,
error_info=None,
thread_id=None,
last_updated_at=None,
source="sdk",
)
assert msg.environment is None
def test_update_environment__colour_on_builtin_raises_error():
from opik.exceptions import EnvironmentConfigurationError
client = opik_client.Opik(project_name="test-project")
for env_name in ("production", "staging", "development"):
try:
client.update_environment(env_name, color="#ff0000")
assert False, f"expected EnvironmentConfigurationError for {env_name!r}"
except EnvironmentConfigurationError as e:
assert env_name in str(e)
def test_update_environment__colour_on_builtin_not_called_when_no_colour():
from unittest.mock import patch
from opik.rest_api.types.environment_public import EnvironmentPublic
client = opik_client.Opik(project_name="test-project")
fake_env = EnvironmentPublic(id="abc", name="production")
with (
patch.object(client, "_find_environment_by_name", return_value=fake_env),
patch.object(
client._rest_client.environments,
"update_environment",
return_value=None,
) as mock_update,
patch.object(
client._rest_client.environments,
"get_environment_by_id",
return_value=fake_env,
),
):
result = client.update_environment("production", description="new desc")
mock_update.assert_called_once()
assert result == fake_env
def test_update_environment__colour_on_custom_env_is_allowed():
from unittest.mock import patch
from opik.rest_api.types.environment_public import EnvironmentPublic
client = opik_client.Opik(project_name="test-project")
fake_env = EnvironmentPublic(id="xyz", name="my-custom-env")
with (
patch.object(client, "_find_environment_by_name", return_value=fake_env),
patch.object(
client._rest_client.environments,
"update_environment",
return_value=None,
) as mock_update,
patch.object(
client._rest_client.environments,
"get_environment_by_id",
return_value=fake_env,
),
):
result = client.update_environment("my-custom-env", color="#123456")
assert result == fake_env
mock_update.assert_called_once()
assert mock_update.call_args.kwargs.get("color") == "#123456"
def test_create_environment__conflict_raises_environment_already_exists():
from unittest.mock import patch
from opik.exceptions import EnvironmentAlreadyExists
from opik.rest_api.errors import ConflictError
client = opik_client.Opik(project_name="test-project")
with patch.object(
client._rest_client.environments,
"create_environment",
side_effect=ConflictError(body={"message": "already exists"}),
):
try:
client.create_environment("production")
assert False, "expected EnvironmentAlreadyExists"
except EnvironmentAlreadyExists as e:
assert "production" in str(e)
def test_create_span_message__environment_field_default_is_none_for_backwards_compat():
msg = messages.CreateSpanMessage(
span_id="s",
trace_id="t",
project_name="p",
parent_span_id=None,
name=None,
start_time=None, # type: ignore[arg-type]
end_time=None,
input=None,
output=None,
metadata=None,
tags=None,
type="general",
usage=None,
model=None,
provider=None,
error_info=None,
total_cost=None,
last_updated_at=None,
source="sdk",
)
assert msg.environment is None
def _update_span_messages(streamer: MagicMock):
return [
c.args[0]
for c in streamer.put.call_args_list
if isinstance(c.args[0], messages.UpdateSpanMessage)
]
def _update_trace_messages(streamer: MagicMock):
return [
c.args[0]
for c in streamer.put.call_args_list
if isinstance(c.args[0], messages.UpdateTraceMessage)
]
def test_span_end__preserves_environment_in_update_message():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
trace = client.trace(name="t", environment="staging")
span = trace.span(name="s")
span.end()
update_msgs = _update_span_messages(streamer)
assert len(update_msgs) == 1
assert update_msgs[0].environment == "staging"
def test_span_update__preserves_environment_in_update_message():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
trace = client.trace(name="t", environment="production")
span = trace.span(name="s")
span.update(output={"result": "ok"})
update_msgs = _update_span_messages(streamer)
assert len(update_msgs) == 1
assert update_msgs[0].environment == "production"
def test_trace_end__preserves_environment_in_update_message():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
trace = client.trace(name="t", environment="staging")
trace.end()
update_msgs = _update_trace_messages(streamer)
assert len(update_msgs) == 1
assert update_msgs[0].environment == "staging"
def test_trace_update__preserves_environment_in_update_message():
client = opik_client.Opik(project_name="test-project")
streamer = _capture_messages(client)
trace = client.trace(name="t", environment="production")
trace.update(output={"result": "ok"})
update_msgs = _update_trace_messages(streamer)
assert len(update_msgs) == 1
assert update_msgs[0].environment == "production"