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 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 commented Mar 21, 2020

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

AnnaShera commented May 11, 2020

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

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

ronk7 commented Jul 20, 2020

@miguelgrinberg
Copy link
Author

miguelgrinberg commented Jul 20, 2020

@ronk7 pip install requests

@Ming-D-BigData
Copy link

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.

@1ncredibleM1nd
Copy link

1ncredibleM1nd commented May 10, 2021

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

miguelgrinberg commented May 10, 2021

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

@1ncredibleM1nd
Copy link

1ncredibleM1nd commented May 11, 2021

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

1ncredibleM1nd commented May 11, 2021

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

1ncredibleM1nd commented May 11, 2021

(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

1ncredibleM1nd commented May 11, 2021

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

@1ncredibleM1nd
Copy link

1ncredibleM1nd commented May 11, 2021

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

miguelgrinberg commented May 11, 2021

@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

chris-kasa commented Oct 24, 2021

super useful and straightforward... thanks!!

@arafatmohammed
Copy link

arafatmohammed commented Dec 21, 2021

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

miguelgrinberg commented Dec 21, 2021

@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.

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