設定處理

應用程式需要某種設定。您可能想要根據應用程式環境變更不同的設定,例如切換除錯模式、設定密鑰,以及其他此類特定於環境的事項。

Flask 的設計方式通常要求設定在應用程式啟動時可用。您可以將設定硬編碼在程式碼中,對於許多小型應用程式來說,這實際上並沒有那麼糟糕,但有更好的方法。

無論您如何載入設定,都有一個可用的 config 物件,其中包含已載入的設定值:config 屬性,屬於 Flask 物件。這是 Flask 本身放置特定設定值的地方,也是擴充套件可以放置其設定值的地方。但這也是您可以擁有自己的設定的地方。

設定基礎

config 實際上是字典的子類別,可以像任何字典一樣進行修改

app = Flask(__name__)
app.config['TESTING'] = True

某些設定值也會轉發到 Flask 物件,因此您可以從那裡讀取和寫入它們

app.testing = True

若要一次更新多個鍵,您可以使用 dict.update() 方法

app.config.update(
    TESTING=True,
    SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
)

除錯模式

DEBUG 設定值很特殊,因為如果在應用程式開始設定後變更,它的行為可能會不一致。為了可靠地設定除錯模式,請在 flaskflask run 命令中使用 --debug 選項。flask run 在除錯模式下預設會使用互動式除錯器和重新載入器。

$ flask --app hello run --debug

建議使用此選項。雖然可以在您的設定或程式碼中設定 DEBUG,但強烈建議不要這樣做。 flask run 命令無法及早讀取它,並且某些系統或擴充套件可能已經根據先前的值進行了配置。

內建設定值

以下設定值由 Flask 內部使用

DEBUG

是否啟用除錯模式。當使用 flask run 啟動開發伺服器時,將會顯示互動式除錯器以處理未處理的例外,並且當程式碼變更時,伺服器將會重新載入。debug 屬性對應到此設定鍵。這會使用 FLASK_DEBUG 環境變數來設定。如果在程式碼中設定,它的行為可能不如預期。

請勿在部署到生產環境時啟用除錯模式。

預設值: False

TESTING

啟用測試模式。例外會被傳播,而不是由應用程式的錯誤處理常式處理。擴充套件也可能會變更其行為,以方便進行更輕鬆的測試。您應該在自己的測試中啟用此設定。

預設值: False

PROPAGATE_EXCEPTIONS

例外會被重新引發,而不是由應用程式的錯誤處理常式處理。如果未設定,則在啟用 TESTINGDEBUG 時,這會隱含地為真。

預設值: None

TRAP_HTTP_EXCEPTIONS

如果沒有處理 HTTPException 類型例外的處理常式,請重新引發它,以便由互動式除錯器處理,而不是將其作為簡單的錯誤回應傳回。

預設值: False

TRAP_BAD_REQUEST_ERRORS

嘗試從請求字典(如 argsform)存取不存在的鍵將會傳回 400 Bad Request 錯誤頁面。啟用此設定可將錯誤視為未處理的例外,以便您獲得互動式除錯器。這是 TRAP_HTTP_EXCEPTIONS 的更具體版本。如果未設定,則在除錯模式下啟用。

預設值: None

SECRET_KEY

一個密鑰,將用於安全地簽署 session cookie,並且可以由擴充套件或您的應用程式用於任何其他與安全性相關的需求。它應該是一個長的隨機 bytesstr。例如,將此輸出複製到您的設定中

$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

在發布問題或提交程式碼時,請勿洩露密鑰。

預設值: None

SECRET_KEY_FALLBACKS

可用於取消簽署的舊密鑰列表,最近的密鑰優先。這允許專案實作密鑰輪換,而不會使活動中的 session 或其他最近簽署的密鑰失效。

密鑰應在適當的時間段後移除,因為檢查每個額外的密鑰都會增加一些額外負荷。

Flask 的內建安全 cookie session 支援此功能。使用 SECRET_KEY 的擴充套件可能尚未支援此功能。

預設值: None

在版本 3.1 中新增。

session cookie 的名稱。如果已經有同名的 cookie,可以變更此名稱。

預設值: 'session'

session cookie 上 Domain 參數的值。如果未設定,瀏覽器只會將 cookie 傳送到設定它的確切網域。否則,它們也會將其傳送到給定值的任何子網域。

不設定此值比設定它更受限制且更安全。

預設值: None

警告

如果在瀏覽器使用一個設定建立 cookie 後變更此設定,則可能會導致建立另一個 cookie。瀏覽器可能會以未定義的順序傳送兩者。在這種情況下,您可能也想要變更 SESSION_COOKIE_NAME,或以其他方式使舊的 session 失效。

變更記錄

在版本 2.3 中變更:預設不設定,不會回退到 SERVER_NAME

session cookie 將有效的路徑。如果未設定,則 cookie 將在 APPLICATION_ROOT/ (如果未設定)下有效。

預設值: None

瀏覽器將不允許 JavaScript 存取標記為 “HTTP only” 的 cookie,以確保安全性。

預設值: True

如果 cookie 標記為 “secure”,則瀏覽器只會在透過 HTTPS 的請求中傳送 cookie。應用程式必須透過 HTTPS 提供服務,這樣才有意義。

預設值: False

瀏覽器將根據頂層文件的網域傳送 cookie,而不是僅根據設定 cookie 的文件的網域。這可以防止在 iframe 中設定的第三方 cookie 在不同的網站之間「洩漏」。

瀏覽器開始不允許未分割的第三方 cookie,因此如果您希望它們在這種嵌入式情況下工作,則需要將您的 cookie 標記為已分割。

啟用此設定會隱含地啟用 SESSION_COOKIE_SECURE,因為它僅在透過 HTTPS 提供服務時才有效。

預設值: False

在版本 3.1 中新增。

限制 cookie 如何與來自外部網站的請求一起傳送。可以設定為 'Lax' (建議) 或 'Strict'。請參閱 Set-Cookie 選項

預設值: None

變更記錄

在版本 1.0 中新增。

PERMANENT_SESSION_LIFETIME

如果 session.permanent 為 true,則 cookie 的到期時間將設定為未來這個秒數。可以是 datetime.timedeltaint

Flask 的預設 cookie 實作驗證密碼編譯簽章是否未超過此值。

預設值: timedelta(days=31) (2678400 秒)

SESSION_REFRESH_EACH_REQUEST

控制當 session.permanent 為 true 時,是否在每個回應中傳送 cookie。每次都傳送 cookie (預設值) 可以更可靠地防止 session 過期,但會使用更多頻寬。非永久 session 不受影響。

預設值: True

USE_X_SENDFILE

在提供檔案時,設定 X-Sendfile 標頭,而不是使用 Flask 提供資料。某些 Web 伺服器 (例如 Apache) 會識別此標頭並更有效率地提供資料。這僅在使用此類伺服器時才有意義。

預設值: False

SEND_FILE_MAX_AGE_DEFAULT

在提供檔案時,將快取控制最大期限設定為此秒數。可以是 datetime.timedeltaint。使用應用程式或藍圖上的 get_send_file_max_age(),在每個檔案的基礎上覆寫此值。

如果 Nonesend_file 會告知瀏覽器使用條件請求,而不是定時快取,這通常更可取。

預設值: None

TRUSTED_HOSTS

根據這些信任的值驗證 Request.host 和使用它的其他屬性。如果主機無效,則引發 SecurityError,這會導致 400 錯誤。如果它是 None,則所有主機都有效。每個值都是完全符合,或者可以以點 . 開頭以符合任何子網域。

驗證是在路由期間針對此值完成的。before_requestafter_request 回呼仍將被呼叫。

預設值: None

在版本 3.1 中新增。

SERVER_NAME

告知應用程式它繫結到的主機和連接埠。

如果啟用 subdomain_matching,則必須設定此值,才能從請求中提取子網域。

必須設定此值,url_for 才能在請求上下文之外產生外部 URL。

預設值: None

在版本 3.1 中變更:不限制請求僅限於此網域,對於 subdomain_matchinghost_matching 都是如此。

變更記錄

在版本 2.3 中變更:不影響 SESSION_COOKIE_DOMAIN

在版本 1.0 中變更:不隱含地啟用 subdomain_matching

APPLICATION_ROOT

告知應用程式它由應用程式/Web 伺服器掛載在什麼路徑下。這用於在請求上下文之外產生 URL (在請求內部,分派器負責設定 SCRIPT_NAME;請參閱 應用程式分派 以取得分派配置的範例)。

如果未設定 SESSION_COOKIE_PATH,則將用於 session cookie 路徑。

預設值: '/'

PREFERRED_URL_SCHEME

在不在請求上下文中的情況下,使用此協定產生外部 URL。

預設值: 'http'

MAX_CONTENT_LENGTH

在此請求期間將讀取的最大位元組數。如果超過此限制,則會引發 413 RequestEntityTooLarge 錯誤。如果設定為 None,則在 Flask 應用程式層級不會強制執行任何限制。但是,如果它是 None,並且請求沒有 Content-Length 標頭,並且 WSGI 伺服器未指示它終止串流,則不會讀取任何資料,以避免無限串流。

每個請求預設為此設定。可以在特定的 Request.max_content_length 上設定,以將限制套用至該特定檢視。應根據應用程式或檢視的特定需求適當地設定此值。

預設值: None

變更記錄

在版本 0.6 中新增。

MAX_FORM_MEMORY_SIZE

multipart/form-data body 中,任何非檔案表單欄位可以擁有的最大大小 (以位元組為單位)。如果超過此限制,則會引發 413 RequestEntityTooLarge 錯誤。如果設定為 None,則在 Flask 應用程式層級不會強制執行任何限制。

每個請求預設為此設定。可以在特定的 Request.max_form_memory_parts 上設定,以將限制套用至該特定檢視。應根據應用程式或檢視的特定需求適當地設定此值。

預設值: 500_000

在版本 3.1 中新增。

MAX_FORM_PARTS

multipart/form-data body 中可能存在的最大欄位數。如果超過此限制,則會引發 413 RequestEntityTooLarge 錯誤。如果設定為 None,則在 Flask 應用程式層級不會強制執行任何限制。

每個請求預設為此設定。可以在特定的 Request.max_form_parts 上設定,以將限制套用至該特定檢視。應根據應用程式或檢視的特定需求適當地設定此值。

預設值: 1_000

在版本 3.1 中新增。

TEMPLATES_AUTO_RELOAD

範本在變更時重新載入。如果未設定,則在除錯模式下啟用。

預設值: None

EXPLAIN_TEMPLATE_LOADING

記錄除錯資訊,追蹤範本檔案的載入方式。這可用於找出為何範本未載入或出現錯誤的檔案。

預設值: False

如果 cookie 標頭大於此位元組數,則發出警告。預設值為 4093。較大的 cookie 可能會被瀏覽器靜默忽略。設定為 0 以停用警告。

PROVIDE_AUTOMATIC_OPTIONS

設定為 False 以停用自動新增 OPTIONS 回應。可以透過變更 provide_automatic_options 屬性來覆寫每個路由的此設定。

在版本 3.10 中新增:新增 PROVIDE_AUTOMATIC_OPTIONS 以控制自動產生的 OPTIONS 回應的預設新增。

變更記錄

在版本 2.3 中變更:移除了 JSON_AS_ASCIIJSON_SORT_KEYSJSONIFY_MIMETYPEJSONIFY_PRETTYPRINT_REGULAR。預設的 app.json 提供者改為具有等效的屬性。

在版本 2.3 中變更:移除了 ENV

在版本 2.2 中變更:移除了 PRESERVE_CONTEXT_ON_EXCEPTION

在版本 1.0 中變更:移除了 LOGGER_NAMELOGGER_HANDLER_POLICY。請參閱 記錄 以取得有關設定的資訊。

新增 ENV 以反映 FLASK_ENV 環境變數。

新增 SESSION_COOKIE_SAMESITE 以控制 session cookie 的 SameSite 選項。

新增 MAX_COOKIE_SIZE 以控制來自 Werkzeug 的警告。

在版本 0.11 中新增:SESSION_REFRESH_EACH_REQUESTTEMPLATES_AUTO_RELOADLOGGER_HANDLER_POLICYEXPLAIN_TEMPLATE_LOADING

在版本 0.10 中新增:JSON_AS_ASCIIJSON_SORT_KEYSJSONIFY_PRETTYPRINT_REGULAR

在版本 0.9 中新增:PREFERRED_URL_SCHEME

在版本 0.8 中新增:TRAP_BAD_REQUEST_ERRORSTRAP_HTTP_EXCEPTIONSAPPLICATION_ROOTSESSION_COOKIE_DOMAINSESSION_COOKIE_PATHSESSION_COOKIE_HTTPONLYSESSION_COOKIE_SECURE

在版本 0.7 中新增:PROPAGATE_EXCEPTIONSPRESERVE_CONTEXT_ON_EXCEPTION

在版本 0.6 中新增:MAX_CONTENT_LENGTH

在版本 0.5 中新增:SERVER_NAME

在版本 0.4 中新增:LOGGER_NAME

從 Python 檔案設定

如果可以將設定儲存在單獨的檔案中,理想情況下位於實際應用程式套件之外,則設定會變得更有用。您可以部署您的應用程式,然後單獨為特定部署配置它。

常見的模式是這樣

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

這首先從 yourapplication.default_settings 模組載入設定,然後使用 YOURAPPLICATION_SETTINGS 環境變數指向的檔案內容覆寫這些值。可以在啟動伺服器之前在 shell 中設定此環境變數

$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
$ flask run
 * Running on http://127.0.0.1:5000/

設定檔本身是實際的 Python 檔案。稍後只有大寫的值才會儲存在 config 物件中。因此,請務必對您的設定鍵使用大寫字母。

這是設定檔的範例

# Example configuration
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

請務必儘早載入設定,以便擴充套件能夠在啟動時存取設定。 config 物件上還有其他方法可以從個別檔案載入。如需完整參考,請閱讀 Config 物件的文件。

從資料檔案設定

也可以使用 from_file() 從您選擇格式的檔案載入設定。例如,從 TOML 檔案載入

import tomllib
app.config.from_file("config.toml", load=tomllib.load, text=False)

或從 JSON 檔案載入

import json
app.config.from_file("config.json", load=json.load)

從環境變數設定

除了使用環境變數指向設定檔之外,您可能會發現直接從環境控制您的設定值很有用 (或必要)。可以使用 from_prefixed_env() 指示 Flask 將所有以特定字首開頭的環境變數載入到 config 中。

可以在啟動伺服器之前在 shell 中設定環境變數

$ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
$ export FLASK_MAIL_ENABLED=false
$ flask run
 * Running on http://127.0.0.1:5000/

然後可以使用與環境變數名稱相同的鍵 (不含字首) 透過 config 載入和存取變數,即

app.config.from_prefixed_env()
app.config["SECRET_KEY"]  # Is "5f352379324c22463451387a0aec5d2f"

字首預設為 FLASK_。這是可透過 from_prefixed_env()prefix 引數設定的。

將會剖析值,以嘗試將它們轉換為比字串更具體的類型。預設情況下,使用 json.loads(),因此任何有效的 JSON 值都是可能的,包括列表和字典。這是可透過 from_prefixed_env()loads 引數設定的。

當使用預設 JSON 剖析新增布林值時,只有小寫的 “true” 和 “false” 是有效值。請記住,任何非空字串都被 Python 視為 True

可以透過使用雙底線 (__) 分隔鍵,在巢狀字典中設定鍵。任何在父字典上不存在的中間鍵都將初始化為空字典。

$ export FLASK_MYAPI__credentials__username=user123
app.config["MYAPI"]["credentials"]["username"]  # Is "user123"

在 Windows 上,環境變數鍵始終為大寫,因此上述範例最終將為 MYAPI__CREDENTIALS__USERNAME

如需更多設定載入功能,包括合併和不區分大小寫的 Windows 支援,請嘗試使用專用程式庫,例如 Dynaconf,其中包括與 Flask 的整合。

設定最佳實務

先前提到方法的不利之處在於,它使測試變得有點困難。對於此問題,通常沒有 100% 的解決方案,但是您可以記住一些事項來改善這種體驗

  1. 在函數中建立您的應用程式,並在其上註冊藍圖。這樣,您可以建立具有不同附加設定的應用程式的多個實例,這使得單元測試變得容易得多。您可以使用它來根據需要傳入設定。

  2. 不要編寫在匯入時需要設定的程式碼。如果您將自己限制為僅請求存取設定,則可以稍後根據需要重新配置物件。

  3. 請務必儘早載入設定,以便擴充套件可以在呼叫 init_app 時存取設定。

開發/生產環境

大多數應用程式需要多個設定。至少應該為生產伺服器和開發期間使用的伺服器提供單獨的設定。處理這個問題的最簡單方法是使用始終載入並且是版本控制一部分的預設設定,以及一個單獨的設定,該設定根據需要在上面範例中提到的覆寫這些值

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

然後您只需新增一個單獨的 config.py 檔案並匯出 YOURAPPLICATION_SETTINGS=/path/to/config.py,就完成了。但是,也有其他替代方法。例如,您可以使用匯入或子類別化。

在 Django 世界中非常流行的是,透過在檔案頂部新增 from yourapplication.default_settings import *,然後手動覆寫變更,從而在設定檔中明確匯入。您也可以檢查環境變數 (例如 YOURAPPLICATION_MODE),並將其設定為 productiondevelopment 等,並根據此變數匯入不同的硬編碼檔案。

一個有趣的模式是將類別和繼承用於設定

class Config(object):
    TESTING = False

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DATABASE_URI = "sqlite:////tmp/foo.db"

