返回介绍

用户资源 Endpoint

发布于 2025-01-02 21:54:02 字数 8210 浏览 0 评论 0 收藏 0

必需的用户 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 错误,而不是返回 Noneget_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

我现在可以请求 id1 的用户(可能是你自己),命令如下:

(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"
}

检索用户集合

要返回所有用户的集合,我现在可以依靠 PaginatedAPIMixinto_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)

对于这个实现,我首先从请求的查询字符串中提取 pageper_page ,如果它们没有被定义,则分别使用默认值 1 和 10。 per_page 具有额外的逻辑,以 100 为上限。 给客户端控件请求太大的页面并不是一个好主意,因为这可能会导致服务器的性能问题。 然后 pageper_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.followersuser.followed 关系查询提供给 to_collection_dict() ,所以希望现在你可以看到,花费一点点额外的时间,并以通用的方式设计该方法,对于获得的回报而言是值得的。 to_collection_dict() 的最后两个参数是 endpoint 名称和 idid 将在 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 {} 确保我总是可以获得一个字典。

在我可以使用这些数据之前,我需要确保我已经掌握了所有信息,因此我首先检查是否包含三个必填字段, usernameemailpassword 。 如果其中任何一个缺失,那么我使用 app/api/errors.py 模块中的 bad_request() 辅助函数向客户端返回一个错误。 除此之外,我还需要确保 usernameemail 字段尚未被其他用户使用,因此我尝试使用获得的用户名和电子邮件从数据库中加载用户,如果返回了有效的用户,那么我也将返回错误给客户端。

一旦通过了数据验证,我可以轻松创建一个用户对象并将其添加到数据库中。 为了创建用户,我依赖 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 错误(如果找不到)。 就像注册新用户一样,我需要验证客户端提供的 usernameemail 字段是否与其他用户发生了冲突,但在这种情况下,验证有点棘手。 首先,这些字段在此请求中是可选的,所以我需要检查字段是否存在。 第二个复杂因素是客户端可能提供与目前字段相同的值,所以在检查用户名或电子邮件是否被采用之前,我需要确保它们与当前的不同。 如果任何验证检查失败,那么我会像之前一样返回 400 错误给客户端。

一旦数据验证通过,我可以使用 User 模型的 from_dict() 方法导入客户端提供的所有数据,然后将更改提交到数据库。 该请求的响应会将更新后的用户表示返回给用户,并使用默认的 200 状态代码。

以下是一个示例请求,它用 HTTPie 编辑 about_me 字段:

(venv) $ http PUT http://localhost:5000/api/users/2 "about_me=Hi, I am Miguel"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文