Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit. Core functionality to make queries against MixPanel's…
… query API is working. A single method, `get_unique_events()` is implemented and returns th e expected results.
- Loading branch information
Sean Coonce
committed
Aug 3, 2014
0 parents
commit ee9d4b0
Showing
9 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
build/ | ||
dist/ | ||
*.egg-info/ | ||
*.pyc | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# mixpanel-query-py | ||
|
||
The Python interface to fetch data from Mixpanel via [Mixpanel's Data Query API](https://mixpanel.com/docs/api-documentation/data-export-api). Note, this differs from the official [Python binding](https://github.com/mixpanel/mixpanel-python) which only provides an interface to send data to Mixpanel. | ||
|
||
# Installation | ||
|
||
To install mixpanel-query-py, simply: | ||
|
||
``` | ||
$ sudo pip install mixpanel-query-py | ||
``` | ||
|
||
or alternatively (you really should be using pip though): | ||
|
||
``` | ||
$ sudo easy_install mixpanel-query-py | ||
``` | ||
or from source: | ||
|
||
``` | ||
$ git clone git@github.com:cooncesean/mixpanel-query-py.git | ||
$ cd mixpanel-query-py | ||
$ python setup.py install | ||
``` | ||
|
||
# Usage | ||
|
||
You will need a [Mixpanel account](https://mixpanel.com/register/) and your `API_KEY` + `API_SECRET` to access your project's data via their API; which can be found in "Account" > "Projects". | ||
|
||
```python | ||
from mixpanel_query.client import MixpanelQueryClient | ||
from your_project.conf import MIXPANEL_API_KEY, MIXPANEL_API_SECRET | ||
|
||
# Instantiate the client | ||
query_client = MixpanelQueryClient(MIXPANEL_API_KEY, MIXPANEL_API_SECRET) | ||
|
||
# Query your project's data | ||
data = query_client.get_unique_events(['Some Event Name'], 'hour', 24) | ||
print data | ||
{ | ||
'data': { | ||
'series': ['2010-05-29', '2010-05-30', '2010-05-31'], | ||
'values': { | ||
'account-page': {'2010-05-30': 1}, | ||
'splash features': { | ||
'2010-05-29': 6, | ||
'2010-05-30': 4, | ||
'2010-05-31': 5, # Date + unique event counts | ||
} | ||
} | ||
}, | ||
'legend_size': 2 | ||
} | ||
``` | ||
|
||
View the [api reference](#api-reference) for details on accessing different endpoints. | ||
|
||
# API Reference | ||
|
||
Mixpanels' full [API reference is documented here](https://mixpanel.com/docs/api-documentation/data-export-api). | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = '0.0.1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
from mixpanel_query.connection import Connection | ||
from mixpanel_query.exceptions import InvalidUnitException, InvalidFormatException | ||
|
||
|
||
class MixpanelQueryClient(object): | ||
""" | ||
Connects to the `Mixpanel Data Export API` | ||
and provides an interface to query data based on the project | ||
specified with your api credentials. | ||
Full API Docs: https://mixpanel.com/docs/api-documentation/data-export-api | ||
""" | ||
ENDPOINT = 'http://mixpanel.com/api' | ||
VERSION = '2.0' | ||
|
||
UNIT_MINUTE = 'minute' | ||
UNIT_HOUR = 'hour' | ||
UNIT_DAY = 'day' | ||
UNIT_WEEK = 'week' | ||
UNIT_MONTH = 'month' | ||
VALID_UNITS = (UNIT_MINUTE, UNIT_HOUR, UNIT_DAY, UNIT_WEEK, UNIT_MONTH) | ||
|
||
FORMAT_JSON = 'json' | ||
FORMAT_CSV = 'csv' | ||
VALID_RESPONSE_FORMATS = (FORMAT_JSON, FORMAT_CSV) | ||
|
||
def __init__(self, api_key, api_secret): | ||
self.api_key = api_key | ||
self.api_secret = api_secret | ||
self.connection = Connection(self) | ||
|
||
# Annotation methods ############## | ||
|
||
# Event methods ################### | ||
def get_unique_events(self, event_names, unit, interval, response_format=FORMAT_JSON): | ||
""" | ||
Get unique event data for a set of event types over the last N days, weeks, or months. | ||
Args: | ||
`event_names`: [list] The event or events that you wish to get data for. | ||
[sample]: ["play song", "log in", "add playlist"] | ||
`unit`: [str] Determines the level of granularity of the data you get back. | ||
[sample]: "day" or "month" or "week" | ||
`interval`: [int] The number of "units" to return data for. `1` will return data for the | ||
current unit (minute, hour, day, week or month). `2` will return the | ||
current and previous units, and so on. | ||
`response_format`: [string (optional)]: The data return format. | ||
[sample]: "json" or "csv" | ||
Response format: | ||
{ | ||
u'data': { | ||
u'series': [u'2014-07-11', u'2014-07-12', u'2014-07-13'], | ||
u'values': { | ||
u'Guide Download': { | ||
u'2014-07-11': 80, | ||
u'2014-07-12': 100, | ||
u'2014-07-13': 123, # Date + unique event counts | ||
} | ||
} | ||
}, | ||
u'legend_size': 1 | ||
} | ||
""" | ||
self._validate_unit(unit) | ||
self._validate_response_format(response_format) | ||
return self.connection.request( | ||
'events', | ||
{ | ||
'event': event_names, | ||
'unit': unit, | ||
'interval': interval, | ||
'type': 'unique' | ||
} | ||
) | ||
|
||
# Event properties methods ######## | ||
# Funnel methods ################## | ||
# Segmentation methods ############ | ||
# Retention methods ############### | ||
# People methods ################## | ||
|
||
# Util methods #################### | ||
def _validate_unit(self, unit): | ||
" Utility method used to validate a `unit` param. " | ||
if unit not in self.VALID_UNITS: | ||
raise InvalidUnitException('The `unit` specified is invalid. Must be: {0}'.format(self.VALID_UNITS)) | ||
|
||
def _validate_response_format(self, response_format): | ||
" Utility method used to validate a `response_format` param. " | ||
if response_format not in self.VALID_RESPONSE_FORMATS: | ||
raise InvalidFormatException('The `response_format` specified is invalid. Must be {0}.'.format(self.VALID_RESPONSE_FORMATS)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
""" | ||
The class(es) in this module contain logic to make http | ||
requests to the Mixpanel API. | ||
""" | ||
import hashlib | ||
import json | ||
import time | ||
import urllib | ||
import urllib2 | ||
|
||
|
||
class Connection(object): | ||
""" | ||
The `Connection` object's sole responsibility is to format, send to | ||
and parse http responses from the Mixpanel API. | ||
""" | ||
ENDPOINT = 'http://mixpanel.com/api' | ||
VERSION = '2.0' | ||
|
||
def __init__(self, client): | ||
self.client = client | ||
|
||
def request(self, method_name, params, format='json'): | ||
""" | ||
Make a request to Mixpanel query endpoints and return the | ||
response. | ||
""" | ||
params['api_key'] = self.client.api_key | ||
params['expire'] = int(time.time()) + 600 # Grant this request 10 minutes. | ||
params['format'] = format | ||
if 'sig' in params: | ||
del params['sig'] | ||
params['sig'] = self.hash_args(params) | ||
request_url = '{base_url}/{version}/{method_name}/?{encoded_params}'.format( | ||
base_url=self.ENDPOINT, | ||
version=self.VERSION, | ||
method_name=method_name, | ||
encoded_params=self.unicode_urlencode(params) | ||
) | ||
request = urllib2.urlopen(request_url, timeout=120) | ||
data = request.read() | ||
return json.loads(data) | ||
|
||
def unicode_urlencode(self, params): | ||
""" | ||
Convert lists to JSON encoded strings, and correctly handle any | ||
unicode URL parameters. | ||
""" | ||
if isinstance(params, dict): | ||
params = params.items() | ||
for i, param in enumerate(params): | ||
if isinstance(param[1], list): | ||
params[i] = (param[0], json.dumps(param[1]),) | ||
|
||
return urllib.urlencode( | ||
[(k, isinstance(v, unicode) and v.encode('utf-8') or v) for k, v in params] | ||
) | ||
|
||
def hash_args(self, args, secret=None): | ||
""" | ||
Hashes arguments by joining key=value pairs, appending the api_secret, and | ||
then taking the MD5 hex digest. | ||
""" | ||
for a in args: | ||
if isinstance(args[a], list): | ||
args[a] = json.dumps(args[a]) | ||
|
||
args_joined = '' | ||
for a in sorted(args.keys()): | ||
if isinstance(a, unicode): | ||
args_joined += a.encode('utf-8') | ||
else: | ||
args_joined += str(a) | ||
|
||
args_joined += '=' | ||
|
||
if isinstance(args[a], unicode): | ||
args_joined += args[a].encode('utf-8') | ||
else: | ||
args_joined += str(args[a]) | ||
|
||
hash = hashlib.md5(args_joined) | ||
|
||
if secret: | ||
hash.update(secret) | ||
elif self.client.api_secret: | ||
hash.update(self.client.api_secret) | ||
return hash.hexdigest() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
class MixpanelQueryException(Exception): | ||
pass | ||
|
||
class InvalidUnitException(MixpanelQueryException): | ||
" An invalid time unit was passed. " | ||
pass | ||
|
||
class InvalidFormatException(MixpanelQueryException): | ||
" An invalid response format was passed. " | ||
pass | ||
|
||
class InvalidAPIKeyException(MixpanelQueryException): | ||
" An invalid API key + secret were passed. Check your account page for the correct key. " | ||
pass | ||
|
||
class ExpiredRequestException(MixpanelQueryException): | ||
" The request is past its expiration date (default 10 minutes). " | ||
pass | ||
|
||
class InvalidDateRangeException(MixpanelQueryException): | ||
" The date range you have specified is not 30 days or less. " | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from setuptools import setup, find_packages | ||
|
||
|
||
setup( | ||
name='mixpanel-query-py', | ||
version=__import__('mixpanel_query').__version__, | ||
description='The Python interface to query data from Mixpanel.', | ||
author='Sean Coonce', | ||
author_email='cooncesean@gmail.com', | ||
url='https://www.github.com/cooncesean/mixpanel-query-py', | ||
packages=find_packages(), | ||
classifiers=[ | ||
'Environment :: Web Environment', | ||
'Intended Audience :: Developers', | ||
'License :: OSI Approved :: MIT License', | ||
'Operating System :: OS Independent', | ||
'Programming Language :: Python', | ||
], | ||
include_package_data=True, | ||
zip_safe=False, | ||
) |