Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888457387b | ||
|
|
fe45ff2ab0 | ||
|
|
221d7f09f3 | ||
|
|
98d2d4cc05 | ||
|
|
b7c1572c32 | ||
|
|
16acf2e278 | ||
|
|
df9ae05202 | ||
|
|
b05ee3884a | ||
|
|
144a7744e4 | ||
|
|
ca979f06c8 | ||
|
|
caa64fb6fe | ||
|
|
e007dee07e | ||
|
|
1de9553d61 | ||
|
|
d447d170fa | ||
|
|
d92c398c0f | ||
|
|
b331c4aae3 | ||
|
|
5e70ca84bb | ||
|
|
adef8d4928 | ||
|
|
8682091eec | ||
|
|
8977c4e3ab | ||
|
|
710ac05862 | ||
|
|
721a6aacf7 | ||
|
|
678e4ac97b | ||
|
|
72e7e4ad72 | ||
|
|
95d4375663 | ||
|
|
6e39aa0ceb | ||
|
|
c72ab9a3fd | ||
|
|
6508aa6994 | ||
|
|
3b7f37aa39 | ||
|
|
ac74ee9a5c | ||
|
|
b9e323bf47 | ||
|
|
8fee12f004 | ||
|
|
3c23f0159f | ||
|
|
c944d7df2c | ||
|
|
611e01f9eb | ||
|
|
1840bb8f57 |
@@ -0,0 +1,27 @@
|
||||
name: Backend CI
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Python tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
|
||||
- name: Install requirements.txt dependencies with pip
|
||||
run: |
|
||||
python -m pip install -e .
|
||||
|
||||
- name: Run posthog tests
|
||||
run: |
|
||||
python setup.py test
|
||||
@@ -12,3 +12,5 @@ env
|
||||
flake8.out
|
||||
pylint.out
|
||||
posthog-analytics
|
||||
.idea
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# PostHog Python
|
||||
|
||||
Official PostHog Python library to capture and send events to any PostHog instance (including PostHog.com).
|
||||
Please see the main [PostHog docs](https://posthog.com/docs).
|
||||
|
||||
This library uses an internal queue to make calls non-blocking and fast. It also batches requests and flushes asynchronously, making it perfect to use in any part of your web app or other server side application that needs performance.
|
||||
|
||||
See [PostHog docs](https://docs.posthog.com) for all documentation, including our [Python docs](https://docs.posthog.com/#/integrations/python-integration).
|
||||
Specifically, the [Python integration](https://posthog.com/docs/integrations/python-integration) details.
|
||||
|
||||
+13
-4
@@ -2,19 +2,28 @@
|
||||
|
||||
# Import the library
|
||||
import posthog
|
||||
import time
|
||||
|
||||
# You can find this key on the /setup page in PostHog
|
||||
posthog.api_key = '<your key>'
|
||||
posthog.api_key = ''
|
||||
posthog.personal_api_key = ''
|
||||
|
||||
# Where you host PostHog, with no trailing /.
|
||||
# You can remove this line if you're using posthog.com
|
||||
posthog.host = 'http://127.0.0.1:8000'
|
||||
posthog.host = 'http://127.0.0.1:8000'
|
||||
|
||||
# Capture an event
|
||||
posthog.capture('distinct_id', 'event', {'property1': 'value', 'property2': 'value'})
|
||||
|
||||
# Alias a previous distinct id with a new one
|
||||
print(posthog.feature_enabled('beta-feature', 'distinct_id'))
|
||||
|
||||
print('sleeping')
|
||||
time.sleep(45)
|
||||
|
||||
print(posthog.feature_enabled('beta-feature', 'distinct_id'))
|
||||
|
||||
# # Alias a previous distinct id with a new one
|
||||
posthog.alias('distinct_id', 'new_distinct_id')
|
||||
|
||||
# Add properties to the person
|
||||
# # Add properties to the person
|
||||
posthog.identify('distinct_id', {'email': 'something@something.com'})
|
||||
+22
-2
@@ -13,6 +13,7 @@ debug = False # type: bool
|
||||
send = True # type: bool
|
||||
sync_mode = False # type: bool
|
||||
disabled = False # type: bool
|
||||
personal_api_key = None # type: str
|
||||
|
||||
default_client = None
|
||||
|
||||
@@ -100,6 +101,25 @@ def alias(
|
||||
"""
|
||||
_proxy('alias', previous_id=previous_id, distinct_id=distinct_id, context=context, timestamp=timestamp, message_id=message_id)
|
||||
|
||||
def feature_enabled(
|
||||
key, # type: str,
|
||||
distinct_id, # type: str,
|
||||
default=False, # type: bool
|
||||
):
|
||||
# type: (...) -> bool
|
||||
"""
|
||||
Use feature flags to enable or disable features for users.
|
||||
|
||||
For example:
|
||||
```python
|
||||
if posthog.feature_enabled('beta feature', 'distinct id'):
|
||||
# do something
|
||||
```
|
||||
|
||||
You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.
|
||||
"""
|
||||
return _proxy('feature_enabled', key=key, distinct_id=distinct_id, default=default)
|
||||
|
||||
|
||||
def page(*args, **kwargs):
|
||||
"""Send a page call."""
|
||||
@@ -135,7 +155,7 @@ def _proxy(method, *args, **kwargs):
|
||||
if not default_client:
|
||||
default_client = Client(api_key, host=host, debug=debug,
|
||||
on_error=on_error, send=send,
|
||||
sync_mode=sync_mode)
|
||||
sync_mode=sync_mode, personal_api_key=personal_api_key)
|
||||
|
||||
fn = getattr(default_client, method)
|
||||
fn(*args, **kwargs)
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
+92
-66
@@ -1,16 +1,19 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from uuid import uuid4
|
||||
import logging
|
||||
import numbers
|
||||
import atexit
|
||||
import hashlib
|
||||
|
||||
import requests
|
||||
from dateutil.tz import tzutc
|
||||
from six import string_types
|
||||
|
||||
from posthog.utils import guess_timezone, clean
|
||||
from posthog.consumer import Consumer
|
||||
from posthog.request import post
|
||||
from posthog.request import batch_post, decide, get, APIError
|
||||
from posthog.version import VERSION
|
||||
from posthog.poller import Poller
|
||||
|
||||
try:
|
||||
import queue
|
||||
@@ -19,6 +22,7 @@ except ImportError:
|
||||
|
||||
|
||||
ID_TYPES = (numbers.Number, string_types)
|
||||
__LONG_SCALE__ = float(0xFFFFFFFFFFFFFFF)
|
||||
|
||||
|
||||
class Client(object):
|
||||
@@ -28,10 +32,12 @@ class Client(object):
|
||||
def __init__(self, api_key=None, host=None, debug=False,
|
||||
max_queue_size=10000, send=True, on_error=None, flush_at=100,
|
||||
flush_interval=0.5, gzip=False, max_retries=3,
|
||||
sync_mode=False, timeout=15, thread=1):
|
||||
sync_mode=False, timeout=15, thread=1, poll_interval=30, personal_api_key=None):
|
||||
require('api_key', api_key, string_types)
|
||||
|
||||
self.queue = queue.Queue(max_queue_size)
|
||||
|
||||
# api_key: This should be the Team API Key (token), public
|
||||
self.api_key = api_key
|
||||
self.on_error = on_error
|
||||
self.debug = debug
|
||||
@@ -40,6 +46,11 @@ class Client(object):
|
||||
self.host = host
|
||||
self.gzip = gzip
|
||||
self.timeout = timeout
|
||||
self.feature_flags = None
|
||||
self.poll_interval = poll_interval
|
||||
|
||||
# personal_api_key: This should be a generated Personal API Key, private
|
||||
self.personal_api_key = personal_api_key
|
||||
|
||||
if debug:
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
@@ -78,7 +89,6 @@ class Client(object):
|
||||
msg = {
|
||||
'timestamp': timestamp,
|
||||
'context': context,
|
||||
'type': 'identify',
|
||||
'distinct_id': distinct_id,
|
||||
'$set': properties,
|
||||
'event': '$identify',
|
||||
@@ -100,7 +110,6 @@ class Client(object):
|
||||
'timestamp': timestamp,
|
||||
'context': context,
|
||||
'distinct_id': distinct_id,
|
||||
'type': 'capture',
|
||||
'event': event,
|
||||
'messageId': message_id,
|
||||
}
|
||||
@@ -121,34 +130,12 @@ class Client(object):
|
||||
},
|
||||
'timestamp': timestamp,
|
||||
'context': context,
|
||||
'type': 'alias',
|
||||
'event': '$create_alias'
|
||||
}
|
||||
|
||||
return self._enqueue(msg)
|
||||
|
||||
def group(self, distinct_id=None, group_id=None, traits=None, context=None,
|
||||
timestamp=None, message_id=None):
|
||||
traits = traits or {}
|
||||
context = context or {}
|
||||
|
||||
require('distinct_id', distinct_id, ID_TYPES)
|
||||
require('group_id', group_id, ID_TYPES)
|
||||
require('traits', traits, dict)
|
||||
|
||||
msg = {
|
||||
'timestamp': timestamp,
|
||||
'groupId': group_id,
|
||||
'context': context,
|
||||
'distinct_id': distinct_id,
|
||||
'traits': traits,
|
||||
'type': 'group',
|
||||
'messageId': message_id,
|
||||
}
|
||||
|
||||
return self._enqueue(msg)
|
||||
|
||||
def page(self, distinct_id=None, category=None, name=None, properties=None,
|
||||
def page(self, distinct_id=None, url=None, properties=None,
|
||||
context=None, timestamp=None, message_id=None):
|
||||
properties = properties or {}
|
||||
context = context or {}
|
||||
@@ -156,46 +143,15 @@ class Client(object):
|
||||
require('distinct_id', distinct_id, ID_TYPES)
|
||||
require('properties', properties, dict)
|
||||
|
||||
if name:
|
||||
require('name', name, string_types)
|
||||
if category:
|
||||
require('category', category, string_types)
|
||||
require('url', url, string_types)
|
||||
properties['$current_url'] = url
|
||||
|
||||
msg = {
|
||||
'event': '$pageview',
|
||||
'properties': properties,
|
||||
'timestamp': timestamp,
|
||||
'category': category,
|
||||
'context': context,
|
||||
'distinct_id': distinct_id,
|
||||
'type': 'page',
|
||||
'name': name,
|
||||
'messageId': message_id,
|
||||
}
|
||||
|
||||
return self._enqueue(msg)
|
||||
|
||||
def screen(self, distinct_id=None, category=None, name=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)
|
||||
|
||||
if name:
|
||||
require('name', name, string_types)
|
||||
if category:
|
||||
require('category', category, string_types)
|
||||
|
||||
msg = {
|
||||
|
||||
'properties': properties,
|
||||
'timestamp': timestamp,
|
||||
'category': category,
|
||||
'context': context,
|
||||
'distinct_id': distinct_id,
|
||||
'type': 'screen',
|
||||
'name': name,
|
||||
'messageId': message_id,
|
||||
}
|
||||
|
||||
@@ -210,7 +166,6 @@ class Client(object):
|
||||
if message_id is None:
|
||||
message_id = uuid4()
|
||||
|
||||
require('type', msg['type'], string_types)
|
||||
require('timestamp', timestamp, datetime)
|
||||
require('context', msg['context'], dict)
|
||||
|
||||
@@ -233,15 +188,15 @@ class Client(object):
|
||||
return True, msg
|
||||
|
||||
if self.sync_mode:
|
||||
self.log.debug('enqueued with blocking %s.', msg['type'])
|
||||
post(self.api_key, self.host, gzip=self.gzip,
|
||||
self.log.debug('enqueued with blocking %s.', msg['event'])
|
||||
batch_post(self.api_key, self.host, gzip=self.gzip,
|
||||
timeout=self.timeout, batch=[msg])
|
||||
|
||||
return True, msg
|
||||
|
||||
try:
|
||||
self.queue.put(msg, block=False)
|
||||
self.log.debug('enqueued %s.', msg['type'])
|
||||
self.log.debug('enqueued %s.', msg['event'])
|
||||
return True, msg
|
||||
except queue.Full:
|
||||
self.log.warning('analytics-python queue is full')
|
||||
@@ -272,6 +227,77 @@ class Client(object):
|
||||
self.flush()
|
||||
self.join()
|
||||
|
||||
def _load_feature_flags(self):
|
||||
try:
|
||||
self.feature_flags = get(self.personal_api_key, '/api/feature_flag/', self.host)['results']
|
||||
except APIError as e:
|
||||
if e.status == 401:
|
||||
raise APIError(
|
||||
status=401,
|
||||
message='You are using a write-only key with feature flags. ' \
|
||||
'To use feature flags, please set a personal_api_key ' \
|
||||
'More information: https://posthog.com/docs/api/overview'
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.warning('[FEATURE FLAGS] Fetching feature flags failed with following error. We will retry in %s seconds.' % self.poll_interval)
|
||||
self.log.warning(e)
|
||||
|
||||
self._last_feature_flag_poll = datetime.utcnow().replace(tzinfo=tzutc())
|
||||
|
||||
def load_feature_flags(self):
|
||||
if not self.personal_api_key:
|
||||
self.log.warning('[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags.')
|
||||
self.feature_flags = []
|
||||
return
|
||||
|
||||
self._load_feature_flags()
|
||||
poller = Poller(interval=timedelta(seconds=self.poll_interval), execute=self._load_feature_flags)
|
||||
poller.start()
|
||||
|
||||
def feature_enabled(self, key, distinct_id, default=False):
|
||||
require('key', key, string_types)
|
||||
require('distinct_id', distinct_id, ID_TYPES)
|
||||
|
||||
if not self.personal_api_key:
|
||||
self.log.warning('[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags.')
|
||||
if not self.feature_flags:
|
||||
self.load_feature_flags()
|
||||
|
||||
# If loading in previous line failed
|
||||
if not self.feature_flags:
|
||||
response = default
|
||||
else:
|
||||
try:
|
||||
feature_flag = [flag for flag in self.feature_flags if flag['key'] == key][0]
|
||||
except IndexError:
|
||||
return default
|
||||
|
||||
if feature_flag.get('is_simple_flag'):
|
||||
response = _hash(key, distinct_id) <= (feature_flag['rollout_percentage'] / 100)
|
||||
else:
|
||||
try:
|
||||
request_data = {
|
||||
"distinct_id": distinct_id,
|
||||
"personal_api_key": self.personal_api_key,
|
||||
}
|
||||
resp_data = decide(self.api_key, self.host, timeout=10, **request_data)
|
||||
response = key in resp_data['featureFlags']
|
||||
except Exception as e:
|
||||
response = default
|
||||
self.log.warning('[FEATURE FLAGS] Unable to get data for flag %s, because of the following error:' % key)
|
||||
self.log.warning(e)
|
||||
|
||||
self.capture(distinct_id, '$feature_flag_called', {'$feature_flag': key, '$feature_flag_response': response})
|
||||
return response
|
||||
|
||||
# This function takes a distinct_id and a feature flag key and returns a float between 0 and 1.
|
||||
# Given the same distinct_id and key, it'll always return the same float. These floats are
|
||||
# uniformly distributed between 0 and 1, so if we want to show this feature to 20% of traffic
|
||||
# we can do _hash(key, distinct_id) < 0.2
|
||||
def _hash(key, distinct_id):
|
||||
hash_key = "%s.%s" % (key, distinct_id)
|
||||
hash_val = int(hashlib.sha1(hash_key.encode("utf-8")).hexdigest()[:15], 16)
|
||||
return hash_val / __LONG_SCALE__
|
||||
|
||||
def require(name, field, data_type):
|
||||
"""Require that the named `field` has the right `data_type`"""
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import monotonic
|
||||
import backoff
|
||||
import json
|
||||
|
||||
from posthog.request import post, APIError, DatetimeSerializer
|
||||
from posthog.request import batch_post, APIError, DatetimeSerializer
|
||||
|
||||
try:
|
||||
from queue import Empty
|
||||
@@ -128,7 +128,7 @@ class Consumer(Thread):
|
||||
max_tries=self.retries + 1,
|
||||
giveup=fatal_exception)
|
||||
def send_request():
|
||||
post(self.api_key, self.host, gzip=self.gzip,
|
||||
batch_post(self.api_key, self.host, gzip=self.gzip,
|
||||
timeout=self.timeout, batch=batch)
|
||||
|
||||
send_request()
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import threading
|
||||
|
||||
class Poller(threading.Thread):
|
||||
def __init__(self, interval, execute, *args, **kwargs):
|
||||
threading.Thread.__init__(self)
|
||||
self.daemon = False
|
||||
self.stopped = threading.Event()
|
||||
self.interval = interval
|
||||
self.execute = execute
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def stop(self):
|
||||
self.stopped.set()
|
||||
self.join()
|
||||
|
||||
def run(self):
|
||||
while not self.stopped.wait(self.interval.total_seconds()):
|
||||
self.execute(*self.args, **self.kwargs)
|
||||
+58
-11
@@ -4,27 +4,30 @@ import logging
|
||||
import json
|
||||
from gzip import GzipFile
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from requests import sessions
|
||||
import requests
|
||||
from io import BytesIO
|
||||
|
||||
from posthog.version import VERSION
|
||||
from posthog.utils import remove_trailing_slash
|
||||
|
||||
_session = sessions.Session()
|
||||
_session = requests.sessions.Session()
|
||||
|
||||
DEFAULT_HOST = 'https://app.posthog.com'
|
||||
USER_AGENT = 'posthog-python/' + VERSION
|
||||
|
||||
|
||||
def post(api_key, host=None, gzip=False, timeout=15, **kwargs):
|
||||
def post(api_key, host=None, path=None, gzip=False, timeout=15, **kwargs):
|
||||
"""Post the `kwargs` to the API"""
|
||||
log = logging.getLogger('posthog')
|
||||
body = kwargs
|
||||
body["sentAt"] = datetime.utcnow().replace(tzinfo=tzutc()).isoformat()
|
||||
url = remove_trailing_slash(host or 'https://t.posthog.com') + '/batch/'
|
||||
url = remove_trailing_slash(host or DEFAULT_HOST) + path
|
||||
body['api_key'] = api_key
|
||||
data = json.dumps(body, cls=DatetimeSerializer)
|
||||
log.debug('making request: %s', data)
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'analytics-python/' + VERSION
|
||||
'User-Agent': USER_AGENT
|
||||
}
|
||||
if gzip:
|
||||
headers['Content-Encoding'] = 'gzip'
|
||||
@@ -42,24 +45,68 @@ def post(api_key, host=None, gzip=False, timeout=15, **kwargs):
|
||||
log.debug('data uploaded successfully')
|
||||
return res
|
||||
|
||||
|
||||
def decide(api_key, host=None, gzip=False, timeout=15, **kwargs):
|
||||
"""Post the `kwargs to the decide API endpoint"""
|
||||
log = logging.getLogger('posthog')
|
||||
res = post(api_key, host, '/decide/', gzip, timeout, **kwargs)
|
||||
if res.status_code == 200:
|
||||
log.debug('Feature flags decided successfully')
|
||||
return res.json()
|
||||
try:
|
||||
payload = res.json()
|
||||
log.debug('received response: %s', payload)
|
||||
raise APIError(res.status_code, payload['code'], payload['message'])
|
||||
raise APIError(res.status_code, payload['detail'])
|
||||
except ValueError:
|
||||
raise APIError(res.status_code, 'unknown', res.text)
|
||||
raise APIError(res.status_code, res.text)
|
||||
|
||||
|
||||
def batch_post(api_key, host=None, gzip=False, timeout=15, **kwargs):
|
||||
"""Post the `kwargs` to the batch API endpoint for events"""
|
||||
log = logging.getLogger('posthog')
|
||||
res = post(api_key, host, '/batch/', gzip, timeout, **kwargs)
|
||||
|
||||
if res.status_code == 200:
|
||||
log.debug('data uploaded successfully')
|
||||
return res
|
||||
try:
|
||||
payload = res.json()
|
||||
log.debug('received response: %s', payload)
|
||||
raise APIError(res.status_code, payload['detail'])
|
||||
except ValueError:
|
||||
raise APIError(res.status_code, res.text)
|
||||
|
||||
|
||||
def get(api_key, url, host=None, timeout=None):
|
||||
log = logging.getLogger('posthog')
|
||||
url = remove_trailing_slash(host or DEFAULT_HOST) + url
|
||||
response = requests.get(
|
||||
url,
|
||||
headers={
|
||||
'Authorization': 'Bearer %s' % api_key,
|
||||
'User-Agent': USER_AGENT
|
||||
},
|
||||
timeout=timeout
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
try:
|
||||
payload = response.json()
|
||||
log.debug('received response: %s', payload)
|
||||
raise APIError(response.status_code, payload['detail'])
|
||||
except ValueError:
|
||||
raise APIError(response.status_code, response.text)
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
|
||||
def __init__(self, status, code, message):
|
||||
def __init__(self, status, message):
|
||||
self.message = message
|
||||
self.status = status
|
||||
self.code = code
|
||||
|
||||
def __str__(self):
|
||||
msg = "[PostHog] {0}: {1} ({2})"
|
||||
return msg.format(self.code, self.message, self.status)
|
||||
msg = "[PostHog] {0} ({1})"
|
||||
return msg.format(self.message, self.status)
|
||||
|
||||
|
||||
class DatetimeSerializer(json.JSONEncoder):
|
||||
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
|
||||
def all_names():
|
||||
for _, modname, _ in pkgutil.iter_modules(__path__):
|
||||
yield 'analytics.test.' + modname
|
||||
yield 'posthog.test.' + modname
|
||||
|
||||
|
||||
def all():
|
||||
|
||||
+124
-123
@@ -3,20 +3,24 @@ import unittest
|
||||
import six
|
||||
import mock
|
||||
import time
|
||||
from freezegun import freeze_time
|
||||
|
||||
from posthog.version import VERSION
|
||||
from posthog.client import Client
|
||||
from posthog.test.utils import TEST_API_KEY
|
||||
|
||||
from posthog.request import APIError
|
||||
|
||||
class TestClient(unittest.TestCase):
|
||||
|
||||
def fail(self, e, batch):
|
||||
def set_fail(self, e, batch):
|
||||
"""Mark the failure handler"""
|
||||
print('FAIL', e, batch)
|
||||
self.failed = True
|
||||
|
||||
def setUp(self):
|
||||
self.failed = False
|
||||
self.client = Client('testsecret', on_error=self.fail)
|
||||
self.client = Client(TEST_API_KEY, on_error=self.set_fail)
|
||||
|
||||
def test_requires_api_key(self):
|
||||
self.assertRaises(AssertionError, Client)
|
||||
@@ -24,9 +28,9 @@ class TestClient(unittest.TestCase):
|
||||
def test_empty_flush(self):
|
||||
self.client.flush()
|
||||
|
||||
def test_basic_track(self):
|
||||
def test_basic_capture(self):
|
||||
client = self.client
|
||||
success, msg = client.track('distinct_id', 'python test event')
|
||||
success, msg = client.capture('distinct_id', 'python test event')
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
self.assertFalse(self.failed)
|
||||
@@ -35,14 +39,14 @@ class TestClient(unittest.TestCase):
|
||||
self.assertTrue(isinstance(msg['timestamp'], str))
|
||||
self.assertTrue(isinstance(msg['messageId'], str))
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['properties'], {})
|
||||
self.assertEqual(msg['type'], 'track')
|
||||
self.assertEqual(msg['properties']['$lib'], 'posthog-python')
|
||||
self.assertEqual(msg['properties']['$lib_version'], VERSION)
|
||||
|
||||
def test_stringifies_distinct_id(self):
|
||||
# A large number that loses precision in node:
|
||||
# node -e "console.log(157963456373623802 + 1)" > 157963456373623800
|
||||
client = self.client
|
||||
success, msg = client.track(
|
||||
success, msg = client.capture(
|
||||
distinct_id=157963456373623802, event='python test event')
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
@@ -50,9 +54,9 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
self.assertEqual(msg['distinct_id'], '157963456373623802')
|
||||
|
||||
def test_advanced_track(self):
|
||||
def test_advanced_capture(self):
|
||||
client = self.client
|
||||
success, msg = client.track(
|
||||
success, msg = client.capture(
|
||||
'distinct_id', 'python test event', {'property': 'value'},
|
||||
{'ip': '192.168.0.1'}, datetime(2014, 9, 3),
|
||||
'messageId')
|
||||
@@ -60,14 +64,13 @@ class TestClient(unittest.TestCase):
|
||||
self.assertTrue(success)
|
||||
|
||||
self.assertEqual(msg['timestamp'], '2014-09-03T00:00:00+00:00')
|
||||
self.assertEqual(msg['properties'], {'property': 'value'})
|
||||
self.assertEqual(msg['properties']['property'], 'value')
|
||||
self.assertEqual(msg['context']['ip'], '192.168.0.1')
|
||||
self.assertEqual(msg['event'], 'python test event')
|
||||
self.assertEqual(msg['properties']['$lib'], 'posthog-python')
|
||||
self.assertEqual(msg['properties']['$lib_version'], VERSION)
|
||||
self.assertEqual(msg['messageId'], 'messageId')
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['type'], 'track')
|
||||
|
||||
def test_basic_identify(self):
|
||||
client = self.client
|
||||
@@ -76,11 +79,10 @@ class TestClient(unittest.TestCase):
|
||||
self.assertTrue(success)
|
||||
self.assertFalse(self.failed)
|
||||
|
||||
self.assertEqual(msg['traits'], {'trait': 'value'})
|
||||
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')
|
||||
self.assertEqual(msg['type'], 'identify')
|
||||
|
||||
def test_advanced_identify(self):
|
||||
client = self.client
|
||||
@@ -92,46 +94,12 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
self.assertEqual(msg['timestamp'], '2014-09-03T00:00:00+00:00')
|
||||
self.assertEqual(msg['context']['ip'], '192.168.0.1')
|
||||
self.assertEqual(msg['traits'], {'trait': 'value'})
|
||||
self.assertEqual(msg['context']['library'], {
|
||||
'name': 'analytics-python',
|
||||
'version': VERSION
|
||||
})
|
||||
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')
|
||||
self.assertEqual(msg['type'], 'identify')
|
||||
|
||||
def test_basic_group(self):
|
||||
client = self.client
|
||||
success, msg = client.group('distinct_id', 'groupId')
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
self.assertFalse(self.failed)
|
||||
|
||||
self.assertEqual(msg['groupId'], 'groupId')
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['type'], 'group')
|
||||
|
||||
def test_advanced_group(self):
|
||||
client = self.client
|
||||
success, msg = client.group(
|
||||
'distinct_id', 'groupId', {'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['traits'], {'trait': 'value'})
|
||||
self.assertEqual(msg['context']['library'], {
|
||||
'name': 'analytics-python',
|
||||
'version': VERSION
|
||||
})
|
||||
self.assertTrue(isinstance(msg['timestamp'], str))
|
||||
self.assertEqual(msg['messageId'], 'messageId')
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['type'], 'group')
|
||||
|
||||
def test_basic_alias(self):
|
||||
client = self.client
|
||||
@@ -139,71 +107,35 @@ class TestClient(unittest.TestCase):
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
self.assertFalse(self.failed)
|
||||
self.assertEqual(msg['previousId'], 'previousId')
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['properties']['distinct_id'], 'previousId')
|
||||
self.assertEqual(msg['properties']['alias'], 'distinct_id')
|
||||
|
||||
def test_basic_page(self):
|
||||
client = self.client
|
||||
success, msg = client.page('distinct_id', name='name')
|
||||
success, msg = client.page('distinct_id', url='https://posthog.com/contact')
|
||||
self.assertFalse(self.failed)
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['type'], 'page')
|
||||
self.assertEqual(msg['name'], 'name')
|
||||
self.assertEqual(msg['properties']['$current_url'], 'https://posthog.com/contact')
|
||||
|
||||
def test_advanced_page(self):
|
||||
client = self.client
|
||||
success, msg = client.page(
|
||||
'distinct_id', 'category', 'name', {'property': 'value'},
|
||||
'distinct_id', 'https://posthog.com/contact', {'property': '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['properties'], {'property': 'value'})
|
||||
self.assertEqual(msg['context']['library'], {
|
||||
'name': 'analytics-python',
|
||||
'version': VERSION
|
||||
})
|
||||
self.assertEqual(msg['category'], 'category')
|
||||
self.assertEqual(msg['properties']['$current_url'], 'https://posthog.com/contact')
|
||||
self.assertEqual(msg['properties']['property'], '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')
|
||||
self.assertEqual(msg['type'], 'page')
|
||||
self.assertEqual(msg['name'], 'name')
|
||||
|
||||
def test_basic_screen(self):
|
||||
client = self.client
|
||||
success, msg = client.screen('distinct_id', name='name')
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['type'], 'screen')
|
||||
self.assertEqual(msg['name'], 'name')
|
||||
|
||||
def test_advanced_screen(self):
|
||||
client = self.client
|
||||
success, msg = client.screen(
|
||||
'distinct_id', 'category', 'name', {'property': '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['properties'], {'property': 'value'})
|
||||
self.assertEqual(msg['context']['library'], {
|
||||
'name': 'analytics-python',
|
||||
'version': VERSION
|
||||
})
|
||||
self.assertTrue(isinstance(msg['timestamp'], str))
|
||||
self.assertEqual(msg['messageId'], 'messageId')
|
||||
self.assertEqual(msg['category'], 'category')
|
||||
self.assertEqual(msg['distinct_id'], 'distinct_id')
|
||||
self.assertEqual(msg['type'], 'screen')
|
||||
self.assertEqual(msg['name'], 'name')
|
||||
|
||||
def test_flush(self):
|
||||
client = self.client
|
||||
@@ -230,7 +162,7 @@ class TestClient(unittest.TestCase):
|
||||
self.assertFalse(consumer.is_alive())
|
||||
|
||||
def test_synchronous(self):
|
||||
client = Client('testsecret', sync_mode=True)
|
||||
client = Client(TEST_API_KEY, sync_mode=True)
|
||||
|
||||
success, message = client.identify('distinct_id')
|
||||
self.assertFalse(client.consumers)
|
||||
@@ -238,7 +170,7 @@ class TestClient(unittest.TestCase):
|
||||
self.assertTrue(success)
|
||||
|
||||
def test_overflow(self):
|
||||
client = Client('testsecret', max_queue_size=1)
|
||||
client = Client(TEST_API_KEY, max_queue_size=1)
|
||||
# Ensure consumer thread is no longer uploading
|
||||
client.join()
|
||||
|
||||
@@ -249,46 +181,26 @@ class TestClient(unittest.TestCase):
|
||||
# Make sure we are informed that the queue is at capacity
|
||||
self.assertFalse(success)
|
||||
|
||||
def test_success_on_invalid_api_key(self):
|
||||
client = Client('bad_key', on_error=self.fail)
|
||||
client.track('distinct_id', 'event')
|
||||
client.flush()
|
||||
self.assertFalse(self.failed)
|
||||
|
||||
def test_unicode(self):
|
||||
Client(six.u('unicode_key'))
|
||||
|
||||
def test_numeric_distinct_id(self):
|
||||
self.client.track(1234, 'python event')
|
||||
self.client.capture(1234, 'python event')
|
||||
self.client.flush()
|
||||
self.assertFalse(self.failed)
|
||||
|
||||
def test_debug(self):
|
||||
Client('bad_key', debug=True)
|
||||
|
||||
def test_identify_with_date_object(self):
|
||||
client = self.client
|
||||
success, msg = client.identify(
|
||||
'distinct_id',
|
||||
{
|
||||
'birthdate': date(1981, 2, 2),
|
||||
},
|
||||
)
|
||||
client.flush()
|
||||
self.assertTrue(success)
|
||||
self.assertFalse(self.failed)
|
||||
|
||||
self.assertEqual(msg['traits'], {'birthdate': date(1981, 2, 2)})
|
||||
|
||||
def test_gzip(self):
|
||||
client = Client('testsecret', on_error=self.fail, gzip=True)
|
||||
client = Client(TEST_API_KEY, on_error=self.fail, gzip=True)
|
||||
for _ in range(10):
|
||||
client.identify('distinct_id', {'trait': 'value'})
|
||||
client.flush()
|
||||
self.assertFalse(self.failed)
|
||||
|
||||
def test_user_defined_flush_at(self):
|
||||
client = Client('testsecret', on_error=self.fail,
|
||||
client = Client(TEST_API_KEY, on_error=self.fail,
|
||||
flush_at=10, flush_interval=3)
|
||||
|
||||
def mock_post_fn(*args, **kwargs):
|
||||
@@ -296,7 +208,7 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
# the post function should be called 2 times, with a batch size of 10
|
||||
# each time.
|
||||
with mock.patch('analytics.consumer.post', side_effect=mock_post_fn) \
|
||||
with mock.patch('posthog.consumer.batch_post', side_effect=mock_post_fn) \
|
||||
as mock_post:
|
||||
for _ in range(20):
|
||||
client.identify('distinct_id', {'trait': 'value'})
|
||||
@@ -304,11 +216,100 @@ class TestClient(unittest.TestCase):
|
||||
self.assertEquals(mock_post.call_count, 2)
|
||||
|
||||
def test_user_defined_timeout(self):
|
||||
client = Client('testsecret', timeout=10)
|
||||
client = Client(TEST_API_KEY, timeout=10)
|
||||
for consumer in client.consumers:
|
||||
self.assertEquals(consumer.timeout, 10)
|
||||
|
||||
def test_default_timeout_15(self):
|
||||
client = Client('testsecret')
|
||||
client = Client(TEST_API_KEY)
|
||||
for consumer in client.consumers:
|
||||
self.assertEquals(consumer.timeout, 15)
|
||||
|
||||
@mock.patch('posthog.client.Poller')
|
||||
@mock.patch('posthog.client.get')
|
||||
def test_load_feature_flags(self, patch_get, patch_poll):
|
||||
patch_get.return_value = {
|
||||
'results': [{
|
||||
'id': 1,
|
||||
'name': 'Beta Feature',
|
||||
'key': 'beta-feature'
|
||||
}]
|
||||
}
|
||||
client = Client(TEST_API_KEY, personal_api_key='test')
|
||||
with freeze_time('2020-01-01T12:01:00.0000Z'):
|
||||
client.load_feature_flags()
|
||||
self.assertEqual(client.feature_flags[0]['key'], 'beta-feature')
|
||||
self.assertEqual(client._last_feature_flag_poll.isoformat(), "2020-01-01T12:01:00+00:00")
|
||||
self.assertEqual(patch_poll.call_count, 1)
|
||||
|
||||
def test_load_feature_flags_wrong_key(self):
|
||||
client = Client(TEST_API_KEY, personal_api_key=TEST_API_KEY)
|
||||
with freeze_time('2020-01-01T12:01:00.0000Z'):
|
||||
self.assertRaises(APIError, client.load_feature_flags)
|
||||
|
||||
@mock.patch('posthog.client.get')
|
||||
def test_feature_enabled_simple(self, patch_get):
|
||||
client = Client(TEST_API_KEY)
|
||||
client.feature_flags = [{
|
||||
'id': 1,
|
||||
'name': 'Beta Feature',
|
||||
'key': 'beta-feature',
|
||||
'is_simple_flag': True,
|
||||
'rollout_percentage': 100
|
||||
}]
|
||||
self.assertTrue(client.feature_enabled('beta-feature', 'distinct_id'))
|
||||
|
||||
@mock.patch('posthog.client.decide')
|
||||
def test_feature_enabled_request(self, patch_get):
|
||||
patch_get.return_value = {
|
||||
'featureFlags': ['beta-feature']
|
||||
}
|
||||
client = Client(TEST_API_KEY)
|
||||
client.feature_flags = [{
|
||||
'id': 1,
|
||||
'name': 'Beta Feature',
|
||||
'key': 'beta-feature',
|
||||
'is_simple_flag': False,
|
||||
'rollout_percentage': 100
|
||||
}]
|
||||
self.assertTrue(client.feature_enabled('beta-feature', 'distinct_id'))
|
||||
|
||||
@mock.patch('posthog.client.Poller')
|
||||
@mock.patch('posthog.client.get')
|
||||
def test_feature_enabled_doesnt_exist(self, patch_get, patch_poll):
|
||||
client = Client(TEST_API_KEY, personal_api_key='test')
|
||||
client.feature_flags = []
|
||||
|
||||
self.assertFalse(client.feature_enabled('doesnt-exist', 'distinct_id'))
|
||||
self.assertTrue(client.feature_enabled('doesnt-exist', 'distinct_id', True))
|
||||
|
||||
@mock.patch('posthog.client.Poller')
|
||||
@mock.patch('posthog.client.get')
|
||||
def test_personal_api_key_doesnt_exist(self, patch_get, patch_poll):
|
||||
client = Client(TEST_API_KEY)
|
||||
client.feature_flags = []
|
||||
|
||||
self.assertFalse(client.feature_enabled('doesnt-exist', 'distinct_id'))
|
||||
self.assertTrue(client.feature_enabled('doesnt-exist', 'distinct_id', True))
|
||||
|
||||
@mock.patch('posthog.client.Poller')
|
||||
@mock.patch('posthog.client.get')
|
||||
def test_load_feature_flags_error(self, patch_get, patch_poll):
|
||||
def raise_effect():
|
||||
raise Exception('http exception')
|
||||
patch_get.return_value.raiseError.side_effect = raise_effect
|
||||
client = Client(TEST_API_KEY, personal_api_key='test')
|
||||
client.feature_flags = []
|
||||
|
||||
self.assertFalse(client.feature_enabled('doesnt-exist', 'distinct_id'))
|
||||
|
||||
@mock.patch('posthog.client.Poller')
|
||||
@mock.patch('posthog.client.get')
|
||||
def test_call_identify_fails(self, patch_get, patch_poll):
|
||||
def raise_effect():
|
||||
raise Exception('http exception')
|
||||
patch_get.return_value.raiseError.side_effect = raise_effect
|
||||
client = Client(TEST_API_KEY, personal_api_key='test')
|
||||
client.feature_flags = [{'key': 'example', 'is_simple_flag': False}]
|
||||
|
||||
self.assertFalse(client.feature_enabled('example', 'distinct_id'))
|
||||
+20
-19
@@ -10,6 +10,7 @@ except ImportError:
|
||||
|
||||
from posthog.consumer import Consumer, MAX_MSG_SIZE
|
||||
from posthog.request import APIError
|
||||
from posthog.test.utils import TEST_API_KEY
|
||||
|
||||
|
||||
class TestConsumer(unittest.TestCase):
|
||||
@@ -41,7 +42,7 @@ class TestConsumer(unittest.TestCase):
|
||||
|
||||
def test_upload(self):
|
||||
q = Queue()
|
||||
consumer = Consumer(q, 'testsecret')
|
||||
consumer = Consumer(q, TEST_API_KEY)
|
||||
track = {
|
||||
'type': 'track',
|
||||
'event': 'python event',
|
||||
@@ -57,9 +58,9 @@ class TestConsumer(unittest.TestCase):
|
||||
# The consumer should upload _n_ times.
|
||||
q = Queue()
|
||||
flush_interval = 0.3
|
||||
consumer = Consumer(q, 'testsecret', flush_at=10,
|
||||
consumer = Consumer(q, TEST_API_KEY, flush_at=10,
|
||||
flush_interval=flush_interval)
|
||||
with mock.patch('analytics.consumer.post') as mock_post:
|
||||
with mock.patch('posthog.consumer.batch_post') as mock_post:
|
||||
consumer.start()
|
||||
for i in range(0, 3):
|
||||
track = {
|
||||
@@ -77,9 +78,9 @@ class TestConsumer(unittest.TestCase):
|
||||
q = Queue()
|
||||
flush_interval = 0.5
|
||||
flush_at = 10
|
||||
consumer = Consumer(q, 'testsecret', flush_at=flush_at,
|
||||
consumer = Consumer(q, TEST_API_KEY, flush_at=flush_at,
|
||||
flush_interval=flush_interval)
|
||||
with mock.patch('analytics.consumer.post') as mock_post:
|
||||
with mock.patch('posthog.consumer.batch_post') as mock_post:
|
||||
consumer.start()
|
||||
for i in range(0, flush_at * 2):
|
||||
track = {
|
||||
@@ -92,7 +93,7 @@ class TestConsumer(unittest.TestCase):
|
||||
self.assertEqual(mock_post.call_count, 2)
|
||||
|
||||
def test_request(self):
|
||||
consumer = Consumer(None, 'testsecret')
|
||||
consumer = Consumer(None, TEST_API_KEY)
|
||||
track = {
|
||||
'type': 'track',
|
||||
'event': 'python event',
|
||||
@@ -109,7 +110,7 @@ class TestConsumer(unittest.TestCase):
|
||||
raise expected_exception
|
||||
mock_post.call_count = 0
|
||||
|
||||
with mock.patch('analytics.consumer.post',
|
||||
with mock.patch('posthog.consumer.batch_post',
|
||||
mock.Mock(side_effect=mock_post)):
|
||||
track = {
|
||||
'type': 'track',
|
||||
@@ -135,22 +136,22 @@ class TestConsumer(unittest.TestCase):
|
||||
|
||||
def test_request_retry(self):
|
||||
# we should retry on general errors
|
||||
consumer = Consumer(None, 'testsecret')
|
||||
consumer = Consumer(None, TEST_API_KEY)
|
||||
self._test_request_retry(consumer, Exception('generic exception'), 2)
|
||||
|
||||
# we should retry on server errors
|
||||
consumer = Consumer(None, 'testsecret')
|
||||
consumer = Consumer(None, TEST_API_KEY)
|
||||
self._test_request_retry(consumer, APIError(
|
||||
500, 'code', 'Internal Server Error'), 2)
|
||||
500, 'Internal Server Error'), 2)
|
||||
|
||||
# we should retry on HTTP 429 errors
|
||||
consumer = Consumer(None, 'testsecret')
|
||||
consumer = Consumer(None, TEST_API_KEY)
|
||||
self._test_request_retry(consumer, APIError(
|
||||
429, 'code', 'Too Many Requests'), 2)
|
||||
429, 'Too Many Requests'), 2)
|
||||
|
||||
# we should NOT retry on other client errors
|
||||
consumer = Consumer(None, 'testsecret')
|
||||
api_error = APIError(400, 'code', 'Client Errors')
|
||||
consumer = Consumer(None, TEST_API_KEY)
|
||||
api_error = APIError(400, 'Client Errors')
|
||||
try:
|
||||
self._test_request_retry(consumer, api_error, 1)
|
||||
except APIError:
|
||||
@@ -159,19 +160,19 @@ class TestConsumer(unittest.TestCase):
|
||||
self.fail('request() should not retry on client errors')
|
||||
|
||||
# test for number of exceptions raise > retries value
|
||||
consumer = Consumer(None, 'testsecret', retries=3)
|
||||
consumer = Consumer(None, TEST_API_KEY, retries=3)
|
||||
self._test_request_retry(consumer, APIError(
|
||||
500, 'code', 'Internal Server Error'), 3)
|
||||
500, 'Internal Server Error'), 3)
|
||||
|
||||
def test_pause(self):
|
||||
consumer = Consumer(None, 'testsecret')
|
||||
consumer = Consumer(None, TEST_API_KEY)
|
||||
consumer.pause()
|
||||
self.assertFalse(consumer.running)
|
||||
|
||||
def test_max_batch_size(self):
|
||||
q = Queue()
|
||||
consumer = Consumer(
|
||||
q, 'testsecret', flush_at=100000, flush_interval=3)
|
||||
q, TEST_API_KEY, flush_at=100000, flush_interval=3)
|
||||
track = {
|
||||
'type': 'track',
|
||||
'event': 'python event',
|
||||
@@ -189,7 +190,7 @@ class TestConsumer(unittest.TestCase):
|
||||
% len(data.encode()))
|
||||
return res
|
||||
|
||||
with mock.patch('analytics.request._session.post',
|
||||
with mock.patch('posthog.request._session.post',
|
||||
side_effect=mock_post_fn) as mock_post:
|
||||
consumer.start()
|
||||
for _ in range(0, n_msgs + 2):
|
||||
|
||||
+16
-24
@@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
import analytics
|
||||
import posthog
|
||||
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
@@ -10,40 +10,32 @@ class TestModule(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.failed = False
|
||||
analytics.api_key = 'testsecret'
|
||||
analytics.on_error = self.failed
|
||||
posthog.api_key = 'testsecret'
|
||||
posthog.on_error = self.failed
|
||||
|
||||
def test_no_api_key(self):
|
||||
analytics.api_key = None
|
||||
self.assertRaises(Exception, analytics.track)
|
||||
posthog.api_key = None
|
||||
self.assertRaises(Exception, posthog.capture)
|
||||
|
||||
def test_no_host(self):
|
||||
analytics.host = None
|
||||
self.assertRaises(Exception, analytics.track)
|
||||
posthog.host = None
|
||||
self.assertRaises(Exception, posthog.capture)
|
||||
|
||||
def test_track(self):
|
||||
analytics.track('distinct_id', 'python module event')
|
||||
analytics.flush()
|
||||
posthog.capture('distinct_id', 'python module event')
|
||||
posthog.flush()
|
||||
|
||||
def test_identify(self):
|
||||
analytics.identify('distinct_id', {'email': 'user@email.com'})
|
||||
analytics.flush()
|
||||
|
||||
def test_group(self):
|
||||
analytics.group('distinct_id', 'groupId')
|
||||
analytics.flush()
|
||||
posthog.identify('distinct_id', {'email': 'user@email.com'})
|
||||
posthog.flush()
|
||||
|
||||
def test_alias(self):
|
||||
analytics.alias('previousId', 'distinct_id')
|
||||
analytics.flush()
|
||||
posthog.alias('previousId', 'distinct_id')
|
||||
posthog.flush()
|
||||
|
||||
def test_page(self):
|
||||
analytics.page('distinct_id')
|
||||
analytics.flush()
|
||||
|
||||
def test_screen(self):
|
||||
analytics.screen('distinct_id')
|
||||
analytics.flush()
|
||||
posthog.page('distinct_id', 'https://posthog.com/contact')
|
||||
posthog.flush()
|
||||
|
||||
def test_flush(self):
|
||||
analytics.flush()
|
||||
posthog.flush()
|
||||
|
||||
@@ -3,13 +3,14 @@ import unittest
|
||||
import json
|
||||
import requests
|
||||
|
||||
from posthog.request import post, DatetimeSerializer
|
||||
from posthog.request import batch_post, DatetimeSerializer
|
||||
from posthog.test.utils import TEST_API_KEY
|
||||
|
||||
|
||||
class TestRequests(unittest.TestCase):
|
||||
|
||||
def test_valid_request(self):
|
||||
res = post(batch=[{
|
||||
res = batch_post(TEST_API_KEY, batch=[{
|
||||
'distinct_id': 'distinct_id',
|
||||
'event': 'python event',
|
||||
'type': 'track'
|
||||
@@ -17,11 +18,11 @@ class TestRequests(unittest.TestCase):
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_invalid_request_error(self):
|
||||
self.assertRaises(Exception, post, 'testsecret',
|
||||
self.assertRaises(Exception, batch_post, 'testsecret',
|
||||
'https://t.posthog.com', False, '[{]')
|
||||
|
||||
def test_invalid_host(self):
|
||||
self.assertRaises(Exception, post, 'testsecret',
|
||||
self.assertRaises(Exception, batch_post, 'testsecret',
|
||||
't.posthog.com/', batch=[])
|
||||
|
||||
def test_datetime_serialization(self):
|
||||
@@ -37,7 +38,7 @@ class TestRequests(unittest.TestCase):
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_should_not_timeout(self):
|
||||
res = post(batch=[{
|
||||
res = batch_post(TEST_API_KEY, batch=[{
|
||||
'distinct_id': 'distinct_id',
|
||||
'event': 'python event',
|
||||
'type': 'track'
|
||||
@@ -46,7 +47,7 @@ class TestRequests(unittest.TestCase):
|
||||
|
||||
def test_should_timeout(self):
|
||||
with self.assertRaises(requests.ReadTimeout):
|
||||
post(batch=[{
|
||||
batch_post('key', batch=[{
|
||||
'distinct_id': 'distinct_id',
|
||||
'event': 'python event',
|
||||
'type': 'track'
|
||||
|
||||
@@ -7,6 +7,7 @@ import six
|
||||
|
||||
from posthog import utils
|
||||
|
||||
TEST_API_KEY = 'kOOlRy2QlMY9jHZQv0bKz0FZyazBUoY8Arj0lFVNjs4'
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
VERSION = '1.0.10'
|
||||
VERSION = '1.1.3'
|
||||
|
||||
@@ -24,7 +24,8 @@ install_requires = [
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
"mock>=2.0.0"
|
||||
"mock>=2.0.0",
|
||||
"freezegun==0.3.15"
|
||||
]
|
||||
|
||||
setup(
|
||||
|
||||
+12
-24
@@ -1,4 +1,4 @@
|
||||
import analytics
|
||||
import posthog
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
@@ -12,7 +12,7 @@ def json_hash(str):
|
||||
if str:
|
||||
return json.loads(str)
|
||||
|
||||
# analytics -method=<method> -posthog-write-key=<posthogWriteKey> [options]
|
||||
# posthog -method=<method> -posthog-write-key=<posthogWriteKey> [options]
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='send a posthog message')
|
||||
@@ -45,38 +45,28 @@ def failed(status, msg):
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
def track():
|
||||
analytics.track(options.distinct_id, options.event, anonymous_id=options.anonymousId,
|
||||
def capture():
|
||||
posthog.capture(options.distinct_id, options.event, anonymous_id=options.anonymousId,
|
||||
properties=json_hash(options.properties), context=json_hash(options.context))
|
||||
|
||||
|
||||
def page():
|
||||
analytics.page(options.distinct_id, name=options.name, anonymous_id=options.anonymousId,
|
||||
posthog.page(options.distinct_id, name=options.name, anonymous_id=options.anonymousId,
|
||||
properties=json_hash(options.properties), context=json_hash(options.context))
|
||||
|
||||
|
||||
def screen():
|
||||
analytics.screen(options.distinct_id, name=options.name, anonymous_id=options.anonymousId,
|
||||
properties=json_hash(options.properties), context=json_hash(options.context))
|
||||
|
||||
|
||||
def identify():
|
||||
analytics.identify(options.distinct_id, anonymous_id=options.anonymousId,
|
||||
posthog.identify(options.distinct_id, anonymous_id=options.anonymousId,
|
||||
traits=json_hash(options.traits), context=json_hash(options.context))
|
||||
|
||||
|
||||
def group():
|
||||
analytics.group(options.distinct_id, options.groupId, json_hash(options.traits),
|
||||
json_hash(options.context), anonymous_id=options.anonymousId)
|
||||
|
||||
|
||||
def unknown():
|
||||
print()
|
||||
|
||||
|
||||
analytics.api_key = options.writeKey
|
||||
analytics.on_error = failed
|
||||
analytics.debug = True
|
||||
posthog.api_key = options.writeKey
|
||||
posthog.on_error = failed
|
||||
posthog.debug = True
|
||||
|
||||
log = logging.getLogger('posthog')
|
||||
ch = logging.StreamHandler()
|
||||
@@ -84,16 +74,14 @@ ch.setLevel(logging.DEBUG)
|
||||
log.addHandler(ch)
|
||||
|
||||
switcher = {
|
||||
"track": track,
|
||||
"capture": capture,
|
||||
"page": page,
|
||||
"screen": screen,
|
||||
"identify": identify,
|
||||
"group": group
|
||||
"identify": identify
|
||||
}
|
||||
|
||||
func = switcher.get(options.type)
|
||||
if func:
|
||||
func()
|
||||
analytics.shutdown()
|
||||
posthog.shutdown()
|
||||
else:
|
||||
print("Invalid Message Type " + options.type)
|
||||
|
||||
Reference in New Issue
Block a user