Python Flask RESTful API 多个复杂端点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20715238/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 21:03:47  来源:igfitidea点击:

Flask RESTful API multiple and complex endpoints

pythonrestapiflaskflask-restful

提问by Alex Chumbley

In my Flask-RESTful API, imagine I have two objects, users and cities. It is a 1-to-many relationship. Now when I create my API and add resources to it, all I can seem to do is map very easy and general URLs to them. Here is the code (with useless stuff not included):

在我的 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')

As you can see, I can do everything I want to implement very basic functionality. I can get, post, put, and delete both objects. However, my goal is two-fold:

如您所见,我可以做任何我想做的事情来实现非常基本的功能。我可以获取、发布、放置和删除这两个对象。但是,我的目标有两个:

(1) To be able to request with other parameters like city name instead of just city id. It would look something like:
api.add_resource(CityAPI, '/api/city/<string:name>', endpoint='city')
except it wouldn't throw me this error:

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

AssertionError: View function mapping is overwriting an existing endpoint function

断言错误:视图函数映射正在覆盖现有的端点函数

(2) To be able to combine the two Resources in a Request. Say I wanted to get all the users associated with some city. In REST URLs, it should look something like:
/api/cities/<int:id>/users

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

How do I do that with Flask? What endpoint do I map it to?

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

Basically, I'm looking for ways to take my API from basic to useable. Thanks for any ideas/advice

基本上,我正在寻找将我的 API 从基本变为可用的方法。感谢您的任何想法/建议

采纳答案by Miguel

Your are making two mistakes.

你犯了两个错误。

First, Flask-RESTful leads you to think that a resource is implemented with a single URL. In reality, you can have many different URLs that return resources of the same type. In Flask-RESTful you will need to create a different Resourcesubclass for each URL, but conceptually those URLs belong to the same resource. Note that you have, in fact, created two instances per resource already to handle the list and the individual requests.

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

The second mistake that you are making is that you expect the client to know all the URLs in your API. This is not a good way to build APIs, ideally the client only knows a few top-level URLs and then discovers the rest from data in the responses from the top-level ones.

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

In your API you may want to expose the /api/usersand /api/citiesas top-level APIs. The URLs to individual cities and users will be included in the responses. For example, if I invoke http://example.com/api/usersto get the list of users I may get this response:

在您的 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"
        }
    ]
}

Note that the JSON representation of a user includes the URL for that user, and also the URL for the city. The client does not need to know how to build these, because they are given to it.

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

Getting cities by their name

按名称获取城市

The URL for a city is /api/city/<id>, and the URL to get the complete list of cities is /api/cities, as you have it defined.

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

If you also need to search for cities by their name you can extend the "cities" endpoint to do that. For example, you could have URLs in the form /api/cities/<name>return the list of cities that match the search term given as <name>.

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

With Flask-RESTful you will need to define a new Resourcesubclass for that, for example:

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

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

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

Getting all the users that belong to a city

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

When the client asks for a city it should get a response that includes a URL to get the users in that city. For example, let's say that from the /api/usersresponse above I want to find out about the city of the first user. So now I send a request to http://example/api/city/35, and I get back the following JSON response:

当客户端询问一个城市时,它应该得到一个响应,其中包含一个 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"
}

Now I have the city, and that gave me a URL that I can use to get all the users in that city.

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

Note that it does not matter that your URLs are ugly or hard to construct, because the client never needs to build most of these from scratch, it just gets them from the server. This also enables you to change the format of the URLs in the future.

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

To implement the URL that gets users by city you add yet another Resourcesubclass:

要实现按城市获取用户的 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')

I hope this helps!

我希望这有帮助!

回答by lciamp

you can do the id/name thing without duplicating the resource:

您可以在不复制资源的情况下执行 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

not sure if there are any negative effects from this.

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