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