Kuzunoha-NEのブログ

プログラミングなどの勉強をしてます

【Flask】Configについて

とても参考になったサイト

https://qiita.com/nanakenashi/items/e272ff1aafb3889230bc

https://www.subarunari.com/entry/2018/03/17/いまさらながらFlaskについてまとめる_〜Configuration〜

http://flask.pocoo.org/docs/1.0/config/

https://damyanon.net/post/flask-series-configuration/

Configを使おうというお話。

あなたは何らかのデータベースサーバーやあるいはGoogleSpreadSheetsと連携してプログラムを書いていることでしょう。

書いている内容としてはこんな感じでしょうか。

gss_url = "http://google.****.com"

さて、本番と開発をわけた開発をするとした場合、このgss_urlの値をツドツド変更しているのかもしれません。あるいは、コメントアウトして使っているのかもしれません。

# 本番
# gss_url = 'http://google.****.com'
# 開発
gss_url = 'http://test.google.****.com'

ただ、どちらかといえば本番時はhttp://google.****.com、そうでないときはhttp://test.google.****.comと条件分岐で指定してあげたいところです。

if HONBAN == True:
    gss_url = 'http://google.****.com'
else:
    gss_url = 'http://test.google.****.com'

また、Flaskなら開発環境でDEBUGモードで開発していると思います。本番でDEBUGモードが起きるのはセキュリティ的によくないでしょう。ただ、その複数の切り替えを手動で行っていると絶対忘れが発生して事故るでしょう。

app.run(debug=True)
# app.run()

開発環境、本番環境によって変えたい項目は沢山あるはずです。もう一つテスト環境というものがあると思います。

テスト環境はテスト用のデータが挿入された環境であることが望ましいかも知れませんし、あるいはデータそのものが全くない環境が望ましいものかもしれません。

テスト環境1
テスト環境2
開発環境
本番環境

それぞれの状況でアクセスするDBのアドレスやGSSのアドレスが切り替わるような仕組みがあったら便利ですね。今回は環境変数を使って切り替わりを実施できるような仕組みを考えます。

Flaskのデバッグモードとconfigについて

Flaskクラスのインスタンスにはconfigというものがあります。configはdictionaryとなっています。

app.pyとして以下を作成します。

from flask import Flask
app = Flask(__name__)
print(app.config)

python app.pyとして起動すると以下のようになります。

[kuzunoha@:11:09:05:~/sand_box/flaskconfig]$python app.py 
<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>

また、app.pyの3行目を更新してprint(app.config['DEBUG'])として実行します。

app.py

from flask import Flask
app = Flask(__name__)
print(app.config['DEBUG'])

実行結果

[kuzunoha@:11:09:10:~/sand_box/flaskconfig]$python app.py 
False

次にapp.debug = Trueを追記してprintしてみます。

from flask import Flask
app = Flask(__name__)
app.debug = True
print(app.config['DEBUG'])

これを実行すると

[kuzunoha@:11:14:58:~/sand_box/flaskconfig]$python app.py 
True

それではapp.config['Debug'] = Falseと追記してからprintしてみます。

from flask import Flask
app = Flask(__name__)
app.debug = True
app.config['DEBUG'] = False
print(app.config['DEBUG'])

これを実行すると

[kuzunoha@:11:17:35:~/sand_box/flaskconfig]$python app.py 
False

すなわち以下の2つは同義であるということです。

app.debug = Boolean
app.config['DEBUG'] = Boolean

また、app.run(debug=True)で設定するDebugの真偽も同様です。

その他にも環境変数を使用するなどflaskをデバッグモードで実施する方法はあるようです。

参考:http://flask.pocoo.org/docs/1.0/quickstart/#debug-mode

このようにconfig[PARAMETAR]を変更することで、Flaskアプリケーションのコンフィグを変更できます。

その他のデフォルトのパラメータは公式に載っています。

参考:http://flask.pocoo.org/docs/0.12/config/#builtin-configuration-values

オリジナルのパラメータを作る

flaskのコンフィグパラメータは好きに作ることが出来ます。app.pyを以下のように記載します。

from flask import Flask
app = Flask(__name__)
app.config['hogehoge'] = 'piyopiyo'
print(app.config['hogehoge'])

そして実行すると

[kuzunoha@:11:19:08:~/sand_box/flaskconfig]$python app.py 
piyopiyo

となります。そして、これはすなわち以下のようにも使えます。

from flask import Flask
app = Flask(__name__)
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
print(app.config['MYSQL_ADDRESS'])

実行すると

