首頁>技術>

Flask本身不支援資料庫,相信你已經聽說過了。 正如表單那樣,這也是Flask有意為之。對使用的資料庫外掛自由選擇,豈不是比被迫適應其中之一,更讓人擁有主動權嗎?

絕大多數的資料庫都提供了Python客戶端包,它們之中的大部分都被封裝成Flask外掛以便更好地和Flask應用結合。資料庫被劃分為兩大類,遵循關係模型的一類是關係資料庫,另外的則是非關係資料庫,簡稱NoSQL,表現在它們不支援流行的關係查詢語言SQL(譯者注:部分人也宣稱NoSQL代表不僅僅只是SQL)。雖然兩類資料庫都是偉大的產品,但我認為關係資料庫更適合具有結構化資料的應用程式,例如使用者列表,使用者動態等,而NoSQL資料庫往往更適合非結構化資料。 本應用可以像大多數其他應用一樣,使用任何一種型別的資料庫來實現,但是出於上述原因,我將使用關係資料庫。

在第三章中,我向你展示了第一個Flask擴充套件,在本章中,我還要用到兩個。 第一個是Flask-SQLAlchemy,這個外掛為流行的SQLAlchemy包做了一層封裝以便在Flask中呼叫更方便,類似SQLAlchemy這樣的包叫做Object Relational Mapper,簡稱ORM。 ORM允許應用程式使用高階實體(如類,物件和方法)而不是表和SQL來管理資料庫。 ORM的工作就是將高階操作轉換成資料庫命令。

SQLAlchemy不只是某一款資料庫軟體的ORM,而是支援包含MySQL、PostgreSQL和SQLite在內的很多資料庫軟體。簡直是太強大了,你可以在開發的時候使用簡單易用且無需另起服務的SQLite,需要部署應用到生產伺服器上時,則選用更健壯的MySQL或PostgreSQL服務,並且不需要修改應用程式碼(譯者注:只需修改應用配置)。

確認啟用虛擬環境之後,利用如下命令來安裝Flask-SQLAlchemy外掛:

資料庫遷移

我所見過的絕大多數資料庫教程都是關於如何建立和使用資料庫的,卻沒有指出當需要對現有資料庫更新或者新增表結構時,應當如何應對。 這是一項困難的工作,因為關係資料庫是以結構化資料為中心的,所以當結構發生變化時,資料庫中的已有資料需要被遷移到修改後的結構中。

我將在本章中介紹的第二個外掛是Flask-Migrate。 這個外掛是Alembic的一個Flask封裝,是SQLAlchemy的一個數據庫遷移框架。 使用資料庫遷移增加了啟動資料庫時候的一些工作,但這對將來的資料庫結構穩健變更來說,是一個很小的代價。

安裝Flask-Migrate和安裝你見過的其他外掛的方式一樣:

Flask-SQLAlchemy配置

開發階段,我會使用SQLite資料庫,SQLite資料庫是開發小型乃至中型應用最方便的選擇,因為每個資料庫都儲存在磁碟上的單個檔案中,並且不需要像MySQL和PostgreSQL那樣執行資料庫服務。

讓我們給配置檔案新增兩個新的配置項:

Flask-SQLAlchemy外掛從SQLALCHEMY_DATABASE_URI配置變數中獲取應用的資料庫的位置。 當回顧第三章可以發現,首先從環境變數獲取配置變數,未獲取到就使用預設值,這樣做是一個好習慣。 本處,我從DATABASE_URL環境變數中獲取資料庫URL,如果沒有定義,我將其配置為basedir變量表示的應用頂級目錄下的一個名為app.db的檔案路徑。

SQLALCHEMY_TRACK_MODIFICATIONS配置項用於設定資料發生變更之後是否傳送訊號給應用,我不需要這項功能,因此將其設定為False。

資料庫在應用的表現形式是一個數據庫例項,資料庫遷移引擎同樣如此。它們將會在應用例項化之後進行例項化和註冊操作。app/__init__.py檔案變更如下:

在這個初始化指令碼中我更改了三處。首先,我添加了一個db物件來表示資料庫。然後,我又添加了資料庫遷移引擎migrate。這種註冊Flask外掛的模式希望你了然於胸,因為大多數Flask外掛都是這樣初始化的。最後,我在底部匯入了一個名為models的模組,這個模組將會用來定義資料庫結構。

資料庫模型

定義資料庫中一張表及其欄位的類,通常叫做資料模型。ORM(SQLAlchemy)會將類的例項關聯到資料庫表中的資料行,並翻譯相關操作。

就讓我們從使用者模型開始吧,利用 WWW SQL Designer工具,我畫了一張圖來設計使用者表的各個欄位(譯者注:實際表名為user):

id欄位通常存在於所有模型並用作主鍵。每個使用者都會被資料庫分配一個id值,並存儲到這個欄位中。大多數情況下,主鍵都是資料庫自動賦值的,我只需要提供id欄位作為主鍵即可。

username,email和password_hash欄位被定義為字串(資料庫術語中的VARCHAR),並指定其最大長度,以便資料庫可以優化空間使用率。 username和email欄位的用途不言而喻,password_hash欄位值得提一下。 我想確保我正在構建的應用採用安全最佳實踐,因此我不會將使用者密碼明文儲存在資料庫中。 明文儲存密碼的問題是,如果資料庫被攻破,攻擊者就會獲得密碼,這對使用者隱私來說可能是毀滅性的。 如果使用雜湊密碼,這就大大提高了安全性。 這將是另一章的主題,所以現在不需分心。

使用者表構思完畢之後,我將其用程式碼實現,並存儲到新建的模組app/models.py中,程式碼如下:

上面建立的User類繼承自db.Model,它是Flask-SQLAlchemy中所有模型的基類。 這個類將表的欄位定義為類屬性,欄位被建立為db.Column類的例項,它傳入欄位型別以及其他可選引數,例如,可選引數中允許指示哪些欄位是唯一的並且是可索引的,這對高效的資料檢索十分重要。

該類的__repr__方法用於在除錯時列印使用者例項。在下面的Python互動式會話中你可以看到__repr__()方法的執行情況:

建立資料庫遷移儲存庫

上一節中建立的模型類定義了此應用程式的初始資料庫結構(元資料)。 但隨著應用的不斷增長,很可能會新增、修改或刪除資料庫結構。 Alembic(Flask-Migrate使用的遷移框架)將以一種不需要重新建立資料庫的方式進行資料庫結構的變更。

這是一個看起來相當艱鉅的任務,為了實現它,Alembic維護一個數據庫遷移儲存庫,它是一個儲存遷移指令碼的目錄。 每當對資料庫結構進行更改後,都需要向儲存庫中新增一個包含更改的詳細資訊的遷移指令碼。 當應用這些遷移指令碼到資料庫時,它們將按照建立的順序執行。

Flask-Migrate通過flask命令暴露來它的子命令。 你已經看過flask run,這是一個Flask本身的子命令。 Flask-Migrate添加了flask db子命令來管理與資料庫遷移相關的所有事情。 那麼讓我們通過執行flask db init來建立microblog的遷移儲存庫:

請記住,flask命令依賴於FLASK_APP環境變數來知道Flask應用入口在哪裡。 對於本應用,正如第一章,你需要設定FLASK_APP = microblog.py。

執行遷移初始化命令之後,你會發現一個名為migrations的新目錄。該目錄中包含一個名為versions的子目錄以及若干檔案。從現在起,這些檔案就是你專案的一部分了,應該新增到程式碼版本管理中去。

第一次資料庫遷移

包含對映到User資料庫模型的使用者表的遷移儲存庫生成後,是時候建立第一次資料庫遷移了。 有兩種方法來建立資料庫遷移:手動或自動。 要自動生成遷移,Alembic會將資料庫模型定義的資料庫模式與資料庫中當前使用的實際資料庫模式進行比較。 然後,使用必要的更改來填充遷移指令碼,以使資料庫模式與應用程式模型匹配。 當前情況是,由於之前沒有資料庫,自動遷移將把整個User模型新增到遷移指令碼中。 flask db migrate子命令生成這些自動遷移:

通過命令輸出,你可以了解到Alembic在建立遷移的過程中執行了哪些邏輯。前兩行是常規資訊,通常可以忽略。 之後的輸出表明檢測到了一個使用者表和兩個索引。 然後它會告訴你遷移指令碼的輸出路徑。 e517276bb1c2是自動生成的一個用於遷移的唯一標識(你執行的結果會有所不同)。 -m可選引數為遷移添加了一個簡短的註釋。

生成的遷移指令碼現在是你專案的一部分了,需要將其合併到原始碼管理中。 如果你好奇,並檢查了它的程式碼,就會發現它有兩個函式叫upgrade()和downgrade()。 upgrade()函式應用遷移,downgrade()函式回滾遷移。 Alembic通過使用降級方法可以將資料庫遷移到歷史中的任何點,甚至遷移到較舊的版本。

flask db migrate命令不會對資料庫進行任何更改,只會生成遷移指令碼。 要將更改應用到資料庫,必須使用flask db upgrade命令。

因為本應用使用SQLite,所以upgrade命令檢測到資料庫不存在時,會建立它(在這個命令完成之後,你會注意到一個名為app.db的檔案,即SQLite資料庫)。 在使用類似MySQL和PostgreSQL的資料庫服務時,必須在執行upgrade之前在資料庫伺服器上建立資料庫。

資料庫升級和降級流程

目前,本應用還處於初期階段,但討論一下未來的資料庫遷移戰略也無傷大雅。 假設你的開發計算機上存有應用的原始碼,並且還將其部署到生產伺服器上,執行應用並上線提供服務。

而應用在下一個版本必須對模型進行更改,例如需要新增一個新表。 如果沒有遷移機制,這將需要做許多工作。無論是在你的開發機器上,還是在你的伺服器上,都需要弄清楚如何變更你的資料庫結構才能完成這項任務。

通過資料庫遷移機制的支援,在你修改應用中的模型之後,將生成一個新的遷移指令碼(flask db migrate),你可能會審查它以確保自動生成的正確性,然後將更改應用到你的開發資料庫(flask db upgrade)。 測試無誤後,將遷移指令碼新增到原始碼管理並提交。

當準備將新版本的應用釋出到生產伺服器時,你只需要獲取包含新增遷移指令碼的更新版本的應用,然後執行flask db upgrade即可。 Alembic將檢測到生產資料庫未更新到最新版本,並執行在上一版本之後建立的所有新增遷移指令碼。

正如我前面提到的,flask db downgrade命令可以回滾上次的遷移。 雖然在生產系統上不太可能需要此選項,但在開發過程中可能會發現它非常有用。 你可能已經生成了一個遷移指令碼並將其應用,只是發現所做的更改並不完全是你所需要的。 在這種情況下,可以降級資料庫,刪除遷移指令碼,然後生成一個新的來替換它。

資料庫關係

關係資料庫擅長儲存資料項之間的關係。 考慮使用者發表動態的情況, 使用者將在user表中有一個記錄,並且這條使用者動態將在post表中有一個記錄。 標記誰寫了一個給定的動態的最有效的方法是連結兩個相關的記錄。

一旦建立了使用者和動態之間的關係,資料庫就可以在查詢中展示它。最小的例子就是當你看一條使用者動態的時候需要知道是誰寫的。一個更復雜的查詢是, 如果你好奇一個使用者時,你可能想知道這個使用者寫的所有動態。 Flask-SQLAlchemy有助於實現這兩種查詢。

讓我們擴充套件資料庫來儲存使用者動態,以檢視實際中的關係。 這是一個新表post的設計(譯者注:實際表名分別為user和post):

post表將具有必須的id、使用者動態的body和timestamp欄位。 除了這些預期的欄位之外,我還添加了一個user_id欄位,將該使用者動態連結到其作者。 你已經看到所有使用者都有一個唯一的id主鍵, 將使用者動態連結到其作者的方法是新增對使用者id的引用,這正是user_id欄位所在的位置。 這個user_id欄位被稱為外來鍵。 上面的資料庫圖顯示了外來鍵作為該欄位和它引用的表的id欄位之間的連結。 這種關係被稱為一對多,因為“一個”使用者寫了“多”條動態。

修改後的app/models.py如下:

新的“Post”類表示使用者發表的動態。 timestamp欄位將被編入索引,如果你想按時間順序檢索使用者動態,這將非常有用。 我還為其添加了一個default引數,並傳入了datetime.utcnow函式。 當你將一個函式作為預設值傳入後,SQLAlchemy會將該欄位設定為呼叫該函式的值(請注意,在utcnow之後我沒有包含(),所以我傳遞函式本身,而不是呼叫它的結果)。 通常,在服務應用中使用UTC日期和時間是推薦做法。 這可以確保你使用統一的時間戳,無論使用者位於何處,這些時間戳會在顯示時轉換為使用者的當地時間。

user_id欄位被初始化為user.id的外來鍵,這意味著它引用了來自使用者表的id值。本處的user是資料庫表的名稱,Flask-SQLAlchemy自動設定類名為小寫來作為對應表的名稱。 User類有一個新的posts欄位,用db.relationship初始化。這不是實際的資料庫欄位,而是使用者和其動態之間關係的高階檢視,因此它不在資料庫圖表中。對於一對多關係,db.relationship欄位通常在“一”的這邊定義,並用作訪問“多”的便捷方式。因此,如果我有一個使用者例項u,表示式u.posts將執行一個數據庫查詢,返回該使用者發表過的所有動態。 db.relationship的第一個引數表示代表關係“多”的類。 backref引數定義了代表“多”的類的例項反向呼叫“一”的時候的屬性名稱。這將會為使用者動態新增一個屬性post.author,呼叫它將返回給該使用者動態的使用者例項。 lazy引數定義了這種關係調用的資料庫查詢是如何執行的,這個我會在後面討論。不要覺得這些細節沒什麼意思,本章的結尾將會給出對應的例子。

