mseep-agentops 0.4.18__py3-none-any.whl → 0.4.22__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 +0 -0
- agentops/client/api/base.py +28 -30
- agentops/client/api/versions/v3.py +29 -25
- agentops/client/api/versions/v4.py +87 -46
- agentops/client/client.py +98 -29
- agentops/client/http/README.md +87 -0
- agentops/client/http/http_client.py +126 -172
- agentops/config.py +8 -2
- agentops/instrumentation/OpenTelemetry.md +133 -0
- agentops/instrumentation/README.md +167 -0
- agentops/instrumentation/__init__.py +13 -1
- agentops/instrumentation/agentic/ag2/__init__.py +18 -0
- agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
- agentops/instrumentation/agentic/agno/__init__.py +19 -0
- agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
- agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
- agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
- agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
- agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
- agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
- agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
- agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
- agentops/instrumentation/agentic/crewai/LICENSE +201 -0
- agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
- agentops/instrumentation/agentic/crewai/__init__.py +6 -0
- agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
- agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
- agentops/instrumentation/agentic/crewai/version.py +1 -0
- agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
- agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
- agentops/instrumentation/agentic/google_adk/patch.py +767 -0
- agentops/instrumentation/agentic/haystack/__init__.py +1 -0
- agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
- agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
- agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
- agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
- agentops/instrumentation/agentic/langgraph/version.py +1 -0
- agentops/instrumentation/agentic/openai_agents/README.md +156 -0
- agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
- agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
- agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
- agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
- agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
- agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
- agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
- agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
- agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
- agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
- agentops/instrumentation/agentic/smolagents/README.md +88 -0
- agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
- agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
- agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
- agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
- agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
- agentops/instrumentation/agentic/xpander/__init__.py +15 -0
- agentops/instrumentation/agentic/xpander/context.py +112 -0
- agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
- agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
- agentops/instrumentation/agentic/xpander/version.py +3 -0
- agentops/instrumentation/common/README.md +65 -0
- agentops/instrumentation/common/attributes.py +1 -2
- agentops/instrumentation/providers/anthropic/__init__.py +24 -0
- agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
- agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
- agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
- agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
- agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
- agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
- agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
- agentops/instrumentation/providers/google_genai/README.md +33 -0
- agentops/instrumentation/providers/google_genai/__init__.py +24 -0
- agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
- agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
- agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
- agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
- agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
- agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
- agentops/instrumentation/providers/mem0/__init__.py +45 -0
- agentops/instrumentation/providers/mem0/common.py +377 -0
- agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
- agentops/instrumentation/providers/mem0/memory.py +430 -0
- agentops/instrumentation/providers/openai/__init__.py +21 -0
- agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
- agentops/instrumentation/providers/openai/attributes/common.py +55 -0
- agentops/instrumentation/providers/openai/attributes/response.py +607 -0
- agentops/instrumentation/providers/openai/config.py +36 -0
- agentops/instrumentation/providers/openai/instrumentor.py +312 -0
- agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
- agentops/instrumentation/providers/openai/utils.py +44 -0
- agentops/instrumentation/providers/openai/v0.py +176 -0
- agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
- agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
- agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
- agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
- agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
- agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
- agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
- agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
- agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
- agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
- agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
- agentops/integration/callbacks/dspy/__init__.py +11 -0
- agentops/integration/callbacks/dspy/callback.py +471 -0
- agentops/integration/callbacks/langchain/README.md +59 -0
- agentops/integration/callbacks/langchain/__init__.py +15 -0
- agentops/integration/callbacks/langchain/callback.py +791 -0
- agentops/integration/callbacks/langchain/utils.py +54 -0
- agentops/legacy/crewai.md +121 -0
- agentops/logging/instrument_logging.py +4 -0
- agentops/sdk/README.md +220 -0
- agentops/sdk/core.py +75 -32
- agentops/sdk/descriptors/classproperty.py +28 -0
- agentops/sdk/exporters.py +152 -33
- agentops/semconv/README.md +125 -0
- agentops/semconv/span_kinds.py +0 -2
- agentops/validation.py +102 -63
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/METADATA +30 -40
- mseep_agentops-0.4.22.dist-info/RECORD +178 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/WHEEL +1 -2
- mseep_agentops-0.4.18.dist-info/RECORD +0 -94
- mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
- tests/conftest.py +0 -10
- tests/unit/client/__init__.py +0 -1
- tests/unit/client/test_http_adapter.py +0 -221
- tests/unit/client/test_http_client.py +0 -206
- tests/unit/conftest.py +0 -54
- tests/unit/sdk/__init__.py +0 -1
- tests/unit/sdk/instrumentation_tester.py +0 -207
- tests/unit/sdk/test_attributes.py +0 -392
- tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
- tests/unit/sdk/test_decorators.py +0 -763
- tests/unit/sdk/test_exporters.py +0 -241
- tests/unit/sdk/test_factory.py +0 -1188
- tests/unit/sdk/test_internal_span_processor.py +0 -397
- tests/unit/sdk/test_resource_attributes.py +0 -35
- tests/unit/test_config.py +0 -82
- tests/unit/test_context_manager.py +0 -777
- tests/unit/test_events.py +0 -27
- tests/unit/test_host_env.py +0 -54
- tests/unit/test_init_py.py +0 -501
- tests/unit/test_serialization.py +0 -433
- tests/unit/test_session.py +0 -676
- tests/unit/test_user_agent.py +0 -34
- tests/unit/test_validation.py +0 -405
- {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
- /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/licenses/LICENSE +0 -0
tests/unit/test_serialization.py
DELETED
@@ -1,433 +0,0 @@
|
|
1
|
-
"""Tests for serialization helpers."""
|
2
|
-
|
3
|
-
import json
|
4
|
-
import uuid
|
5
|
-
from datetime import datetime
|
6
|
-
from decimal import Decimal
|
7
|
-
from enum import Enum
|
8
|
-
from typing import Dict
|
9
|
-
|
10
|
-
import pytest
|
11
|
-
|
12
|
-
from agentops.helpers.serialization import (
|
13
|
-
filter_unjsonable,
|
14
|
-
is_jsonable,
|
15
|
-
model_to_dict,
|
16
|
-
safe_serialize,
|
17
|
-
serialize_uuid,
|
18
|
-
)
|
19
|
-
|
20
|
-
|
21
|
-
# Define test models and data structures
|
22
|
-
class SampleEnum(Enum):
|
23
|
-
ONE = 1
|
24
|
-
TWO = 2
|
25
|
-
THREE = "three"
|
26
|
-
|
27
|
-
|
28
|
-
class SimpleModel:
|
29
|
-
"""A simple class with __dict__ but no model_dump or dict method."""
|
30
|
-
|
31
|
-
def __init__(self, value: str):
|
32
|
-
self.value = value
|
33
|
-
|
34
|
-
|
35
|
-
class ModelWithToJson:
|
36
|
-
"""A class that implements to_json method."""
|
37
|
-
|
38
|
-
def __init__(self, data: Dict):
|
39
|
-
self.data = data
|
40
|
-
|
41
|
-
def to_json(self):
|
42
|
-
return self.data
|
43
|
-
|
44
|
-
|
45
|
-
class PydanticV1Model:
|
46
|
-
"""Mock Pydantic v1 model with dict method."""
|
47
|
-
|
48
|
-
def __init__(self, **data):
|
49
|
-
self.__dict__.update(data)
|
50
|
-
|
51
|
-
def dict(self):
|
52
|
-
return self.__dict__
|
53
|
-
|
54
|
-
|
55
|
-
class PydanticV2Model:
|
56
|
-
"""Mock Pydantic v2 model with model_dump method."""
|
57
|
-
|
58
|
-
def __init__(self, **data):
|
59
|
-
self.__dict__.update(data)
|
60
|
-
|
61
|
-
def model_dump(self):
|
62
|
-
return self.__dict__
|
63
|
-
|
64
|
-
|
65
|
-
class ModelWithParse:
|
66
|
-
"""Mock model with parse method."""
|
67
|
-
|
68
|
-
def __init__(self, data):
|
69
|
-
self.data = data
|
70
|
-
|
71
|
-
def parse(self):
|
72
|
-
return self.data
|
73
|
-
|
74
|
-
|
75
|
-
class ModelWithoutDict:
|
76
|
-
"""A class without __dict__ attribute."""
|
77
|
-
|
78
|
-
__slots__ = ["value"]
|
79
|
-
|
80
|
-
def __init__(self, value: str):
|
81
|
-
self.value = value
|
82
|
-
|
83
|
-
|
84
|
-
# Define test cases for is_jsonable
|
85
|
-
class TestIsJsonable:
|
86
|
-
def test_jsonable_types(self):
|
87
|
-
"""Test that jsonable types return True."""
|
88
|
-
jsonable_objects = [
|
89
|
-
"string",
|
90
|
-
"",
|
91
|
-
123,
|
92
|
-
123.45,
|
93
|
-
True,
|
94
|
-
False,
|
95
|
-
None,
|
96
|
-
[1, 2, 3],
|
97
|
-
{"key": "value"},
|
98
|
-
[],
|
99
|
-
{},
|
100
|
-
]
|
101
|
-
|
102
|
-
for obj in jsonable_objects:
|
103
|
-
assert is_jsonable(obj) is True
|
104
|
-
|
105
|
-
def test_unjsonable_types(self):
|
106
|
-
"""Test that unjsonable types return False."""
|
107
|
-
unjsonable_objects = [
|
108
|
-
datetime.now(),
|
109
|
-
uuid.uuid4(),
|
110
|
-
Decimal("123.45"),
|
111
|
-
{1, 2, 3}, # set
|
112
|
-
SampleEnum.ONE,
|
113
|
-
lambda x: x, # function
|
114
|
-
object(), # generic object
|
115
|
-
]
|
116
|
-
|
117
|
-
for obj in unjsonable_objects:
|
118
|
-
assert is_jsonable(obj) is False
|
119
|
-
|
120
|
-
def test_circular_reference(self):
|
121
|
-
"""Test that circular references are not jsonable."""
|
122
|
-
a = {}
|
123
|
-
b = {}
|
124
|
-
a["b"] = b
|
125
|
-
b["a"] = a
|
126
|
-
|
127
|
-
# The current implementation doesn't handle ValueError from circular references
|
128
|
-
# So this will raise an exception instead of returning False
|
129
|
-
with pytest.raises(ValueError, match="Circular reference detected"):
|
130
|
-
is_jsonable(a)
|
131
|
-
|
132
|
-
|
133
|
-
# Define test cases for filter_unjsonable
|
134
|
-
class TestFilterUnjsonable:
|
135
|
-
def test_filter_simple_dict(self):
|
136
|
-
"""Test filtering of simple dictionary."""
|
137
|
-
input_dict = {
|
138
|
-
"string": "value",
|
139
|
-
"number": 42,
|
140
|
-
"list": [1, 2, 3],
|
141
|
-
"dict": {"nested": "value"},
|
142
|
-
"uuid": uuid.uuid4(),
|
143
|
-
"datetime": datetime.now(),
|
144
|
-
"set": {1, 2, 3},
|
145
|
-
}
|
146
|
-
|
147
|
-
result = filter_unjsonable(input_dict)
|
148
|
-
|
149
|
-
# Check that jsonable values are preserved
|
150
|
-
assert result["string"] == "value"
|
151
|
-
assert result["number"] == 42
|
152
|
-
assert result["list"] == [1, 2, 3]
|
153
|
-
assert result["dict"] == {"nested": "value"}
|
154
|
-
|
155
|
-
# Check that unjsonable values are converted to strings or empty strings
|
156
|
-
assert isinstance(result["uuid"], str)
|
157
|
-
assert result["datetime"] == ""
|
158
|
-
assert result["set"] == ""
|
159
|
-
|
160
|
-
def test_filter_nested_dict(self):
|
161
|
-
"""Test filtering of nested dictionaries."""
|
162
|
-
input_dict = {
|
163
|
-
"level1": {
|
164
|
-
"level2": {
|
165
|
-
"uuid": uuid.uuid4(),
|
166
|
-
"string": "preserved",
|
167
|
-
"datetime": datetime.now(),
|
168
|
-
}
|
169
|
-
},
|
170
|
-
"list_with_unjsonable": [
|
171
|
-
{"uuid": uuid.uuid4()},
|
172
|
-
"string",
|
173
|
-
datetime.now(),
|
174
|
-
],
|
175
|
-
}
|
176
|
-
|
177
|
-
result = filter_unjsonable(input_dict)
|
178
|
-
|
179
|
-
# Check nested structure is preserved
|
180
|
-
assert result["level1"]["level2"]["string"] == "preserved"
|
181
|
-
assert isinstance(result["level1"]["level2"]["uuid"], str)
|
182
|
-
assert result["level1"]["level2"]["datetime"] == ""
|
183
|
-
|
184
|
-
# Check list filtering
|
185
|
-
assert result["list_with_unjsonable"][1] == "string"
|
186
|
-
assert isinstance(result["list_with_unjsonable"][0]["uuid"], str)
|
187
|
-
assert result["list_with_unjsonable"][2] == ""
|
188
|
-
|
189
|
-
def test_filter_list(self):
|
190
|
-
"""Test filtering of lists."""
|
191
|
-
input_list = [
|
192
|
-
"string",
|
193
|
-
42,
|
194
|
-
uuid.uuid4(),
|
195
|
-
datetime.now(),
|
196
|
-
[1, 2, uuid.uuid4()],
|
197
|
-
{"uuid": uuid.uuid4()},
|
198
|
-
]
|
199
|
-
|
200
|
-
result = filter_unjsonable(input_list)
|
201
|
-
|
202
|
-
assert result[0] == "string"
|
203
|
-
assert result[1] == 42
|
204
|
-
assert isinstance(result[2], str) # UUID converted to string
|
205
|
-
assert result[3] == "" # datetime converted to empty string
|
206
|
-
assert isinstance(result[4][2], str) # nested UUID converted to string
|
207
|
-
assert isinstance(result[5]["uuid"], str) # nested UUID converted to string
|
208
|
-
|
209
|
-
def test_filter_empty_structures(self):
|
210
|
-
"""Test filtering of empty structures."""
|
211
|
-
assert filter_unjsonable({}) == {}
|
212
|
-
assert filter_unjsonable([]) == []
|
213
|
-
assert filter_unjsonable({"empty": {}}) == {"empty": {}}
|
214
|
-
|
215
|
-
|
216
|
-
# Define test cases for serialize_uuid
|
217
|
-
class TestSerializeUuid:
|
218
|
-
def test_serialize_uuid(self):
|
219
|
-
"""Test UUID serialization."""
|
220
|
-
test_uuid = uuid.uuid4()
|
221
|
-
result = serialize_uuid(test_uuid)
|
222
|
-
|
223
|
-
assert isinstance(result, str)
|
224
|
-
assert result == str(test_uuid)
|
225
|
-
|
226
|
-
def test_serialize_uuid_string(self):
|
227
|
-
"""Test that UUID string representation is correct."""
|
228
|
-
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000001")
|
229
|
-
result = serialize_uuid(test_uuid)
|
230
|
-
|
231
|
-
assert result == "00000000-0000-0000-0000-000000000001"
|
232
|
-
|
233
|
-
|
234
|
-
# Define test cases for safe_serialize
|
235
|
-
class TestSafeSerialize:
|
236
|
-
def test_strings_returned_untouched(self):
|
237
|
-
"""Test that strings are returned untouched."""
|
238
|
-
test_strings = [
|
239
|
-
"simple string",
|
240
|
-
"",
|
241
|
-
"special chars: !@#$%^&*()",
|
242
|
-
'{"json": "string"}', # JSON as a string
|
243
|
-
"[1, 2, 3]", # JSON array as a string
|
244
|
-
"line 1\nline 2", # String with newlines
|
245
|
-
]
|
246
|
-
|
247
|
-
for input_str in test_strings:
|
248
|
-
# The string should be returned exactly as is
|
249
|
-
assert safe_serialize(input_str) == input_str
|
250
|
-
|
251
|
-
def test_complex_objects_serialized(self):
|
252
|
-
"""Test that complex objects are properly serialized."""
|
253
|
-
test_cases = [
|
254
|
-
# Test case, expected serialized form (or None for dict check)
|
255
|
-
({"key": "value"}, '{"key": "value"}'),
|
256
|
-
([1, 2, 3], "[1, 2, 3]"),
|
257
|
-
(123, "123"),
|
258
|
-
(123.45, "123.45"),
|
259
|
-
(True, "true"),
|
260
|
-
(False, "false"),
|
261
|
-
(None, "null"),
|
262
|
-
]
|
263
|
-
|
264
|
-
for input_obj, expected in test_cases:
|
265
|
-
result = safe_serialize(input_obj)
|
266
|
-
if expected is not None:
|
267
|
-
# Check exact match for simple cases
|
268
|
-
assert json.loads(result) == json.loads(expected)
|
269
|
-
else:
|
270
|
-
# For complex cases just verify it's valid JSON
|
271
|
-
assert isinstance(result, str)
|
272
|
-
assert json.loads(result) is not None
|
273
|
-
|
274
|
-
def test_pydantic_models(self):
|
275
|
-
"""Test serialization of Pydantic-like models."""
|
276
|
-
# V1 model with dict()
|
277
|
-
v1_model = PydanticV1Model(name="test", value=42)
|
278
|
-
v1_result = safe_serialize(v1_model)
|
279
|
-
assert json.loads(v1_result) == {"name": "test", "value": 42}
|
280
|
-
|
281
|
-
# V2 model with model_dump()
|
282
|
-
v2_model = PydanticV2Model(name="test", value=42)
|
283
|
-
v2_result = safe_serialize(v2_model)
|
284
|
-
assert json.loads(v2_result) == {"name": "test", "value": 42}
|
285
|
-
|
286
|
-
# Note: parse() method is currently not implemented due to recursion issues
|
287
|
-
# See TODO in serialization.py
|
288
|
-
|
289
|
-
def test_special_types(self):
|
290
|
-
"""Test serialization of special types using AgentOpsJSONEncoder."""
|
291
|
-
test_cases = [
|
292
|
-
# Datetime
|
293
|
-
(datetime(2023, 1, 1, 12, 0, 0), '"2023-01-01T12:00:00"'),
|
294
|
-
# UUID
|
295
|
-
(uuid.UUID("00000000-0000-0000-0000-000000000001"), '"00000000-0000-0000-0000-000000000001"'),
|
296
|
-
# Decimal
|
297
|
-
(Decimal("123.45"), '"123.45"'),
|
298
|
-
# Set
|
299
|
-
({1, 2, 3}, "[1, 2, 3]"),
|
300
|
-
# Enum
|
301
|
-
(SampleEnum.ONE, "1"),
|
302
|
-
(SampleEnum.THREE, '"three"'),
|
303
|
-
# Class with to_json
|
304
|
-
(ModelWithToJson({"key": "value"}), '{"key": "value"}'),
|
305
|
-
]
|
306
|
-
|
307
|
-
for input_obj, expected in test_cases:
|
308
|
-
result = safe_serialize(input_obj)
|
309
|
-
|
310
|
-
# Handle list comparison for sets where order might vary
|
311
|
-
if isinstance(input_obj, set):
|
312
|
-
assert sorted(json.loads(result)) == sorted(json.loads(expected))
|
313
|
-
else:
|
314
|
-
assert json.loads(result) == json.loads(expected)
|
315
|
-
|
316
|
-
def test_nested_objects(self):
|
317
|
-
"""Test serialization of nested objects."""
|
318
|
-
nested_obj = {
|
319
|
-
"string": "value",
|
320
|
-
"number": 42,
|
321
|
-
"list": [1, 2, {"inner": "value"}],
|
322
|
-
"dict": {"inner": {"deeper": [1, 2, 3]}},
|
323
|
-
"model": PydanticV2Model(name="test"),
|
324
|
-
}
|
325
|
-
|
326
|
-
result = safe_serialize(nested_obj)
|
327
|
-
|
328
|
-
# Verify it's valid JSON
|
329
|
-
parsed = json.loads(result)
|
330
|
-
assert parsed["string"] == "value"
|
331
|
-
assert parsed["number"] == 42
|
332
|
-
assert parsed["list"][2]["inner"] == "value"
|
333
|
-
assert parsed["dict"]["inner"]["deeper"] == [1, 2, 3]
|
334
|
-
|
335
|
-
# Just verify we have the model in some form
|
336
|
-
assert "model" in parsed
|
337
|
-
# And verify it contains the expected data in some form
|
338
|
-
assert "test" in str(parsed["model"])
|
339
|
-
|
340
|
-
def test_fallback_to_str(self):
|
341
|
-
"""Test fallback to str() for unserializable objects."""
|
342
|
-
|
343
|
-
class Unserializable:
|
344
|
-
def __str__(self):
|
345
|
-
return "Unserializable object"
|
346
|
-
|
347
|
-
obj = Unserializable()
|
348
|
-
result = safe_serialize(obj)
|
349
|
-
# The string is wrapped in quotes because it's serialized as a JSON string
|
350
|
-
assert result == '"Unserializable object"'
|
351
|
-
|
352
|
-
def test_serialization_error_handling(self):
|
353
|
-
"""Test handling of serialization errors."""
|
354
|
-
|
355
|
-
# Create an object that causes JSON serialization to fail
|
356
|
-
class BadObject:
|
357
|
-
def __init__(self):
|
358
|
-
self.recursive = None
|
359
|
-
|
360
|
-
def __getitem__(self, key):
|
361
|
-
# This will cause infinite recursion during JSON serialization
|
362
|
-
return self.recursive
|
363
|
-
|
364
|
-
def __str__(self):
|
365
|
-
return "BadObject representation"
|
366
|
-
|
367
|
-
bad_obj = BadObject()
|
368
|
-
bad_obj.recursive = bad_obj
|
369
|
-
|
370
|
-
result = safe_serialize(bad_obj)
|
371
|
-
assert result == '"BadObject representation"'
|
372
|
-
|
373
|
-
def test_value_error_handling(self):
|
374
|
-
"""Test handling of ValueError during JSON serialization."""
|
375
|
-
|
376
|
-
# Create an object that causes a ValueError during JSON serialization
|
377
|
-
class ValueErrorObject:
|
378
|
-
def to_json(self):
|
379
|
-
raise ValueError("Cannot serialize this object")
|
380
|
-
|
381
|
-
def __str__(self):
|
382
|
-
return "ValueErrorObject representation"
|
383
|
-
|
384
|
-
obj = ValueErrorObject()
|
385
|
-
result = safe_serialize(obj)
|
386
|
-
assert result == "ValueErrorObject representation"
|
387
|
-
|
388
|
-
|
389
|
-
class TestModelToDict:
|
390
|
-
def test_none_returns_empty_dict(self):
|
391
|
-
"""Test that None returns an empty dict."""
|
392
|
-
assert model_to_dict(None) == {}
|
393
|
-
|
394
|
-
def test_dict_returns_unchanged(self):
|
395
|
-
"""Test that a dict is returned unchanged."""
|
396
|
-
test_dict = {"key": "value"}
|
397
|
-
assert model_to_dict(test_dict) is test_dict
|
398
|
-
|
399
|
-
def test_pydantic_models(self):
|
400
|
-
"""Test conversion of Pydantic-like models to dicts."""
|
401
|
-
# V1 model with dict()
|
402
|
-
v1_model = PydanticV1Model(name="test", value=42)
|
403
|
-
assert model_to_dict(v1_model) == {"name": "test", "value": 42}
|
404
|
-
|
405
|
-
# V2 model with model_dump()
|
406
|
-
v2_model = PydanticV2Model(name="test", value=42)
|
407
|
-
assert model_to_dict(v2_model) == {"name": "test", "value": 42}
|
408
|
-
|
409
|
-
@pytest.mark.skip(reason="parse() method handling is currently commented out in the implementation")
|
410
|
-
def test_parse_method(self):
|
411
|
-
"""Test models with parse method."""
|
412
|
-
parse_model = ModelWithParse({"name": "test", "value": 42})
|
413
|
-
assert model_to_dict(parse_model) == {"name": "test", "value": 42}
|
414
|
-
|
415
|
-
def test_dict_fallback(self):
|
416
|
-
"""Test fallback to __dict__."""
|
417
|
-
simple_model = SimpleModel("test value")
|
418
|
-
assert model_to_dict(simple_model) == {"value": "test value"}
|
419
|
-
|
420
|
-
def test_dict_fallback_exception_handling(self):
|
421
|
-
"""Test exception handling in dict fallback."""
|
422
|
-
# Test with object that has no __dict__ attribute
|
423
|
-
model_without_dict = ModelWithoutDict("test value")
|
424
|
-
assert model_to_dict(model_without_dict) == {}
|
425
|
-
|
426
|
-
# Test with object that raises exception when accessing __dict__
|
427
|
-
class BadModel:
|
428
|
-
@property
|
429
|
-
def __dict__(self):
|
430
|
-
raise AttributeError("No dict for you!")
|
431
|
-
|
432
|
-
bad_model = BadModel()
|
433
|
-
assert model_to_dict(bad_model) == {}
|