[kuzunoha@:11:34:35:~/sand_box/flaskconfig]$python app.py 
127.0.0.1

となります。ただ、以下のようにインスタンスとして持ち合わせることは出来ません。

from flask import Flask
app = Flask(__name__)
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
print(app.config.mysql_address)

Traceback (most recent call last):
  File "app.py", line 4, in <module>
    print(app.config.mysql_address)
AttributeError: 'Config' object has no attribute 'mysql_address'

さて、それではその他にもパラメータを追加してMySQLに繋げてみましょう。以下が一例になります。(ほんとにただの一例ですのでこれで実行しても何も起きないです。)

from flask import Flask
app = Flask(__name__)
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'password'
app.config['MYSQL_PORT'] = 3306
app.config['MYSQL_DATABASE'] = 'hogepiyodb'

import MySQLdb
connection = MySQLdb.connect(
    host=app.config['MYSQL_ADDRESS'],
    user=app.config['MYSQL_USER'],
    passwd=app.config['MYSQL_PASSWORD'],
    port=app.config['MYSQL_PORT'],
    db=app.config['MYSQL_DATABASE'])
connection.commit()
connection.close()

MySQLdbというモジュールについては以下の参考サイトを見てください。

参考:https://uxmilk.jp/23509

app.config['MYSQL_ADDRESS'] = '127.0.0.1'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'password'
app.config['MYSQL_PORT'] = 3306
app.config['MYSQL_DATABASE'] = 'hogepiyodb'

と、このようにパラメータに値を代入していくことが出来るのです。

パラメータの代入方法の選択

Configのパラメータには直接値を代入する方法以外にも方法があります。

参考:https://www.subarunari.com/entry/2018/03/17/いまさらながらFlaskについてまとめる_〜Configuration〜

以下がそれぞれ同じ設定方法になります。ここに記載してる方法以外のものもあります。

ハードコーディング(参考)

上記に書いたものと同じですね。

app.py

from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['MYSQL_ADDRESS'] = '127.0.0.1'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'password'
app.config['MYSQL_PORT'] = 3306
app.config['MYSQL_DATABASE'] = 'hogepiyodb'
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

結果は以下の感じ

[kuzunoha@:12:14:56:~/sand_box/flaskconfig]$python app.py 
True
127.0.0.1
root
password
3306
hogepiyodb

from_pyfile

cfgファイルを読み込むタイプです。app.pyと同じディレクトリにdevelopment.cfgというcfgファイルを作って、それを読み込むようにします。

development.cfg

DEBUG=True
MYSQL_ADDRESS='127.0.0.1'
MYSQL_USER='root'
MYSQL_PASSWORD='password'
MYSQL_PORT=3306
MYSQL_DATABASE='hogepiyodb'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_pyfile('development.cfg')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

app.pyの3行目app.config.from_pyfile('development.cfg')に注目です。

app.config.from_pyfile(CFGファイル名)となっております。

実行結果は以下の通り

[kuzunoha@:12:29:10:~/sand_box/flaskconfig]$python app.py
True
127.0.0.1
root
password
3306
hogepiyodb

from_object

設定のクラスが書かれたPythonファイルを読み込むようにします。

setting.py

class Development(object):
    DEBUG=True
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('setting.Development')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果は以下の通り

[kuzunoha@:12:37:18:~/sand_box/flaskconfig]$python app.py 
True
127.0.0.1
root
password
3306
hogepiyodb

app.pyの3行目app.config.from_object('setting.Development')に注目です。

app.config.from_object('Pythonファイル名.オブジェクト名')となっていることがわかると思います。

オブジェクトを取得するようになっているので、setting.pyに新しいクラスを作って読み込ませることも出来ます。クラスなので継承を使って新しいクラスを作っても良いです。

setting.py

class Development(object):
    DEBUG=True
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

class Production(Development):
    DEBUG=False
    MYSQL_ADDRESS='63.31.125.212'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('setting.Production')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果はこうなりました

[kuzunoha@:12:54:12:~/sand_box/flaskconfig]$python app.py 
False
63.31.125.212
root
password
3306
hogepiyodb

DEBUGMYSQL_ADDRESSの値が変更されていることと、それ以外は継承元のそれと同じであるというところが注目点です。

from_pyfile + from_object

ではこの2つを合体して使ってみるとどうでしょうか

password.cfg

MYSQL_PASSWORD='heavensdoor'

setting.py

class Development(object):
    DEBUG=True
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

class Production(Development):
    DEBUG=False
    MYSQL_ADDRESS='63.31.125.212'

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('setting.Production')
app.config.from_pyfile('password.cfg')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果は以下の通りです。

