|
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | | - |
| 15 | +import pytest |
| 16 | +from pydantic import BaseModel |
16 | 17 | from opentelemetry.instrumentation.google_genai import dict_util |
17 | 18 |
|
18 | 19 |
|
| 20 | +class PydanticModel(BaseModel): |
| 21 | + """Used to verify handling of pydantic models in the flattener.""" |
| 22 | + str_value: str |
| 23 | + int_value: int |
| 24 | + |
| 25 | + |
| 26 | +class ModelDumpableNotPydantic: |
| 27 | + """Used to verify general handling of 'model_dump'.""" |
| 28 | + |
| 29 | + def __init__(self, dump_output): |
| 30 | + self._dump_output = dump_output |
| 31 | + |
| 32 | + def model_dump(self): |
| 33 | + return self._dump_output |
| 34 | + |
| 35 | + |
| 36 | +class NotJsonSerializable: |
| 37 | + |
| 38 | + def __init__(self): |
| 39 | + pass |
| 40 | + |
| 41 | + |
19 | 42 | def test_flatten_empty_dict(): |
20 | 43 | input_dict = {} |
21 | 44 | output_dict = dict_util.flatten_dict(input_dict) |
@@ -137,3 +160,65 @@ def summarize_int_list(key, value, **kwargs): |
137 | 160 | "some.deeply.nested.key": "9 items (total: 45, average: 5.0)", |
138 | 161 | "other": [1, 2, 3, 4, 5, 6, 7, 8, 9], |
139 | 162 | } |
| 163 | + |
| 164 | + |
| 165 | +def test_flatten_with_pydantic_model_value(): |
| 166 | + input_dict = { |
| 167 | + "foo": PydanticModel(str_value="bar", int_value=123), |
| 168 | + } |
| 169 | + |
| 170 | + output = dict_util.flatten_dict(input_dict) |
| 171 | + assert output == { |
| 172 | + "foo.str_value": "bar", |
| 173 | + "foo.int_value": 123, |
| 174 | + } |
| 175 | + |
| 176 | + |
| 177 | +def test_flatten_with_model_dumpable_value(): |
| 178 | + input_dict = { |
| 179 | + "foo": ModelDumpableNotPydantic({ |
| 180 | + "str_value": "bar", |
| 181 | + "int_value": 123, |
| 182 | + }), |
| 183 | + } |
| 184 | + |
| 185 | + output = dict_util.flatten_dict(input_dict) |
| 186 | + assert output == { |
| 187 | + "foo.str_value": "bar", |
| 188 | + "foo.int_value": 123, |
| 189 | + } |
| 190 | + |
| 191 | + |
| 192 | +def test_flatten_with_mixed_structures(): |
| 193 | + input_dict = { |
| 194 | + "foo": ModelDumpableNotPydantic({ |
| 195 | + "pydantic": PydanticModel(str_value="bar", int_value=123), |
| 196 | + }), |
| 197 | + } |
| 198 | + |
| 199 | + output = dict_util.flatten_dict(input_dict) |
| 200 | + assert output == { |
| 201 | + "foo.pydantic.str_value": "bar", |
| 202 | + "foo.pydantic.int_value": 123, |
| 203 | + } |
| 204 | + |
| 205 | + |
| 206 | +def test_flatten_with_complex_object_not_json_serializable(): |
| 207 | + with pytest.raises(ValueError): |
| 208 | + dict_util.flatten_dict({ |
| 209 | + "cannot_serialize_directly": NotJsonSerializable(), |
| 210 | + }) |
| 211 | + |
| 212 | + |
| 213 | +def test_flatten_with_complex_object_not_json_serializable_and_custom_flatten_func(): |
| 214 | + def flatten_not_json_serializable(key, value, **kwargs): |
| 215 | + assert isinstance(value, NotJsonSerializable) |
| 216 | + return "blah" |
| 217 | + output = dict_util.flatten_dict({ |
| 218 | + "cannot_serialize_directly": NotJsonSerializable(), |
| 219 | + }, flatten_functions={ |
| 220 | + "cannot_serialize_directly": flatten_not_json_serializable, |
| 221 | + }) |
| 222 | + assert output == { |
| 223 | + "cannot_serialize_directly": "blah", |
| 224 | + } |
0 commit comments