Skip to content

Commit

Permalink
add support for bulk tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
m-vdb committed Jan 16, 2019
1 parent c0f942f commit 361277d
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 17 deletions.
68 changes: 61 additions & 7 deletions heapapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
"""The client for the Heap Api."""
import math

import json

import requests


class HeapAPIClient(object):
class HeapAPIClient:
"""
The client for the Heap Api.
"""

base_url = "https://heapanalytics.com/api"
track_api = f"{base_url}/track"
props_api = f"{base_url}/add_user_properties"
headers = {"Content-Type": "application/json"}
BULK_LIMIT = 1000

def __init__(self, app_id):
"""
Expand Down Expand Up @@ -38,9 +43,7 @@ def track(self, identity, event, properties=None):
if properties is not None:
data["properties"] = properties

response = requests.post(
self.base_url + "/track", data=json.dumps(data), headers=self.headers
)
response = requests.post(self.track_api, data=json.dumps(data), headers=self.headers)
response.raise_for_status()
return response

Expand All @@ -55,8 +58,59 @@ def add_user_properties(self, identity, properties):
"""
data = {"app_id": self.app_id, "identity": identity, "properties": properties}

response = requests.post(
self.base_url + "/add_user_properties", data=json.dumps(data), headers=self.headers
)
response = requests.post(self.props_api, data=json.dumps(data), headers=self.headers)
response.raise_for_status()
return response

def bulk_track(self, events):
"""
Track events in bulk.
Documentation: https://docs.heapanalytics.com/reference#bulk-track
It returns a list of responses. It is the caller's responsibility to
analyze success / failure of each response.
:param events: a list of dictionaries representing the events.
:type properties: list
"""
return [
requests.post(
self.track_api,
data=json.dumps({"app_id": self.app_id, "events": events_batch}),
headers=self.headers,
)
for events_batch in self._get_bulks(events)
]

def bulk_add_user_properties(self, users):
"""
Add user properties in bulk.
Documentation: https://docs.heapanalytics.com/reference#bulk-add-user-properties
It returns a list of responses. It is the caller's responsibility to
analyze success / failure of each response.
:param users: a list of dictionaries representing the users and their properties.
:type properties: list
"""
return [
requests.post(
self.props_api,
data=json.dumps({"app_id": self.app_id, "users": users_batch}),
headers=self.headers,
)
for users_batch in self._get_bulks(users)
]

def _get_bulks(self, objects):
"""
A private method to split objects in bulk, in order
to respect `BULK_LIMIT`.
"""
nb_batch = int(math.ceil(len(objects) / self.BULK_LIMIT))
for idx in range(nb_batch):
start = idx * self.BULK_LIMIT
end = (idx + 1) * self.BULK_LIMIT
objects_batch = objects[start:end]
if objects_batch:
yield objects_batch
81 changes: 71 additions & 10 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import json
import types
import unittest

from mock import patch
from unittest.mock import patch, call

from heapapi import HeapAPIClient


class HeapAPIClientTestCase(unittest.TestCase):
def setUp(self):
super().setUp()
self.client = HeapAPIClient(42)

def test_init_missing_app_id(self):
with self.assertRaises(AssertionError):
HeapAPIClient(None)

def test_init_ok(self):
client = HeapAPIClient(12)
self.assertEqual(client.app_id, "12")
self.assertEqual(self.client.app_id, "42")

@patch("requests.post")
def test_track(self, request_post):
client = HeapAPIClient(42)
resp = client.track("xxx", "Purchase")
resp = self.client.track("xxx", "Purchase")

request_post.assert_called_with(
"https://heapanalytics.com/api/track",
Expand All @@ -30,8 +32,7 @@ def test_track(self, request_post):

@patch("requests.post")
def test_track_with_properties(self, request_post):
client = HeapAPIClient(42)
resp = client.track("xxx", "Purchase", {"amount": 12, "currency": "USD"})
resp = self.client.track("xxx", "Purchase", {"amount": 12, "currency": "USD"})

request_post.assert_called_with(
"https://heapanalytics.com/api/track",
Expand All @@ -50,8 +51,7 @@ def test_track_with_properties(self, request_post):

@patch("requests.post")
def test_add_user_properties(self, request_post):
client = HeapAPIClient(42)
resp = client.add_user_properties("xxx", {"age": 22})
resp = self.client.add_user_properties("xxx", {"age": 22})

request_post.assert_called_with(
"https://heapanalytics.com/api/add_user_properties",
Expand All @@ -60,3 +60,64 @@ def test_add_user_properties(self, request_post):
)
self.assertEqual(resp, request_post.return_value)
resp.raise_for_status.assert_called_with()

def test__get_bulks_empty(self):
batches = self.client._get_bulks([])
self.assertIsInstance(batches, types.GeneratorType)
self.assertEqual(list(batches), [])

def test__get_bulks_one_batch(self):
objects = list(range(500))
batches = self.client._get_bulks(objects)
self.assertIsInstance(batches, types.GeneratorType)
self.assertEqual(list(batches), [objects])

def test__get_bulks_exactly_2_batches(self):
objects = list(range(2000))
batches = self.client._get_bulks(objects)
self.assertIsInstance(batches, types.GeneratorType)
self.assertEqual(list(batches), [list(range(1000)), list(range(1000, 2000))])

@patch("requests.post")
def test_bulk_track(self, request_post):
events = [{"idx": idx} for idx in range(1001)]
responses = self.client.bulk_track(events)
self.assertEqual(responses, [request_post.return_value, request_post.return_value])
request_post.assert_has_calls(
[
call(
self.client.track_api,
data=json.dumps(
{"app_id": "42", "events": [{"idx": idx} for idx in range(1000)]}
),
headers={"Content-Type": "application/json"},
),
call(
self.client.track_api,
data=json.dumps({"app_id": "42", "events": [{"idx": 1000}]}),
headers={"Content-Type": "application/json"},
),
]
)

@patch("requests.post")
def test_bulk_add_user_properties(self, request_post):
users = [{"idx": idx} for idx in range(1001)]
responses = self.client.bulk_add_user_properties(users)
self.assertEqual(responses, [request_post.return_value, request_post.return_value])
request_post.assert_has_calls(
[
call(
self.client.props_api,
data=json.dumps(
{"app_id": "42", "users": [{"idx": idx} for idx in range(1000)]}
),
headers={"Content-Type": "application/json"},
),
call(
self.client.props_api,
data=json.dumps({"app_id": "42", "users": [{"idx": 1000}]}),
headers={"Content-Type": "application/json"},
),
]
)

0 comments on commit 361277d

Please sign in to comment.