mseep-agentops 0.4.18__py3-none-any.whl → 0.4.23__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.
Files changed (153) hide show
  1. agentops/__init__.py +0 -0
  2. agentops/client/api/base.py +28 -30
  3. agentops/client/api/versions/v3.py +29 -25
  4. agentops/client/api/versions/v4.py +87 -46
  5. agentops/client/client.py +98 -29
  6. agentops/client/http/README.md +87 -0
  7. agentops/client/http/http_client.py +126 -172
  8. agentops/config.py +8 -2
  9. agentops/instrumentation/OpenTelemetry.md +133 -0
  10. agentops/instrumentation/README.md +167 -0
  11. agentops/instrumentation/__init__.py +13 -1
  12. agentops/instrumentation/agentic/ag2/__init__.py +18 -0
  13. agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
  14. agentops/instrumentation/agentic/agno/__init__.py +19 -0
  15. agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
  16. agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
  17. agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
  18. agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
  19. agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
  20. agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
  21. agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
  22. agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
  23. agentops/instrumentation/agentic/crewai/LICENSE +201 -0
  24. agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
  25. agentops/instrumentation/agentic/crewai/__init__.py +6 -0
  26. agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
  27. agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
  28. agentops/instrumentation/agentic/crewai/version.py +1 -0
  29. agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
  30. agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
  31. agentops/instrumentation/agentic/google_adk/patch.py +767 -0
  32. agentops/instrumentation/agentic/haystack/__init__.py +1 -0
  33. agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
  34. agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
  35. agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
  36. agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
  37. agentops/instrumentation/agentic/langgraph/version.py +1 -0
  38. agentops/instrumentation/agentic/openai_agents/README.md +156 -0
  39. agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
  40. agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
  41. agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
  42. agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
  43. agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
  44. agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
  45. agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
  46. agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
  47. agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
  48. agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
  49. agentops/instrumentation/agentic/smolagents/README.md +88 -0
  50. agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
  51. agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
  52. agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
  53. agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
  54. agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
  55. agentops/instrumentation/agentic/xpander/__init__.py +15 -0
  56. agentops/instrumentation/agentic/xpander/context.py +112 -0
  57. agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
  58. agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
  59. agentops/instrumentation/agentic/xpander/version.py +3 -0
  60. agentops/instrumentation/common/README.md +65 -0
  61. agentops/instrumentation/common/attributes.py +1 -2
  62. agentops/instrumentation/providers/anthropic/__init__.py +24 -0
  63. agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
  64. agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
  65. agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
  66. agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
  67. agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
  68. agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
  69. agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
  70. agentops/instrumentation/providers/google_genai/README.md +33 -0
  71. agentops/instrumentation/providers/google_genai/__init__.py +24 -0
  72. agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
  73. agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
  74. agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
  75. agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
  76. agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
  77. agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
  78. agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
  79. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
  80. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
  81. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
  82. agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
  83. agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
  84. agentops/instrumentation/providers/mem0/__init__.py +45 -0
  85. agentops/instrumentation/providers/mem0/common.py +377 -0
  86. agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
  87. agentops/instrumentation/providers/mem0/memory.py +430 -0
  88. agentops/instrumentation/providers/openai/__init__.py +21 -0
  89. agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
  90. agentops/instrumentation/providers/openai/attributes/common.py +55 -0
  91. agentops/instrumentation/providers/openai/attributes/response.py +607 -0
  92. agentops/instrumentation/providers/openai/config.py +36 -0
  93. agentops/instrumentation/providers/openai/instrumentor.py +312 -0
  94. agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
  95. agentops/instrumentation/providers/openai/utils.py +44 -0
  96. agentops/instrumentation/providers/openai/v0.py +176 -0
  97. agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
  98. agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
  99. agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
  100. agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
  101. agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
  102. agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
  103. agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
  104. agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
  105. agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
  106. agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
  107. agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
  108. agentops/integration/callbacks/dspy/__init__.py +11 -0
  109. agentops/integration/callbacks/dspy/callback.py +471 -0
  110. agentops/integration/callbacks/langchain/README.md +59 -0
  111. agentops/integration/callbacks/langchain/__init__.py +15 -0
  112. agentops/integration/callbacks/langchain/callback.py +791 -0
  113. agentops/integration/callbacks/langchain/utils.py +54 -0
  114. agentops/legacy/crewai.md +121 -0
  115. agentops/logging/instrument_logging.py +4 -0
  116. agentops/sdk/README.md +220 -0
  117. agentops/sdk/core.py +75 -32
  118. agentops/sdk/descriptors/classproperty.py +28 -0
  119. agentops/sdk/exporters.py +152 -33
  120. agentops/semconv/README.md +125 -0
  121. agentops/semconv/span_kinds.py +0 -2
  122. agentops/validation.py +102 -63
  123. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/METADATA +30 -40
  124. mseep_agentops-0.4.23.dist-info/RECORD +178 -0
  125. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/WHEEL +1 -2
  126. mseep_agentops-0.4.18.dist-info/RECORD +0 -94
  127. mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
  128. tests/conftest.py +0 -10
  129. tests/unit/client/__init__.py +0 -1
  130. tests/unit/client/test_http_adapter.py +0 -221
  131. tests/unit/client/test_http_client.py +0 -206
  132. tests/unit/conftest.py +0 -54
  133. tests/unit/sdk/__init__.py +0 -1
  134. tests/unit/sdk/instrumentation_tester.py +0 -207
  135. tests/unit/sdk/test_attributes.py +0 -392
  136. tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
  137. tests/unit/sdk/test_decorators.py +0 -763
  138. tests/unit/sdk/test_exporters.py +0 -241
  139. tests/unit/sdk/test_factory.py +0 -1188
  140. tests/unit/sdk/test_internal_span_processor.py +0 -397
  141. tests/unit/sdk/test_resource_attributes.py +0 -35
  142. tests/unit/test_config.py +0 -82
  143. tests/unit/test_context_manager.py +0 -777
  144. tests/unit/test_events.py +0 -27
  145. tests/unit/test_host_env.py +0 -54
  146. tests/unit/test_init_py.py +0 -501
  147. tests/unit/test_serialization.py +0 -433
  148. tests/unit/test_session.py +0 -676
  149. tests/unit/test_user_agent.py +0 -34
  150. tests/unit/test_validation.py +0 -405
  151. {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
  152. /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
  153. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/licenses/LICENSE +0 -0
@@ -1,1188 +0,0 @@
1
- import asyncio
2
- import inspect
3
- import pytest
4
-
5
- from agentops.sdk.decorators.factory import create_entity_decorator
6
- from agentops.semconv import SpanKind
7
- from agentops.semconv.span_attributes import SpanAttributes
8
- from tests.unit.sdk.instrumentation_tester import InstrumentationTester
9
- from agentops.sdk.core import tracer
10
-
11
-
12
- class TestFactoryModule:
13
- """Comprehensive tests for the factory.py module functionality."""
14
-
15
- def test_create_entity_decorator_factory_function(self):
16
- """Test that create_entity_decorator returns a callable decorator."""
17
- decorator = create_entity_decorator("test_kind")
18
- assert callable(decorator)
19
-
20
- # Test that it can be used as a decorator
21
- @decorator
22
- def test_function():
23
- return "test"
24
-
25
- assert test_function() == "test"
26
-
27
- def test_decorator_with_parameters(self):
28
- """Test decorator with explicit parameters."""
29
- decorator = create_entity_decorator("test_kind")
30
-
31
- @decorator(name="custom_name", version="1.0", tags=["tag1", "tag2"])
32
- def test_function():
33
- return "test"
34
-
35
- assert test_function() == "test"
36
-
37
- def test_decorator_partial_application(self):
38
- """Test that decorator can be partially applied."""
39
- decorator = create_entity_decorator("test_kind")
40
- partial_decorator = decorator(name="partial_name", version="2.0")
41
-
42
- @partial_decorator
43
- def test_function():
44
- return "test"
45
-
46
- assert test_function() == "test"
47
-
48
- def test_class_decoration_basic(self, instrumentation: InstrumentationTester):
49
- """Test basic class decoration functionality."""
50
- decorator = create_entity_decorator("test_kind")
51
-
52
- @decorator
53
- class TestClass:
54
- def __init__(self, value=42):
55
- self.value = value
56
-
57
- # Test instantiation
58
- instance = TestClass(100)
59
- assert instance.value == 100
60
-
61
- # Note: The current factory implementation has a bug where class decoration
62
- # creates spans but doesn't properly end them, so no spans are recorded
63
- spans = instrumentation.get_finished_spans()
64
- assert len(spans) == 0
65
-
66
- def test_class_decoration_with_parameters(self, instrumentation: InstrumentationTester):
67
- """Test class decoration with explicit parameters."""
68
- decorator = create_entity_decorator("test_kind")
69
-
70
- @decorator(name="CustomClass", version="1.0", tags={"env": "test"})
71
- class TestClass:
72
- def __init__(self, value=42):
73
- self.value = value
74
-
75
- instance = TestClass(100)
76
- assert instance.value == 100
77
-
78
- # Note: The current factory implementation has a bug where class decoration
79
- # creates spans but doesn't properly end them, so no spans are recorded
80
- spans = instrumentation.get_finished_spans()
81
- assert len(spans) == 0
82
-
83
- def test_class_metadata_preservation(self):
84
- """Test that class metadata is preserved after decoration."""
85
- decorator = create_entity_decorator("test_kind")
86
-
87
- @decorator
88
- class TestClass:
89
- """Test class docstring."""
90
-
91
- def __init__(self):
92
- pass
93
-
94
- assert TestClass.__name__ == "TestClass"
95
- # The qualname will include the test function context, which is expected
96
- assert "TestClass" in TestClass.__qualname__
97
- assert TestClass.__module__ == TestClass.__module__ # Should be preserved
98
- assert TestClass.__doc__ == "Test class docstring."
99
-
100
- def test_async_context_manager_normal_flow(self, instrumentation: InstrumentationTester):
101
- """Test async context manager with normal flow."""
102
- decorator = create_entity_decorator("test_kind")
103
-
104
- @decorator
105
- class TestClass:
106
- def __init__(self):
107
- self.value = 42
108
-
109
- async def test_async_context():
110
- async with TestClass() as instance:
111
- assert instance.value == 42
112
- assert hasattr(instance, "_agentops_active_span")
113
- assert instance._agentops_active_span is not None
114
- return "success"
115
-
116
- result = asyncio.run(test_async_context())
117
- assert result == "success"
118
-
119
- spans = instrumentation.get_finished_spans()
120
- assert len(spans) == 1
121
-
122
- def test_async_context_manager_exception_flow(self, instrumentation: InstrumentationTester):
123
- """Test async context manager with exception flow."""
124
- decorator = create_entity_decorator("test_kind")
125
-
126
- @decorator
127
- class TestClass:
128
- def __init__(self):
129
- self.value = 42
130
-
131
- async def test_async_context_with_exception():
132
- try:
133
- async with TestClass() as instance:
134
- assert instance.value == 42
135
- raise ValueError("Test exception")
136
- except ValueError:
137
- return "exception_handled"
138
-
139
- result = asyncio.run(test_async_context_with_exception())
140
- assert result == "exception_handled"
141
-
142
- spans = instrumentation.get_finished_spans()
143
- assert len(spans) == 1
144
-
145
- def test_async_context_manager_reuse(self, instrumentation: InstrumentationTester):
146
- """Test that async context manager can be reused."""
147
- decorator = create_entity_decorator("test_kind")
148
-
149
- @decorator
150
- class TestClass:
151
- def __init__(self):
152
- self.value = 42
153
-
154
- async def test_reuse():
155
- instance = TestClass()
156
-
157
- # First use
158
- async with instance as inst1:
159
- assert inst1.value == 42
160
-
161
- # Second use - should work with existing span
162
- async with instance as inst2:
163
- assert inst2.value == 42
164
- assert inst2 is instance
165
-
166
- asyncio.run(test_reuse())
167
-
168
- spans = instrumentation.get_finished_spans()
169
- # The current implementation creates a span for __init__ and another for the async context
170
- assert len(spans) == 2
171
-
172
- def test_sync_function_decoration(self, instrumentation: InstrumentationTester):
173
- """Test synchronous function decoration."""
174
- decorator = create_entity_decorator("test_kind")
175
-
176
- @decorator
177
- def test_function(x, y=10):
178
- return x + y
179
-
180
- result = test_function(5, y=15)
181
- assert result == 20
182
-
183
- spans = instrumentation.get_finished_spans()
184
- assert len(spans) == 1
185
- span = spans[0]
186
- assert span.name == "test_function.test_kind"
187
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
188
-
189
- def test_async_function_decoration(self, instrumentation: InstrumentationTester):
190
- """Test asynchronous function decoration."""
191
- decorator = create_entity_decorator("test_kind")
192
-
193
- @decorator
194
- async def test_async_function(x, y=10):
195
- await asyncio.sleep(0.01)
196
- return x + y
197
-
198
- result = asyncio.run(test_async_function(5, y=15))
199
- assert result == 20
200
-
201
- spans = instrumentation.get_finished_spans()
202
- assert len(spans) == 1
203
- span = spans[0]
204
- assert span.name == "test_async_function.test_kind"
205
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
206
-
207
- def test_generator_function_decoration(self, instrumentation: InstrumentationTester):
208
- """Test generator function decoration."""
209
- decorator = create_entity_decorator("test_kind")
210
-
211
- @decorator
212
- def test_generator(count):
213
- for i in range(count):
214
- yield f"item_{i}"
215
-
216
- results = list(test_generator(3))
217
- assert results == ["item_0", "item_1", "item_2"]
218
-
219
- spans = instrumentation.get_finished_spans()
220
- assert len(spans) == 1
221
- span = spans[0]
222
- assert span.name == "test_generator.test_kind"
223
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
224
-
225
- def test_async_generator_function_decoration(self, instrumentation: InstrumentationTester):
226
- """Test async generator function decoration."""
227
- decorator = create_entity_decorator("test_kind")
228
-
229
- @decorator
230
- async def test_async_generator(count):
231
- for i in range(count):
232
- await asyncio.sleep(0.01)
233
- yield f"async_item_{i}"
234
-
235
- async def collect_results():
236
- results = []
237
- async for item in test_async_generator(3):
238
- results.append(item)
239
- return results
240
-
241
- results = asyncio.run(collect_results())
242
- assert results == ["async_item_0", "async_item_1", "async_item_2"]
243
-
244
- spans = instrumentation.get_finished_spans()
245
- assert len(spans) == 1
246
- span = spans[0]
247
- assert span.name == "test_async_generator.test_kind"
248
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
249
-
250
- def test_session_entity_kind_sync(self, instrumentation: InstrumentationTester):
251
- """Test SESSION entity kind with sync function."""
252
- decorator = create_entity_decorator("session")
253
-
254
- @decorator
255
- def test_session_function():
256
- return "session_result"
257
-
258
- result = test_session_function()
259
- assert result == "session_result"
260
-
261
- spans = instrumentation.get_finished_spans()
262
- assert len(spans) == 1
263
- span = spans[0]
264
- assert span.name == "test_session_function.session"
265
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "session"
266
-
267
- def test_session_entity_kind_async(self, instrumentation: InstrumentationTester):
268
- """Test SESSION entity kind with async function."""
269
- decorator = create_entity_decorator("session")
270
-
271
- @decorator
272
- async def test_session_async_function():
273
- await asyncio.sleep(0.01)
274
- return "session_async_result"
275
-
276
- result = asyncio.run(test_session_async_function())
277
- assert result == "session_async_result"
278
-
279
- spans = instrumentation.get_finished_spans()
280
- assert len(spans) == 1
281
- span = spans[0]
282
- assert span.name == "test_session_async_function.session"
283
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "session"
284
-
285
- def test_session_entity_kind_generator_warning(self, caplog, instrumentation: InstrumentationTester):
286
- """Test that SESSION entity kind logs warning for generators."""
287
- # TODO: This test should assert that a warning is logged, but logger capture is complex due to custom logger setup.
288
- # For now, we only assert the correct span behavior.
289
- decorator = create_entity_decorator("session")
290
-
291
- @decorator
292
- def test_session_generator():
293
- yield "session_generator_item"
294
-
295
- # The decorator should return a generator, not None
296
- # For session decorators, the generator logic falls through to the regular generator handling
297
- generator = test_session_generator()
298
- results = list(generator)
299
- assert results == ["session_generator_item"]
300
-
301
- spans = instrumentation.get_finished_spans()
302
- assert len(spans) == 1
303
-
304
- def test_tool_entity_kind_with_cost(self, instrumentation: InstrumentationTester):
305
- """Test tool entity kind with cost parameter."""
306
- decorator = create_entity_decorator("tool")
307
-
308
- @decorator(cost=0.05)
309
- def test_tool_function():
310
- return "tool_result"
311
-
312
- result = test_tool_function()
313
- assert result == "tool_result"
314
-
315
- spans = instrumentation.get_finished_spans()
316
- assert len(spans) == 1
317
- span = spans[0]
318
- assert span.name == "test_tool_function.tool"
319
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "tool"
320
- assert span.attributes.get("gen_ai.usage.total_cost") == 0.05
321
-
322
- def test_guardrail_entity_kind_with_spec(self, instrumentation: InstrumentationTester):
323
- """Test guardrail entity kind with spec parameter."""
324
- decorator = create_entity_decorator("guardrail")
325
-
326
- @decorator(spec="input")
327
- def test_guardrail_function():
328
- return "guardrail_result"
329
-
330
- result = test_guardrail_function()
331
- assert result == "guardrail_result"
332
-
333
- spans = instrumentation.get_finished_spans()
334
- assert len(spans) == 1
335
- span = spans[0]
336
- assert span.name == "test_guardrail_function.guardrail"
337
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "guardrail"
338
- assert span.attributes.get("agentops.guardrail.spec") == "input"
339
-
340
- def test_guardrail_entity_kind_with_output_spec(self, instrumentation: InstrumentationTester):
341
- """Test guardrail entity kind with output spec parameter."""
342
- decorator = create_entity_decorator("guardrail")
343
-
344
- @decorator(spec="output")
345
- def test_guardrail_output_function():
346
- return "guardrail_output_result"
347
-
348
- result = test_guardrail_output_function()
349
- assert result == "guardrail_output_result"
350
-
351
- spans = instrumentation.get_finished_spans()
352
- assert len(spans) == 1
353
- span = spans[0]
354
- assert span.name == "test_guardrail_output_function.guardrail"
355
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "guardrail"
356
- assert span.attributes.get("agentops.guardrail.spec") == "output"
357
-
358
- def test_guardrail_entity_kind_with_invalid_spec(self, instrumentation: InstrumentationTester):
359
- """Test guardrail entity kind with invalid spec parameter."""
360
- decorator = create_entity_decorator("guardrail")
361
-
362
- @decorator(spec="invalid")
363
- def test_guardrail_invalid_function():
364
- return "guardrail_invalid_result"
365
-
366
- result = test_guardrail_invalid_function()
367
- assert result == "guardrail_invalid_result"
368
-
369
- spans = instrumentation.get_finished_spans()
370
- assert len(spans) == 1
371
- span = spans[0]
372
- assert span.name == "test_guardrail_invalid_function.guardrail"
373
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "guardrail"
374
- # Should not have the spec attribute for invalid spec
375
- assert "agentops.decorator.guardrail.spec" not in span.attributes
376
-
377
- def test_tags_parameter_list(self, instrumentation: InstrumentationTester):
378
- """Test tags parameter with list."""
379
- decorator = create_entity_decorator("test_kind")
380
-
381
- @decorator(tags=["tag1", "tag2", "tag3"])
382
- def test_function_with_tags():
383
- return "tagged_result"
384
-
385
- result = test_function_with_tags()
386
- assert result == "tagged_result"
387
-
388
- spans = instrumentation.get_finished_spans()
389
- assert len(spans) == 1
390
- span = spans[0]
391
- assert span.name == "test_function_with_tags.test_kind"
392
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
393
- # Tags should be recorded in the span attributes
394
-
395
- def test_tags_parameter_dict(self, instrumentation: InstrumentationTester):
396
- """Test tags parameter with dictionary."""
397
- decorator = create_entity_decorator("test_kind")
398
-
399
- @decorator(tags={"env": "test", "version": "1.0"})
400
- def test_function_with_dict_tags():
401
- return "dict_tagged_result"
402
-
403
- result = test_function_with_dict_tags()
404
- assert result == "dict_tagged_result"
405
-
406
- spans = instrumentation.get_finished_spans()
407
- assert len(spans) == 1
408
- span = spans[0]
409
- assert span.name == "test_function_with_dict_tags.test_kind"
410
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
411
-
412
- def test_version_parameter(self, instrumentation: InstrumentationTester):
413
- """Test version parameter."""
414
- decorator = create_entity_decorator("test_kind")
415
-
416
- @decorator(version="2.1.0")
417
- def test_function_with_version():
418
- return "versioned_result"
419
-
420
- result = test_function_with_version()
421
- assert result == "versioned_result"
422
-
423
- spans = instrumentation.get_finished_spans()
424
- assert len(spans) == 1
425
- span = spans[0]
426
- assert span.name == "test_function_with_version.test_kind"
427
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
428
-
429
- def test_function_with_exception(self, instrumentation: InstrumentationTester):
430
- """Test function decoration with exception handling."""
431
- decorator = create_entity_decorator("test_kind")
432
-
433
- @decorator
434
- def test_function_with_exception():
435
- raise ValueError("Test exception")
436
-
437
- with pytest.raises(ValueError, match="Test exception"):
438
- test_function_with_exception()
439
-
440
- spans = instrumentation.get_finished_spans()
441
- assert len(spans) == 1
442
- span = spans[0]
443
- assert span.name == "test_function_with_exception.test_kind"
444
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
445
-
446
- def test_async_function_with_exception(self, instrumentation: InstrumentationTester):
447
- """Test async function decoration with exception handling."""
448
- decorator = create_entity_decorator("test_kind")
449
-
450
- @decorator
451
- async def test_async_function_with_exception():
452
- await asyncio.sleep(0.01)
453
- raise RuntimeError("Async test exception")
454
-
455
- with pytest.raises(RuntimeError, match="Async test exception"):
456
- asyncio.run(test_async_function_with_exception())
457
-
458
- spans = instrumentation.get_finished_spans()
459
- assert len(spans) == 1
460
- span = spans[0]
461
- assert span.name == "test_async_function_with_exception.test_kind"
462
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
463
-
464
- def test_class_init_with_exception(self, instrumentation: InstrumentationTester):
465
- """Test class decoration with exception in __init__."""
466
- decorator = create_entity_decorator("test_kind")
467
-
468
- @decorator
469
- class TestClassWithException:
470
- def __init__(self, should_raise=False):
471
- if should_raise:
472
- raise ValueError("Init exception")
473
- self.value = 42
474
-
475
- # Normal instantiation
476
- instance = TestClassWithException(should_raise=False)
477
- assert instance.value == 42
478
-
479
- # Exception during instantiation
480
- with pytest.raises(ValueError, match="Init exception"):
481
- TestClassWithException(should_raise=True)
482
-
483
- spans = instrumentation.get_finished_spans()
484
- # Only one span should be created (for the successful instantiation)
485
- # The failed instantiation doesn't create a span because the exception is raised before span creation
486
- assert len(spans) == 1
487
-
488
- def test_tracer_not_initialized(self, instrumentation: InstrumentationTester):
489
- """Test behavior when tracer is not initialized."""
490
- # We can't directly set tracer.initialized as it's a read-only property
491
- # Instead, we'll test that the decorator works when tracer is not initialized
492
- # by temporarily mocking the tracer.initialized property
493
-
494
- decorator = create_entity_decorator("test_kind")
495
-
496
- @decorator
497
- def test_function_no_tracer():
498
- return "no_tracer_result"
499
-
500
- # This should work normally since tracer is initialized in tests
501
- result = test_function_no_tracer()
502
- assert result == "no_tracer_result"
503
-
504
- # Should create spans normally
505
- spans = instrumentation.get_finished_spans()
506
- assert len(spans) == 1
507
-
508
- def test_complex_parameter_combination(self, instrumentation: InstrumentationTester):
509
- """Test decorator with all parameters combined."""
510
- decorator = create_entity_decorator("tool")
511
-
512
- @decorator(
513
- name="complex_function", version="3.0.0", tags={"env": "test", "component": "test"}, cost=0.1, spec="input"
514
- )
515
- def test_complex_function(x, y):
516
- return x * y
517
-
518
- result = test_complex_function(5, 6)
519
- assert result == 30
520
-
521
- spans = instrumentation.get_finished_spans()
522
- assert len(spans) == 1
523
- span = spans[0]
524
- assert span.name == "complex_function.tool"
525
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "tool"
526
- assert span.attributes.get("gen_ai.usage.total_cost") == 0.1
527
-
528
- def test_method_decoration(self, instrumentation: InstrumentationTester):
529
- """Test decoration of class methods."""
530
- decorator = create_entity_decorator("test_kind")
531
-
532
- class TestClass:
533
- def __init__(self):
534
- self.value = 0
535
-
536
- @decorator
537
- def test_method(self, increment):
538
- self.value += increment
539
- return self.value
540
-
541
- instance = TestClass()
542
- result = instance.test_method(5)
543
- assert result == 5
544
- assert instance.value == 5
545
-
546
- spans = instrumentation.get_finished_spans()
547
- assert len(spans) == 1
548
- span = spans[0]
549
- assert span.name == "test_method.test_kind"
550
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
551
-
552
- def test_static_method_decoration(self, instrumentation: InstrumentationTester):
553
- """Test decoration of static methods."""
554
- decorator = create_entity_decorator("test_kind")
555
-
556
- class TestClass:
557
- @staticmethod
558
- @decorator
559
- def test_static_method(x, y):
560
- return x + y
561
-
562
- result = TestClass.test_static_method(3, 4)
563
- assert result == 7
564
-
565
- spans = instrumentation.get_finished_spans()
566
- assert len(spans) == 1
567
- span = spans[0]
568
- assert span.name == "test_static_method.test_kind"
569
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
570
-
571
- def test_class_method_decoration(self, instrumentation: InstrumentationTester):
572
- """Test decoration of class methods."""
573
- decorator = create_entity_decorator("test_kind")
574
-
575
- class TestClass:
576
- class_value = 100
577
-
578
- @classmethod
579
- @decorator
580
- def test_class_method(cls, increment):
581
- cls.class_value += increment
582
- return cls.class_value
583
-
584
- result = TestClass.test_class_method(50)
585
- assert result == 150
586
- assert TestClass.class_value == 150
587
-
588
- spans = instrumentation.get_finished_spans()
589
- assert len(spans) == 1
590
- span = spans[0]
591
- assert span.name == "test_class_method.test_kind"
592
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
593
-
594
- def test_nested_decorators(self, instrumentation: InstrumentationTester):
595
- """Test multiple decorators applied to the same function."""
596
- decorator1 = create_entity_decorator("kind1")
597
- decorator2 = create_entity_decorator("kind2")
598
-
599
- @decorator1
600
- @decorator2
601
- def test_nested_function():
602
- return "nested_result"
603
-
604
- result = test_nested_function()
605
- assert result == "nested_result"
606
-
607
- spans = instrumentation.get_finished_spans()
608
- assert len(spans) == 2 # Should create spans for both decorators
609
-
610
- # Check that both spans were created with correct names
611
- span_names = [span.name for span in spans]
612
- assert "test_nested_function.kind2" in span_names
613
- assert "test_nested_function.kind1" in span_names
614
-
615
- span_kinds = [span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) for span in spans]
616
- assert "kind1" in span_kinds
617
- assert "kind2" in span_kinds
618
-
619
- def test_decorator_with_lambda(self, instrumentation: InstrumentationTester):
620
- """Test decorator with lambda function."""
621
- decorator = create_entity_decorator("test_kind")
622
-
623
- test_lambda = decorator(lambda x: x * 2)
624
-
625
- result = test_lambda(5)
626
- assert result == 10
627
-
628
- spans = instrumentation.get_finished_spans()
629
- assert len(spans) == 1
630
- span = spans[0]
631
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
632
-
633
- def test_decorator_with_builtin_function(self, instrumentation: InstrumentationTester):
634
- """Test decorator with built-in function (should work but may not create spans)."""
635
- decorator = create_entity_decorator("test_kind")
636
-
637
- # This should not raise an error, but may not create spans due to built-in function limitations
638
- decorated_len = decorator(len)
639
-
640
- result = decorated_len([1, 2, 3, 4, 5])
641
- assert result == 5
642
-
643
- # Built-in functions may not be instrumented the same way
644
- _ = instrumentation.get_finished_spans()
645
- # The behavior may vary depending on the implementation
646
-
647
- def test_decorator_with_coroutine_function(self, instrumentation: InstrumentationTester):
648
- """Test decorator with coroutine function."""
649
- decorator = create_entity_decorator("test_kind")
650
-
651
- @decorator
652
- async def test_coroutine():
653
- await asyncio.sleep(0.01)
654
- return "coroutine_result"
655
-
656
- # Test that it's actually a coroutine function
657
- assert asyncio.iscoroutinefunction(test_coroutine)
658
-
659
- result = asyncio.run(test_coroutine())
660
- assert result == "coroutine_result"
661
-
662
- spans = instrumentation.get_finished_spans()
663
- assert len(spans) == 1
664
- span = spans[0]
665
- assert span.name == "test_coroutine.test_kind"
666
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
667
-
668
- def test_decorator_with_async_generator_function(self, instrumentation: InstrumentationTester):
669
- """Test decorator with async generator function."""
670
- decorator = create_entity_decorator("test_kind")
671
-
672
- @decorator
673
- async def test_async_gen():
674
- for i in range(3):
675
- await asyncio.sleep(0.01)
676
- yield f"async_gen_item_{i}"
677
-
678
- # Test that it's actually an async generator function
679
- assert inspect.isasyncgenfunction(test_async_gen)
680
-
681
- async def collect_async_gen():
682
- results = []
683
- async for item in test_async_gen():
684
- results.append(item)
685
- return results
686
-
687
- results = asyncio.run(collect_async_gen())
688
- assert results == ["async_gen_item_0", "async_gen_item_1", "async_gen_item_2"]
689
-
690
- spans = instrumentation.get_finished_spans()
691
- assert len(spans) == 1
692
- span = spans[0]
693
- assert span.name == "test_async_gen.test_kind"
694
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
695
-
696
- def test_decorator_with_generator_function(self, instrumentation: InstrumentationTester):
697
- """Test decorator with generator function."""
698
- decorator = create_entity_decorator("test_kind")
699
-
700
- @decorator
701
- def test_gen():
702
- for i in range(3):
703
- yield f"gen_item_{i}"
704
-
705
- # Test that it's actually a generator function
706
- assert inspect.isgeneratorfunction(test_gen)
707
-
708
- results = list(test_gen())
709
- assert results == ["gen_item_0", "gen_item_1", "gen_item_2"]
710
-
711
- spans = instrumentation.get_finished_spans()
712
- assert len(spans) == 1
713
- span = spans[0]
714
- assert span.name == "test_gen.test_kind"
715
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
716
-
717
- def test_decorator_with_kwargs_only_function(self, instrumentation: InstrumentationTester):
718
- """Test decorator with function that only accepts kwargs."""
719
- decorator = create_entity_decorator("test_kind")
720
-
721
- @decorator
722
- def test_kwargs_only(**kwargs):
723
- return sum(kwargs.values())
724
-
725
- result = test_kwargs_only(a=1, b=2, c=3)
726
- assert result == 6
727
-
728
- spans = instrumentation.get_finished_spans()
729
- assert len(spans) == 1
730
- span = spans[0]
731
- assert span.name == "test_kwargs_only.test_kind"
732
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
733
-
734
- def test_decorator_with_args_only_function(self, instrumentation: InstrumentationTester):
735
- """Test decorator with function that only accepts args."""
736
- decorator = create_entity_decorator("test_kind")
737
-
738
- @decorator
739
- def test_args_only(*args):
740
- return sum(args)
741
-
742
- result = test_args_only(1, 2, 3, 4, 5)
743
- assert result == 15
744
-
745
- spans = instrumentation.get_finished_spans()
746
- assert len(spans) == 1
747
- span = spans[0]
748
- assert span.name == "test_args_only.test_kind"
749
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
750
-
751
- def test_decorator_with_mixed_args_function(self, instrumentation: InstrumentationTester):
752
- """Test decorator with function that accepts both args and kwargs."""
753
- decorator = create_entity_decorator("test_kind")
754
-
755
- @decorator
756
- def test_mixed_args(x, y, *args, **kwargs):
757
- return x + y + sum(args) + sum(kwargs.values())
758
-
759
- result = test_mixed_args(1, 2, 3, 4, a=5, b=6)
760
- assert result == 21 # 1 + 2 + 3 + 4 + 5 + 6
761
-
762
- spans = instrumentation.get_finished_spans()
763
- assert len(spans) == 1
764
- span = spans[0]
765
- assert span.name == "test_mixed_args.test_kind"
766
- assert span.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == "test_kind"
767
-
768
- def test_class_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
769
- """Test exception handling when recording class input fails."""
770
- decorator = create_entity_decorator("test_kind")
771
-
772
- # Create a class that will cause _record_entity_input to fail
773
- @decorator
774
- class TestClass:
775
- def __init__(self, value=42):
776
- # Create an object that will cause serialization to fail
777
- self.value = value
778
- self.bad_object = object() # This will cause serialization issues
779
-
780
- # The exception should be caught and logged
781
- instance = TestClass(100)
782
- assert instance.value == 100
783
- # Note: The actual exception might not be logged in the current implementation
784
- # but the coverage will show if the exception handling path was executed
785
-
786
- def test_class_output_recording_exception(self, instrumentation: InstrumentationTester, caplog):
787
- """Test exception handling when recording class output fails."""
788
- decorator = create_entity_decorator("test_kind")
789
-
790
- @decorator
791
- class TestClass:
792
- def __init__(self):
793
- self.value = 42
794
- # Create an object that will cause serialization to fail
795
- self.bad_object = object()
796
-
797
- async def test_async_context():
798
- async with TestClass():
799
- return "success"
800
-
801
- result = asyncio.run(test_async_context())
802
- assert result == "success"
803
- # Note: The actual exception might not be logged in the current implementation
804
- # but the coverage will show if the exception handling path was executed
805
-
806
- def test_session_generator_implementation(self, instrumentation: InstrumentationTester, caplog):
807
- """Test the session generator implementation that was previously not implemented."""
808
- decorator = create_entity_decorator(SpanKind.SESSION)
809
-
810
- @decorator
811
- def test_session_generator():
812
- yield 1
813
- yield 2
814
- yield 3
815
-
816
- results = list(test_session_generator())
817
- assert results == [1, 2, 3]
818
-
819
- # The warning should be logged, but the exact message might vary
820
- # Just verify that the function works and creates spans
821
- spans = instrumentation.get_finished_spans()
822
- assert len(spans) == 1
823
-
824
- def test_session_async_generator_implementation(self, instrumentation: InstrumentationTester, caplog):
825
- """Test the session async generator implementation."""
826
- decorator = create_entity_decorator(SpanKind.SESSION)
827
-
828
- @decorator
829
- async def test_session_async_generator():
830
- yield 1
831
- yield 2
832
- yield 3
833
-
834
- async def collect_results():
835
- results = []
836
- async for item in test_session_async_generator():
837
- results.append(item)
838
- return results
839
-
840
- results = asyncio.run(collect_results())
841
- assert results == [1, 2, 3]
842
-
843
- # The warning should be logged, but the exact message might vary
844
- # Just verify that the function works and creates spans
845
- spans = instrumentation.get_finished_spans()
846
- assert len(spans) == 1
847
-
848
- def test_session_generator_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
849
- """Test exception handling when recording session generator input fails."""
850
- decorator = create_entity_decorator(SpanKind.SESSION)
851
-
852
- @decorator
853
- def test_session_generator():
854
- # Create an object that will cause serialization to fail
855
- _ = object() # This will cause serialization issues
856
- yield 1
857
- yield 2
858
-
859
- results = list(test_session_generator())
860
- assert results == [1, 2]
861
- # Note: The actual exception might not be logged in the current implementation
862
- # but the coverage will show if the exception handling path was executed
863
-
864
- def test_session_async_generator_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
865
- """Test exception handling when recording session async generator input fails."""
866
- decorator = create_entity_decorator(SpanKind.SESSION)
867
-
868
- @decorator
869
- async def test_session_async_generator():
870
- # Create an object that will cause serialization to fail
871
- _ = object() # This will cause serialization issues
872
- yield 1
873
- yield 2
874
-
875
- async def collect_results():
876
- results = []
877
- async for item in test_session_async_generator():
878
- results.append(item)
879
- return results
880
-
881
- results = asyncio.run(collect_results())
882
- assert results == [1, 2]
883
- # Note: The actual exception might not be logged in the current implementation
884
- # but the coverage will show if the exception handling path was executed
885
-
886
- def test_session_async_trace_start_failure(self, instrumentation: InstrumentationTester, caplog):
887
- """Test handling when trace start fails for session async function."""
888
- decorator = create_entity_decorator(SpanKind.SESSION)
889
-
890
- # Mock tracer.start_trace to return None
891
- with pytest.MonkeyPatch().context() as m:
892
- m.setattr(tracer, "start_trace", lambda *args, **kwargs: None)
893
-
894
- @decorator
895
- async def test_session_async_function():
896
- return "success"
897
-
898
- result = asyncio.run(test_session_async_function())
899
- assert result == "success"
900
- # The error message should be logged, but the exact format might vary
901
- # Just verify that the function works when trace start fails
902
-
903
- def test_session_async_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
904
- """Test exception handling when recording session async input fails."""
905
- decorator = create_entity_decorator(SpanKind.SESSION)
906
-
907
- @decorator
908
- async def test_session_async_function():
909
- # Create an object that will cause serialization to fail
910
- _ = object() # This will cause serialization issues
911
- return "success"
912
-
913
- result = asyncio.run(test_session_async_function())
914
- assert result == "success"
915
- # Note: The actual exception might not be logged in the current implementation
916
- # but the coverage will show if the exception handling path was executed
917
-
918
- def test_session_async_output_recording_exception(self, instrumentation: InstrumentationTester, caplog):
919
- """Test exception handling when recording session async output fails."""
920
- decorator = create_entity_decorator(SpanKind.SESSION)
921
-
922
- @decorator
923
- async def test_session_async_function():
924
- # Return an object that will cause serialization to fail
925
- return object()
926
-
927
- result = asyncio.run(test_session_async_function())
928
- assert result is not None
929
- # Note: The actual exception might not be logged in the current implementation
930
- # but the coverage will show if the exception handling path was executed
931
-
932
- def test_session_async_exception_handling(self, instrumentation: InstrumentationTester):
933
- """Test exception handling in session async function."""
934
- decorator = create_entity_decorator(SpanKind.SESSION)
935
-
936
- @decorator
937
- async def test_session_async_function():
938
- raise ValueError("Test exception")
939
-
940
- with pytest.raises(ValueError, match="Test exception"):
941
- asyncio.run(test_session_async_function())
942
-
943
- # Should end trace with "Indeterminate" state
944
- spans = instrumentation.get_finished_spans()
945
- assert len(spans) == 1
946
-
947
- def test_session_async_finally_block(self, instrumentation: InstrumentationTester, caplog):
948
- """Test finally block handling in session async function."""
949
- decorator = create_entity_decorator(SpanKind.SESSION)
950
-
951
- @decorator
952
- async def test_session_async_function():
953
- return "success"
954
-
955
- result = asyncio.run(test_session_async_function())
956
- assert result == "success"
957
-
958
- # Should not log warning about trace not being ended since it was ended properly
959
- assert "not explicitly ended" not in caplog.text
960
-
961
- def test_session_sync_trace_start_failure(self, instrumentation: InstrumentationTester, caplog):
962
- """Test handling when trace start fails for session sync function."""
963
- decorator = create_entity_decorator(SpanKind.SESSION)
964
-
965
- # Mock tracer.start_trace to return None
966
- with pytest.MonkeyPatch().context() as m:
967
- m.setattr(tracer, "start_trace", lambda *args, **kwargs: None)
968
-
969
- @decorator
970
- def test_session_sync_function():
971
- return "success"
972
-
973
- result = test_session_sync_function()
974
- assert result == "success"
975
- # The error message should be logged, but the exact format might vary
976
- # Just verify that the function works when trace start fails
977
-
978
- def test_session_sync_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
979
- """Test exception handling when recording session sync input fails."""
980
- decorator = create_entity_decorator(SpanKind.SESSION)
981
-
982
- @decorator
983
- def test_session_sync_function():
984
- # Create an object that will cause serialization to fail
985
- _ = object() # This will cause serialization issues
986
- return "success"
987
-
988
- result = test_session_sync_function()
989
- assert result == "success"
990
- # Note: The actual exception might not be logged in the current implementation
991
- # but the coverage will show if the exception handling path was executed
992
-
993
- def test_session_sync_output_recording_exception(self, instrumentation: InstrumentationTester, caplog):
994
- """Test exception handling when recording session sync output fails."""
995
- decorator = create_entity_decorator(SpanKind.SESSION)
996
-
997
- @decorator
998
- def test_session_sync_function():
999
- # Return an object that will cause serialization to fail
1000
- return object()
1001
-
1002
- result = test_session_sync_function()
1003
- assert result is not None
1004
- # Note: The actual exception might not be logged in the current implementation
1005
- # but the coverage will show if the exception handling path was executed
1006
-
1007
- def test_session_sync_exception_handling(self, instrumentation: InstrumentationTester):
1008
- """Test exception handling in session sync function."""
1009
- decorator = create_entity_decorator(SpanKind.SESSION)
1010
-
1011
- @decorator
1012
- def test_session_sync_function():
1013
- raise ValueError("Test exception")
1014
-
1015
- with pytest.raises(ValueError, match="Test exception"):
1016
- test_session_sync_function()
1017
-
1018
- # Should end trace with "Indeterminate" state
1019
- spans = instrumentation.get_finished_spans()
1020
- assert len(spans) == 1
1021
-
1022
- def test_session_sync_finally_block(self, instrumentation: InstrumentationTester, caplog):
1023
- """Test finally block handling in session sync function."""
1024
- decorator = create_entity_decorator(SpanKind.SESSION)
1025
-
1026
- @decorator
1027
- def test_session_sync_function():
1028
- return "success"
1029
-
1030
- result = test_session_sync_function()
1031
- assert result == "success"
1032
-
1033
- # Should not log warning about trace not being ended since it was ended properly
1034
- assert "not explicitly ended" not in caplog.text
1035
-
1036
- def test_generator_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
1037
- """Test exception handling when recording generator input fails."""
1038
- decorator = create_entity_decorator("test_kind")
1039
-
1040
- @decorator
1041
- def test_generator():
1042
- # Create an object that will cause serialization to fail
1043
- _ = object() # This will cause serialization issues
1044
- yield 1
1045
- yield 2
1046
-
1047
- results = list(test_generator())
1048
- assert results == [1, 2]
1049
- # Note: The actual exception might not be logged in the current implementation
1050
- # but the coverage will show if the exception handling path was executed
1051
-
1052
- def test_async_generator_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
1053
- """Test exception handling when recording async generator input fails."""
1054
- decorator = create_entity_decorator("test_kind")
1055
-
1056
- @decorator
1057
- async def test_async_generator():
1058
- # Create an object that will cause serialization to fail
1059
- _ = object() # This will cause serialization issues
1060
- yield 1
1061
- yield 2
1062
-
1063
- async def collect_results():
1064
- results = []
1065
- async for item in test_async_generator():
1066
- results.append(item)
1067
- return results
1068
-
1069
- results = asyncio.run(collect_results())
1070
- assert results == [1, 2]
1071
- # Note: The actual exception might not be logged in the current implementation
1072
- # but the coverage will show if the exception handling path was executed
1073
-
1074
- def test_async_function_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
1075
- """Test exception handling when recording async function input fails."""
1076
- decorator = create_entity_decorator("test_kind")
1077
-
1078
- @decorator
1079
- async def test_async_function():
1080
- # Create an object that will cause serialization to fail
1081
- _ = object() # This will cause serialization issues
1082
- return "success"
1083
-
1084
- result = asyncio.run(test_async_function())
1085
- assert result == "success"
1086
- # Note: The actual exception might not be logged in the current implementation
1087
- # but the coverage will show if the exception handling path was executed
1088
-
1089
- def test_async_function_output_recording_exception(self, instrumentation: InstrumentationTester, caplog):
1090
- """Test exception handling when recording async function output fails."""
1091
- decorator = create_entity_decorator("test_kind")
1092
-
1093
- @decorator
1094
- async def test_async_function():
1095
- # Return an object that will cause serialization to fail
1096
- return object()
1097
-
1098
- result = asyncio.run(test_async_function())
1099
- assert result is not None
1100
- # Note: The actual exception might not be logged in the current implementation
1101
- # but the coverage will show if the exception handling path was executed
1102
-
1103
- def test_async_function_execution_exception(self, instrumentation: InstrumentationTester, caplog):
1104
- """Test exception handling in async function execution."""
1105
- decorator = create_entity_decorator("test_kind")
1106
-
1107
- @decorator
1108
- async def test_async_function():
1109
- raise ValueError("Test exception")
1110
-
1111
- with pytest.raises(ValueError, match="Test exception"):
1112
- asyncio.run(test_async_function())
1113
-
1114
- # The error should be logged, but the exact message might vary
1115
- # Just verify that the exception is handled properly
1116
- spans = instrumentation.get_finished_spans()
1117
- assert len(spans) == 1
1118
-
1119
- def test_sync_function_input_recording_exception(self, instrumentation: InstrumentationTester, caplog):
1120
- """Test exception handling when recording sync function input fails."""
1121
- decorator = create_entity_decorator("test_kind")
1122
-
1123
- @decorator
1124
- def test_sync_function():
1125
- # Create an object that will cause serialization to fail
1126
- _ = object() # This will cause serialization issues
1127
- return "success"
1128
-
1129
- result = test_sync_function()
1130
- assert result == "success"
1131
- # Note: The actual exception might not be logged in the current implementation
1132
- # but the coverage will show if the exception handling path was executed
1133
-
1134
- def test_sync_function_output_recording_exception(self, instrumentation: InstrumentationTester, caplog):
1135
- """Test exception handling when recording sync function output fails."""
1136
- decorator = create_entity_decorator("test_kind")
1137
-
1138
- @decorator
1139
- def test_sync_function():
1140
- # Return an object that will cause serialization to fail
1141
- return object()
1142
-
1143
- result = test_sync_function()
1144
- assert result is not None
1145
- # Note: The actual exception might not be logged in the current implementation
1146
- # but the coverage will show if the exception handling path was executed
1147
-
1148
- def test_sync_function_execution_exception(self, instrumentation: InstrumentationTester, caplog):
1149
- """Test exception handling in sync function execution."""
1150
- decorator = create_entity_decorator("test_kind")
1151
-
1152
- @decorator
1153
- def test_sync_function():
1154
- raise ValueError("Test exception")
1155
-
1156
- with pytest.raises(ValueError, match="Test exception"):
1157
- test_sync_function()
1158
-
1159
- # The error should be logged, but the exact message might vary
1160
- # Just verify that the exception is handled properly
1161
- spans = instrumentation.get_finished_spans()
1162
- assert len(spans) == 1
1163
-
1164
- def test_class_del_method_coverage(self, instrumentation: InstrumentationTester):
1165
- """Test that __del__ method is called when object is garbage collected."""
1166
- decorator = create_entity_decorator("test_kind")
1167
-
1168
- @decorator
1169
- class TestClass:
1170
- def __init__(self):
1171
- self.value = 42
1172
-
1173
- # Create instance and let it go out of scope to trigger __del__
1174
- def create_and_destroy():
1175
- instance = TestClass()
1176
- assert instance.value == 42
1177
- # The __del__ method should be called when instance goes out of scope
1178
-
1179
- create_and_destroy()
1180
-
1181
- # Force garbage collection to trigger __del__
1182
- import gc
1183
-
1184
- gc.collect()
1185
-
1186
- # The __del__ method should have been called, but we can't easily test this
1187
- # since it's called during garbage collection. The coverage will show if the
1188
- # lines were executed.