Skip to content

Commit 6982671

Browse files
committed
fix: treat a per-call disable_geoip as a value of the event
A disable_geoip argument now fills $geoip_disable as the event's own value: above context tags, super_properties and the client setting, and below a $geoip_disable in the call's properties. disable_geoip=False now sends $geoip_disable: false. Client and AsyncClient share the layer in _build_event_defaults.
1 parent d092fce commit 6982671

7 files changed

Lines changed: 84 additions & 17 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pypi/posthog: major
3+
---
4+
5+
A `disable_geoip` argument to a call now counts as a value of that event. It wins over a `$geoip_disable` in context tags or `super_properties`, and a `$geoip_disable` in the call's own `properties` still wins over it. `disable_geoip=False` now sends `$geoip_disable: false`, so it turns GeoIP lookup on for that event even when a context tag or `super_properties` turns it off. The client's `disable_geoip` setting keeps its place below every caller value. `AsyncPosthog` follows the same rules.

‎docs/migration-7.x-to-8.0.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ Capture v1 sends processing options in an `options` object, next to `properties`
149149

150150
Values apply in this order. Steps 2 to 4 fill only the options and properties that the steps before them left unset:
151151

152-
1. the `options` and `properties` of the call
152+
1. the `options` and `properties` of the call, then the call's `disable_geoip` argument, which fills `$geoip_disable`
153153
2. context options and tags
154154
3. `super_options` and `super_properties`
155-
4. values the SDK sets: `$is_server` from `is_server`, `$geoip_disable` from `disable_geoip`, system properties such as `$os` and `$python_version`, `options.process_person_profile = false` for events without a distinct ID, and `$release_id` from `POSTHOG_RELEASE_ID`
155+
4. values the SDK sets: `$is_server` from `is_server`, `$geoip_disable` from the client's `disable_geoip` setting, system properties such as `$os` and `$python_version`, `options.process_person_profile = false` for events without a distinct ID, and `$release_id` from `POSTHOG_RELEASE_ID`
156156
5. `before_send`, which sees the result of steps 1 to 4 and can change or remove any of it
157157
6. legacy properties, which fill unset options and are then removed
158158

@@ -165,6 +165,7 @@ These changes follow from this order:
165165

166166
- Properties passed to a call now override `super_properties`. `super_properties` can no longer change `$lib` or `$lib_version`.
167167
- A `$is_server`, `$geoip_disable` or system property such as `$os` that you set in a call or a context tag now wins over the SDK's value. In 7.x the SDK overwrote it. `super_properties` win over the SDK's value too, so `super_properties={"$geoip_disable": False}` turns GeoIP lookup on for events, even with `disable_geoip=True`.
168+
- A `disable_geoip` argument to a call belongs to that event, so it wins over context tags and `super_properties`. A `$geoip_disable` in the call's own `properties` still wins over it. `disable_geoip=False` now sends `$geoip_disable: false`. In 7.x it sent no `$geoip_disable` property.
168169
- The `groups` argument merges into a `$groups` property of the call, and wins key by key. In 7.x it replaced the property. MCP events merge their identity's groups into a custom `$groups` property the same way.
169170
- A `$set`, `$set_once`, `$groups` or `$group_set` in `super_properties` no longer replaces the whole value of the call. The two merge, and the call wins key by key.
170171
- An event without a distinct ID gets `options.process_person_profile = false`. A `$process_person_profile: true` property no longer turns person processing back on. Set the option instead.

