Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
The code from my article on building RESTful web services with Python and the Flask microframework. See the article here: http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
#!flask/bin/python
from flask import Flask, jsonify, abort, request, make_response, url_for
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__, static_url_path = "")
auth = HTTPBasicAuth()
@auth.get_password
def get_password(username):
if username == 'miguel':
return 'python'
return None
@auth.error_handler
def unauthorized():
return make_response(jsonify( { 'error': 'Unauthorized access' } ), 403)
# return 403 instead of 401 to prevent browsers from displaying the default auth dialog
@app.errorhandler(400)
def not_found(error):
return make_response(jsonify( { 'error': 'Bad request' } ), 400)
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify( { 'error': 'Not found' } ), 404)
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
}
]
def make_public_task(task):
new_task = {}
for field in task:
if field == 'id':
new_task['uri'] = url_for('get_task', task_id = task['id'], _external = True)
else:
new_task[field] = task[field]
return new_task
@app.route('/todo/api/v1.0/tasks', methods = ['GET'])
@auth.login_required
def get_tasks():
return jsonify( { 'tasks': map(make_public_task, tasks) } )
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['GET'])
@auth.login_required
def get_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
return jsonify( { 'task': make_public_task(task[0]) } )
@app.route('/todo/api/v1.0/tasks', methods = ['POST'])
@auth.login_required
def create_task():
if not request.json or not 'title' in request.json:
abort(400)
task = {
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify( { 'task': make_public_task(task) } ), 201
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['PUT'])
@auth.login_required
def update_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify( { 'task': make_public_task(task[0]) } )
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['DELETE'])
@auth.login_required
def delete_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
tasks.remove(task[0])
return jsonify( { 'result': True } )
if __name__ == '__main__':
app.run(debug = True)
@DeepakDinakaran22

This comment has been minimized.

Copy link

@DeepakDinakaran22 DeepakDinakaran22 commented Feb 4, 2016

Very useful...Thanks

@girisagar46

This comment has been minimized.

Copy link

@girisagar46 girisagar46 commented Feb 21, 2016

Thanks :)

@nmud19

This comment has been minimized.

Copy link

@nmud19 nmud19 commented Mar 4, 2016

Thanks man!

@Rsimpossible

This comment has been minimized.

Copy link

@Rsimpossible Rsimpossible commented Apr 27, 2016

I am getting error while executing app.py

C:\Python27\todo-api\flask>python app.py
Traceback (most recent call last):
File "app.py", line 3, in
from flask.ext.httpauth import HTTPBasicAuth
File "C:\Python27\Scripts\flask\lib\site-packages\flask\exthook.py", line 87, in load_module
raise ImportError('No module named %s' % fullname)
ImportError: No module named flask.ext.httpauth

@matrixfox

This comment has been minimized.

Copy link

@matrixfox matrixfox commented Apr 28, 2016

@Rsimpossible What does that error message say?

flask/bin/pip install flask-httpauth
https://github.com/miguelgrinberg/flask-httpauth

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented May 15, 2016

@app.errorhandler(400)
def not_found(error):
return make_response(jsonify( { 'error': 'Bad request' } ), 400)

@app.errorhandler(404)
def not_found(error):
return make_response(jsonify( { 'error': 'Not found' } ), 404)

in the above code function name is same for both 400 and 404.

@somukempraj

This comment has been minimized.

Copy link

@somukempraj somukempraj commented May 20, 2016

Very helpful code and thanks .I'm getting "error": "Unauthorized access" in the browser while I run rest-server.py .Can you help me get rid of this please?

@shivam-kotwalia

This comment has been minimized.

Copy link

@shivam-kotwalia shivam-kotwalia commented Jul 18, 2016

Thanks miguelgrinberg for this wonderful code !!

For other how are getting the error for flask.ext.httpauth is depricated
So just change flask.ext.httpauth to flask_httpauth in line number 3

@asavalia

This comment has been minimized.

Copy link

@asavalia asavalia commented Sep 5, 2016

Really, it's very helpfull... 👍

@SandhiyaSelvaraj

This comment has been minimized.

Copy link

@SandhiyaSelvaraj SandhiyaSelvaraj commented Sep 19, 2016

Thank you so much Man !!!! It's really helpful !!!

@Muthama-Kahohi

