Flask 使ってみた


Python で軽量な Web フレームワークで人気のあるらしい Flask を使ってみたメモ
Bottle にしなかったのは Flask が View にロジック書けない実装になっているらしい（[参考](http://nopnop2002.webcrow.jp/flask_python/flask-bottle.html)）から。
ぶっちゃけほぼなんとなく。

Python なのは自分に馴染みがあるから（自分が ruby 弱すぎるというのもある）

# インストール

[Flask のドキュメント](http://flask.pocoo.org/docs/0.12/installation/#installation)読むと積極的に virtualenv を勧められているし、恐らくそうするのが正しいのだろうが、 ~~そんな色々な開発するほどには個人的にならないと思うので pyenv で十分と判断し、そのままインストールしていく。~~
~~（環境試して壊してってしたい人は virtualenv 使ったほうがいいと思います）~~

やっといたほうが後々便利なのでやっぱりやります。
※ pyenv, pyenv-virtualenv で実施：[参考](https://qiita.com/niwak2/items/5490607be32202ce1314)

```
$ cd /path/to/workingdirectory/
$ mkdir flask_test
$ cd flask_test
$ pyenv virtualenv 3.6.1 flask_test_3.6.1
$ pyenv local flask_test_3.6.1
$ echo .python-version > .gitignore
```

dependency が Werkzeug ([ヴェルクツォイクと読むらしい](https://qiita.com/mzsm/items/dff7db90151220bb5b28)) と Jinja2 らしいので入れる。

```
$ pip install Werkzeug
$ pip install Jinja2
```

終わったので Flask 入れる

```
$ pip install Flask
```

終わったらしい

```bash
$ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
click (6.7)
Flask (0.12.2)
itsdangerous (0.24)
Jinja2 (2.9.6)
MarkupSafe (1.0)
pip (9.0.1)
setuptools (36.6.0)
Werkzeug (0.12.2)
wheel (0.30.0)
```

いいと思う（たぶん）

# 試す

## web server の起動

参考：https://qiita.com/zaburo/items/5091041a5afb2a7dffc8

写経

```py:hello.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello Flask!"

@app.route('/good')
def good():
    return "Good page."

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

動かす

```bash
$ python ./hello.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: hogehoge

```

動いてるっぽいのでアクセス

http://localhost:5000/ 
![スクリーンショット 2017-10-25 13.01.04.png](https://qiita-image-store.s3.amazonaws.com/0/88428/860fdc02-c5c8-e1d8-8edc-7615782e6773.png "スクリーンショット 2017-10-25 13.01.04.png")

http://localhost:5000/good
![スクリーンショット 2017-10-25 13.01.14.png](https://qiita-image-store.s3.amazonaws.com/0/88428/eb51a593-d1bc-6d39-cfb0-cfb048e23f14.png "スクリーンショット 2017-10-25 13.01.14.png")


表示された。こりゃあ楽でいいわ


## テンプレートエンジンの利用

写経

共通テンプレート（外組）

```html:layout.html
<!DOCTYPE html>
<html>
	<head>
		<title>{{ title }}</title>
		<body>
			{% block content %}
			<!-- main content -->
			{% end block %}　←後述するが謝り
		</body>
	</head>
</html>

```


個別コンテンツ（内組）

```html:hello.html
{% extends "layout.html" %}
{% block content %}
<h3>Hello</h3>
hello, {{ name }}.
{% end block %}　←後述略
```


呼び出し側プログラムの改修

```py:hello.py
#from flask import Flask
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def hello():
    #return "Hello Flask!"
    return render_template('hello.html', title="this is Title!", name="kokomi")

@app.route('/good')
def good():
    return "Good page."

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

あまり関係ないが、`hello.py` に変更を加えて保存した瞬間 reload された

```
 * Detected change in '/path/to/wrokingdirectory/hello.py', reloading
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: hogehoge
```


再度アクセス
![スクリーンショット 2017-10-25 13.29.06.png](https://qiita-image-store.s3.amazonaws.com/0/88428/89185e34-27ec-6898-8cf4-960858538d53.png "スクリーンショット 2017-10-25 13.29.06.png")


あべしっ！
どうやらテンプレートの場所が悪いらしい

```bash:before
$ tree .
.
├── hello.html
├── hello.py
└── layout.html

0 directories, 3 files
```


```bash:after
$ tree .
.
├── hello.py
└── templates
    ├── hello.html
    └── layout.html

1 directory, 3 files
```

気を取り直して
![スクリーンショット 2017-10-25 13.34.17.png](https://qiita-image-store.s3.amazonaws.com/0/88428/f9af5db1-3f9a-394f-cabd-2c3832fe9f93.png "スクリーンショット 2017-10-25 13.34.17.png")


あべしっ！（Syntax Error とは恥ずかしい）

```html:hello.html(before)
{% extends "layout.html" %}
{% block content %}
<h3>Hello</h3>
hello, {{ name }}.
{% end block %}　←ここ
```

```html:hello.html(after)
{% extends "layout.html" %}
{% block content %}
<h3>Hello</h3>
hello, {{ name }}.
{% endblock %}　←こう
```

※ layout.html も同じ
※ 写経はちゃんとスペースにも気をつけてやりましょう


今度こそ！
http://localhost:5000/
![スクリーンショット 2017-10-25 13.38.12.png](https://qiita-image-store.s3.amazonaws.com/0/88428/50712d6d-e567-895d-7029-a68c2390366c.png "スクリーンショット 2017-10-25 13.38.12.png")

やったぜ :tada: 

## DB の利用

を、しようと思ったけどこの辺になってくると動かす先でどう構築するかって話になってくるのでやめる

# heroku で動かす

せっかくなので heroku にデプロイできるようにしてみたい。
参考：https://qiita.com/kounoike/items/6fc31fe051e5d688f136

heroku 用に色々ファイルを記述（多分）
gunicorn は多分 heroku で動かすのに必要なはず…

```
$ pip install gunicorn
$ pip freeze > requirements.txt
$ echo python-3.6.1 > runtime.txt
$ echo web: gunicorn app:app --log-file=- > Procfile　←後述するが誤り
```

git repository 

```
$ git init
$ git add .gitignore
$ git add .
$ git commit -m "initial commit"
```

```
$ heroku login
$ heroku apps:create
$ git push heroku master
```

アクセスしてみる
![スクリーンショット 2017-10-25 16.59.09.png](https://qiita-image-store.s3.amazonaws.com/0/88428/9e51e437-0590-aac2-1a47-5d0580763516.png "スクリーンショット 2017-10-25 16.59.09.png")

┐(´д｀)┌

Procfile コピペしたせいだった

```before
web: gunicorn app:app --log-file=-
```

```after
web: gunicorn hello:app --log-file=-
```

![スクリーンショット 2017-10-25 17.06.55.png](https://qiita-image-store.s3.amazonaws.com/0/88428/9b44ee5a-a0c5-feaf-2c4f-2481bc0186d6.png "スクリーンショット 2017-10-25 17.06.55.png")
無事デプロイ完了

## DB を使う（データの永続化）

せっかくなので[このまま参考にして](https://qiita.com/kounoike/items/6fc31fe051e5d688f136#mongohq%E3%82%92%E4%BD%BF%E3%81%86) MongoHQ を使う

```
$ heroku addons:add mongohq
Creating mongohq on ⬢ abababababa... !
 ▸    Please verify your account to install this add-on plan (please enter a credit card) For
 ▸    more information, see https://devcenter.heroku.com/categories/billing Verify now at
 ▸    https://heroku.com/verify
```
 
(´・ω・｀)

~~クレカ登録し直してから（無料範囲のはずなので料金はかからないはず）再実施~~
普通に課金されました。（気づいてすぐ止めたので2$くらい）
[Pricing](https://elements.heroku.com/addons/mongohq) 見ると最低プランで 18$/month なので、今は無料やってないのかな？

```
$ heroku addons:add mongohq
$ pip install pymongo
$ pip freeze > requirements.txt
```

app 修正（汚いけどまとめるためこうしてます）
※ 後述するが数カ所誤りあり

```py:hello.py
from flask import Flask, render_template
app = Flask(__name__)

# add for MongoHQ test
import os
from urlparse import urlparse
from pymongo import Connection

MONGO_URL = os.environ.get('MONGOHQ_URL')

if MONGO_URL:
    con = Connection(MONGO_URL)
    db = con[urlparse(MONGO_URL).path[1:]]
else:
    con = Connection('localhost', 27017)
    db = con['flask_test']

@app.route('/count/<name>')
def count(name=''):
    if name=='':
        name = 'NoName'

    count_obj = db.count.find_one({'name': name})
    if not count_obj:
        count_obj = {'name': name, 'count': 1}
    else:
        count_obj['count'] += 1
    db.count.save(count_obj)

    return render_template('count.html', title="Count Page", count=count_obj)
# /add

@app.route('/')
def hello():
    return render_template('hello.html', title="this is Title!", name="kokomi")

@app.route('/good')
def good():
    return "Good page."

if __name__ == '__main__':
    app.run(debug=True)

```

view 追加
※ 英語力の苦情は受け付けません

```html:count.html
{% extends "layout.html" %}
{% block content %}
<h3>Count</h3>
hello, {{ count.name }}.
you visit this page {{ count.count }} times.
{% endblock %}
```

こんな感じで

```bash
$ tree .
.
├── Procfile
├── hello.py
├── requirements.txt
├── runtime.txt
└── templates
    ├── count.html
    ├── hello.html
    └── layout.html
```
 
再度デプロイ

```
$ git add .
$ git commit -m "add counter"
$ git push heroku master
```

アクセスしてみる
![スクリーンショット 2017-10-25 17.43.58.png](https://qiita-image-store.s3.amazonaws.com/0/88428/ccb63b7d-4a4c-a2a2-b748-47546e1c19cf.png "スクリーンショット 2017-10-25 17.43.58.png")

┐(´д｀)┌
慌てず騒がず `heroku logs` してログ表示

```
$ heroku logs
（前略）
2017-10-25T08:39:55.456744+00:00 app[web.1]: ModuleNotFoundError: No module named 'urlparse'
（後略）
```

ｱｯﾊｲ

```
$ pip install urlparse
Collecting urlparse
  Could not find a version that satisfies the requirement urlparse (from versions: )
No matching distribution found for urlparse
```

ないんかーい
仕方ないので[調べて修正](https://docs.python.jp/3/library/urllib.parse.html)

`urlparse` → `urllib.parse` に変更して push→まだ駄目

慌てず騒がず（略

```
（前略）
2017-10-25T08:51:53.122808+00:00 app[web.1]: ImportError: cannot import name 'Connection'
（後略）
```

Oh...
どうやら[バージョンが違う](http://api.mongodb.com/python/current/tutorial.html)模様

`Connection` → `MongoClient` に変更
最終的に↓な感じ

```py:hello.py(before抜粋)
import os
from urlparse import urlparse
from pymongo import Connection

MONGO_URL = os.environ.get('MONGOHQ_URL')

if MONGO_URL:
    con = Connection(MONGO_URL)
    db = con[urlparse(MONGO_URL).path[1:]]
else:
    con = Connection('localhost', 27017)
    db = con['flask_test']

@app.route('/count/<name>')
def count(name=''):
    if name=='':
        name = 'NoName'

    count_obj = db.count.find_one({'name': name})
    if not count_obj:
        count_obj = {'name': name, 'count': 1}
    else:
        count_obj['count'] += 1
    db.count.save(count_obj)

    return render_template('count.html', title="Count Page", count=count_obj)
```

↓

```py:hello.py(after抜粋)
import os
from urllib.parse import urlparse
from pymongo import MongoClient

MONGO_URL = os.environ.get('MONGOHQ_URL')

if MONGO_URL:
    con = MongoClient(MONGO_URL)
    db = con[urlparse(MONGO_URL).path[1:]]
else:
    con = MongoClient('localhost', 27017)
    db = con['flask_test']

@app.route('/count/<name>')
def count(name=''):
    if name=='':
        name = 'NoName'

    count_obj = db.count.find_one({'name': name})
    if not count_obj:
        count_obj = {'name': name, 'count': 1}
    else:
        count_obj['count'] += 1
    db.count.save(count_obj)

    return render_template('count.html', title="Count Page", count=count_obj)
```

アクセス！
![スクリーンショット 2017-10-25 18.01.05.png](https://qiita-image-store.s3.amazonaws.com/0/88428/b6fec640-2ef8-16a4-66c2-74de99ba7611.png "スクリーンショット 2017-10-25 18.01.05.png")

やったー！ :tada: 


# 感想

- マイクロフレームワークというだけあって 1 ファイルで書けて簡単でとっつきやすい
- 構造が単純だからどこに何があるのかすぐ分かって楽
- 普通に Python でそのまま書けるので今までローカルで動かしてた自作スクリプトとか Web に移行しやすそうで良い