Compare commits

...
8 Commits
Author SHA1 Message Date
Michael Matloka d52b605742 Rerun CI 2020-12-15 11:14:18 +01:00
Michael Matloka a5f2e030b5 Allow passing in UUID as property 2020-11-03 17:14:00 +01:00
Tim GlaserandGitHub 98d2d4cc05 Merge pull request #12 from PostHog/investigate
Send distinctID to the decide endpoint to determin if user should have features enabled
2020-09-30 14:34:43 +01:00
James Greenhill b7c1572c32 bump version to 1.1.2 2020-09-30 14:25:49 +01:00
James Greenhill 16acf2e278 better testing and org 2020-09-30 14:20:50 +01:00
James Greenhill df9ae05202 fix tests 2020-09-30 14:10:34 +01:00
James Greenhill b05ee3884a Send distinctID to the decide endpoint to determin if user should have features enabled 2020-09-30 13:53:47 +01:00
James GreenhillandGitHub 144a7744e4 Merge pull request #11 from PostHog/none
Default to False if the feature flag key does not exist. None is confusing
2020-09-29 17:06:53 +01:00
9 changed files with 58 additions and 23 deletions
+9 -7
View File
@@ -5,12 +5,13 @@ 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, get, APIError
from posthog.request import batch_post, decide, get, APIError
from posthog.version import VERSION
from posthog.poller import Poller
@@ -188,7 +189,7 @@ class Client(object):
if self.sync_mode:
self.log.debug('enqueued with blocking %s.', msg['event'])
post(self.api_key, self.host, gzip=self.gzip,
batch_post(self.api_key, self.host, gzip=self.gzip,
timeout=self.timeout, batch=[msg])
return True, msg
@@ -256,7 +257,6 @@ class Client(object):
def feature_enabled(self, key, distinct_id, default=False):
require('key', key, string_types)
require('distinct_id', distinct_id, ID_TYPES)
error = False
if not self.personal_api_key:
self.log.warning('[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags.')
@@ -266,7 +266,6 @@ class Client(object):
# If loading in previous line failed
if not self.feature_flags:
response = default
error = True
else:
try:
feature_flag = [flag for flag in self.feature_flags if flag['key'] == key][0]
@@ -277,13 +276,16 @@ class Client(object):
response = _hash(key, distinct_id) <= (feature_flag['rollout_percentage'] / 100)
else:
try:
request = get(self.api_key, '/decide/', self.host, timeout=1)
response = key in request['featureFlags']
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)
error = True
self.capture(distinct_id, '$feature_flag_called', {'$feature_flag': key, '$feature_flag_response': response})
return response
+2 -2
View File
@@ -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()
+28 -2
View File
@@ -15,12 +15,13 @@ _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 DEFAULT_HOST) + '/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)
@@ -44,6 +45,14 @@ 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)
@@ -51,6 +60,23 @@ def post(api_key, host=None, gzip=False, timeout=15, **kwargs):
except ValueError:
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
+2 -2
View File
@@ -208,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('posthog.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'})
@@ -259,7 +259,7 @@ class TestClient(unittest.TestCase):
}]
self.assertTrue(client.feature_enabled('beta-feature', 'distinct_id'))
@mock.patch('posthog.client.get')
@mock.patch('posthog.client.decide')
def test_feature_enabled_request(self, patch_get):
patch_get.return_value = {
'featureFlags': ['beta-feature']
+3 -3
View File
@@ -60,7 +60,7 @@ class TestConsumer(unittest.TestCase):
flush_interval = 0.3
consumer = Consumer(q, TEST_API_KEY, flush_at=10,
flush_interval=flush_interval)
with mock.patch('posthog.consumer.post') as mock_post:
with mock.patch('posthog.consumer.batch_post') as mock_post:
consumer.start()
for i in range(0, 3):
track = {
@@ -80,7 +80,7 @@ class TestConsumer(unittest.TestCase):
flush_at = 10
consumer = Consumer(q, TEST_API_KEY, flush_at=flush_at,
flush_interval=flush_interval)
with mock.patch('posthog.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 = {
@@ -110,7 +110,7 @@ class TestConsumer(unittest.TestCase):
raise expected_exception
mock_post.call_count = 0
with mock.patch('posthog.consumer.post',
with mock.patch('posthog.consumer.batch_post',
mock.Mock(side_effect=mock_post)):
track = {
'type': 'track',
+6 -6
View File
@@ -3,14 +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(TEST_API_KEY, batch=[{
res = batch_post(TEST_API_KEY, batch=[{
'distinct_id': 'distinct_id',
'event': 'python event',
'type': 'track'
@@ -18,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):
@@ -38,7 +38,7 @@ class TestRequests(unittest.TestCase):
self.assertEqual(result, expected)
def test_should_not_timeout(self):
res = post(TEST_API_KEY, batch=[{
res = batch_post(TEST_API_KEY, batch=[{
'distinct_id': 'distinct_id',
'event': 'python event',
'type': 'track'
@@ -47,7 +47,7 @@ class TestRequests(unittest.TestCase):
def test_should_timeout(self):
with self.assertRaises(requests.ReadTimeout):
post('key', batch=[{
batch_post('key', batch=[{
'distinct_id': 'distinct_id',
'event': 'python event',
'type': 'track'
+4
View File
@@ -1,5 +1,6 @@
from datetime import date, datetime, timedelta
from decimal import Decimal
from uuid import UUID
import unittest
from dateutil.tz import tzutc
@@ -50,6 +51,9 @@ class TestUtils(unittest.TestCase):
utils.clean(combined)
self.assertEqual(combined.keys(), pre_clean_keys)
# test UUID separately, as the UUID object doesn't equal its string representation according to Python
self.assertEqual(utils.clean(UUID('12345678123456781234567812345678')), '12345678-1234-5678-1234-567812345678')
def test_clean_with_dates(self):
dict_with_dates = {
'birthdate': date(1980, 1, 1),
+3
View File
@@ -1,6 +1,7 @@
from dateutil.tz import tzlocal, tzutc
from datetime import date, datetime
from decimal import Decimal
from uuid import UUID
import logging
import numbers
@@ -47,6 +48,8 @@ def remove_trailing_slash(host):
def clean(item):
if isinstance(item, Decimal):
return float(item)
if isinstance(item, UUID):
return str(item)
elif isinstance(item, (six.string_types, bool, numbers.Number, datetime,
date, type(None))):
return item
+1 -1
View File
@@ -1 +1 @@
VERSION = '1.0.11'
VERSION = '1.1.2'