Alembic 和 SQLAlchemy 的最佳实践
来源:dev.to
时间:2024-11-01 14:31:09 414浏览 收藏
积累知识,胜过积蓄金银!毕竟在文章开发的过程中,会遇到各种各样的问题,往往都是一些细节知识点还没有掌握好而导致的,因此基础知识点的积累是很重要的。下面本文《Alembic 和 SQLAlchemy 的最佳实践》,就带大家讲解一下知识点,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~
在本文中,我将简要介绍一些最佳实践,这些最佳实践在使用 alembic 和 sqlalchemy 时帮助保持项目有序、简化数据库维护并防止常见陷阱。这些技巧不止一次地让我摆脱了麻烦。以下是我们将介绍的内容:
- 命名约定
- 按日期对迁移进行排序
- 表、列和迁移注释
- 无模型迁移中的数据处理
- 迁移测试(楼梯测试)
- 运行迁移的服务
- 对模型使用 mixins
1. 命名约定
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)
2. 按日期对迁移进行排序
alembic 迁移文件名通常以修订标签开头,这可以使目录中的迁移顺序显得随机。有时按时间顺序排列它们很有用。
alembic 允许使用 file_template 设置在 alembic.ini 文件中自定义迁移文件名模板。以下是两种方便的命名格式,可让迁移保持井井有条:
- 基于日期:
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
- 基于 unix 时间戳:
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
在文件名中使用日期或 unix 时间戳可以使迁移保持井井有条,使导航更容易。我更喜欢使用 unix 时间戳,下一节将提供一个示例。
3. 表和迁移的注释
对于在团队中工作的人来说,注释属性是一个很好的做法。对于 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 <comment> 添加注释。注释将出现在文档字符串和文件名中。此命名可以更轻松地找到所需的迁移。
1728372261_c0a05e0cd317_add_integration_service.py 1728372272_a1b4c9df789d_add_user.py 1728372283_f32d57aa1234_update_order_status.py 1728372294_9c8e7ab45e11_create_payment.py 1728372305_bef657cd9342_remove_old_column_from_users.py
4. 避免在迁移中使用模型
模型通常用于数据操作,例如将数据从一个表传输到另一个表或修改列值。但是,如果模型在创建迁移后发生更改,则在迁移中使用 orm 模型可能会导致问题。在这种情况下,基于旧模型的迁移在执行时将会中断,因为数据库架构可能不再与当前模型匹配。
迁移应该是静态的并且独立于模型的当前状态,以确保无论代码如何更改都能正确执行。以下是避免使用模型进行数据操作的两种方法。
- 使用原始 sql 进行数据操作:
def upgrade(): op.execute( "update user_account set email = concat(username, '@example.com') where email is null;" ) def downgrade(): op.execute( "update user_account set email = null where email like '%@example.com';" )
- 直接在迁移中定义表: 如果想使用sqlalchemy进行数据操作,可以直接在迁移中手动定义表。这确保了迁移执行时的静态模式,并且不会依赖于模型中的更改。
from sqlalchemy import table, column, string def upgrade(): # define the user_account table to work with data user_account = table( 'user_account', column('id'), column('username', string), column('email', string) ) # get a connection to the database conn = op.get_bind() # select all users without an email users = conn.execute( user_account.select().where(user_account.c.email == none) ) # update email for each user for user in users: conn.execute( user_account.update().where( user_account.c.id == user.id ).values( email=f"{@example.com">user.username}@example.com" ) ) def downgrade(): user_account = table( 'user_account', column('id'), column('email', string) ) conn = op.get_bind() # remove email for users added in the upgrade conn.execute( user_account.update().where( user_account.c.email.like('%@example.com') ).values(email=none) )
5. 迁移测试的阶梯测试
阶梯测试涉及逐步测试升级/降级迁移,以确保整个迁移链正常工作。这确保每次迁移都可以成功地从头开始创建新数据库并降级而不会出现问题。将此测试添加到 ci 对于团队来说非常宝贵,可以节省时间并减少挫败感。
将测试集成到您的项目中可以轻松快速地完成。您可以在此存储库中找到代码示例。它还包括其他可能有帮助的有价值的迁移测试。
6. 迁移服务
用于执行迁移的单独服务。这只是执行迁移的一种方法。当在本地或类似开发的环境中开发时,这种方法非常适合。我想提醒您有关条件 dependent_on 功能,该功能与此处相关。我们使用 alembic 获取应用程序映像并在单独的容器中运行它。我们添加对数据库的依赖关系,条件是仅当数据库准备好处理请求(service_healthy)时才开始迁移。此外,可以为应用程序添加条件依赖项(service_completed_successively),确保它仅在迁移成功完成后启动。
db: image: postgres:15 ... healthcheck: test: ["cmd-shell", "pg_isready -u ${postgres_user} -d ${postgres_db}"] interval: 10s start_period: 10s app_migrations: image: <app-image> command: [ "python", "-m", "alembic", "-c", "<path>/alembic.ini", "upgrade", "head" ] depends_on: db: condition: service_healthy app: ... depends_on: app_migrations: condition: service_completed_successfully
depends_on 条件确保迁移仅在数据库完全准备好后运行,并且应用程序在迁移完成后启动。
7. 模型的 mixins
虽然这可能是显而易见的一点,但重要的是不要忽视它。使用 mixins 是避免代码重复的便捷方法。 mixin 是包含常用字段和方法的类,可以将其集成到需要的任何模型中。例如,我们经常需要created_at和updated_at字段来跟踪记录的创建和更新时间。使用基于 uuid 的 id 来标准化主键也很有用。所有这些都可以封装在 mixin 中。
import uuid from sqlalchemy import column, datetime, func from sqlalchemy.dialects.postgresql import uuid class timestampmixin: created_at = column( datetime, server_default=func.now(), nullable=false, comment="record creation time" ) updated_at = column( datetime, onupdate=func.now(), nullable=true, comment="unique record identifier" ) class uuidprimarykeymixin: id = column( uuid(as_uuid=true), primary_key=true, default=uuid.uuid4, comment="unique record identifier" )
通过添加这些 mixins,我们可以在需要的任何模型中包含 uuid id 和时间戳:
class User(UUIDPrimaryKeyMixin, TimestampMixin, BaseModel): __tablename__ = 'user' # Other columns...
结论
处理迁移可能具有挑战性,但遵循这些简单的做法有助于保持项目井然有序且易于管理。命名约定、日期排序、注释和测试使我免于混乱并有助于防止错误。我希望这篇文章对您有所帮助——请随时在评论中分享您自己的迁移技巧!
到这里,我们也就讲完了《Alembic 和 SQLAlchemy 的最佳实践》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
390 收藏
-
451 收藏
-
199 收藏
-
127 收藏
-
260 收藏
-
342 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习