64·模块十三:Web开发基础高级

使用 Flask 构建 REST API

flaskrest-apiapi-development

Python 编程:从零基础到精通

第 64 课 - 使用 Flask 构建 REST API

模块: 十三:Web开发基础 难度: advanced 标签: flask, rest-api, api-development


学习目标

通过本课的学习,你将能够:

  1. 理解 RESTful API 的核心设计原则(资源、状态码、HTTP方法)。
  2. 使用 Flask 框架定义处理不同 HTTP 方法(GET, POST, PUT, DELETE)的路由和视图函数。
  3. 熟练使用 jsonifyrequest 对象来格式化与解析 JSON 数据。
  4. 构建并测试一个功能完整的简单图书管理 REST API。
  5. 了解构建 API 时常见的错误模式与最佳实践。

核心概念

想象一下你去餐厅点餐。你(客户端)告诉服务员(服务器):"我要一份宫保鸡丁(资源)",服务员把订单(请求)送到厨房,厨房做好后通过服务员把宫保鸡丁(响应)交给你。REST API 的工作方式与此类似,它是客户端与服务器之间通过 HTTP 协议进行结构化通信的一套"点餐规则"。

REST 的核心是"资源":在 REST 中,服务器上的任何东西(如用户、文章、图片)都可以被抽象为一个"资源",每个资源都有一个唯一的标识符(URL)。我们通过 HTTP 动词(方法)来操作这些资源。

  • GET:查询/获取一个资源。例如,GET /books/1 表示获取 ID 为 1 的图书。
  • POST:创建一个新的资源。例如,POST /books 并附上图书数据,表示创建一本新书。
  • PUT:更新一个已存在的资源(通常需要提供完整的更新数据)。例如,PUT /books/1 并附上新的数据,表示更新 ID 为 1 的图书。
  • DELETE:删除一个资源。例如,DELETE /books/1 表示删除 ID 为 1 的图书。

响应状态码是服务器告诉客户端"点餐结果"的代码:

  • 200 OK:请求成功。
  • 201 Created:资源创建成功。
  • 400 Bad Request:客户端发送了错误的请求(如缺少必要字段)。
  • 404 Not Found:请求的资源不存在。
  • 500 Internal Server Error:服务器内部出错。

在 Flask 中,我们使用路由装饰器 @app.route 来绑定 URL 和视图函数,并通过指定 methods 参数来处理不同的 HTTP 动作。

代码示例

下面是一个完整的 Flask REST API 示例,实现了一个简单的图书管理接口。我们将使用内存中的列表来模拟数据库。

# app.py
from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# 使用一个列表模拟数据库,存储字典形式的图书数据
books_db = [
    {
        'id': 1,
        'title': 'Flask Web Development',
        'author': 'Miguel Grinberg',
        'year': 2018
    },
    {
        'id': 2,
        'title': 'Python Crash Course',
        'author': 'Eric Matthes',
        'year': 2019
    }
]

# 用于生成下一个可用 ID 的简单函数
def get_next_id():
    return max(book['id'] for book in books_db) + 1 if books_db else 1

# 1. 获取所有图书 (GET /books)
@app.route('/books', methods=['GET'])
def get_books():
    """返回所有图书的 JSON 列表"""
    return jsonify({'books': books_db})

