Skip to content

Commit 49d0821

Browse files
authored
Merge pull request #23 from PostHog/set_once
Add $set and $set_once support
2 parents 4f85340 + b4489f1 commit 49d0821

File tree

14 files changed

+223
-16
lines changed

14 files changed

+223
-16
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454

5555
- name: Install requirements.txt dependencies with pip
5656
run: |
57-
python -m pip install -e .
57+
python -m pip install -e .[test]
5858
5959
- name: Run posthog tests
6060
run: |

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ flake8.out
1313
pylint.out
1414
posthog-analytics
1515
.idea
16+
.python-version
17+
.coverage

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
test:
1+
lint:
22
pylint --rcfile=.pylintrc --reports=y --exit-zero analytics | tee pylint.out
33
flake8 --max-complexity=10 --statistics analytics > flake8.out || true
4-
coverage run --branch --include=analytics/\* --omit=*/test* setup.py test
4+
5+
test:
6+
coverage run -m pytest
7+
coverage report
58

69
release:
710
rm -rf dist/*
@@ -26,4 +29,4 @@ release_analytics:
2629
e2e_test:
2730
.buildscripts/e2e.sh
2831

29-
.PHONY: test release e2e_test
32+
.PHONY: test lint release e2e_test

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,16 @@ Specifically, the [Python integration](https://posthog.com/docs/integrations/pyt
77
## Questions?
88

99
### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ)
10+
11+
# Local Development
12+
13+
## Testing Locally
14+
15+
1. Run `python3 -m venv env` (creates virtual environment called "env")
16+
2. Run `source env/bin/activate` (activates the virtual environment)
17+
3. Run `python3 -m pip install -e ".[test]"` (installs the package in develop mode, along with test dependencies)
18+
4. Run `make test`
19+
20+
## Running Locally
21+
22+
Assuming you have a [local version of PostHog](https://posthog.com/docs/developing-locally) running, you can run `python3 example.py` to see the library in action.

example.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,29 @@
1919
print(posthog.feature_enabled("beta-feature", "distinct_id"))
2020

2121
print("sleeping")
22-
time.sleep(45)
22+
time.sleep(5)
2323

2424
print(posthog.feature_enabled("beta-feature", "distinct_id"))
2525

2626
# # Alias a previous distinct id with a new one
27+
2728
posthog.alias("distinct_id", "new_distinct_id")
2829

30+
posthog.capture("new_distinct_id", "event2", {"property1": "value", "property2": "value"})
31+
2932
# # Add properties to the person
30-
posthog.identify("distinct_id", {"email": "[email protected]"})
33+
posthog.identify("new_distinct_id", {"email": "[email protected]"})
34+
35+
# properties set only once to the person
36+
posthog.set_once("new_distinct_id", {"self_serve_signup": True})
37+
38+
time.sleep(3)
39+
40+
posthog.set_once(
41+
"new_distinct_id", {"self_serve_signup": False}
42+
) # this will not change the property (because it was already set)
43+
44+
posthog.set("new_distinct_id", {"current_browser": "Chrome"})
45+
posthog.set("new_distinct_id", {"current_browser": "Firefox"})
46+
47+
posthog.shutdown()

posthog/__init__.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def capture(
3232
3333
A `capture` call requires
3434
- `distinct id` which uniquely identifies your user
35-
- `event name` to make sure
35+
- `event name` to specify the event
3636
- We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on.
3737
3838
Optionally you can submit
@@ -87,6 +87,72 @@ def identify(
8787
)
8888

8989

90+
def set(
91+
distinct_id, # type: str,
92+
properties=None, # type: Optional[Dict]
93+
context=None, # type: Optional[Dict]
94+
timestamp=None, # type: Optional[datetime.datetime]
95+
message_id=None, # type: Optional[str]
96+
):
97+
# type: (...) -> None
98+
"""
99+
Set properties on a user record.
100+
This will overwrite previous people property values, just like `identify`.
101+
102+
A `set` call requires
103+
- `distinct id` which uniquely identifies your user
104+
- `properties` with a dict with any key: value pairs
105+
106+
For example:
107+
```python
108+
posthog.set('distinct id', {
109+
'current_browser': 'Chrome',
110+
})
111+
```
112+
"""
113+
_proxy(
114+
"set",
115+
distinct_id=distinct_id,
116+
properties=properties,
117+
context=context,
118+
timestamp=timestamp,
119+
message_id=message_id,
120+
)
121+
122+
123+
def set_once(
124+
distinct_id, # type: str,
125+
properties=None, # type: Optional[Dict]
126+
context=None, # type: Optional[Dict]
127+
timestamp=None, # type: Optional[datetime.datetime]
128+
message_id=None, # type: Optional[str]
129+
):
130+
# type: (...) -> None
131+
"""
132+
Set properties on a user record, only if they do not yet exist.
133+
This will not overwrite previous people property values, unlike `identify`.
134+
135+
A `set_once` call requires
136+
- `distinct id` which uniquely identifies your user
137+
- `properties` with a dict with any key: value pairs
138+
139+
For example:
140+
```python
141+
posthog.set_once('distinct id', {
142+
'referred_by': 'friend',
143+
})
144+
```
145+
"""
146+
_proxy(
147+
"set_once",
148+
distinct_id=distinct_id,
149+
properties=properties,
150+
context=context,
151+
timestamp=timestamp,
152+
message_id=message_id,
153+
)
154+
155+
90156
def group(*args, **kwargs):
91157
"""Send a group call."""
92158
_proxy("group", *args, **kwargs)

posthog/client.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,40 @@ def capture(self, distinct_id=None, event=None, properties=None, context=None, t
137137

138138
return self._enqueue(msg)
139139

140+
def set(self, distinct_id=None, properties=None, context=None, timestamp=None, message_id=None):
141+
properties = properties or {}
142+
context = context or {}
143+
require("distinct_id", distinct_id, ID_TYPES)
144+
require("properties", properties, dict)
145+
146+
msg = {
147+
"timestamp": timestamp,
148+
"context": context,
149+
"distinct_id": distinct_id,
150+
"$set": properties,
151+
"event": "$set",
152+
"messageId": message_id,
153+
}
154+
155+
return self._enqueue(msg)
156+
157+
def set_once(self, distinct_id=None, properties=None, context=None, timestamp=None, message_id=None):
158+
properties = properties or {}
159+
context = context or {}
160+
require("distinct_id", distinct_id, ID_TYPES)
161+
require("properties", properties, dict)
162+
163+
msg = {
164+
"timestamp": timestamp,
165+
"context": context,
166+
"distinct_id": distinct_id,
167+
"$set_once": properties,
168+
"event": "$set_once",
169+
"messageId": message_id,
170+
}
171+
172+
return self._enqueue(msg)
173+
140174
def alias(self, previous_id=None, distinct_id=None, context=None, timestamp=None, message_id=None):
141175
context = context or {}
142176

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from posthog.client import Client
1111
from posthog.request import APIError
12-
from posthog.test.utils import TEST_API_KEY
12+
from posthog.test.test_utils import TEST_API_KEY
1313
from posthog.version import VERSION
1414

1515

@@ -105,6 +105,64 @@ def test_advanced_identify(self):
105105
self.assertEqual(msg["messageId"], "messageId")
106106
self.assertEqual(msg["distinct_id"], "distinct_id")
107107

108+
def test_basic_set(self):
109+
client = self.client
110+
success, msg = client.set("distinct_id", {"trait": "value"})
111+
client.flush()
112+
self.assertTrue(success)
113+
self.assertFalse(self.failed)
114+
115+
self.assertEqual(msg["$set"]["trait"], "value")
116+
self.assertTrue(isinstance(msg["timestamp"], str))
117+
self.assertTrue(isinstance(msg["messageId"], str))
118+
self.assertEqual(msg["distinct_id"], "distinct_id")
119+
120+
def test_advanced_set(self):
121+
client = self.client
122+
success, msg = client.set(
123+
"distinct_id", {"trait": "value"}, {"ip": "192.168.0.1"}, datetime(2014, 9, 3), "messageId"
124+
)
125+
126+
self.assertTrue(success)
127+
128+
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
129+
self.assertEqual(msg["context"]["ip"], "192.168.0.1")
130+
self.assertEqual(msg["$set"]["trait"], "value")
131+
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
132+
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
133+
self.assertTrue(isinstance(msg["timestamp"], str))
134+
self.assertEqual(msg["messageId"], "messageId")
135+
self.assertEqual(msg["distinct_id"], "distinct_id")
136+
137+
def test_basic_set_once(self):
138+
client = self.client
139+
success, msg = client.set_once("distinct_id", {"trait": "value"})
140+
client.flush()
141+
self.assertTrue(success)
142+
self.assertFalse(self.failed)
143+
144+
self.assertEqual(msg["$set_once"]["trait"], "value")
145+
self.assertTrue(isinstance(msg["timestamp"], str))
146+
self.assertTrue(isinstance(msg["messageId"], str))
147+
self.assertEqual(msg["distinct_id"], "distinct_id")
148+
149+
def test_advanced_set_once(self):
150+
client = self.client
151+
success, msg = client.set_once(
152+
"distinct_id", {"trait": "value"}, {"ip": "192.168.0.1"}, datetime(2014, 9, 3), "messageId"
153+
)
154+
155+
self.assertTrue(success)
156+
157+
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
158+
self.assertEqual(msg["context"]["ip"], "192.168.0.1")
159+
self.assertEqual(msg["$set_once"]["trait"], "value")
160+
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
161+
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
162+
self.assertTrue(isinstance(msg["timestamp"], str))
163+
self.assertEqual(msg["messageId"], "messageId")
164+
self.assertEqual(msg["distinct_id"], "distinct_id")
165+
108166
def test_basic_alias(self):
109167
client = self.client
110168
success, msg = client.alias("previousId", "distinct_id")
@@ -125,7 +183,7 @@ def test_basic_page(self):
125183

126184
def test_basic_page_distinct_uuid(self):
127185
client = self.client
128-
distinct_id = uuid4()
186+
distinct_id = str(uuid4())
129187
success, msg = client.page(distinct_id, url="https://posthog.com/contact")
130188
self.assertFalse(self.failed)
131189
client.flush()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from posthog.consumer import MAX_MSG_SIZE, Consumer
1313
from posthog.request import APIError
14-
from posthog.test.utils import TEST_API_KEY
14+
from posthog.test.test_utils import TEST_API_KEY
1515

1616

1717
class TestConsumer(unittest.TestCase):

0 commit comments

Comments
 (0)