tag:crieit.net,2005:https://crieit.net/tags/Graphene/feed 「Graphene」の記事 - Crieit Crieitでタグ「Graphene」に投稿された最近の記事 2019-06-20T22:44:28+09:00 https://crieit.net/tags/Graphene/feed tag:crieit.net,2005:PublicArticle/15135 2019-06-20T22:44:28+09:00 2019-06-20T22:44:28+09:00 https://crieit.net/posts/Graphene-Flask-GraphQL-Query-Mutation Graphene + FlaskでGraphQLサーバを作り、QueryとMutationを試す <p>Pythonで<a target="_blank" rel="nofollow noopener" href="https://graphql.org/">GraphQL</a>サーバーを構築する。<br /> GraphQLサーバの構築にはPythonのGraphQLライブラリである<a target="_blank" rel="nofollow noopener" href="https://github.com/graphql-python/graphene">Graphene</a>を使う。</p> <h2 id="GraphQLとは"><a href="#GraphQL%E3%81%A8%E3%81%AF">GraphQLとは</a></h2> <p>GraphQLは、RESTに代わるAPIの仕様だ。<br /> APIは、クライアントがサーバーからデータを取得する方法を定義する。</p> <p>GraphQLクライアントは<strong>GraphQLクエリ言語</strong>(GraphQL query language)によりデータを絞り込み、GraphQLサーバから必要なデータを取得する。<br /> GraphQLサーバは、固定のデータ構造を返す複数のエンドポイントではなく、<strong>単一のエンドポイントのみを公開</strong>し、クライアントが要求したデータを返す。<strong>GraphQLスキーマ言語</strong>(GraphQL schema language)によりデータの追加や変更、型を定義する。PythonやJavaScriptなど様々な言語で実装されたライブラリのいずれかを使い実装する。 この記事ではPythonを使う。</p> <h2 id="GraphQLサーバの実装で必要なこと"><a href="#GraphQL%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E5%AE%9F%E8%A3%85%E3%81%A7%E5%BF%85%E8%A6%81%E3%81%AA%E3%81%93%E3%81%A8">GraphQLサーバの実装で必要なこと</a></h2> <p>GraphQLサーバを作成するためには、次の2つを実装する必要がある。</p> <ol> <li>スキーマを定義する</li> <li>各フィールドのリゾルバ関数を実装する</li> </ol> <h3 id="スキーマを定義する"><a href="#%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B">スキーマを定義する</a></h3> <p>GraphQLスキーマ言語にしたがってスキーマを定義する。<br /> スキーマにはQueryタイプ、Mutatinタイプ、あるいは両方をルートに定義する。<br /> QueryタイプはGraphQLサーバからデータを取得する場合に定義する。<br /> MutatinタイプはGraphQLサーバに対してデータを更新する場合に定義する。<br /> 意味のあるまとまったデータ(ユーザー、とかブログ)に名前がつけたい場合は、データタイプを定義する。<br /> なお、Subscriptionタイプというものもあるが、これを実装したい場合は<a target="_blank" rel="nofollow noopener" href="https://github.com/graphql-python/graphql-ws">graphql-ws</a>という別のライブラリで実装することになる。Subscriptionタイプはこの記事では紹介しない。</p> <p>スキーマの最も単純な具体例は次のような定義だ。<br /> ルートにQueryタイプを定義している。定義されたタイプ内の項目<code>hello</code>はフィールドと呼ばれる。 フィールドには型を指定できる。StringはUTF8の文字を意味する定義済みの型だ。</p> <pre><code>type Query { hello: String } </code></pre> <h3 id="各フィールドのリゾルバ関数を実装する"><a href="#%E5%90%84%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%AE%E3%83%AA%E3%82%BE%E3%83%AB%E3%83%90%E9%96%A2%E6%95%B0%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B">各フィールドのリゾルバ関数を実装する</a></h3> <p><strong>リゾルバ関数</strong>(resolver function)は、1つのフィールドにつき1つ用意する必要のある、データを取得するための関数だ。<br /> GraphQLサーバはクエリを受け取ると、クエリに指定されているフィールドのすべての関数を呼び出す。その結果をクエリに記述された形式にまとめてGraphQLクライアントに返す。</p> <h2 id="AriadneとGraphene"><a href="#Ariadne%E3%81%A8Graphene">AriadneとGraphene</a></h2> <p>PythonにはGraphQLサーバを実装するためのライブラリとして、<a target="_blank" rel="nofollow noopener" href="https://github.com/mirumee/ariadne">Ariadne</a>とGrapheneという2つが用意されている。<br /> AriadneはスキーマファーストでGraphQLサーバーを作成するのに対して、GrapheneはコードファーストでGraphQLサーバーを作成する。</p> <p>スキーマファーストとは、先ほど見たようなスキーマをまずは定義することでGraphQLサーバを実装していく。</p> <pre><code class="py">from ariadne import gql type_defs = gql(""" type Query { hello: String } """) </code></pre> <p>一方で、コードファーストとは<code>type Query { ...}</code>のような記述をせず、その代わりにスキーマの定義を<code>class Query</code>のようにコードで記述していく。</p> <pre><code class="py">from graphene import ObjectType, String class Query(ObjectType): hello = String() </code></pre> <p>この記事ではGraphQLの公式サイトからリンクされているGrapheneを使いコードファーストで実装していく。</p> <h2 id="GrapheneでGraphQLサーバを実装する"><a href="#Graphene%E3%81%A7GraphQL%E3%82%B5%E3%83%BC%E3%83%90%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B">GrapheneでGraphQLサーバを実装する</a></h2> <p>それでは、実際にGrapheneを使ってGraphQLサーバを実装していく。<br /> 名前を与えたら、「こんにちは {名前}さん」と返すようにする。</p> <h3 id="grapheneのインストール"><a href="#graphene%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">grapheneのインストール</a></h3> <p>まずはPython3.7.3で仮想環境を作成する。</p> <pre><code class="sh">$ touch Pipfile $ pipenv --python 3.7.3 $ pipenv shell </code></pre> <p>grapheneをインストールする。</p> <pre><code class="sh">$ pipenv install graphene==2.1.6 </code></pre> <h3 id="Queryの実装"><a href="#Query%E3%81%AE%E5%AE%9F%E8%A3%85">Queryの実装</a></h3> <p>コードは次のようになる。</p> <p><code>hello.py</code></p> <pre><code class="py">from graphene import ObjectType, String, Schema class Query(ObjectType): hello = String(name=String(default_value="名無しさん")) def resolve_hello(self, info, name): return f'こんにちは {name}さん' schema = Schema(query=Query) </code></pre> <p><code>ObjectType</code>を継承したクラスを作成する。(<code>Query</code>てクラス名に縛りはなく、何でもよい)<br /> このクラスには、フィールドとそのフィールドのリゾルバ関数を実装する。<br /> このクラスの属性はフィールドをあらわし、型を表すクラスを代入することで型を定義する。<br /> 型を表すクラスとしてInt、Float、String、Boolean、IDのようなスカラー型やEnum、Listなどが用意されている。<br /> <strong><code>resolve_クラスの属性名</code>はリゾルバ関数</strong>をあらわす。<br /> リゾルバ関数の引数の説明はこちら。<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers">https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers</a></p> <p>つまり、<code>hello</code>はフィールドで<code>String</code>型であり、<code>resolve_hello</code>は<code>hello</code>フィールドのリゾルバ関数である。<br /> 作成した<code>ObjectType</code>を継承したクラスは<code>Schema</code>クラスに<code>query=</code>の形で渡す。これでルートにQueryタイプを定義したことになる。</p> <p>さて、定義したスキーマに対してクエリを実行したい。<br /> <code>schema.execute()</code>でクエリを実行できる。</p> <p><code>hello.py</code></p> <pre><code class="py">query_string = '{ hello }' result = schema.execute(query_string) print(result.data['hello']) query_string_with_argument = '{ hello (name: "なんしー") }' result = schema.execute(query_string_with_argument) print(result.data['hello']) </code></pre> <p>実行するとクエリで問い合わせた結果が取得できる。</p> <pre><code class="sh">$ python hello.py こんにちは 名無しさんさん こんにちは なんしーさん </code></pre> <h3 id="GraphQL APIをウェブアプリケーション(Flask)で提供する"><a href="#GraphQL+API%E3%82%92%E3%82%A6%E3%82%A7%E3%83%96%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%28Flask%29%E3%81%A7%E6%8F%90%E4%BE%9B%E3%81%99%E3%82%8B">GraphQL APIをウェブアプリケーション(Flask)で提供する</a></h3> <p>GraphQL APIをサーバーで完結させるのではなく、ブラウザから呼び出せるようにする。<br /> Flaskと<a target="_blank" rel="nofollow noopener" href="https://github.com/graphql-python/flask-graphql">Flask-GraphQL</a>というGraphQLと連携するライブラリをインストールする。</p> <pre><code class="sh">$ pipenv install Flask==1.0.3 $ pipenv install Flask-GraphQL==2.0.0 </code></pre> <p>Flaskの<code>add_url_rule</code>メソッドで<code>/graphql</code>にアクセスするとGraph GraphQLからデータを取得できるようにする。</p> <p><code>app.py</code></p> <pre><code class="py">from flask import Flask from flask_graphql import GraphQLView from hello import schema app = Flask(__name__) app.debug = True app.add_url_rule( '/graphql', view_func=GraphQLView.as_view( 'graphql', schema=schema, graphiql=True # GraphiQLを表示 ) ) if __name__ == '__main__': app.run() </code></pre> <p><code>python app.py</code>でサーバーを立ち上げる。</p> <pre><code class="sh">$ python app.py ... * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) </code></pre> <p><code>http://127.0.0.1:5000/graphql</code>を開くとGraphQLをGUIで操作できるGraphiQLが表示される。<br /> 左の窓に<code>{ hello }</code>と入力して、実行ボタンを押すと、右側の窓に結果が返ってくる!<br /> <a href="https://crieit.now.sh/upload_images/d2d7965ad9fc71a83e2ad7eb98aeadce5d0b8d66c59f6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d2d7965ad9fc71a83e2ad7eb98aeadce5d0b8d66c59f6.png?mw=700" alt="引数なしのクエリ結果" /></a><br /> <code>hello (name: "なんしー")</code>と引数付きで実行すると、それに対応した値が返ってくる。<br /> <a href="https://crieit.now.sh/upload_images/ca6e36d8fb4f85e6956229c6a55b90c95d0b8d850f991.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ca6e36d8fb4f85e6956229c6a55b90c95d0b8d850f991.png?mw=700" alt="引数ありのクエリ結果" /></a></p> <h3 id="Mutationの実装"><a href="#Mutation%E3%81%AE%E5%AE%9F%E8%A3%85">Mutationの実装</a></h3> <p>Queryの確認ができたので、次はMutationを実装する。<br /> ユーザーの名前、年齢を渡したら、ID、変更された名前、変更された年齢が返ってくるようにする。<br /> <code>graphene.Mutation</code>を継承したクラスを作成する。<br /> このクラスの属性が戻り値になる。<br /> <code>Arguments</code>クラスの属性は引数になる。<br /> そして、<code>mutate</code>メソッドで<code>graphene.Mutation</code>を継承したクラスのインスタンスを返す。<br /> 本来<code>mutate</code>メソッドの中でDBへの永続化などの処理を書く。</p> <pre><code class="py">from graphene import ObjectType, String, Schema, Mutation, ID, Int class CreatePerson(Mutation): # created_id、created_name、created_ageが戻り値 created_id = ID() created_name = String() created_age = Int() # name, ageが引数 class Arguments: name = String() age = Int() # mutateメソッドの引数はself,infoの次に引数が並ぶ def mutate(self, info, name, age): # ここで永続化する # Mutationを継承したクラスをインスタンス化して返す return CreatePerson( created_id=1, created_name=f'{name}さん', created_age=age + 10, ) class MyMutation(ObjectType): create_person = CreatePerson.Field() schema = Schema(query=Query, mutation=MyMutation) </code></pre> <p>次のように問い合わせると、</p> <pre><code>mutation { createPerson(name:"加藤", age: 15) { createdId createdAge createdName } } </code></pre> <p><code>mutate</code>メソッドで変更を加えた通り、年齢が加算され、名前にさんがついて返ってくる。</p> <pre><code>{ "data": { "createPerson": { "createdId": "1", "createdAge": 25, "createdName": "加藤さん" } } } </code></pre> <p><a href="https://crieit.now.sh/upload_images/73a77a40e79d078cf2207506b8a720d65d0b8d9b60c49.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/73a77a40e79d078cf2207506b8a720d65d0b8d9b60c49.png?mw=700" alt="mutate結果" /></a></p> <p>参考<br /> <a target="_blank" rel="nofollow noopener" href="https://graphql.org/">https://graphql.org/</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.howtographql.com/">https://www.howtographql.com/</a><br /> <a target="_blank" rel="nofollow noopener" href="https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000">https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000</a><br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/graphql-python/graphene/tree/master/examples">https://github.com/graphql-python/graphene/tree/master/examples</a><br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/howtographql/graphql-python/blob/master/hackernews/links/schema.py">https://github.com/howtographql/graphql-python/blob/master/hackernews/links/schema.py</a></p> nancy