Created
August 8, 2011 21:08
-
-
Save shazow/1132742 to your computer and use it in GitHub Desktop.
Make good HTTP API libraries really easily using urllib3.
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
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') | |
""" |
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
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) | |
# ... | |
""" |
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
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