tag:crieit.net,2005:https://crieit.net/users/nan_system/feed
nancyの投稿 - Crieit
Crieitでユーザーnancyによる最近の投稿
2019-08-10T20:58:35+09:00
https://crieit.net/users/nan_system/feed
tag:crieit.net,2005:PublicArticle/15314
2019-08-10T20:58:35+09:00
2019-08-10T20:58:35+09:00
https://crieit.net/posts/Flask-Nuxt-js-spa-axios-CSV-multipart-form-data
Flask + Nuxt.js(spa) + axiosでCSVファイルをmultipart/form-dataによりアップロードする
<p>Flask + Nuxt.js + axiosでCSVファイルをmultipart/form-dataによりアップロードする。なお、Nuxt.jsのモードは<code>spa</code>にしている。<br />
FlaskとNuxt.jsの連携を確認したいので、バリデーションやエラーのハンドリングはほとんどしない。<br />
まずFlaskでファイルのアップロードを確認し、その後Nuxt.jsでファイルをアップロードしていく。</p>
<h2 id="FlaskでCSVファイルをmultipart/form-dataによるアップロード(POST)を受けつける"><a href="#Flask%E3%81%A7CSV%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92multipart%2Fform-data%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%28POST%29%E3%82%92%E5%8F%97%E3%81%91%E3%81%A4%E3%81%91%E3%82%8B">FlaskでCSVファイルをmultipart/form-dataによるアップロード(POST)を受けつける</a></h2>
<h3 id="Flaskをインストールする"><a href="#Flask%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B">Flaskをインストールする</a></h3>
<p><code>pipenv</code>で<code>Flask</code>が動く環境を作る。</p>
<pre><code class="sh">$ touch Pipfile
$ pipenv --python 3.7.4
$ pipenv install Flask==1.1.1
</code></pre>
<p><code>Flask</code>が動くことを次のコードで確認する。</p>
<p><code>app.py</code></p>
<pre><code class="py">from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'hello'
if __name__ == "__main__":
app.run(debug=True)
</code></pre>
<p>次のコマンドで起動することで、オートリロードを有効にする。<br />
<code>FLASK_ENV=development flask run</code></p>
<pre><code class="sh">$ FLASK_ENV=development flask run
...(略)
Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
</code></pre>
<p><code>curl</code>で<code>/</code>にアクセスし、<code>hello</code>がレスポンスで返ってくることを確認する。</p>
<pre><code class="sh">$ curl http://127.0.0.1:5000/
hello
</code></pre>
<h3 id="FlaskでPOSTを受けつけるようにする"><a href="#Flask%E3%81%A7POST%E3%82%92%E5%8F%97%E3%81%91%E3%81%A4%E3%81%91%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">FlaskでPOSTを受けつけるようにする</a></h3>
<p><code>@app.route</code>の<code>methods</code>で<code>POST</code>を受け付けるようにする。</p>
<p><code>app.py</code></p>
<pre><code class="py">@app.route("/api/upload", methods=["POST"])
def upload():
return 'アップロード成功'
</code></pre>
<p><code>curl</code>のオプション<code>-X HTTPメソッド</code>によりPOSTでレスポンスが返ってくることを確かめる。</p>
<pre><code class="sh">$ curl -X POST http://127.0.0.1:5000/api/upload
アップロード成功
</code></pre>
<h3 id="Flaskでmultipart/form-dataのファイルを受けつける"><a href="#Flask%E3%81%A7multipart%2Fform-data%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E5%8F%97%E3%81%91%E3%81%A4%E3%81%91%E3%82%8B">Flaskでmultipart/form-dataのファイルを受けつける</a></h3>
<p><code>app.config['UPLOAD_FOLDER']</code>にアップロード先のディレクトリを指定しておく。<br />
<code>multipart/form-data</code>によりアップロードされたファイルは<code>request.files</code>に格納されている。<br />
<code>request.files[フィールド名]</code>からファイルを<code>FileStorage</code>オブジェクトとして取得する。<br />
<code>FileStorage</code>オブジェクトは<code>save</code>メソッドを持っており、このメソッドでファイルを保存することができる。<br />
保存時は<code>secure_filename</code>関数で安全なファイル名にする。</p>
<pre><code class="py">import os
from flask import Flask, request
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = './uploads'
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route("/api/upload", methods=["POST"])
def upload():
fileStorageObj = request.files['file']
filename = secure_filename(fileStorageObj.filename)
fileStorageObj.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return 'アップロード成功'
if __name__ == "__main__":
app.run(debug=True)
</code></pre>
<p>テスト用のダミーCSVを用意する。</p>
<p><code>dummy.csv</code></p>
<pre><code class="csv">name,age,pref
田原 唯菜,56,香川県
伊沢 圭一,57,鳥取県
鈴村 良夫,6,千葉県
</code></pre>
<p>このファイルを<code>curl</code>でアップロードする。<br />
<code>multipart/form-data</code>は<code>-F フィールド名=値</code>で指定する。ファイルの場合は<code>@ファイル名</code>で指定する。</p>
<pre><code class="sh">$ curl -X POST -F [email protected] http://127.0.0.1:5000/api/upload
アップロード成功
</code></pre>
<p><code>uploads</code>ディレクトリにファイルが格納されていることが確認できる。</p>
<h3 id="ファイルと一緒にテキストもリクエストする"><a href="#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%A8%E4%B8%80%E7%B7%92%E3%81%AB%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%82%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B">ファイルと一緒にテキストもリクエストする</a></h3>
<p>ファイルと一緒にテキストもリクエストできることを確認しておく。</p>
<p>ファイルの取得は以下の形で行うが、</p>
<pre><code class="py">request.files['file']
</code></pre>
<p>ファイル以外の取得は<code>form[フィールド名]</code>で取得する。</p>
<pre><code class="py">request.form['text']
</code></pre>
<p>このフィールドを<code>print</code>で表示できるようにし、<code>curl</code>で確認する。</p>
<pre><code class="sh">$ curl -X POST -F [email protected] -F text="大切なCSVです" http://127.0.0.1:5000/api/upload
</code></pre>
<h3 id="レスポンスをJSONにする"><a href="#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92JSON%E3%81%AB%E3%81%99%E3%82%8B">レスポンスをJSONにする</a></h3>
<p><code>Nuxt.js</code>の<code>axios</code>で扱うためレスポンスをJSONにする。<br />
<code>JSON_AS_ASCII</code>を設定して、日本語が文字化けしないようにする。</p>
<p><code>app.config['JSON_AS_ASCII'] = False</code></p>
<p>戻り値は<code>jsonify(オブジェクト)</code>としてJSONで返せるようにする。</p>
<p>エラーの場合は<code>abort(400, {'message': 'ファイルは必須です'})</code>を呼び出し、<br />
<code>@app.errorhandler(400)</code>でエラーをハンドリングする。</p>
<p><code>app.py</code></p>
<pre><code class="py">import os
from flask import Flask, request, jsonify, abort
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = './uploads'
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['JSON_AS_ASCII'] = False
@app.route("/api/upload", methods=["POST"])
def upload():
print(request.form['text'])
if 'file' not in request.files:
return abort(400, {'message': 'ファイルは必須です'})
fileStorageObj = request.files['file']
filename = secure_filename(fileStorageObj.filename)
fileStorageObj.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return jsonify({'message': 'アップロード成功'})
@app.errorhandler(400)
def custom400(error):
return jsonify({'message': error.description['message']})
if __name__ == "__main__":
app.run(debug=True)
</code></pre>
<p>ファイル、テキストを指定した場合は<code>アップロード成功</code>と返ってくる。</p>
<pre><code class="sh">$ curl -X POST -F [email protected] -F text="大切なCSVです" http://127.0.0.1:5000/api/upload
{
"message": "アップロード成功"
}
</code></pre>
<p>また、ファイルをあえて指定しない場合は<code>ファイルは必須です</code>とエラーハンドリング通り返ってくる。</p>
<pre><code class="sh">$ curl -X POST -F text="大切なCSVです" http://127.0.0.1:5000/api/upload
{
"message": "ファイルは必須です"
}
</code></pre>
<p>ここまででFlaskアプリケーションの実装は終わり。</p>
<h2 id="Nuxt.jsでCSVファイルをmultipart/form-dataによりアップロード(POST)する"><a href="#Nuxt.js%E3%81%A7CSV%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92multipart%2Fform-data%E3%81%AB%E3%82%88%E3%82%8A%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%28POST%29%E3%81%99%E3%82%8B">Nuxt.jsでCSVファイルをmultipart/form-dataによりアップロード(POST)する</a></h2>
<p>つぎはNuxt.jsの実装を進めていく。</p>
<p><code>npx create-nuxt-app プロジェクト名</code>でプロジェクトを作成する。<br />
<code>Axios</code>をインストールし、<code>mode</code>を<code>Single Page App</code>にする。他の設定項目はデフォルトのままにした。</p>
<pre><code class="sh">$ npx create-nuxt-app ui
? Choose features to install Axios
? Choose rendering mode Single Page App
</code></pre>
<p><code>nuxt.config.js</code>で<code>axios</code>のオプション<code>proxy</code>を<code>true</code>とし、<code>proxy</code>で<code>api</code>にアクセスがきたらFlaskに渡すよう設定する。</p>
<p><code>nuxt.config.js</code></p>
<pre><code class="js"> axios: {
proxy: true
},
proxy: {
'/api/': 'http://127.0.0.1:5000/',
},
</code></pre>
<p><code>npm run dev</code>で起動し、<code>proxy</code>の設定を確認する。</p>
<pre><code class="sh">$ npm run dev
...略
Listening on: http://localhost:3000/
</code></pre>
<p><code>Nuxt.js</code>は3000番ポートで起動するので、<code>http://localhost:3000/api/upload</code>に向けてcurlでPOSTする。<br />
今まで通りレスポンスが返ってきている。</p>
<pre><code class="sh">$ curl -l -X POST -F [email protected] -F text="大切なCSVです" http://localhost:3000/api/upload
{
"message": "アップロード成功"
}
</code></pre>
<p>あとはコンポーネントを作れば良い。<br />
<code>template</code>部分は、テキストは<code>v-model</code>で入力をうけつける。<br />
ファイルは<code>type="file"</code>としたうえで、<code>@change</code>イベントでファイルが選択されたときのハンドリングをする。 <code>event.target.files[0]</code>からファイルを取得し、<code>data</code>に保持しておく。<br />
アップロードするボタンが押されたら、<code>multipart/form-data</code>でPOSTするために<code>new FormData()</code>に対して<code>text</code>、<code>file</code>を<code>append</code>する。<br />
<code>this.$axios.post(URL, formData, options)</code>でPOSTする。<br />
第一引数にURLを指定し、第二引数に<code>formData</code>を指定する。第三引数の<code>options</code>でHTTPヘッダーに<code>multipart/form-data</code>を指定する。<br />
<code>index.vue</code></p>
<pre><code class="vue"><template>
<section class="container">
<form>
<input v-model="text" />
<input type="file" @change="onChange" />
<button type="button" @click="onSubmit">アップロードする</button>
</form>
</section>
</template>
<script>
export default {
data() {
return {
text: '',
file: null,
}
},
methods: {
onChange(event) {
this.file = event.target.files[0]
},
async onSubmit() {
const formData = new FormData()
formData.append('text', this.text)
formData.append('file', this.file)
const response = await this.$axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).catch(error => {
return error.response
})
console.info(response.data.message)
}
}
}
</script>
</code></pre>
<p>画面からテキストを入力し、画像を選択して「アップロードする」するボタンを押すと<code>uplodas</code>ディレクトリにファイルがアップロードされ、ブラウザのコンソールにメッセージが表示される。</p>
<pre><code>info アップロード成功
</code></pre>
<p>また、画像を選択しなかった場合、400エラーとなり、エラーメッセージが取得できる。</p>
<pre><code>POST http://localhost:3000/api/upload 400 (BAD REQUEST)
info ファイルは必須です
</code></pre>
<h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/?highlight=upload">https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/?highlight=upload</a><br />
<a target="_blank" rel="nofollow noopener" href="http://tm-webtools.com/Tools/TestData">http://tm-webtools.com/Tools/TestData</a><br />
<a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/21294889/how-to-get-access-to-error-message-from-abort-command-when-using-custom-error-ha">https://stackoverflow.com/questions/21294889/how-to-get-access-to-error-message-from-abort-command-when-using-custom-error-ha</a><br />
<a target="_blank" rel="nofollow noopener" href="https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/#werkzeug.datastructures.ImmutableMultiDict">https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/#werkzeug.datastructures.ImmutableMultiDict</a><br />
<a target="_blank" rel="nofollow noopener" href="https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/#werkzeug.datastructures.FileStorage">https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/#werkzeug.datastructures.FileStorage</a></p>
nancy
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