-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial checkin of Stats, LLC wrapper
- Loading branch information
1 parent
e3c6d63
commit dd82b30
Showing
14 changed files
with
3,532 additions
and
1 deletion.
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
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,72 @@ | ||
# sports-stats | ||
|
||
sports-stats is written to make restful API calls to [Stats, LLC](http://www.stats.com/) easier and faster. | ||
|
||
sports-stats must be used with Python 2.7 or above. | ||
|
||
## Install | ||
|
||
To install sports-stats using `pip`: | ||
|
||
pip install sports-stats -U | ||
|
||
To manually install sports-stats: | ||
|
||
python setup.py install | ||
|
||
To install sports-stats using `pip` from GitHub: | ||
|
||
pip install -e git+git://github.com/sportsy/sports-stats.git#egg=sports-stats | ||
|
||
|
||
## Example | ||
You can make a simple call with this API wrapper, below is an example to get a MLB team's information. Please refer to the IO docs on Stats for endpoints and data returned. | ||
|
||
```python | ||
from stats import StatsAPI | ||
|
||
#Get a team's schedule | ||
api = StatsAPI('API_KEY', 'API_SECRET') | ||
resp = api.request('/stats/baseball/mlb/events/teams/:teamId', {'teamId': 251}) | ||
print resp.json() | ||
``` | ||
The wrapper itself is pretty flexible allowing both rest url parameters and get parameters when passing flags in with the request. | ||
|
||
To make requests, you can keep the same structure as the endpoints on Stats documentation and the second argument in the request method is the rest URL params you want to replace with their values. The third argument is the URL get parameters. Both of these method arguments take a *dictionary*. | ||
```python | ||
from stats import StatsAPI | ||
|
||
#Get an event's score | ||
api = StatsAPI('API_KEY', 'API_SECRET') | ||
resp = api.request('/stats/baseball/mlb/scores/:eventId', {'eventId': 1234}, {'linescore': 'true'}) | ||
print resp.json() | ||
``` | ||
|
||
```python | ||
from stats import StatsAPI | ||
|
||
#Get information about a specific MLB team | ||
api = StatsAPI('API_KEY', 'API_SECRET') | ||
resp = api.request('/stats/baseball/mlb/teams/:teamId', {'teamId': 251}) | ||
print resp.json() | ||
``` | ||
|
||
## Response methods / values | ||
- `resp.headers` returns the headers of the request | ||
- `resp.status_code` returns the status code of the request | ||
- `resp.text()` returns the raw object | ||
- `resp.json()` returns the object in json format | ||
|
||
## Testing | ||
We've integrated pytest into our library so you can run your tests. To run your test, all you need to do is run | ||
|
||
python setup.py test | ||
|
||
The test will then run and make API calls to the Stats API, we've put 1 second delay into each call so there won't be any false positives with API rate limiting. | ||
|
||
## Need help? | ||
Submit a ticket, we'll be more than happy to help out! | ||
|
||
## Useful Links | ||
[Stats IO Docs](http://developer.stats.com/io-docs) | ||
[Stats Documentation](http://developer.stats.com/docs) |
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 @@ | ||
__title__ = 'Stats LLC API Wrapper' | ||
__version__ = '0.0.1' | ||
__author__ = 'Sportsy, Inc.' | ||
__license__ = 'MIT' | ||
__copyright__ = 'Copyright 2015 Sportsy, Inc.' |
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,2 @@ | ||
test: | ||
cd sportsstats && nosetests -v |
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,3 @@ | ||
mox==0.5.3 | ||
pytest==2.7.0 | ||
requests==2.6.0 |
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 @@ | ||
requests==2.6.0 |
Large diffs are not rendered by default.
Oops, something went wrong.
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,75 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
from __future__ import absolute_import | ||
from setuptools import setup, find_packages, Command | ||
import re | ||
import os | ||
import codecs | ||
|
||
__title__ = 'Stats, LLC API Wrapper' | ||
__version__ = '0.0.1' | ||
__author__ = 'Sportsy, Inc.' | ||
__license__ = 'MIT' | ||
__copyright__ = 'Copyright 2015 Sportsy, Inc.' | ||
|
||
|
||
|
||
class PyTest(Command): | ||
user_options = [] | ||
def initialize_options(self): | ||
pass | ||
|
||
def finalize_options(self): | ||
pass | ||
|
||
def run(self): | ||
import subprocess | ||
import sys | ||
errno = subprocess.call([sys.executable, 'runtests.py']) | ||
raise SystemExit(errno) | ||
|
||
def read(*parts): | ||
path = os.path.join(os.path.dirname(__file__), *parts) | ||
with codecs.open(path, encoding='utf-8') as fobj: | ||
return fobj.read() | ||
|
||
|
||
def find_version(*file_paths): | ||
version_file = read(*file_paths) | ||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", | ||
version_file, re.M) | ||
if version_match: | ||
return version_match.group(1) | ||
raise RuntimeError("Unable to find version string.") | ||
|
||
|
||
with open('requirements.txt') as f: | ||
install_requires = f.read().splitlines() | ||
|
||
with open('requirements-dev.txt') as f: | ||
tests_require = f.read().splitlines() | ||
|
||
print install_requires | ||
print tests_require | ||
|
||
setup( | ||
name='sports-stats', | ||
version=__version__, | ||
description='', | ||
url='https://github.com/sportsy/sports-stats', | ||
author=__author__, | ||
license=__license__, | ||
packages=find_packages(exclude=['tests.*', 'tests']), | ||
include_package_data=True, | ||
test_suite='nose.collector', | ||
install_requires=install_requires, | ||
dependency_links=['https://github.com/kennethreitz/requests/tarball/master#egg=requests==2.6.0'], | ||
tests_require=tests_require, | ||
cmdclass = {'test': PyTest}, | ||
#entry_points={ | ||
# 'console_scripts': [ | ||
# 'sportsstats = sportsstats.api:main', | ||
# ] | ||
# }, | ||
) |
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,26 @@ | ||
__title__ = 'Stats LLC API Wrapper' | ||
__version__ = '0.0.1' | ||
__author__ = 'Sportsy, Inc.' | ||
__license__ = 'MIT' | ||
__copyright__ = 'Copyright 2015 Sportsy, Inc.' | ||
|
||
import logging | ||
|
||
|
||
# No logging unless the client provides a handler, | ||
logging.getLogger(__name__).addHandler(logging.NullHandler()) | ||
|
||
try: | ||
from .api import StatsAPI, StatsResponse | ||
from .exceptions import StatsConnectionError, StatsError, StatsRequestError | ||
except: | ||
pass | ||
|
||
|
||
__all__ = [ | ||
'StatsAPI', | ||
'StatsResponse', | ||
'StatsConnectionError', | ||
'StatsError', | ||
'StatsRequestError' | ||
] |
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,161 @@ | ||
from datetime import datetime | ||
from requests.exceptions import ConnectionError, ReadTimeout, SSLError | ||
from requests.packages.urllib3.exceptions import ReadTimeoutError, ProtocolError | ||
from .exceptions import * | ||
|
||
import requests | ||
import hashlib | ||
import socket | ||
import ssl | ||
import time | ||
|
||
BASE_URL = 'http://api.stats.com/v1' | ||
REST_TIMEOUT = None | ||
API_SECRET = None | ||
|
||
|
||
class StatsAPI(object): | ||
|
||
"""Access REST API or Streaming API resources. | ||
:param key: Stats application key | ||
:param secret: Stats application secret | ||
:param accept: Stats accept type (json, xml) | ||
""" | ||
|
||
api_key = None | ||
api_secret = None | ||
api_accept = None | ||
|
||
def __init__( | ||
self, | ||
key=None, | ||
secret=None, | ||
accept='json'): | ||
"""Initialize with your application credentials""" | ||
|
||
if not all([key, secret]): | ||
raise Exception('Missing authentication parameter') | ||
|
||
self.api_key = key | ||
self.api_secret = secret | ||
self.api_accept = accept | ||
|
||
def _prepare_url(self, domain, path, params=None): | ||
auth = self.generate_auth() | ||
url = '{}{}?api_key={}&sig={}'.format(domain, path, auth['api_key'], auth['sig']) | ||
if params is not None: | ||
for k in params: | ||
url += '&{}={}'.format(str(k), str(params[k])) | ||
return url | ||
|
||
def _get_endpoint(self, resource, params=None): | ||
"""Substitute any parameters in the resource path with :PARAM.""" | ||
if ':' in resource: | ||
if params is not None: | ||
for k in params: | ||
try: | ||
url_param = ':%s' % k | ||
resource = resource.replace(url_param, str(params[k])) | ||
except: | ||
pass | ||
return resource, resource | ||
|
||
else: | ||
return resource, resource | ||
|
||
else: | ||
return resource, resource | ||
|
||
def request(self, resource, rest_params=None, params=None, files=None): | ||
"""Request a Stats REST API . | ||
:param resource: A valid Stats endpoint | ||
:param params: Dictionary with endpoint parameters or None (default) | ||
:returns: StatsResponse | ||
:raises: StatsConnectionError | ||
""" | ||
resource, endpoint = self._get_endpoint(resource, rest_params) | ||
# if endpoint not in ENDPOINTS: | ||
# raise Exception('Endpoint "%s" unsupported' % endpoint) | ||
|
||
# payload = {'key1': 'value1', 'key2': 'value2'} | ||
# r = requests.get("http://httpbin.org/get", params=payload) | ||
session = requests.Session() | ||
# session.headers = {'User-Agent': USER_AGENT} | ||
method = 'GET' | ||
subdomain = BASE_URL | ||
url = self._prepare_url(subdomain, resource, params) | ||
session.stream = False | ||
timeout = REST_TIMEOUT | ||
if method == 'POST': | ||
data = params | ||
params = None | ||
else: | ||
data = None | ||
try: | ||
r = session.request( | ||
method, | ||
url, | ||
data=data, | ||
params=params, | ||
timeout=timeout, | ||
files=files, | ||
# proxies=self.proxies | ||
) | ||
except (ConnectionError, ProtocolError, ReadTimeout, ReadTimeoutError, | ||
SSLError, ssl.SSLError, socket.error) as e: | ||
raise StatsConnectionError(e) | ||
return StatsResponse(r) | ||
|
||
def generate_auth(self): | ||
"""Returns the dictionary for the authentication get params | ||
""" | ||
return {'accept': self.api_accept, 'api_key': self.api_key, 'sig': self.generate_sig()} | ||
|
||
def generate_sig(self): | ||
"""Returns the SHA256 signature for making requests | ||
""" | ||
timestamp = int(time.time()) | ||
hash_it = '{}{}{}'.format(self.api_key, self.api_secret, timestamp) | ||
return hashlib.sha256(hash_it).hexdigest() | ||
|
||
|
||
class StatsResponse(object): | ||
|
||
"""Response from either a REST API call. | ||
:param response: The requests.Response object returned by the API call | ||
""" | ||
|
||
def __init__(self, response): | ||
self.response = response | ||
|
||
@property | ||
def headers(self): | ||
""":returns: Dictionary of API response header contents.""" | ||
return self.response.headers | ||
|
||
@property | ||
def status_code(self): | ||
""":returns: HTTP response status code.""" | ||
return self.response.status_code | ||
|
||
@property | ||
def text(self): | ||
""":returns: Raw API response text.""" | ||
return self.response.text | ||
|
||
def json(self): | ||
""":returns: response as JSON object.""" | ||
return self.response.json() | ||
|
||
def get_rest_quota(self): | ||
""":returns: Quota information in the REST-only response header.""" | ||
remaining, limit, reset = None, None, None | ||
if self.response: | ||
if 'x-rate-limit-remaining' in self.response.headers: | ||
remaining = int( | ||
self.response.headers['x-rate-limit-remaining']) | ||
if remaining == 0: | ||
limit = int(self.response.headers['x-rate-limit-limit']) | ||
reset = int(self.response.headers['x-rate-limit-reset']) | ||
reset = datetime.fromtimestamp(reset) | ||
return {'remaining': remaining, 'limit': limit, 'reset': reset} |
Oops, something went wrong.