Skip to content

Commit 2c17d99

Browse files
author
David Grant
authored
api_secret support (#89)
* api_secret support. * Clarify key/secret a little more. * Don't use root logger.
1 parent 57a12d6 commit 2c17d99

File tree

2 files changed

+79
-22
lines changed

2 files changed

+79
-22
lines changed

mixpanel/__init__.py

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717
from __future__ import absolute_import, unicode_literals
1818
import datetime
1919
import json
20+
import logging
2021
import time
2122
import uuid
2223

2324
import six
2425
from six.moves import range
2526
import urllib3
2627

27-
__version__ = '4.7.0'
28+
__version__ = '4.8.0'
2829
VERSION = __version__ # TODO: remove when bumping major version.
2930

31+
logger = logging.getLogger(__name__)
32+
3033

3134
class DatetimeSerializer(json.JSONEncoder):
3235
def default(self, obj):
@@ -100,23 +103,37 @@ def track(self, distinct_id, event_name, properties=None, meta=None):
100103
self._consumer.send('events', json_dumps(event, cls=self._serializer))
101104

102105
def import_data(self, api_key, distinct_id, event_name, timestamp,
103-
properties=None, meta=None):
106+
properties=None, meta=None, api_secret=None):
104107
"""Record an event that occurred more than 5 days in the past.
105108
106-
:param str api_key: your Mixpanel project's API key
109+
:param str api_key: (DEPRECATED) your Mixpanel project's API key
107110
:param str distinct_id: identifies the user triggering the event
108111
:param str event_name: a name describing the event
109112
:param int timestamp: UTC seconds since epoch
110113
:param dict properties: additional data to record; keys should be
111114
strings, and values should be strings, numbers, or booleans
112115
:param dict meta: overrides Mixpanel special properties
116+
:param str api_secret: Your Mixpanel project's API secret.
117+
118+
Important: Mixpanel's ``import`` HTTP endpoint requires the project API
119+
secret found in your Mixpanel project's settings. The older API key is
120+
no longer accessible in the Mixpanel UI, but will continue to work.
121+
The api_key parameter will be removed in an upcoming release of
122+
mixpanel-python.
123+
124+
.. versionadded:: 4.8.0
125+
The *api_secret* parameter.
113126
114127
To avoid accidentally recording invalid events, the Mixpanel API's
115128
``track`` endpoint disallows events that occurred too long ago. This
116129
method can be used to import such events. See our online documentation
117130
for `more details
118131
<https://developer.mixpanel.com/docs/importing-old-events>`__.
119132
"""
133+
134+
if api_secret is None:
135+
logger.warning("api_key will soon be removed from mixpanel-python; please use api_secret instead.")
136+
120137
all_properties = {
121138
'token': self._token,
122139
'distinct_id': distinct_id,
@@ -133,7 +150,8 @@ def import_data(self, api_key, distinct_id, event_name, timestamp,
133150
}
134151
if meta:
135152
event.update(meta)
136-
self._consumer.send('imports', json_dumps(event, cls=self._serializer), api_key)
153+
154+
self._consumer.send('imports', json_dumps(event, cls=self._serializer), api_key, api_secret)
137155

138156
def alias(self, alias_id, original, meta=None):
139157
"""Creates an alias which Mixpanel will use to remap one id to another.
@@ -166,19 +184,32 @@ def alias(self, alias_id, original, meta=None):
166184
event.update(meta)
167185
sync_consumer.send('events', json_dumps(event, cls=self._serializer))
168186

169-
def merge(self, api_key, distinct_id1, distinct_id2, meta=None):
187+
def merge(self, api_key, distinct_id1, distinct_id2, meta=None, api_secret=None):
170188
"""
171189
Merges the two given distinct_ids.
172190
173-
:param str api_key: Your Mixpanel project's API key.
191+
:param str api_key: (DEPRECATED) Your Mixpanel project's API key.
174192
:param str distinct_id1: The first distinct_id to merge.
175193
:param str distinct_id2: The second (other) distinct_id to merge.
176194
:param dict meta: overrides Mixpanel special properties
195+
:param str api_secret: Your Mixpanel project's API secret.
196+
197+
Important: Mixpanel's ``merge`` HTTP endpoint requires the project API
198+
secret found in your Mixpanel project's settings. The older API key is
199+
no longer accessible in the Mixpanel UI, but will continue to work.
200+
The api_key parameter will be removed in an upcoming release of
201+
mixpanel-python.
202+
203+
.. versionadded:: 4.8.0
204+
The *api_secret* parameter.
177205
178206
See our online documentation for `more
179207
details
180208
<https://developer.mixpanel.com/docs/http#merge>`__.
181209
"""
210+
if api_secret is None:
211+
logger.warning("api_key will soon be removed from mixpanel-python; please use api_secret instead.")
212+
182213
event = {
183214
'event': '$merge',
184215
'properties': {
@@ -188,7 +219,7 @@ def merge(self, api_key, distinct_id1, distinct_id2, meta=None):
188219
}
189220
if meta:
190221
event.update(meta)
191-
self._consumer.send('imports', json_dumps(event, cls=self._serializer), api_key)
222+
self._consumer.send('imports', json_dumps(event, cls=self._serializer), api_key, api_secret)
192223

193224
def people_set(self, distinct_id, properties, meta=None):
194225
"""Set properties of a people record.
@@ -530,22 +561,27 @@ def __init__(self, events_url=None, people_url=None, import_url=None,
530561
cert_reqs=cert_reqs,
531562
)
532563

533-
def send(self, endpoint, json_message, api_key=None):
564+
def send(self, endpoint, json_message, api_key=None, api_secret=None):
534565
"""Immediately record an event or a profile update.
535566
536567
:param endpoint: the Mixpanel API endpoint appropriate for the message
537568
:type endpoint: "events" | "people" | "groups" | "imports"
538569
:param str json_message: a JSON message formatted for the endpoint
539570
:param str api_key: your Mixpanel project's API key
571+
:param str api_secret: your Mixpanel project's API secret
540572
:raises MixpanelException: if the endpoint doesn't exist, the server is
541573
unreachable, or the message cannot be processed
574+
575+
576+
.. versionadded:: 4.8.0
577+
The *api_secret* parameter.
542578
"""
543-
if endpoint in self._endpoints:
544-
self._write_request(self._endpoints[endpoint], json_message, api_key)
545-
else:
579+
if endpoint not in self._endpoints:
546580
raise MixpanelException('No such endpoint "{0}". Valid endpoints are one of {1}'.format(endpoint, self._endpoints.keys()))
547581

548-
def _write_request(self, request_url, json_message, api_key=None):
582+
self._write_request(self._endpoints[endpoint], json_message, api_key, api_secret)
583+
584+
def _write_request(self, request_url, json_message, api_key=None, api_secret=None):
549585
data = {
550586
'data': json_message,
551587
'verbose': 1,
@@ -554,11 +590,17 @@ def _write_request(self, request_url, json_message, api_key=None):
554590
if api_key:
555591
data.update({'api_key': api_key})
556592

593+
headers = None
594+
595+
if api_secret is not None:
596+
headers = urllib3.util.make_headers(basic_auth="{}:".format(api_secret))
597+
557598
try:
558599
response = self._http.request(
559600
'POST',
560601
request_url,
561602
fields=data,
603+
headers=headers,
562604
encode_multipart=False, # URL-encode payload in POST body.
563605
)
564606
except Exception as e:
@@ -621,7 +663,7 @@ def __init__(self, max_size=50, events_url=None, people_url=None, import_url=Non
621663
self._max_size = min(50, max_size)
622664
self._api_key = None
623665

624-
def send(self, endpoint, json_message, api_key=None):
666+
def send(self, endpoint, json_message, api_key=None, api_secret=None):
625667
"""Record an event or profile update.
626668
627669
Internally, adds the message to a buffer, and then flushes the buffer
@@ -633,6 +675,7 @@ def send(self, endpoint, json_message, api_key=None):
633675
:type endpoint: "events" | "people" | "groups" | "imports"
634676
:param str json_message: a JSON message formatted for the endpoint
635677
:param str api_key: your Mixpanel project's API key
678+
:param str api_secret: your Mixpanel project's API secret
636679
:raises MixpanelException: if the endpoint doesn't exist, the server is
637680
unreachable, or any buffered message cannot be processed
638681
@@ -644,8 +687,9 @@ def send(self, endpoint, json_message, api_key=None):
644687

645688
buf = self._buffers[endpoint]
646689
buf.append(json_message)
647-
if api_key is not None:
648-
self._api_key = api_key
690+
# Fixme: Don't stick these in the instance.
691+
self._api_key = api_key
692+
self._api_secret = api_secret
649693
if len(buf) >= self._max_size:
650694
self._flush_endpoint(endpoint)
651695

@@ -664,7 +708,7 @@ def _flush_endpoint(self, endpoint):
664708
batch = buf[:self._max_size]
665709
batch_json = '[{0}]'.format(','.join(batch))
666710
try:
667-
self._consumer.send(endpoint, batch_json, self._api_key)
711+
self._consumer.send(endpoint, batch_json, self._api_key, self._api_secret)
668712
except MixpanelException as orig_e:
669713
mp_e = MixpanelException(orig_e)
670714
mp_e.message = batch_json

test_mixpanel.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ class LogConsumer(object):
2020
def __init__(self):
2121
self.log = []
2222

23-
def send(self, endpoint, event, api_key=None):
23+
def send(self, endpoint, event, api_key=None, api_secret=None):
24+
entry = [endpoint, json.loads(event)]
2425
if api_key:
25-
self.log.append((endpoint, json.loads(event), api_key))
26-
else:
27-
self.log.append((endpoint, json.loads(event)))
26+
entry.append(api_key)
27+
if api_secret:
28+
entry.append(api_secret)
29+
self.log.append(tuple(entry))
2830

2931

3032
class TestMixpanel:
@@ -79,7 +81,9 @@ def test_track_empty(self):
7981

8082
def test_import_data(self):
8183
timestamp = time.time()
82-
self.mp.import_data('MY_API_KEY', 'ID', 'button press', timestamp, {'size': 'big', 'color': 'blue', '$insert_id': 'abc123'})
84+
self.mp.import_data('MY_API_KEY', 'ID', 'button press', timestamp,
85+
{'size': 'big', 'color': 'blue', '$insert_id': 'abc123'},
86+
api_secret='MY_SECRET')
8387
assert self.consumer.log == [(
8488
'imports', {
8589
'event': 'button press',
@@ -94,7 +98,8 @@ def test_import_data(self):
9498
'$lib_version': mixpanel.__version__,
9599
},
96100
},
97-
'MY_API_KEY'
101+
'MY_API_KEY',
102+
'MY_SECRET',
98103
)]
99104

100105
def test_track_meta(self):
@@ -517,6 +522,14 @@ def test_send_remembers_api_key(self):
517522
self.consumer.flush()
518523
assert self.log == [('imports', ['Event'], 'MY_API_KEY')]
519524

525+
def test_send_remembers_api_secret(self):
526+
self.consumer.send('imports', '"Event"', api_secret='ZZZZZZ')
527+
assert len(self.log) == 0
528+
self.consumer.flush()
529+
assert self.log == [('imports', ['Event'], 'ZZZZZZ')]
530+
531+
532+
520533

521534
class TestFunctional:
522535
@classmethod

0 commit comments

Comments
 (0)