From e1dc4e53d0133feb52c050ea30887d0d0f5200d5 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Thu, 20 Jun 2013 22:11:51 +0000 Subject: [PATCH 01/19] basic mixpanel tracking api --- mixpanel.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 mixpanel.py diff --git a/mixpanel.py b/mixpanel.py new file mode 100644 index 0000000..34ba9eb --- /dev/null +++ b/mixpanel.py @@ -0,0 +1,78 @@ +TRACK_ENDPOINT = "http://api.mixpanel.com/track/" +ENGAGE_ENDPOINT = "http://api.mixpanel.com/engage/" + +class Mixpanel: + __token = None + __distinct_id = None + + + def __write_request__(self, endpoint, request): + data = urllib.urlencode({'data': base64.b64encode(json.dumps(record))}) + ENDPOINT = (endpoint == "engage" ? ENGAGE_ENDPOINT : TRACK_ENDPOINT) + try: + response = urllib2.urlopen(ENDPOINT, data).read() + except urllib2.HTTPError as e: + print e.read() + raise + if response == '1': + # will have to change this + print 'success' + else: + raise RuntimeError('%s failed', endpoint) + + def __write_event__(self, event): + self.__write_request__('track', event) + + def __write_record__(self, record): + self.__write_request__('engage', record) + + def track(event_name, properties, ip=0, verbose=False): + properties.update( {"ip": ip, "verbose": verbose} ) + event = { + "event": event_name, + "properties": properties, + } + self.__write_event__(event) + + def __engage_update_(self, update_type, properties): + record = { + "token": self.__token, + "$distinct_id": self.__distinct_id, + update_type: properties, + } + self.__write_record__(record) + + def identify(self, some_id): + self.__distinct_id = some_id + + def alias(self, alias_id, original=__distinct_id): + record = { + "event": "$create_alias", + "properties": { + "distinct_id": self.__distinct_id, + "alias": alias_id, + "token": self.__token, + } + } + + def people_set(self, properties): + __engage_update__(self, "$set", properties) + + def people_set_once(self, properties): + __engage_update__(self, "$set_once", properties) + + def people_add(self, properties): + __engage_update__(self, "$add", properties) + + def people_append(self, properties): + __engage_update__(self, "$append", properties) + + def people_union(self, properties): + __engage_update__(self, "$union", properties) + + def people_unset(self, properties): + __engage_update__(self, "$unset", properties) + + def people_delete(self): + __engage_update__(self, "$delete", "") + From f389a32b001a27d6f7c166191ad08d7acee4bd56 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Thu, 20 Jun 2013 22:14:48 +0000 Subject: [PATCH 02/19] fixed small syntax erros --- mixpanel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 34ba9eb..487c02e 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -8,7 +8,7 @@ class Mixpanel: def __write_request__(self, endpoint, request): data = urllib.urlencode({'data': base64.b64encode(json.dumps(record))}) - ENDPOINT = (endpoint == "engage" ? ENGAGE_ENDPOINT : TRACK_ENDPOINT) + ENDPOINT = ENGAGE_ENDPOINT if (endpoint == "engage") else TRACK_ENDPOINT try: response = urllib2.urlopen(ENDPOINT, data).read() except urllib2.HTTPError as e: @@ -34,7 +34,7 @@ def track(event_name, properties, ip=0, verbose=False): } self.__write_event__(event) - def __engage_update_(self, update_type, properties): + def __engage_update_(self, update_type, properties): record = { "token": self.__token, "$distinct_id": self.__distinct_id, From 3132fdf2b6146a47ace0f900792066f63d3f9bc6 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Fri, 21 Jun 2013 00:07:22 +0000 Subject: [PATCH 03/19] fixed first round of changes (mostly style) thanks to joe and josh --- mixpanel.py | 79 +++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 487c02e..e1283dc 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -1,78 +1,73 @@ -TRACK_ENDPOINT = "http://api.mixpanel.com/track/" -ENGAGE_ENDPOINT = "http://api.mixpanel.com/engage/" - class Mixpanel: - __token = None - __distinct_id = None - + TRACK_ENDPOINT = "http://api.mixpanel.com/track/" + ENGAGE_ENDPOINT = "http://api.mixpanel.com/engage/" + _token = None + _distinct_id = None - def __write_request__(self, endpoint, request): + def _write_request(self, endpoint, request): data = urllib.urlencode({'data': base64.b64encode(json.dumps(record))}) - ENDPOINT = ENGAGE_ENDPOINT if (endpoint == "engage") else TRACK_ENDPOINT try: - response = urllib2.urlopen(ENDPOINT, data).read() + response = urllib2.urlopen(endpoint, data).read() except urllib2.HTTPError as e: + # remove when done with development print e.read() raise if response == '1': - # will have to change this + # remove when done with development print 'success' else: raise RuntimeError('%s failed', endpoint) - def __write_event__(self, event): - self.__write_request__('track', event) + def _write_event(self, event): + self._write_request(TRACK_ENDPOINT, event) - def __write_record__(self, record): - self.__write_request__('engage', record) + def _write_record(self, record): + self._write_request(ENGAGE_ENDPOINT, record) def track(event_name, properties, ip=0, verbose=False): properties.update( {"ip": ip, "verbose": verbose} ) event = { - "event": event_name, - "properties": properties, - } - self.__write_event__(event) + "event": event_name, + "properties": properties, + } + self._write_event(event) - def __engage_update_(self, update_type, properties): + def _engage_update(self, update_type, properties): record = { - "token": self.__token, - "$distinct_id": self.__distinct_id, - update_type: properties, - } - self.__write_record__(record) - - def identify(self, some_id): - self.__distinct_id = some_id + "token": self._token, + "$distinct_id": self._distinct_id, + update_type: properties, + } + self._write_record(record) - def alias(self, alias_id, original=__distinct_id): + def alias(self, alias_id, original=_distinct_id): record = { - "event": "$create_alias", - "properties": { - "distinct_id": self.__distinct_id, - "alias": alias_id, - "token": self.__token, - } - } + "event": "$create_alias", + "properties": { + "distinct_id": self._distinct_id, + "alias": alias_id, + "token": self._token, + } + } def people_set(self, properties): - __engage_update__(self, "$set", properties) + self._engage_update(self, "$set", properties) def people_set_once(self, properties): - __engage_update__(self, "$set_once", properties) + self._engage_update(self, "$set_once", properties) def people_add(self, properties): - __engage_update__(self, "$add", properties) + self._engage_update(self, "$add", properties) def people_append(self, properties): - __engage_update__(self, "$append", properties) + self._engage_update(self, "$append", properties) def people_union(self, properties): - __engage_update__(self, "$union", properties) + self._engage_update(self, "$union", properties) def people_unset(self, properties): - __engage_update__(self, "$unset", properties) + self._engage_update(self, "$unset", properties) def people_delete(self): - __engage_update__(self, "$delete", "") + self._engage_update(self, "$delete", "") From f6efe868109151939fb989c5af3e30067ed55abc Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Fri, 21 Jun 2013 01:08:26 +0000 Subject: [PATCH 04/19] re-added identify. made people things optionally persistent --- mixpanel.py | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index e1283dc..5be1a16 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -4,6 +4,9 @@ class Mixpanel: _token = None _distinct_id = None + def __init__(self, token): + _token = token + def _write_request(self, endpoint, request): data = urllib.urlencode({'data': base64.b64encode(json.dumps(record))}) try: @@ -11,7 +14,7 @@ def _write_request(self, endpoint, request): except urllib2.HTTPError as e: # remove when done with development print e.read() - raise + raise e if response == '1': # remove when done with development print 'success' @@ -21,7 +24,8 @@ def _write_request(self, endpoint, request): def _write_event(self, event): self._write_request(TRACK_ENDPOINT, event) - def _write_record(self, record): + def _write_record(self, record, distinct_id): + self._distinct_id = distinct_id self._write_request(ENGAGE_ENDPOINT, record) def track(event_name, properties, ip=0, verbose=False): @@ -32,7 +36,10 @@ def track(event_name, properties, ip=0, verbose=False): } self._write_event(event) - def _engage_update(self, update_type, properties): + def identify(self, distinct_id): + self._distinct_id = distinct_id + + def _engage_update(self, update_type, properties, distinct_id=self._distinct_id): record = { "token": self._token, "$distinct_id": self._distinct_id, @@ -40,7 +47,7 @@ def _engage_update(self, update_type, properties): } self._write_record(record) - def alias(self, alias_id, original=_distinct_id): + def alias(self, alias_id, distinct_id=self._distinct_id): record = { "event": "$create_alias", "properties": { @@ -50,24 +57,23 @@ def alias(self, alias_id, original=_distinct_id): } } - def people_set(self, properties): - self._engage_update(self, "$set", properties) - - def people_set_once(self, properties): - self._engage_update(self, "$set_once", properties) + def people_set(self, properties, distinct_id=self._distinct_id): + self._engage_update("$set", properties, distinct_id) - def people_add(self, properties): - self._engage_update(self, "$add", properties) + def people_set_once(self, properties, distinct_id=self._distinct_id): + self._engage_update("$set_once", properties, distinct_id) - def people_append(self, properties): - self._engage_update(self, "$append", properties) + def people_add(self, properties, distinct_id=self._distinct_id): + self._engage_update("$add", properties, distinct_id) - def people_union(self, properties): - self._engage_update(self, "$union", properties) + def people_append(self, properties, distinct_id=self._distinct_id): + self._engage_update("$append", properties, distinct_id) - def people_unset(self, properties): - self._engage_update(self, "$unset", properties) + def people_union(self, properties, distinct_id=self._distinct_id): + self._engage_update("$union", properties, distinct_id) - def people_delete(self): - self._engage_update(self, "$delete", "") + def people_unset(self, properties, distinct_id=self._distinct_id): + self._engage_update("$unset", properties, distinct_id) + def people_delete(self, distinct_id=self._distinct_id): + self._engage_update("$delete", "", distinct_id) From 0e977e68d11bc8b63700583f19376fac32a9c757 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Fri, 21 Jun 2013 01:49:02 +0000 Subject: [PATCH 05/19] fixed a few things with initial unit testing. --- mixpanel.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 5be1a16..c79b34e 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -1,6 +1,11 @@ +import urllib +import urllib2 +import base64 +import json + class Mixpanel: - TRACK_ENDPOINT = "http://api.mixpanel.com/track/" - ENGAGE_ENDPOINT = "http://api.mixpanel.com/engage/" + _track_endpoint = "http://api.mixpanel.com/track/" + _engage_endpoint = "http://api.mixpanel.com/engage/" _token = None _distinct_id = None @@ -8,7 +13,7 @@ def __init__(self, token): _token = token def _write_request(self, endpoint, request): - data = urllib.urlencode({'data': base64.b64encode(json.dumps(record))}) + data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) try: response = urllib2.urlopen(endpoint, data).read() except urllib2.HTTPError as e: @@ -22,13 +27,13 @@ def _write_request(self, endpoint, request): raise RuntimeError('%s failed', endpoint) def _write_event(self, event): - self._write_request(TRACK_ENDPOINT, event) + self._write_request(self._track_endpoint, event) def _write_record(self, record, distinct_id): self._distinct_id = distinct_id - self._write_request(ENGAGE_ENDPOINT, record) + self._write_request(self._engage_endpoint, record) - def track(event_name, properties, ip=0, verbose=False): + def track(self, event_name, properties, ip=0, verbose=False): properties.update( {"ip": ip, "verbose": verbose} ) event = { "event": event_name, @@ -39,41 +44,41 @@ def track(event_name, properties, ip=0, verbose=False): def identify(self, distinct_id): self._distinct_id = distinct_id - def _engage_update(self, update_type, properties, distinct_id=self._distinct_id): + def _engage_update(self, update_type, properties, distinct_id=_distinct_id): record = { "token": self._token, - "$distinct_id": self._distinct_id, + "$distinct_id": distinct_id, update_type: properties, } self._write_record(record) - def alias(self, alias_id, distinct_id=self._distinct_id): + def alias(self, alias_id, distinct_id=_distinct_id): record = { "event": "$create_alias", "properties": { - "distinct_id": self._distinct_id, + "distinct_id": distinct_id, "alias": alias_id, "token": self._token, } } - def people_set(self, properties, distinct_id=self._distinct_id): + def people_set(self, properties, distinct_id=_distinct_id): self._engage_update("$set", properties, distinct_id) - def people_set_once(self, properties, distinct_id=self._distinct_id): + def people_set_once(self, properties, distinct_id=_distinct_id): self._engage_update("$set_once", properties, distinct_id) - def people_add(self, properties, distinct_id=self._distinct_id): + def people_add(self, properties, distinct_id=_distinct_id): self._engage_update("$add", properties, distinct_id) - def people_append(self, properties, distinct_id=self._distinct_id): + def people_append(self, properties, distinct_id=_distinct_id): self._engage_update("$append", properties, distinct_id) - def people_union(self, properties, distinct_id=self._distinct_id): + def people_union(self, properties, distinct_id=_distinct_id): self._engage_update("$union", properties, distinct_id) - def people_unset(self, properties, distinct_id=self._distinct_id): + def people_unset(self, properties, distinct_id=_distinct_id): self._engage_update("$unset", properties, distinct_id) - def people_delete(self, distinct_id=self._distinct_id): + def people_delete(self, distinct_id=_distinct_id): self._engage_update("$delete", "", distinct_id) From 4b21b86bf93eda48c6f8423b76a23fe02c3680c4 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Fri, 21 Jun 2013 02:16:55 +0000 Subject: [PATCH 06/19] the beginnings of some tests. --- test_mixpanel.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 test_mixpanel.py diff --git a/test_mixpanel.py b/test_mixpanel.py new file mode 100755 index 0000000..2048d7a --- /dev/null +++ b/test_mixpanel.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +import urllib +import unittest +import mixpanel + +class MixpanelTestCase(unittest.TestCase): + def setUp(self): + print "set up" + + def tearDown(self): + print "tear down" + + def test_constructor(self): + mp = mixpanel.Mixpanel() + + def test_track1(self): + mp = mixpanel.Mixpanel("1234") + mp.track("pushed button", {"color": "blue", "weight": "5lbs"}) + + def test_track2(self): + mp = mixpanel.Mixpanel("1234") + mp.track("event2", {"x": "y", "poppin": "tags", "ip": "something"}) + + # woo this test actually passes + def test_identify(self): + mp = mixpanel.Mixpanel("1234") + mp.identify("2345") + self.assertEqual(mp._distinct_id, "2345") + +if __name__ == "__main__": + unittest.main() From 407714a1158e59bd3984ea410c5aee5916b86c9f Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Fri, 21 Jun 2013 23:41:59 +0000 Subject: [PATCH 07/19] fixed changes as per last pull request. added sending batch events --- mixpanel.py | 101 +++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index c79b34e..f75a6af 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -1,21 +1,17 @@ -import urllib -import urllib2 import base64 import json +import urllib +import urllib2 -class Mixpanel: - _track_endpoint = "http://api.mixpanel.com/track/" - _engage_endpoint = "http://api.mixpanel.com/engage/" - _token = None - _distinct_id = None - - def __init__(self, token): - _token = token +class Mixpanel(object): + def __init__(self, token, base_url='https://api.mixpanel.com/'): + self._token = token + self._base_url = base_url def _write_request(self, endpoint, request): data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) try: - response = urllib2.urlopen(endpoint, data).read() + response = urllib2.urlopen(''.join([self._base_url,endpoint]), data).read() except urllib2.HTTPError as e: # remove when done with development print e.read() @@ -26,59 +22,60 @@ def _write_request(self, endpoint, request): else: raise RuntimeError('%s failed', endpoint) - def _write_event(self, event): - self._write_request(self._track_endpoint, event) - - def _write_record(self, record, distinct_id): - self._distinct_id = distinct_id - self._write_request(self._engage_endpoint, record) + def _send_batch(self, endpoint, request): + data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) + try: + request = urllib2.Request(''.join([self._base_url, endpoint]), data) + response = urllib2.urlopen(request).read() + except urllib2.HTTPError as e: + # remove when done with development + print e.read() + raise e + if response == '1': + # remove when done with development + print 'success' + else: + raise RuntimeError('%s failed', endpoint) - def track(self, event_name, properties, ip=0, verbose=False): - properties.update( {"ip": ip, "verbose": verbose} ) + def track(self, event_name, properties, geolocate_ip=False, verbose=True): + assert(type(event_name) == str), "event_name not a string" + assert(len(event_name) > 0), "event_name empty string" + assert(type(properties) == dict), "properties not dictionary" + all_properties = { '$token' : self._token } + all_properties.update(properties) + all_properties.update( {'ip': (0 if not geolocate_ip else 1), 'verbose': verbose} ) event = { - "event": event_name, - "properties": properties, + 'event': event_name, + 'properties': all_properties, } - self._write_event(event) - - def identify(self, distinct_id): - self._distinct_id = distinct_id + self._write_request('track/', event) - def _engage_update(self, update_type, properties, distinct_id=_distinct_id): + def engage_update(self, distinct_id, update_type, properties): + assert(type(update_type) == str), "update_type not a string" + assert(len(update_type) > 0), "update_type empty string" + assert(type(properties) == dict), "properties not dictionary" record = { - "token": self._token, - "$distinct_id": distinct_id, + '$token': self._token, + '$distinct_id': distinct_id, update_type: properties, } - self._write_record(record) + self._write_request('engage/', record) - def alias(self, alias_id, distinct_id=_distinct_id): + def alias(self, alias_id, original): record = { - "event": "$create_alias", - "properties": { - "distinct_id": distinct_id, - "alias": alias_id, - "token": self._token, + 'event': '$create_alias', + 'properties': { + 'distinct_id': original, + 'alias': alias_id, + 'token': self._token, } } + self._write_request('engage/', record) - def people_set(self, properties, distinct_id=_distinct_id): - self._engage_update("$set", properties, distinct_id) - - def people_set_once(self, properties, distinct_id=_distinct_id): - self._engage_update("$set_once", properties, distinct_id) - - def people_add(self, properties, distinct_id=_distinct_id): - self._engage_update("$add", properties, distinct_id) - - def people_append(self, properties, distinct_id=_distinct_id): - self._engage_update("$append", properties, distinct_id) + def send_events_batch(self, data): + self.__send_batch(data, 'track/') - def people_union(self, properties, distinct_id=_distinct_id): - self._engage_update("$union", properties, distinct_id) + def send_people_batch(self, data): + self.__send_batch(data, 'engage/') - def people_unset(self, properties, distinct_id=_distinct_id): - self._engage_update("$unset", properties, distinct_id) - def people_delete(self, distinct_id=_distinct_id): - self._engage_update("$delete", "", distinct_id) From bb0ab7dda1265222afb1934b05ad80fe4923a12c Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Sat, 22 Jun 2013 00:46:34 +0000 Subject: [PATCH 08/19] added some light documentation --- mixpanel.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index f75a6af..d15a0d4 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -4,10 +4,20 @@ import urllib2 class Mixpanel(object): + """ + To use mixpanel, create a new Mixpanel object using your token. + Use this object to start tracking. + Example: + mp = Mixpanel('36ada5b10da39a1347559321baf13063') + """ def __init__(self, token, base_url='https://api.mixpanel.com/'): self._token = token self._base_url = base_url + """ + For internal use. Writes a request taking in either 'track/' for events or + 'engage/' for people. + """ def _write_request(self, endpoint, request): data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) try: @@ -22,7 +32,13 @@ def _write_request(self, endpoint, request): else: raise RuntimeError('%s failed', endpoint) + """ + For internal use. Sends a list of events or people in a POST request. + Useful if sending a lot of requests at once. + """ def _send_batch(self, endpoint, request): + for item in request: + item['properties'] = item['properties'].update({'token': self._token}) data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) try: request = urllib2.Request(''.join([self._base_url, endpoint]), data) @@ -37,10 +53,16 @@ def _send_batch(self, endpoint, request): else: raise RuntimeError('%s failed', endpoint) + """ + For basic event tracking. Should pass in name of event name and dictionary + of properties. + Example: + mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) + """ def track(self, event_name, properties, geolocate_ip=False, verbose=True): - assert(type(event_name) == str), "event_name not a string" - assert(len(event_name) > 0), "event_name empty string" - assert(type(properties) == dict), "properties not dictionary" + assert(type(event_name) == str), 'event_name not a string' + assert(len(event_name) > 0), 'event_name empty string' + assert(type(properties) == dict), 'properties not dictionary' all_properties = { '$token' : self._token } all_properties.update(properties) all_properties.update( {'ip': (0 if not geolocate_ip else 1), 'verbose': verbose} ) @@ -50,10 +72,24 @@ def track(self, event_name, properties, geolocate_ip=False, verbose=True): } self._write_request('track/', event) - def engage_update(self, distinct_id, update_type, properties): - assert(type(update_type) == str), "update_type not a string" - assert(len(update_type) > 0), "update_type empty string" - assert(type(properties) == dict), "properties not dictionary" + """ + For all people tracking. Should pass in distinct_id, type of update, + and dictionary of properties. + Examples: + person1 = { + 'Address': '1313 Mockingbird Lane', + 'Birthday': '1948-01-01' + } + mp.engage('13793', '$set', person1) + mp.engage('13793', '$add', { 'Coins Gathered': '12' }) + mp.engage('13793', '$unset', [ 'Birthday' ]) + mp.engage('13793', '$delete', '') + """ + def engage(self, distinct_id, update_type, properties): + assert(type(distinct_id) == str), 'distinct_id not a string' + assert(len(distinct_id) > 0), 'distinct_id empty string' + assert(type(update_type) == str), 'update_type not a string' + assert(len(update_type) > 0), 'update_type empty string' record = { '$token': self._token, '$distinct_id': distinct_id, @@ -61,6 +97,11 @@ def engage_update(self, distinct_id, update_type, properties): } self._write_request('engage/', record) + """ + Allows you to set a custom alias for people records. + Example: + mp.alias('amy@mixpanel.com', '13793') + """ def alias(self, alias_id, original): record = { 'event': '$create_alias', @@ -72,6 +113,34 @@ def alias(self, alias_id, original): } self._write_request('engage/', record) + """ + If sending many events at once, this is useful. Accepts lists of 50 events + at a time and sends them via a POST request. + + Example: + + events_list = [ + { + "event": "Signed Up", + "properties": { + "distinct_id": "13793", + "Referred By": "Friend", + "time": 1371002000 + } + }, + { + "event": "Uploaded Photo", + "properties": { + "distinct_id": "13793", + "Topic": "Vacation", + "time": 1371002104 + } + } + ] + + mp.send_events_batch(events_list) + + """ def send_events_batch(self, data): self.__send_batch(data, 'track/') From 8cb953bdd3a63e9bb039c448b996a3e0838ba5bd Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Mon, 1 Jul 2013 21:15:35 +0000 Subject: [PATCH 09/19] removed ip address logic --- mixpanel.py | 23 +++++++++++++---------- test_mixpanel.py | 31 ------------------------------- 2 files changed, 13 insertions(+), 41 deletions(-) delete mode 100755 test_mixpanel.py diff --git a/mixpanel.py b/mixpanel.py index d15a0d4..194b15f 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -14,20 +14,23 @@ def __init__(self, token, base_url='https://api.mixpanel.com/'): self._token = token self._base_url = base_url + def _encode_data(self, data): + return urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) + """ For internal use. Writes a request taking in either 'track/' for events or 'engage/' for people. """ def _write_request(self, endpoint, request): - data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) + data = self._encode_data(request) try: response = urllib2.urlopen(''.join([self._base_url,endpoint]), data).read() except urllib2.HTTPError as e: - # remove when done with development + # TODO remove when done with development print e.read() raise e if response == '1': - # remove when done with development + # TODO remove when done with development print 'success' else: raise RuntimeError('%s failed', endpoint) @@ -39,16 +42,16 @@ def _write_request(self, endpoint, request): def _send_batch(self, endpoint, request): for item in request: item['properties'] = item['properties'].update({'token': self._token}) - data = urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) + data = self._encode_data(request) try: request = urllib2.Request(''.join([self._base_url, endpoint]), data) response = urllib2.urlopen(request).read() except urllib2.HTTPError as e: - # remove when done with development + # TODO remove when done with development print e.read() raise e if response == '1': - # remove when done with development + # TODO remove when done with development print 'success' else: raise RuntimeError('%s failed', endpoint) @@ -59,13 +62,13 @@ def _send_batch(self, endpoint, request): Example: mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) """ - def track(self, event_name, properties, geolocate_ip=False, verbose=True): + def track(self, event_name, properties={}, verbose=True): assert(type(event_name) == str), 'event_name not a string' assert(len(event_name) > 0), 'event_name empty string' assert(type(properties) == dict), 'properties not dictionary' all_properties = { '$token' : self._token } all_properties.update(properties) - all_properties.update( {'ip': (0 if not geolocate_ip else 1), 'verbose': verbose} ) + all_properties.update( { 'verbose': verbose} ) event = { 'event': event_name, 'properties': all_properties, @@ -142,9 +145,9 @@ def alias(self, alias_id, original): """ def send_events_batch(self, data): - self.__send_batch(data, 'track/') + self._send_batch(data, 'track/') def send_people_batch(self, data): - self.__send_batch(data, 'engage/') + self._send_batch(data, 'engage/') diff --git a/test_mixpanel.py b/test_mixpanel.py deleted file mode 100755 index 2048d7a..0000000 --- a/test_mixpanel.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -import urllib -import unittest -import mixpanel - -class MixpanelTestCase(unittest.TestCase): - def setUp(self): - print "set up" - - def tearDown(self): - print "tear down" - - def test_constructor(self): - mp = mixpanel.Mixpanel() - - def test_track1(self): - mp = mixpanel.Mixpanel("1234") - mp.track("pushed button", {"color": "blue", "weight": "5lbs"}) - - def test_track2(self): - mp = mixpanel.Mixpanel("1234") - mp.track("event2", {"x": "y", "poppin": "tags", "ip": "something"}) - - # woo this test actually passes - def test_identify(self): - mp = mixpanel.Mixpanel("1234") - mp.identify("2345") - self.assertEqual(mp._distinct_id, "2345") - -if __name__ == "__main__": - unittest.main() From 9d3ee7ef371530887c5763ba4cfaf8fdd0837173 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Mon, 1 Jul 2013 22:35:15 +0000 Subject: [PATCH 10/19] changed 'engage' function to 'epople' function to be more consistent with other libs --- mixpanel.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 194b15f..84f23b8 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -83,12 +83,12 @@ def track(self, event_name, properties={}, verbose=True): 'Address': '1313 Mockingbird Lane', 'Birthday': '1948-01-01' } - mp.engage('13793', '$set', person1) - mp.engage('13793', '$add', { 'Coins Gathered': '12' }) - mp.engage('13793', '$unset', [ 'Birthday' ]) - mp.engage('13793', '$delete', '') + mp.people('13793', '$set', person1) + mp.people('13793', '$add', { 'Coins Gathered': '12' }) + mp.people('13793', '$unset', [ 'Birthday' ]) + mp.people('13793', '$delete', '') """ - def engage(self, distinct_id, update_type, properties): + def people(self, distinct_id, update_type, properties): assert(type(distinct_id) == str), 'distinct_id not a string' assert(len(distinct_id) > 0), 'distinct_id empty string' assert(type(update_type) == str), 'update_type not a string' From b909172431383ba03ef265a815b7f1571d0e42bf Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Mon, 1 Jul 2013 23:16:47 +0000 Subject: [PATCH 11/19] moved comments around, fixed commas --- mixpanel.py | 146 ++++++++++++++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 84f23b8..c58d116 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -4,27 +4,27 @@ import urllib2 class Mixpanel(object): - """ - To use mixpanel, create a new Mixpanel object using your token. - Use this object to start tracking. - Example: - mp = Mixpanel('36ada5b10da39a1347559321baf13063') - """ - def __init__(self, token, base_url='https://api.mixpanel.com/'): + def __init__(self, token, base_url='https://api.mixpanel.com/'): + """ + To use mixpanel, create a new Mixpanel object using your token. + Use this object to start tracking. + Example: + mp = Mixpanel('36ada5b10da39a1347559321baf13063') + """ self._token = token self._base_url = base_url def _encode_data(self, data): - return urllib.urlencode({'data': base64.b64encode(json.dumps(request))}) + return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) - """ - For internal use. Writes a request taking in either 'track/' for events or - 'engage/' for people. - """ - def _write_request(self, endpoint, request): + def _write_request(self, endpoint, request): + """ + Writes a request taking in either 'track/' for events or 'engage/' for + people. + """ data = self._encode_data(request) try: - response = urllib2.urlopen(''.join([self._base_url,endpoint]), data).read() + response = urllib2.urlopen(''.join([self._base_url, endpoint]), data).read() except urllib2.HTTPError as e: # TODO remove when done with development print e.read() @@ -35,11 +35,11 @@ def _write_request(self, endpoint, request): else: raise RuntimeError('%s failed', endpoint) - """ - For internal use. Sends a list of events or people in a POST request. - Useful if sending a lot of requests at once. - """ - def _send_batch(self, endpoint, request): + def _send_batch(self, endpoint, request): + """ + Sends a list of events or people in a POST request. Useful if sending a + lot of requests at once. + """ for item in request: item['properties'] = item['properties'].update({'token': self._token}) data = self._encode_data(request) @@ -56,17 +56,17 @@ def _send_batch(self, endpoint, request): else: raise RuntimeError('%s failed', endpoint) - """ - For basic event tracking. Should pass in name of event name and dictionary - of properties. - Example: - mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) - """ - def track(self, event_name, properties={}, verbose=True): + def track(self, event_name, properties={}, verbose=True): + """ + For basic event tracking. Should pass in name of event name and dictionary + of properties. + Example: + mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) + """ assert(type(event_name) == str), 'event_name not a string' assert(len(event_name) > 0), 'event_name empty string' assert(type(properties) == dict), 'properties not dictionary' - all_properties = { '$token' : self._token } + all_properties = { 'token' : self._token } all_properties.update(properties) all_properties.update( { 'verbose': verbose} ) event = { @@ -75,20 +75,20 @@ def track(self, event_name, properties={}, verbose=True): } self._write_request('track/', event) - """ - For all people tracking. Should pass in distinct_id, type of update, - and dictionary of properties. - Examples: - person1 = { - 'Address': '1313 Mockingbird Lane', - 'Birthday': '1948-01-01' - } - mp.people('13793', '$set', person1) - mp.people('13793', '$add', { 'Coins Gathered': '12' }) - mp.people('13793', '$unset', [ 'Birthday' ]) - mp.people('13793', '$delete', '') - """ - def people(self, distinct_id, update_type, properties): + def people(self, distinct_id, update_type, properties): + """ + For all people tracking. Should pass in distinct_id, type of update, + and dictionary of properties. + Examples: + person1 = { + 'Address': '1313 Mockingbird Lane', + 'Birthday': '1948-01-01' + } + mp.people('13793', '$set', person1) + mp.people('13793', '$add', { 'Coins Gathered': '12' }) + mp.people('13793', '$unset', [ 'Birthday' ]) + mp.people('13793', '$delete', '') + """ assert(type(distinct_id) == str), 'distinct_id not a string' assert(len(distinct_id) > 0), 'distinct_id empty string' assert(type(update_type) == str), 'update_type not a string' @@ -100,12 +100,12 @@ def people(self, distinct_id, update_type, properties): } self._write_request('engage/', record) - """ - Allows you to set a custom alias for people records. - Example: - mp.alias('amy@mixpanel.com', '13793') - """ - def alias(self, alias_id, original): + def alias(self, alias_id, original): + """ + Allows you to set a custom alias for people records. + Example: + mp.alias('amy@mixpanel.com', '13793') + """ record = { 'event': '$create_alias', 'properties': { @@ -116,35 +116,35 @@ def alias(self, alias_id, original): } self._write_request('engage/', record) - """ - If sending many events at once, this is useful. Accepts lists of 50 events - at a time and sends them via a POST request. + def send_events_batch(self, data): + """ + If sending many events at once, this is useful. Accepts lists of 50 events + at a time and sends them via a POST request. - Example: + Example: - events_list = [ - { - "event": "Signed Up", - "properties": { - "distinct_id": "13793", - "Referred By": "Friend", - "time": 1371002000 - } - }, - { - "event": "Uploaded Photo", - "properties": { - "distinct_id": "13793", - "Topic": "Vacation", - "time": 1371002104 - } - } - ] + events_list = [ + { + "event": "Signed Up", + "properties": { + "distinct_id": "13793", + "Referred By": "Friend", + "time": 1371002000 + } + }, + { + "event": "Uploaded Photo", + "properties": { + "distinct_id": "13793", + "Topic": "Vacation", + "time": 1371002104 + } + } + ] - mp.send_events_batch(events_list) - - """ - def send_events_batch(self, data): + mp.send_events_batch(events_list) + + """ self._send_batch(data, 'track/') def send_people_batch(self, data): From 0fc883cf0a8004ae3d371bb555bf8d85b75832ea Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Tue, 2 Jul 2013 19:06:59 +0000 Subject: [PATCH 12/19] seems to be working. moved People to inner class, added helper functions for people --- mixpanel.py | 252 ++++++++++++++++++++++++++-------------------------- 1 file changed, 124 insertions(+), 128 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index c58d116..e044f72 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -3,109 +3,111 @@ import urllib import urllib2 +def encode_data(data): + return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) + +def write_request(base_url, endpoint, request): + """ + Writes a request taking in either 'track/' for events or 'engage/' for + people. + """ + data = encode_data(request) + try: + response = urllib2.urlopen(''.join([base_url, endpoint]), data).read() + except urllib2.HTTPError as e: + raise e + return response == '1' + +def send_batch(base_url, endpoint, batch): + """ + Sends a list of events or people in a POST request. Useful if sending a lot + of requests at once. + """ + if len(batch) > 50: + raise + for item in batch: + # TODO test this hardcore + item['properties'] = item['properties'].update({'token': self._token}) + data = self._encode_data(batch) + try: + batch = urllib2.Request(''.join([self._base_url, endpoint]), data) + response = urllib2.urlopen(batch).read() + except urllib2.HTTPError as e: + raise e + return response == '1' + class Mixpanel(object): - def __init__(self, token, base_url='https://api.mixpanel.com/'): - """ - To use mixpanel, create a new Mixpanel object using your token. - Use this object to start tracking. - Example: - mp = Mixpanel('36ada5b10da39a1347559321baf13063') - """ + class People(object): + def __init__(self, token, base_url): + self._token = token + self._base_url = base_url + + def _people(self, distinct_id, update_type, properties): + record = { + '$token': self._token, + '$distinct_id': distinct_id, + update_type: properties, + } + return write_request(self._base_url, 'engage/', record) + + def set(self, distinct_id, properties): + return self._people(distinct_id, '$set', properties) + + def set_once(self, distinct_id, properties): + return self._people(distinct_id, '$set_once', properties) + + def add(self, distinct_id, properties): + return self._people(distinct_id, '$add', properties) + + def append(self, distinct_id, properties): + return self._people(distinct_id, '$append', properties) + + def union(self, distinct_id, properties): + return self._people(distinct_id, '$union', properties) + + def unset(self, distinct_id, properties): + return self._people(distinct_id, '$unset', properties) + + def delete(self, distinct_id): + return self._people(distinct_id, '$append', "") + + def send_people_batch(self, data): + return send_batch(self._base_url, data, 'engage/') + + + def __init__(self, token, base_url='https://api.mixpanel.com/'): + """ + To use mixpanel, create a new Mixpanel object using your token. + Use this object to start tracking. + Example: + mp = Mixpanel('36ada5b10da39a1347559321baf13063') + """ self._token = token self._base_url = base_url + self.people = self.People(self._token, self._base_url) - def _encode_data(self, data): - return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) - - def _write_request(self, endpoint, request): - """ - Writes a request taking in either 'track/' for events or 'engage/' for - people. - """ - data = self._encode_data(request) - try: - response = urllib2.urlopen(''.join([self._base_url, endpoint]), data).read() - except urllib2.HTTPError as e: - # TODO remove when done with development - print e.read() - raise e - if response == '1': - # TODO remove when done with development - print 'success' - else: - raise RuntimeError('%s failed', endpoint) - - def _send_batch(self, endpoint, request): - """ - Sends a list of events or people in a POST request. Useful if sending a - lot of requests at once. - """ - for item in request: - item['properties'] = item['properties'].update({'token': self._token}) - data = self._encode_data(request) - try: - request = urllib2.Request(''.join([self._base_url, endpoint]), data) - response = urllib2.urlopen(request).read() - except urllib2.HTTPError as e: - # TODO remove when done with development - print e.read() - raise e - if response == '1': - # TODO remove when done with development - print 'success' - else: - raise RuntimeError('%s failed', endpoint) - - def track(self, event_name, properties={}, verbose=True): - """ - For basic event tracking. Should pass in name of event name and dictionary - of properties. - Example: - mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) - """ - assert(type(event_name) == str), 'event_name not a string' - assert(len(event_name) > 0), 'event_name empty string' - assert(type(properties) == dict), 'properties not dictionary' + def track(self, event_name, properties={}, verbose=True): + """ + For basic event tracking. Should pass in name of event name and + dictionary of properties. + Example: + mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) + """ all_properties = { 'token' : self._token } all_properties.update(properties) - all_properties.update( { 'verbose': verbose} ) + all_properties.update( { 'verbose': verbose } ) event = { 'event': event_name, 'properties': all_properties, } - self._write_request('track/', event) - - def people(self, distinct_id, update_type, properties): - """ - For all people tracking. Should pass in distinct_id, type of update, - and dictionary of properties. - Examples: - person1 = { - 'Address': '1313 Mockingbird Lane', - 'Birthday': '1948-01-01' - } - mp.people('13793', '$set', person1) - mp.people('13793', '$add', { 'Coins Gathered': '12' }) - mp.people('13793', '$unset', [ 'Birthday' ]) - mp.people('13793', '$delete', '') - """ - assert(type(distinct_id) == str), 'distinct_id not a string' - assert(len(distinct_id) > 0), 'distinct_id empty string' - assert(type(update_type) == str), 'update_type not a string' - assert(len(update_type) > 0), 'update_type empty string' - record = { - '$token': self._token, - '$distinct_id': distinct_id, - update_type: properties, - } - self._write_request('engage/', record) - - def alias(self, alias_id, original): - """ - Allows you to set a custom alias for people records. - Example: - mp.alias('amy@mixpanel.com', '13793') - """ + return write_request(self._base_url, 'track/', event) + + def alias(self, alias_id, original): + """ + Allows you to set a custom alias for people records. + Example: + mp.alias('amy@mixpanel.com', '13793') + """ record = { 'event': '$create_alias', 'properties': { @@ -114,40 +116,34 @@ def alias(self, alias_id, original): 'token': self._token, } } - self._write_request('engage/', record) - - def send_events_batch(self, data): - """ - If sending many events at once, this is useful. Accepts lists of 50 events - at a time and sends them via a POST request. - - Example: - - events_list = [ - { - "event": "Signed Up", - "properties": { - "distinct_id": "13793", - "Referred By": "Friend", - "time": 1371002000 - } - }, - { - "event": "Uploaded Photo", - "properties": { - "distinct_id": "13793", - "Topic": "Vacation", - "time": 1371002104 - } - } - ] - - mp.send_events_batch(events_list) - - """ - self._send_batch(data, 'track/') - - def send_people_batch(self, data): - self._send_batch(data, 'engage/') + return write_request(self._base_url, 'engage/', record) + + def send_events_batch(self, data): + """ + If sending many events at once, this is useful. Accepts lists of 50 + events at a time and sends them via a POST request. + + Example: + events_list = [ + { + "event": "Signed Up", + "properties": { + "distinct_id": "13793", + "Referred By": "Friend", + "time": 1371002000 + } + }, + { + "event": "Uploaded Photo", + "properties": { + "distinct_id": "13793", + "Topic": "Vacation", + "time": 1371002104 + } + } + ] + mp.send_events_batch(events_list) + """ + send_batch(self._base_url, data, 'track/') From 90ed90515b08445565a2b7158d0a6b86a9bbfeb2 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Tue, 2 Jul 2013 21:05:13 +0000 Subject: [PATCH 13/19] put all requests through write_request function --- mixpanel.py | 65 ++++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 51 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index e044f72..3a6986c 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -6,36 +6,26 @@ def encode_data(data): return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) -def write_request(base_url, endpoint, request): +def write_request(base_url, endpoint, request, batch=False): """ Writes a request taking in either 'track/' for events or 'engage/' for people. """ - data = encode_data(request) + data = encode_data(request) + request_url = ''.join([base_url, endpoint]) try: - response = urllib2.urlopen(''.join([base_url, endpoint]), data).read() + if not batch: + response = urllib2.urlopen(request_url, data).read() + else: + if len(request) > 50: + raise + batch_request = urllib2.Request(request_url, data) + response = urllib2.urlopen(batch_request).read() + except urllib2.HTTPError as e: raise e return response == '1' -def send_batch(base_url, endpoint, batch): - """ - Sends a list of events or people in a POST request. Useful if sending a lot - of requests at once. - """ - if len(batch) > 50: - raise - for item in batch: - # TODO test this hardcore - item['properties'] = item['properties'].update({'token': self._token}) - data = self._encode_data(batch) - try: - batch = urllib2.Request(''.join([self._base_url, endpoint]), data) - response = urllib2.urlopen(batch).read() - except urllib2.HTTPError as e: - raise e - return response == '1' - class Mixpanel(object): class People(object): def __init__(self, token, base_url): @@ -69,10 +59,10 @@ def unset(self, distinct_id, properties): return self._people(distinct_id, '$unset', properties) def delete(self, distinct_id): - return self._people(distinct_id, '$append', "") + return self._people(distinct_id, '$delete', "") def send_people_batch(self, data): - return send_batch(self._base_url, data, 'engage/') + return write_request(self._base_url, data, 'engage/') def __init__(self, token, base_url='https://api.mixpanel.com/'): @@ -119,31 +109,4 @@ def alias(self, alias_id, original): return write_request(self._base_url, 'engage/', record) def send_events_batch(self, data): - """ - If sending many events at once, this is useful. Accepts lists of 50 - events at a time and sends them via a POST request. - - Example: - - events_list = [ - { - "event": "Signed Up", - "properties": { - "distinct_id": "13793", - "Referred By": "Friend", - "time": 1371002000 - } - }, - { - "event": "Uploaded Photo", - "properties": { - "distinct_id": "13793", - "Topic": "Vacation", - "time": 1371002104 - } - } - ] - - mp.send_events_batch(events_list) - """ - send_batch(self._base_url, data, 'track/') + write_request(self._base_url, data, 'track/') From e3705a60f4b89d692b73eb6302a13722c14c83c9 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Tue, 2 Jul 2013 23:44:33 +0000 Subject: [PATCH 14/19] wrote some basic tests, fixed up batch --- mixpanel.py | 7 ++-- test_mixpanel.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) create mode 100755 test_mixpanel.py diff --git a/mixpanel.py b/mixpanel.py index 3a6986c..b9aa03c 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -62,7 +62,7 @@ def delete(self, distinct_id): return self._people(distinct_id, '$delete', "") def send_people_batch(self, data): - return write_request(self._base_url, data, 'engage/') + return write_request(self._base_url, 'engage/', data, True) def __init__(self, token, base_url='https://api.mixpanel.com/'): @@ -76,7 +76,7 @@ def __init__(self, token, base_url='https://api.mixpanel.com/'): self._base_url = base_url self.people = self.People(self._token, self._base_url) - def track(self, event_name, properties={}, verbose=True): + def track(self, event_name, properties={}): """ For basic event tracking. Should pass in name of event name and dictionary of properties. @@ -85,7 +85,6 @@ def track(self, event_name, properties={}, verbose=True): """ all_properties = { 'token' : self._token } all_properties.update(properties) - all_properties.update( { 'verbose': verbose } ) event = { 'event': event_name, 'properties': all_properties, @@ -109,4 +108,4 @@ def alias(self, alias_id, original): return write_request(self._base_url, 'engage/', record) def send_events_batch(self, data): - write_request(self._base_url, data, 'track/') + write_request(self._base_url, 'track/', data, True) diff --git a/test_mixpanel.py b/test_mixpanel.py new file mode 100755 index 0000000..dbf0f76 --- /dev/null +++ b/test_mixpanel.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +import urllib +import unittest +from mixpanel import Mixpanel, encode_data +from mock import Mock, patch + +class MixpanelTestCase(unittest.TestCase): + track_request_url = 'https://api.mixpanel.com/track/' + engage_request_url = 'https://api.mixpanel.com/engage/' + + def setUp(self): + print "set up" + + def tearDown(self): + print "tear down" + + def test_constructor(self): + token = '12345' + mp = Mixpanel(token) + self.assertEqual(mp._token, token) + self.assertEqual(mp.people._token, token) + + def test_encode_data(self): + encoded_ab = encode_data({'a': 'b'}) + self.assertEqual('data=eyJhIjogImIifQ%3D%3D', encoded_ab) + + def test_track(self): + token = '12345' + mp = Mixpanel(token) + mock_response = Mock() + mock_response.read.return_value = '1' + with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: + mp.track('button press', {'size': 'big', 'color': 'blue'}) + data = encode_data({'event': 'button press', 'properties': {'token': '12345', 'size': 'big', 'color': 'blue'}}) + mock_urlopen.assert_called_once_with(self.track_request_url, data) + + def test_people_set(self): + token = '12345' + mp = Mixpanel(token) + mock_response = Mock() + mock_response.read.return_value = '1' + with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: + mp.people.set('amq', {'birth month': 'october', 'favorite color': 'purple'}) + data = encode_data({'$token': '12345', '$distinct_id': 'amq', '$set': {'birth month': 'october', 'favorite color': 'purple'}}) + mock_urlopen.assert_called_once_with(self.engage_request_url, data) + + def test_alias(self): + token = '12345' + mp = Mixpanel(token) + mock_response = Mock() + mock_response.read.return_value = '1' + with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: + mp.alias('amq','3680') + data = encode_data({'event': '$create_alias', 'properties': {'distinct_id': '3680', 'alias': 'amq', 'token': '12345'}}) + mock_urlopen.assert_called_once_with(self.engage_request_url, data) + + def test_events_batch(self): + events_list = [ + { + "event": "Signed Up", + "properties": { + "distinct_id": "13793", + "token": "e3bc4100330c35722740fb8c6f5abddc", + "Referred By": "Friend", + "time": 1371002000 + } + }, + { + "event": "Uploaded Photo", + "properties": { + "distinct_id": "13793", + "token": "e3bc4100330c35722740fb8c6f5abddc", + "Topic": "Vacation", + "time": 1371002104 + } + } + ] + token = "e3bc4100330c35722740fb8c6f5abddc" + mp = Mixpanel(token) + mock_response = Mock() + mock_response.read.return_value = '1' + data = encode_data(events_list) + with patch('urllib2.Request', return_value = mock_response) as mock_Request: + with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: + mp.send_events_batch(events_list) + mock_Request.assert_called_once_with(self.track_request_url, data) + + + +if __name__ == "__main__": + unittest.main() From 1b2a5361c4927c1e69a06c586d6791000b900c8e Mon Sep 17 00:00:00 2001 From: Tim Trefren Date: Wed, 3 Jul 2013 01:10:46 +0000 Subject: [PATCH 15/19] get rid of nested people object --- mixpanel.py | 137 +++++++++++++++++------------------- test_mixpanel.py => test.py | 25 +++---- 2 files changed, 74 insertions(+), 88 deletions(-) rename test_mixpanel.py => test.py (78%) diff --git a/mixpanel.py b/mixpanel.py index b9aa03c..222ce92 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -3,93 +3,64 @@ import urllib import urllib2 -def encode_data(data): - return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) - -def write_request(base_url, endpoint, request, batch=False): - """ - Writes a request taking in either 'track/' for events or 'engage/' for - people. - """ - data = encode_data(request) - request_url = ''.join([base_url, endpoint]) - try: - if not batch: - response = urllib2.urlopen(request_url, data).read() - else: - if len(request) > 50: - raise - batch_request = urllib2.Request(request_url, data) - response = urllib2.urlopen(batch_request).read() - - except urllib2.HTTPError as e: - raise e - return response == '1' - class Mixpanel(object): - class People(object): - def __init__(self, token, base_url): - self._token = token - self._base_url = base_url - - def _people(self, distinct_id, update_type, properties): - record = { - '$token': self._token, - '$distinct_id': distinct_id, - update_type: properties, - } - return write_request(self._base_url, 'engage/', record) - - def set(self, distinct_id, properties): - return self._people(distinct_id, '$set', properties) - - def set_once(self, distinct_id, properties): - return self._people(distinct_id, '$set_once', properties) - - def add(self, distinct_id, properties): - return self._people(distinct_id, '$add', properties) - - def append(self, distinct_id, properties): - return self._people(distinct_id, '$append', properties) - - def union(self, distinct_id, properties): - return self._people(distinct_id, '$union', properties) - - def unset(self, distinct_id, properties): - return self._people(distinct_id, '$unset', properties) - - def delete(self, distinct_id): - return self._people(distinct_id, '$delete', "") - - def send_people_batch(self, data): - return write_request(self._base_url, 'engage/', data, True) - def __init__(self, token, base_url='https://api.mixpanel.com/'): - """ + """ To use mixpanel, create a new Mixpanel object using your token. Use this object to start tracking. Example: mp = Mixpanel('36ada5b10da39a1347559321baf13063') - """ + """ self._token = token self._base_url = base_url - self.people = self.People(self._token, self._base_url) + + @classmethod + def _encode_data(self, data): + return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) + + def _write_request(self, base_url, endpoint, request, batch=False): + """ + Writes a request taking in either 'track/' for events or 'engage/' for + people. + """ + data = self._encode_data(request) + request_url = ''.join([base_url, endpoint]) + try: + if not batch: + response = urllib2.urlopen(request_url, data).read() + else: + if len(request) > 50: + raise + batch_request = urllib2.Request(request_url, data) + response = urllib2.urlopen(batch_request).read() + + except urllib2.HTTPError as e: + raise e + return response == '1' + + def _people(self, distinct_id, update_type, properties): + record = { + '$token': self._token, + '$distinct_id': distinct_id, + update_type: properties, + } + return self._write_request(self._base_url, 'engage/', record) def track(self, event_name, properties={}): - """ + """ For basic event tracking. Should pass in name of event name and dictionary of properties. Example: mp.track('clicked button', { 'color': 'blue', 'text': 'no' }) - """ + """ all_properties = { 'token' : self._token } all_properties.update(properties) event = { 'event': event_name, - 'properties': all_properties, + 'properties': all_properties, } - return write_request(self._base_url, 'track/', event) + return self._write_request(self._base_url, 'track/', event) def alias(self, alias_id, original): """ @@ -100,12 +71,36 @@ def alias(self, alias_id, original): record = { 'event': '$create_alias', 'properties': { - 'distinct_id': original, + 'distinct_id': original, 'alias': alias_id, 'token': self._token, - } + } } - return write_request(self._base_url, 'engage/', record) + return self._write_request(self._base_url, 'engage/', record) + + def people_set(self, distinct_id, properties): + return self._people(distinct_id, '$set', properties) + + def people_set_once(self, distinct_id, properties): + return self._people(distinct_id, '$set_once', properties) + + def people_add(self, distinct_id, properties): + return self._people(distinct_id, '$add', properties) + + def people_append(self, distinct_id, properties): + return self._people(distinct_id, '$append', properties) + + def people_union(self, distinct_id, properties): + return self._people(distinct_id, '$union', properties) + + def people_unset(self, distinct_id, properties): + return self._people(distinct_id, '$unset', properties) + + def people_delete(self, distinct_id): + return self._people(distinct_id, '$delete', "") + + def send_people_batch(self, data): + return self._write_request(self._base_url, 'engage/', data, batch=True) def send_events_batch(self, data): - write_request(self._base_url, 'track/', data, True) + return self._write_request(self._base_url, 'track/', data, batch=True) diff --git a/test_mixpanel.py b/test.py similarity index 78% rename from test_mixpanel.py rename to test.py index dbf0f76..f572a5e 100755 --- a/test_mixpanel.py +++ b/test.py @@ -1,27 +1,20 @@ #!/usr/bin/env python import urllib import unittest -from mixpanel import Mixpanel, encode_data -from mock import Mock, patch +from mixpanel import Mixpanel +from mock import Mock, patch class MixpanelTestCase(unittest.TestCase): track_request_url = 'https://api.mixpanel.com/track/' engage_request_url = 'https://api.mixpanel.com/engage/' - - def setUp(self): - print "set up" - - def tearDown(self): - print "tear down" def test_constructor(self): token = '12345' mp = Mixpanel(token) self.assertEqual(mp._token, token) - self.assertEqual(mp.people._token, token) def test_encode_data(self): - encoded_ab = encode_data({'a': 'b'}) + encoded_ab = Mixpanel._encode_data({'a': 'b'}) self.assertEqual('data=eyJhIjogImIifQ%3D%3D', encoded_ab) def test_track(self): @@ -31,7 +24,7 @@ def test_track(self): mock_response.read.return_value = '1' with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.track('button press', {'size': 'big', 'color': 'blue'}) - data = encode_data({'event': 'button press', 'properties': {'token': '12345', 'size': 'big', 'color': 'blue'}}) + data = mp._encode_data({'event': 'button press', 'properties': {'token': '12345', 'size': 'big', 'color': 'blue'}}) mock_urlopen.assert_called_once_with(self.track_request_url, data) def test_people_set(self): @@ -40,8 +33,8 @@ def test_people_set(self): mock_response = Mock() mock_response.read.return_value = '1' with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: - mp.people.set('amq', {'birth month': 'october', 'favorite color': 'purple'}) - data = encode_data({'$token': '12345', '$distinct_id': 'amq', '$set': {'birth month': 'october', 'favorite color': 'purple'}}) + mp.people_set('amq', {'birth month': 'october', 'favorite color': 'purple'}) + data = mp._encode_data({'$token': '12345', '$distinct_id': 'amq', '$set': {'birth month': 'october', 'favorite color': 'purple'}}) mock_urlopen.assert_called_once_with(self.engage_request_url, data) def test_alias(self): @@ -51,7 +44,7 @@ def test_alias(self): mock_response.read.return_value = '1' with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.alias('amq','3680') - data = encode_data({'event': '$create_alias', 'properties': {'distinct_id': '3680', 'alias': 'amq', 'token': '12345'}}) + data = mp._encode_data({'event': '$create_alias', 'properties': {'distinct_id': '3680', 'alias': 'amq', 'token': '12345'}}) mock_urlopen.assert_called_once_with(self.engage_request_url, data) def test_events_batch(self): @@ -79,13 +72,11 @@ def test_events_batch(self): mp = Mixpanel(token) mock_response = Mock() mock_response.read.return_value = '1' - data = encode_data(events_list) + data = mp._encode_data(events_list) with patch('urllib2.Request', return_value = mock_response) as mock_Request: with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.send_events_batch(events_list) mock_Request.assert_called_once_with(self.track_request_url, data) - - if __name__ == "__main__": unittest.main() From 90f902a823672384ca46b6a5b807a9315b407863 Mon Sep 17 00:00:00 2001 From: Tim Trefren Date: Wed, 3 Jul 2013 01:11:23 +0000 Subject: [PATCH 16/19] ignore pycs --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc From cbd1c2ba574ebc9881b8f0e23d4bb3e6e79dcac4 Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Wed, 3 Jul 2013 01:45:35 +0000 Subject: [PATCH 17/19] added in verbose flag --- mixpanel.py | 2 +- test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 222ce92..5951c98 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -17,7 +17,7 @@ def __init__(self, token, base_url='https://api.mixpanel.com/'): @classmethod def _encode_data(self, data): - return urllib.urlencode({'data': base64.b64encode(json.dumps(data))}) + return urllib.urlencode({'data': base64.b64encode(json.dumps(data)),'verbose':1}) def _write_request(self, base_url, endpoint, request, batch=False): """ diff --git a/test.py b/test.py index f572a5e..813d89b 100755 --- a/test.py +++ b/test.py @@ -15,7 +15,7 @@ def test_constructor(self): def test_encode_data(self): encoded_ab = Mixpanel._encode_data({'a': 'b'}) - self.assertEqual('data=eyJhIjogImIifQ%3D%3D', encoded_ab) + self.assertEqual('data=eyJhIjogImIifQ%3D%3D&verbose=1', encoded_ab) def test_track(self): token = '12345' From 79b3912728e282231e8d98ea424c100f2a1f5dbd Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Wed, 3 Jul 2013 02:04:03 +0000 Subject: [PATCH 18/19] removed len check for batch requests - unecessary given verbose flag --- mixpanel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index 5951c98..b4d09a0 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -30,8 +30,6 @@ def _write_request(self, base_url, endpoint, request, batch=False): if not batch: response = urllib2.urlopen(request_url, data).read() else: - if len(request) > 50: - raise batch_request = urllib2.Request(request_url, data) response = urllib2.urlopen(batch_request).read() From 2b9aa6fb48d576736a82f5d6e008e62ee8c4e78d Mon Sep 17 00:00:00 2001 From: Amy Quispe Date: Wed, 3 Jul 2013 21:51:21 +0000 Subject: [PATCH 19/19] changed name of _encode_data to _prepare_data since that better describes it --- mixpanel.py | 4 ++-- test.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mixpanel.py b/mixpanel.py index b4d09a0..7d94f46 100644 --- a/mixpanel.py +++ b/mixpanel.py @@ -16,7 +16,7 @@ def __init__(self, token, base_url='https://api.mixpanel.com/'): self._base_url = base_url @classmethod - def _encode_data(self, data): + def _prepare_data(self, data): return urllib.urlencode({'data': base64.b64encode(json.dumps(data)),'verbose':1}) def _write_request(self, base_url, endpoint, request, batch=False): @@ -24,7 +24,7 @@ def _write_request(self, base_url, endpoint, request, batch=False): Writes a request taking in either 'track/' for events or 'engage/' for people. """ - data = self._encode_data(request) + data = self._prepare_data(request) request_url = ''.join([base_url, endpoint]) try: if not batch: diff --git a/test.py b/test.py index 813d89b..bc2bcbf 100755 --- a/test.py +++ b/test.py @@ -13,9 +13,9 @@ def test_constructor(self): mp = Mixpanel(token) self.assertEqual(mp._token, token) - def test_encode_data(self): - encoded_ab = Mixpanel._encode_data({'a': 'b'}) - self.assertEqual('data=eyJhIjogImIifQ%3D%3D&verbose=1', encoded_ab) + def test_prepare_data(self): + prepared_ab = Mixpanel._prepare_data({'a': 'b'}) + self.assertEqual('data=eyJhIjogImIifQ%3D%3D&verbose=1', prepared_ab) def test_track(self): token = '12345' @@ -24,7 +24,7 @@ def test_track(self): mock_response.read.return_value = '1' with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.track('button press', {'size': 'big', 'color': 'blue'}) - data = mp._encode_data({'event': 'button press', 'properties': {'token': '12345', 'size': 'big', 'color': 'blue'}}) + data = mp._prepare_data({'event': 'button press', 'properties': {'token': '12345', 'size': 'big', 'color': 'blue'}}) mock_urlopen.assert_called_once_with(self.track_request_url, data) def test_people_set(self): @@ -34,7 +34,7 @@ def test_people_set(self): mock_response.read.return_value = '1' with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.people_set('amq', {'birth month': 'october', 'favorite color': 'purple'}) - data = mp._encode_data({'$token': '12345', '$distinct_id': 'amq', '$set': {'birth month': 'october', 'favorite color': 'purple'}}) + data = mp._prepare_data({'$token': '12345', '$distinct_id': 'amq', '$set': {'birth month': 'october', 'favorite color': 'purple'}}) mock_urlopen.assert_called_once_with(self.engage_request_url, data) def test_alias(self): @@ -44,7 +44,7 @@ def test_alias(self): mock_response.read.return_value = '1' with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.alias('amq','3680') - data = mp._encode_data({'event': '$create_alias', 'properties': {'distinct_id': '3680', 'alias': 'amq', 'token': '12345'}}) + data = mp._prepare_data({'event': '$create_alias', 'properties': {'distinct_id': '3680', 'alias': 'amq', 'token': '12345'}}) mock_urlopen.assert_called_once_with(self.engage_request_url, data) def test_events_batch(self): @@ -72,7 +72,7 @@ def test_events_batch(self): mp = Mixpanel(token) mock_response = Mock() mock_response.read.return_value = '1' - data = mp._encode_data(events_list) + data = mp._prepare_data(events_list) with patch('urllib2.Request', return_value = mock_response) as mock_Request: with patch('urllib2.urlopen', return_value = mock_response) as mock_urlopen: mp.send_events_batch(events_list)