Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:

- name: Install requirements.txt dependencies with pip
run: |
python -m pip install -e .
python -m pip install -e .[test]

- name: Run posthog tests
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ flake8.out
pylint.out
posthog-analytics
.idea
.python-version
.coverage
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
test:
lint:
pylint --rcfile=.pylintrc --reports=y --exit-zero analytics | tee pylint.out
flake8 --max-complexity=10 --statistics analytics > flake8.out || true
coverage run --branch --include=analytics/\* --omit=*/test* setup.py test

test:
coverage run -m pytest
coverage report

release:
rm -rf dist/*
Expand All @@ -26,4 +29,4 @@ release_analytics:
e2e_test:
.buildscripts/e2e.sh

.PHONY: test release e2e_test
.PHONY: test lint release e2e_test
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,16 @@ Specifically, the [Python integration](https://posthog.com/docs/integrations/pyt
## Questions?

### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ)

# Local Development

## Testing Locally

1. Run `python3 -m venv env` (creates virtual environment called "env")
2. Run `source env/bin/activate` (activates the virtual environment)
3. Run `python3 -m pip install -e ".[test]"` (installs the package in develop mode, along with test dependencies)
4. Run `make test`

## Running Locally

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.
21 changes: 19 additions & 2 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,29 @@
print(posthog.feature_enabled("beta-feature", "distinct_id"))

print("sleeping")
time.sleep(45)
time.sleep(5)

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

# # Alias a previous distinct id with a new one

posthog.alias("distinct_id", "new_distinct_id")

posthog.capture("new_distinct_id", "event2", {"property1": "value", "property2": "value"})

# # Add properties to the person
posthog.identify("distinct_id", {"email": "something@something.com"})
posthog.identify("new_distinct_id", {"email": "something@something.com"})

# properties set only once to the person
posthog.set_once("new_distinct_id", {"self_serve_signup": True})

time.sleep(3)

posthog.set_once(
"new_distinct_id", {"self_serve_signup": False}
) # this will not change the property (because it was already set)

posthog.set("new_distinct_id", {"current_browser": "Chrome"})
posthog.set("new_distinct_id", {"current_browser": "Firefox"})

posthog.shutdown()
68 changes: 67 additions & 1 deletion posthog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def capture(

A `capture` call requires
- `distinct id` which uniquely identifies your user
- `event name` to make sure
- `event name` to specify the event
- We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on.

Optionally you can submit
Expand Down Expand Up @@ -87,6 +87,72 @@ def identify(
)


def set(
distinct_id, # type: str,
properties=None, # type: Optional[Dict]
context=None, # type: Optional[Dict]
timestamp=None, # type: Optional[datetime.datetime]
message_id=None, # type: Optional[str]
):
# type: (...) -> None
"""
Set properties on a user record.
This will overwrite previous people property values, just like `identify`.

A `set` call requires
- `distinct id` which uniquely identifies your user
- `properties` with a dict with any key: value pairs

For example:
```python
posthog.set('distinct id', {
'current_browser': 'Chrome',
})
```
"""
_proxy(
"set",
distinct_id=distinct_id,
properties=properties,
context=context,
timestamp=timestamp,
message_id=message_id,
)


def set_once(
distinct_id, # type: str,
properties=None, # type: Optional[Dict]
context=None, # type: Optional[Dict]
timestamp=None, # type: Optional[datetime.datetime]
message_id=None, # type: Optional[str]
):
# type: (...) -> None
"""
Set properties on a user record, only if they do not yet exist.
This will not overwrite previous people property values, unlike `identify`.

A `set_once` call requires
- `distinct id` which uniquely identifies your user
- `properties` with a dict with any key: value pairs

For example:
```python
posthog.set_once('distinct id', {
'referred_by': 'friend',
})
```
"""
_proxy(
"set_once",
distinct_id=distinct_id,
properties=properties,
context=context,
timestamp=timestamp,
message_id=message_id,
)


def group(*args, **kwargs):
"""Send a group call."""
_proxy("group", *args, **kwargs)
Expand Down
34 changes: 34 additions & 0 deletions posthog/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,40 @@ def capture(self, distinct_id=None, event=None, properties=None, context=None, t

return self._enqueue(msg)

def set(self, distinct_id=None, properties=None, context=None, timestamp=None, message_id=None):
properties = properties or {}
context = context or {}
require("distinct_id", distinct_id, ID_TYPES)
require("properties", properties, dict)

msg = {
"timestamp": timestamp,
"context": context,
"distinct_id": distinct_id,
"$set": properties,
"event": "$set",
"messageId": message_id,
}

return self._enqueue(msg)

def set_once(self, distinct_id=None, properties=None, context=None, timestamp=None, message_id=None):
properties = properties or {}
context = context or {}
require("distinct_id", distinct_id, ID_TYPES)
require("properties", properties, dict)

msg = {
"timestamp": timestamp,
"context": context,
"distinct_id": distinct_id,
"$set_once": properties,
"event": "$set_once",
"messageId": message_id,
}

return self._enqueue(msg)

def alias(self, previous_id=None, distinct_id=None, context=None, timestamp=None, message_id=None):
context = context or {}

Expand Down
2 changes: 1 addition & 1 deletion posthog/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def next(self):
return items

def request(self, batch):
"""Attempt to upload the batch and retry before raising an error """
"""Attempt to upload the batch and retry before raising an error"""

def fatal_exception(exc):
if isinstance(exc, APIError):
Expand Down
62 changes: 60 additions & 2 deletions posthog/test/client.py → posthog/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from posthog.client import Client
from posthog.request import APIError
from posthog.test.utils import TEST_API_KEY
from posthog.test.test_utils import TEST_API_KEY
from posthog.version import VERSION


Expand Down Expand Up @@ -105,6 +105,64 @@ def test_advanced_identify(self):
self.assertEqual(msg["messageId"], "messageId")
self.assertEqual(msg["distinct_id"], "distinct_id")

def test_basic_set(self):
client = self.client
success, msg = client.set("distinct_id", {"trait": "value"})
client.flush()
self.assertTrue(success)
self.assertFalse(self.failed)

self.assertEqual(msg["$set"]["trait"], "value")
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertTrue(isinstance(msg["messageId"], str))
self.assertEqual(msg["distinct_id"], "distinct_id")

def test_advanced_set(self):
client = self.client
success, msg = client.set(
"distinct_id", {"trait": "value"}, {"ip": "192.168.0.1"}, datetime(2014, 9, 3), "messageId"
)

self.assertTrue(success)

self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
self.assertEqual(msg["context"]["ip"], "192.168.0.1")
self.assertEqual(msg["$set"]["trait"], "value")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertEqual(msg["messageId"], "messageId")
self.assertEqual(msg["distinct_id"], "distinct_id")

def test_basic_set_once(self):
client = self.client
success, msg = client.set_once("distinct_id", {"trait": "value"})
client.flush()
self.assertTrue(success)
self.assertFalse(self.failed)

self.assertEqual(msg["$set_once"]["trait"], "value")
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertTrue(isinstance(msg["messageId"], str))
self.assertEqual(msg["distinct_id"], "distinct_id")

def test_advanced_set_once(self):
client = self.client
success, msg = client.set_once(
"distinct_id", {"trait": "value"}, {"ip": "192.168.0.1"}, datetime(2014, 9, 3), "messageId"
)

self.assertTrue(success)

self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
self.assertEqual(msg["context"]["ip"], "192.168.0.1")
self.assertEqual(msg["$set_once"]["trait"], "value")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertEqual(msg["messageId"], "messageId")
self.assertEqual(msg["distinct_id"], "distinct_id")

def test_basic_alias(self):
client = self.client
success, msg = client.alias("previousId", "distinct_id")
Expand All @@ -125,7 +183,7 @@ def test_basic_page(self):

def test_basic_page_distinct_uuid(self):
client = self.client
distinct_id = uuid4()
distinct_id = str(uuid4())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this worked before O.o

success, msg = client.page(distinct_id, url="https://posthog.com/contact")
self.assertFalse(self.failed)
client.flush()
Expand Down
2 changes: 1 addition & 1 deletion posthog/test/consumer.py → posthog/test/test_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from posthog.consumer import MAX_MSG_SIZE, Consumer
from posthog.request import APIError
from posthog.test.utils import TEST_API_KEY
from posthog.test.test_utils import TEST_API_KEY


class TestConsumer(unittest.TestCase):
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion posthog/test/request.py → posthog/test/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import requests

from posthog.request import DatetimeSerializer, batch_post
from posthog.test.utils import TEST_API_KEY
from posthog.test.test_utils import TEST_API_KEY


class TestRequests(unittest.TestCase):
Expand Down
File renamed without changes.
6 changes: 2 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
"black",
"isort",
"pre-commit",
]
],
"test": ["mock>=2.0.0", "freezegun==0.3.15", "pylint", "flake8", "coverage"],
}

tests_require = ["mock>=2.0.0", "freezegun==0.3.15"]

setup(
name="posthog",
version=VERSION,
Expand All @@ -39,7 +38,6 @@
license="MIT License",
install_requires=install_requires,
extras_require=extras_require,
tests_require=tests_require,
description="Integrate PostHog into any python application.",
long_description=long_description,
classifiers=[
Expand Down
18 changes: 17 additions & 1 deletion simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ def identify():
)


def set_once():
posthog.set_once(
options.distinct_id,
properties=json_hash(options.traits),
context=json_hash(options.context),
)


Comment on lines +74 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit, but might be good to add "set" here for completion as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, makes sense

def set():
posthog.set(
options.distinct_id,
properties=json_hash(options.traits),
context=json_hash(options.context),
)


def unknown():
print()

Expand All @@ -84,7 +100,7 @@ def unknown():
ch.setLevel(logging.DEBUG)
log.addHandler(ch)

switcher = {"capture": capture, "page": page, "identify": identify}
switcher = {"capture": capture, "page": page, "identify": identify, "set_once": set_once, "set": set}

func = switcher.get(options.type)
if func:
Expand Down