GAEではじめてのFlaskアプリをつくる[Progate修了レベル]

Google App Engine(以下、GAE)のスタンダード環境(Standard Environment)でFlaskを用いて簡単なWEBアプリをつくります。

想定レベルは「Progateで一通りPythonはやったけど、まだ実際にアプリを作ったことはないしフレームワークなにそれ」の人です。


...ごめんなさい、若干盛りました。デコレータ がわからない人は先にこのページを読んできてください。...でも書くコードの100%理解していなくても気持ち悪くない、いわゆる「おまじない」が嫌じゃなければ正直デコレータ を理解していなくても大丈夫です。


...あ、httpプロトコルについては簡単に知っていた方がいいですね。「GETメソッド」「クエリ」「絶対パス・相対パス」の意味くらいはわかってからの方がいいと思います。ぐぐればトータルで15分くらいで理解できる内容かと思います。


基本的に公式チュートリアルを日本語で噛み砕いた内容です。

なお、PCはMac/Linuxでpip, python, virtualenvはインストール済み想定です(インストールがまだの場合はこちら)。念のため最新版にアップデートしておきましょう。ただし、今回はGAEのスタンダード環境の制限のため、Pythonについては2.7でないといけません。3.5などをインストールしている場合もこちらを参考に2.7に切り替えておいてください。

あとGoogle Cloud Platformのアカウントは持っているしGoogle Cloud SDKもインストール済みです。

ディレクトリを準備

まずは自分のPC上で今回つくるアプリのディレクトリ(フォルダ)を作成します。
今回は「myapp」というフォルダを作ったことにして話を進めます。(写真は中にいろいろ入ってますが、これは完成系です。今はmyappフォルダをつくってくれればOK)
f:id:simonsnote:20180530194711p:plain:w400

ディレクトリの中身はこんなかんじです。(それぞれのファイルの用意の仕方もこれから解説します。)
※なお、たとえばtemplates/とは「templates」という名前のディレクトリ(フォルダ)のことです。なお、ターミナル(コマンドライン)がわからない方は先にProgateかなんかで学習してきた方がいいと思います。

  • myapp/
    • main.py
    • static/
    • templates/
    • app.yaml
    • appengine_config.py
    • requirements.txt

なお、GAEに限らずFlaskで必要となるのはtemplates/より上の部分です。app.yaml以下はGAE用の設定ファイルです。

appengine_config.pyの作成

以下の内容のpythonファイルをつくってmyappフォルダの直下に置きます。
(テキストエディタにコピペして、「appengine_config.py」という名前で保存すればOK)

from google.appengine.ext import vendor
vendor.add('lib')

requirements.txtの作成

以下の内容のテキストファイルをつくってmyappフォルダの直下に置きます。拡張子は.txtです。

Flask==0.12.2
Werkzeug<0.13.0,>=0.12.0

ここに書いたものがあとでpipコマンドによってlibディレクトリにインストールされるわけです。

今回はFlaskフレームワークを利用します。
FlaskはWerkzeugというWSGI規格のアプリ(これは...そういうもんと思ってください)をつくるためのツールと、Jinja2というテンプレートエンジン(HTML内に動的に変数を埋め込むもの。PHPでいうTwig)を利用します。
▷WSGIがなんなのか気になる方はこちらの記事がわかりやすかったです。

あれJinja2はインストールしなくていいのかと思われたでしょうが、これでJinja2もセットでダウンロードされます。

ここではFlaskとWerkzeugのバージョンを指定するので、なるべく最新の安定版を指定しましょう。
最新版はこちらから確認できます。
Flask · PyPI
Werkzeug · PyPI

Virtualenvをアクティベート

Virtualenvはその名の通り「仮想環境」です。これからWEBアプリに必要な各ライブラリや依存ファイルをダウンロードするのですが、Virtualenvを利用すれば自分のMacをごちゃごちゃにしてしまう心配がありません。

ここからはターミナルを使います。

