Files
wehub-resource-sync 85742ab165
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:17 +08:00

198 lines
5.9 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import base64
import json
from dataclasses import dataclass
from typing import Any, Dict, Optional, cast
import pytest
from agentlightning.emitter import emit_object
from agentlightning.emitter import object as object_module
from agentlightning.emitter.object import encode_object, get_object_value
from agentlightning.semconv import AGL_OBJECT, LightningSpanAttributes
from agentlightning.types.tracer import SpanLike
@dataclass
class FakeSpan:
attributes: Optional[Dict[str, Any]]
class DummySpan:
def __enter__(self) -> "DummySpan":
return self
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> bool:
return False
class DummyTracer:
def __init__(self, span: DummySpan) -> None:
self._span = span
self.last_name: Optional[str] = None
self.last_attributes: Optional[Dict[str, Any]] = None
def create_span(self, name: str, attributes: Optional[Dict[str, Any]] = None, **kwargs: Any) -> DummySpan:
self.last_name = name
self.last_attributes = attributes or {}
return self._span
def _stub_tracer(monkeypatch: pytest.MonkeyPatch, span: DummySpan) -> DummyTracer:
tracer = DummyTracer(span)
def fake_get_tracer(*_: Any, **__: Any) -> DummyTracer:
return tracer
monkeypatch.setattr(object_module, "get_active_tracer", fake_get_tracer)
return tracer
def test_encode_object_for_primitives() -> None:
encoded = encode_object("hello")
assert encoded == {
LightningSpanAttributes.OBJECT_TYPE.value: "str",
LightningSpanAttributes.OBJECT_LITERAL.value: "hello",
}
def test_encode_object_for_bytes() -> None:
payload = b"binary"
encoded = encode_object(payload)
expected_literal = base64.b64encode(payload).decode("utf-8")
assert encoded == {
LightningSpanAttributes.OBJECT_TYPE.value: "bytes",
LightningSpanAttributes.OBJECT_LITERAL.value: expected_literal,
}
def test_encode_object_for_dict_serializes_json() -> None:
payload = {"key": 1, "nested": [1, 2]}
encoded = encode_object(payload)
assert encoded[LightningSpanAttributes.OBJECT_TYPE.value] == "dict"
assert json.loads(encoded[LightningSpanAttributes.OBJECT_JSON.value]) == payload
def test_encode_object_raises_for_unserializable() -> None:
with pytest.raises(RuntimeError):
encode_object(object())
def test_get_object_value_from_json_attribute() -> None:
payload = {"answer": 42}
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_JSON.value: json.dumps(payload),
LightningSpanAttributes.OBJECT_TYPE.value: "dict",
}
)
assert get_object_value(cast(SpanLike, span)) == payload
def test_get_object_value_from_literal_types() -> None:
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_LITERAL.value: "123",
LightningSpanAttributes.OBJECT_TYPE.value: "int",
}
)
assert get_object_value(cast(SpanLike, span)) == 123
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_LITERAL.value: "3.14",
LightningSpanAttributes.OBJECT_TYPE.value: "float",
}
)
assert get_object_value(cast(SpanLike, span)) == pytest.approx(3.14) # type: ignore
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_LITERAL.value: "true",
LightningSpanAttributes.OBJECT_TYPE.value: "bool",
}
)
assert get_object_value(cast(SpanLike, span)) is True
literal = base64.b64encode(b"hi").decode("utf-8")
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_LITERAL.value: literal,
LightningSpanAttributes.OBJECT_TYPE.value: "bytes",
}
)
assert get_object_value(cast(SpanLike, span)) == b"hi"
def test_get_object_value_returns_none_when_missing() -> None:
span = FakeSpan(attributes={})
assert get_object_value(cast(SpanLike, span)) is None
def test_get_object_value_raises_for_invalid_json() -> None:
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_JSON.value: "not json",
}
)
with pytest.raises(RuntimeError):
get_object_value(cast(SpanLike, span))
def test_emit_object_serializes_payload(monkeypatch: pytest.MonkeyPatch) -> None:
span = DummySpan()
tracer = _stub_tracer(monkeypatch, span)
payload = {"foo": "bar", "baz": [1, 2, 3]}
emit_object(payload)
assert tracer.last_name == AGL_OBJECT
assert tracer.last_attributes is not None
assert json.loads(tracer.last_attributes[LightningSpanAttributes.OBJECT_JSON.value]) == payload
def test_emit_object_requires_json_serializable() -> None:
with pytest.raises(RuntimeError):
emit_object(object()) # type: ignore[arg-type]
def test_get_object_value_raises_for_unknown_literal_type() -> None:
span = FakeSpan(
attributes={
LightningSpanAttributes.OBJECT_LITERAL.value: "value",
LightningSpanAttributes.OBJECT_TYPE.value: "complex",
}
)
with pytest.raises(RuntimeError):
get_object_value(cast(SpanLike, span))
def test_emit_object_flattens_attributes(monkeypatch: pytest.MonkeyPatch) -> None:
span = DummySpan()
tracer = _stub_tracer(monkeypatch, span)
emit_object({"foo": "bar"}, attributes={"meta": {"tag": "foo"}, "labels": ["x", "y"]})
assert tracer.last_attributes is not None
assert tracer.last_attributes["meta.tag"] == "foo"
assert tracer.last_attributes["labels"] == ["x", "y"]
def test_emit_object_propagate_false(monkeypatch: pytest.MonkeyPatch) -> None:
def fail_get_active_tracer() -> DummyTracer:
raise AssertionError("Should not resolve tracer when propagate=False")
monkeypatch.setattr(object_module, "get_active_tracer", fail_get_active_tracer)
emit_object({"foo": "bar"}, propagate=False)