web-dev-qa-db-ja.com

Flask RESTful APIの複数および複雑なエンドポイント

Flask-RESTful APIで、ユーザーと都市の2つのオブジェクトがあるとします。これは1対多の関係です。 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')
_

ご覧のとおり、非常に基本的な機能を実装するために必要なことはすべて実行できます。両方のオブジェクトを取得、投稿、配置、および削除できます。ただし、私の目標は2つあります。

(1)単なる都市IDではなく、都市名などの他のパラメーターで要求できるようにする。次のようになります。
api.add_resource(CityAPI, '/api/city/<string:name>', endpoint='city')
ただし、このエラーはスローされません。

AssertionError:ビュー関数マッピングは既存のエンドポイント関数を上書きしています

(2)リクエストで2つのリソースを結合できるようにする。ある都市に関連付けられているすべてのユーザーを取得したいとします。 REST URLsでは、次のようになります。
_/api/cities/<int:id>/users_

Flaskでこれを行うにはどうすればよいですか?どのエンドポイントにマップしますか?

基本的に、APIを基本から使用可能にする方法を探しています。アイデア/アドバイスをありがとう

29
Alex Chumbley

あなたは2つの間違いを犯しています。

まず、Flask-RESTfulは、リソースが単一のURLで実装されていると考えるように導きます。実際には、同じタイプのリソースを返す多くの異なるURLを持つことができます。 Flask-RESTfulでは、URLごとに異なるResourceサブクラスを作成する必要がありますが、概念的にはこれらのURLは同じリソースに属します。実際には、リストと個々のリクエストを処理するために、リソースごとにすでに2つのインスタンスを作成していることに注意してください。

2つ目の間違いは、クライアントが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が含まれていることに注意してください。クライアントはこれらのビルド方法を知っている必要はありません。

名前で都市を取得する

都市のURLは/api/city/<id>であり、都市の完全なリストを取得するURLは、定義したとおり/api/citiesです。

名前で都市を検索する必要がある場合は、「都市」エンドポイントを拡張してそれを行うことができます。たとえば、/api/cities/<name>という形式のURLを使用して、<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')

これがお役に立てば幸いです!

64
Miguel

リソースを複製せずに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

これによる悪影響があるかどうかはわかりません。

1
lciamp