# 2. 获取单本图书 (GET /books/<int:book_id>)
@app.route('/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
    """根据 ID 返回一本图书"""
    # 使用列表推导式查找图书
    book = [book for book in books_db if book['id'] == book_id]
    if len(book) == 0:
        # 如果没找到,返回 404 错误
        abort(404)
    # 返回找到的第一本书(book 是一个列表,取第一个元素)
    return jsonify({'book': book[0]})

# 3. 创建新图书 (POST /books)
@app.route('/books', methods=['POST'])
def create_book():
    """创建一本新图书"""
    # 检查请求是否是 JSON 格式,以及是否包含必需字段
    if not request.json or not 'title' in request.json:
        abort(400) # 错误的请求

    new_book = {
        'id': get_next_id(),
        'title': request.json['title'],
        # 使用 .get() 方法为可选字段提供默认值
        'author': request.json.get('author', ""),
        'year': request.json.get('year', 2000)
    }
    books_db.append(new_book)
    # 返回新创建的资源和 201 Created 状态码
    return jsonify({'book': new_book}), 201

# 4. 更新图书 (PUT /books/<int:book_id>)
@app.route('/books/<int:book_id>', methods=['PUT'])
def update_book(book_id):
    """更新一本图书的信息"""
    # 找到要更新的图书
    book = [book for book in books_db if book['id'] == book_id]
    if len(book) == 0:
        abort(404)
    if not request.json:
        abort(400)
    # 更新字段,注意类型检查
    book[0]['title'] = request.json.get('title', book[0]['title'])
    book[0]['author'] = request.json.get('author', book[0]['author'])
    book[0]['year'] = request.json.get('year', book[0]['year'])
    # 返回更新后的资源
    return jsonify({'book': book[0]})

# 5. 删除图书 (DELETE /books/<int:book_id>)
@app.route('/books/<int:book_id>', methods=['DELETE'])
def delete_book(book_id):
    """删除一本图书"""
    book = [book for book in books_db if book['id'] == book_id]
    if len(book) == 0:
        abort(404)
    books_db.remove(book[0])
    # 返回空响应和 200 OK
    return jsonify({'result': True})

# 6. 自定义错误处理器,让错误信息也以 JSON 格式返回
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(400)
def bad_request(error):
    return jsonify({'error': 'Bad request'}), 400

if __name__ == '__main__':
    # 开启调试模式,方便开发
    app.run(debug=True)

运行与测试:

  1. 保存以上代码为 app.py
  2. 确保已安装 Flask:pip install flask
  3. 运行程序:python app.py
  4. 服务器将启动在 http://127.0.0.1:5000
  5. 使用浏览器访问 http://127.0.0.1:5000/books 将看到 JSON 格式的图书列表。
  6. 更复杂的请求(如 POST, PUT, DELETE)需要使用工具,如 curl 命令、Postman 或 Python 的 requests 库。

实践练习

  1. 基础练习:添加作者查询功能

    • 要求:为 /books 路由添加一个可选的查询参数 author。例如,访问 GET /books?author=Miguel Grinberg 应该只返回该作者的图书。如果未提供 author 参数,则返回所有图书。
    • 预期输出:当请求 GET /books?author=Eric Matthes 时,API 应返回一个只包含 Eric Matthes 所著图书的 JSON 列表。
  2. 进阶练习:添加数据验证

    • 要求:在创建图书 (POST /books) 的视图函数中添加更严格的数据验证。
      • title 必须是字符串类型且长度大于 0。
      • year 必须是整数,且范围在 1000 到 2024 之间。
    • 如果验证失败,应返回清晰的错误信息(例如 {'error': 'Invalid year, must be between 1000 and 2024'})和 400 状态码。
    • 提示:使用 isinstance() 和条件判断。
  3. 挑战练习:设计"书籍评论"子资源 API

    • 要求:为一本图书设计添加评论的功能。可以设计如下路由:
      • POST /books/<int:book_id>/reviews:为指定图书添加一条评论。评论应包含 reviewer (评论者) 和 content (内容) 字段。
      • GET /books/<int:book_id>/reviews:获取指定图书的所有评论。
    • 注意:你需要修改数据结构,例如为每本书添加一个 reviews 列表。思考如何在内存中维护这种嵌套数据。

常见错误

  1. 忘记返回 JSON 响应:Flask 视图函数默认返回字符串。使用 jsonify() 来确保响应的 Content-Typeapplication/json,并正确格式化数据。
  2. 错误的状态码使用:创建成功时用 201,而不是 200;客户端请求错误用 4xx 系列,而不是 500。正确的状态码对 API 的可用性至关重要。
  3. 路由冲突或模糊:例如,将 /books/books/<id> 都定义为 GET 方法。Flask 通常能处理,但清晰的设计是让 /books 处理集合,/books/<id> 处理个体。
  4. 未处理异常导致的 500 错误:在访问请求数据(如 request.json['title'])前,应先检查键是否存在,或使用 .get() 方法提供默认值,避免 KeyError 导致服务器崩溃。
  5. 在视图函数中修改全局数据:像本例中直接修改 books_db 列表,在真实应用中是危险且不可行的。实际项目应使用数据库和 ORM(如 SQLAlchemy,见上一课)来持久化数据。

小结

本节课我们学习了如何使用 Python 的 Flask 微框架构建一个基础的 RESTful API。关键要点包括:

  • REST 核心思想:将后端服务抽象为资源,使用标准的 HTTP 方法(动词)和状态码进行交互。
  • Flask 实现:通过 @app.route 装饰器定义路由,使用 methods 参数指定处理的 HTTP 方法。视图函数负责业务逻辑并返回响应。
  • 数据处理:使用 request.json 解析客户端发送的 JSON 请求体;使用 jsonify() 将字典转换为 JSON 格式的响应。
  • 错误处理:通过自定义错误处理器(如 @app.errorhandler)统一 API 的错误信息格式,提升用户体验。
  • 实践:通过一个完整的"图书管理"示例,综合运用了 GETPOSTPUTDELETE 四种方法。

掌握了这些基础,你就打开了 API 开发的大门。在后续学习中,你将接触到更高级的主题,如身份验证、限流、数据库集成和自动化测试,从而构建出健壮、安全的生产级 API。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「全栈框架入门:Django 简介与项目创建」 以巩固所学知识。