Skip to content

Commit 2779ad1

Browse files
authored
feat: Support serializing dataclasses (#206)
* Support serializing dataclasses * Update version * Run black * Fix for Python 3.9
1 parent 5a4167d commit 2779ad1

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
## 3.21.0 – 2025-03-17
12

3+
1. Support serializing dataclasses.
24

35
## 3.20.0 – 2025-03-13
46

posthog/test/test_utils.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from dataclasses import dataclass
23
from datetime import date, datetime, timedelta
34
from decimal import Decimal
45
from typing import Optional
@@ -56,7 +57,10 @@ def test_clean(self):
5657
self.assertEqual(combined.keys(), pre_clean_keys)
5758

5859
# test UUID separately, as the UUID object doesn't equal its string representation according to Python
59-
self.assertEqual(utils.clean(UUID("12345678123456781234567812345678")), "12345678-1234-5678-1234-567812345678")
60+
self.assertEqual(
61+
utils.clean(UUID("12345678123456781234567812345678")),
62+
"12345678-1234-5678-1234-567812345678",
63+
)
6064

6165
def test_clean_with_dates(self):
6266
dict_with_dates = {
@@ -100,7 +104,8 @@ class NestedModel(BaseModel):
100104
self.assertEqual(utils.clean(ModelV2(foo="1", bar=2)), {"foo": "1", "bar": 2, "baz": None})
101105
self.assertEqual(utils.clean(ModelV1(foo=1, bar="2")), {"foo": 1, "bar": "2"})
102106
self.assertEqual(
103-
utils.clean(NestedModel(foo=ModelV2(foo="1", bar=2, baz="3"))), {"foo": {"foo": "1", "bar": 2, "baz": "3"}}
107+
utils.clean(NestedModel(foo=ModelV2(foo="1", bar=2, baz="3"))),
108+
{"foo": {"foo": "1", "bar": 2, "baz": "3"}},
104109
)
105110

106111
class Dummy:
@@ -110,6 +115,47 @@ def model_dump(self, required_param):
110115
# Skips a class with a defined non-Pydantic `model_dump` method.
111116
self.assertEqual(utils.clean({"test": Dummy()}), {})
112117

118+
def test_clean_dataclass(self):
119+
@dataclass
120+
class InnerDataClass:
121+
inner_foo: str
122+
inner_bar: int
123+
inner_uuid: UUID
124+
inner_date: datetime
125+
inner_optional: Optional[str] = None
126+
127+
@dataclass
128+
class TestDataClass:
129+
foo: str
130+
bar: int
131+
nested: InnerDataClass
132+
133+
self.assertEqual(
134+
utils.clean(
135+
TestDataClass(
136+
foo="1",
137+
bar=2,
138+
nested=InnerDataClass(
139+
inner_foo="3",
140+
inner_bar=4,
141+
inner_uuid=UUID("12345678123456781234567812345678"),
142+
inner_date=datetime(2025, 1, 1),
143+
),
144+
)
145+
),
146+
{
147+
"foo": "1",
148+
"bar": 2,
149+
"nested": {
150+
"inner_foo": "3",
151+
"inner_bar": 4,
152+
"inner_uuid": "12345678-1234-5678-1234-567812345678",
153+
"inner_date": datetime(2025, 1, 1),
154+
"inner_optional": None,
155+
},
156+
},
157+
)
158+
113159

114160
class TestSizeLimitedDict(unittest.TestCase):
115161
def test_size_limited_dict(self):

posthog/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import numbers
33
import re
44
from collections import defaultdict
5+
from dataclasses import asdict, is_dataclass
56
from datetime import date, datetime, timezone
67
from decimal import Decimal
78
from uuid import UUID
@@ -68,6 +69,8 @@ def clean(item):
6869
pass
6970
if isinstance(item, dict):
7071
return _clean_dict(item)
72+
if is_dataclass(item) and not isinstance(item, type):
73+
return _clean_dataclass(item)
7174
return _coerce_unicode(item)
7275

7376

@@ -90,6 +93,12 @@ def _clean_dict(dict_):
9093
return data
9194

9295

96+
def _clean_dataclass(dataclass_):
97+
data = asdict(dataclass_)
98+
data = _clean_dict(data)
99+
return data
100+
101+
93102
def _coerce_unicode(cmplx):
94103
try:
95104
item = cmplx.decode("utf-8", "strict")

posthog/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = "3.20.0"
1+
VERSION = "3.21.0"
22

33
if __name__ == "__main__":
44
print(VERSION, end="") # noqa: T201

0 commit comments

Comments
 (0)