Skip to content

Instantly share code, notes, and snippets.

@shazow
Created March 9, 2013 21:40
Show Gist options
  • Save shazow/5125867 to your computer and use it in GitHub Desktop.
Save shazow/5125867 to your computer and use it in GitHub Desktop.
My API controller for my Pyramid meta-framework thing.
import json
from pyramid.httpexceptions import HTTPSeeOther, HTTPBadRequest
from pyramid.response import Response
from jobcupid.lib.exceptions import APIControllerError, LoginRequired
from jobcupid.model.meta import SchemaEncoder
API_METHOD_MAP = {}
def expose_api(name, check_csrf=True, check_referer=True):
""" Decorator helper for registering an API method. """
def decorator(fn):
API_METHOD_MAP[name] = fn
fn.exposed_name = name
fn.check_csrf = check_csrf
fn.check_referer = check_referer
return fn
return decorator
def maybe_flash(request, msg):
""" Save a flash message to the session if it makes sense. """
if request.params.get('format') != 'redirect':
return
request.session.flash(msg)
def api_controller(request, method_whitelist=None):
""" Performs the internal exposed API routing and error handling.
:param request:
Request object.
:param method_whitelist:
If provided, limits the methods which we're allowed to process in this
call.
"""
try:
method = request.params['method']
except KeyError, e:
raise APIControllerError("Missing required parameter: %s" % e.args[0])
if method_whitelist and method not in method_whitelist:
raise APIControllerError("Method not permitted: %s" % method)
fn = API_METHOD_MAP.get(method)
if not fn:
raise APIControllerError("Method does not exist: %s" % method)
if fn.check_referer and request.referer:
expected_referer = request.application_url.split('://', 1)[1]
request_referer = request.referer.split('://', 1)[1]
if not request_referer.startswith(expected_referer):
raise APIControllerError("Bad referer: %s" % request.referer)
if fn.check_csrf and request.params.get('csrf_token') != request.session.get_csrf_token():
raise APIControllerError("Invalid csrf_token value: %s" % request.params.get('csrf_token'))
try:
return fn(request)
except KeyError, e:
raise APIControllerError("Missing required parameter: %s" % e.args[0])
def _report_error(data, e):
data['messages'] += [e.message]
data['code'] = e.code
data['status'] = 'error'
return data
def index(request):
""" The only app-routed view which delegates the rest of the API-related
functionality. Collates the API result into the final payload and response
object.
"""
data = {
'status': 'ok',
'code': 200,
'messages': [],
'result': {},
}
format = request.params.get('format', 'json')
if format not in ('json', 'redirect', 'fragment'):
return HTTPBadRequest('Invalid format requested: %s' % format)
encode_settings = {'cls': SchemaEncoder}
if request.params.get('pretty'):
encode_settings['sort_keys'] = True
encode_settings['indent'] = 4
next = request.params.get('next') or request.referer or '/'
try:
r = api_controller(request)
if r:
# FIXME: r or {}?
data['result'] = r
# FIXME: This isn't the cleanest...
except APIControllerError, e:
if format == 'redirect':
request.session.flash(e.message)
return HTTPSeeOther(next)
_report_error(data, e)
except LoginRequired, e:
if format == 'redirect':
return HTTPSeeOther('/account/login', next=e.next)
_report_error(data, e)
if format == 'redirect':
return HTTPSeeOther(next)
elif format == 'fragment':
return data['result']
body = json.dumps(data, **encode_settings)
return Response(body, content_type='application/json', status=data['code'])
# Exposed APIs:
# (These can be anywhere, not just this module)
@expose_api('ping')
def ping(request):
return {'ping': 'pong'}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment