42

在我的 Flask-RESTful API 中,假设我有两个对象,用户和城市。这是一对多的关系。现在,当我创建我的 API 并向其添加资源时,我所能做的似乎就是将非常简单和通用的 URL 映射到它们。这是代码(不包括无用的东西):

class UserAPI(Resource):  # The API class that handles a single user
  def __init__(self):
    # Initialize

  def get(self, id):
    # GET requests

  def put(self, id):
    # PUT requests

  def delete(self, id):
    # DELETE requests

class UserListAPI(Resource):  # The API class that handles the whole group of Users
  def __init__(self):

  def get(self):

  def post(self):

api.add_resource(UserAPI, '/api/user/<int:id>', endpoint='user')
api.add_resource(UserListAPI, '/api/users/', endpoint='users')

class CityAPI(Resource):
  def __init__(self):

  def get(self, id):

  def put(self, id):

  def delete(self, id):

class CityListAPI(Resource):
  def __init__(self):

  def get(self):

  def post(self):

api.add_resource(CityListAPI, '/api/cities/', endpoint='cities')
api.add_resource(CityAPI, '/api/city/<int:id>', endpoint='city')

如您所见,我可以做任何我想做的事情来实现一个非常基本的功能。我可以 GET、POST、PUT 和 DELETE 两个对象。但是,我的目标有两个:

(1) 能够使用城市名称等其他参数进行请求,而不仅仅是城市 ID。它看起来像:
api.add_resource(CityAPI, '/api/city/<string:name>', endpoint='city')
除了它不会给我这个错误:

AssertionError:视图函数映射正在覆盖现有端点函数

(2) 能够将两个资源组合在一个请求中。假设我想让所有用户与某个城市相关联。在 REST URL 中,它应该类似于:
/api/cities/<int:id>/users

我如何用 Flask 做到这一点?我将它映射到哪个端点?

基本上,我正在寻找将我的 API 从基本变为有用的方法。

4

2 回答 2

86

你犯了两个错误。

首先,Flask-RESTful 让您认为资源是通过单个 URL 实现的。实际上,您可以有许多不同的 URL,它们返回相同类型的资源。在 Flask-RESTful 中,您需要Resource为每个 URL 创建不同的子类,但从概念上讲,这些 URL 属于同一资源。请注意,实际上,您已经为每个资源创建了两个实例来处理列表和单个请求。

您犯的第二个错误是您希望客户端知道您 API 中的所有 URL。这不是构建 API 的好方法,理想情况下,客户端只知道几个顶级 URL,然后从顶级 URL 的响应中的数据中发现其余部分。

在您的 API 中,您可能希望将/api/users/api/cities作为顶级 API 公开。各个城市和用户的 URL 将包含在响应中。例如,如果我调用http://example.com/api/users以获取用户列表,我可能会收到以下响应:

{
    "users": [ 
        {
            "url": "http://example.com/api/user/1",
            "name": "John Smith",
            "city": "http://example.com/api/city/35"
        },
        {
            "url": "http://example.com/api/user/2",
            "name": "Susan Jones",
            "city": "http://example.com/api/city/2"
        }
    ]
}

请注意,用户的 JSON 表示形式包括该用户的 URL,以及城市的 URL。客户不需要知道如何构建这些,因为它们是给它的。

按名称获取城市

城市/api/city/<id>的 URL 是 ,获取城市完整列表的 URL 是/api/cities,正如您定义的那样。

如果您还需要按城市名称搜索城市,您可以扩展“城市”端点来执行此操作。例如,您可以让表单中的 URL/api/cities/<name>返回与给出的搜索词匹配的城市列表<name>

使用 Flask-RESTful,您需要为此定义一个新的Resource子类,例如:

    class CitiesByNameAPI(Resource):
        def __init__(self):
            # ...    
        def get(self, name):
            # ...

    api.add_resource(CitiesByNameAPI, '/api/cities/<name>', endpoint = 'cities_by_name')

获取属于一个城市的所有用户

当客户端请求一个城市时,它应该得到一个响应,其中包含一个 URL 以获取该城市的用户。例如,假设从上面的/api/users响应中我想了解第一个用户所在的城市。所以现在我向 发送一个请求http://example/api/city/35,我得到以下 JSON 响应:

{
    "url": "http://example.com/api/city/35",
    "name": "San Francisco",
    "users": "http://example/com/api/city/35/users"
}

现在我有了这个城市,这给了我一个 URL,我可以用它来获取那个城市的所有用户。

请注意,您的 URL 难看或难以构建并不重要,因为客户端不需要从头开始构建其中的大部分,它只是从服务器获取它们。这也使您能够在将来更改 URL 的格式。

要实现按城市获取用户的 URL,您需要添加另一个Resource子类:

    class UsersByCityAPI(Resource):
        def __init__(self):
            # ...    
        def get(self, id):
            # ...

    api.add_resource(UsersByCityAPI, '/api/cities/<int:id>/users', endpoint = 'users_by_city')

我希望这有帮助!

于 2013-12-21T19:32:47.307 回答
3

您可以在不复制资源的情况下执行 id/name 操作:

api.add_resource(CitiesByNameAPI, '/api/cities/<name_or_id>', endpoint = 'cities_by_name')

class CitiesByNameAPI(Resource):
    def get(self, name_or_id):
        if name_or_id.isdigit():
            city = CityModel.find_by_id(name_or_id)
        else:
            city = CityModel.find_by_name(name_or_id)

        if city:
            return city.to_json(), 200
        return {'error': 'not found'}, 404

不确定这是否有任何负面影响。

于 2018-09-13T22:59:06.860 回答