まずはさきほど作成したmyappフォルダに移動します。
▷Macの場合、フォルダを右クリック→Optionを押していると「"〜〜〜"のパス名をコピー」というメニューが出るので、それをクリックしてペーストすればOKです。ただし、パス名にスペースが含まれているとエラーになります。そういう場合はパスを""(ダブルクォーテーション)で囲えばOKです。

cd myappフォルダのパス

Virtualenvに必要なenv/ディレクトリを自動生成します。

virtualenv env

自動生成したenv/bin/activateに対してsourceコマンドを実行すると、virtualenvがアクティベートされます。(=以降は仮想環境に入ります)

なお、仮想環境を終了するときはdeactivateと入力すればOKです。

source env/bin/activate

ライブラリをダウンロード

Flaskライブラリをpipでダウンロードします。
※pipはversion 6.0.0以上である必要があります。pip --versionで確かめましょう。もしそれ以下ならこちらを参考にアップデートしてください。ちなみにGAEのスタンダード環境では2018/5/29現在、利用可能なPythonのバージョンは2.7となります。Python3を利用したい場合はフレキシブル環境(Flexible Enviroment)を選択する必要があります。それぞれの環境の違いはこちらの公式ガイドをご覧ください。

以下のコマンドを入力します。意味は「libフォルダにrequirements.txt(さっき作ったやつ)に書かれているものを全てインストールする」ということになります。

pip install --target lib --requirement requirements.txt

これでmyappにFlaskとWerkzeugをインストールできました。

app.yamlを作成

app.yamlファイルは、Google App Engineに対してアプリケーションの構成を伝えるファイルです。

以下の内容のテキストファイルをつくり、ファイル名を「app.yaml」にしてmyappフォルダの直下に置きます。

runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /static
  static_dir: static
- url: /.*
  script: main.app

libraries:
- name: ssl
  version: latest

なお、app.yamlの詳しい書き方はこちらに公式リファレンス(英語)があります。
app.yaml Reference  |  App Engine standard environment for Python  |  Google Cloud


ここまででGoogle App Engineのための設定は終わりです。ここからはFlaskアプリケーションの設計になります。

Flaskアプリケーションの設計

出力するHTMLを用意する

Flaskでは出力するHTML(HTML以外にもXML、XHTMLにも対応しています)templates/ディレクトリに入れておきます。今回は簡単なWEBページを表示するので、HTMLファイルを用意します。

もちろん静的な(普通の)HTMLだけでなく、文中に変数を埋め込んだ動的なHTMLも用意できます。その時に使うのがテンプレートエンジンであるJinja2ですね。
▷ちなみにどうでもいいですが、Jinjaは「神社」です。公式サイトを覗いてみると鳥居までありますよ。

今回は静的なHTMLファイルと、Jinja2で変数を埋め込んだHTMLファイルの2通りを用意します。

静的なHTMLファイル

これはもうそのまんまです。
今回はこんなかんじのHTMLファイルをつくって、toppage.htmlという名前でtemplates/ディレクトリに保存します。

<html>
    <body>
        Welcome to Myapp!!
    </body>
</html>
変数を埋め込むHTMLファイル

Jinja2では{{ }}で囲った部分が変数として扱われます。

今回は受け取ったユーザーの名前を表示するページを作ってみます。

<html>
    <body>
        あなたの名前は{{ name }}ですね。
    </body>
</html>

この{{ name }}に、次のステップで作るmain.py内のPythonコードから値を渡すわけです。作ったファイルの拡張子は普通に.htmlでOKです。showname.htmlという名前でtemplates/ディレクトリに保存しましょう。

main.pyを書く

main.pyはその名の通り、Pythonで書かれたFlaskアプリのメインファイルです。基本的なFlaskアプリの仕組みは、

  1. main.pyが通信リクエスト(httpなど)を受け取る
  2. templates/に格納されているHTMLファイルをレスポンスとして返す

という流れになります。static/フォルダにはCSSやJavascript、画像ファイルなどのHTMLから参照する静的(static)なファイルをいれておきます。


当然ながら実用的なWEBアプリをつくるにはすべてのPythonコードを1ファイルに書いていたら管理しにくすぎるのでファイルは分割しますが、べつにmain.py1ファイルにすべてのコードを書いてもOKです。今回は初めてのFlaskアプリ作成なので、全体の流れを見通すために1ファイルで書くことにしましょう。


以下、main.pyの内容です。テキストエディタで作成しましょう。

文字コードを指定する

コード中に英数字しか書かないなら問題ないのですが、もし日本語を含める場合は文字コードを指定しないとエラーになります。

最初にこのようにコメントの形で書けばOKです。プログラムの1〜2行目にこのようなコメントを書くと例外的にコメントではなく文字コードの指定として扱われます。

# coding: UTF-8
flaskパッケージをimportする

さて、そうしたらまずはflaskのライブラリ(flaskモジュール)を読み込みます。

import flask

flaskモジュールとはすなわちflask.pyのこと...とProgateで習いましたよね。import flaskflask.pyを読み込むはずです。しかしlibフォルダのどこを探してもflask.pyなんていうファイルはありません。

...じゃあどうなっているのかというと、Pythonには「パッケージ」という機能があります。これについては詳しくはまた今度、コードを複数ファイルに分割して書く方法とあわせて説明しますが、要するにここではimport flaskflask/ディレクトリ内すべてを読み込みます。

なので正確にいうとimportしたのはflaskモジュールではなくflaskパッケージです。

インスタンス'app'を作成する

