2
\$\begingroup\$

I've created my first basic API using Flask, that has the ability to create new tasks, delete tasks, update tasks, handles error pages, and has authentication required.

What I would like to know is if there are anyways to make this better, can I make the authentication a little more complicated? What did I do well? What could I have done better?

from flask import Flask
from flask import jsonify
from flask import abort
from flask import make_response
from flask import request
from flask import url_for
from flask.ext.httpauth import HTTPBasicAuth


auth = HTTPBasicAuth()
app = Flask(__name__)
tasks = [  # Data that will be supplied to the API
    {
        "id": 1,
        "title": u"Buy groceries",
        "description": u"Milk, Cheese, Pizza, Fruit, Tylenol",
        "done": False
    },
    {
        "id": 2,
        "description": u"Need to find a good Python tutorial on the web",
        "done": False,
        "title": "Learn Python"
    }
]


@auth.get_password
def get_password(username):
    """ Require a username and password """
    if username == "miguel":
        return "python"
    return None


def make_public_task(task):
    """ Turn a task public and provide a URI that will be used for callback
    >>> curl -i http://localhost:5000/todo/api/v1.0/tasks
    {
      "tasks": [
        {
          "title": "Buy groceries",
          "done": false,
          "description": "Milk, Cheese, Pizza, Fruit, Tylenol",
          "uri": "http://localhost:5000/todo/api/v1.0/tasks/1"
    }, ... """
    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=["POST"])
def create_task():
    """ Create a task in the API with POST
    >>> curl -i -H "Content-Type: application/json" -X POST -d '{"title":"Read a book"}' http://localhost:5000/todo/api/v1.0/tasks
    {
      "task": {
      "description": "",
      "done": false,
      "id": 3,
      "title": "Read a book"
      }
    } """
    if not request.json or "title" not 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": task}), 201


@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
    """ Update a task in the API using PUT
    >>> curl -i -H "Content-Type: application/json" -X PUT -d '{"done":true}' http://localhost:5000/todo/api/v1.0/tasks/2
    {
      "task": [
        {
          "description": "Need to find a good Python tutorial on the web",
          "done": true,
          "id": 2,
          "title": "Learn Python"
        }
      ]
    } """
    task = [task for task in tasks if task['id'] == task_id]
    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': task[0]})


@app.route("/todo/api/v1.0/tasks/<int:task_id>")
def delete_task(task_id):
    """ Delete a task from the API """
    task = [task for task in tasks if task["id"] == task_id]
    if len(task) == 0:
        abort(404)
    tasks.remove(task[0])
    return jsonify({"result": True})


@app.route('/todo/api/v1.0/tasks', methods=['GET'])
@auth.login_required
def get_tasks():
    return jsonify({'tasks': [make_public_task(task) for task in tasks]})


@app.route("/todo/api/v1.0/tasks/<int:task_id>", methods=["GET"])
def get_task(task_id):
    """ Get a specific task from the API """
    task = [task for task in tasks if task["id"] == task_id]
    if len(task) == 0:
        abort(404)
    return jsonify({"task": task[0]})


@app.errorhandler(404)
def not_found(error):
    """ Return an error """
    return make_response(jsonify({"error": "Not Found"}), 404)


@auth.error_handler
def unauthorized():
    """ Return unauthorized access in JSON form """
    return make_response(jsonify({"error": "Unauthorized access"}), 401)


if __name__ == '__main__':
    app.run(debug=True)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.