RESTful Web Services with Python - Dynamic Languages conference

Post on 10-May-2015

19,819 views 2 download

transcript

RESTful Web Services with

Python

Juozas “Joe” Kaziukėnashttp://juokaz.com / juozas@juokaz.com / @juokaz

Who is this guy?• Juozas Kaziukėnas,

Lithuanian• You can call me Joe• 3 years in Edinburgh, UK• CEO of Web Species• Occasional open source

developer• Conferences speaker• More info in http://

juokaz.com

• Tweet me @juokaz

What is a RESTful API?

What is an API?• Web service is a type of web application• Integrate different apps together• Mashups

• Content is in multiple formats• JSON• XML• HTML?

• Used by other apps mainly• Not necessary

REST• REpresentational State Transfer• 99% HTTP, 1% conventions/ideas• Stateless• Resources• Name -> URI• http://apple.com/devices/iphone• Hierarchy

• Operations• Not in the URL, but via HTTP terms

• Trivial to cache• Cache-control, Last-Modified, Expires, Etag

Difference from other web services• XML-RPC• Single url as a front controller • Calling methods on a remote app• Most of the actions are via POST

• SOAP• No comments…

Difference from web apps• PUT and DELETE HTTP verbs• POST and GET only supported by browsers today• Create: POST• Update: PUT• Delete: DELETE• View: GET

• Status codes• Not just 404 and 500• Meaning of the response without analyzing the returned

data

• And many other headers• Web page is not a resource, it’s a representation

of a resource

Accept header means the format

$ curl -H "Accept: application/html" localhost/products/iphone<html><body>Iphone 5</body></html>

$ curl -H "Accept: application/xml" localhost/products/iphone<name>Iphone 5</name>

$ curl -H "Accept: application/json“ localhost/products/iphone{‘name’:‘Iphone 5’}

$ curl -H "Accept: text/plain" localhost/products/iphoneIphone 5

Doing it wrong

URLs• Before we had:

http://www.example.com/videos.py?by=joe&type=funny• Now we have:

http://www.example.com/videos/funny/joe/offset/0/10• This is wrong

REST + XML-RPC• What is new?

http://www.example.com/clients/new• Unclear hierarchy

http://www.example.com/clients/photos/32723• Filtering

http://www.example.com/clients/name/john/offset/10/sort/name

Wrong• Everything is REST now• But it’s not

• Twitter, Facebook, Google all inventing their own @$^&$s

• “Users are stupid”, ditching standards• How to figure out the URIs?• To fix this you need…

HATEOASHypermedia as the Engine of Application State

The steps to being REST• “The Swamp of POX.” You’re using HTTP to make

RPC calls. HTTP is only really used as a tunnel.• Resources. Rather than making every call to a

service endpoint, you have multiple endpoints that are used to represent resources, and you’re talking to them. This is the very beginnings of supporting REST.

• HTTP Verbs. This is the level that something like Rails gives you out of the box: You interact with these Resources using HTTP verbs, rather than always using POST.

• Hypermedia Controls. HATEOAS. You’re 100% REST compliant.

From Richardson Maturity Model

HATEOAS• A requirement for REST• One url, everything else is discoverable• What to• Do next?• Do with the resource?

• Reduces code errors• Invalid URLS• Invalid state transfers

• Documentation is not needed• Evolution

XML example

<appointment> <slot id="1234" doctor="mjones" start="1400" end="1450“/> <patient id="jsmith“/> <link rel="cancel" uri="/slots/1234/appointment"/> <link rel="addTest" uri="/slots/1234/appointment/tests"/> <link rel="updateContactInfo" uri="/patients/jsmith/contactInfo"/></appointment>

Accept header means the format• Media type• Versioning

$ curl -H "Accept: application/vnd.demo.v1+json“ localhost/products/iphone

{‘name’:‘Iphone 5’}

$ curl -H "Accept: application/vnd.demo.v2+json" localhost/products/iphone

{‘product_name’:‘Iphone 5’}

Solving the problems with Python