This comment has been minimized.

Copy link

@Muthama-Kahohi Muthama-Kahohi commented Jan 10, 2017

This was super helpful. It got me acquainted with api using flask very easily. Keep up the good work Miguel

@rishabhricky

This comment has been minimized.

Copy link

@rishabhricky rishabhricky commented Mar 21, 2017

How can I convert my code in API : Please follow this link I have posted detailed question on Stack Overflow

http://stackoverflow.com/questions/42928251/making-my-python-code-as-an-restful-api-python-flask

@Project-MAR

This comment has been minimized.

Copy link

@Project-MAR Project-MAR commented Mar 24, 2017

Great guide, Thank you very much.

@vboyk

This comment has been minimized.

Copy link

@vboyk vboyk commented Apr 26, 2017

flask.ext.httpauth package is marked as deprecated (Python 3.6). Use flask_httpauth instead:
from flask_httpauth import HTTPBasicAuth

@4admin2root

This comment has been minimized.

Copy link

@4admin2root 4admin2root commented May 11, 2017

just I want

@MITsVision

This comment has been minimized.

Copy link

@MITsVision MITsVision commented May 29, 2017

very easy to understand, best for beginners

@vrangasayee

This comment has been minimized.

Copy link

@vrangasayee vrangasayee commented Jun 1, 2017

Awesome writeup for beginners. I was trying to teach my son to write a restful api. I am definitely going to point him to this.

@robinvd

This comment has been minimized.

Copy link

@robinvd robinvd commented Aug 2, 2017

on python36

return jsonify( { 'tasks': map(make_public_task, tasks) } )

has type 'map' and get_tasks(), panics with

  File "/nix/store/mhmw7rqpvr7vg16z4gz3wan58bxbk2an-python3-3.6.1/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'map' is not JSON serializable

any fix for this?

@MarcAragones

This comment has been minimized.

Copy link

@MarcAragones MarcAragones commented Oct 27, 2017

Hey @robinvd, I fixed the issue by wrapping map with list:

return jsonify( { 'tasks': list(map(make_public_task, tasks)) } )

And the same happens with filter:

task = list(filter(lambda t: t['id'] == task_id, tasks))
@SonawaneMayur

This comment has been minimized.

Copy link

@SonawaneMayur SonawaneMayur commented Jan 4, 2018

Really useful article. Thanks.

@JackOHara

This comment has been minimized.

Copy link

@JackOHara JackOHara commented Feb 8, 2018

Great stuff. To the point. Cheers!

@trssssrt

This comment has been minimized.

Copy link

@trssssrt trssssrt commented Mar 2, 2018

Great tutorial. This code does give an error, but it may be related to the newest version of python.

The fixes are easy though:

  1. the map fails on line 54. So change it to your suggestion on the blog post: return jsonify({'tasks': [make_public_task(task) for task in tasks]})
    Or use @MarcAragones solution above.
  2. changing unicode to str (Python 3.6 made this change)

And with those modifications, the code works!

@bjcom1299

This comment has been minimized.

Copy link

@bjcom1299 bjcom1299 commented Jun 8, 2018

When I try to test POST method by curl, I get the result:

HTTP/1.0 405 METHOD NOT ALLOWED
Content-Type: text/html
Allow: HEAD, OPTIONS, GET

what's wrong with me? Please help.

Thanks.

@iYadavVaibhav

This comment has been minimized.

Copy link

@iYadavVaibhav iYadavVaibhav commented Jul 12, 2018

Really easy to follow and very useful. Do we have any tuttorial to take this further and hook with Mongo/MySQL? Thanks :)

@xcosmos6

This comment has been minimized.

Copy link

@xcosmos6 xcosmos6 commented Aug 13, 2018

Thank you! This is a great tutorial that is easy to follow.

@newtonkiragu

This comment has been minimized.

Copy link

@newtonkiragu newtonkiragu commented Aug 16, 2018

This is a very good tutorial, is there a tutorial on how to build a REST api using the MV (model view) design pattern instead of singleton?

@holms

This comment has been minimized.

Copy link

@holms holms commented Oct 13, 2018

Would be nice to get at least one functional test

@UmutAlihan

This comment has been minimized.

Copy link

@UmutAlihan UmutAlihan commented Oct 14, 2018

Thank you very much for the contribution!
That is an elegantly simple and informative tutorial.

