Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ari Lacenski

Ari Lacenski

Background on REST and non-REST API development strategies, followed by a working Tastypie example.

PyCon Canada

August 10, 2013
Tweet

More Decks by PyCon Canada

Other Decks in Technology

Transcript

  1. APIs in 10 seconds • API stands for Application Programming

    Interface • Building an API lets people write applications that use your technology • Web APIs are collections of URLs pointing to service or data locations
  2. • Requesting data? GET it and trust that the URL

    to it won’t change REST in 10 seconds • Uses HTTP concepts with standardized Create Read Update Delete headers. POST, GET, PUT, DELETE • POSTing data? A new record will be created, keeping existing records safe • DELETE only what you mean to DELETE
  3. Why the unREST? • Often, you want to represent the

    state of data that you are accessing... but sometimes you don’t • Queries whose result is not a data record do not obey REST. And that’s okay!
  4. Why not make up your own URL structure? • Remote

    Procedure Call APIs don’t enforce a relationship between the request and the data • No correlation between URL and DB record • Misusing GET can create massive security holes GET /api/photo/uploads/1 ? POST /api/photo/uploader/ ?
  5. But what about queryable data? • When searching or calculating

    with data, you probably don’t want to store every result. • POST requests are good at sending complex parameters • Millions of POSTs, no new DB data? Not very REST-friendly. json_data = { data: [‘that’, ‘does’, ‘not’, ‘fit’, ‘keywords’] }
  6. It’s okay to mix REST and RPC patterns. If you’re

    really clear about where and why.
  7. Extend your pattern. Consider this photo manager service. Upload a

    photo? POST to /photo/ with file + metadata Get photo details? GET /photo/ Want to let your API users calculate the average color in images? ... Hmm.
  8. viewer/models.py from django.db import models import uuid class Photo(models.Model): title

    = models.CharField(max_length=255) created_at = models.DateTimeField(auto_now_add=True, editable=False) image = models.ImageField(upload_to='static/photos/' + uuid.uuid4().__str__() +'/') average_color = models.CharField(max_length=6) # average_color e.g. ‘FF2437’ How do you turn this into an API? I used Tastypie.
  9. viewer/api.py from tastypie.resources import ModelResource from models import Photo class

    PhotoResource(ModelResource): class Meta: queryset = Photo.objects.all() resource_name = 'photo' allowed_methods = ['get', 'post']
  10. urls.py from django.conf.urls import patterns, include, url from viewer.api import

    PhotoResource photo_resource = PhotoResource() urlpatterns = patterns('', # Examples: url(r'^browse', 'viewer.views.browse'), url(r'^upload', 'viewer.views.upload'), url(r'^api/', include(photo_resource.urls)) # ... other routes in your app can go # into this definition, if you like )
  11. Ta-da, a REST API Can we get back one photo’s

    data? Can we upload a photo by POSTing it? Can we see data about all photos we’ve uploaded? Yup. In principle, yes. Sure can.
  12. GET /api/photo/ { "meta": {"limit": 20, "next": null, "offset": 0,

    "previous": null, "total_count": 3}, "objects": [{"average_color": "234222", "created_at": "2013-08-04T17:59:45.398263", "id": 60, "image": "photos/ e1eb48c0-4835-449b-94a4-b220e5afcc7b/photo1.png", "resource_uri": "/api/photo/60/", "title": "Terrestrial"}, {"average_color": "F92342", "created_at": "2013-08-09T14:18:15.771842", "id": 34, "image": "static/ photos/e9563101-0fb2-4b2a-ab32-73ad887340b8/onions.png", "resource_uri": "/api/photo/34/", "title": "Galaxy"}, {"average_color": "000000", "created_at": "2013-08-09T14:18:30.931446", "id": 29, "image": "static/ photos/e9563101-0fb2-4b2a-ab32-73ad887340b8/stamen.png", "resource_uri": "/api/photo/29/", "title": "Stamen"}] }
  13. Calculation time Remember the color-averaging service I wanted? photos by

    me from http://www.flickr.com/photos/tensory/ id=34 id=60 id=29
  14. Use prepend_urls() • The prepend_urls method of a Tastypie Resource

    lets you create custom URLs. • Redefine prepend_urls on the Resource. • Point the custom URL to a helper that calls a utility method elsewhere. • The helper should return self.create_response(request, obj)
  15. viewer/api.py import ... # Tastypie, Django, and your resource model

    class PhotoResource(ModelResource): # class Meta defined as before def find_average_color(self, request, **kwargs): self.method_check(request, allowed=['post']) response = {} if request.body: params = json.loads(request.body) average_colors = Photo.objects.filter(pk__in=params['photo_ids']).values_list('average_color', flat=True) # Some terribly clever calculation to find the average color. response['average_color'] = average_colors[0] self.create_response(request, response) def prepend_urls(self): return [ url(r"^(?P<resource_name>%s)/find_average_color%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('find_average_color'), name="api_find_average_color"), ]
  16. Now POST to it curl -i -H "Content-Type: application/json" -X

    POST -d '{"photo_ids": [29, 34, 60]}' http://localhost:8000/api/photo/ find_average_color/ (Yes, we could have used GET)
  17. Now, you try it Comparison of REST vs XML RPC

    http://bit.ly/8lkoPK Django API packages comparison https://www.djangopackages.com/grids/g/api/ Tastypie http://django-tastypie.readthedocs.org/ https://github.com/toastdriven/django-tastypie Cloneable sample code https://github.com/tensory/Snapjar