Skip to content

Instantly share code, notes, and snippets.

@jamescasbon
Created June 20, 2012 21:27
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jamescasbon/2962341 to your computer and use it in GitHub Desktop.
Save jamescasbon/2962341 to your computer and use it in GitHub Desktop.
Tornado github API client
import tornado.ioloop
import tornado.web
import tornado.escape
import tornado.options
import tornado.httputil
import jinja2
import pyjade.compiler
import coffeescript
import markdown
import github
class GithubLoginHandler(tornado.web.RequestHandler, github.GithubMixin):
_OAUTH_REDIRECT_URL = 'http://localhost:8888/auth/github'
@tornado.web.asynchronous
def get(self):
# we can append next to the redirect uri, so the user gets the
# correct URL on login
redirect_uri = tornado.httputil.url_concat(
self._OAUTH_REDIRECT_URL, {'next': self.get_argument('next', '/')})
# if we have a code, we have been authorized so we can log in
if self.get_argument("code", False):
self.get_authenticated_user(
redirect_uri=redirect_uri,
client_id=self.settings["github_client_id"],
client_secret=self.settings["github_secret"],
code=self.get_argument("code"),
callback=self.async_callback(self._on_login)
)
return
# otherwise we need to request an authorization code
self.authorize_redirect(
redirect_uri=redirect_uri,
client_id=self.settings["github_client_id"],
extra_params={"scope": self.settings['github_scope'], "foo":1})
def _on_login(self, user):
""" This handles the user object from the login request """
if user:
logging.info('logged in user from github: ' + str(user))
self.set_secure_cookie("user", tornado.escape.json_encode(user))
else:
self.clear_cookie("user")
self.redirect(self.get_argument("next","/"))
class GistLister(BaseHandler, github.GithubMixin):
@tornado.web.authenticated
@tornado.web.asynchronous
def get(self):
self.github_request(
'/gists', self._on_get_gists,
access_token=self.current_user['access_token'])
def _on_get_gists(self, gists):
self.render('gists.jade', gists=gists)
import urllib
import tornado.ioloop
import tornado.web
import tornado.auth
import tornado.httpclient
import tornado.escape
import tornado.httputil
import logging
class GithubMixin(tornado.auth.OAuth2Mixin):
""" Github OAuth Mixin, based on FacebookGraphMixin
"""
_OAUTH_AUTHORIZE_URL = 'https://github.com/login/oauth/authorize'
_OAUTH_ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token'
_API_URL = 'https://api.github.com'
def get_authenticated_user(self, redirect_uri, client_id, client_secret,
code, callback, extra_fields=None):
""" Handles the login for Github, queries /user and returns a user object
"""
logging.debug('gau ' + redirect_uri)
http = tornado.httpclient.AsyncHTTPClient()
args = {
"redirect_uri": redirect_uri,
"code": code,
"client_id": client_id,
"client_secret": client_secret,
}
http.fetch(self._oauth_request_token_url(**args),
self.async_callback(self._on_access_token, redirect_uri, client_id,
client_secret, callback, fields))
def _on_access_token(self, redirect_uri, client_id, client_secret,
callback, fields, response):
""" callback for authentication url, if successful get the user details """
if response.error:
logging.warning('Github auth error: %s' % str(response))
callback(None)
return
args = tornado.escape.parse_qs_bytes(
tornado.escape.native_str(response.body))
if 'error' in args:
logging.error('oauth error ' + args['error'][-1])
raise Exception(args['error'][-1])
session = {
"access_token": args["access_token"][-1],
}
self.github_request(
method="/user",
callback=self.async_callback(
self._on_get_user_info, callback, session),
access_token=session["access_token"],
)
def _on_get_user_info(self, callback, session, user):
""" callback for github request /user to create a user """
logging.debug('user data from github ' + str(user))
if user is None:
callback(None)
return
callback({
"login": user["login"],
"name": user["name"],
"email": user["email"],
"access_token": session["access_token"],
})
def github_request(self, path, callback, access_token=None,
method='GET', body=None, **args):
""" Makes a github API request, hands callback the parsed data """
args["access_token"] = access_token
url = tornado.httputil.url_concat(self._API_URL + path, args)
logging.debug('request to ' + url)
http = tornado.httpclient.AsyncHTTPClient()
if body is not None:
body = tornado.escape.json_encode(body)
logging.debug('body is' + body)
http.fetch(url, callback=self.async_callback(
self._parse_response, callback), method=method, body=body)
def _parse_response(self, callback, response):
""" Parse the JSON from the API """
if response.error:
logging.warning("HTTP error from Github: %s", response.error)
callback(None)
return
try:
json = tornado.escape.json_decode(response.body)
except Exception:
logging.warning("Invalid JSON from Github: %r", response.body)
callback(None)
return
if isinstance(json, dict) and json.get("error_code"):
logging.warning("Facebook error: %d: %r", json["error_code"],
json.get("error_msg"))
callback(None)
return
callback(json)
@bcambel
Copy link

bcambel commented Jul 12, 2012

fields parameter on line 34 is not defined. Did you mean "extra_fields" ?

@shinriyo
Copy link

shinriyo commented Feb 4, 2013

Thank you the code

@paulocheque
Copy link

nice code.
method="/user", => path="/user"
ACtually the fields must be something like this:

fields = set(['id', 'name', 'first_name', 'last_name',
              'locale', 'picture', 'link'])
if extra_fields:
    fields.update(extra_fields)

@guneysus
Copy link

I implemented a brand new GithubOAuth2Mixin and GithubLoginHandler since I can not make run the some of the features (like self.async_callback)

https://gist.github.com/guneysus/4947e73248cf521d0a01

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment