# 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 ```