Alembic 简介
Alembic 是由 SQLAlchemy 的创始人 Mike Bayer 设计的一个数据库迁移工具。它不仅支持自动迁移脚本生成,还允许开发者手动编辑迁移脚本来满足特定的需求。Alembic 通过提供一个环境来跟踪数据库模式的变更历史,确保数据库的版本与应用代码保持同步。
详见功能参见:
- https://alembic.sqlalchemy.org/en/latest/tutorial.html
- https://pypi.org/search/?q=alembic
独特的功能和特
Alembic 作为一个数据库迁移工具,在某些方面具有独特的功能和特性,这些特性让它在数据库迁移工具领域中脱颖而出:
-
与 SQLAlchemy 紧密集成:
Alembic 是为 SQLAlchemy 定制的迁移工具,它可以无缝地与 SQLAlchemy ORM 一起工作。这意味着如果你的项目已经在使用 SQLAlchemy,Alembic 可以非常自然地融入你的工作流程。 -
自动迁移生成:
Alembic 提供了自动迁移生成功能,能够根据模型定义的变化自动创建迁移脚本。这大大减少了编写迁移脚本的工作量,并且减少了人为错误。 -
多重数据库支持:
Alembic 不仅限于单一数据库,它支持多数据库环境。通过配置,Alembic 可以管理多个数据库的迁移,这对于需要在多种数据库之间迁移数据的项目非常有用。 -
灵活的命令行工具:
Alembic 提供了一个功能丰富的命令行界面,允许开发者执行各种迁移操作,如创建迁移脚本、升级数据库、回滚迁移等。 -
迁移依赖管理:
Alembic 使用down_revision
来管理迁移之间的依赖关系,这确保了迁移的顺序性和一致性。这种依赖管理机制使得迁移过程更加可靠。 -
版本序列化:
Alembic 使用一种部分 GUID 方案来命名迁移脚本,而不是简单的递增数字。这为迁移脚本的组织和合并提供了更大的灵活性。 -
可扩展的脚本模板:
Alembic 允许自定义迁移脚本模板,这意味着开发者可以控制迁移脚本的结构,包括导入的模块和upgrade()
与downgrade()
函数的布局。 -
环境脚本自定义:
env.py
脚本是 Alembic 迁移环境的一部分,可以被自定义以适应特定的项目需求,如加载项目特定的库或配置多数据库环境。 -
支持复杂的迁移操作:
Alembic 不仅仅支持简单的迁移操作,它还支持更复杂的数据库迁移任务,如批量数据操作和复杂的数据转换。 -
可与现有工作流集成:
Alembic 可以轻松集成到现有的版本控制系统和开发工作流程中,与其他开发工具和流程协同工作。
与其他迁移工具相比,Alembic 的这些特性使其在灵活性、易用性和功能丰富性方面具有优势。然而,选择哪个迁移工具还取决于项目的具体需求、团队的熟悉度以及现有的技术栈。
如何在fastapi中使用?
def run_migrations():try:from alembic.config import Configfrom alembic import commandalembic_cfg = Config("alembic.ini")command.upgrade(alembic_cfg, "head")except Exception as e:print(f"Error: {e}")@asynccontextmanager
async def lifespan(app: FastAPI):run_migrations()yieldapp = FastAPI(docs_url="/docs", redoc_url=None, lifespan=lifespan
)
配置文件示例
alembic.ini
# A generic, single database configuration.[alembic]
# path to migration scripts
script_location = migrations# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python>=3.9 or backports.zoneinfo library.
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false# version location specification; This defaults
# to migrations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8# sqlalchemy.url = REPLACE_WITH_DATABASE_URL[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic[handlers]
keys = console[formatters]
keys = generic[logger_root]
level = WARN
handlers = console
qualname =[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine[logger_alembic]
level = INFO
handlers =
qualname = alembic[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
脚本管理目录示例
具体参见:https://github.com/open-webui/open-webui/blob/main/backend/alembic.ini
坑
注意:如果在logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
中设置了日志格式之后,又运行了alembic.upgrade
, 则python日志的输出格式是由alembic.ini
中的[formatter_generic]
部分中定义的format确定的,
如果要修改日志的输出格式,需要修改alembic.ini
中的[formatter_generic]
部分中的format, 或者先运行alembic.upgrade
,再执行logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
部分。