Skip to content

Instantly share code, notes, and snippets.

@shazow
Created August 8, 2011 21:08
Show Gist options
  • Save shazow/1132742 to your computer and use it in GitHub Desktop.
Save shazow/1132742 to your computer and use it in GitHub Desktop.
Make good HTTP API libraries really easily using urllib3.
from simpleapi import SimpleAPI
class FacebookError(Exception):
def __init__(self, type, message, response=None):
self.type = type
self.message = message
self.response = response
def __str__(self):
return "%s (%s)" % (self.type, self.message)
def __repr__(self):
return "%s(type=%s)" % (self.__class__.__name__, self.type)
class FacebookAPI(SimpleAPI):
BASE_URL = 'https://graph.facebook.com/'
def _handle_response(self, response):
r = super(FacebookAPI, self)._handle_response(response)
has_error = r.get('error')
if not has_error:
return r
raise FacebookError(has_error['type'], has_error['message'], response=response)
"""
Usage:
api = FacebookAPI()
r = api.call('/', ids='shazow,minecraft')
"""
from simpleapi import SimpleAPI_SharedSecret
from urllib import urlencode
class KloutError(Exception):
STATUS_MAP = {
200: "OK: Success",
202: "Accepted: Your request was accepted and the user was queued for processing.",
401: "Not Authorized: either you need to provide authentication credentials, or the credentials provided aren't valid.",
403: "Bad Request: your request is invalid, and we'll return an error message that tells you why. This is the status code returned if you've exceeded the rate limit (see below).",
404: "Not Found: either you're requesting an invalid URI or the resource in question doesn't exist (ex: no such user in our system).",
500: "Internal Server Error: we did something wrong.",
501: "Not implemented.",
502: "Bad Gateway: returned if Klout is down or being upgraded.",
503: "Service Unavailable: the Klout servers are up, but are overloaded with requests. Try again later.",
}
def __init__(self, status, response=None):
self.status = status
self.response = response
def __str__(self):
return "%s (%s)" % (self.status, self.STATUS_MAP.get(self.status, 'Unknown error.'))
def __repr__(self):
return "%s(status=%s)" % (self.__class__.__name__, self.status)
class KloutAPI(SimpleAPI_SharedSecret):
"""
Methods:
- klout
- users/show
- users/topics
- soi/influenced_by
- soi/influencer_of
"""
BASE_URL = 'http://api.klout.com/1/'
def _compose_url(self, path, params=None):
p = dict(key=self.api_key, **(params or {}))
if params:
p.update(params)
return self.BASE_URL + path + '.json' + '?' + urlencode(p)
def _handle_response(self, response):
if response.status > 299:
raise KloutError(response.status, response=response)
return super(KloutAPI, self)._handle_response(self, response)
def call(self, path, **params):
return self._request('GET', path, params=params)
"""
Usage:
api = KloutAPI('xxxxx')
r = api.call('/users/show', users='shazow,limedaring')
api_limits = RateLimiter(max_messages=10, every_seconds=1)
api = KloutAPI('xxxxx', rate_limit_lock=api_limits)
# ...
"""
from urllib3 import connection_from_url, MaxRetryError, TimeoutError
from urllib import urlencode
import json
import logging
log = logging.getLogger(__name__)
def _connection_pool_factory(base_url):
return connection_from_url(base_url)
class SimpleAPI(object):
BASE_URL = 'http://localhost:5000/'
def __init__(self, connection_pool_factory=_connection_pool_factory, rate_limit_lock=None):
self.rate_limit_lock = rate_limit_lock
self.connection_pool = connection_pool_factory(self.BASE_URL)
def _compose_url(self, path, params=None):
return self.BASE_URL + path + '?' + urlencode(params)
def _handle_response(self, response):
return json.loads(response.data)
def _request(self, method, path, params=None):
url = self._compose_url(path, params)
self.rate_limit_lock and self.rate_limit_lock.acquire()
r = self.connection_pool.urlopen(method.upper(), url)
return self._handle_response(r)
def call(self, path, **params):
return self._request('GET', path, params=params)
class SimpleAPI_SharedSecret(SimpleAPI):
API_KEY_PARAM = 'key'
def __init__(self, api_key, *args, **kw):
super(SimpleAPI_SharedSecret, self).__init__(*args, **kw)
self.api_key = api_key
def _compose_url(self, path, params=None):
p = {self.API_KEY_PARAM: self.api_key}
if params:
p.update(params)
return self.BASE_URL + path + '?' + urlencode(p)
import time
from threading import Lock
class RateExceededError(Exception):
pass
class RateLimiter(object):
def __init__(self, max_messages=10, every_seconds=1):
self.max_messages = max_messages
self.every_seconds = every_seconds
self.lock = Lock()
self._reset_window()
def _reset_window(self):
self.window_num = 0
self.window_time = time.time()
def acquire(self, block=True, timeout=None):
self.lock.acquire()
now = time.time()
if now - self.window_time > self.every_seconds:
# New rate window
self._reset_window()
if self.window_num >= self.max_messages:
# Rate exceeding
if not block:
self.lock.release()
raise RateExceededError()
wait_time = self.window_time + self.every_seconds - now
if timeout and wait_time > timeout:
self.lock.release()
time.sleep(timeout)
raise RateExceededError()
self.lock.release()
time.sleep(wait_time)
self.lock.acquire()
self._reset_window()
self.window_num += 1
self.lock.release()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment