class: split-30 nopadding background-image: url(https://cloud.githubusercontent.com/assets/4231611/11257839/21b8b24e-8e87-11e5-9099-25f77383f620.jpg) .column_t2.center[.vmiddle[ .fgtransparent[ #
] ]] .column_t2[.vmiddle.nopadding[ .shadelight[.boxtitle1[ # Flask-RESTful # Flask-HTTPAuth ### [Eueung Mulyana](https://github.com/eueung) ### http://eueung.github.io/python/flask-restful #### Python CodeLabs | [Attribution-ShareAlike CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/) #### ]] ]] --- class: split-30 nopadding background-image: url(https://cloud.githubusercontent.com/assets/4231611/11257839/21b8b24e-8e87-11e5-9099-25f77383f620.jpg) .column_t2.center[.vmiddle[ .fgtransparent[ #
] ]] .column_t2[.vmiddle.nopadding[ .shadelight[.boxtitle1[ # Flask-RESTful #### ]] ]] --- class: split-50 nopadding .column_t1[.vmiddle[ #Example #1 .figstyle1[ ![](images/restful-ex01.jpg) ] ]] .column_t2[.vmiddle[ ``` from flask import Flask *from flask.ext import restful app = Flask(__name__) *api = restful.Api(app) #---------------------------------- *class HelloWorld(restful.Resource): * def get(self): return {'hello': 'world'} #---------------------------------- *api.add_resource(HelloWorld, '/') #---------------------------------- if __name__ == '__main__': app.run(debug=True) ``` ]] --- class: split-50 nopadding .column_t2[.vmiddle[ ``` from flask import Flask, request *from flask.ext.restful import Resource, Api app = Flask(__name__) api = Api(app) todos = {} #---------------------------------- *class TodoSimple(Resource): def get(self, todo_id): return {todo_id: todos[todo_id]} def put(self, todo_id): todos[todo_id] = request.form['data'] return {todo_id: todos[todo_id]} #---------------------------------- *api.add_resource(TodoSimple, '/
') #---------------------------------- if __name__ == '__main__': app.run(debug=True) ``` ``` 127.0.0.1 - - [23/Nov/2015 04:41:41] "PUT /todo1 HTTP/1.1" 200 - 127.0.0.1 - - [23/Nov/2015 04:47:57] "GET /todo1 HTTP/1.1" 200 - ``` ]] .column_t1[.vmiddle[ #Example #2 ```bash *$ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT {"todo1": "Remember the milk"} *$ curl http://localhost:5000/todo1 {"todo1": "Remember the milk"} ``` .figstyle1[ ![](images/restful-ex02.jpg) ] ]] --- class: split-50 nopadding .column_t1[.vmiddle[ ]] .column_t2[.vmiddle[ ##Notes ###Using .blue[requests] ``` from requests import put, get *put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json() # {u'todo1': u'Remember the milk'} *get('http://localhost:5000/todo1').json() # {u'todo1': u'Remember the milk'} ``` ]] --- class: split-50 nopadding .column_t2[.vmiddle[ ``` from flask import Flask *from flask.ext.restful import reqparse, abort, Api, Resource app = Flask(__name__) api = Api(app) TODOS = { 'todo1': {'task': 'build an API'}, 'todo2': {'task': '?????'}, 'todo3': {'task': 'profit!'}, } #---------------------------------- *def abort_if_todo_doesnt_exist(todo_id): if todo_id not in TODOS: abort(404, message="Todo {} doesn't exist".format(todo_id)) parser = reqparse.RequestParser() parser.add_argument('task', type=str) #---------------------------------- *class TodoList(Resource): def get(self): return TODOS def post(self): args = parser.parse_args() todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1 todo_id = 'todo%i' % todo_id TODOS[todo_id] = {'task': args['task']} return TODOS[todo_id], 201 ``` ]] .column_t1[.vmiddle[ ##Example #3 ``` *# single item; get ,update and delete class Todo(Resource): def get(self, todo_id): abort_if_todo_doesnt_exist(todo_id) return TODOS[todo_id] def delete(self, todo_id): abort_if_todo_doesnt_exist(todo_id) del TODOS[todo_id] return '', 204 def put(self, todo_id): args = parser.parse_args() task = {'task': args['task']} TODOS[todo_id] = task return task, 201 #---------------------------------- *api.add_resource(TodoList, '/todos') *api.add_resource(Todo, '/todos/
') #---------------------------------- if __name__ == '__main__': app.run(debug=True) ``` ]] --- class: split-50 nopadding .column_t1[.vmiddle[ .figstyle1[ ![](images/restful-ex03.jpg) ] .figstyle1[ ![](images/restful-ex03a.jpg) ] ]] .column_t2[.vmiddle[ ``` 127.0.0.1 - - [23/Nov/2015 05:15:54] "GET /todos HTTP/1.1" 200 - 127.0.0.1 - - [23/Nov/2015 05:17:14] "GET /todos/todo3 HTTP/1.1" 200 - 127.0.0.1 - - [23/Nov/2015 05:17:36] "DELETE /todos/todo2 HTTP/1.1" 204 - *127.0.0.1 - - [23/Nov/2015 05:17:59] "GET /todos/todo2 HTTP/1.1" 404 - 127.0.0.1 - - [23/Nov/2015 07:38:07] "POST /todos HTTP/1.1" 201 - 127.0.0.1 - - [23/Nov/2015 07:38:56] "GET /todos/todo4 HTTP/1.1" 200 - *127.0.0.1 - - [23/Nov/2015 07:39:41] "PUT /todos/todo4 HTTP/1.1" 201 - ``` ```bash $ curl http://localhost:5000/todos $ curl http://localhost:5000/todos/todo3 *# verbose $ curl http://localhost:5000/todos/todo2 -X DELETE -v $ curl http://localhost:5000/todos -d "task=something new" -X POST -v $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v ``` ]] --- class: split-50 nopadding .column_t2[.vmiddle[ ``` *from simplexml import dumps from flask import make_response, Flask from flask_restful import Api, Resource #------------------------------------------ *def output_xml(data, code, headers=None): resp = make_response(dumps({'response' :data}), code) resp.headers.extend(headers or {}) return resp #------------------------------------------ app = Flask(__name__) api = Api(app, default_mediatype='application/xml') *api.representations['application/xml'] = output_xml #------------------------------------------ class Hello(Resource): def get(self, entry): return {'hello': entry} #------------------------------------------ api.add_resource(Hello, '/
') #------------------------------------------ if __name__ == '__main__': app.run(debug=True) ``` ]] .column_t1[.vmiddle[ #Example #4 ``` from requests import get get('http://localhost:5000/me').content # default_mediatype *# Chrome ok, Postman default json get('http://localhost:5000/me', headers={"accept":"application/json"}).content get('http://localhost:5000/me', headers={"accept":"application/xml"}).content ``` .figstyle1[ ![](images/restful-ex04.jpg) ] ]] --- #References ##Flask-RESTful - [Flask-RESTful Documentation](http://flask-restful.readthedocs.org/) - [Flask-RESTful @github](https://github.com/flask-restful/flask-restful) --- class: split-30 nopadding background-image: url(https://cloud.githubusercontent.com/assets/4231611/11257839/21b8b24e-8e87-11e5-9099-25f77383f620.jpg) .column_t2.center[.vmiddle[ .fgtransparent[ #
] ]] .column_t2[.vmiddle.nopadding[ .shadelight[.boxtitle1[ # Flask-HTTPAuth #### ]] ]] --- class: split-50 nopadding .column_t2[.vmiddle[ ``` from flask import Flask *from flask_httpauth import HTTPBasicAuth app = Flask(__name__) *auth = HTTPBasicAuth() users = { "john": "hello", "susan": "bye" } #---------------------------------- *@auth.get_password def get_pw(username): if username in users: return users.get(username) return None #---------------------------------- @app.route('/') *@auth.login_required def index(): return "Hello, %s!" % auth.username() #---------------------------------- if __name__ == '__main__': app.run(debug=True) ``` ]] .column_t1[.vmiddle[ # Example #1 ``` 127.0.0.1 - - [23/Nov/2015 18:24:07] "GET / HTTP/1.1" 401 - 127.0.0.1 - - [23/Nov/2015 18:24:32] "POST / HTTP/1.1" 405 - 127.0.0.1 - - [23/Nov/2015 18:26:37] "GET / HTTP/1.1" 401 - *127.0.0.1 - - [23/Nov/2015 18:27:30] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [23/Nov/2015 18:27:42] "GET / HTTP/1.1" 401 - *127.0.0.1 - - [23/Nov/2015 18:27:49] "GET / HTTP/1.1" 200 - ``` .figstyle1[ ![](images/httpauth-ex01.jpg) ] ]] --- class: split-50 nopadding .column_t1[.vmiddle[ # Example #2 ```bash # http://john:hello@localhost:5000 127.0.0.1 - - [24/Nov/2015 15:47:55] "GET / HTTP/1.1" 401 - 127.0.0.1 - - [24/Nov/2015 15:47:55] "GET / HTTP/1.1" 200 - *#Notes # Postman failed! # Todos: HTTPie, requests ``` .figstyle1[ ![](images/httpauth-ex02.jpg) ] ]] .column_t2[.vmiddle[ ``` from flask import Flask *from flask_httpauth import HTTPDigestAuth app = Flask(__name__) *app.config['SECRET_KEY'] = 'secret key here' *auth = HTTPDigestAuth() #---------------------------------- users = { "john": "hello", "susan": "bye" } #---------------------------------- @auth.get_password def get_pw(username): if username in users: return users.get(username) return None #---------------------------------- @app.route('/') @auth.login_required def index(): return "Hello, %s!" % auth.username() #---------------------------------- if __name__ == '__main__': app.run() ``` ]] --- #References ##Flask-HTTPAuth - [Flask-HTTPAuth Documentation](https://flask-httpauth.readthedocs.org/en/latest/) - [Flask-HTTPAuth @github](https://github.com/miguelgrinberg/Flask-HTTPAuth) --- class: split-30 nopadding background-image: url(https://cloud.githubusercontent.com/assets/4231611/11257839/21b8b24e-8e87-11e5-9099-25f77383f620.jpg) .column_t2.center[.vmiddle[ .fgtransparent[ #
] ]] .column_t2[.vmiddle.nopadding[ .shadelight[.boxtitle1[ # Tuts by [@miguelgrinberg](https://github.com/miguelgrinberg) #### ]] ]] --- class: split-50 nopadding .column_t2[.vmiddle[ ``` from flask import Flask, jsonify, abort, make_response *from flask.ext.restful import Api, Resource, reqparse, fields, marshal from flask.ext.httpauth import HTTPBasicAuth # app = Flask(__name__, static_url_path="") -> 404 *app = Flask(__name__) api = Api(app) auth = HTTPBasicAuth() #-------------------------------------------------- *@auth.get_password def get_password(username): if username == 'miguel': return 'python' return None *@auth.error_handler def unauthorized(): # return 403 instead of 401 to prevent browsers from displaying the default # auth dialog return make_response(jsonify({'message': 'Unauthorized access'}), 403) *#-------------------------------------------------- *#-------------------------------------------------- api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint='tasks') *api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/
', endpoint='task') #-------------------------------------------------- if __name__ == '__main__': app.run(debug=True) ``` ]] .column_t1[.vmiddle[ #Example #1 ### Part 1,2 ``` tasks = [ { * 'id': 1, 'title': u'Buy groceries', 'description': u'Milk, Cheese, Pizza, Fruit, Tylenol', 'done': False }, { * 'id': 2, 'title': u'Learn Python', 'description': u'Need to find a good Python tutorial on the web', 'done': False } ] #-------------------------------------------------- task_fields = { 'title': fields.String, 'description': fields.String, 'done': fields.Boolean, * 'uri': fields.Url('task') } ``` ]] --- class: split-50 nopadding .column_t1[.vmiddle[ ### Part 3,4 ``` class TaskListAPI(Resource): decorators = [auth.login_required] * def __init__(self): self.reqparse = reqparse.RequestParser() self.reqparse.add_argument('title', type=str, required=True, help='No task title provided', location='json') self.reqparse.add_argument('description', type=str, default="", location='json') super(TaskListAPI, self).__init__() * def get(self): return {'tasks': [marshal(task, task_fields) for task in tasks]} * def post(self): args = self.reqparse.parse_args() task = { 'id': tasks[-1]['id'] + 1, 'title': args['title'], 'description': args['description'], 'done': False } tasks.append(task) return {'task': marshal(task, task_fields)}, 201 ``` ]] .column_t2[.vmiddle[ ``` class TaskAPI(Resource): decorators = [auth.login_required] * def __init__(self): self.reqparse = reqparse.RequestParser() self.reqparse.add_argument('title', type=str, location='json') self.reqparse.add_argument('description', type=str, location='json') self.reqparse.add_argument('done', type=bool, location='json') super(TaskAPI, self).__init__() * def get(self, id): task = [task for task in tasks if task['id'] == id] if len(task) == 0: abort(404) return {'task': marshal(task[0], task_fields)} * def put(self, id): task = [task for task in tasks if task['id'] == id] if len(task) == 0: abort(404) task = task[0] args = self.reqparse.parse_args() for k, v in args.items(): if v is not None: task[k] = v return {'task': marshal(task, task_fields)} * def delete(self, id): task = [task for task in tasks if task['id'] == id] if len(task) == 0: abort(404) tasks.remove(task[0]) return {'result': True} ``` ]] --- class: column_t1 # The APIs #### .tab2.fullwidth[ | HTTP Method | URI | Endpoint | Action | |-----------|:---:|:--------:|:------:| |GET | /todo/api/v1.0/tasks | **tasks** |Retrieve list of tasks| |POST | /todo/api/v1.0/tasks | **tasks** |Create a new task| |GET | /todo/api/v1.0/tasks/[task_id] | task |Retrieve a task| |PUT | /todo/api/v1.0/tasks/[task_id] | task |Update an existing task| |DELETE | /todo/api/v1.0/tasks/[task_id] | task |Delete a task| ] --- class: split-50 nopadding .column_t2[.vmiddle[ ###Notes - Marshalling - `RequestParser` : define the arguments and how to validate them. - A side benefit of letting Flask-RESTful do the validation is that now there is no need to have a handler for the bad request code 400 error, this is all taken care of by the extension. ]] .column_t1[.vmiddle[ #Example #1 #### .figstyle1[ ![](images/mgrinberg-ex01.jpg) ] ]] --- #References - [Designing a RESTful API using Flask-RESTful](http://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful) - [Code Repo @github](https://github.com/miguelgrinberg/REST-tutorial/blob/master/rest-server-v2.py) --- class: split-30 nopadding background-image: url(https://cloud.githubusercontent.com/assets/4231611/11257839/21b8b24e-8e87-11e5-9099-25f77383f620.jpg) .column_t2.center[.vmiddle[ .fgtransparent[ #
] ]] .column_t2[.vmiddle.nopadding[ .shadelight[.boxtitle1[ # END ### [Eueung Mulyana](https://github.com/eueung) ### http://eueung.github.io/python/flask-restful #### Python CodeLabs | [Attribution-ShareAlike CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/) #### ]] ]]