flaskパッケージをimportして最初にやることは、flaskパッケージ内(具体的にはapp.pyで定義されているFlaskクラスのインスタンスを作成することです。

Flaskクラスはアプリケーションそのものを定義するクラスです。よってFlaskクラスのインスタンスはアプリケーションそのものです。

Flaskクラスの引数にはモジュール/パッケージ名を入れます。

今回はmain.py(='main'モジュール)1ファイルで作成するので、こうなりますね。

app = flask.Flask('main')

これはつまり、「appインスタンスは'main'モジュールを参照するFlaskアプリケーションである」ということです。

...おいおい、自分自身のファイル名もわざわざ入力してやらなきゃわかんねえのか?と思いますよね。実はもっといい書き方があります。Pythonには__name__という変数が予約されています。これは自分自身のモジュール/パッケージ名を表します。
▷厳密には場合によるのですが、またこんど説明します。

なので、上のコードはこのように書き換えられます。

app = flask.Flask(__name__)
ルーティングを作成する

appインスタンスを作成したら、あとはFlaskクラスに定義されているメソッドであるrouteを利用してパス(URLのこと)ごとの処理を関数として書いていくだけです。ちょっとイメージわいてきたでしょ?

さて、routeメソッドの内部処理がどうなっているのか見ていくのはprint関数の仕組みを見るのと同じで、つまり「WEBアプリケーションをつくる」という観点からすると不要です。

実用的には、routeメソッドにパスと関数を渡すと、そのパスにアクセスしたときにその関数を実行する」という理解でOKです。
▷厳密には「アクセス」というか「リクエスト」です。一般的にブラウザでWEBサイトにアクセスするということは、サーバーに対してURL(パス)という形のリクエストを送り、そのリクエストに対するレスポンスとして受け取ったHTMLを画面に表示しているわけです。なので一般的なWEBサイトでもバックグラウンドで使う(REST)サーバーでも、仕組みは一緒です。詳しくは「通信プロトコル」とか「httpリクエスト」についてぐぐってもらえれば勉強できます。


さて、具体的にどうやって「パスと関数を渡す」のでしょう。デコレータ を理解していればここは想像がつきますね。
▷デコレータ を理解していなくても実用上は問題ないのですが、理解しておけばなんでこの形になってるかがわかります。デコレータ の解説はこちらからどうぞ。


さて、デコレータ について軽くおさらいします。
関数routeに引数としてパス/mypage関数myfunctionを渡したような挙動の関数を定義したい場合、デコレータ を用いてこう書きます。

@route('/mypage')
def myfunction(なんらかの引数):
    処理

今回routeappのインスタンスメソッドなので、app.routeの形で呼び出します。

なので、ルーティングの形はこうなります。

@app.route(パス)
def 関数名(なんらかの引数):
    処理

あとはこのセットをパスの数だけ用意して、それぞれにパスごとの処理を書けばいいのです。


今回は初めてなので、まずはさっき用意した静的なページtoppage.htmlを出力することにしましょう。
トップページにしたいので、@app.routeの引数は'/'とします。

パス(=リクエスト)が'/'のときの処理を関数として書きます。関数名はなんでもいいです。今回は引数はいらないですね。

templates/に格納しているHTMLを出力するには、flaskパッケージに定義されている関数であるrender_template(ファイル名)を利用します。

@app.route('/')
def toppage():
    return flask.render_template('toppage.html')

これで、「'/'にアクセスされたときはtoppage.htmlを出力する」というコードができました。


...実は、(トップページを表示するだけでいいのであれば)ここまででもうWEBアプリは完成しているのです。コードらしいコードなんて最後の数行だけしか書いてないのに、もうこれで一通りなのです。あとはページやら処理やらを増やしていくだけです。


さて、しかし今回はもうひとつページをつくります。先ほどshowname.htmlをつくりましたね。あれを出力しましょう。

さて、今度はJinja2による埋め込みのある、動的なページです。さっきと同じようにrender_template('showname.html')をやるだけだと、埋め込んだ変数を利用できません。

実はrender_templateメソッドには出力するファイル名以外にも引数を渡せます。

@app.route('/showname')
def showname():
    return flask.render_template('showname.html', name='フラスク太郎')

これでOKです。雰囲気でわかると思いますが、引数としてテンプレート内の変数に値を代入できるのです。render_template('showname.html', name='フラスク太郎', age='50歳', film='ベニスに死す')のようにいくらでも増やせます。

しかしこれでは表示する名前が「フラスク太郎」に固定されてしまって、変数を埋め込んだ意味があまりありません。

ということで、クエリで受け取った名前を出力するようにしてみましょう。
▷「クエリ」とはhttp://exapmle.com/abc?name=taro&age=50のようなリクエストURLの?name=taro&age=50の部分です。このURLの意味は、「パスはhttp://exapmle.com/abcnametaroage50」という意味です。パラメータを複数用意するときは&で接続します。

FlaskではURLやクエリを扱うメソッドが用意されています。

完全なURLはrequest.urlで取得できますし、クエリ文字列はrequest.query_stringで取得できます。

しかしクエリを扱う際に最も便利なのはrequest.argsです。ここにクエリパラメータが辞書型で入っています。たとえばhttp://exapmle.com/abc?name=taro&age=50というリクエストが来た時には、request.args['name'] == 'taro'となります。

これを使って、リクエストのクエリのパラメータ「name」の値をHTMLに埋め込んだ変数{{ name }}に渡しましょう。

@app.route('/showname')
def showname():
    name = flask.request.args['name']
    return flask.render_template('showname.html', name=name)

これでmain.pyは完成です!

完成したmain.py

main.pyはこのようになりました。

# coding: UTF-8
import flask

app = flask.Flask(__name__)


@app.route('/')
def toppage():
    return flask.render_template('toppage.html')

@app.route('/showname')
def showname():
    name = flask.request.args['name']
    return flask.render_template('showname.html', name=name)

ちなみに下のように書いても同じことです。import文の挙動を理解していればわかりますね。(実はこっちの方がメジャーな書き方です)

# coding: UTF-8
from flask import Flask, render_template, request

app = Flask(__name__)


@app.route('/')
def toppage():
    return render_template('toppage.html')

@app.route('/showname')
def showname():
    name = request.args['name']
    return render_template('showname.html', name=name)

staticファイルを用意する

CSS /JavaScriptや画像ファイルなどstatic(静的な)ファイルを用意する場合はstatic/フォルダにいれておきます。

静的ファイルはURLで参照するだけなので別に必ずこのstatic/フォルダにいれなければいけないわけではなく、Google Cloud Storageのような外部サービスを利用しても全く問題ありません。

今回はCSSを用意しましょう。

以下のような内容のCSSファイルを作って、stylesheet.cssという名前でstatic/フォルダ内に保存しましょう。

body{
    background:pink;
}

用意したCSSを読み込むようにHTMLにCSSへのリンクを加えましょう。今回はトップページのみこのCSSを読み込むようにします。

toppage.htmlを以下のようにします。

<html>
    <head>
        <link rel="stylesheet" type="text/css" href="/static/stylesheet.css">
    </head>
    <body>
        Welcome to Myapp!!
    </body>
</html>

app.yamlを作成した際に、こんなふうに設定していました。(抜粋)

handlers:
- url: /static
  static_dir: static
- url: /.*
  script: main.app

これは「URLが'/static'で始まる場合はstatic/フォルダを参照し、それ以外はmain.appに渡す」という指示になっています。

アプリ自体は完成!テストしよう

これでFlaskアプリ自体は完成です!

あとはこのアプリをGoogle App Engineにデプロイする(アップロードして利用可能にする)だけなんですが、その前にちゃんと動くのか手元で試しに動かしてみましょう。

これはGoogle Cloud SDKで簡単にできます。


ここからはターミナルでの作業です。

まずはmyapp/フォルダの直下ルートディレクトリといいます)に移動します。
▷myappフォルダのパスの取得方法を忘れてしまった場合はこちら

