Skip to content

Instantly share code, notes, and snippets.

@miguelgrinberg
Last active March 28, 2024 08:13
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)
@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.

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