From d86f0e1e35c8f83aa3ef64d2c4447f497baa89ba Mon Sep 17 00:00:00 2001 From: toby-m121 Date: Mon, 19 Oct 2026 22:12:23 +1100 Subject: [PATCH] fix: send non-finite float properties as null instead of invalid JSON Closes #1078 --- .sampo/changesets/non-finite-floats-null.md | 5 +++++ posthog/test/test_utils.py | 17 +++++++++++++++++ posthog/utils.py | 5 ++++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .sampo/changesets/non-finite-floats-null.md diff --git a/.sampo/changesets/non-finite-floats-null.md b/.sampo/changesets/non-finite-floats-null.md new file mode 100644 index 000000000..27df049f3 --- /dev/null +++ b/.sampo/changesets/non-finite-floats-null.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Non-finite float property values (`inf`, `-inf`, `nan`, including `Decimal("Infinity")`) are now sent as `null` instead of the invalid JSON tokens `Infinity`/`NaN`, so strict-parsing ingestion no longer rejects the whole batch. This matches posthog-js. diff --git a/posthog/test/test_utils.py b/posthog/test/test_utils.py index e655c8cd5..8440dcae3 100644 --- a/posthog/test/test_utils.py +++ b/posthog/test/test_utils.py @@ -250,6 +250,23 @@ def test_clean_fn(self): cleaned = utils.clean({"fn": lambda x: x, "number": 4}) assert cleaned == {"fn": None, "number": 4} + @parameterized.expand( + [ + ("inf", float("inf"), None), + ("negative_inf", float("-inf"), None), + ("nan", float("nan"), None), + ("decimal_inf", Decimal("Infinity"), None), + ("finite_float", 1.5, 1.5), + ("finite_decimal", Decimal("1.25"), 1.25), + ] + ) + def test_clean_non_finite_floats(self, _name, value, expected): + assert utils.clean(value) == expected + + def test_clean_non_finite_floats_nested(self): + cleaned = utils.clean({"a": [float("nan"), {"b": float("inf")}], "ok": 2.0}) + assert cleaned == {"a": [None, {"b": None}], "ok": 2.0} + @parameterized.expand( [ ("http://posthog.io/", "http://posthog.io"), diff --git a/posthog/utils.py b/posthog/utils.py index 05608983f..71fa2580f 100644 --- a/posthog/utils.py +++ b/posthog/utils.py @@ -1,5 +1,6 @@ import json import logging +import math import numbers import re import threading @@ -84,9 +85,11 @@ def remove_trailing_slash(host: str) -> str: def clean(item): if isinstance(item, Decimal): - return float(item) + return clean(float(item)) if isinstance(item, UUID): return str(item) + if isinstance(item, float) and not math.isfinite(item): + return None if isinstance(item, (str, bool, numbers.Number, datetime, date, type(None))): return item if isinstance(item, (set, list, tuple)):