cd myappフォルダのパス

そうしたら、以下のコマンドを入力します。

dev_appserver.py app.yaml

これで、自分のPCの8080番ポートで今作ったアプリが動いている状態になりました!
ブラウザで以下のURLにアクセスしましょう。
'localhost'とは自分自身という意味です。

http://localhost:8080/


...ちゃんとこんな画面が表示されましたか?
f:id:simonsnote:20180530195637p:plain:w400

背景がピンク色なら、CSSもしっかり配置できているということですね。

試しにhttp://localhost:8080/static/stylesheet.cssにアクセスしてみると、ちゃんとCSSファイルが表示されます。
f:id:simonsnote:20180530195650p:plain:w400

さて、次は/shownameページをみてみましょう。

http://localhost:8080/showname


こんな画面が表示されましたね?
f:id:simonsnote:20180530195706p:plain:w400

クエリをつけなかったので、名前を表示する部分には'None'が入っています。これを避けるにはif文でパラメータが空の場合の挙動を設定してあげればいいですね。(今回はそこまでやりませんが、このくらいのアレンジはもうできますよね?)

クエリに名前をいれてもう一度アクセスしてみましょう。

http://localhost:8080/showname?name=フラスク太郎


パラメータ'name'の値が変数nameにちゃんと代入されています。
f:id:simonsnote:20180530195721p:plain:w400

ちなみにdev_appserver.py app.yamlの実行中にmyapp内に変更を加えることができます。変更はすぐに反映されます。リアルタイムプレビューとして利用できるので、これは開発に便利ですね!試しにどこかいじってブラウザを更新してみてください。

テスト(localhost:8080への出力)を終了するには、ターミナルでcontrolキーを押しながらcです。

Google App Engineにデプロイ

いよいよGoogle App Engineにデプロイします。これもターミナルからGoogle Cloud SDKでワンコマンドです。

myappのルートディレクトリに移動します。

cd myappフォルダのパス

gcloudコマンドでデプロイします。
'プロジェクトID'の部分にはGCPの「ホーム」画面の左上にある「プロジェクト情報」の中の「プロジェクト ID」に書いてある英数字をそのままコピペします。(というかGoogle Cloud SDKインストールしたときにもうやったはずですよね)

gcloud app deploy --project プロジェクトID

これでデプロイ完了です!
▷デプロイについて詳しくはこちらの公式チュートリアル(英語)をご覧ください。


すでにいまデプロイしたmyappは世界中に公開されています。デフォルトのURLは以下のようになります。

http://プロジェクトID.appspot.com/

もちろん独自ドメインを設定することも可能です。それはまた次回。


ご質問などあれば気軽に下のコメントにお書きください!