From 617bb53501972d9a5085373fd0654e5b7af596c8 Mon Sep 17 00:00:00 2001 From: Li Yi Yu Date: Mon, 17 Apr 2023 07:06:57 -0400 Subject: [PATCH] Disable geoip for capture and decide calls by default (#98) Co-authored-by: Neil Kakkar --- CHANGELOG.md | 19 +++++ posthog/__init__.py | 24 ++++++ posthog/client.py | 117 ++++++++++++++++++++------ posthog/test/test_client.py | 130 ++++++++++++++++++++++++++++- posthog/test/test_feature_flags.py | 42 ++++++++++ posthog/version.py | 2 +- 6 files changed, 305 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d81b45..2410c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 3.0.0 - 2023-04-14 + +Breaking change: + +All events by default now send the `$geoip_disable` property to disable geoip lookup in app. This is because usually we don't +want to update person properties to take the server's location. + +The same now happens for feature flag requests, where we discard the IP address of the server for matching on geoip properties like city, country, continent. + +To restore previous behaviour, you can set the default to False like so: + +```python +posthog.disable_geoip = False + +# // and if using client instantiation: +posthog = Posthog('api_key', disable_geoip=False) + +``` + ## 2.5.0 - 2023-04-10 1. Add option for instantiating separate client object diff --git a/posthog/__init__.py b/posthog/__init__.py index 4963e57..8812101 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -17,6 +17,7 @@ personal_api_key = None # type: str project_api_key = None # type: str poll_interval = 30 # type: int +disable_geoip = True # type: bool default_client = None @@ -30,6 +31,7 @@ def capture( uuid=None, # type: Optional[str] groups=None, # type: Optional[Dict] send_feature_flags=False, + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> None """ @@ -62,6 +64,7 @@ def capture( uuid=uuid, groups=groups, send_feature_flags=send_feature_flags, + disable_geoip=disable_geoip, ) @@ -71,6 +74,7 @@ def identify( context=None, # type: Optional[Dict] timestamp=None, # type: Optional[datetime.datetime] uuid=None, # type: Optional[str] + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> None """ @@ -95,6 +99,7 @@ def identify( context=context, timestamp=timestamp, uuid=uuid, + disable_geoip=disable_geoip, ) @@ -104,6 +109,7 @@ def set( context=None, # type: Optional[Dict] timestamp=None, # type: Optional[datetime.datetime] uuid=None, # type: Optional[str] + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> None """ @@ -128,6 +134,7 @@ def set( context=context, timestamp=timestamp, uuid=uuid, + disable_geoip=disable_geoip, ) @@ -137,6 +144,7 @@ def set_once( context=None, # type: Optional[Dict] timestamp=None, # type: Optional[datetime.datetime] uuid=None, # type: Optional[str] + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> None """ @@ -161,6 +169,7 @@ def set_once( context=context, timestamp=timestamp, uuid=uuid, + disable_geoip=disable_geoip, ) @@ -171,6 +180,7 @@ def group_identify( context=None, # type: Optional[Dict] timestamp=None, # type: Optional[datetime.datetime] uuid=None, # type: Optional[str] + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> None """ @@ -196,6 +206,7 @@ def group_identify( context=context, timestamp=timestamp, uuid=uuid, + disable_geoip=disable_geoip, ) @@ -205,6 +216,7 @@ def alias( context=None, # type: Optional[Dict] timestamp=None, # type: Optional[datetime.datetime] uuid=None, # type: Optional[str] + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> None """ @@ -230,6 +242,7 @@ def alias( context=context, timestamp=timestamp, uuid=uuid, + disable_geoip=disable_geoip, ) @@ -241,6 +254,7 @@ def feature_enabled( group_properties={}, # type: dict only_evaluate_locally=False, # type: bool send_feature_flag_events=True, # type: bool + disable_geoip=None, # type: Optional[bool] ): # type: (...) -> bool """ @@ -265,6 +279,7 @@ def feature_enabled( group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, + disable_geoip=disable_geoip, ) @@ -276,6 +291,7 @@ def get_feature_flag( group_properties={}, # type: dict only_evaluate_locally=False, # type: bool send_feature_flag_events=True, # type: bool + disable_geoip=None, # type: Optional[bool] ): """ Get feature flag variant for users. Used with experiments. @@ -308,6 +324,7 @@ def get_feature_flag( group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, + disable_geoip=disable_geoip, ) @@ -317,6 +334,7 @@ def get_all_flags( person_properties={}, # type: dict group_properties={}, # type: dict only_evaluate_locally=False, # type: bool + disable_geoip=None, # type: Optional[bool] ): """ Get all flags for a given user. @@ -334,6 +352,7 @@ def get_all_flags( person_properties=person_properties, group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, + disable_geoip=disable_geoip, ) @@ -346,6 +365,7 @@ def get_feature_flag_payload( group_properties={}, only_evaluate_locally=False, send_feature_flag_events=True, + disable_geoip=None, # type: Optional[bool] ): return _proxy( "get_feature_flag_payload", @@ -357,6 +377,7 @@ def get_feature_flag_payload( group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, + disable_geoip=disable_geoip, ) @@ -366,6 +387,7 @@ def get_all_flags_and_payloads( person_properties={}, group_properties={}, only_evaluate_locally=False, + disable_geoip=None, # type: Optional[bool] ): return _proxy( "get_all_flags_and_payloads", @@ -374,6 +396,7 @@ def get_all_flags_and_payloads( person_properties=person_properties, group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, + disable_geoip=disable_geoip, ) @@ -418,6 +441,7 @@ def _proxy(method, *args, **kwargs): project_api_key=project_api_key, poll_interval=poll_interval, disabled=disabled, + disable_geoip=disable_geoip, ) # always set incase user changes it diff --git a/posthog/client.py b/posthog/client.py index ad2fa7b..4b97b5c 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -48,6 +48,7 @@ def __init__( personal_api_key=None, project_api_key=None, disabled=False, + disable_geoip=True, ): self.queue = queue.Queue(max_queue_size) @@ -71,6 +72,7 @@ def __init__( self.poller = None self.distinct_ids_feature_flags_reported = SizeLimitedDict(MAX_DICT_SIZE, set) self.disabled = disabled + self.disable_geoip = disable_geoip # personal_api_key: This should be a generated Personal API Key, private self.personal_api_key = personal_api_key @@ -112,7 +114,7 @@ def __init__( if send: consumer.start() - def identify(self, distinct_id=None, properties=None, context=None, timestamp=None, uuid=None): + def identify(self, distinct_id=None, properties=None, context=None, timestamp=None, uuid=None, disable_geoip=None): properties = properties or {} context = context or {} require("distinct_id", distinct_id, ID_TYPES) @@ -127,25 +129,36 @@ def identify(self, distinct_id=None, properties=None, context=None, timestamp=No "uuid": uuid, } - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def get_feature_variants(self, distinct_id, groups=None, person_properties=None, group_properties=None): - resp_data = self.get_decide(distinct_id, groups, person_properties, group_properties) + def get_feature_variants( + self, distinct_id, groups=None, person_properties=None, group_properties=None, disable_geoip=None + ): + resp_data = self.get_decide(distinct_id, groups, person_properties, group_properties, disable_geoip) return resp_data["featureFlags"] - def _get_active_feature_variants(self, distinct_id, groups=None, person_properties=None, group_properties=None): - feature_variants = self.get_feature_variants(distinct_id, groups, person_properties, group_properties) + def _get_active_feature_variants( + self, distinct_id, groups=None, person_properties=None, group_properties=None, disable_geoip=None + ): + feature_variants = self.get_feature_variants( + distinct_id, groups, person_properties, group_properties, disable_geoip + ) return { k: v for (k, v) in feature_variants.items() if v is not False } # explicitly test for false to account for values that may seem falsy (ex: 0) - def get_feature_payloads(self, distinct_id, groups=None, person_properties=None, group_properties=None): - resp_data = self.get_decide(distinct_id, groups, person_properties, group_properties) + def get_feature_payloads( + self, distinct_id, groups=None, person_properties=None, group_properties=None, disable_geoip=None + ): + resp_data = self.get_decide(distinct_id, groups, person_properties, group_properties, disable_geoip) return resp_data["featureFlagPayloads"] - def get_decide(self, distinct_id, groups=None, person_properties=None, group_properties=None): + def get_decide(self, distinct_id, groups=None, person_properties=None, group_properties=None, disable_geoip=None): require("distinct_id", distinct_id, ID_TYPES) + if disable_geoip is None: + disable_geoip = self.disable_geoip + if groups: require("groups", groups, dict) else: @@ -156,6 +169,7 @@ def get_decide(self, distinct_id, groups=None, person_properties=None, group_pro "groups": groups, "person_properties": person_properties, "group_properties": group_properties, + "disable_geoip": disable_geoip, } resp_data = decide(self.api_key, self.host, timeout=10, **request_data) @@ -171,6 +185,7 @@ def capture( uuid=None, groups=None, send_feature_flags=False, + disable_geoip=None, ): properties = properties or {} context = context or {} @@ -193,7 +208,7 @@ def capture( if send_feature_flags: try: - feature_variants = self._get_active_feature_variants(distinct_id, groups) + feature_variants = self._get_active_feature_variants(distinct_id, groups, disable_geoip=disable_geoip) except Exception as e: self.log.exception(f"[FEATURE FLAGS] Unable to get feature variants: {e}") else: @@ -201,9 +216,9 @@ def capture( msg["properties"]["$feature/{}".format(feature)] = variant msg["properties"]["$active_feature_flags"] = list(feature_variants.keys()) - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def set(self, distinct_id=None, properties=None, context=None, timestamp=None, uuid=None): + def set(self, distinct_id=None, properties=None, context=None, timestamp=None, uuid=None, disable_geoip=None): properties = properties or {} context = context or {} require("distinct_id", distinct_id, ID_TYPES) @@ -218,9 +233,9 @@ def set(self, distinct_id=None, properties=None, context=None, timestamp=None, u "uuid": uuid, } - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def set_once(self, distinct_id=None, properties=None, context=None, timestamp=None, uuid=None): + def set_once(self, distinct_id=None, properties=None, context=None, timestamp=None, uuid=None, disable_geoip=None): properties = properties or {} context = context or {} require("distinct_id", distinct_id, ID_TYPES) @@ -235,9 +250,18 @@ def set_once(self, distinct_id=None, properties=None, context=None, timestamp=No "uuid": uuid, } - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def group_identify(self, group_type=None, group_key=None, properties=None, context=None, timestamp=None, uuid=None): + def group_identify( + self, + group_type=None, + group_key=None, + properties=None, + context=None, + timestamp=None, + uuid=None, + disable_geoip=None, + ): properties = properties or {} context = context or {} require("group_type", group_type, ID_TYPES) @@ -257,9 +281,9 @@ def group_identify(self, group_type=None, group_key=None, properties=None, conte "uuid": uuid, } - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def alias(self, previous_id=None, distinct_id=None, context=None, timestamp=None, uuid=None): + def alias(self, previous_id=None, distinct_id=None, context=None, timestamp=None, uuid=None, disable_geoip=None): context = context or {} require("previous_id", previous_id, ID_TYPES) @@ -276,9 +300,11 @@ def alias(self, previous_id=None, distinct_id=None, context=None, timestamp=None "distinct_id": previous_id, } - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def page(self, distinct_id=None, url=None, properties=None, context=None, timestamp=None, uuid=None): + def page( + self, distinct_id=None, url=None, properties=None, context=None, timestamp=None, uuid=None, disable_geoip=None + ): properties = properties or {} context = context or {} @@ -297,9 +323,9 @@ def page(self, distinct_id=None, url=None, properties=None, context=None, timest "uuid": uuid, } - return self._enqueue(msg) + return self._enqueue(msg, disable_geoip) - def _enqueue(self, msg): + def _enqueue(self, msg, disable_geoip): """Push a new `msg` onto the queue, return `(success, msg)`""" if self.disabled: @@ -327,6 +353,12 @@ def _enqueue(self, msg): msg["properties"]["$lib"] = "posthog-python" msg["properties"]["$lib_version"] = VERSION + if disable_geoip is None: + disable_geoip = self.disable_geoip + + if disable_geoip: + msg["properties"]["$geoip_disable"] = True + msg["distinct_id"] = stringify_id(msg.get("distinct_id", None)) msg = clean(msg) @@ -470,6 +502,7 @@ def feature_enabled( group_properties={}, only_evaluate_locally=False, send_feature_flag_events=True, + disable_geoip=None, ): response = self.get_feature_flag( key, @@ -479,6 +512,7 @@ def feature_enabled( group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, send_feature_flag_events=send_feature_flag_events, + disable_geoip=disable_geoip, ) if response is None: @@ -495,6 +529,7 @@ def get_feature_flag( group_properties={}, only_evaluate_locally=False, send_feature_flag_events=True, + disable_geoip=None, ): require("key", key, string_types) require("distinct_id", distinct_id, ID_TYPES) @@ -528,7 +563,11 @@ def get_feature_flag( if not flag_was_locally_evaluated and not only_evaluate_locally: try: feature_flags = self.get_feature_variants( - distinct_id, groups=groups, person_properties=person_properties, group_properties=group_properties + distinct_id, + groups=groups, + person_properties=person_properties, + group_properties=group_properties, + disable_geoip=disable_geoip, ) response = feature_flags.get(key) if response is None: @@ -551,6 +590,7 @@ def get_feature_flag( "locally_evaluated": flag_was_locally_evaluated, }, groups=groups, + disable_geoip=disable_geoip, ) self.distinct_ids_feature_flags_reported[distinct_id].add(feature_flag_reported_key) return response @@ -566,6 +606,7 @@ def get_feature_flag_payload( group_properties={}, only_evaluate_locally=False, send_feature_flag_events=True, + disable_geoip=None, ): if match_value is None: match_value = self.get_feature_flag( @@ -576,6 +617,7 @@ def get_feature_flag_payload( group_properties=group_properties, send_feature_flag_events=send_feature_flag_events, only_evaluate_locally=True, + disable_geoip=disable_geoip, ) response = None @@ -584,7 +626,9 @@ def get_feature_flag_payload( response = self._compute_payload_locally(key, match_value) if response is None and not only_evaluate_locally: - decide_payloads = self.get_feature_payloads(distinct_id, groups, person_properties, group_properties) + decide_payloads = self.get_feature_payloads( + distinct_id, groups, person_properties, group_properties, disable_geoip + ) response = decide_payloads.get(str(key).lower(), None) return response @@ -602,7 +646,14 @@ def _compute_payload_locally(self, key, match_value): return payload def get_all_flags( - self, distinct_id, *, groups={}, person_properties={}, group_properties={}, only_evaluate_locally=False + self, + distinct_id, + *, + groups={}, + person_properties={}, + group_properties={}, + only_evaluate_locally=False, + disable_geoip=None, ): flags = self.get_all_flags_and_payloads( distinct_id, @@ -610,11 +661,19 @@ def get_all_flags( person_properties=person_properties, group_properties=group_properties, only_evaluate_locally=only_evaluate_locally, + disable_geoip=disable_geoip, ) return flags["featureFlags"] def get_all_flags_and_payloads( - self, distinct_id, *, groups={}, person_properties={}, group_properties={}, only_evaluate_locally=False + self, + distinct_id, + *, + groups={}, + person_properties={}, + group_properties={}, + only_evaluate_locally=False, + disable_geoip=None, ): flags, payloads, fallback_to_decide = self._get_all_flags_and_payloads_locally( distinct_id, groups=groups, person_properties=person_properties, group_properties=group_properties @@ -624,7 +683,11 @@ def get_all_flags_and_payloads( if fallback_to_decide and not only_evaluate_locally: try: flags_and_payloads = self.get_decide( - distinct_id, groups=groups, person_properties=person_properties, group_properties=group_properties + distinct_id, + groups=groups, + person_properties=person_properties, + group_properties=group_properties, + disable_geoip=disable_geoip, ) response = flags_and_payloads except Exception as e: diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index 37c90a9..4435253 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -112,8 +112,18 @@ def test_get_active_feature_flags(self, patch_decide): } client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, personal_api_key=FAKE_TEST_API_KEY) - variants = client._get_active_feature_variants("some_id", None, None, None) + variants = client._get_active_feature_variants("some_id", None, None, None, False) self.assertEqual(variants, {"beta-feature": "random-variant", "alpha-feature": True}) + patch_decide.assert_called_with( + "random_key", + None, + timeout=10, + distinct_id="some_id", + groups={}, + person_properties=None, + group_properties=None, + disable_geoip=False, + ) @mock.patch("posthog.client.decide") def test_basic_capture_with_feature_flags_returns_active_only(self, patch_decide): @@ -131,6 +141,7 @@ def test_basic_capture_with_feature_flags_returns_active_only(self, patch_decide self.assertTrue(isinstance(msg["timestamp"], str)) self.assertIsNone(msg.get("uuid")) self.assertEqual(msg["distinct_id"], "distinct_id") + self.assertTrue(msg["properties"]["$geoip_disable"]) self.assertEqual(msg["properties"]["$lib"], "posthog-python") self.assertEqual(msg["properties"]["$lib_version"], VERSION) self.assertEqual(msg["properties"]["$feature/beta-feature"], "random-variant") @@ -138,6 +149,53 @@ def test_basic_capture_with_feature_flags_returns_active_only(self, patch_decide self.assertEqual(msg["properties"]["$active_feature_flags"], ["beta-feature", "alpha-feature"]) self.assertEqual(patch_decide.call_count, 1) + patch_decide.assert_called_with( + "random_key", + None, + timeout=10, + distinct_id="distinct_id", + groups={}, + person_properties=None, + group_properties=None, + disable_geoip=True, + ) + + @mock.patch("posthog.client.decide") + def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(self, patch_decide): + patch_decide.return_value = { + "featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False} + } + + client = Client( + FAKE_TEST_API_KEY, on_error=self.set_fail, personal_api_key=FAKE_TEST_API_KEY, disable_geoip=True + ) + success, msg = client.capture("distinct_id", "python test event", send_feature_flags=True, disable_geoip=False) + client.flush() + self.assertTrue(success) + self.assertFalse(self.failed) + + self.assertEqual(msg["event"], "python test event") + self.assertTrue(isinstance(msg["timestamp"], str)) + self.assertIsNone(msg.get("uuid")) + self.assertTrue("$geoip_disable" not in msg["properties"]) + self.assertEqual(msg["distinct_id"], "distinct_id") + self.assertEqual(msg["properties"]["$lib"], "posthog-python") + self.assertEqual(msg["properties"]["$lib_version"], VERSION) + self.assertEqual(msg["properties"]["$feature/beta-feature"], "random-variant") + self.assertEqual(msg["properties"]["$feature/alpha-feature"], True) + self.assertEqual(msg["properties"]["$active_feature_flags"], ["beta-feature", "alpha-feature"]) + + self.assertEqual(patch_decide.call_count, 1) + patch_decide.assert_called_with( + "random_key", + None, + timeout=10, + distinct_id="distinct_id", + groups={}, + person_properties=None, + group_properties=None, + disable_geoip=False, + ) @mock.patch("posthog.client.decide") def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(self, patch_decide): @@ -305,6 +363,7 @@ def test_basic_group_identify(self): "$group_set": {}, "$lib": "posthog-python", "$lib_version": VERSION, + "$geoip_disable": True, }, ) self.assertTrue(isinstance(msg["timestamp"], str)) @@ -326,6 +385,7 @@ def test_advanced_group_identify(self): "$group_set": {"trait": "value"}, "$lib": "posthog-python", "$lib_version": VERSION, + "$geoip_disable": True, }, ) self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00") @@ -494,6 +554,74 @@ def test_enabled_to_disabled(self): self.assertEqual(msg, "disabled") + def test_disable_geoip_default_on_events(self): + client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disable_geoip=True) + _, capture_msg = client.capture("distinct_id", "python test event") + client.flush() + self.assertEqual(capture_msg["properties"]["$geoip_disable"], True) + + _, identify_msg = client.identify("distinct_id", {"trait": "value"}) + client.flush() + self.assertEqual(identify_msg["properties"]["$geoip_disable"], True) + + def test_disable_geoip_override_on_events(self): + client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disable_geoip=False) + _, capture_msg = client.set("distinct_id", {"a": "b", "c": "d"}, disable_geoip=True) + client.flush() + self.assertEqual(capture_msg["properties"]["$geoip_disable"], True) + + _, identify_msg = client.page("distinct_id", "http://a.com", {"trait": "value"}, disable_geoip=False) + client.flush() + self.assertEqual("$geoip_disable" not in identify_msg["properties"], True) + + def test_disable_geoip_method_overrides_init_on_events(self): + client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disable_geoip=True) + _, msg = client.capture("distinct_id", "python test event", disable_geoip=False) + client.flush() + self.assertTrue("$geoip_disable" not in msg["properties"]) + + @mock.patch("posthog.client.decide") + def test_disable_geoip_default_on_decide(self, patch_decide): + patch_decide.return_value = { + "featureFlags": {"beta-feature": "random-variant", "alpha-feature": True, "off-feature": False} + } + client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disable_geoip=False) + client.get_feature_flag("random_key", "some_id", disable_geoip=True) + patch_decide.assert_called_with( + "random_key", + None, + timeout=10, + distinct_id="some_id", + groups={}, + person_properties={}, + group_properties={}, + disable_geoip=True, + ) + patch_decide.reset_mock() + client.feature_enabled("random_key", "feature_enabled_distinct_id", disable_geoip=True) + patch_decide.assert_called_with( + "random_key", + None, + timeout=10, + distinct_id="feature_enabled_distinct_id", + groups={}, + person_properties={}, + group_properties={}, + disable_geoip=True, + ) + patch_decide.reset_mock() + client.get_all_flags_and_payloads("all_flags_payloads_id") + patch_decide.assert_called_with( + "random_key", + None, + timeout=10, + distinct_id="all_flags_payloads_id", + groups={}, + person_properties={}, + group_properties={}, + disable_geoip=False, + ) + @mock.patch("posthog.client.Poller") @mock.patch("posthog.client.get") def test_call_identify_fails(self, patch_get, patch_poll): diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 69a1e1e..c98222e 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -1890,6 +1890,7 @@ def test_capture_is_called(self, patch_decide, patch_capture): "$feature_flag_called", {"$feature_flag": "complex-flag", "$feature_flag_response": True, "locally_evaluated": True}, groups={}, + disable_geoip=None, ) patch_capture.reset_mock() @@ -1914,6 +1915,7 @@ def test_capture_is_called(self, patch_decide, patch_capture): "$feature_flag_called", {"$feature_flag": "complex-flag", "$feature_flag_response": True, "locally_evaluated": True}, groups={}, + disable_geoip=None, ) patch_capture.reset_mock() @@ -1946,6 +1948,45 @@ def test_capture_is_called(self, patch_decide, patch_capture): "$feature_flag_called", {"$feature_flag": "decide-flag", "$feature_flag_response": "decide-value", "locally_evaluated": False}, groups={"organization": "org1"}, + disable_geoip=None, + ) + + @mock.patch.object(Client, "capture") + @mock.patch("posthog.client.decide") + def test_disable_geoip_get_flag_capture_call(self, patch_decide, patch_capture): + patch_decide.return_value = {"featureFlags": {"decide-flag": "decide-value"}} + client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY, disable_geoip=True) + client.feature_flags = [ + { + "id": 1, + "name": "Beta Feature", + "key": "complex-flag", + "is_simple_flag": False, + "active": True, + "filters": { + "groups": [ + { + "properties": [{"key": "region", "value": "USA"}], + "rollout_percentage": 100, + } + ], + }, + } + ] + + client.get_feature_flag( + "complex-flag", + "some-distinct-id", + person_properties={"region": "USA", "name": "Aloha"}, + disable_geoip=False, + ) + + patch_capture.assert_called_with( + "some-distinct-id", + "$feature_flag_called", + {"$feature_flag": "complex-flag", "$feature_flag_response": True, "locally_evaluated": True}, + groups={}, + disable_geoip=False, ) @mock.patch("posthog.client.MAX_DICT_SIZE", 100) @@ -1979,6 +2020,7 @@ def test_capture_multiple_users_doesnt_out_of_memory(self, patch_decide, patch_c "$feature_flag_called", {"$feature_flag": "complex-flag", "$feature_flag_response": True, "locally_evaluated": True}, groups={}, + disable_geoip=None, ) self.assertEqual(len(client.distinct_ids_feature_flags_reported), i % 100 + 1) diff --git a/posthog/version.py b/posthog/version.py index eecdc62..f436e1d 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1,4 +1,4 @@ -VERSION = "2.5.0" +VERSION = "3.0.0" if __name__ == "__main__": print(VERSION, end="") # noqa: T201