Skip to content

Instantly share code, notes, and snippets.

@miguelgrinberg
Last active March 29, 2024 09:05
Show Gist options
  • Save miguelgrinberg/5614326 to your computer and use it in GitHub Desktop.
Save miguelgrinberg/5614326 to your computer and use it in GitHub Desktop.
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 bad_request(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)
@miguelgrinberg
Copy link
Author

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

@patilanup246
Copy link

patilanup246 commented Oct 15, 2019 via email

@miguelgrinberg
Copy link
Author

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

@SavAcharya
Copy link

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

@sskanishk
Copy link

@ineam how to solve that Unicode error ?

@Synster
Copy link

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
Copy link

@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
Copy link

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
Copy link

Thank you!
Great tutorial!!

@ronk7
Copy link

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
Copy link
Author

@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
Copy link

ronk7 commented Jul 20, 2020 via email

@miguelgrinberg
Copy link
Author

@ronk7 pip install requests

@Ming-D-BigData
Copy link

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.

@1ncredibleM1nd
Copy link

Hello,Miguel, sorry for my question about it. I'm beginner in flask , true, I'am front-end developer , but i need learn Flask and write Rest server for my task. But i have error while i copy-paste your code.
If a write request curl -i http://localhost:5000/todo/api/v1.0/tasks/2
I getting Error in JSON , why? Can u help me.
See you soon

@miguelgrinberg
Copy link
Author

@CPPtrueCoder What's the error? You don't have any JSON, so what do you mean by "error in JSON"?

@1ncredibleM1nd
Copy link

When i get curl -i http://localhost:5000/todo/api/v1.0/tasks/2
From server response {
"error": "Not found"
}
but tasks is available :(

@miguelgrinberg
Copy link
Author

miguelgrinberg commented May 11, 2021

@CPPtrueCoder How do you know it is available? What is the response when you send a request to http://localhost:5000/todo/api/v1.0/tasks?

@1ncredibleM1nd
Copy link

if request : http://localhost:5000/todo/api/v1.0/tasks then tasks like your guide , but then i try request with ID , i have troubles
image

@1ncredibleM1nd
Copy link

(venv) C:\Users\a.rodin\PycharmProjects\flaskProject>curl -i http://localhost:5000/todo/api/v1.0/tasks/1
HTTP/1.0 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 232
Server: Werkzeug/1.0.1 Python/3.8.8
Date: Tue, 11 May 2021 10:02:55 GMT

<title>404 Not Found</title>

Not Found

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

@1ncredibleM1nd
Copy link

i tried rewrite it according updates from comments , but no result , unfortunately

@1ncredibleM1nd
Copy link

Lol, i changed function :
@app.route('/todo/api/v1.0/tasks/<task_id>', methods=['GET']), instead @app.route('/todo/api/v1.0/tasks/int:task_id', methods=['GET'])
And it's working , but i think it isnt' safely, isn't it ?

@miguelgrinberg
Copy link
Author

@CPPtrueCoder you should use the code as shown above. That is the best. Neither of the two options you posted above matches my code.

@gabidoye
Copy link

gabidoye commented Sep 8, 2021

Thank you. exactly what i needed as a complete beginner

@chris-kasa
Copy link

super useful and straightforward... thanks!!

@arafatmohammed
Copy link

Thanks, this is helpful, but I do not get the id in the response:

~ curl -u username:password -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 407
Server: Werkzeug/2.0.2 Python/3.8.9
Date: Tue, 21 Dec 2021 01:34:46 GMT

{
  "tasks": [
    {
      "description": "Milk, Cheese, Pizza, Fruit, Tylenol",
      "done": false,
      "title": "Buy groceries",
      "uri": "http://localhost:5000/todo/api/v1.0/tasks/1"
    },
    {
      "description": "Need to find a good Python tutorial on the web",
      "done": false,
      "title": "Learn Python",
      "uri": "http://localhost:5000/todo/api/v1.0/tasks/2"
    }
  ]
}

And so I am unable to DELETE it either:

line 102, in delete_task
    if len(task) == 0:
TypeError: object of type 'filter' has no len()

@miguelgrinberg
Copy link
Author

@arafatmohammed you are obviously using different code than what I posted here, so I have no way to tell you what's wrong. Try using my code exactly as shown here.

@alirezaAsadi2018
Copy link

alirezaAsadi2018 commented Feb 25, 2022

@miguelgrinberg Thnks for ur great job.
I think you'd better rename this function name to sth better as it conflicts with this function name.
I suggest using def bad_request(error) instead.

@Antoh1
Copy link

Antoh1 commented Nov 12, 2022

Awesome step by step tutorial, Thank you miguel.

@EljonesA
Copy link

Thank you. exactly what i needed as a complete beginner

yeah, right guide.

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