Flask RESTful API несколько и сложных конечных точек

в моем API-интерфейсе Flask-RESTful представьте, что у меня есть два объекта: пользователи и города. Это отношения "один ко многим". Теперь, когда я создаю свой 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')

как вы можете видеть, я могу сделать все, что я хочу, чтобы реализовать базовую функциональность. Я могу получить, опубликовать, поместить и удалить оба объекта. Однако моя цель двоякая:

(1) Чтобы иметь возможность запрашивать с другими параметрами, такими как название города, а не просто city id. Это будет выглядеть примерно так:
api.add_resource(CityAPI, '/api/city/<string:name>', endpoint='city')
кроме того, он не бросил бы мне эту ошибку:

AssertionError: отображение функции просмотра перезаписывает существующий функция конечной точки

(2), чтобы иметь возможность объединить два ресурса в запросе. Скажи, что я хотел получить все пользователи, связанные с каким-то городом. В REST URLs это должно выглядеть что-то например:
/api/cities/<int:id>/users

как это сделать с фляжкой? К какой конечной точке я ее сопоставляю?

В основном, я ищу способы взять мой API от basic до useable. Спасибо за любые идеи/советы

2 ответов


Вы делаете две ошибки.

во-первых, Flask-RESTful заставляет вас думать, что ресурс реализован с одним URL-адресом. На самом деле у вас может быть много разных URL-адресов, возвращающих ресурсы одного типа. В Flask-RESTful вам нужно будет создать другой Resource подкласс для каждого URL, но концептуально эти URL принадлежат одному и тому же ресурсу. Обратите внимание, что фактически вы уже создали два экземпляра для каждого ресурса для обработки списка и отдельного запросы.

вторая ошибка, которую вы делаете, заключается в том, что вы ожидаете, что клиент будет знать все URL-адреса в вашем API. Это не очень хороший способ создания API, в идеале клиент знает только несколько URL верхнего уровня, а затем обнаруживает остальные данные в ответах с верхнего уровня.

в вашем API вы можете выставить /api/users и /api/cities как API верхнего уровня. URL-адреса отдельных городов и пользователей будут включены в ответы. Например, если я invoke 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 для города. Клиенту не нужно знать, как их построить, потому что они ему даны.

получение городов по их названию

URL для города является /api/city/<id>, и URL, чтобы получить полный список городов является /api/cities, как вы его определили.

если вы также нужно искать города по их названию вы можете расширить конечную точку "города", чтобы сделать это. Например, у вас могут быть URL-адреса в форме /api/cities/<name> верните список городов, которые соответствуют поисковому термину, заданному как <name>.

с фляжкой-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')

надеюсь, это поможет!


вы можете сделать 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()
        else:
            city = CityModel.find_by_name()

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

Не уверен, есть ли какие-либо негативные последствия от этого.