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

Building beautiful REST APIs with Flask, Swagge...

Building beautiful REST APIs with Flask, Swagger UI and Flask-RESTPlus

Outlines steps needed to create a RESTful API using Flask and Flask-RESTPlus. These tools combine into a framework, which automates common tasks:

* API input validation
* formatting output (as JSON)
* generating interactive documentation (with Swagger UI)
* turning Python exceptions into machine-readable HTTP responses

Talk by Michal Karzynski

Michał Karzyński

July 22, 2016
Tweet

More Decks by Michał Karzyński

Other Decks in Programming

Transcript

  1. ABOUT ME • My name is Michał Karzyński (that’s Polish

    for Mike) • I blog at http://michal.karzynski.pl
 Short URL: karzyn.com • I wrote a book for Linux admins, I write code in Python and JavaScript • I’m the tech lead of a Web UI team at
  2. Method Path Query Headers Body Status Code Headers Body Request

    Response GET POST PUT DELETE /api/books ?search=Moby Dick Cookies… JSON 200 OK 404 Not Found
  3. REST CONVENTIONS Method Path Query Headers Body GET PUT POST

    DELETE Collection
 /books List books New book Item
 /books/123 Display book Update book Delete book Controller /books/123/borrow Borrow book
  4. FLASK-RESTPlus • define and document endpoints • validate input •

    format output (as JSON) • turn Python exceptions into HTTP responses • minimise boilerplate code • generate interactive documentation (Swagger UI) flask-restplus.rtfd.io
  5. OPEN API SPECIFICATION • Standard language to describe REST APIs

    • Open source (Linux Foundation) • Tools: • Swagger UI • Swagger Editor • Code generators • Initiative with many powerful members swagger.io openapis.org
  6. OPEN API SPECIFICATION • Standard language to describe REST APIs

    • Open source (Linux Foundation) • Tools: • Swagger UI • Swagger Editor • Code generators • Initiative with many powerful members swagger.io openapis.org
  7. REQUEST METHOD POST /api/books/123/borrow?when=today Method Path Query Headers Body from

    flask_restplus import Resource @api.route('/<int:id>/borrow') class BorrowBookController(Resource):
 
 def post(self, id):
 """ Borrow book from library.
 
 Allows the current user to borrow the book out of the library.
 """
 ... return {'message': 'OK'}
  8. REQUEST METHOD POST /api/books/123/borrow?when=today Method Path Query Headers Body from

    flask_restplus import Resource @api.route('/<int:id>/borrow') class BorrowBookController(Resource):
 
 def post(self, id):
 """ Borrow book from library.
 
 Allows the current user to borrow the book out of the library.
 """
 ... return {'message': 'OK'} class Resource:
 
 def get(self)...
 def post(self)...
 def put(self)...
 def delete(self)...
 def patch(self)...
 def options(self)... def head(self)...

  9. REQUEST PATH POST /api/books/123/borrow?when=today Method Path Query Headers Body @api.route(‘/books/<int:id>/borrow’)

    @api.route('/articles/<title>') @api.route('/wiki/<path:wikipage>') @api.route('/values/<float:value>') @api.route('/object/<uuid:identifier>')
  10. QUERY ARGUMENTS GET /api/books?page=1&per_page=10 Method Path Query Headers Body from

    flask_restplus import reqparse
 
 pagination = reqparse.RequestParser()
 pagination.add_argument('page', type=int, required=False, default=1, help='Page number')
 pagination.add_argument('per_page', type=int, required=False, choices=[10, 20, 30, 40, 50])

  11. QUERY ARGUMENTS GET /api/books?page=1&per_page=10 Method Path Query Headers Body from

    flask import request
 from flask_restplus import Resource
 
 @api.route('/')
 class PostsCollection(Resource):
 
 @api.expect(parsers.pagination)
 def get(self):
 args = pagination_arguments.parse_args(request)
 page = args.get('page', 1)
 per_page = args.get('per_page', 10)
 ...
  12. REQUEST BODY (JSON)
 API MODELS Method Path Query Headers Body

    blog_post = api.model('Blog post', {
 'title': fields.String(description='Article title'),
 'body': fields.String(description='Article content'),
 'pub_date': fields.DateTime,
 'category_id': fields.Integer(min=1),
 }) @api.expect(blog_post)
 def post(self):
 ...
  13. Method Path Query Headers Body category = api.model('Blog category', {


    'id': fields.Integer(description='The unique id of category'),
 'name': fields.String(description='Category name'),
 })
 
 category_with_posts = api.inherit('Blog category with posts', category, {
 'posts': fields.List(fields.Nested(blog_post))
 }) API MODELS
 INHERITANCE AND NESTING
  14. RESPONSE STATUS CODE Status Code Headers Body @api.route('/<int:id>')
 @api.response(404, 'Post

    not found.')
 class PostItem(Resource):
 
 @api.response(204, 'Post successfully deleted.')
 def delete(self, id):
 """
 Deletes blog post.
 """
 delete_post(id)
 return None, 204
  15. RESPONSE BODY (JSON) Status Code Headers Body blog_post = api.model('Blog

    post', { ...
 'category_id': fields.Integer(attribute='category.id'), 'name': fields.String(attribute=lambda x: x._private_name),
 }) @api.marshal_with(blog_post)
 def get(self):
 ... @api.marshal_list_with(blog_post)
 def get(self):
 ...
  16. EXCEPTION HANDLING from sqlalchemy.orm.exc import NoResultFound
 
 @api.errorhandler(NoResultFound)
 def database_not_found_error_handler(e):


    log.warning(traceback.format_exc())
 return {'message': 'A database result was not found.'}, 404
  17. from flask import Flask
 from flask_restplus import Resource, Api
 


    app = Flask(__name__)
 api = Api(app)
 
 @api.route('/hello')
 class HelloWorld(Resource):
 def get(self):
 return {'hello': 'world'}
 
 if __name__ == '__main__':
 app.run(debug=True)