From 7c9dde915c40f4da88a144645bd2142437da5356 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Mon, 29 Jun 2026 09:53:51 +0100 Subject: [PATCH 1/5] Ensure proxies and timeout options are handled correctly --- customerio/analytics/request.py | 5 ++-- customerio/analytics/test/client.py | 16 ++++++++--- customerio/analytics/test/request.py | 40 ++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/customerio/analytics/request.py b/customerio/analytics/request.py index 5b0eb99a..cac8cc77 100644 --- a/customerio/analytics/request.py +++ b/customerio/analytics/request.py @@ -39,14 +39,13 @@ def post(write_key, host=None, gzip=False, timeout=15, proxies=None, **kwargs): "data": data, "auth": auth, "headers": headers, - "timeout": 15, + "timeout": timeout, } if proxies: kwargs['proxies'] = proxies - res = _session.post(url, data=data, auth=auth, - headers=headers, timeout=timeout) + res = _session.post(url, **kwargs) if res.status_code == 200: log.debug('data uploaded successfully') diff --git a/customerio/analytics/test/client.py b/customerio/analytics/test/client.py index 5fc58350..b0b5793b 100644 --- a/customerio/analytics/test/client.py +++ b/customerio/analytics/test/client.py @@ -343,6 +343,16 @@ def test_default_timeout_15(self): self.assertEqual(consumer.timeout, 15) def test_proxies(self): - client = Client('testsecret', proxies='203.243.63.16:80') - success, msg = client.identify('userId', {'trait': 'value'}) - self.assertTrue(success) + proxies = { + 'https': 'http://proxy.example.com:8080', + 'http': 'http://proxy.example.com:8080', + } + client = Client('testsecret', sync_mode=True, proxies=proxies) + mock_response = mock.Mock() + mock_response.status_code = 200 + with mock.patch('customerio.analytics.request._session.post', + return_value=mock_response) as mock_post: + client.identify('userId', {'trait': 'value'}) + mock_post.assert_called_once() + _, call_kwargs = mock_post.call_args + self.assertEqual(call_kwargs['proxies'], proxies) diff --git a/customerio/analytics/test/request.py b/customerio/analytics/test/request.py index 003caa1e..ecc25091 100644 --- a/customerio/analytics/test/request.py +++ b/customerio/analytics/test/request.py @@ -1,5 +1,6 @@ from datetime import datetime, date import unittest +import unittest.mock as mock import json import requests @@ -52,11 +53,34 @@ def test_should_timeout(self): 'type': 'track' }], timeout=0.0001) - def test_proxies(self): - res = post('testsecret', batch=[{ - 'userId': 'userId', - 'event': 'python event', - 'type': 'track', - 'proxies': '203.243.63.16:80' - }]) - self.assertEqual(res.status_code, 200) + def test_proxies_passed_to_session(self): + proxies = { + 'https': 'http://proxy.example.com:8080', + 'http': 'http://proxy.example.com:8080', + } + mock_response = mock.Mock() + mock_response.status_code = 200 + with mock.patch('customerio.analytics.request._session.post', + return_value=mock_response) as mock_post: + post('testsecret', proxies=proxies, batch=[{ + 'userId': 'userId', + 'event': 'python event', + 'type': 'track', + }]) + mock_post.assert_called_once() + _, call_kwargs = mock_post.call_args + self.assertEqual(call_kwargs['proxies'], proxies) + + def test_no_proxies_by_default(self): + mock_response = mock.Mock() + mock_response.status_code = 200 + with mock.patch('customerio.analytics.request._session.post', + return_value=mock_response) as mock_post: + post('testsecret', batch=[{ + 'userId': 'userId', + 'event': 'python event', + 'type': 'track', + }]) + mock_post.assert_called_once() + _, call_kwargs = mock_post.call_args + self.assertNotIn('proxies', call_kwargs) From 576cf40137d51ab2a5e7e41be0e744c44f93e314 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Mon, 29 Jun 2026 10:02:48 +0100 Subject: [PATCH 2/5] Hints to AI to stop opening PRs in upstream --- AGENTS.md | 13 +++++++++++++ CLAUDE.md | 1 + 2 files changed, 14 insertions(+) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..fe88bd7c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +# AGENTS.md + +## Pull Requests + +PRs MUST be opened against **customerio/cdp-analytics-python** (branch: `main`). + +Never open PRs against segmentio/analytics-python — this repo is a fork and we do not contribute upstream. + +When creating a PR, always use: `gh pr create --repo customerio/cdp-analytics-python --base main` + +## Tests + +Run tests with: `make test` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000..47dc3e3d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 0917e8744d627015210941a140057cd7dd664148 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Mon, 29 Jun 2026 10:11:20 +0100 Subject: [PATCH 3/5] Verify upload_interval option for Client --- customerio/analytics/test/client.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/customerio/analytics/test/client.py b/customerio/analytics/test/client.py index b0b5793b..d663fc26 100644 --- a/customerio/analytics/test/client.py +++ b/customerio/analytics/test/client.py @@ -318,7 +318,7 @@ def test_gzip(self): def test_user_defined_upload_size(self): client = Client('testsecret', on_error=self.fail, - upload_size=10, upload_interval=3) + upload_size=10, upload_interval=0.3) def mock_post_fn(*args, **kwargs): self.assertEqual(len(kwargs['batch']), 10) @@ -332,6 +332,17 @@ def mock_post_fn(*args, **kwargs): time.sleep(1) self.assertEqual(mock_post.call_count, 2) + def test_user_defined_upload_interval(self): + upload_interval = 0.3 + client = Client('testsecret', on_error=self.fail, + upload_size=100, upload_interval=upload_interval) + + with mock.patch('customerio.analytics.consumer.post') as mock_post: + for _ in range(3): + client.identify('userId', {'trait': 'value'}) + time.sleep(upload_interval * 1.1) + self.assertEqual(mock_post.call_count, 3) + def test_user_defined_timeout(self): client = Client('testsecret', timeout=10) for consumer in client.consumers: From b086a37000e8a7a939faa95ebb6695fb7a9f9b82 Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Mon, 29 Jun 2026 10:23:13 +0100 Subject: [PATCH 4/5] More test coverage for proxies --- customerio/analytics/test/consumer.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/customerio/analytics/test/consumer.py b/customerio/analytics/test/consumer.py index d10ded2e..d6c5f5f1 100644 --- a/customerio/analytics/test/consumer.py +++ b/customerio/analytics/test/consumer.py @@ -198,12 +198,10 @@ def mock_post_fn(_, data, **kwargs): q.join() self.assertEqual(mock_post.call_count, 2) - @classmethod - def test_proxies(cls): - consumer = Consumer(None, 'testsecret', proxies='203.243.63.16:80') - track = { - 'type': 'track', - 'event': 'python event', - 'userId': 'userId' + def test_proxies(self): + proxies = { + 'http': 'http://192.0.2.1:80', + 'https': 'http://192.0.2.1:80', } - consumer.request([track]) + consumer = Consumer(None, 'testsecret', proxies=proxies) + self.assertEqual(consumer.proxies, proxies) From 44600a0251c9fa4897a767ab38c400f98c74541b Mon Sep 17 00:00:00 2001 From: Richard Dawe Date: Tue, 30 Jun 2026 09:28:31 +0100 Subject: [PATCH 5/5] Review feedback --- customerio/analytics/request.py | 2 +- customerio/analytics/test/client.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/customerio/analytics/request.py b/customerio/analytics/request.py index cac8cc77..8573fe85 100644 --- a/customerio/analytics/request.py +++ b/customerio/analytics/request.py @@ -42,7 +42,7 @@ def post(write_key, host=None, gzip=False, timeout=15, proxies=None, **kwargs): "timeout": timeout, } - if proxies: + if proxies is not None: kwargs['proxies'] = proxies res = _session.post(url, **kwargs) diff --git a/customerio/analytics/test/client.py b/customerio/analytics/test/client.py index d663fc26..4c10be30 100644 --- a/customerio/analytics/test/client.py +++ b/customerio/analytics/test/client.py @@ -324,7 +324,8 @@ def mock_post_fn(*args, **kwargs): self.assertEqual(len(kwargs['batch']), 10) # the post function should be called 2 times, with a batch size of 10 - # each time. + # each time. 0.3s upload_interval ensures both batches are sent + # during 1 second sleep. with mock.patch('customerio.analytics.consumer.post', side_effect=mock_post_fn) \ as mock_post: for _ in range(20):