在本文中,我將簡要介紹一些最佳實踐,這些最佳實踐在使用 Alembic 和 SQLAlchemy 時幫助保持專案有序、簡化資料庫維護並防止常見陷阱。這些技巧不只一次讓我擺脫了麻煩。以下是我們將要介紹的內容:
SQLAlchemy 允許您設定命名約定,在產生遷移時自動套用至所有資料表和約束。這使您無需手動命名索引、外鍵和其他約束,從而使資料庫結構可預測且一致。
要在新專案中進行設置,請在基底類別中新增約定,以便 Alembic 將自動使用所需的命名格式。以下是大多數情況下都有效的約定範例:
from sqlalchemy import MetaData from sqlalchemy.orm import DeclarativeBase convention = { 'all_column_names': lambda constraint, table: '_'.join( [column.name for column in constraint.columns.values()] ), 'ix': 'ix__%(table_name)s__%(all_column_names)s', 'uq': 'uq__%(table_name)s__%(all_column_names)s', 'ck': 'ck__%(table_name)s__%(constraint_name)s', 'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s', 'pk': 'pk__%(table_name)s', } class BaseModel(DeclarativeBase): metadata = MetaData(naming_convention=convention)
Alembic 遷移檔案名稱通常以修訂標籤開頭,這可以使目錄中的遷移順序顯得隨機。有時按時間順序排列它們很有用。
Alembic 允許使用 file_template 設定在 alembic.ini 檔案中自訂移轉檔案名稱範本。以下是兩種方便的命名格式,可讓遷移保持井井有條:
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
在檔案名稱中使用日期或 Unix 時間戳可以讓遷移保持井井有條,使導航更容易。我更喜歡使用 Unix 時間戳,下一節將提供一個範例。
對於在團隊中工作的人來說,註釋屬性是一個很好的做法。對於 SQLAlchemy 模型,請考慮直接向列和表添加註釋,而不是依賴文件字串。這樣,註釋在程式碼和資料庫中都可用,使 DBA 或分析師更容易理解表格和欄位的用途。
class Event(BaseModel): __table_args__ = {'comment': 'System (service) event'} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, comment='Event ID - PK', ) service_id: Mapped[int] = mapped_column( sa.Integer, sa.ForeignKey( f'{IntegrationServiceModel.__tablename__}.id', ondelete='CASCADE', ), nullable=False, comment='FK to integration service that owns the event', ) name: Mapped[str] = mapped_column( sa.String(256), nullable=False, comment='Event name' )
為遷移添加註解也很有幫助,以便更容易在檔案系統中找到它們。可以使用 -m
from sqlalchemy import MetaData from sqlalchemy.orm import DeclarativeBase convention = { 'all_column_names': lambda constraint, table: '_'.join( [column.name for column in constraint.columns.values()] ), 'ix': 'ix__%(table_name)s__%(all_column_names)s', 'uq': 'uq__%(table_name)s__%(all_column_names)s', 'ck': 'ck__%(table_name)s__%(constraint_name)s', 'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s', 'pk': 'pk__%(table_name)s', } class BaseModel(DeclarativeBase): metadata = MetaData(naming_convention=convention)
模型通常用於資料操作,例如將資料從一個表傳輸到另一個表或修改列值。但是,如果模型在建立遷移後發生更改,則在遷移中使用 ORM 模型可能會導致問題。在這種情況下,基於舊模型的遷移在執行時將會中斷,因為資料庫架構可能不再與目前模型相符。
遷移應該是靜態的並且獨立於模型的當前狀態,以確保無論程式碼如何更改都能正確執行。以下是避免使用模型進行資料操作的兩種方法。
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
階梯測試涉及逐步測試升級/降級遷移,以確保整個遷移鏈正常運作。這確保每次遷移都可以成功地從頭開始建立新資料庫並降級而不會出現問題。將此測試添加到 CI 對於團隊來說非常寶貴,可以節省時間並減少挫折感。
將測試整合到您的專案中可以輕鬆快速地完成。您可以在此存儲庫中找到程式碼範例。它還包括其他可能有幫助的有價值的遷移測試。
用於執行遷移的單獨服務。這只是執行遷移的一種方法。當在本地或類似開發的環境中開發時,這種方法非常適合。我想提醒您有關條件 dependent_on 功能,該功能與此處相關。我們使用 Alembic 獲取應用程式映像並在單獨的容器中運行它。我們新增對資料庫的依賴關係,條件是僅當資料庫準備好處理請求(service_healthy)時才開始遷移。此外,可以為應用程式新增條件依賴項(service_completed_successively),確保它僅在遷移成功完成後啟動。
class Event(BaseModel): __table_args__ = {'comment': 'System (service) event'} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, comment='Event ID - PK', ) service_id: Mapped[int] = mapped_column( sa.Integer, sa.ForeignKey( f'{IntegrationServiceModel.__tablename__}.id', ondelete='CASCADE', ), nullable=False, comment='FK to integration service that owns the event', ) name: Mapped[str] = mapped_column( sa.String(256), nullable=False, comment='Event name' )
depends_on 條件確保遷移僅在資料庫完全準備好後運行,並且應用程式在遷移完成後啟動。
雖然這可能是顯而易見的一點,但重要的是不要忽視它。使用 mixins 是避免程式碼重複的便捷方法。 Mixin 是包含常用欄位和方法的類,可以將其整合到需要的任何模型中。例如,我們經常需要created_at和updated_at欄位來追蹤記錄的建立和更新時間。使用基於 UUID 的 id 來標準化主鍵也很有用。所有這些都可以封裝在 mixin 中。
from sqlalchemy import MetaData from sqlalchemy.orm import DeclarativeBase convention = { 'all_column_names': lambda constraint, table: '_'.join( [column.name for column in constraint.columns.values()] ), 'ix': 'ix__%(table_name)s__%(all_column_names)s', 'uq': 'uq__%(table_name)s__%(all_column_names)s', 'ck': 'ck__%(table_name)s__%(constraint_name)s', 'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s', 'pk': 'pk__%(table_name)s', } class BaseModel(DeclarativeBase): metadata = MetaData(naming_convention=convention)
透過新增這些 mixins,我們可以在需要的任何模型中包含 UUID id 和時間戳記:
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
處理遷移可能具有挑戰性,但遵循這些簡單的做法有助於保持專案井然有序且易於管理。命名約定、日期排序、註釋和測試使我免於混亂並有助於防止錯誤。我希望這篇文章對您有所幫助——請隨時在評論中分享您自己的遷移技巧!
以上是Alembic 和 SQLAlchemy 的最佳實踐的詳細內容。更多資訊請關注PHP中文網其他相關文章!