|
2 | 2 | # pyright: reportUnknownArgumentType=false |
3 | 3 | # pyright: reportPrivateUsage=false |
4 | 4 | import json |
| 5 | +import warnings |
5 | 6 | from typing import Any |
6 | 7 | from unittest import TestCase |
7 | 8 |
|
@@ -282,6 +283,58 @@ def test_deep_copy_numeric_and_special_keys(self): |
282 | 283 | self.assertTrue("(1, 2)" in result or "1, 2" in result) |
283 | 284 | self.assertIn("None", result) |
284 | 285 |
|
| 286 | + def test_model_dump_no_pydantic_serializer_warnings(self): |
| 287 | + """Test that model_dump() calls in _to_bt_safe don't emit Pydantic serializer warnings. |
| 288 | +
|
| 289 | + Regression test for https://github.com/braintrustdata/braintrust-sdk-python/issues/60 |
| 290 | + OpenAI's parsed response models (e.g. ParsedResponse[T]) emit |
| 291 | + PydanticSerializationUnexpectedValue warnings from model_dump(). |
| 292 | + Braintrust should suppress these warnings during serialization. |
| 293 | + """ |
| 294 | + try: |
| 295 | + from pydantic import BaseModel |
| 296 | + except ImportError: |
| 297 | + self.skipTest("Pydantic not available") |
| 298 | + |
| 299 | + class WarningModel(BaseModel): |
| 300 | + """A model whose model_dump() emits a UserWarning, simulating OpenAI parsed responses.""" |
| 301 | + |
| 302 | + name: str |
| 303 | + |
| 304 | + def model_dump(self, **kwargs): |
| 305 | + warnings.warn( |
| 306 | + "Pydantic serializer warnings:\n PydanticSerializationUnexpectedValue: Expected `none`", |
| 307 | + UserWarning, |
| 308 | + stacklevel=2, |
| 309 | + ) |
| 310 | + return {"name": self.name} |
| 311 | + |
| 312 | + obj = WarningModel(name="test") |
| 313 | + |
| 314 | + # _to_bt_safe (via bt_safe_deep_copy) should NOT propagate the warning |
| 315 | + with warnings.catch_warnings(record=True) as caught: |
| 316 | + warnings.simplefilter("always") |
| 317 | + result = bt_safe_deep_copy({"response": obj}) |
| 318 | + pydantic_warnings = [w for w in caught if "Pydantic serializer warnings" in str(w.message)] |
| 319 | + assert len(pydantic_warnings) == 0, ( |
| 320 | + f"Expected no Pydantic serializer warnings, but got {len(pydantic_warnings)}: " |
| 321 | + f"{[str(w.message) for w in pydantic_warnings]}" |
| 322 | + ) |
| 323 | + |
| 324 | + assert result["response"]["name"] == "test" |
| 325 | + |
| 326 | + # bt_dumps should also NOT propagate the warning |
| 327 | + with warnings.catch_warnings(record=True) as caught: |
| 328 | + warnings.simplefilter("always") |
| 329 | + json_str = bt_dumps(obj) |
| 330 | + pydantic_warnings = [w for w in caught if "Pydantic serializer warnings" in str(w.message)] |
| 331 | + assert len(pydantic_warnings) == 0, ( |
| 332 | + f"Expected no Pydantic serializer warnings, but got {len(pydantic_warnings)}: " |
| 333 | + f"{[str(w.message) for w in pydantic_warnings]}" |
| 334 | + ) |
| 335 | + |
| 336 | + assert "test" in json_str |
| 337 | + |
285 | 338 |
|
286 | 339 | @pytest.mark.vcr |
287 | 340 | def test_to_bt_safe_special_objects(): |
|
0 commit comments