Skip to content

Commit d16880f

Browse files
committed
Merge branch 'master' of github.com:PostHog/posthog-python into feat/langchain-spans
2 parents ccc63e3 + eb07aaf commit d16880f

File tree

4 files changed

+47
-6
lines changed

4 files changed

+47
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 3.11.0 - 2025-01-27
1+
## 3.11.0 - 2025-01-28
22

33
1. Add the `$ai_span` event to the LangChain callback handler to capture the input and output of intermediary chains.
4+
2. Fix serialiazation of Pydantic models in methods.
45

56
## 3.10.0 - 2025-01-24
67

posthog/test/test_utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import unittest
22
from datetime import date, datetime, timedelta
33
from decimal import Decimal
4+
from typing import Optional
45
from uuid import UUID
56

67
import six
78
from dateutil.tz import tzutc
9+
from pydantic import BaseModel
10+
from pydantic.v1 import BaseModel as BaseModelV1
811

912
from posthog import utils
1013

@@ -81,6 +84,32 @@ def test_remove_slash(self):
8184
self.assertEqual("http://posthog.io", utils.remove_trailing_slash("http://posthog.io/"))
8285
self.assertEqual("http://posthog.io", utils.remove_trailing_slash("http://posthog.io"))
8386

87+
def test_clean_pydantic(self):
88+
class ModelV2(BaseModel):
89+
foo: str
90+
bar: int
91+
baz: Optional[str] = None
92+
93+
class ModelV1(BaseModelV1):
94+
foo: int
95+
bar: str
96+
97+
class NestedModel(BaseModel):
98+
foo: ModelV2
99+
100+
self.assertEqual(utils.clean(ModelV2(foo="1", bar=2)), {"foo": "1", "bar": 2, "baz": None})
101+
self.assertEqual(utils.clean(ModelV1(foo=1, bar="2")), {"foo": 1, "bar": "2"})
102+
self.assertEqual(
103+
utils.clean(NestedModel(foo=ModelV2(foo="1", bar=2, baz="3"))), {"foo": {"foo": "1", "bar": 2, "baz": "3"}}
104+
)
105+
106+
class Dummy:
107+
def model_dump(self, required_param):
108+
pass
109+
110+
# Skips a class with a defined non-Pydantic `model_dump` method.
111+
self.assertEqual(utils.clean({"test": Dummy()}), {})
112+
84113

85114
class TestSizeLimitedDict(unittest.TestCase):
86115
def test_size_limited_dict(self):

posthog/utils.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,24 @@ def clean(item):
5151
return float(item)
5252
if isinstance(item, UUID):
5353
return str(item)
54-
elif isinstance(item, (six.string_types, bool, numbers.Number, datetime, date, type(None))):
54+
if isinstance(item, (six.string_types, bool, numbers.Number, datetime, date, type(None))):
5555
return item
56-
elif isinstance(item, (set, list, tuple)):
56+
if isinstance(item, (set, list, tuple)):
5757
return _clean_list(item)
58-
elif isinstance(item, dict):
58+
# Pydantic model
59+
try:
60+
# v2+
61+
if hasattr(item, "model_dump") and callable(item.model_dump):
62+
item = item.model_dump()
63+
# v1
64+
elif hasattr(item, "dict") and callable(item.dict):
65+
item = item.dict()
66+
except TypeError as e:
67+
log.debug(f"Could not serialize Pydantic-like model: {e}")
68+
pass
69+
if isinstance(item, dict):
5970
return _clean_dict(item)
60-
else:
61-
return _coerce_unicode(item)
71+
return _coerce_unicode(item)
6272

6373

6474
def _clean_list(list_):

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"langchain-community>=0.2.0",
4747
"langchain-openai>=0.2.0",
4848
"langchain-anthropic>=0.2.0",
49+
"pydantic",
4950
],
5051
"sentry": ["sentry-sdk", "django"],
5152
"langchain": ["langchain>=0.2.0"],

0 commit comments

Comments
 (0)