Skip to content

Commit a1583f6

Browse files
authored
chore(flags): latest version of posthog-python now uses /flags by default, except for a few exceptions (#222)
* wahahaha * fix tests * whoops don't forget to roll it out
1 parent dfa7f70 commit a1583f6

File tree

5 files changed

+228
-235
lines changed

5 files changed

+228
-235
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.25.0 – 2025-04-15
2+
3+
1. Roll out new `/flags` endpoint to 100% of `/decide` traffic, excluding the top 10 customers.
4+
15
## 3.24.3 – 2025-04-15
26

37
1. Fix hash inclusion/exclusion for flag rollout

posthog/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
MAX_DICT_SIZE = 50_000
5454

5555
# TODO: Get rid of these when you're done rolling out `/flags` to all customers
56-
ROLLOUT_PERCENTAGE = 0.1
56+
ROLLOUT_PERCENTAGE = 1
5757
INCLUDED_HASHES = set({"bc94e67150c97dbcbf52549d50a7b80814841dbf"}) # this is PostHog's API key
5858
# Explicitly excluding all the API tokens associated with the top 10 customers; we'll get to them soon, but don't want to rollout to them just yet
5959
EXCLUDED_HASHES = set(

posthog/test/test_client.py

Lines changed: 55 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,9 @@ def test_capture_exception_logs_when_enabled(self):
271271
self.assertEqual(logs.output[0], "ERROR:posthog:test exception\nNoneType: None")
272272
self.assertEqual(getattr(logs.records[0], "path"), "one/two/three")
273273

274-
@mock.patch("posthog.client.decide")
275-
def test_basic_capture_with_feature_flags(self, patch_decide):
276-
patch_decide.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
274+
@mock.patch("posthog.client.flags")
275+
def test_basic_capture_with_feature_flags(self, patch_flags):
276+
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
277277

278278
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, personal_api_key=FAKE_TEST_API_KEY)
279279
success, msg = client.capture("distinct_id", "python test event", send_feature_flags=True)
@@ -290,11 +290,11 @@ def test_basic_capture_with_feature_flags(self, patch_decide):
290290
self.assertEqual(msg["properties"]["$feature/beta-feature"], "random-variant")
291291
self.assertEqual(msg["properties"]["$active_feature_flags"], ["beta-feature"])
292292

293-
self.assertEqual(patch_decide.call_count, 1)
293+
self.assertEqual(patch_flags.call_count, 1)
294294

295-
@mock.patch("posthog.client.decide")
296-
def test_basic_capture_with_locally_evaluated_feature_flags(self, patch_decide):
297-
patch_decide.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
295+
@mock.patch("posthog.client.flags")
296+
def test_basic_capture_with_locally_evaluated_feature_flags(self, patch_flags):
297+
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
298298
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, personal_api_key=FAKE_TEST_API_KEY)
299299

300300
multivariate_flag = {
@@ -380,7 +380,7 @@ def test_basic_capture_with_locally_evaluated_feature_flags(self, patch_decide):
380380
self.assertEqual(msg["properties"]["$active_feature_flags"], ["beta-feature-local"])
381381
assert "$feature/beta-feature" not in msg["properties"]
382382

383-
self.assertEqual(patch_decide.call_count, 0)
383+
self.assertEqual(patch_flags.call_count, 0)
384384

385385
# test that flags are not evaluated without local evaluation
386386
client.feature_flags = []
@@ -412,9 +412,9 @@ def test_load_feature_flags_quota_limited(self, patch_get):
412412
self.assertEqual(client.cohorts, {})
413413
self.assertIn("PostHog feature flags quota limited", logs.output[0])
414414

415-
@mock.patch("posthog.client.decide")
416-
def test_dont_override_capture_with_local_flags(self, patch_decide):
417-
patch_decide.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
415+
@mock.patch("posthog.client.flags")
416+
def test_dont_override_capture_with_local_flags(self, patch_flags):
417+
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
418418
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, personal_api_key=FAKE_TEST_API_KEY)
419419