一旦我變更了應用模型,就需要生成一個新的資料庫遷移:

並將這個遷移應用到資料庫:

如果你對專案使用了版本控制,記得將新的遷移指令碼新增進去並提交。

表演時刻

經歷了一個漫長的過程來定義資料庫,我卻還沒向你展示它們如何使用。 由於應用還沒有任何資料庫邏輯,所以讓我們在Python直譯器中來使用以便熟悉它。 立即執行python命令來啟動Python(在啟動直譯器之前,確保您的虛擬環境已被啟用)。

進入Python互動式環境後,匯入資料庫例項和模型:

開始階段,建立一個新使用者:

對資料庫的更改是在會話的上下文中完成的,你可以通過db.session進行訪問驗證。 允許在會話中累積多個更改,一旦所有更改都被註冊,你可以發出一個指令db.session.commit()來以原子方式寫入所有更改。 如果在會話執行的任何時候出現錯誤,呼叫db.session.rollback()會中止會話並刪除儲存在其中的所有更改。 要記住的重要一點是,只有在呼叫db.session.commit()時才會將更改寫入資料庫。 會話可以保證資料庫永遠不會處於不一致的狀態。

新增另一個使用者:

資料庫執行返回所有使用者的查詢:

所有模型都有一個query屬性,它是執行資料庫查詢的入口。 最基本的查詢就是返回該類的所有元素,它被適當地命名為all()。 請注意,新增這些使用者時,它們的id欄位依次自動設定為1和2。

另外一種查詢方式是,如果你知道使用者的id,可以用以下方式直接獲取使用者例項:

現在新增一條使用者動態:

我不需要為timestamp欄位設定一個值,因為這個欄位有一個預設值,你可以在模型定義中看到。 那麼user_id欄位呢? 回想一下,我在User類中建立的db.relationship為使用者添加了posts屬性,併為使用者動態添加了author屬性。 我使用author虛擬欄位來呼叫其作者,而不必通過使用者ID來處理。 SQLAlchemy在這方面非常出色,因為它提供了對關係和外來鍵的高階抽象。

為了完成演示,讓我們看看另外的資料庫查詢案例:

Flask-SQLAlchemy文件是學習其對應操作的最好去處。

學完本節內容,我們需要清除這些測試使用者和使用者動態,以便保持資料整潔和為下一章做好準備:

Shell上下文

還記得上一節的啟動Python直譯器之後你做過什麼嗎?第一件事是執行兩條匯入語句:

開發應用時,你經常會在Python shell中測試,所以每次重複上面的匯入都會變得枯燥乏味。 flask shell命令是flask命令集中的另一個非常有用的工具。 shell命令是Flask在繼run之後的實現第二個“核心”命令。 這個命令的目的是在應用的上下文中啟動一個Python直譯器。 這意味著什麼? 看下面的例子:

使用常規的直譯器會話時,除非明確地被匯入,否則app物件是未知的,但是當使用flask shell時,該命令預先匯入應用例項。 flask shell的絕妙之處不在於它預先匯入了app,而是你可以配置一個“shell上下文”,也就是可以預先匯入一份物件列表。

​在microblog.py中實現一個函式,它通過新增資料庫例項和模型來建立了一個shell上下文環境:

app.shell_context_processor裝飾器將該函式註冊為一個shell上下文函式。 當flask shell命令執行時,它會呼叫這個函式並在shell會話中註冊它返回的專案。 函式返回一個字典而不是一個列表,原因是對於每個專案,你必須通過字典的鍵提供一個名稱以便在shell中被呼叫。

在新增shell上下文處理器函式後,你無需匯入就可以使用資料庫例項:

如果執行以上的db, User, Post命令,報 NameError異常,說明 make_shell_context() 沒有被Flask註冊。最有可能的原因是你的環境變數中沒有設定 FLASK_APP=microblog.py。此時你可以回到第一章複習一下如何設定FLASK_APP環境變數。如果你經常忘記在新開終端時設定該環境變數,可以如第一章末尾處那樣,在專案的根目錄新增一個名為 .flaskenv 的檔案,並將環境變數設定在裡面。

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 在React中實現條件渲染的5種方法及優缺點