[kuzunoha@:13:10:48:~/sand_box/flaskconfig]$python app.py 
False
63.31.125.212
root
heavensdoor
3306
hogepiyodb

最終的なMYSQL_PASSWORDheavensdoorになりました。app.pyの3-4行目を見てみてください。

app.config.from_object('setting.Production')
app.config.from_pyfile('password.cfg')

変数のそれと同じく、最後に宣言された値に上書きされていくからです。

環境変数を使ってそれぞれの環境にConfを変える

from_objectで読み込むクラスを変更すれば各環境ごとに使うconfigを変更できます。

# 開発なら
app.config.from_object('setting.Development')
# 本番なら
app.config.from_object('setting.Production')

辞書型を使うともう少しスマートになります。

dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}

# 開発なら
confmode = dict_confmode['dev']
# 本番なら
confmode = dict_confmode['pro']

app.config.from_object(confmode)

さらに環境変数を使います。例えばFLASK_CONFIGURATIONという環境変数名に開発ならdev、本番ならproと入れておきます。

---開発環境なら---

環境変数

FLASK_CONFIGURATION=dev

app.pyの一部

import os

dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}

env_confmode = os.getenv('FLASK_CONFIGURATION')

confmode = dict_confmode[env_confmode]

app.config.from_object(confmode)

---本番環境なら---

環境変数

FLASK_CONFIGURATION=pro

app.pyの一部

import os

dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}

env_confmode = os.getenv('FLASK_CONFIGURATION')

confmode = dict_confmode[env_confmode]

app.config.from_object(confmode)

それぞれのapp.pyには全く違いはありません。この通り、PCに宣言した環境変数だけで環境を変更できるようになりました。

参考:https://wa3.i-3-i.info/word11027.html

共有することがあまりよろしくない値はfrom_pyfileへ

DBのpasswordや暗号方法、暗号キーやアクセストークンなどはcfgファイルへ記載するようにします。

そうしてinstanceディレクトリを作ってそのcfgファイルを置いておくようにします。

アプリケーションを初期化する際にinstance_relative_config=Trueという引数を当ててあげます。そうすることでinstanceというディレクトリからcfgファイルを読みに行くようになります。

app = Flask(__name__, instance_relative_config=True)

そうして、.gitignoreinstanceを入れてあげます。

最終的な構成はこのようになりました。

app.py
setting.py
instance/
 ┗ password.cfg

password.cfg

MYSQL_PASSWORD='heavensdoor'

setting.py

class Development(object):
    DEBUG=True 
    MYSQL_ADDRESS='127.0.0.1'
    MYSQL_USER='root'
    MYSQL_PASSWORD='password'
    MYSQL_PORT=3306
    MYSQL_DATABASE='hogepiyodb'

class Production(Development):
    DEBUG = False
    MYSQL_ADDRESS='63.31.125.212'

app.py

import os

from flask import Flask

app = Flask(__name__, instance_relative_config=True)
dict_confmode = {
    'dev' : 'setting.Development',
    'pro' : 'setting.Production'
}
env_confmode = os.getenv('FLASK_CONFIGURATION')
confmode = dict_confmode[env_confmode]
app.config.from_object(confmode)
app.config.from_pyfile('password.cfg')
print(app.config['DEBUG'])
print(app.config['MYSQL_ADDRESS'])
print(app.config['MYSQL_USER'])
print(app.config['MYSQL_PASSWORD'])
print(app.config['MYSQL_PORT'])
print(app.config['MYSQL_DATABASE'])

実行結果

[kuzunoha@:14:38:21:~/sand_box/flaskconfig]$export FLASK_CONFIGURATION=dev
[kuzunoha@:14:38:30:~/sand_box/flaskconfig]$python app.py 
True
127.0.0.1
root
heavensdoor
3306
hogepiyodb
[kuzunoha@:14:38:32:~/sand_box/flaskconfig]$export FLASK_CONFIGURATION=pro
[kuzunoha@:14:38:43:~/sand_box/flaskconfig]$python app.py 
False
63.31.125.212
root
heavensdoor
3306
hogepiyodb

参考 Windows環境変数設定:https://www.k-cube.co.jp/wakaba/server/environ.html

参考 Unix,Linux,Mac環境変数設定:https://qiita.com/iam1at/items/91cb8478160c9fbee134

残りはベストプラクティスを見てみてください。

細かいところは違いますが、以下のURLの内容がよくわかるようになったと思います。後はここを読んでください。

https://qiita.com/nanakenashi/items/e272ff1aafb3889230bc