Python3+Flask Quick Start

# はじめに
FlaskはPython用のマイクロWebフレームワークです。
必要最低限の機能で、動作も軽量なので、ちょっとした簡単なAPIを使えます。

Flaskは公式ドキュメントがありますが、全てのソースコードが掲載されてるわけではないので、必要な部分だけ抽出してます。

## 実行環境
Ubuntu 19.04
python3.7

## 参照ドキュメント
|サイト|URL|
|---|---|
|公式サイト|http://flask.pocoo.org/|
|ドキュメント|https://flask.palletsprojects.com/en/1.1.x/|

## Flaskのインストール(venv)
### アプリごとに固有の環境を用意するため、Pythonをローカルでインストールするには？
環境を個別に用意するため、venvモジュールを使って仮想環境を用意し、flaskをインストールします。
Python 3.3 と 3.4 では、仮想環境の作成に推奨していたツールは pyvenv でしたが、Python 3.6では非推奨という情報があり、今回はvenvを使用することにいます。

参考：
https://docs.python.org/ja/3/library/venv.html

``` bash:
$ sudo apt-get install python3-venv
$ python3.7 -m venv env
$ source env/bin/activate
$ pip3.7 install flask
```

## Flask Quick Start
### Flaskアプリケーションを動作させるには？

まずは https://flask.palletsprojects.com/en/1.1.x/quickstart/ を参考に簡単に Hello Worldを表示するアプリケーションを作成します。

``` python:hello.py
#!/usr/bin/env python3

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
        return "Hello World!"

if __name__ == '__main__':
    app.run()
```

@app.route はアクセスするディレクトリに応じた関数を呼び出すためのデコレータです。
ユーザがWebサーバへアクセスしたときに、リクエストのあったパスに応じた処理を行う機能を提供します。

```
@app.route('/')
def hello_world():
```

### Flaskアプリケーションの実行
アプリケーションを立ち上げる方法はいくつかありますが、ここでは flask run コマンドを実行してWebサーバを立ち上げます。
自分の使用しているPC（ http://127.0.0.1:5000/ )に接続すると、「Hello World!」と表示されます。

``` bash:command
$ export FLASK_ENV="development"
$ export FLASK_APP="hello.py"
$ flask run
 * Serving Flask app "hello.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
```

この状態だとローカルのPCからしか接続できないので、以下のように--hostオプションを設定するとサーバ上でListenするIPアドレスを指定できます。0.0.0.0で指定すると、サーバが所持するすべてのネットワークインターフェースでListenすることができます。

``` bash:command
$ flask run --host=0.0.0.0
```

# Flaskの基本機能
## ルーティング
### アクセスされたパスに応じた処理を返すには？
リクエストを受けたときのルーティングを制御します。
デコレータのすぐ次の行にある関数が実行されます。

``` python:routing.py
#!/usr/bin/env python3

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

if __name__ == '__main__':
    app.run()
```

デコレータに\<変数\>を含めると入力したURLを変数として受け取ることができます。
\<converter:変数\> のように変数を指定すると、型で文字列を受け取れます。

|converter|説明|
|---|---|
|string| テキスト状態として変数を受け取ります。デフォルトはこの動作を行います。|
|int|整数として受け取ります。|
|float|浮動小数点として受け取ります。|
|path|パスとして受け取ります。|
|uuid|UUIDとして受け取ります。|


``` python:routing_variable.py
#!/usr/bin/env python3

from flask import Flask, escape
app = Flask(__name__)

@app.route('/user/<username>')
def show_user_profile(username):
    return 'User %s' % escape(username)

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return 'Post %d' % post_id

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    return 'Subpath %s' % escape(subpath)

if __name__ == '__main__':
    app.run()
```

## HTTPメソッド
### HTTPメソッドに応じて処理を指定するには？
HTTPメソッドを指定して処理を行うことができます。
デフォルトでは、ルートはGET要求にのみ応答しますが、デコレータのmethods引数を使って、他のHTTPメソッドも処理できます。

``` python:http_methods.py
#!/usr/bin/env python3

from flask import Flask, request
app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()
```
## レンダリングテンプレート
### テンプレートを使ってWebページを表示するには？

テンプレートを使って、Web画面を表示することができますが、render_template() メソッドを使います。裏ではjinjaというテンプレートエンジンが動いています。
テンプレートエンジンにキーワード引数として渡す変数を指定するだけです

``` python:render_template.py
#!/usr/bin/env python3
from flask import render_template

app = Flask(__name__)
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)
```

``` html:hello.html
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}
```


## リクエストオブジェクト
### フォームデータを処理するには？
要求方法は、method属性を使用して利用できます。
フォームデータにアクセスするには、POSTを受け付けるようにします。

``` python:form.py
#!/usr/bin/env python3
from flask import Flask, request, render_template, redirect, url_for

app = Flask(__name__)

@app.route('/', methods=['POST', 'GET'])
def index():
    return redirect(url_for('login'))

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = ""
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return render_template('hello.html', user=request.form['username'])
        else:
            error = 'Invalid username/password'
    return render_template('login.html', error=error)
    
def valid_login(user,password):
    if user == "test" and password == "pass":
        return True
    return False
```

``` python:login.html
<html>
   <body>
      {{ error }}
      <form action = "/login" method = "POST">
         <p>Name <input type = "text" name = "username" /></p>
         <p>Value <input type ="text" name = "password" /></p>
         <p><input type = "submit" value = "submit" /></p>
      </form>
   </body>
</html>
```

``` python:hello.html
<html>
   <body>
        hello {{user}}!
   </body>
</html>
```

## リダイレクトとエラー
### リダイレクトするには？
HTTPアクセスをリダイレクトするには、redirect() を使用します。

``` python:redirect.py
#!/usr/bin/env python3
from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.route('/')
def index():
    return redirect(url_for('redirect'))
    
@app.route('/redirect')
def redirect():
    return "Hello"
```

### エラーページを表示するには？
errorhandlerのデコレータでエラーが発生した場合のページを設定できます。
404でアクセス先のリソースがない場合、page_not_found()で指定された関数を実行します。

``` python:error.py
#!/usr/bin/env python3

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template("hello.html")

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404
```

``` html:page_not_found.html
404 File not found.
```

## APIs with JSON
### JSONを扱うには？
FlaskでREST APIを扱う場合は、json形式でデータを受け渡しする必要があります。
JSONデータを扱うには jsonify() を使います。

```
#!/usr/bin/env python3
from flask import Flask, jsonify
app = Flask(__name__)

@app.route("/" methods=['GET'])
def output_data():
    return jsonify({'message': 'Hello, world!})
    
@app.route("/", methods=['POST']")
def accept_data():
    data = request.get_json() 
    return jsonify(data)
```

``` bash:commandline
$ curl localhost:5000
{
  "message": "Hello World!"
}

$ curl -X POST -H 'Accept:application/json' -H 'Content-Type:application/json' -d '{"Hello":"World"}' http://localhost:5000/
{
  "Hello": "World"
}
```

## セッション
### Flaskでセッションを管理するには？
Webアクセスに使うHTTPプロトコルは状態を保持できないので、ログイン状態を保持することができません。そのため、一度ログインした情報を保持するためには他の手段が必要です。

Flaskでは、session()関数で、クッキーを用いてセッション管理を行います。

``` python:session.py
#!/usr/bin/env python3
from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)
app.secret_key = 'app key'

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if its there
    session.pop('username', None)
    return redirect(url_for('index'))

```