Flask + SQLAlchemyプロジェクトを始める手順

いつも行っていることをまとめる。

参考にするドキュメント

- [Welcome to Flask — Flask Documentation (0.12)](http://flask.pocoo.org/docs/0.12/)
- [SQLAlchemy - The Database Toolkit for Python](http://www.sqlalchemy.org/)
- [Flask-SQLAlchemy — Flask-SQLAlchemy Documentation (2.3)](http://flask-sqlalchemy.pocoo.org/2.3/)

今回の利用した環境は以下の通り。

- MacOS 10.13.3
- Python 3.6.3
- flask 0.12.2
- flask-sqlalchemy 2.3.2
- PyMySQL 0.8.0
- SQLAlchemy 1.2.2


## 必要なライブラリをインストールする

- flask
- SQLAlchemy
- flask-sqlalchemy
- pymysql

をインストールする。

```
$ pip install flask SQLAlchemy flask-sqlalchemy PyMySQL
```


## アプリ初期構築
アプリの初期構築を行う。
Flaskは自由度が高いのでアプリのディレクトリ構成は様々な形を取ることができるが、以下はその一例。この例に沿って以下話を進める。

```
├── flask_sample
│   ├── app.py
│   ├── config.py
│   └── database.py
└── run.py
```

- `flask_sample/database.py`

```python
"""FlaskアプリがSQLAlchemyを使えるようにするための初期化"""
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


def init_db(app):
    db.init_app(app)
```

- `flask_sample/config.py`

```python
"""FlaskのConfigを提供する"""
import os


class DevelopmentConfig:

    # Flask
    DEBUG = True

    # SQLAlchemy
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/flask_sample?charset=utf8'.format(**{
        'user': os.getenv('DB_USER', 'root'),
        'password': os.getenv('DB_PASSWORD', ''),
        'host': os.getenv('DB_HOST', 'localhost'),
    })
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_ECHO = False


Config = DevelopmentConfig
```

※1 上記の `SQLALCHEMY_DATABASE_URI` は、DriverにPyMySQLを利用した場合の書き方になっている。PyMySQLとは別のDriverを用いる場合は別の設定にする必要がある。詳しくはリンク先ページ参照。 http://docs.sqlalchemy.org/en/latest/dialects/mysql.html
※2 今回Database名を `flask_sample` としている。当然MySQLからCREATE DATABASE文を使ってDatabaseを先に作っておく必要がある。（Databaseの作成方法は割愛）


- `flask_sample/app.py`

```python
"""flask appの初期化を行い、flask appオブジェクトの実体を持つ"""
from flask import Flask

from flask_sample.database import init_db


def create_app():
    app = Flask(__name__)
    app.config.from_object('flask_sample.config.Config')
    
    init_db(app)

    return app

app = create_app()
```

- `run.py`

```python
from flask_sample.app import app


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

このアプリを起動する場合は、以下のコマンドを実行する。

```
$ python run.py
```

あるいは環境変数`FLASK_APP`を定義して`flask`コマンドを利用することでもアプリを起動することができる。

```
$ FLASK_APP=run.py flask run
```

これでFlaskでSQLAlchemyを利用する基本的な準備は整った。

## Modelを一つ作ってみる
実際にModelクラスを一つ作ってみることにする。

### Modelクラスの定義
以下のように `flask_sample/models` 以下を用意する。

```
flask_sample

├── app.py
~
└── models
    ├── __init__.py
    └── models.py
```

- `flask_sample/models/models.py`

```python
from datetime import datetime
from flask_sample.database import db


class User(db.Model):

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
    updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
```

- `flask_sample/models/__init__.py`

```python
from .models import User


__all__ = [
    User,
]
```

そして、appがロードされた時にこのUserクラスもloadされるように、`flask_sample/app.py`内でUserクラスをimportするようにする。

```python
from flask import Flask
from flask_sample.database import init_db
import flask_sample.models

...

```


### テーブルの作成

> ※2019/01/03追記
この直後に説明する `Flask-Migrate` を使ったMigrationを実施する場合は、ここで説明している `db.create_all()` を使ったテーブルの作成は行うべきではありません。 
`Flask-Migrate` ではテーブルの状態管理をしており、 `Flask-Migrate` を使わずにテーブル作成した場合、未知の状態を与えてしまうためです。もし、 `Flask-Migrate` を利用する予定の場合はこの項の説明を読み飛ばして次の説明を読み進めることをおすすめします。

> 参考issue
https://github.com/miguelgrinberg/Flask-Migrate/issues/173


Modelクラスを用意したら、次にDatabaseにテーブルを作成する。
テーブルを作成するには `SQLAlchemy.create_all()` を呼び出す。これでimportされているModelクラスのテーブルが全て作成される。

```
$ FLASK_APP=run.py flask shell
Python 3.6.3 (default, Jan  8 2018, 18:42:55)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
App: flask_sample.app [debug]
Instance: ~/flask-sample/instance
>>> from flask_sample.database import db
>>> db.create_all()
```

これで `users` テーブルが作成された。


## migrationを行うには
直前の手順で、定義しているModelクラスに関するテーブルが作成されたわけだが、都度手動で `db.create_all()` を呼び出してテーブルを定義するには少し面倒が多く、きちんとschemaのバージョン管理を行うことも含めてMigrationの導入を行う。

Flask + SQLAlchemy でMigrationを行うには [Flask-Migrate](https://flask-migrate.readthedocs.io/en/latest/) を使うと便利。


### Flask-Migrateのインストール
pipでインストール可能。

```
$ pip install Flask-Migrate
```

### Flask-Migrateを使う
Flask-Migrateを使うには、`flask_migrate.Migrate` にflaskオブジェクトと `flask_sqlalchemy.SQLAlchemy` オブジェクトを食わせることでFlask-MigrateのCLIコマンドを使うことが可能になる。以下は上記までのサンプル上でFlask-Migrateを使う場合の例。

- `flask_sample/database.py`

```python
"""FlaskアプリがSQLAlchemyとFlask-Migrateを使えるようにするための初期化"""
from flask_sqlalchemy import SQLAlchemy

from flask_sample.database import init_db

from flask_migrate import Migrate  # 追加

db = SQLAlchemy()


def init_db(app):
    db.init_app(app)
    Migrate(app, db)  # 追加
```

こうした後では `flask db` コマンドが利用可能になる。

```
$ FLASK_APP=run.py flask db init
```

### Flask-Migrateのコマンド例
最低限のコマンド例を示す。 

https://flask-migrate.readthedocs.io/en/latest/#command-reference
こちらにコマンドリファレンスがあるので、これで利用可能なコマンドの全貌がわかる。

※以下のコマンド例は環境変数 `FLASK_APP` を適切に設定している場合における場合。

#### 初期化
```
$ flask db init
```

`migrations`ディレクトリが作成され、その中で必要なファイルが設置される。


#### migrationファイルの作成
```
$ flask db migrate
```

modelクラスの定義を見て、差分をmigrationファイルとして作成する。（まだmigrationの適用はされない）


#### migrationの実行
```
$ flask db upgrade
```

未実行のmigrationを実行する。


#### migrationのrollback
```
$ flask db downgrade
```

一つ前のバージョンに戻す。


## まとめ
- Flaskアプリの初期構築
- Flask-SQLAlchemyの使い方(Model定義)
- Flask-Migrateの使い方

を簡単に説明した。これでベーシックなFlask + SQLAlchemyを使ったアプリケーション構築のための準備が整った。
追加でこれをしていくといいよ、というのがあればどしどし。