420420
multivariate_flag = {
@@ -487,11 +487,11 @@ def test_dont_override_capture_with_local_flags(self, patch_decide):
487487
assert "$feature/beta-feature" not in msg["properties"]
488488
assert "$feature/person-flag" not in msg["properties"]
489489

490-
self.assertEqual(patch_decide.call_count, 0)
490+
self.assertEqual(patch_flags.call_count, 0)
491491

492-
@mock.patch("posthog.client.decide")
493-
def test_basic_capture_with_feature_flags_returns_active_only(self, patch_decide):
494-
patch_decide.return_value = {
492+
@mock.patch("posthog.client.flags")
493+
def test_basic_capture_with_feature_flags_returns_active_only(self, patch_flags):
494+
patch_flags.return_value = {
495495
"featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False}
496496
}
497497

@@ -512,8 +512,8 @@ def test_basic_capture_with_feature_flags_returns_active_only(self, patch_decide
512512
self.assertEqual(msg["properties"]["$feature/alpha-feature"], True)
513513
self.assertEqual(msg["properties"]["$active_feature_flags"], ["beta-feature", "alpha-feature"])
514514

515-
self.assertEqual(patch_decide.call_count, 1)
516-
patch_decide.assert_called_with(
515+
self.assertEqual(patch_flags.call_count, 1)
516+
patch_flags.assert_called_with(
517517
"random_key",
518518
"https://us.i.posthog.com",
519519
timeout=3,
@@ -524,9 +524,9 @@ def test_basic_capture_with_feature_flags_returns_active_only(self, patch_decide
524524
disable_geoip=True,
525525
)
526526

527-
@mock.patch("posthog.client.decide")
528-
def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(self, patch_decide):
529-
patch_decide.return_value = {
527+
@mock.patch("posthog.client.flags")
528+
def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(self, patch_flags):
529+
patch_flags.return_value = {
530530
"featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False}
531531
}
532532

@@ -554,8 +554,8 @@ def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(se
554554
self.assertEqual(msg["properties"]["$feature/alpha-feature"], True)
555555
self.assertEqual(msg["properties"]["$active_feature_flags"], ["beta-feature", "alpha-feature"])
556556

557-
self.assertEqual(patch_decide.call_count, 1)
558-
patch_decide.assert_called_with(
557+
self.assertEqual(patch_flags.call_count, 1)
558+
patch_flags.assert_called_with(
559559
"random_key",
560560
"https://us.i.posthog.com",
561561
timeout=12,
@@ -566,9 +566,9 @@ def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(se
566566
disable_geoip=False,
567567
)
568568

569-
@mock.patch("posthog.client.decide")
570-
def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(self, patch_decide):
571-
patch_decide.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
569+
@mock.patch("posthog.client.flags")
570+
def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(self, patch_flags):
571+
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
572572

573573
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, personal_api_key=FAKE_TEST_API_KEY)
574574
success, msg = client.capture("distinct_id", "python test event", send_feature_flags=False)
@@ -585,7 +585,7 @@ def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(self, pa
585585
self.assertTrue("$feature/beta-feature" not in msg["properties"])
586586
self.assertTrue("$active_feature_flags" not in msg["properties"])
587587

588-
self.assertEqual(patch_decide.call_count, 0)
588+
self.assertEqual(patch_flags.call_count, 0)
589589

590590
def test_stringifies_distinct_id(self):
591591
# A large number that loses precision in node:
@@ -942,29 +942,29 @@ def test_disabled(self):
942942

943943
self.assertEqual(msg, "disabled")
944944

945-
@mock.patch("posthog.client.decide")
946-
def test_disabled_with_feature_flags(self, patch_decide):
945+
@mock.patch("posthog.client.flags")
946+
def test_disabled_with_feature_flags(self, patch_flags):
947947
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disabled=True)
948948

949949
response = client.get_feature_flag("beta-feature", "12345")
950950
self.assertIsNone(response)
951-
patch_decide.assert_not_called()
951+
patch_flags.assert_not_called()
952952

953953
response = client.feature_enabled("beta-feature", "12345")
954954
self.assertIsNone(response)
955-
patch_decide.assert_not_called()
955+
patch_flags.assert_not_called()
956956

957957
response = client.get_all_flags("12345")
958958
self.assertIsNone(response)
959-
patch_decide.assert_not_called()
959+
patch_flags.assert_not_called()
960960

961961
response = client.get_feature_flag_payload("key", "12345")
962962
self.assertIsNone(response)
963-
patch_decide.assert_not_called()
963+
patch_flags.assert_not_called()
964964

965965
response = client.get_all_flags_and_payloads("12345")
966966
self.assertEqual(response, {"featureFlags": None, "featureFlagPayloads": None})
967-
patch_decide.assert_not_called()
967+
patch_flags.assert_not_called()
968968

969969
# no capture calls
970970
self.assertTrue(client.queue.empty())
@@ -1012,14 +1012,14 @@ def test_disable_geoip_method_overrides_init_on_events(self):
10121012
client.flush()
10131013
self.assertTrue("$geoip_disable" not in msg["properties"])
10141014

1015-
@mock.patch("posthog.client.decide")
1016-
def test_disable_geoip_default_on_decide(self, patch_decide):
1017-
patch_decide.return_value = {
1015+
@mock.patch("posthog.client.flags")
1016+
def test_disable_geoip_default_on_decide(self, patch_flags):
1017+
patch_flags.return_value = {
10181018
"featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False}
10191019
}
10201020
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disable_geoip=False)
10211021
client.get_feature_flag("random_key", "some_id", disable_geoip=True)
1022-
patch_decide.assert_called_with(
1022+
patch_flags.assert_called_with(
10231023
"random_key",
10241024
"https://us.i.posthog.com",
10251025
timeout=3,
@@ -1029,9 +1029,9 @@ def test_disable_geoip_default_on_decide(self, patch_decide):
10291029
group_properties={},
10301030
disable_geoip=True,
10311031
)
1032-
patch_decide.reset_mock()
1032+
patch_flags.reset_mock()
10331033
client.feature_enabled("random_key", "feature_enabled_distinct_id", disable_geoip=True)
1034-
patch_decide.assert_called_with(
1034+
patch_flags.assert_called_with(
10351035
"random_key",
10361036
"https://us.i.posthog.com",
10371037
timeout=3,
@@ -1041,9 +1041,9 @@ def test_disable_geoip_default_on_decide(self, patch_decide):
10411041
group_properties={},
10421042
disable_geoip=True,
10431043
)
1044-
patch_decide.reset_mock()
1044+
patch_flags.reset_mock()
10451045
client.get_all_flags_and_payloads("all_flags_payloads_id")
1046-
patch_decide.assert_called_with(
1046+
patch_flags.assert_called_with(
10471047
"random_key",
10481048
"https://us.i.posthog.com",
10491049
timeout=3,
@@ -1066,9 +1066,9 @@ def raise_effect():
10661066

10671067
self.assertFalse(client.feature_enabled("example", "distinct_id"))
10681068

1069-
@mock.patch("posthog.client.decide")
1070-
def test_default_properties_get_added_properly(self, patch_decide):
1071-
patch_decide.return_value = {
1069+
@mock.patch("posthog.client.flags")
1070+
def test_default_properties_get_added_properly(self, patch_flags):
1071+
patch_flags.return_value = {
10721072
"featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False}
10731073
}
10741074
client = Client(FAKE_TEST_API_KEY, host="http://app2.posthog.com", on_error=self.set_fail, disable_geoip=False)
@@ -1079,7 +1079,7 @@ def test_default_properties_get_added_properly(self, patch_decide):
10791079
person_properties={"x1": "y1"},
10801080
group_properties={"company": {"x": "y"}},
10811081
)
1082-
patch_decide.assert_called_with(
1082+
patch_flags.assert_called_with(
10831083
"random_key",
10841084
"http://app2.posthog.com",
10851085
timeout=3,
@@ -1093,7 +1093,7 @@ def test_default_properties_get_added_properly(self, patch_decide):
10931093
disable_geoip=False,
10941094
)
10951095

1096-
patch_decide.reset_mock()
1096+
patch_flags.reset_mock()
10971097
client.get_feature_flag(
10981098
"random_key",
10991099
"some_id",
@@ -1105,7 +1105,7 @@ def test_default_properties_get_added_properly(self, patch_decide):
11051105
}
11061106
},
11071107
)
1108-
patch_decide.assert_called_with(
1108+
patch_flags.assert_called_with(
11091109
"random_key",
11101110
"http://app2.posthog.com",
11111111
timeout=3,
@@ -1119,10 +1119,10 @@ def test_default_properties_get_added_properly(self, patch_decide):
11191119
disable_geoip=False,
11201120
)
11211121

1122-
patch_decide.reset_mock()
1122+
patch_flags.reset_mock()
11231123
# test nones
11241124
client.get_all_flags_and_payloads("some_id", groups={}, person_properties=None, group_properties=None)
1125-
patch_decide.assert_called_with(
1125+
patch_flags.assert_called_with(
11261126
"random_key",
11271127
"http://app2.posthog.com",
11281128
timeout=3,
@@ -1222,9 +1222,9 @@ def test_mock_system_context(
12221222

12231223
assert context == expected_context
12241224

1225-
@mock.patch("posthog.client.decide")
1226-
def test_get_decide_returns_normalized_decide_response(self, patch_decide):
1227-
patch_decide.return_value = {
1225+
@mock.patch("posthog.client.flags")
1226+
def test_get_decide_returns_normalized_decide_response(self, patch_flags):
1227+
patch_flags.return_value = {
12281228
"featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False},
12291229
"featureFlagPayloads": {"beta-feature": '{"some": "data"}'},
12301230
"errorsWhileComputingFlags": False,
@@ -1272,9 +1272,9 @@ def test_get_decide_returns_normalized_decide_response(self, patch_decide):
12721272
"requestId": "test-id",
12731273
}
12741274

1275-
@mock.patch("posthog.client.flags")
12761275
@mock.patch("posthog.client.decide")
1277-
def test_get_flags_decision_rollout(self, patch_decide, patch_flags):
1276+
@mock.patch("posthog.client.flags")
1277+
def test_get_flags_decision_rollout(self, patch_flags, patch_decide):
12781278
# Set up mock responses
12791279
decide_response = {
12801280
"featureFlags": {"flag1": True},
@@ -1291,22 +1291,11 @@ def test_get_flags_decision_rollout(self, patch_decide, patch_flags):
12911291

12921292
client = Client(FAKE_TEST_API_KEY)
12931293

1294-
# Test 0% rollout - should use decide
1295-
with mock.patch("posthog.client.is_token_in_rollout", return_value=False) as mock_rollout:
1296-
client.get_flags_decision("distinct_id")
1297-
mock_rollout.assert_called_with(
1298-
FAKE_TEST_API_KEY, 0.1, included_hashes=INCLUDED_HASHES, excluded_hashes=EXCLUDED_HASHES
1299-
)
1300-
patch_decide.assert_called_once()
1301-
patch_flags.assert_not_called()
1302-
patch_decide.reset_mock()
1303-
patch_flags.reset_mock()
1304-
13051294
# Test 100% rollout - should use flags
13061295
with mock.patch("posthog.client.is_token_in_rollout", return_value=True) as mock_rollout:
13071296
client.get_flags_decision("distinct_id")
13081297
mock_rollout.assert_called_with(
1309-
FAKE_TEST_API_KEY, 0.1, included_hashes=INCLUDED_HASHES, excluded_hashes=EXCLUDED_HASHES
1298+
FAKE_TEST_API_KEY, 1, included_hashes=INCLUDED_HASHES, excluded_hashes=EXCLUDED_HASHES
13101299
)
13111300
patch_flags.assert_called_once()
13121301
patch_decide.assert_not_called()

0 commit comments

Comments
 (0)