Python 编程:从零基础到精通
第 64 课 - 使用 Flask 构建 REST API
模块: 十三:Web开发基础 难度: advanced 标签: flask, rest-api, api-development
学习目标
通过本课的学习,你将能够:
- 理解 RESTful API 的核心设计原则(资源、状态码、HTTP方法)。
- 使用 Flask 框架定义处理不同 HTTP 方法(GET, POST, PUT, DELETE)的路由和视图函数。
- 熟练使用
jsonify和request对象来格式化与解析 JSON 数据。 - 构建并测试一个功能完整的简单图书管理 REST API。
- 了解构建 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)
运行与测试:
- 保存以上代码为
app.py。 - 确保已安装 Flask:
pip install flask - 运行程序:
python app.py - 服务器将启动在
http://127.0.0.1:5000。 - 使用浏览器访问
http://127.0.0.1:5000/books将看到 JSON 格式的图书列表。 - 更复杂的请求(如 POST, PUT, DELETE)需要使用工具,如
curl命令、Postman 或 Python 的requests库。
实践练习
-
基础练习:添加作者查询功能
- 要求:为
/books路由添加一个可选的查询参数author。例如,访问GET /books?author=Miguel Grinberg应该只返回该作者的图书。如果未提供author参数,则返回所有图书。 - 预期输出:当请求
GET /books?author=Eric Matthes时,API 应返回一个只包含 Eric Matthes 所著图书的 JSON 列表。
- 要求:为
-
进阶练习:添加数据验证
- 要求:在创建图书 (
POST /books) 的视图函数中添加更严格的数据验证。title必须是字符串类型且长度大于 0。year必须是整数,且范围在 1000 到 2024 之间。
- 如果验证失败,应返回清晰的错误信息(例如
{'error': 'Invalid year, must be between 1000 and 2024'})和400状态码。 - 提示:使用
isinstance()和条件判断。
- 要求:在创建图书 (
-
挑战练习:设计"书籍评论"子资源 API
- 要求:为一本图书设计添加评论的功能。可以设计如下路由:
POST /books/<int:book_id>/reviews:为指定图书添加一条评论。评论应包含reviewer(评论者) 和content(内容) 字段。GET /books/<int:book_id>/reviews:获取指定图书的所有评论。
- 注意:你需要修改数据结构,例如为每本书添加一个
reviews列表。思考如何在内存中维护这种嵌套数据。
- 要求:为一本图书设计添加评论的功能。可以设计如下路由:
常见错误
- 忘记返回 JSON 响应:Flask 视图函数默认返回字符串。使用
jsonify()来确保响应的Content-Type是application/json,并正确格式化数据。 - 错误的状态码使用:创建成功时用
201,而不是200;客户端请求错误用4xx系列,而不是500。正确的状态码对 API 的可用性至关重要。 - 路由冲突或模糊:例如,将
/books和/books/<id>都定义为GET方法。Flask 通常能处理,但清晰的设计是让/books处理集合,/books/<id>处理个体。 - 未处理异常导致的
500错误:在访问请求数据(如request.json['title'])前,应先检查键是否存在,或使用.get()方法提供默认值,避免KeyError导致服务器崩溃。 - 在视图函数中修改全局数据:像本例中直接修改
books_db列表,在真实应用中是危险且不可行的。实际项目应使用数据库和 ORM(如 SQLAlchemy,见上一课)来持久化数据。
小结
本节课我们学习了如何使用 Python 的 Flask 微框架构建一个基础的 RESTful API。关键要点包括:
- REST 核心思想:将后端服务抽象为资源,使用标准的 HTTP 方法(动词)和状态码进行交互。
- Flask 实现:通过
@app.route装饰器定义路由,使用methods参数指定处理的 HTTP 方法。视图函数负责业务逻辑并返回响应。 - 数据处理:使用
request.json解析客户端发送的 JSON 请求体;使用jsonify()将字典转换为 JSON 格式的响应。 - 错误处理:通过自定义错误处理器(如
@app.errorhandler)统一 API 的错误信息格式,提升用户体验。 - 实践:通过一个完整的"图书管理"示例,综合运用了
GET、POST、PUT、DELETE四种方法。
掌握了这些基础,你就打开了 API 开发的大门。在后续学习中,你将接触到更高级的主题,如身份验证、限流、数据库集成和自动化测试,从而构建出健壮、安全的生产级 API。
练习编辑器
python
Loading...