#前回のあらすじ
###FlaskローカルサーバでHello Worldした
前回記事:Python初心者がFlaskでLINEbotを作ることになった(Flaskざっくり解説編)
###オウム返しbotを作ってみた(割愛)
以下の記事を参考に、オウム返しbotをHerokuに作り、HerokuとLINE bot SDKは何となく使えるようになりました。
LINEbotの大枠は、オウム返しbotを作ってみると理解できると思います。
とてもわかりやすい記事が数多くあるので、この部分については省略します。
参考: Pythonでオウム返しをするLine botを作成
#次はDB接続だ!
ただ言われたことを返すだけでは、ちょっとつまらない。
特定のワードに、対応した返答を返してほしい。
プログラムの場合分けだけでは限界があるので、やはりデータベースは使いたい!
##botは「セッション」という考え方がない・・・?
例えば、
「今日の夕飯は?」→『カレーライスです』→「辛い?」→『激辛です』
みたいに、前の会話を元にして次の会話に進めたら、それっぽいですよね。
前のカレーライスの会話がなければ、急に「辛い?」なんて聞かれても『つらい、ですか???』とかなってしまいそうです。
Webであれば、ログイン情報や入力データはCookieやセッションなどに持たせることができますが、botはブラウザアクセスではないので、これができません。
##会話状況やユーザーを確認するには、DBが不可欠
LINEから送られてくるのは、ユーザIDとメッセージ、リプライトークンくらいなので、
ユーザIDをキーとして、「今、何について話しているのか」をサーバ側で保存しておかないといけないわけです。
どうやって現在の話題を記録するか、についてはまた後程考えることにして、
とにかくデータベースが使えないと話にならない!
##FlaskとMySQLをDockerに載せる
ということで、MySQLとFlaskの開発環境をDockerの上で構築することにしました。
オウム返しbotで使ったHerokuの環境はいったん置いておきます。。
FlaskからMySQLのDBを扱うために、SQLAlchemyというORM(オブジェクト・リレーショナル・マッパー)モジュールなるものを使うことにしました。
ドライバはPyMySQL(PythonのMySQLクライアントモジュール)を使います。
DBスキーマをマイグレーション管理するため、Flask-Migrateもインストールします。
#サンプル:Peterを増やそう大作戦
LINEを使ったアプリにする前に、データベースを使えるようにしましょう。
ということで、WebアクセスでUserテーブルのPeterさんが増えたり減ったりするプログラムを作ってみます。
ツリー構造はこんな感じ。
前提として、Docker,Docker-composeをインストールしています。
FLASK_SQLALCHEMY
│ docker-compose.yml
│
├─docker
│ │ Dockerfile
│ │ requirements.txt
│ │
│ └─db
│
└─server
└─project
│ run.py
│
└─Peter
│ app.py
│ config.py
│ database.py
│ __init__.py
│
└─models
│ models.py
└─ __init__.py
###Docker関係のファイル
####docker-compose.yml
version: '3'
services:
flask:
container_name: linebot-python
build: ./docker
ports:
- 5000:5000
links:
- mysql
privileged: true
volumes:
- ./server/project:/project
tty: true
environment:
TZ: Asia/Tokyo
FLASK_ENV: 'development' #デバッグモードON
FLASK_APP: 'run.py' #起動用アプリの設定
command: flask run -h 0.0.0.0
mysql:
container_name: linebot-db
image: mysql:5.5
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: peter
MYSQL_USER: hoge
MYSQL_PASSWORD: huga
TZ: 'Asia/Tokyo'
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- ./docker/db/data:/var/lib/mysql
- ./docker/db/sql:/docker-entrypoint-initdb.d
ports:
- 3306:3306
flaskのコンテナは5000番ポート、mysqlは3306番ポートで接続しています。
flaskのcommandでflask run -h 0.0.0.0
を記載しておくと、
コンテナ起動と同時にFlaskサーバが立ち上がり結果を確認することができます。
今回はデバッグモードでFlaskを起動したいので、environmentにFLASK_ENV: 'development'
を追加しています。
デバッグモードが不要の場合はコメントアウトします。
環境変数FLASK_APP: 'run.py'
についてですが、
今回起動用のファイルと主機能を載せたファイルを分離しているので、
起動用のファイルを指定しています。
これがないと、projectフォルダ直下でapp.py(デフォルトのアプリファイル名)が見つからずにエラーとなってしまいます。
MySQLは仕事上の制約で5.5にしてますが、これはお好みで。
MySQLの文字コード指定は、my.cnfを設定してもよいのですが、
コマンドで指定したほうが早そうなのでこちらにしました。
####Dockerfile
FROM python:3.6
ARG project_dir=/project/
ADD ./requirements.txt $project_dir
WORKDIR $project_dir
RUN pip install -r requirements.txt
requirements.txtに一覧化されているモジュールを一括インストールしています。
いちいちpipしなくていいので便利です。
####requirements.txt
Flask
sqlalchemy
flask-sqlalchemy
flask-migrate
pymysql
利用したいモジュールをリスト化しています。
####db
フォルダだけ作っておきます。
先に読ませたいSQLを置くこともできますが、今回は特にないので何もしません。
MySQLコンテナを立ち上げたときにvolumes指定したものを自動で作ってくれます。
###SQLAlchemyの設定
ここからは、Flaskのソースコードになります。
server/project/
にソースコードを配置します。
####config.py
import os
class DevelopmentConfig:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{database}?charset=utf8mb4'.format(**{
'user': os.getenv('DB_USER', 'hoge'),
'password': os.getenv('DB_PASSWORD', 'huga'),
'host': os.getenv('DB_HOST', '[MySQLコンテナのIP]'),
'database': os.getenv('DB_DATABASE', 'peter')
})
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
Config = DevelopmentConfig
環境変数の設定ファイルです。
DBのhostは、dockerのネットワークを調べたうえで記入する必要があります。
なので、コンテナをbuild,upした後で記入します。
####database.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
def init_db(app):
db.init_app(app)
Migrate(app, db)
SQLAlchemyの初期化用ファイルです。
####models
今回はPeterさんを増やすので、IDと名前だけ記録するUserテーブルを作ります。
作成日時・アップデート日時は実際必要になることが多いので、備忘録がてら取り入れています。
from datetime import datetime
from Peter.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)
def __repr__(self):
return '<User id={id} name={name}>'.format(
id=self.id, name=self.name)
from .models import User
__all__ = [
User,
]
###Flaskアプリ動作の設定
ここまで作った枠組みを、実際に動かす機能を作ります。
先述の通り、主機能を持ったapp.py
と起動のみのrun.py
に分けています。
####app.py
from flask import Flask
from Peter.database import init_db, db
from Peter.models import User
def create_app():
app = Flask(__name__)
app.config.from_object('Peter.config.Config')
init_db(app)
@app.route('/')
def index():
return 'Peterが増えるプログラムです'
@app.route('/show')
def show_users():
all_peter = User.query.filter_by(name='peter').all()
how_many_peter = len(all_peter)
return '今Peterは{}人います'.format(how_many_peter)
@app.route('/add')
def add_user():
peter = User(name='peter')
db.session.add(peter)
db.session.commit()
return 'Peterを増やしました。'
@app.route('/delete')
def delete_user():
peter = User.query.filter_by(name='peter').first()
if peter is not None:
db.session.delete(peter)
db.session.commit()
return 'Peterを減らしました。'
else:
return 'Peterはひとりもいません'
return app
app = create_app()
localhost:5000/
でこのサービスの概要説明、
localhost:5000/add
でPeterが増える
localhost:5000/delete
でPeterが減る
localhost:5000/show
でPeterの人数がわかる、といった具合です。
####run.py
from Peter.app import app
if __name__ == '__main__':
app.run()
####__init__.py
何も書かなくてよいですが、ファイルだけ作成しておきます。
(ないとエラーになります。)
##Dockerコンテナを起動して、トップページにアクセス
これで、DBを使わないトップページだけならアクセスできるはずです。
一度コンテナを起動します。
まず、
イメージをビルドします。
$ docker-compose build
処理が終わったら、コンテナを起動します。
$ docker-compose up
-d
オプションを付けるとバックグラウンドで起動しますが、きちんとFlaskサーバが動いているかを確認したいので、最初はオプションなしで起動をお勧めします。
↓のように出力されればOKです。
linebot-python | * Serving Flask app "run.py" (lazy loading)
linebot-python | * Environment: development
linebot-python | * Debug mode: on
linebot-python | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
linebot-python | * Restarting with stat
linebot-python | * Debugger is active!
linebot-python | * Debugger PIN: 122-417-323
Webブラウザで、localhost:5000
にアクセスしてみましょう。
「Peterが増えるプログラムです」と表示されればOK!
コンテナから抜けるときはCTRL+Cを押します。(コンテナが停止します)
##DBのホストを設定する
このまま/add
などにアクセスしても、DBのホストを設定していませんし、
そもそもテーブルを作っていないのでエラーになります。
まずはホスト設定を行います。
dockerのネットワークを調べて、MySQLコンテナのIPアドレスを探します。
サンプルアプリが属しているネットワークを調べます。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
573eb5f34bc9 bridge bridge local
ebd6757e246e flask_sqlalchemy_default bridge local
28b6ef9d5831 host host local
b28c335f87bb none null local
flask_sqlalchemy_default
っていうのが該当していますね。
このネットワークを詳しく見てみます。
$ docker network inspect flask_sqlalchemy_default
~~中略~~
"Containers": {
"8eba3e42ef3b89ba116e27a755abe135aae5fd5e2ff98382e6906e65cfe406b5": {
"Name": "linebot-db",
"EndpointID": "02cc7765950bb9bca313a0cd8eb0001b5417eaab1225b02220f9ad63e2d71cf6",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"9cd7f8c9eea6abb37662dc7d8f49eb19fa42735dc7df6348becbcc30fe315dd1": {
"Name": "linebot-python",
"EndpointID": "d440a077abbb83905ca290c80e2900f6e7da58990826fb2b6932e925728a4a45",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
}
~~中略~~
私の環境では、linebot-dbコンテナは172.18.0.2、linebot-pythonコンテナは172.18.0.3であることがわかります。
※内部で利用しているIPアドレスなので、外部からアクセスできません。
Webブラウザでたたいてもエラーになるはずです。
このIPアドレスを、config.pyのhost部分に記入します。
##migrateする
マイグレーションを行って、データベースを作ります。
作業のために、コンテナ内に入りますので、コンテナをバックグラウンドで起動するか、コマンドラインを複数立ち上げます。
$ docker exec -it linebot-python bash
コンテナ内で以下の操作を行います。
root@[コンテナID]:/project# flask db init
root@[コンテナID]:/project# flask db migrate
root@[コンテナID]:/project# flask db upgrade
これでFlaskコンテナを経由して、MySQLコンテナにUserテーブルが作られたはずです。
(データベースそのものはdocker-compose.ymlでpeterデータベースを作成済み)
MySQLコンテナに入って、DBの様子を確認できます。
$ docker exec -it linebot-db bin/bash
root@[コンテナID]:/# mysql -u hoge -phuga peter
mysql>show tables;
+-----------------+
| Tables_in_peter |
+-----------------+
| alembic_version |
| users |
+-----------------+
2 rows in set (0.01 sec)
##実際にアクセスしてみる
localhost:5000/add
にアクセスしてみましょう。
Peterが増えたら、localhost:5000/show
で人数を確認。
deleteも試してみます。
#次回はLINEbotアプリとDB接続の組み合わせ
オウム返しbot作成で学んだbotの作り方を、今回のサンプルアプリに組み合わせてみようと思います。
前回も今回も、LINEbotと直接関わりない内容だったので、タイトル詐欺にならないよう気を付けます。
#参考記事一覧
・PythonのFlaskでMySQLを利用したRESTfulなAPIをDocker環境で実装する
・Flask + SQLAlchemyプロジェクトを始める手順
・Flask-SQLAlchemyの使い方
・Docker公式イメージのMySQLで文字コードを指定する
・SQLAlchemy で Update するには?
・SQLAlchemyについて(公式)
大変勉強になりました!ありがとうございました!!