tag:crieit.net,2005:https://crieit.net/tags/Flask/feed 「Flask」の記事 - Crieit Crieitでタグ「Flask」に投稿された最近の記事 2022-04-09T22:40:57+09:00 https://crieit.net/tags/Flask/feed tag:crieit.net,2005:PublicArticle/18161 2022-04-09T22:40:57+09:00 2022-04-09T22:40:57+09:00 https://crieit.net/posts/metnal 気分が落ち込んだ時に、自動で周囲の人に通知するアプリを作りました <p>こんにちは!</p> <p>「<a target="_blank" rel="nofollow noopener" href="https://metnal.stock-stu.com/">metnal</a>」というアプリを作りましたので、ご紹介させてください。</p> <h2 id="metnalとは"><a href="#metnal%E3%81%A8%E3%81%AF">metnalとは</a></h2> <p>metnelは、「気分が落ち込んだ時に、自動で家族や友人、パートナーなどに通知することで、周囲の人の助けを受けることをサポートする」アプリです。</p> <p>特徴として、<br /> 1. 手軽に始められるように、LINEでのみログイン可能。メールアドレスやパスワードでアカウントを管理すると、アカウントを紛失したり、ログインが面倒になったりすることで、離脱してしまうリスクがあります(多くの人がアカウントを紛失した経験があると思います笑)。<br /> 2. 機能は非常にシンプル。その時の気分をつけるだけで、独自のスコアリングに基づき、自動で気分のスコアが表示されます。<br /> 3. 周囲の人に、気分のスコアを共有できます。また周囲の人が登録することで、スコアが極端に低くなった時(気分が落ち込んだ時)にLINEのbotを通じて自動で通知されます。</p> <p>となっております。<br /> このアプリが十分に機能した場合、周囲の人の落ち込みを即座に発見できることが期待されます。</p> <h3 id="利用方法"><a href="#%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95">利用方法</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://stock-stu.com/introduce_metnal/#howtouse">こちら</a>をご覧ください!</p> <h3 id="利用技術等"><a href="#%E5%88%A9%E7%94%A8%E6%8A%80%E8%A1%93%E7%AD%89">利用技術等</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/StStu/items/11dabe66a2f92665d551#使用技術等">qiita</a>にまとめてみました。<br /> 一読して頂ければ幸いです!</p> nyonyataro tag:crieit.net,2005:PublicArticle/16692 2021-02-20T11:18:40+09:00 2021-02-20T11:21:48+09:00 https://crieit.net/posts/python-library-flask-flask-on-vs-code FlaskをVisual Studio Codeで動かしデバックする <p>Pythonの人気のWebアプリケーションフレームワークFlaskをVisual Studio Codeで動かす方法です。</p> <h2 id="Flask用のPython仮想環境を構築"><a href="#Flask%E7%94%A8%E3%81%AEPython%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%82%92%E6%A7%8B%E7%AF%89">Flask用のPython仮想環境を構築</a></h2> <p>Visual Studio Codeで<code>Ctrl + Shift + @</code>、<br /> あるいはメニューの<code>ターミナル(T) > 新しいターミナル</code>でターミナルを表示する。<br /> 環境を作りたいディレクトリに移動して、以下のコマンドを実行すると"myflask"の名称でPythonの仮想環境が作られる。<br /> (myflaskの名称でディレクトリが作成され、中にライブラリやpythonのexeが作成される)</p> <pre><code class="python">python -m venv myflask </code></pre> <p><code>Ctrl + Shift + P</code>でコマンドパレットを表示し、"interpreter"と入力し、Pythonインタープリターを選択、<br /> 作成した仮想環境が表示されいればそれを、なければ<code>.\myflask\Scripts\python.exe</code>のように入力する。</p> <p>改めて<code>Ctrl + Shift + P</code>でコマンドパレットを表示し、<code>Python Create Terminal</code>と入力する。<br /> ターミナルが立ち上がり(myflask)と先頭に表示されていれば、<br /> 仮想環境のPythonが叩けます。</p> <p>(ターミナルで<code>.\myflask\Scripts\Activate.ps1</code>としても仮想環境への切り替えが行なえます。)</p> <h2 id="Flaskのインストール"><a href="#Flask%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Flaskのインストール</a></h2> <p>Pythonの仮想環境でFlaskのインストールを行う。</p> <pre><code class="shell">pip install flask </code></pre> <p>自分の環境だと以下のエラーがでたので指示に従ってpipをアップグレードしてから再度インストールしました。</p> <pre><code class="text">WARNING: You are using pip version 20.1.1; however, version 21.0.1 is available. You should consider upgrading via the 'c:\path\src\myflask\scripts\python.exe -m pip install --upgrade pip' command. </code></pre> <pre><code class="shell">c:\path\src\myflask\scripts\python.exe -m pip install --upgrade pip pip install flask </code></pre> <p>動作確認用にapp.pyを以下のように作成しておきます。</p> <pre><code class="python"># app.py from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' </code></pre> <h3 id="Python FlaskのVS Codeデバッグ環境"><a href="#Python+Flask%E3%81%AEVS+Code%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E7%92%B0%E5%A2%83">Python FlaskのVS Codeデバッグ環境</a></h3> <p><code>Ctrl + Shift + D</code>あるいは、虫に三角形のマークからデバッグのメニューを表示し、"実行とデバッグ"をクリックします。<br /> 選択肢は"Python Flask"を選びます。</p> <p>Flaskのファイルがrun.pyのようにapp.pyと異なる場合は、launch.jsonを適宜編集し、<br /> FLASK_APPにファイル名を指定しておきます。</p> <pre><code class="javascript">"env": { "FLASK_APP": "run.py", }, </code></pre> <p>デバッグ実行は緑色の三角ボタンで実行できます。<br /> ソースコード上行数の左をクリック、あるいは、F9でブレークポイントを指定します。</p> <p>コマンドでFlaskを立ち上げる場合は以下のようにします。</p> <pre><code class="powershell">$env:FLASK_APP = "run.py" flask run </code></pre> <p>(コマンドプロンプトでなくPowerShellの場合<br /> <code>set FLASK_APP=run.py</code>でなく上記でないと動かないようです)</p> maru3kaku4kaku tag:crieit.net,2005:PublicArticle/16587 2021-01-13T15:45:49+09:00 2021-01-13T18:21:58+09:00 https://crieit.net/posts/2021-1-13 nginx + uWSGI + Flask で python webアプリを立ち上げる <h1 id="nginx + uWSGI + Flask で python webアプリを立ち上げる"><a href="#nginx+%2B+uWSGI+%2B+Flask+%E3%81%A7+python+web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E7%AB%8B%E3%81%A1%E4%B8%8A%E3%81%92%E3%82%8B">nginx + uWSGI + Flask で python webアプリを立ち上げる</a></h1> <p>開発中は Flask の dev server で良いのだけれど、リリース時にはフロントのHTTPサーバを用意する必要がある。</p> <p>Deployment は選択肢がいくつかあるけれど、今回はUbuntu上で、HTTPサーバに nginx、Flaskで作ったPython Webアプリを動かす WSGIコンテナに uWSGI を使った構成の設定例。</p> <h2 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h2> <p>nginx, uwsgi, Flask をインストールする。uwsgi と Flask は venv 環境で。</p> <pre><code class="shell">$ uname -a Linux hoge-machine 5.4.0-52-generic #57~18.04.1-Ubuntu SMP Thu Oct 15 14:04:49 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ sudo apt install nginx $ python3 -V Python 3.6.9 $ mkdir venv; cd venv $ python3 -m venv flaskapp $ source ~/venv/flaskapp/bin/activate (flaskapp) $ pip install -U pip (flaskapp) $ pip install flask uwsgi </code></pre> <h2 id="nginx の設定ファイル"><a href="#nginx+%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB">nginx の設定ファイル</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://flask.palletsprojects.com/en/1.1.x/deploying/uwsgi/#configuring-nginx">https://flask.palletsprojects.com/en/1.1.x/deploying/uwsgi/#configuring-nginx</a></p> <p>flask で uwsgi を使う場合の nginx 設定ファイル例は上に載っているので、それを参考に。<br /> アプリ名を <code>nginx_yourapp</code> とか <code>your_application</code> とかにしているが適当に変える。</p> <p>nginx_yourapp.conf</p> <pre><code class="conf">server { listen 80; listen [::]:80; server_name hoge-machine.com; root /var/www/example.com; index index.html; location = /your_application { rewrite ^ /your_application/; } location /your_application { try_files $uri @your_application; } location @your_application { include uwsgi_params; uwsgi_pass unix:/tmp/your_application.sock; } } </code></pre> <p>nginx の設定ファイルは <code>/etc/nginx/sites-available</code> 下に置いて、<code>/etc/nginx/sites-enabled</code> 下に<br /> シンボリックリンクを張り、サービス再起動する。Permission 関係はよしなに。</p> <pre><code class="shell">$ mkdir -p ~/path/to/nginx_yourapp; cd ~/path/to/nginx_yourapp $ vim nginx_yourapp.conf (上記の設定ファイルを作る) $ cp nginx_yourapp.conf /etc/nginx/sites-available/ $ ln -s /etc/nginx/sites-available/nginx_yourapp.conf /etc/nginx/sites-enabled/nginx_yourapp.conf $ systemctl restart nginx </code></pre> <h2 id="webアプリの準備"><a href="#web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E6%BA%96%E5%82%99">webアプリの準備</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://flask.palletsprojects.com/en/1.1.x/quickstart/#a-minimal-application">https://flask.palletsprojects.com/en/1.1.x/quickstart/#a-minimal-application</a></p> <p>Flask の HelloWorld。</p> <p>index.py</p> <pre><code class="python">from flask import Flask app = Flask(__name__) @app.route("/") def hello_world(): return "Hello, World!" </code></pre> <h2 id="uwsgi の起動コマンド"><a href="#uwsgi+%E3%81%AE%E8%B5%B7%E5%8B%95%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89">uwsgi の起動コマンド</a></h2> <ul> <li>ソケットファイルは nginx の設定ファイルと合わせること</li> <li><code>--chmod-socket=666</code> はパーミッション関連でこけたら</li> <li><code>--mount</code> はアプリパスと実行するスクリプト名:Flask appオブジェクト名を正しく指定する</li> <li><code>--virtualenv</code> は venv で作ったPython仮想環境を正しく指定する。</li> </ul> <pre><code class="shell">$ uwsgi -s /tmp/nginx_yourapp.sock --chmod-socket=666 --manage-script-name --mount /nginx_yourapp=index:app --virtualenv ~/venv/flaskapp </code></pre> <p>後はブラウザから <code>http://<サーバのIPアドレス>/nginx_yourapp/</code> にアクセスすれば <code>Hello World</code> が表示される。</p> <h2 id="FAQ"><a href="#FAQ">FAQ</a></h2> <h3 id="502 Bad Gateway が出る"><a href="#502+Bad+Gateway+%E3%81%8C%E5%87%BA%E3%82%8B">502 Bad Gateway が出る</a></h3> <p>ソケットファイルの permission 関連がおかしいかも。<br /> <code>/var/log/nginx/error.log</code> あたりを覗くとヒントがあるかも。</p> <h3 id="404 Not Found が出る"><a href="#404+Not+Found+%E3%81%8C%E5%87%BA%E3%82%8B">404 Not Found が出る</a></h3> <p>アプリパス指定が正しくないかも。</p> <h3 id="接続できない"><a href="#%E6%8E%A5%E7%B6%9A%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">接続できない</a></h3> <p>nginx が正しく動いているなら、 <code>http://<サーバのIPアドレス>/</code> で何かが開けるはず。<br /> ダメならnginxが正しく起動できていないか、そもそもサーバに到達できてないか、<br /> サーバが外からのリクエストを受け付けていないので、そちらの方面でなんとかする。</p> keyangu 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