mseep-agentops 0.4.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentops/__init__.py +488 -0
- agentops/client/__init__.py +5 -0
- agentops/client/api/__init__.py +71 -0
- agentops/client/api/base.py +162 -0
- agentops/client/api/types.py +21 -0
- agentops/client/api/versions/__init__.py +10 -0
- agentops/client/api/versions/v3.py +65 -0
- agentops/client/api/versions/v4.py +104 -0
- agentops/client/client.py +211 -0
- agentops/client/http/__init__.py +0 -0
- agentops/client/http/http_adapter.py +116 -0
- agentops/client/http/http_client.py +215 -0
- agentops/config.py +268 -0
- agentops/enums.py +36 -0
- agentops/exceptions.py +38 -0
- agentops/helpers/__init__.py +44 -0
- agentops/helpers/dashboard.py +54 -0
- agentops/helpers/deprecation.py +50 -0
- agentops/helpers/env.py +52 -0
- agentops/helpers/serialization.py +137 -0
- agentops/helpers/system.py +178 -0
- agentops/helpers/time.py +11 -0
- agentops/helpers/version.py +36 -0
- agentops/instrumentation/__init__.py +598 -0
- agentops/instrumentation/common/__init__.py +82 -0
- agentops/instrumentation/common/attributes.py +278 -0
- agentops/instrumentation/common/instrumentor.py +147 -0
- agentops/instrumentation/common/metrics.py +100 -0
- agentops/instrumentation/common/objects.py +26 -0
- agentops/instrumentation/common/span_management.py +176 -0
- agentops/instrumentation/common/streaming.py +218 -0
- agentops/instrumentation/common/token_counting.py +177 -0
- agentops/instrumentation/common/version.py +71 -0
- agentops/instrumentation/common/wrappers.py +235 -0
- agentops/legacy/__init__.py +277 -0
- agentops/legacy/event.py +156 -0
- agentops/logging/__init__.py +4 -0
- agentops/logging/config.py +86 -0
- agentops/logging/formatters.py +34 -0
- agentops/logging/instrument_logging.py +91 -0
- agentops/sdk/__init__.py +27 -0
- agentops/sdk/attributes.py +151 -0
- agentops/sdk/core.py +607 -0
- agentops/sdk/decorators/__init__.py +51 -0
- agentops/sdk/decorators/factory.py +486 -0
- agentops/sdk/decorators/utility.py +216 -0
- agentops/sdk/exporters.py +87 -0
- agentops/sdk/processors.py +71 -0
- agentops/sdk/types.py +21 -0
- agentops/semconv/__init__.py +36 -0
- agentops/semconv/agent.py +29 -0
- agentops/semconv/core.py +19 -0
- agentops/semconv/enum.py +11 -0
- agentops/semconv/instrumentation.py +13 -0
- agentops/semconv/langchain.py +63 -0
- agentops/semconv/message.py +61 -0
- agentops/semconv/meters.py +24 -0
- agentops/semconv/resource.py +52 -0
- agentops/semconv/span_attributes.py +118 -0
- agentops/semconv/span_kinds.py +50 -0
- agentops/semconv/status.py +11 -0
- agentops/semconv/tool.py +15 -0
- agentops/semconv/workflow.py +69 -0
- agentops/validation.py +357 -0
- mseep_agentops-0.4.18.dist-info/METADATA +49 -0
- mseep_agentops-0.4.18.dist-info/RECORD +94 -0
- mseep_agentops-0.4.18.dist-info/WHEEL +5 -0
- mseep_agentops-0.4.18.dist-info/licenses/LICENSE +21 -0
- mseep_agentops-0.4.18.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +10 -0
- tests/unit/__init__.py +0 -0
- tests/unit/client/__init__.py +1 -0
- tests/unit/client/test_http_adapter.py +221 -0
- tests/unit/client/test_http_client.py +206 -0
- tests/unit/conftest.py +54 -0
- tests/unit/sdk/__init__.py +1 -0
- tests/unit/sdk/instrumentation_tester.py +207 -0
- tests/unit/sdk/test_attributes.py +392 -0
- tests/unit/sdk/test_concurrent_instrumentation.py +468 -0
- tests/unit/sdk/test_decorators.py +763 -0
- tests/unit/sdk/test_exporters.py +241 -0
- tests/unit/sdk/test_factory.py +1188 -0
- tests/unit/sdk/test_internal_span_processor.py +397 -0
- tests/unit/sdk/test_resource_attributes.py +35 -0
- tests/unit/test_config.py +82 -0
- tests/unit/test_context_manager.py +777 -0
- tests/unit/test_events.py +27 -0
- tests/unit/test_host_env.py +54 -0
- tests/unit/test_init_py.py +501 -0
- tests/unit/test_serialization.py +433 -0
- tests/unit/test_session.py +676 -0
- tests/unit/test_user_agent.py +34 -0
- tests/unit/test_validation.py +405 -0
tests/unit/conftest.py
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
import uuid
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
import requests_mock
|
5
|
+
|
6
|
+
from agentops.config import Config
|
7
|
+
from tests.fixtures.client import * # noqa
|
8
|
+
from tests.unit.sdk.instrumentation_tester import InstrumentationTester
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.fixture
|
12
|
+
def api_key() -> str:
|
13
|
+
"""Standard API key for testing"""
|
14
|
+
return "test-api-key"
|
15
|
+
|
16
|
+
|
17
|
+
@pytest.fixture
|
18
|
+
def endpoint() -> str:
|
19
|
+
"""Base API URL"""
|
20
|
+
return Config().endpoint
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.fixture(autouse=True)
|
24
|
+
def mock_req(endpoint, api_key):
|
25
|
+
"""
|
26
|
+
Mocks AgentOps backend API requests.
|
27
|
+
"""
|
28
|
+
with requests_mock.Mocker(real_http=False) as m:
|
29
|
+
# Map session IDs to their JWTs
|
30
|
+
m.post(
|
31
|
+
endpoint + "/v3/auth/token",
|
32
|
+
json={"token": str(uuid.uuid4()), "project_id": "test-project-id", "api_key": api_key},
|
33
|
+
)
|
34
|
+
yield m
|
35
|
+
|
36
|
+
|
37
|
+
@pytest.fixture
|
38
|
+
def noinstrument():
|
39
|
+
# Tells the client to not instrument LLM calls
|
40
|
+
yield
|
41
|
+
|
42
|
+
|
43
|
+
@pytest.fixture
|
44
|
+
def mock_config(mocker):
|
45
|
+
"""Mock the Client.configure method"""
|
46
|
+
return mocker.patch("agentops.client.Client.configure")
|
47
|
+
|
48
|
+
|
49
|
+
@pytest.fixture
|
50
|
+
def instrumentation():
|
51
|
+
"""Fixture for the instrumentation tester."""
|
52
|
+
tester = InstrumentationTester()
|
53
|
+
yield tester
|
54
|
+
tester.reset()
|
@@ -0,0 +1 @@
|
|
1
|
+
# Test package for the SDK
|
@@ -0,0 +1,207 @@
|
|
1
|
+
from typing import Any, Dict, List, Protocol, Tuple, Union
|
2
|
+
import importlib
|
3
|
+
import unittest.mock as mock
|
4
|
+
|
5
|
+
from opentelemetry import trace as trace_api
|
6
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span, TracerProvider
|
7
|
+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
8
|
+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
9
|
+
from opentelemetry.util.types import Attributes
|
10
|
+
|
11
|
+
from agentops.sdk.core import tracer
|
12
|
+
|
13
|
+
|
14
|
+
def create_tracer_provider(
|
15
|
+
**kwargs,
|
16
|
+
) -> Tuple[TracerProvider, InMemorySpanExporter, SimpleSpanProcessor]:
|
17
|
+
"""Helper to create a configured tracer provider.
|
18
|
+
|
19
|
+
Creates and configures a `TracerProvider` with a
|
20
|
+
`SimpleSpanProcessor` and a `InMemorySpanExporter`.
|
21
|
+
All the parameters passed are forwarded to the TracerProvider
|
22
|
+
constructor.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A tuple with the tracer provider, memory exporter, and span processor.
|
26
|
+
"""
|
27
|
+
tracer_provider = TracerProvider(**kwargs)
|
28
|
+
memory_exporter = InMemorySpanExporter()
|
29
|
+
|
30
|
+
# Use SimpleSpanProcessor instead of both processors to avoid duplication
|
31
|
+
span_processor = SimpleSpanProcessor(memory_exporter)
|
32
|
+
tracer_provider.add_span_processor(span_processor)
|
33
|
+
|
34
|
+
return tracer_provider, memory_exporter, span_processor
|
35
|
+
|
36
|
+
|
37
|
+
def reset_trace_globals():
|
38
|
+
"""Reset the global trace state to avoid conflicts."""
|
39
|
+
# Reset tracer provider
|
40
|
+
trace_api._TRACER_PROVIDER = None
|
41
|
+
|
42
|
+
# Reload the trace module to clear warning state
|
43
|
+
importlib.reload(trace_api)
|
44
|
+
|
45
|
+
|
46
|
+
class HasAttributesViaProperty(Protocol):
|
47
|
+
@property
|
48
|
+
def attributes(self) -> Attributes:
|
49
|
+
...
|
50
|
+
|
51
|
+
|
52
|
+
class HasAttributesViaAttr(Protocol):
|
53
|
+
attributes: Attributes
|
54
|
+
|
55
|
+
|
56
|
+
HasAttributes = Union[HasAttributesViaProperty, HasAttributesViaAttr]
|
57
|
+
|
58
|
+
|
59
|
+
class InstrumentationTester:
|
60
|
+
"""
|
61
|
+
A utility class for testing instrumentation in the AgentOps SDK.
|
62
|
+
|
63
|
+
This class provides methods for setting up a test environment with
|
64
|
+
in-memory span exporters, and for asserting properties of spans.
|
65
|
+
"""
|
66
|
+
|
67
|
+
tracer_provider: TracerProvider
|
68
|
+
memory_exporter: InMemorySpanExporter
|
69
|
+
span_processor: SimpleSpanProcessor
|
70
|
+
|
71
|
+
def __init__(self):
|
72
|
+
"""Initialize the instrumentation tester."""
|
73
|
+
# Reset any global state first
|
74
|
+
reset_trace_globals()
|
75
|
+
|
76
|
+
# Shut down any existing tracing core
|
77
|
+
# self._shutdown_core()
|
78
|
+
|
79
|
+
# Create a new tracer provider and memory exporter
|
80
|
+
(
|
81
|
+
self.tracer_provider,
|
82
|
+
self.memory_exporter,
|
83
|
+
self.span_processor,
|
84
|
+
) = create_tracer_provider()
|
85
|
+
|
86
|
+
# Set the tracer provider
|
87
|
+
trace_api.set_tracer_provider(self.tracer_provider)
|
88
|
+
|
89
|
+
# Create a mock for the meter provider
|
90
|
+
self.mock_meter_provider = mock.MagicMock()
|
91
|
+
|
92
|
+
# Patch the setup_telemetry function to return our test providers
|
93
|
+
self.setup_telemetry_patcher = mock.patch(
|
94
|
+
"agentops.sdk.core.setup_telemetry", return_value=(self.tracer_provider, self.mock_meter_provider)
|
95
|
+
)
|
96
|
+
self.mock_setup_telemetry = self.setup_telemetry_patcher.start()
|
97
|
+
|
98
|
+
# Reset the tracing core to force reinitialization
|
99
|
+
core = tracer
|
100
|
+
core._initialized = False
|
101
|
+
core._provider = None
|
102
|
+
|
103
|
+
# Initialize the core, which will now use our mocked setup_telemetry
|
104
|
+
core.initialize()
|
105
|
+
|
106
|
+
self.clear_spans()
|
107
|
+
|
108
|
+
def _shutdown_core(self):
|
109
|
+
"""Safely shut down the tracing core."""
|
110
|
+
try:
|
111
|
+
tracer.shutdown()
|
112
|
+
except Exception as e:
|
113
|
+
print(f"Warning: Error shutting down tracing core: {e}")
|
114
|
+
|
115
|
+
def clear_spans(self):
|
116
|
+
"""Clear all spans from the memory exporter."""
|
117
|
+
# Force flush spans
|
118
|
+
self.span_processor.force_flush()
|
119
|
+
|
120
|
+
# Then clear the memory
|
121
|
+
self.memory_exporter.clear()
|
122
|
+
print("Cleared all spans from memory exporter")
|
123
|
+
|
124
|
+
def reset(self):
|
125
|
+
"""Reset the instrumentation tester."""
|
126
|
+
# Force flush any pending spans
|
127
|
+
self.span_processor.force_flush()
|
128
|
+
|
129
|
+
# Clear any existing spans
|
130
|
+
self.clear_spans()
|
131
|
+
|
132
|
+
# Reset global trace state
|
133
|
+
reset_trace_globals()
|
134
|
+
|
135
|
+
# Set our tracer provider again
|
136
|
+
trace_api.set_tracer_provider(self.tracer_provider)
|
137
|
+
|
138
|
+
# Shut down and re-initialize the tracing core
|
139
|
+
self._shutdown_core()
|
140
|
+
|
141
|
+
# Reset the mock setup_telemetry function
|
142
|
+
self.mock_setup_telemetry.reset_mock()
|
143
|
+
|
144
|
+
# Reset the tracing core to force reinitialization
|
145
|
+
core = tracer
|
146
|
+
core._initialized = False
|
147
|
+
core._provider = None
|
148
|
+
|
149
|
+
# Initialize the core, which will now use our mocked setup_telemetry
|
150
|
+
core.initialize()
|
151
|
+
|
152
|
+
def __del__(self):
|
153
|
+
"""Clean up resources when the tester is garbage collected."""
|
154
|
+
try:
|
155
|
+
# Stop the patcher when the tester is deleted
|
156
|
+
self.setup_telemetry_patcher.stop()
|
157
|
+
except Exception:
|
158
|
+
pass
|
159
|
+
|
160
|
+
def get_finished_spans(self) -> List[ReadableSpan]:
|
161
|
+
"""Get all finished spans."""
|
162
|
+
# Force flush any pending spans
|
163
|
+
self.span_processor.force_flush()
|
164
|
+
|
165
|
+
# Get the spans
|
166
|
+
spans = list(self.memory_exporter.get_finished_spans())
|
167
|
+
print(f"Instrumentation Tester: Found {len(spans)} finished spans")
|
168
|
+
for i, span in enumerate(spans):
|
169
|
+
print(f"Span {i}: name={span.name}, attributes={span.attributes}")
|
170
|
+
return spans
|
171
|
+
|
172
|
+
def get_spans_by_name(self, name: str) -> List[ReadableSpan]:
|
173
|
+
"""Get all spans with the given name."""
|
174
|
+
return [span for span in self.get_finished_spans() if span.name == name]
|
175
|
+
|
176
|
+
def get_spans_by_kind(self, kind: str) -> List[ReadableSpan]:
|
177
|
+
"""Get all spans with the given kind."""
|
178
|
+
return [
|
179
|
+
span for span in self.get_finished_spans() if span.attributes and span.attributes.get("span.kind") == kind
|
180
|
+
]
|
181
|
+
|
182
|
+
@staticmethod
|
183
|
+
def assert_has_attributes(obj: HasAttributes, attributes: Dict[str, Any]):
|
184
|
+
"""Assert that an object has the given attributes."""
|
185
|
+
import json
|
186
|
+
|
187
|
+
assert obj.attributes is not None
|
188
|
+
for key, val in attributes.items():
|
189
|
+
assert key in obj.attributes, f"Key {key!r} not found in attributes"
|
190
|
+
|
191
|
+
actual_val = obj.attributes[key]
|
192
|
+
|
193
|
+
# Try to handle JSON-serialized values
|
194
|
+
if isinstance(actual_val, str) and isinstance(val, (list, dict)):
|
195
|
+
try:
|
196
|
+
actual_val = json.loads(actual_val)
|
197
|
+
except json.JSONDecodeError:
|
198
|
+
pass
|
199
|
+
|
200
|
+
assert actual_val == val, f"Value for key {key!r} does not match: {actual_val} != {val}"
|
201
|
+
|
202
|
+
@staticmethod
|
203
|
+
def assert_span_instrumented_for(span: Union[Span, ReadableSpan], module):
|
204
|
+
"""Assert that a span is instrumented for the given module."""
|
205
|
+
assert span.instrumentation_scope is not None
|
206
|
+
assert span.instrumentation_scope.name == module.__name__
|
207
|
+
assert span.instrumentation_scope.version == module.__version__
|
@@ -0,0 +1,392 @@
|
|
1
|
+
"""
|
2
|
+
Tests for agentops.sdk.attributes module.
|
3
|
+
|
4
|
+
This module tests all attribute management functions for telemetry contexts.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import platform
|
8
|
+
from unittest.mock import Mock, patch
|
9
|
+
|
10
|
+
import pytest
|
11
|
+
|
12
|
+
from agentops.sdk.attributes import (
|
13
|
+
get_system_resource_attributes,
|
14
|
+
get_global_resource_attributes,
|
15
|
+
get_trace_attributes,
|
16
|
+
get_span_attributes,
|
17
|
+
get_session_end_attributes,
|
18
|
+
)
|
19
|
+
from agentops.semconv import ResourceAttributes, SpanAttributes, CoreAttributes
|
20
|
+
|
21
|
+
|
22
|
+
class TestGetSystemResourceAttributes:
|
23
|
+
"""Test get_system_resource_attributes function."""
|
24
|
+
|
25
|
+
def test_basic_system_attributes(self):
|
26
|
+
"""Test that basic system attributes are included."""
|
27
|
+
attributes = get_system_resource_attributes()
|
28
|
+
|
29
|
+
# Check that all basic platform attributes are present
|
30
|
+
assert ResourceAttributes.HOST_MACHINE in attributes
|
31
|
+
assert ResourceAttributes.HOST_NAME in attributes
|
32
|
+
assert ResourceAttributes.HOST_NODE in attributes
|
33
|
+
assert ResourceAttributes.HOST_PROCESSOR in attributes
|
34
|
+
assert ResourceAttributes.HOST_SYSTEM in attributes
|
35
|
+
assert ResourceAttributes.HOST_VERSION in attributes
|
36
|
+
assert ResourceAttributes.HOST_OS_RELEASE in attributes
|
37
|
+
|
38
|
+
# Check that values match platform module
|
39
|
+
assert attributes[ResourceAttributes.HOST_MACHINE] == platform.machine()
|
40
|
+
assert attributes[ResourceAttributes.HOST_NAME] == platform.node()
|
41
|
+
assert attributes[ResourceAttributes.HOST_NODE] == platform.node()
|
42
|
+
assert attributes[ResourceAttributes.HOST_PROCESSOR] == platform.processor()
|
43
|
+
assert attributes[ResourceAttributes.HOST_SYSTEM] == platform.system()
|
44
|
+
assert attributes[ResourceAttributes.HOST_VERSION] == platform.version()
|
45
|
+
assert attributes[ResourceAttributes.HOST_OS_RELEASE] == platform.release()
|
46
|
+
|
47
|
+
@patch("agentops.sdk.attributes.os.cpu_count")
|
48
|
+
@patch("agentops.sdk.attributes.psutil.cpu_percent")
|
49
|
+
def test_cpu_stats_success(self, mock_cpu_percent, mock_cpu_count):
|
50
|
+
"""Test CPU stats when successfully retrieved."""
|
51
|
+
mock_cpu_count.return_value = 8
|
52
|
+
mock_cpu_percent.return_value = 25.5
|
53
|
+
|
54
|
+
attributes = get_system_resource_attributes()
|
55
|
+
|
56
|
+
assert ResourceAttributes.CPU_COUNT in attributes
|
57
|
+
assert ResourceAttributes.CPU_PERCENT in attributes
|
58
|
+
assert attributes[ResourceAttributes.CPU_COUNT] == 8
|
59
|
+
assert attributes[ResourceAttributes.CPU_PERCENT] == 25.5
|
60
|
+
|
61
|
+
@patch("agentops.sdk.attributes.os.cpu_count")
|
62
|
+
@patch("agentops.sdk.attributes.psutil.cpu_percent")
|
63
|
+
def test_cpu_stats_cpu_count_none(self, mock_cpu_percent, mock_cpu_count):
|
64
|
+
"""Test CPU stats when cpu_count returns None."""
|
65
|
+
mock_cpu_count.return_value = None
|
66
|
+
mock_cpu_percent.return_value = 25.5
|
67
|
+
|
68
|
+
attributes = get_system_resource_attributes()
|
69
|
+
|
70
|
+
assert ResourceAttributes.CPU_COUNT in attributes
|
71
|
+
assert attributes[ResourceAttributes.CPU_COUNT] == 0
|
72
|
+
|
73
|
+
@patch("agentops.sdk.attributes.os.cpu_count")
|
74
|
+
@patch("agentops.sdk.attributes.psutil.cpu_percent")
|
75
|
+
def test_cpu_stats_exception(self, mock_cpu_percent, mock_cpu_count):
|
76
|
+
"""Test CPU stats when exception occurs."""
|
77
|
+
mock_cpu_count.side_effect = Exception("CPU count error")
|
78
|
+
mock_cpu_percent.side_effect = Exception("CPU percent error")
|
79
|
+
|
80
|
+
attributes = get_system_resource_attributes()
|
81
|
+
|
82
|
+
# Should not include CPU attributes when exception occurs
|
83
|
+
assert ResourceAttributes.CPU_COUNT not in attributes
|
84
|
+
assert ResourceAttributes.CPU_PERCENT not in attributes
|
85
|
+
|
86
|
+
@patch("agentops.sdk.attributes.psutil.virtual_memory")
|
87
|
+
def test_memory_stats_success(self, mock_virtual_memory):
|
88
|
+
"""Test memory stats when successfully retrieved."""
|
89
|
+
mock_memory = Mock()
|
90
|
+
mock_memory.total = 8589934592 # 8GB
|
91
|
+
mock_memory.available = 4294967296 # 4GB
|
92
|
+
mock_memory.used = 4294967296 # 4GB
|
93
|
+
mock_memory.percent = 50.0
|
94
|
+
mock_virtual_memory.return_value = mock_memory
|
95
|
+
|
96
|
+
attributes = get_system_resource_attributes()
|
97
|
+
|
98
|
+
assert ResourceAttributes.MEMORY_TOTAL in attributes
|
99
|
+
assert ResourceAttributes.MEMORY_AVAILABLE in attributes
|
100
|
+
assert ResourceAttributes.MEMORY_USED in attributes
|
101
|
+
assert ResourceAttributes.MEMORY_PERCENT in attributes
|
102
|
+
assert attributes[ResourceAttributes.MEMORY_TOTAL] == 8589934592
|
103
|
+
assert attributes[ResourceAttributes.MEMORY_AVAILABLE] == 4294967296
|
104
|
+
assert attributes[ResourceAttributes.MEMORY_USED] == 4294967296
|
105
|
+
assert attributes[ResourceAttributes.MEMORY_PERCENT] == 50.0
|
106
|
+
|
107
|
+
@patch("agentops.sdk.attributes.psutil.virtual_memory")
|
108
|
+
def test_memory_stats_exception(self, mock_virtual_memory):
|
109
|
+
"""Test memory stats when exception occurs."""
|
110
|
+
mock_virtual_memory.side_effect = Exception("Memory error")
|
111
|
+
|
112
|
+
attributes = get_system_resource_attributes()
|
113
|
+
|
114
|
+
# Should not include memory attributes when exception occurs
|
115
|
+
assert ResourceAttributes.MEMORY_TOTAL not in attributes
|
116
|
+
assert ResourceAttributes.MEMORY_AVAILABLE not in attributes
|
117
|
+
assert ResourceAttributes.MEMORY_USED not in attributes
|
118
|
+
assert ResourceAttributes.MEMORY_PERCENT not in attributes
|
119
|
+
|
120
|
+
|
121
|
+
class TestGetGlobalResourceAttributes:
|
122
|
+
"""Test get_global_resource_attributes function."""
|
123
|
+
|
124
|
+
@patch("agentops.sdk.attributes.get_imported_libraries")
|
125
|
+
def test_basic_attributes_with_project_id(self, mock_get_libs):
|
126
|
+
"""Test basic attributes with project ID."""
|
127
|
+
mock_get_libs.return_value = ["requests", "pandas"]
|
128
|
+
|
129
|
+
attributes = get_global_resource_attributes("test-service", project_id="test-project")
|
130
|
+
|
131
|
+
assert ResourceAttributes.SERVICE_NAME in attributes
|
132
|
+
assert ResourceAttributes.PROJECT_ID in attributes
|
133
|
+
assert ResourceAttributes.IMPORTED_LIBRARIES in attributes
|
134
|
+
assert attributes[ResourceAttributes.SERVICE_NAME] == "test-service"
|
135
|
+
assert attributes[ResourceAttributes.PROJECT_ID] == "test-project"
|
136
|
+
assert attributes[ResourceAttributes.IMPORTED_LIBRARIES] == ["requests", "pandas"]
|
137
|
+
|
138
|
+
@patch("agentops.sdk.attributes.get_imported_libraries")
|
139
|
+
def test_basic_attributes_without_project_id(self, mock_get_libs):
|
140
|
+
"""Test basic attributes without project ID."""
|
141
|
+
mock_get_libs.return_value = ["requests", "pandas"]
|
142
|
+
|
143
|
+
attributes = get_global_resource_attributes("test-service")
|
144
|
+
|
145
|
+
assert ResourceAttributes.SERVICE_NAME in attributes
|
146
|
+
assert ResourceAttributes.PROJECT_ID not in attributes
|
147
|
+
assert ResourceAttributes.IMPORTED_LIBRARIES in attributes
|
148
|
+
assert attributes[ResourceAttributes.SERVICE_NAME] == "test-service"
|
149
|
+
assert attributes[ResourceAttributes.IMPORTED_LIBRARIES] == ["requests", "pandas"]
|
150
|
+
|
151
|
+
@patch("agentops.sdk.attributes.get_imported_libraries")
|
152
|
+
def test_no_imported_libraries(self, mock_get_libs):
|
153
|
+
"""Test when no imported libraries are found."""
|
154
|
+
mock_get_libs.return_value = None
|
155
|
+
|
156
|
+
attributes = get_global_resource_attributes("test-service", project_id="test-project")
|
157
|
+
|
158
|
+
assert ResourceAttributes.SERVICE_NAME in attributes
|
159
|
+
assert ResourceAttributes.PROJECT_ID in attributes
|
160
|
+
assert ResourceAttributes.IMPORTED_LIBRARIES not in attributes
|
161
|
+
assert attributes[ResourceAttributes.SERVICE_NAME] == "test-service"
|
162
|
+
assert attributes[ResourceAttributes.PROJECT_ID] == "test-project"
|
163
|
+
|
164
|
+
@patch("agentops.sdk.attributes.get_imported_libraries")
|
165
|
+
def test_empty_imported_libraries(self, mock_get_libs):
|
166
|
+
"""Test when imported libraries list is empty."""
|
167
|
+
mock_get_libs.return_value = []
|
168
|
+
|
169
|
+
attributes = get_global_resource_attributes("test-service", project_id="test-project")
|
170
|
+
|
171
|
+
assert ResourceAttributes.SERVICE_NAME in attributes
|
172
|
+
assert ResourceAttributes.PROJECT_ID in attributes
|
173
|
+
assert ResourceAttributes.IMPORTED_LIBRARIES not in attributes
|
174
|
+
assert attributes[ResourceAttributes.SERVICE_NAME] == "test-service"
|
175
|
+
assert attributes[ResourceAttributes.PROJECT_ID] == "test-project"
|
176
|
+
|
177
|
+
|
178
|
+
class TestGetTraceAttributes:
|
179
|
+
"""Test get_trace_attributes function."""
|
180
|
+
|
181
|
+
def test_no_tags(self):
|
182
|
+
"""Test when no tags are provided."""
|
183
|
+
attributes = get_trace_attributes()
|
184
|
+
|
185
|
+
assert attributes == {}
|
186
|
+
|
187
|
+
def test_list_tags(self):
|
188
|
+
"""Test with list of tags."""
|
189
|
+
tags = ["tag1", "tag2", "tag3"]
|
190
|
+
attributes = get_trace_attributes(tags)
|
191
|
+
|
192
|
+
assert CoreAttributes.TAGS in attributes
|
193
|
+
assert attributes[CoreAttributes.TAGS] == ["tag1", "tag2", "tag3"]
|
194
|
+
|
195
|
+
def test_dict_tags(self):
|
196
|
+
"""Test with dictionary of tags."""
|
197
|
+
tags = {"key1": "value1", "key2": "value2"}
|
198
|
+
attributes = get_trace_attributes(tags)
|
199
|
+
|
200
|
+
assert "key1" in attributes
|
201
|
+
assert "key2" in attributes
|
202
|
+
assert attributes["key1"] == "value1"
|
203
|
+
assert attributes["key2"] == "value2"
|
204
|
+
|
205
|
+
def test_mixed_dict_tags(self):
|
206
|
+
"""Test with dictionary containing various value types."""
|
207
|
+
tags = {
|
208
|
+
"string_key": "string_value",
|
209
|
+
"int_key": 42,
|
210
|
+
"float_key": 3.14,
|
211
|
+
"bool_key": True,
|
212
|
+
"list_key": [1, 2, 3],
|
213
|
+
}
|
214
|
+
attributes = get_trace_attributes(tags)
|
215
|
+
|
216
|
+
assert attributes["string_key"] == "string_value"
|
217
|
+
assert attributes["int_key"] == 42
|
218
|
+
assert attributes["float_key"] == 3.14
|
219
|
+
assert attributes["bool_key"] is True
|
220
|
+
assert attributes["list_key"] == [1, 2, 3]
|
221
|
+
|
222
|
+
def test_invalid_tags_type(self):
|
223
|
+
"""Test with invalid tags type."""
|
224
|
+
with patch("agentops.sdk.attributes.logger") as mock_logger:
|
225
|
+
attributes = get_trace_attributes("invalid_tags")
|
226
|
+
|
227
|
+
assert attributes == {}
|
228
|
+
mock_logger.warning.assert_called_once()
|
229
|
+
|
230
|
+
def test_none_tags(self):
|
231
|
+
"""Test with None tags."""
|
232
|
+
attributes = get_trace_attributes(None)
|
233
|
+
|
234
|
+
assert attributes == {}
|
235
|
+
|
236
|
+
|
237
|
+
class TestGetSpanAttributes:
|
238
|
+
"""Test get_span_attributes function."""
|
239
|
+
|
240
|
+
def test_basic_span_attributes(self):
|
241
|
+
"""Test basic span attributes."""
|
242
|
+
attributes = get_span_attributes("test-operation", "test-kind")
|
243
|
+
|
244
|
+
assert SpanAttributes.AGENTOPS_SPAN_KIND in attributes
|
245
|
+
assert SpanAttributes.OPERATION_NAME in attributes
|
246
|
+
assert attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == "test-kind"
|
247
|
+
assert attributes[SpanAttributes.OPERATION_NAME] == "test-operation"
|
248
|
+
assert SpanAttributes.OPERATION_VERSION not in attributes
|
249
|
+
|
250
|
+
def test_span_attributes_with_version(self):
|
251
|
+
"""Test span attributes with version."""
|
252
|
+
attributes = get_span_attributes("test-operation", "test-kind", version=1)
|
253
|
+
|
254
|
+
assert SpanAttributes.AGENTOPS_SPAN_KIND in attributes
|
255
|
+
assert SpanAttributes.OPERATION_NAME in attributes
|
256
|
+
assert SpanAttributes.OPERATION_VERSION in attributes
|
257
|
+
assert attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == "test-kind"
|
258
|
+
assert attributes[SpanAttributes.OPERATION_NAME] == "test-operation"
|
259
|
+
assert attributes[SpanAttributes.OPERATION_VERSION] == 1
|
260
|
+
|
261
|
+
def test_span_attributes_with_version_zero(self):
|
262
|
+
"""Test span attributes with version zero."""
|
263
|
+
attributes = get_span_attributes("test-operation", "test-kind", version=0)
|
264
|
+
|
265
|
+
assert SpanAttributes.OPERATION_VERSION in attributes
|
266
|
+
assert attributes[SpanAttributes.OPERATION_VERSION] == 0
|
267
|
+
|
268
|
+
def test_span_attributes_with_additional_kwargs(self):
|
269
|
+
"""Test span attributes with additional keyword arguments."""
|
270
|
+
attributes = get_span_attributes(
|
271
|
+
"test-operation",
|
272
|
+
"test-kind",
|
273
|
+
version=1,
|
274
|
+
custom_key="custom_value",
|
275
|
+
another_key=42,
|
276
|
+
)
|
277
|
+
|
278
|
+
assert SpanAttributes.AGENTOPS_SPAN_KIND in attributes
|
279
|
+
assert SpanAttributes.OPERATION_NAME in attributes
|
280
|
+
assert SpanAttributes.OPERATION_VERSION in attributes
|
281
|
+
assert "custom_key" in attributes
|
282
|
+
assert "another_key" in attributes
|
283
|
+
assert attributes["custom_key"] == "custom_value"
|
284
|
+
assert attributes["another_key"] == 42
|
285
|
+
|
286
|
+
def test_span_attributes_overwrite_kwargs(self):
|
287
|
+
"""Test that kwargs can overwrite default attributes."""
|
288
|
+
attributes = get_span_attributes(
|
289
|
+
"test-operation",
|
290
|
+
"test-kind",
|
291
|
+
version=1,
|
292
|
+
custom_operation_name="overwritten-name",
|
293
|
+
custom_span_kind="overwritten-kind",
|
294
|
+
)
|
295
|
+
|
296
|
+
# kwargs should overwrite the default values
|
297
|
+
assert attributes["custom_operation_name"] == "overwritten-name"
|
298
|
+
assert attributes["custom_span_kind"] == "overwritten-kind"
|
299
|
+
# The original positional arguments should still be set
|
300
|
+
assert attributes[SpanAttributes.OPERATION_NAME] == "test-operation"
|
301
|
+
assert attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == "test-kind"
|
302
|
+
|
303
|
+
|
304
|
+
class TestGetSessionEndAttributes:
|
305
|
+
"""Test get_session_end_attributes function."""
|
306
|
+
|
307
|
+
def test_session_end_attributes_success(self):
|
308
|
+
"""Test session end attributes with success state."""
|
309
|
+
attributes = get_session_end_attributes("Success")
|
310
|
+
|
311
|
+
assert SpanAttributes.AGENTOPS_SESSION_END_STATE in attributes
|
312
|
+
assert attributes[SpanAttributes.AGENTOPS_SESSION_END_STATE] == "Success"
|
313
|
+
|
314
|
+
def test_session_end_attributes_failure(self):
|
315
|
+
"""Test session end attributes with failure state."""
|
316
|
+
attributes = get_session_end_attributes("Failure")
|
317
|
+
|
318
|
+
assert SpanAttributes.AGENTOPS_SESSION_END_STATE in attributes
|
319
|
+
assert attributes[SpanAttributes.AGENTOPS_SESSION_END_STATE] == "Failure"
|
320
|
+
|
321
|
+
def test_session_end_attributes_custom_state(self):
|
322
|
+
"""Test session end attributes with custom state."""
|
323
|
+
attributes = get_session_end_attributes("CustomState")
|
324
|
+
|
325
|
+
assert SpanAttributes.AGENTOPS_SESSION_END_STATE in attributes
|
326
|
+
assert attributes[SpanAttributes.AGENTOPS_SESSION_END_STATE] == "CustomState"
|
327
|
+
|
328
|
+
def test_session_end_attributes_empty_string(self):
|
329
|
+
"""Test session end attributes with empty string."""
|
330
|
+
attributes = get_session_end_attributes("")
|
331
|
+
|
332
|
+
assert SpanAttributes.AGENTOPS_SESSION_END_STATE in attributes
|
333
|
+
assert attributes[SpanAttributes.AGENTOPS_SESSION_END_STATE] == ""
|
334
|
+
|
335
|
+
|
336
|
+
class TestAttributesIntegration:
|
337
|
+
"""Integration tests for attributes module."""
|
338
|
+
|
339
|
+
def test_all_functions_work_together(self):
|
340
|
+
"""Test that all attribute functions work together without conflicts."""
|
341
|
+
# Get system attributes
|
342
|
+
system_attrs = get_system_resource_attributes()
|
343
|
+
assert isinstance(system_attrs, dict)
|
344
|
+
|
345
|
+
# Get global attributes
|
346
|
+
global_attrs = get_global_resource_attributes("test-service", project_id="test-project")
|
347
|
+
assert isinstance(global_attrs, dict)
|
348
|
+
|
349
|
+
# Get trace attributes
|
350
|
+
trace_attrs = get_trace_attributes(["tag1", "tag2"])
|
351
|
+
assert isinstance(trace_attrs, dict)
|
352
|
+
|
353
|
+
# Get span attributes
|
354
|
+
span_attrs = get_span_attributes("test-operation", "test-kind", version=1)
|
355
|
+
assert isinstance(span_attrs, dict)
|
356
|
+
|
357
|
+
# Get session end attributes
|
358
|
+
session_attrs = get_session_end_attributes("Success")
|
359
|
+
assert isinstance(session_attrs, dict)
|
360
|
+
|
361
|
+
# Verify no key conflicts between different attribute types
|
362
|
+
all_keys = (
|
363
|
+
set(system_attrs.keys())
|
364
|
+
| set(global_attrs.keys())
|
365
|
+
| set(trace_attrs.keys())
|
366
|
+
| set(span_attrs.keys())
|
367
|
+
| set(session_attrs.keys())
|
368
|
+
)
|
369
|
+
assert len(all_keys) == len(system_attrs) + len(global_attrs) + len(trace_attrs) + len(span_attrs) + len(
|
370
|
+
session_attrs
|
371
|
+
)
|
372
|
+
|
373
|
+
def test_attribute_types_consistency(self):
|
374
|
+
"""Test that all attributes return consistent types."""
|
375
|
+
# All functions should return dictionaries
|
376
|
+
assert isinstance(get_system_resource_attributes(), dict)
|
377
|
+
assert isinstance(get_global_resource_attributes("test"), dict)
|
378
|
+
assert isinstance(get_trace_attributes(), dict)
|
379
|
+
assert isinstance(get_span_attributes("test", "test"), dict)
|
380
|
+
assert isinstance(get_session_end_attributes("test"), dict)
|
381
|
+
|
382
|
+
# All dictionary values should be serializable
|
383
|
+
import json
|
384
|
+
|
385
|
+
try:
|
386
|
+
json.dumps(get_system_resource_attributes())
|
387
|
+
json.dumps(get_global_resource_attributes("test"))
|
388
|
+
json.dumps(get_trace_attributes())
|
389
|
+
json.dumps(get_span_attributes("test", "test"))
|
390
|
+
json.dumps(get_session_end_attributes("test"))
|
391
|
+
except (TypeError, ValueError) as e:
|
392
|
+
pytest.fail(f"Attributes are not JSON serializable: {e}")
|