Compare commits
8
Commits
none
...
uuid-property
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d52b605742 | ||
|
|
a5f2e030b5 | ||
|
|
98d2d4cc05 | ||
|
|
b7c1572c32 | ||
|
|
16acf2e278 | ||
|
|
df9ae05202 | ||
|
|
b05ee3884a | ||
|
|
144a7744e4 |
+9
-7
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
VERSION = '1.0.11'
|
||||
VERSION = '1.1.2'
|
||||
|
||||
Reference in New Issue
Block a user