# Flask實作_建置一個使用者註冊頁面_04_調整系統配置
###### tags: `python` `flask` `config`
:::danger
參考:
* [簡中翻譯官方文檔](http://www.pythondoc.com/flask/config.html#id3)<sub>注意到目前官方文檔已經是正式版本,部份功能有所落差</sub>
:::
Flask以及它的所有的擴展都擁有自己的參數配置,也因此這些參數的配置非常的重要,影響整個網站的執行。
參數的配置有很多種方式,官方文件建議採行『工廠模式』<sub>(軟體設計)</sub>來執行,無奈個人火候未到,無法理解『工廠模式』的意函,無法清楚說明,但miguelgrinberg在他的flasky範例中所使用即為『工廠模式』,可見下方<a href='#ext_1'>延伸閱讀</a>。
## 作業說明
實務上,基礎參數的設置會在專案根目錄上建置一個Python文件,設置一個類別來導入,在專案啟動的時候透過`app.config.form_object`來渲染參數,但針對敏感性參數還是建議採用『環境變數』來設置。
:::success
* 文件:config.py
* 說明:在root處加入config.py,並加入一個Config類別設置參數
```python=
import os
class Config:
pjdir = os.path.abspath(os.path.dirname(__file__))
DEBUG = True
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + \
os.path.join(pjdir, 'static\\data\\data_register.sqlite')
SECRET_KEY = b'\xb9k\xdf@\x0e\x1f(\xf2\xb0\xd0\xcb?Y\xdcN\x19G\x12e\xa8\x8b\xe5\xccS'
```
將初始化文件上的參數移置`config.py`
:::
接著,我們在專案初始化的時候import,渲染相關應用程式,如下:
:::success
* 文件:app_blog\\_\_init\_\_.py
* 說明:專案初始化的時候import參數,渲染相關應用程式
```python=
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bootstrap import Bootstrap
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
#...下略...#
```
第4行:導入參數設置類別
第7行:渲染參數
:::
在上面範例中,我們透過了`app.config.from_object`來渲染系統參數。此方式有兩個問題:
1. 如果在測試環境跟正式環境參數不同怎麼辦?
2. 如果想將專案push到git上開源又不想給人知道敏感參數怎麼辦?
問題一:可以直接在`config.py文`件上再追加個類,針對開發、測試、正式三個環境來設置三個類即可(或採用工廠模式在不同環境下產生不同的參數配置)。
問題二:Flask追加了一個`instance`資料夾功能,這資料夾的功用在於你呼叫一次`from_object`之後,可以透過`from_pyfile`再從`instance`資料夾調用一次設置於該資料夾的參數文件<sub>(Python文件)</sub>。
:::success
* 說明:利用`instance`資料夾內設置的Python文件再次的渲染參數
```python=
app = Flask(__name__, instance_relative_config=True)
app.config.from_object('Config')
app.config.from_pyfile('config.py')
```
第1行:在實作Flask的時候加入參數`instance-relative_config=True`
第3行:利用`instance`內`config.py`再次渲染參數
<font color='red'>如果將專案`push`至`github`,務必將該資料夾設置於`.gitignore`</font>
:::
針對兩個問題的另一個解法就是利用環境變量,一個重點,針對文件路徑必需指定<font color=red>絕對路徑</font>,文件本身其實也是一個Python文件,而且只會將『<font color=red>大寫</font>』參數讀進去,所以記得確認你的參數都是大寫。
```python=
app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('環境變量')
```
windows作法如下:
```shell
>set YOURAPPLICATION_SETTINGS=\path\to\your_setting_python.py
>python app_blog.py
```
linux作法如下:
```shell
$ export YOURAPPLICATION_SETTINGS=/path/to/your_setting_python.py
$ python run-app.py
```
當然並未一定這樣處理,如果配置的參數很少的話,直接寫死的方式也是可行的,作法如下:
```python
app = Flask(__name__)
app.config['DEBUG']=True
```
或者透過`app.config.update`來設罝,如下:
```python
app.config.update(
DEBUG=True,
SECRET_KEY='Your Key')
```
## 總結
Flask是自由的,沒有強迫使用什麼方式來初始化系統參數,先求有,再求精,需求到了,自然就會因為需求而去探索答案。『探、自謙、必勝』。
最後,透過『工廠模式』需要導入`blueprint`,否則會有路由的問題,關於`blueprint`的部份再於後續導入的時候說明。
**上一話:**[Flask實作_建置一個使用者註冊頁面_03_調整專案架構](https://hackmd.io/s/ryGTXNsQG)
**下一話:**[Flask實作_建置一個使用者註冊頁面_05_密碼加密](https://hackmd.io/s/SkSeIBii7)
## <span id='#ext_1'>延伸閱讀 - 工廠模式</span>
這是官方最建議的作法了,找到的資料多是由『Flask Web開發』此書而來,以下範例取自[miguelgrinberg_flasky](https://github.com/miguelgrinberg/flasky/blob/master/config.py)
```python=
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.googlemail.com')
MAIL_PORT = int(os.environ.get('MAIL_PORT', '587'))
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in \
['true', 'on', '1']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
SSL_REDIRECT = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_RECORD_QUERIES = True
FLASKY_POSTS_PER_PAGE = 20
FLASKY_FOLLOWERS_PER_PAGE = 50
FLASKY_COMMENTS_PER_PAGE = 30
FLASKY_SLOW_DB_QUERY_TIME = 0.5
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite://'
WTF_CSRF_ENABLED = False
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
@classmethod
def init_app(cls, app):
Config.init_app(app)
# email errors to the administrators
import logging
from logging.handlers import SMTPHandler
credentials = None
secure = None
if getattr(cls, 'MAIL_USERNAME', None) is not None:
credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD)
if getattr(cls, 'MAIL_USE_TLS', None):
secure = ()
mail_handler = SMTPHandler(
mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT),
fromaddr=cls.FLASKY_MAIL_SENDER,
toaddrs=[cls.FLASKY_ADMIN],
subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ' Application Error',
credentials=credentials,
secure=secure)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
```
作者利用物件的繼承,設置了基礎、開發、測試、正式四個參數(git上還增加了很多環境用的參數類別),再利用覆寫將不同環境下的參數做了調整。
透過dict組合,在實作類別的時候丟入參數選擇要啟動的環境參數,於app.\_\_init\_\_.py將所有的擴展再透過`init_app`讓參數進入渲染。
下面取自bootstrap的`init_app`<sub>(單純瞭解`init_app`如何渲染)</sub>
```python
class Bootstrap(object):
def __init__(self, app=None):
if app is not None:
self.init_app(app)
def init_app(self, app):
app.config.setdefault('BOOTSTRAP_USE_MINIFIED', True)
app.config.setdefault('BOOTSTRAP_CDN_FORCE_SSL', False)
app.config.setdefault('BOOTSTRAP_QUERYSTRING_REVVING', True)
app.config.setdefault('BOOTSTRAP_SERVE_LOCAL', False)
app.config.setdefault('BOOTSTRAP_LOCAL_SUBDOMAIN', None)
#...下略...#
```
然後return app物件
```python=
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
login_manager.init_app(app)
#... 中略...#
return app
```