Python Flask RESTful API:多个复杂端点

Python Flask RESTful API:多个复杂端点,python,rest,flask,flask-restful,restapi,Python,Rest,Flask,Flask Restful,Restapi,在我的Flask RESTful API中,假设我有两个对象,用户和城市。这是一种一对多的关系。现在,当我创建API并向其添加资源时,我所能做的似乎就是将非常简单和通用的URL映射到它们。下面是代码(不包括无用的东西): class UserAPI(资源):#处理单个用户的API类 定义初始化(自): #初始化 def get(自我,id): #获取请求 def put(自我,id): #提出请求 def delete(自我,id): #删除请求 类UserListAPI(资源):#处理整个用户

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

class UserAPI(资源):#处理单个用户的API类
定义初始化(自):
#初始化
def get(自我,id):
#获取请求
def put(自我,id):
#提出请求
def delete(自我,id):
#删除请求
类UserListAPI(资源):#处理整个用户组的API类
定义初始化(自):
def get(自我):
def post(自我):
api.add_资源(UserAPI,'/api/user/',endpoint='user')
api.add_资源(UserListAPI,“/api/users/”,endpoint='users')
CityAPI类(资源):
定义初始化(自):
def get(自我,id):
def put(自我,id):
def delete(自我,id):
类CityListAPI(资源):
定义初始化(自):
def get(自我):
def post(自我):
api.add_资源(CityListAPI,“/api/cities/”,endpoint='cities')
api.add_资源(CityAPI,“/api/city/”,endpoint='city')
正如您所看到的,我可以做任何我想实现的基本功能。我可以获取、发布、放置和删除这两个对象。然而,我的目标有两个:

(1) 能够使用其他参数(如城市名称)进行请求,而不仅仅是 城市id。它看起来像:
api.add_资源(CityAPI,“/api/city/”,endpoint='city')

但它不会让我犯这样的错误:

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

(2) 能够在一个请求中组合这两种资源。说我想得到所有的 与某个城市关联的用户。在RESTURL中,它应该类似于:
/api/cities//用户

我该怎么用烧瓶呢?我应该将其映射到哪个端点


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

您犯了两个错误

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

您犯的第二个错误是,您希望客户机知道API中的所有URL。这不是构建API的好方法,理想情况下,客户端只知道几个顶级URL,然后从顶级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"
        }
    ]
}
{
    "url": "http://example.com/api/city/35",
    "name": "San Francisco",
    "users": "http://example/com/api/city/35/users"
}
请注意,用户的JSON表示包括该用户的URL,以及该城市的URL。客户不需要知道如何构建它们,因为它们是为客户提供的

以城市的名字命名 城市的URL是
/api/city/
,获取完整城市列表的URL是
/api/cities
,正如您所定义的那样

如果还需要按城市名称搜索城市,则可以扩展“cities”端点来完成此操作。例如,您可以使用
/api/cities/
格式的URL返回与搜索词匹配的城市列表,如

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

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

    api.add_resource(CitiesByNameAPI, '/api/cities/<name>', endpoint = 'cities_by_name')
现在我有了城市,这给了我一个URL,我可以用它来获取该城市的所有用户

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

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

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

    api.add_resource(UsersByCityAPI, '/api/cities/<int:id>/users', endpoint = 'users_by_city')
class UsersByCityAPI(资源):
定义初始化(自):
# ...    
def get(自我,id):
# ...
api.add_资源(UsersByCityAPI,'/api/cities//users',endpoint='users_by_city')

我希望这有帮助

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

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
api.add_资源(CitiesByNameAPI,/api/cities/,endpoint='cities_by_name')
类别CitiesByNameAPI(资源):
def get(自我、姓名或id):
如果名称\u或\u id.isdigit():
city=CityModel.find\u by\u id(name\u或\u id)
其他:
city=CityModel。按名称(名称或id)查找
如果城市:
返回city.to_json(),200
返回{'error':'notfound'},404

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

太好了,谢谢你的修复。另外,感谢您建议隐藏我的大部分API虽然Miguel几乎完全正确,但我认为有必要指出,“[希望客户端知道API中的所有URL]并不是构建API的好方法”只是对这一问题的一种看法。预先定义URL不会导致语法错误,也不会导致应用程序崩溃;这是一个设计决策。是使用已定义的URL列表更好,还是只使用带有指向API子级别URL的顶级API(或其他解决方案)更好,仍有待讨论,正确答案可能仍然是特定于项目的。@MarkHildreth:同意。有趣的话题。你会认为只有我计划使用这个API会有所不同吗?因此,如果此API用于我不打算向其他人公开的移动应用程序,
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