diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 46188fce5..ac3063237 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -13,7 +13,7 @@ from typing import Any from uuid import UUID -from pydantic import BaseModel +from pydantic import BaseModel, SecretBytes, SecretStr from langfuse.media import LangfuseMedia, LangfuseMediaReference @@ -52,6 +52,9 @@ def default(self, obj: Any) -> Any: def _default_inner(self, obj: Any) -> Any: try: + if isinstance(obj, (SecretStr, SecretBytes)): + return "" + if isinstance(obj, (datetime)): # Timezone-awareness check return serialize_datetime(obj) diff --git a/tests/unit/test_serializer.py b/tests/unit/test_serializer.py index ce5798f67..48e6fc292 100644 --- a/tests/unit/test_serializer.py +++ b/tests/unit/test_serializer.py @@ -7,7 +7,7 @@ from uuid import UUID import pytest -from pydantic import BaseModel +from pydantic import BaseModel, SecretBytes, SecretStr from langfuse._utils.serializer import ( EventSerializer, @@ -29,6 +29,11 @@ class TestBaseModel(BaseModel): field: str +class SecretBaseModel(BaseModel): + api_key: SecretStr + token: SecretBytes + + def test_datetime(): dt = datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) serializer = EventSerializer() @@ -71,6 +76,32 @@ def test_pydantic_model(): assert json.loads(serializer.encode(model)) == {"field": "test"} +@pytest.mark.parametrize( + "secret", + [ + SecretStr("not-a-real-api-key"), + SecretBytes(b"not-a-real-token"), + ], +) +def test_pydantic_secret(secret): + serializer = EventSerializer() + + assert serializer.encode(secret) == '""' + + +def test_pydantic_model_with_secrets(): + model = SecretBaseModel( + api_key=SecretStr("not-a-real-api-key"), + token=SecretBytes(b"not-a-real-token"), + ) + serializer = EventSerializer() + + assert json.loads(serializer.encode(model)) == { + "api_key": "", + "token": "", + } + + def test_langfuse_media_reference_serializes_to_reference_string(): # Resolved references must round-trip back to their original reference string # rather than falling through to asdict() and emitting an opaque dict.