‎posthog/async_client.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,6 @@ def _event_defaults(
521521
disable_geoip: Optional[bool] = None,
522522
system_properties: Optional[dict[str, Any]] = None,
523523
) -> _EventDefaults:
524-
if disable_geoip is None:
525-
disable_geoip = self.disable_geoip
526524
return _build_event_defaults(
527525
super_properties=self.super_properties,
528526
super_options=self.super_options,
@@ -532,7 +530,8 @@ def _event_defaults(
532530
derived_options=derived_options,
533531
property_allowlist=property_allowlist,
534532
is_server=self.is_server,
535-
disable_geoip=disable_geoip,
533+
disable_geoip=self.disable_geoip,
534+
call_disable_geoip=disable_geoip,
536535
system_properties=system_properties,
537536
)
538537

‎posthog/capture_event.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def _event_options(value: Any) -> dict[str, Any]:
124124

125125
@dataclass(frozen=True)
126126
class _EventDefaults:
127-
"""Context, global and SDK-derived values for one event, highest layer first.
127+
"""Per-call, context, global and SDK-derived values for one event, highest layer first.
128128
129129
They fill in before ``before_send``, so the hook sees them and can change
130130
or remove them. The event's own values win over every default.
@@ -146,9 +146,18 @@ def _build_event_defaults(
146146
property_allowlist: Optional[Collection[str]] = None,
147147
is_server: bool = False,
148148
disable_geoip: bool = False,
149+
call_disable_geoip: Optional[bool] = None,
149150
system_properties: Optional[Mapping[str, Any]] = None,
150151
) -> _EventDefaults:
151-
"""Order the layers: context, then global, then values the SDK derives."""
152+
"""Order the layers: per-call arguments, context, global, then SDK values.
153+
154+
``disable_geoip`` is the client setting, an SDK value. ``call_disable_geoip``
155+
is the argument of one call: it is a value of that event, so only the
156+
event's own ``$geoip_disable`` property beats it.
157+
"""
158+
call_properties = (
159+
{} if call_disable_geoip is None else {"$geoip_disable": call_disable_geoip}
160+
)
152161
# A value in the event, the context or super properties wins over every
153162
# value the SDK adds, including `$is_server` and `$geoip_disable`.
154163
sdk_properties = dict(system_properties or {})
@@ -160,6 +169,7 @@ def _build_event_defaults(
160169
sdk_properties["$geoip_disable"] = True
161170
return _EventDefaults(
162171
property_layers=(
172+
call_properties,
163173
context_properties or {},
164174
super_properties or {},
165175
sdk_properties,

‎posthog/client.py‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,9 +1682,10 @@ def capture(
16821682
send_feature_flags: Deprecated. Prefer flags=... from
16831683
evaluate_flags(). When truthy, evaluates flags during capture and
16841684
attaches them to the event.
1685-
disable_geoip: Whether to disable GeoIP for this event. A
1686-
``$geoip_disable`` property in the event, context tags or
1687-
``super_properties`` wins.
1685+
disable_geoip: Whether to disable GeoIP for this event. It wins
1686+
over a ``$geoip_disable`` in context tags or
1687+
``super_properties``. A ``$geoip_disable`` property in this
1688+
call's ``properties`` wins over it.
16881689
options: Capture options for this event, such as
16891690
``{"process_person_profile": False}``. Sent as given, for
16901691
PostHog to validate. They override context options and
@@ -2527,9 +2528,6 @@ def _enqueue(
25272528
msg["properties"]["$lib"] = self._library_id
25282529
msg["properties"]["$lib_version"] = self._library_version
25292530

2530-
if disable_geoip is None:
2531-
disable_geoip = self.disable_geoip
2532-
25332531
msg["options"] = msg.get("options") or {}
25342532

25352533
_fill_event_defaults(
@@ -2543,7 +2541,8 @@ def _enqueue(
25432541
derived_options=derived_options,
25442542
property_allowlist=property_allowlist,
25452543
is_server=self.is_server,
2546-
disable_geoip=disable_geoip,
2544+
disable_geoip=self.disable_geoip,
2545+
call_disable_geoip=disable_geoip,
25472546
system_properties=system_properties,
25482547
),
25492548
)

‎posthog/test/test_client.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,7 +1364,7 @@ def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(
13641364
self.assertEqual(msg["event"], "python test event")
13651365
self.assertTrue(isinstance(msg["timestamp"], str))
13661366
self.assertIsNotNone(msg.get("uuid"))
1367-
self.assertTrue("$geoip_disable" not in msg["properties"])
1367+
self.assertIs(msg["properties"]["$geoip_disable"], False)
13681368
self.assertEqual(msg["distinct_id"], "distinct_id")
13691369
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
13701370
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
@@ -3800,7 +3800,7 @@ def test_disable_geoip_override_on_events(self):
38003800
# Check page event
38013801
page_batch = sent_batch(mock_post, 1)
38023802
identify_msg = page_batch[0]
3803-
self.assertEqual("$geoip_disable" not in identify_msg["properties"], True)
3803+
self.assertIs(identify_msg["properties"]["$geoip_disable"], False)
38043804

38053805
def test_disable_geoip_method_overrides_init_on_events(self):
38063806
with patch_capture_send("client") as mock_post:
@@ -3819,7 +3819,7 @@ def test_disable_geoip_method_overrides_init_on_events(self):
38193819
mock_post.assert_called_once()
38203820
batch_data = sent_batch(mock_post)
38213821
msg = batch_data[0]
3822-
self.assertTrue("$geoip_disable" not in msg["properties"])
3822+
self.assertIs(msg["properties"]["$geoip_disable"], False)
38233823

38243824
@mock.patch("posthog.client.flags")
38253825
def test_disable_geoip_default_on_decide(self, patch_flags):

‎posthog/test/test_event_options.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,59 @@ async def test_async_caller_values_beat_sdk_values(source):
410410
_assert_caller_values(events, CALLER_SDK_VALUES)
411411

412412

413+
GEOIP_CALL_CASES = [
414+
pytest.param(True, "context", False, True, id="call_beats_context"),
415+
pytest.param(True, "super", False, True, id="call_beats_super"),
416+
pytest.param(False, "super", True, False, id="call_false_beats_super"),
417+
pytest.param(False, None, None, False, id="call_false_beats_client_setting"),
418+
pytest.param(True, "event", False, False, id="event_property_beats_call"),
419+
]
420+
421+
422+
def _capture_with_call_geoip(call_value, source, layer_value):
423+
def call(client):
424+
with new_context(fresh=True):
425+
if source == "context":
426+
tag("$geoip_disable", layer_value)
427+
properties = {"$geoip_disable": layer_value} if source == "event" else None
428+
return client.capture(
429+
"e", distinct_id="u", properties=properties, disable_geoip=call_value
430+
)
431+
432+
return call
433+
434+
435+
def _geoip_layer_config(source, layer_value):
436+
if source == "super":
437+
return {"super_properties": {"$geoip_disable": layer_value}}
438+
return {}
439+
440+
441+
@pytest.mark.parametrize("call_value, source, layer_value, expected", GEOIP_CALL_CASES)
442+
def test_sync_call_disable_geoip_is_an_event_value(
443+
call_value, source, layer_value, expected
444+
):
445+
events = _sync_wire_events(
446+
_capture_with_call_geoip(call_value, source, layer_value),
447+
disable_geoip=True,
448+
**_geoip_layer_config(source, layer_value),
449+
)
450+
assert events[0]["properties"]["$geoip_disable"] is expected
451+
452+
453+
@pytest.mark.asyncio
454+
@pytest.mark.parametrize("call_value, source, layer_value, expected", GEOIP_CALL_CASES)
455+
async def test_async_call_disable_geoip_is_an_event_value(
456+
call_value, source, layer_value, expected
457+
):
458+
events = await _async_wire_events(
459+
_capture_with_call_geoip(call_value, source, layer_value),
460+
disable_geoip=True,
461+
**_geoip_layer_config(source, layer_value),
462+
)
463+
assert events[0]["properties"]["$geoip_disable"] is expected
464+
465+
413466
@pytest.mark.parametrize("method", list(CAPTURE_CALLS))
414467
def test_sync_super_properties_beat_sdk_values_on_every_path(method):
415468
events = _sync_wire_events(

0 commit comments

Comments
 (0)