REST APIs for Cruel
World
(using Python)
April 12, 2004
What is REST?
• Client-Server
• Stateless
• Cacheable
• Layered System
• Uniform Interface
• Code on demand (optional)
Why REST
• REST is awesome
• SOAP is ugly
• all others are even uglier
REST ==* JSON
* at least let’s wish this
REST verbs
HTTP Method Action Examples
GET
Obtain information
about a resource
http://example.com/api/orders/
(retrieve list of orders)
GET
Obtain information
about a resource
http://example.com/api/orders/123
(retrieve order #123)
POST
Create a new
resource
http://example.com/api/orders
(create a new order, from data
provided with the request)
PUT Update a resource
http://example.com/api/orders/123
(update order #123, from data
provided with the request)
DELETE Delete a resource http://example.com/api/orders/123
(delete order #123)
API versions
• Always use version
• Versions in URLs:
• /api/v1.0
• /api/20140412/
• Versions in headers
Authentication
• End-client to API server (oAuth)
• Server-to-Server
Server-to-Server
• API key identification
• Signing with API secret
• Timestamp (servers should be ntp synced)
• Whitelists (optional)
import hashlib!
import hmac!
!
def get_signature(data, secret_key):!
if isinstance(data, dict):!
data = u'&'.join(!
(u'='.join((key, value))!
for (key, value) in sorted(data.iteritems())))!
hash_key = hmac.new(!
! ! secret_key, !
! ! data.encode('utf-8'), !
! ! hashlib.sha256)!
return hash_key.hexdigest()
timestamp = get_utc_timestamp()!
!
data = 'api_key=test&key=test&timestamp=%s' % timestamp!
!
signature = get_signature(data, TEST_SECRET_KEY)!
!
resp = (!
! self.client.get(“/test?%s&signature=%s" % (!
! ! data, signature))!
)
REST and Django
• Django-tastypie
• Django-rest-framework
Django-tastypie
• Django model is resource
• All actions are hardly linked with
models
• http://tastypieapi.org/
Django-rest-framework
• The Web browseable API
• Authentication policies including OAuth1a and
OAuth2 out of the box.
• Serialization that supports both ORM and non-ORM
data sources.
• Customizable all the way down
• http://www.django-rest-framework.org/
from rest_framework.views import APIView!
from rest_framework.response import Response!
from rest_framework import (!
! authentication, permissions!
)!
!
class ListUsers(APIView):!
!
authentication_classes = (!
! ! authentication.TokenAuthentication,)!
permission_classes = (permissions.IsAdminUser,)!
!
def get(self, request, format=None):!
usernames = [!
! ! ! user.username for user in User.objects.all()]!
return Response(usernames)
django-rest-swagger
!
!
!
!
• https://github.com/marcgibbons/django-rest-
swagger
Flask-RESTful
• http://flask-restful.readthedocs.org/en/latest/
from flask import Flask!
from flask.ext.restful import Api, Resource!
!
app = Flask(__name__)!
api = Api(app)!
!
class UserAPI(Resource):!
def get(self, id):!
pass!
!
def put(self, id):!
pass!
!
def delete(self, id):!
pass!
!
api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
API testing
class ApiAuthTestCase(BaseApiTestCase):!
def test_get_without_params(self):!
resp = self.client.get('/test')!
self.assertEquals(resp.status_code, 400)!
!
def test_post_without_params(self):!
resp = self.client.post('/test')!
self.assertEquals(resp.status_code, 400)!
!
def test_get_bad_signature(self):!
timestamp = get_utc_timestamp()!
resp = self.client.get(!
'/test?key=test&api_key=test&signature=bad&timestamp=%s' %!
timestamp)!
self.assertEquals(resp.status_code, 403)!
Volodymyr Hotsyk
https://github.com/hotsyk/
@hotsyk
Questions?

Reliable Python REST API (by Volodymyr Hotsyk) - Web Back-End Tech Hangout - 2014.04.12

  • 1.
    REST APIs forCruel World (using Python) April 12, 2004
  • 3.
    What is REST? •Client-Server • Stateless • Cacheable • Layered System • Uniform Interface • Code on demand (optional)
  • 4.
    Why REST • RESTis awesome • SOAP is ugly • all others are even uglier
  • 5.
    REST ==* JSON *at least let’s wish this
  • 6.
    REST verbs HTTP MethodAction Examples GET Obtain information about a resource http://example.com/api/orders/ (retrieve list of orders) GET Obtain information about a resource http://example.com/api/orders/123 (retrieve order #123) POST Create a new resource http://example.com/api/orders (create a new order, from data provided with the request) PUT Update a resource http://example.com/api/orders/123 (update order #123, from data provided with the request) DELETE Delete a resource http://example.com/api/orders/123 (delete order #123)
  • 7.
    API versions • Alwaysuse version • Versions in URLs: • /api/v1.0 • /api/20140412/ • Versions in headers
  • 8.
    Authentication • End-client toAPI server (oAuth) • Server-to-Server
  • 9.
    Server-to-Server • API keyidentification • Signing with API secret • Timestamp (servers should be ntp synced) • Whitelists (optional)
  • 10.
    import hashlib! import hmac! ! defget_signature(data, secret_key):! if isinstance(data, dict):! data = u'&'.join(! (u'='.join((key, value))! for (key, value) in sorted(data.iteritems())))! hash_key = hmac.new(! ! ! secret_key, ! ! ! data.encode('utf-8'), ! ! ! hashlib.sha256)! return hash_key.hexdigest()
  • 11.
    timestamp = get_utc_timestamp()! ! data= 'api_key=test&key=test&timestamp=%s' % timestamp! ! signature = get_signature(data, TEST_SECRET_KEY)! ! resp = (! ! self.client.get(“/test?%s&signature=%s" % (! ! ! data, signature))! )
  • 12.
    REST and Django •Django-tastypie • Django-rest-framework
  • 13.
    Django-tastypie • Django modelis resource • All actions are hardly linked with models • http://tastypieapi.org/
  • 14.
    Django-rest-framework • The Webbrowseable API • Authentication policies including OAuth1a and OAuth2 out of the box. • Serialization that supports both ORM and non-ORM data sources. • Customizable all the way down • http://www.django-rest-framework.org/
  • 15.
    from rest_framework.views importAPIView! from rest_framework.response import Response! from rest_framework import (! ! authentication, permissions! )! ! class ListUsers(APIView):! ! authentication_classes = (! ! ! authentication.TokenAuthentication,)! permission_classes = (permissions.IsAdminUser,)! ! def get(self, request, format=None):! usernames = [! ! ! ! user.username for user in User.objects.all()]! return Response(usernames)
  • 16.
  • 17.
  • 18.
    from flask importFlask! from flask.ext.restful import Api, Resource! ! app = Flask(__name__)! api = Api(app)! ! class UserAPI(Resource):! def get(self, id):! pass! ! def put(self, id):! pass! ! def delete(self, id):! pass! ! api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
  • 19.
    API testing class ApiAuthTestCase(BaseApiTestCase):! deftest_get_without_params(self):! resp = self.client.get('/test')! self.assertEquals(resp.status_code, 400)! ! def test_post_without_params(self):! resp = self.client.post('/test')! self.assertEquals(resp.status_code, 400)! ! def test_get_bad_signature(self):! timestamp = get_utc_timestamp()! resp = self.client.get(! '/test?key=test&api_key=test&signature=bad&timestamp=%s' %! timestamp)! self.assertEquals(resp.status_code, 403)!
  • 20.