How to create a REST protocol• What are the URIs?• What's the format?• What methods are supported at each URI?• What status codes could be returned?

By Joe Gregorio

Why Python?• Fast, relatively • Robust to develop• New code live in seconds

• Huge selection of web frameworks• Interfaces with databases, servers etc.• APIs do not need much else

Python• As long as it’s WSGI it’s OK• Simple code• Different feature sets• Talking here about• Django• Bottle, similar to Flask• Web.py• Tornado (asynchronous)

• Render content in the request format

Sample API with Djangoxml_poll_resource = Collection( queryset = Poll.objects.all(), permitted_methods = ('GET', 'POST', 'PUT', 'DELETE'), expose_fields = ('id', 'question', 'pub_date'), responder = XMLResponder(paginate_by = 10) ) xml_choice_resource = Collection( queryset = Choice.objects.all(), permitted_methods = ('GET',), expose_fields = ('id', 'poll_id', 'choice'), responder = XMLResponder(paginate_by = 5) )

urlpatterns = patterns('', url(r'^xml/polls/(.*?)/?$', xml_poll_resource), url(r'^xml/choices/(.*?)/?$', xml_choice_resource) )

Sample API with Bottleimport bottle from bottle import route, run

@route('/', method='GET') def homepage():

return 'Hello world!'

@route('/events/:id', method='GET') def get_event(id):

return dict(name = 'Event ' + str(id))

bottle.debug(True) run()

Sample API with Tornadoclass PlaceHandler(tornado.web.RequestHandler):    def get(self, id):        self.write('GETting something')     def post(self):        self.write('POSTing something')

application = tornado.web.Application([    (r"/place", PlaceHandler),    (r"/place/([0-9]+)", PlaceHandler)]) if __name__ == "__main__":    http_server = tornado.httpserver.HTTPServer(application)    tornado.ioloop.IOLoop.instance().start()

MIME types• Detect which format to use from Accept header• Content negotiation

• Mimeparse - http://code.google.com/p/mimeparse/• Use it, works great

> mimeparse:best_match(["application/xbel+xml", "text/xml"], "text/*;q=0.5,*/*; q=0.1").> "text/xml"

Mimerenderrender_xml = lambda message: '<message>%s</message>'%messagerender_json = lambda **args: json.dumps(args)render_html = lambda message: '<html><body>%s</body></html>'%message

urls = ('/(.*)', 'greet')app = web.application(urls, globals())

class greet:    @mimerender(default = 'html', html = render_html, xml = render_xml, json = render_json)    def GET(self, name):        if not name:             name = 'world'        return {'message': 'Hello, ' + name + '!'}

if __name__ == "__main__":    app.run()

Return formats• Rendering XML takes a lot of code• doc = xml.dom.minidom.Document()# Something happensreturn doc.toxml()

• JSON is as easy as• json_dumps(data, sort_keys=True)• Maybe allow human readable output• json_dumps(data, sort_keys=True, indent=4)

• JSON is great for Ajax, if not XML brings huge advantages

Which framework to choose• Django for existing apps• Different sub-frameworks for REST

• Bottle or similar• Small• Effective• Build your own logic

• Asynchronous Tornado • If API needs to be asynchronous

What’s missing• Cache headers• Authentication• Different types

• No real REST framework

Applying it practically for Edinburgh Festivals

Applying it practically for Edinburgh Festivals• Start at http://api.festivalslab.com• 7 summer festivals• Built in 100% Python• Fast• Very stable• Some bad decisions• Works awesome• More info in the blog

Structure

Inside• Whole API – 100 LoC of Python code• Mainly interacting with the ElasticSearch server• Scheduled data imports – main task• Nginx as a reverse proxy• Supervisor to manage the processes

Conclusion

Conclusion• REST is awesome• Support different formats• Follow HATEOAS• Try to create as little as possible custom

behaviour• Go with light Python code

Keep in touch: http://juokaz.com / juozas@juokaz.com / @juokaz

Thank you!

We can help you build them, talk to us!