restful web services with python - confoo
TRANSCRIPT
Juozas “Joe” Kaziukėnas
http://juokaz.com / [email protected] / @juokaz
• 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
• 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
• 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
• XML-RPC
• Single url as a front controller
• Calling methods on a remote app
• Most of the actions are via POST
• SOAP
• No comments…
• 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
$ curl -H "Accept: application/html" localhost/products/iphone <html><body>Iphone 5</body></html> $ curl -H "Accept: application/xml" localhost/products/iphone <product><name>Iphone 5</name></product> $ curl -H "Accept: application/json“ localhost/products/iphone {‘name’:‘Iphone 5’} $ curl -H "Accept: text/plain" localhost/products/iphone Iphone 5
• 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
• 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
• 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…
Hypermedia as the Engine of Application State
• “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
• 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
<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>
• 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’}
• What are the URIs?
• What's the format?
• What methods are supported at each URI?
• What status codes could be returned?
By Joe Gregorio
• 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
• 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
xml_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'^polls/(.*?)/?$', xml_poll_resource),
url(r'^choices/(.*?)/?$', xml_choice_resource)
)
import 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))
run()
class 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()
• 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"
render_xml = lambda message: '<message>%s</message>'%message render_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()
• Rendering XML takes a lot of code
• doc = xml.dom.minidom.Document() # Something happens return 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 consumption
• XML is better than JSON for anything else
• 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
• Cache headers
• Authentication
• Different types
• No real REST framework
• Start at http://api.festivalslab.com
• 7 summer festivals
• Built in 100% Python
• Fast
• Very stable
• Some bad decisions
• Works really well
• More info in the blog
• 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
• REST is awesome
• Support different formats
• Follow HATEOAS
• Try to create as little as possible custom behavior
• Go with light Python code
Keep in touch: http://juokaz.com / [email protected] / @juokaz
We can help you build them, talk to us!