Подтвердить что ты не робот

Многократные и сложные конечные точки API RESTful API

Представьте, что в моем 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')

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

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

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

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

Как мне это сделать с Flask? Какую конечную точку мне сопоставить?

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

4b9b3361

Ответ 1

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

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

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

Получение городов по их имени

URL для города /api/city/<id>, а 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')

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

Ответ 2

Вы можете сделать идентификатор/имя, не дублируя ресурс:

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

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