From 60ddbea100da3255be75afe26c2b94323d632a2c Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Sat, 27 Jun 2026 15:21:14 +0200 Subject: [PATCH 1/3] fix: fall back when payload compression fails --- .../changesets/doughty-princess-loviatar.md | 5 +++++ posthog/request.py | 17 ++++++++------- posthog/test/test_request.py | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 .sampo/changesets/doughty-princess-loviatar.md diff --git a/.sampo/changesets/doughty-princess-loviatar.md b/.sampo/changesets/doughty-princess-loviatar.md new file mode 100644 index 00000000..c3c4a218 --- /dev/null +++ b/.sampo/changesets/doughty-princess-loviatar.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Fall back to uncompressed uploads when gzip compression fails diff --git a/posthog/request.py b/posthog/request.py index de16f33a..2215609b 100644 --- a/posthog/request.py +++ b/posthog/request.py @@ -240,13 +240,16 @@ def post( log.debug("making request: %s to url: %s", data, url) headers = {"Content-Type": "application/json", "User-Agent": USER_AGENT} if gzip: - headers["Content-Encoding"] = "gzip" - buf = BytesIO() - with GzipFile(fileobj=buf, mode="w") as gz: - # 'data' was produced by json.dumps(), - # whose default encoding is utf-8. - gz.write(cast(str, data).encode("utf-8")) - data = buf.getvalue() + try: + buf = BytesIO() + with GzipFile(fileobj=buf, mode="w") as gz: + # 'data' was produced by json.dumps(), + # whose default encoding is utf-8. + gz.write(cast(str, data).encode("utf-8")) + data = buf.getvalue() + headers["Content-Encoding"] = "gzip" + except OSError as exc: + log.warning("failed to gzip request body, sending uncompressed: %s", exc) res = (session or _get_session()).post( url, data=data, headers=headers, timeout=timeout diff --git a/posthog/test/test_request.py b/posthog/test/test_request.py index 4cae68f9..9d7d98c0 100644 --- a/posthog/test/test_request.py +++ b/posthog/test/test_request.py @@ -179,6 +179,27 @@ def test_post_sends_bytes_payload_with_gzip(self): self.assertIsInstance(data, bytes) self.assertEqual(headers["Content-Encoding"], "gzip") + def test_post_falls_back_to_uncompressed_payload_when_gzip_fails(self): + mock_response = requests.Response() + mock_response.status_code = 200 + mock_session = mock.MagicMock() + mock_session.post.return_value = mock_response + + with mock.patch.object(request_module, "GzipFile", side_effect=OSError("boom")): + request_module.post( + TEST_API_KEY, + host="https://test.posthog.com", + path="/batch/", + gzip=True, + session=mock_session, + batch=[], + ) + + data = mock_session.post.call_args.kwargs["data"] + headers = mock_session.post.call_args.kwargs["headers"] + self.assertIsInstance(data, str) + self.assertNotIn("Content-Encoding", headers) + def test_datetime_serialization(self): data = {"created": datetime(2012, 3, 4, 5, 6, 7, 891011)} result = json.dumps(data, cls=DatetimeSerializer) From b42076f3739b10f819476a163444c65c08ef18ba Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Sun, 28 Jun 2026 10:50:17 +0200 Subject: [PATCH 2/3] Address compression fallback review comments --- posthog/request.py | 2 +- posthog/test/test_request.py | 39 +++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/posthog/request.py b/posthog/request.py index 2215609b..18111894 100644 --- a/posthog/request.py +++ b/posthog/request.py @@ -248,7 +248,7 @@ def post( gz.write(cast(str, data).encode("utf-8")) data = buf.getvalue() headers["Content-Encoding"] = "gzip" - except OSError as exc: + except Exception as exc: log.warning("failed to gzip request body, sending uncompressed: %s", exc) res = (session or _get_session()).post( diff --git a/posthog/test/test_request.py b/posthog/test/test_request.py index 9d7d98c0..fb2c476e 100644 --- a/posthog/test/test_request.py +++ b/posthog/test/test_request.py @@ -1,5 +1,6 @@ import json import unittest +import zlib from datetime import date, datetime from unittest import mock @@ -180,25 +181,27 @@ def test_post_sends_bytes_payload_with_gzip(self): self.assertEqual(headers["Content-Encoding"], "gzip") def test_post_falls_back_to_uncompressed_payload_when_gzip_fails(self): - mock_response = requests.Response() - mock_response.status_code = 200 - mock_session = mock.MagicMock() - mock_session.post.return_value = mock_response - - with mock.patch.object(request_module, "GzipFile", side_effect=OSError("boom")): - request_module.post( - TEST_API_KEY, - host="https://test.posthog.com", - path="/batch/", - gzip=True, - session=mock_session, - batch=[], - ) + for compression_error in [OSError("boom"), zlib.error("boom")]: + with self.subTest(compression_error=type(compression_error)): + mock_response = requests.Response() + mock_response.status_code = 200 + mock_session = mock.MagicMock() + mock_session.post.return_value = mock_response + + with mock.patch.object(request_module, "GzipFile", side_effect=compression_error): + request_module.post( + TEST_API_KEY, + host="https://test.posthog.com", + path="/batch/", + gzip=True, + session=mock_session, + batch=[], + ) - data = mock_session.post.call_args.kwargs["data"] - headers = mock_session.post.call_args.kwargs["headers"] - self.assertIsInstance(data, str) - self.assertNotIn("Content-Encoding", headers) + data = mock_session.post.call_args.kwargs["data"] + headers = mock_session.post.call_args.kwargs["headers"] + self.assertIsInstance(data, str) + self.assertNotIn("Content-Encoding", headers) def test_datetime_serialization(self): data = {"created": datetime(2012, 3, 4, 5, 6, 7, 891011)} From 1705747d74a6e37f3ba14ee07dde28588e5bd427 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Sun, 28 Jun 2026 11:10:23 +0200 Subject: [PATCH 3/3] Format compression fallback test --- posthog/test/test_request.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/posthog/test/test_request.py b/posthog/test/test_request.py index fb2c476e..f1ab7032 100644 --- a/posthog/test/test_request.py +++ b/posthog/test/test_request.py @@ -188,7 +188,9 @@ def test_post_falls_back_to_uncompressed_payload_when_gzip_fails(self): mock_session = mock.MagicMock() mock_session.post.return_value = mock_response - with mock.patch.object(request_module, "GzipFile", side_effect=compression_error): + with mock.patch.object( + request_module, "GzipFile", side_effect=compression_error + ): request_module.post( TEST_API_KEY, host="https://test.posthog.com",