Skip to content

Commit b775339

Browse files
authored
feat: session and identity integrate with context now (#264)
* session and identity in context * bump version * make django integration use context distinct id and session functions * don't use self * fix comments * fix middleware tests * clarify fresh and distinct id's * Fix exported modules, add makefile command to test changes locally * tiny fix
1 parent 250bd42 commit b775339

File tree

10 files changed

+414
-152
lines changed

10 files changed

+414
-152
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
## 5.0.0
1+
# 5.1.0
2+
3+
- feat: session and distinct ID's can now be associated with contexts, and are used as such
4+
- feat: django http request middleware
5+
6+
## 5.0.0 - 2025-06-16
27

38
- fix: removed deprecated sentry integration
49

Makefile

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,23 @@ release_analytics:
3535
e2e_test:
3636
.buildscripts/e2e.sh
3737

38-
.PHONY: test lint release e2e_test
38+
prep_local:
39+
rm -rf ../posthog-python-local
40+
mkdir ../posthog-python-local
41+
cp -r . ../posthog-python-local/
42+
cd ../posthog-python-local && rm -rf dist build posthoganalytics .git
43+
cd ../posthog-python-local && mkdir posthoganalytics
44+
cd ../posthog-python-local && cp -r posthog/* posthoganalytics/
45+
cd ../posthog-python-local && find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthog /from posthoganalytics /g' {} \;
46+
cd ../posthog-python-local && find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthog\./from posthoganalytics\./g' {} \;
47+
cd ../posthog-python-local && find ./posthoganalytics -name "*.bak" -delete
48+
cd ../posthog-python-local && rm -rf posthog
49+
cd ../posthog-python-local && sed -i.bak 's/from version import VERSION/from posthoganalytics.version import VERSION/' setup_analytics.py
50+
cd ../posthog-python-local && rm setup_analytics.py.bak
51+
cd ../posthog-python-local && sed -i.bak 's/"posthog"/"posthoganalytics"/' setup.py
52+
cd ../posthog-python-local && rm setup.py.bak
53+
cd ../posthog-python-local && python -c "import setup_analytics" 2>/dev/null || true
54+
@echo "Local copy created at ../posthog-python-local"
55+
@echo "Install with: pip install -e ../posthog-python-local"
56+
57+
.PHONY: test lint release e2e_test prep_local

posthog/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44

55
from posthog.client import Client
66
from posthog.exception_capture import Integrations # noqa: F401
7-
from posthog.scopes import clear_tags, get_tags, new_context, scoped, tag
7+
from posthog.scopes import (
8+
clear_tags,
9+
get_tags,
10+
new_context,
11+
scoped,
12+
tag,
13+
set_context_session,
14+
identify_context,
15+
)
816
from posthog.types import FeatureFlag, FlagsAndPayloads
917
from posthog.version import VERSION
1018

@@ -16,6 +24,9 @@
1624
get_tags = get_tags
1725
clear_tags = clear_tags
1826
scoped = scoped
27+
identify_context = identify_context
28+
set_context_session = set_context_session
29+
1930

2031
"""Settings."""
2132
api_key = None # type: Optional[str]

posthog/client.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
get,
3434
remote_config,
3535
)
36-
from posthog.scopes import get_tags
36+
from posthog.scopes import (
37+
_get_current_context,
38+
get_context_distinct_id,
39+
get_context_session_id,
40+
)
3741
from posthog.types import (
3842
FeatureFlag,
3943
FeatureFlagResult,
@@ -285,10 +289,17 @@ def identify(
285289
stacklevel=2,
286290
)
287291

292+
if distinct_id is None:
293+
distinct_id = get_context_distinct_id()
294+
288295
properties = properties or {}
296+
289297
require("distinct_id", distinct_id, ID_TYPES)
290298
require("properties", properties, dict)
291299

300+
if "$session_id" not in properties and get_context_session_id():
301+
properties["$session_id"] = get_context_session_id()
302+
292303
msg = {
293304
"timestamp": timestamp,
294305
"distinct_id": distinct_id,
@@ -358,6 +369,9 @@ def get_flags_decision(
358369
"""
359370
Get feature flags decision, using either flags() or decide() API based on rollout.
360371
"""
372+
373+
if distinct_id is None:
374+
distinct_id = get_context_distinct_id()
361375
require("distinct_id", distinct_id, ID_TYPES)
362376

363377
if disable_geoip is None:
@@ -406,14 +420,22 @@ def capture(
406420

407421
properties = {**(properties or {}), **system_context()}
408422

423+
if "$session_id" not in properties and get_context_session_id():
424+
properties["$session_id"] = get_context_session_id()
425+
426+
if distinct_id is None:
427+
distinct_id = get_context_distinct_id()
428+
409429
require("distinct_id", distinct_id, ID_TYPES)
410430
require("properties", properties, dict)
411431
require("event", event, string_types)
412432

413-
# Grab current context tags, if any exist
414-
context_tags = get_tags()
415-
if context_tags:
416-
properties.update(context_tags)
433+
current_context = _get_current_context()
434+
if current_context:
435+
context_tags = current_context.collect_tags()
436+
# We want explicitly passed properties to override context tags
437+
context_tags.update(properties)
438+
properties = context_tags
417439

418440
msg = {
419441
"properties": properties,
@@ -480,6 +502,9 @@ def set(
480502
stacklevel=2,
481503
)
482504

505+
if distinct_id is None:
506+
distinct_id = get_context_distinct_id()
507+
483508
properties = properties or {}
484509
require("distinct_id", distinct_id, ID_TYPES)
485510
require("properties", properties, dict)
@@ -510,6 +535,9 @@ def set_once(
510535
stacklevel=2,
511536
)
512537

538+
if distinct_id is None:
539+
distinct_id = get_context_distinct_id()
540+
513541
properties = properties or {}
514542
require("distinct_id", distinct_id, ID_TYPES)
515543
require("properties", properties, dict)
@@ -581,6 +609,9 @@ def alias(
581609
stacklevel=2,
582610
)
583611

612+
if distinct_id is None:
613+
distinct_id = get_context_distinct_id()
614+
584615
require("previous_id", previous_id, ID_TYPES)
585616
require("distinct_id", distinct_id, ID_TYPES)
586617

@@ -613,6 +644,9 @@ def page(
613644
stacklevel=2,
614645
)
615646

647+
if distinct_id is None:
648+
distinct_id = get_context_distinct_id()
649+
616650
properties = properties or {}
617651
require("distinct_id", distinct_id, ID_TYPES)
618652
require("properties", properties, dict)
@@ -648,6 +682,9 @@ def capture_exception(
648682
stacklevel=2,
649683
)
650684

685+
if distinct_id is None:
686+
distinct_id = get_context_distinct_id()
687+
651688
# this function shouldn't ever throw an error, so it logs exceptions instead of raising them.
652689
# this is important to ensure we don't unexpectedly re-raise exceptions in the user's code.
653690
try:

posthog/integrations/django.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ class PosthogContextMiddleware:
1010
"""Middleware to automatically track Django requests.
1111
1212
This middleware wraps all calls with a posthog context. It attempts to extract the following from the request headers:
13-
- Session ID as $session_id, (extracted from `X-POSTHOG-SESSION-ID`)
14-
- Distinct ID as $distinct_id, (extracted from `X-POSTHOG-DISTINCT-ID`)
13+
- Session ID, (extracted from `X-POSTHOG-SESSION-ID`)
14+
- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`)
1515
- Request URL as $current_url
1616
- Request Method as $request_method
1717
@@ -26,7 +26,9 @@ class PosthogContextMiddleware:
2626
You can use the `POSTHOG_MW_TAG_MAP` function to remove any default tags you don't want to capture, or override them with your own values.
2727
2828
Context tags are automatically included as properties on all events captured within a context, including exceptions.
29-
See the context documentation for more information.
29+
See the context documentation for more information. The extracted distinct ID and session ID, if found, are used to
30+
associate all events captured in the middleware context with the same distinct ID and session as currently active on the
31+
frontend. See the documentation for `set_context_session` and `identify_context` for more details.
3032
"""
3133

3234
def __init__(self, get_response):
@@ -79,12 +81,12 @@ def extract_tags(self, request):
7981
# Extract session ID from X-POSTHOG-SESSION-ID header
8082
session_id = request.headers.get("X-POSTHOG-SESSION-ID")
8183
if session_id:
82-
tags["$session_id"] = session_id
84+
scopes.set_context_session(session_id)
8385

8486
# Extract distinct ID from X-POSTHOG-DISTINCT-ID header
8587
distinct_id = request.headers.get("X-POSTHOG-DISTINCT-ID")
8688
if distinct_id:
87-
tags["$distinct_id"] = distinct_id
89+
scopes.identify_context(distinct_id)
8890

8991
# Extract current URL
9092
absolute_url = request.build_absolute_uri()

0 commit comments

Comments
 (0)