class TestingConfig(Config):
    DATABASE_URI = 'sqlite:///:memory:'
    TESTING = True

若要啟用此設定,您只需呼叫 from_object()

app.config.from_object('configmodule.ProductionConfig')

請注意,from_object() 不會實例化類別物件。如果您需要實例化類別,例如存取屬性,則必須在呼叫 from_object() 之前執行此操作。

from configmodule import ProductionConfig
app.config.from_object(ProductionConfig())

# Alternatively, import via string:
from werkzeug.utils import import_string
cfg = import_string('configmodule.ProductionConfig')()
app.config.from_object(cfg)

實例化組態物件可讓您在組態類別中使用 @property

class Config(object):
    """Base config, uses staging database server."""
    TESTING = False
    DB_SERVER = '192.168.1.56'

    @property
    def DATABASE_URI(self):  # Note: all caps
        return f"mysql://user@{self.DB_SERVER}/foo"

class ProductionConfig(Config):
    """Uses production database server."""
    DB_SERVER = '192.168.19.32'

class DevelopmentConfig(Config):
    DB_SERVER = 'localhost'

class TestingConfig(Config):
    DB_SERVER = 'localhost'
    DATABASE_URI = 'sqlite:///:memory:'

管理組態檔案的方式有很多種,這取決於您。但是,以下列出一些好的建議:

  • 在版本控制中保留預設組態。可以使用此預設組態填入設定,或在覆寫值之前將其匯入您自己的組態檔案中。

  • 使用環境變數在組態之間切換。這可以從 Python 解譯器外部完成,並且使開發和部署更加容易,因為您可以快速輕鬆地在不同的組態之間切換,而無需接觸任何程式碼。如果您經常處理不同的專案,您甚至可以建立自己的腳本來取得虛擬環境,並為您匯出開發組態。

  • 使用像 fabric 這樣的工具,將程式碼和組態分別推送到生產伺服器。

Instance Folders

變更記錄

版本 0.8 新增。

Flask 0.8 引入了 instance folders。長期以來,Flask 允許直接參考相對於應用程式資料夾的路徑(透過 Flask.root_path)。這也是許多開發人員載入儲存在應用程式旁邊的組態的方式。然而,不幸的是,這僅在應用程式未封裝的情況下運作良好,在這種情況下,根路徑指的是套件的內容。

在 Flask 0.8 中,引入了一個新的屬性:Flask.instance_path。它指的是一個稱為「instance folder」的新概念。Instance folder 的設計目的是不進行版本控制,並且特定於部署。它是放置運行時會變更的內容或組態檔案的理想位置。

您可以在建立 Flask 應用程式時明確提供 instance folder 的路徑,也可以讓 Flask 自動偵測 instance folder。對於明確的組態,請使用 instance_path 參數。

app = Flask(__name__, instance_path='/path/to/instance/folder')

請記住,提供的路徑必須是絕對路徑。

如果未提供 instance_path 參數,則使用以下預設位置:

  • 未安裝的模組

    /myapp.py
    /instance
    
  • 未安裝的套件

    /myapp
        /__init__.py
    /instance
    
  • 已安裝的模組或套件

    $PREFIX/lib/pythonX.Y/site-packages/myapp
    $PREFIX/var/myapp-instance
    

    $PREFIX 是您的 Python 安裝的前綴。它可以是 /usr 或您的虛擬環境的路徑。您可以列印 sys.prefix 的值,以查看前綴設定為何。

由於 config 物件提供了從相對檔名載入組態檔案的功能,因此我們允許更改透過檔名載入的方式,使其相對於 instance path(如果需要)。組態檔案中相對路徑的行為可以在「相對於應用程式根目錄」(預設值)和「相對於 instance folder」之間切換,透過應用程式建構函式的 instance_relative_config 開關。

app = Flask(__name__, instance_relative_config=True)

以下是如何設定 Flask 從模組預先載入組態,然後從 instance folder 中的檔案覆寫組態(如果存在)的完整範例:

app = Flask(__name__, instance_relative_config=True)
app.config.from_object('yourapplication.default_settings')
app.config.from_pyfile('application.cfg', silent=True)

instance folder 的路徑可以透過 Flask.instance_path 找到。Flask 還提供了一個快捷方式,可以使用 Flask.open_instance_resource() 從 instance folder 開啟檔案。

兩者的範例用法:

filename = os.path.join(app.instance_path, 'application.cfg')
with open(filename) as f:
    config = f.read()

# or via open_instance_resource:
with app.open_instance_resource('application.cfg') as f:
    config = f.read()