Skip to content

Commit 9caa73e

Browse files
committed
fix
1 parent 527a118 commit 9caa73e

File tree

2 files changed

+64
-55
lines changed

2 files changed

+64
-55
lines changed

posthog/test/test_utils.py

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ class TestUtils(unittest.TestCase):
2020
def test_timezone_utils(self):
2121
now = datetime.now()
2222
utcnow = datetime.now(tz=tzutc())
23-
self.assertTrue(utils.is_naive(now))
24-
self.assertFalse(utils.is_naive(utcnow))
23+
assert utils.is_naive(now) is True
24+
assert utils.is_naive(utcnow) is False
2525

2626
fixed = utils.guess_timezone(now)
27-
self.assertFalse(utils.is_naive(fixed))
27+
assert utils.is_naive(fixed) is False
2828

2929
shouldnt_be_edited = utils.guess_timezone(utcnow)
30-
self.assertEqual(utcnow, shouldnt_be_edited)
30+
assert utcnow == shouldnt_be_edited
3131

3232
def test_clean(self):
3333
simple = {
@@ -54,20 +54,20 @@ def test_clean(self):
5454
pre_clean_keys = combined.keys()
5555

5656
utils.clean(combined)
57-
self.assertEqual(combined.keys(), pre_clean_keys)
57+
assert combined.keys() == pre_clean_keys
5858

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

6565
def test_clean_with_dates(self):
6666
dict_with_dates = {
6767
"birthdate": date(1980, 1, 1),
6868
"registration": datetime.now(tz=tzutc()),
6969
}
70-
self.assertEqual(dict_with_dates, utils.clean(dict_with_dates))
70+
assert dict_with_dates == utils.clean(dict_with_dates)
7171

7272
def test_bytes(self):
7373
if six.PY3:
@@ -79,14 +79,11 @@ def test_bytes(self):
7979

8080
def test_clean_fn(self):
8181
cleaned = utils.clean({"fn": lambda x: x, "number": 4})
82-
self.assertEqual(cleaned["number"], 4)
83-
# TODO: fixme, different behavior on python 2 and 3
84-
if "fn" in cleaned:
85-
self.assertEqual(cleaned["fn"], None)
82+
assert cleaned == {"fn": None, "number": 4}
8683

8784
def test_remove_slash(self):
88-
self.assertEqual("http://posthog.io", utils.remove_trailing_slash("http://posthog.io/"))
89-
self.assertEqual("http://posthog.io", utils.remove_trailing_slash("http://posthog.io"))
85+
assert "http://posthog.io" == utils.remove_trailing_slash("http://posthog.io/")
86+
assert "http://posthog.io" == utils.remove_trailing_slash("http://posthog.io")
9087

9188
def test_clean_pydantic(self):
9289
class ModelV2(BaseModel):
@@ -101,19 +98,22 @@ class ModelV1(BaseModelV1):
10198
class NestedModel(BaseModel):
10299
foo: ModelV2
103100

104-
self.assertEqual(utils.clean(ModelV2(foo="1", bar=2)), {"foo": "1", "bar": 2, "baz": None})
105-
self.assertEqual(utils.clean(ModelV1(foo=1, bar="2")), {"foo": 1, "bar": "2"})
106-
self.assertEqual(
107-
utils.clean(NestedModel(foo=ModelV2(foo="1", bar=2, baz="3"))),
108-
{"foo": {"foo": "1", "bar": 2, "baz": "3"}},
109-
)
101+
assert utils.clean(ModelV2(foo="1", bar=2)) == {
102+
"foo": "1",
103+
"bar": 2,
104+
"baz": None,
105+
}
106+
assert utils.clean(ModelV1(foo=1, bar="2")) == {"foo": 1, "bar": "2"}
107+
assert utils.clean(NestedModel(foo=ModelV2(foo="1", bar=2, baz="3"))) == {
108+
"foo": {"foo": "1", "bar": 2, "baz": "3"}
109+
}
110110

111111
class Dummy:
112112
def model_dump(self, required_param):
113113
pass
114114

115115
# Skips a class with a defined non-Pydantic `model_dump` method.
116-
self.assertEqual(utils.clean({"test": Dummy()}), {})
116+
assert utils.clean({"test": Dummy()}) == {}
117117

118118
def test_clean_dataclass(self):
119119
@dataclass
@@ -130,31 +130,28 @@ class TestDataClass:
130130
bar: int
131131
nested: InnerDataClass
132132

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-
},
133+
assert utils.clean(
134+
TestDataClass(
135+
foo="1",
136+
bar=2,
137+
nested=InnerDataClass(
138+
inner_foo="3",
139+
inner_bar=4,
140+
inner_uuid=UUID("12345678123456781234567812345678"),
141+
inner_date=datetime(2025, 1, 1),
142+
),
143+
)
144+
) == {
145+
"foo": "1",
146+
"bar": 2,
147+
"nested": {
148+
"inner_foo": "3",
149+
"inner_bar": 4,
150+
"inner_uuid": "12345678-1234-5678-1234-567812345678",
151+
"inner_date": datetime(2025, 1, 1),
152+
"inner_optional": None,
156153
},
157-
)
154+
}
158155

159156

160157
class TestSizeLimitedDict(unittest.TestCase):
@@ -165,12 +162,12 @@ def test_size_limited_dict(self):
165162
for i in range(100):
166163
values[i] = i
167164

168-
self.assertEqual(values[i], i)
169-
self.assertEqual(len(values), i % size + 1)
165+
assert values[i] == i
166+
assert len(values) == i % size + 1
170167

171168
if i % size == 0:
172169
# old numbers should've been removed
173-
self.assertIsNone(values.get(i - 1))
174-
self.assertIsNone(values.get(i - 3))
175-
self.assertIsNone(values.get(i - 5))
176-
self.assertIsNone(values.get(i - 9))
170+
assert values.get(i - 1) is None
171+
assert values.get(i - 3) is None
172+
assert values.get(i - 5) is None
173+
assert values.get(i - 9) is None

posthog/utils.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,29 @@ def _coerce_unicode(cmplx: Any) -> Optional[str]:
106106
after many isinstance checks are carried out in `utils.clean`.
107107
When we supported Python 2 it was safe to call `decode` on a `str`
108108
but in Python 3 that will throw.
109-
So, we check if the input is bytes and only call `decode` in that case
109+
So, we check if the input is bytes and only call `decode` in that case.
110+
111+
Previously we would always call `decode` on the input
112+
That would throw an error.
113+
Then we would call `decode` on the stringified error
114+
That would throw an error.
115+
And then we would return `None`
116+
117+
To avoid a breaking change, we can maintain the behavior
118+
that anything which did not have `decode` in Python 2
119+
returns None.
110120
"""
121+
item = None
111122
try:
112123
if isinstance(cmplx, bytes):
113124
item = cmplx.decode("utf-8", "strict")
114-
else:
115-
item = str(cmplx)
125+
elif isinstance(cmplx, str):
126+
item = cmplx
116127
except Exception as exception:
117128
item = ":".join(map(str, exception.args))
118129
log.warning("Error decoding: %s", item)
119130
return None
131+
120132
return item
121133

122134

0 commit comments

Comments
 (0)