Additionally security part is very unique and nice comapred to other web api tutorials!

@naiieandrade

This comment has been minimized.

Copy link

@naiieandrade naiieandrade commented Oct 22, 2018

Very useful! Thanks! It's easy to understand how to work!

@ineam

This comment has been minimized.

Copy link

@ineam ineam commented Nov 14, 2018

Thanks a lot, and for the keyword "unicode" in python3, doesn't exist no more, just put str instead, as now str include unicode too.

@AzraelWarrior

This comment has been minimized.

Copy link

@AzraelWarrior AzraelWarrior commented Feb 1, 2019

Very useful! Thanks! It's easy to understand how to work!

@95rade

This comment has been minimized.

Copy link

@95rade 95rade commented Mar 25, 2019

Great job Miguel. Thank you for sharing your awesome work.

@patilanup246

This comment has been minimized.

Copy link

@patilanup246 patilanup246 commented Oct 15, 2019

How to pass credentials using post like username in get method

@miguelgrinberg

This comment has been minimized.

Copy link
Owner Author

@miguelgrinberg miguelgrinberg commented Oct 15, 2019

@patilanup246: credentials are passed in the same way for GET and POST requests.

@patilanup246

This comment has been minimized.

Copy link

@patilanup246 patilanup246 commented Oct 15, 2019

@miguelgrinberg

This comment has been minimized.

Copy link
Owner Author

@miguelgrinberg miguelgrinberg commented Oct 15, 2019

@patilanup246: use the options provided by Postman for HTTP Basic Authentication.

@SavAcharya

This comment has been minimized.

Copy link

@SavAcharya SavAcharya commented Feb 10, 2020

Thanks Miguel, this was very helpful. Just what i was looking for, well explained and easy to understand.

@sskanishk

This comment has been minimized.

Copy link

@sskanishk sskanishk commented Mar 21, 2020

@ineam how to solve that Unicode error ?

@Synster

This comment has been minimized.

Copy link

@Synster Synster commented Mar 26, 2020

@sskanishk You can just replace it with str in python 3+
if 'title' in request.json and type(request.json['title']) != str:
abort(400)
if 'description' in request.json and type(request.json['description']) is not str:
abort(400)

@sskanishk

This comment has been minimized.

Copy link

@sskanishk sskanishk commented Mar 27, 2020

@sskanishk You can just replace it with str in python 3+
if 'title' in request.json and type(request.json['title']) != str:
abort(400)
if 'description' in request.json and type(request.json['description']) is not str:
abort(400)

solved; thanks

@shweb19

This comment has been minimized.

Copy link

@shweb19 shweb19 commented Apr 3, 2020

Copied the code verbatim. But after curl -u miguel:python -i http://localhost:5000/todo/api/v1.0/tasks
I get the error message:

raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <map object at 0x10a405160> is not JSON serializable
@AnnaShera

This comment has been minimized.

Copy link

@AnnaShera AnnaShera commented May 11, 2020

Thank you!
Great tutorial!!

@ronk7

This comment has been minimized.

Copy link

@ronk7 ronk7 commented Jul 20, 2020

Thank you so much, Miguel, for this wonderful code. Do you happen to have any advice or examples for anyone writing a client for a server like this?

@miguelgrinberg

This comment has been minimized.

Copy link
Owner Author

@miguelgrinberg miguelgrinberg commented Jul 20, 2020

@ronk7 for a JavaScript client I wrote a follow-up tutorial: https://blog.miguelgrinberg.com/post/writing-a-javascript-rest-client. For a Python client use the requests package.

@ronk7

This comment has been minimized.

Copy link

@ronk7 ronk7 commented Jul 20, 2020

@miguelgrinberg

This comment has been minimized.

Copy link
Owner Author

@miguelgrinberg miguelgrinberg commented Jul 20, 2020

@ronk7 pip install requests

@Ming-D-BigData

This comment has been minimized.

Copy link

@Ming-D-BigData Ming-D-BigData commented Feb 24, 2021

Hey @robinvd, I fixed the issue by wrapping map with list:

return jsonify( { 'tasks': list(map(make_public_task, tasks)) } )

And the same happens with filter:

task = list(filter(lambda t: t['id'] == task_id, tasks))

The fixes are needed for Python3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment