用户资源 Endpoint
必需的用户 JSON 表示的支持已完成,因此我已准备好开始对 API endpoint 进行编码了。
检索单个用户
让我们就从使用给定的 id
来检索指定用户开始吧:
app/api/users.py :返回一个用户。
from flask import jsonify
from app.models import User
@bp.route('/users/<int:id>', methods=['GET'])
def get_user(id):
return jsonify(User.query.get_or_404(id).to_dict())
视图函数接收被请求用户的 id
作为 URL 中的动态参数。 查询对象的 get_or_404()
方法是以前见过的 get()
方法的一个非常有用的变体,如果用户存在,它返回给定 id
的对象,当 id 不存在时,它会中止请求并向客户端返回一个 404 错误,而不是返回 None
。 get_or_404()
比 get()
更有优势,它不需要检查查询结果,简化了视图函数中的逻辑。
我添加到 User 的 to_dict()
方法用于生成用户资源表示的字典,然后 Flask 的 jsonify()
函数将该字典转换为 JSON 格式的响应以返回给客户端。
如果你想查看第一条 API 路由的工作原理,请启动服务器,然后在浏览器的地址栏中输入以下 URL:
http://localhost:5000/api/users/1
浏览器会以 JSON 格式显示第一个用户。 也尝试使用大一些的 id
值来查看 SQLAlchemy 查询对象的 get_or_404()
方法如何触发 404 错误(我将在稍后向你演示如何扩展错误处理,以便返回这些错误 JSON 格式)。
为了测试这条新路由,我将安装 HTTPie ,这是一个用 Python 编写的命令行 HTTP 客户端,可以轻松发送 API 请求:
(venv) $ pip install httpie
我现在可以请求 id
为 1
的用户(可能是你自己),命令如下:
(venv) $ http GET http://localhost:5000/api/users/1
HTTP/1.0 200 OK
Content-Length: 457
Content-Type: application/json
Date: Mon, 27 Nov 2017 20:19:01 GMT
Server: Werkzeug/0.12.2 Python/3.6.3
{
"_links": {
"avatar": "https://www.gravatar.com/avatar/993c...2724?d=identicon&s=128",
"followed": "/api/users/1/followed",
"followers": "/api/users/1/followers",
"self": "/api/users/1"
},
"about_me": "Hello! I'm the author of the Flask Mega-Tutorial.",
"followed_count": 0,
"follower_count": 1,
"id": 1,
"last_seen": "2017-11-26T07:40:52.942865Z",
"post_count": 10,
"username": "miguel"
}
检索用户集合
要返回所有用户的集合,我现在可以依靠 PaginatedAPIMixin
的 to_collection_dict()
方法:
app/api/users.py :返回所有用户的集合。
from flask import request
@bp.route('/users', methods=['GET'])
def get_users():
page = request.args.get('page', 1, type=int)
per_page = min(request.args.get('per_page', 10, type=int), 100)
data = User.to_collection_dict(User.query, page, per_page, 'api.get_users')
return jsonify(data)
对于这个实现,我首先从请求的查询字符串中提取 page
和 per_page
,如果它们没有被定义,则分别使用默认值 1 和 10。 per_page
具有额外的逻辑,以 100 为上限。 给客户端控件请求太大的页面并不是一个好主意,因为这可能会导致服务器的性能问题。 然后 page
和 per_page
以及 query 对象(在本例中,该查询只是 User.query
,是返回所有用户的最通用的查询)参数被传递给 to_collection_query()
方法。 最后一个参数是 api.get_users
,这是我在表示中使用的三个链接所需的 endpoint 名称。
要使用 HTTPie 测试此 endpoint,请使用以下命令:
(venv) $ http GET http://localhost:5000/api/users
接下来的两个 endpoint 是返回粉丝集合和关注用户集合。 与上面的非常相似:
app/api/users.py :返回粉丝列表和关注用户列表。
@bp.route('/users/<int:id>/followers', methods=['GET'])
def get_followers(id):
user = User.query.get_or_404(id)
page = request.args.get('page', 1, type=int)
per_page = min(request.args.get('per_page', 10, type=int), 100)
data = User.to_collection_dict(user.followers, page, per_page,
'api.get_followers', id=id)
return jsonify(data)
@bp.route('/users/<int:id>/followed', methods=['GET'])
def get_followed(id):
user = User.query.get_or_404(id)
page = request.args.get('page', 1, type=int)
per_page = min(request.args.get('per_page', 10, type=int), 100)
data = User.to_collection_dict(user.followed, page, per_page,
'api.get_followed', id=id)
return jsonify(data)
由于这两条路由是特定于用户的,因此它们具有 id
动态参数。 id
用于从数据库中获取用户,然后将 user.followers
和 user.followed
关系查询提供给 to_collection_dict()
,所以希望现在你可以看到,花费一点点额外的时间,并以通用的方式设计该方法,对于获得的回报而言是值得的。 to_collection_dict()
的最后两个参数是 endpoint 名称和 id
, id
将在 kwargs
中作为一个额外关键字参数,然后在生成链接时将它传递给 url_for()
。
和前面的示例类似,你可以使用 HTTPie 来测试这两个路由,如下所示:
(venv) $ http GET http://localhost:5000/api/users/1/followers
(venv) $ http GET http://localhost:5000/api/users/1/followed
由于超媒体,你不需要记住这些 URL,因为它们包含在用户表示的 _links
部分。
注册新用户
/users 路由的 POST
请求将用于注册新的用户帐户。 你可以在下面看到这条路由的实现:
app/api/users.py :注册新用户。
from flask import url_for
from app import db
from app.api.errors import bad_request
@bp.route('/users', methods=['POST'])
def create_user():
data = request.get_json() or {}
if 'username' not in data or 'email' not in data or 'password' not in data:
return bad_request('must include username, email and password fields')
if User.query.filter_by(username=data['username']).first():
return bad_request('please use a different username')
if User.query.filter_by(email=data['email']).first():
return bad_request('please use a different email address')
user = User()
user.from_dict(data, new_user=True)
db.session.add(user)
db.session.commit()
response = jsonify(user.to_dict())
response.status_code = 201
response.headers['Location'] = url_for('api.get_user', id=user.id)
return response
该请求将接受请求主体中提供的来自客户端的 JSON 格式的用户表示。 Flask 提供 request.get_json()
方法从请求中提取 JSON 并将其作为 Python 结构返回。 如果在请求中没有找到 JSON 数据,该方法返回 None
,所以我可以使用表达式 request.get_json() or {}
确保我总是可以获得一个字典。
在我可以使用这些数据之前,我需要确保我已经掌握了所有信息,因此我首先检查是否包含三个必填字段, username
, email
和 password
。 如果其中任何一个缺失,那么我使用 app/api/errors.py 模块中的 bad_request()
辅助函数向客户端返回一个错误。 除此之外,我还需要确保 username
和 email
字段尚未被其他用户使用,因此我尝试使用获得的用户名和电子邮件从数据库中加载用户,如果返回了有效的用户,那么我也将返回错误给客户端。
一旦通过了数据验证,我可以轻松创建一个用户对象并将其添加到数据库中。 为了创建用户,我依赖 User
模型中的 from_dict()
方法, new_user
参数被设置为 True
,所以它也接受通常不存在于用户表示中的 password
字段。
我为这个请求返回的响应将是新用户的表示,所以使用 to_dict()
产生它的有效载荷。 创建资源的 POST
请求的响应状态代码应该是 201,即创建新实体时使用的代码。 此外,HTTP 协议要求 201 响应包含一个值为新资源 URL 的 Location
头部。
下面你可以看到如何通过 HTTPie 从命令行注册一个新用户:
(venv) $ http POST http://localhost:5000/api/users username=alice password=dog \
email=alice@example.com "about_me=Hello, my name is Alice!"
编辑用户
示例 API 中使用的最后一个 endpoint 用于修改已存在的用户:
app/api/users.py :修改用户。
@bp.route('/users/<int:id>', methods=['PUT'])
def update_user(id):
user = User.query.get_or_404(id)
data = request.get_json() or {}
if 'username' in data and data['username'] != user.username and \
User.query.filter_by(username=data['username']).first():
return bad_request('please use a different username')
if 'email' in data and data['email'] != user.email and \
User.query.filter_by(email=data['email']).first():
return bad_request('please use a different email address')
user.from_dict(data, new_user=False)
db.session.commit()
return jsonify(user.to_dict())
一个请求到来,我通过 URL 收到一个动态的用户 id
,所以我可以加载指定的用户或返回 404 错误(如果找不到)。 就像注册新用户一样,我需要验证客户端提供的 username
和 email
字段是否与其他用户发生了冲突,但在这种情况下,验证有点棘手。 首先,这些字段在此请求中是可选的,所以我需要检查字段是否存在。 第二个复杂因素是客户端可能提供与目前字段相同的值,所以在检查用户名或电子邮件是否被采用之前,我需要确保它们与当前的不同。 如果任何验证检查失败,那么我会像之前一样返回 400 错误给客户端。
一旦数据验证通过,我可以使用 User
模型的 from_dict()
方法导入客户端提供的所有数据,然后将更改提交到数据库。 该请求的响应会将更新后的用户表示返回给用户,并使用默认的 200 状态代码。
以下是一个示例请求,它用 HTTPie 编辑 about_me
字段:
(venv) $ http PUT http://localhost:5000/api/users/2 "about_me=Hi, I am Miguel"
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论