教程#
Alembic 提供了使用 SQLAlchemy 作为底层引擎为关系数据库创建、管理和调用变更管理脚本的功能。本教程将全面介绍此工具的理论和用法。
首先,请确保已按照 安装 中所述安装 Alembic。如链接文档中所述,通常最好将 Alembic 安装在与目标项目相同的模块/Python 路径中,通常使用 Python 虚拟环境,以便在运行 alembic
命令时,由 alembic
调用的 Python 脚本(即项目的 env.py
脚本)可以访问应用程序的模型。在所有情况下,这并非严格必要,但在绝大多数情况下通常是首选。
以下教程假定 alembic
命令行实用程序存在于本地路径中,并且在调用时,可以访问与目标项目相同的 Python 模块环境。
迁移环境#
使用 Alembic 从创建迁移环境开始。这是一个特定于某个应用程序的脚本目录。迁移环境只创建一次,然后与应用程序的源代码本身一起维护。该环境使用 Alembic 的 init
命令创建,然后可以根据应用程序的特定需求进行自定义。
此环境的结构(包括一些生成的迁移脚本)如下所示
yourproject/
alembic/
env.py
README
script.py.mako
versions/
3512b954651e_add_account.py
2b1ae634e5cd_add_order_id.py
3adcc9a56557_rename_username_field.py
该目录包括以下目录/文件
yourproject
- 这是应用程序源代码的根目录或其中的某个目录。alembic
- 此目录位于应用程序的源代码树中,是迁移环境的主目录。可以将其命名为任何名称,使用多个数据库的项目甚至可能有多个。env.py
- 这是在调用 alembic 迁移工具时运行的 Python 脚本。至少,它包含配置和生成 SQLAlchemy 引擎、从该引擎获取连接以及事务的说明,然后使用该连接作为数据库连接的源来调用迁移引擎。env.py
脚本是生成环境的一部分,因此迁移的运行方式完全可自定义。如何连接的确切细节在此处,以及如何调用迁移环境的细节也在此处。可以修改脚本,以便可以操作多个引擎,可以将自定义参数传递到迁移环境,可以加载并提供应用程序特定的库和模型。Alembic 包含一组初始化模板,其中包含针对不同用例的不同种类的
env.py
。README
- 与各种环境模板一起包含,应包含一些信息。script.py.mako
- 这是一个 Mako 模板文件,用于生成新的迁移脚本。此处的内容用于在versions/
中生成新文件。这是可编写脚本的,以便可以控制每个迁移文件的结构,包括要包含在其中的标准导入,以及对upgrade()
和downgrade()
函数的结构所做的更改。例如,multidb
环境允许使用命名方案upgrade_engine1()
、upgrade_engine2()
生成多个函数。versions/
- 此目录保存各个版本脚本。其他迁移工具的用户可能会注意到,此处的文件不使用升序整数,而是使用部分 GUID 方法。在 Alembic 中,版本脚本的顺序与其自身脚本中的指令相关,并且理论上可以将版本文件“拼接”在其他文件之间,从而允许合并来自不同分支的迁移序列,尽管需要小心手动操作。
创建环境#
对环境有了基本的了解后,我们可以使用 alembic init
创建一个环境。这将使用“通用”模板创建一个环境
$ cd /path/to/yourproject
$ source /path/to/yourproject/.venv/bin/activate # assuming a local virtualenv
$ alembic init alembic
在上面,调用 init
命令以生成名为 alembic
的迁移目录
Creating directory /path/to/yourproject/alembic...done
Creating directory /path/to/yourproject/alembic/versions...done
Generating /path/to/yourproject/alembic.ini...done
Generating /path/to/yourproject/alembic/env.py...done
Generating /path/to/yourproject/alembic/README...done
Generating /path/to/yourproject/alembic/script.py.mako...done
Please edit configuration/connection/logging settings in
'/path/to/yourproject/alembic.ini' before proceeding.
Alembic 还包括其他环境模板。可以使用 list_templates
命令列出这些模板
$ alembic list_templates
Available templates:
generic - Generic single-database configuration.
async - Generic single-database configuration with an async dbapi.
multidb - Rudimentary multi-database configuration.
Templates are used via the 'init' command, e.g.:
alembic init --template generic ./scripts
1.8 版中已更改:已删除“pylons”环境模板。
编辑 .ini 文件#
Alembic 将文件 alembic.ini
放置在当前目录中。这是 alembic
脚本在调用时查找的文件。此文件可以存在于不同的目录中,其位置由 alembic
运行器的 --config
选项或 ALEMBIC_CONFIG
环境变量指定(前者优先)。
使用“通用”配置生成的文件如下所示
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# 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 ${script_location}/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:${script_location}/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 = driver://user:pass@localhost/dbname
# [post_write_hooks]
# This section 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
使用 Python 的 ConfigParser.SafeConfigParser
对象读取文件。提供 %(here)s
变量作为替换变量,可用于生成目录和文件的绝对路径名,如我们在上面使用 Alembic 脚本位置的路径所做的那样。
此文件包含以下功能
[alembic]
- 这是 Alembic 读取以确定配置的部分。Alembic 的核心实现不会直接读取文件的任何其他区域,不包括可能从终端用户可自定义的env.py
文件中使用的其他指令(请参见下面的注释)。可以使用--name
命令行标志自定义“alembic”名称;有关此内容的基本示例,请参见 从一个 .ini 文件运行多个 Alembic 环境。注意
Alembic 环境模板中包含的默认
env.py
文件还将从日志记录部分[logging]
、[handlers]
等读取。如果正在使用的配置文件不包含日志记录指令,请从生成的env.py
文件中移除fileConfig()
指令,以防止它尝试配置日志记录。script_location
- 这是 Alembic 环境的位置。它通常指定为文件系统位置,相对或绝对。如果位置是相对路径,则解释为相对于当前目录。这是 Alembic 在所有情况下唯一需要的键。命令
alembic init alembic
生成的 .ini 文件自动将目录名称alembic
放置在此处。特殊变量%(here)s
也可以使用,如%(here)s/alembic
中所示。为了支持将自己打包成 .egg 文件的应用程序,该值也可以指定为 包资源,在这种情况下,
resource_filename()
用于查找文件(0.2.2 中的新增功能)。任何包含冒号的非绝对 URI 在此处被解释为资源名称,而不是直接的文件名。file_template
- 这是用于生成新迁移文件的命名方案。如果您希望在迁移文件前加上日期和时间,以便按时间顺序排列,请取消对显示值的注释。默认值为%%(rev)s_%%(slug)s
。可用的令牌包括%%(rev)s
- 修订 ID%%(slug)s
- 从修订消息派生的截断字符串%%(epoch)s
- 基于创建日期的纪元时间戳;这利用 Pythondatetime.timestamp()
方法来生成纪元值。%%(year)d
,%%(month).2d
,%%(day).2d
,%%(hour).2d
,%%(minute).2d
,%%(second).2d
- 创建日期的组件,默认情况下为datetime.datetime.now()
,除非还使用了timezone
配置选项。
1.8 版中新增:添加了“epoch”
timezone
- 可选时区名称(例如UTC
、EST5EDT
等),它将应用于迁移文件注释中以及文件名中呈现的时间戳。此选项需要 Python>=3.9 或安装backports.zoneinfo
库。如果指定了timezone
,则创建日期对象不再从datetime.datetime.now()
派生,而是生成如下datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc ).astimezone(ZoneInfo(<timezone>))
1.13.0 版中更改:迁移中现在使用 Python 标准库
zoneinfo
进行时区渲染;以前使用python-dateutil
。truncate_slug_length
- 默认为 40,是“slug”字段中包含的最大字符数。sqlalchemy.url
- 通过 SQLAlchemy 连接到数据库的 URL。仅当env.py
文件调用此配置值时,才使用此配置值;在“通用”模板中,run_migrations_offline()
函数中对config.get_main_option("sqlalchemy.url")
的调用和run_migrations_online()
函数中对engine_from_config(prefix="sqlalchemy.")
的调用是引用此键的位置。如果 SQLAlchemy URL 应来自其他来源(例如来自环境变量或全局注册表),或者如果迁移环境使用多个数据库 URL,则建议开发人员更改env.py
文件以使用任何适当的方法来获取数据库 URL 或 URL。revision_environment
- 当此标志设置为值“true”时,将指示迁移环境脚本env.py
在生成新修订文件以及运行alembic history
命令时应无条件运行。sourceless
- 当设置为“true”时,仅作为 versions 目录中的 .pyc 或 .pyo 文件存在的修订文件将用作版本,从而允许“无源”版本控制文件夹。当保留为默认的“false”时,仅 .py 文件被用作版本文件。version_locations
- 可选的修订文件位置列表,允许修订同时存在于多个目录中。有关示例,请参阅 使用多个基准。version_path_separator
-version_locations
路径的分隔符。如果使用多个version_locations
,则应定义它。有关示例,请参阅 使用多个基准。recursive_version_locations
- 设置为“true”时,将在每个“version_locations”目录中递归搜索修订文件。在 1.10 版中添加。
output_encoding
- 当 Alembic 将script.py.mako
文件写入新迁移文件时要使用的编码。默认为'utf-8'
。[loggers]
、[handlers]
、[formatters]
、[logger_*]
、[handler_*]
、[formatter_*]
- 这些部分都是 Python 标准日志记录配置的一部分,其机制记录在 配置文件格式 中。与数据库连接一样,这些指令直接用作env.py
脚本中存在的logging.config.fileConfig()
调用的结果,你可以自由修改它。
对于仅使用单个数据库和通用配置,设置 SQLAlchemy URL 是唯一需要做的
sqlalchemy.url = postgresql://scott:tiger@localhost/test
创建迁移脚本#
有了这个环境,我们可以使用 alembic revision
创建一个新的修订。
$ alembic revision -m "create account table"
Generating /path/to/yourproject/alembic/versions/1975ea83b712_create_accoun
t_table.py...done
生成了一个新文件 1975ea83b712_create_account_table.py
。查看文件内部
"""create account table
Revision ID: 1975ea83b712
Revises:
Create Date: 2011-11-08 11:40:27.089406
"""
# revision identifiers, used by Alembic.
revision = '1975ea83b712'
down_revision = None
branch_labels = None
from alembic import op
import sqlalchemy as sa
def upgrade():
pass
def downgrade():
pass
该文件包含一些标题信息、当前修订和“降级”修订的标识符、基本 Alembic 指令的导入以及空的 upgrade()
和 downgrade()
函数。我们在这里的工作是使用将对数据库应用一组更改的指令填充 upgrade()
和 downgrade()
函数。通常,需要 upgrade()
,而仅当需要降级功能时才需要 downgrade()
,尽管这可能是个好主意。
另一个需要注意的是 down_revision
变量。这是 Alembic 了解应用迁移的正确顺序的方式。当我们创建下一个修订时,新文件的 down_revision
标识符将指向此文件
# revision identifiers, used by Alembic.
revision = 'ae1027a6acf'
down_revision = '1975ea83b712'
每次 Alembic 对 versions/
目录运行操作时,它都会读取其中的所有文件,并根据 down_revision
标识符如何链接在一起来编写一个列表,其中 down_revision
为 None
的表示第一个文件。从理论上讲,如果一个迁移环境有数千个迁移,这可能会开始给启动增加一些延迟,但实际上一个项目可能应该修剪旧迁移(请参阅部分 从头开始构建一个最新的数据库,了解如何执行此操作,同时保持完全构建当前数据库的能力)。
然后,我们可以向脚本添加一些指令,假设添加一个新表 account
def upgrade():
op.create_table(
'account',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('name', sa.String(50), nullable=False),
sa.Column('description', sa.Unicode(200)),
)
def downgrade():
op.drop_table('account')
create_table()
和 drop_table()
是 Alembic 指令。Alembic 通过这些指令提供所有基本数据库迁移操作,这些指令被设计得尽可能简单和极简;大多数这些指令不依赖于现有的表元数据。它们利用一个全局“上下文”,该上下文指示如何获取数据库连接(如果有的话;迁移也可以将 SQL/DDL 指令转储到文件),以便调用命令。此全局上下文与其他所有内容一样,在 env.py
脚本中设置。
所有 Alembic 指令的概述在 操作参考 中。
运行我们的首次迁移#
我们现在想要运行我们的迁移。假设我们的数据库是完全干净的,它还没有版本化。alembic upgrade
命令将运行升级操作,从当前数据库版本(在本例中为 None
)进行,到给定的目标版本。我们可以将 1975ea83b712
指定为我们想要升级到的版本,但在大多数情况下,只需告诉它“最新版本”,在本例中为 head
$ alembic upgrade head
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running upgrade None -> 1975ea83b712
哇,太棒了!请注意,我们在屏幕上看到的信息是 alembic.ini
中设置的日志记录配置的结果 - 将 alembic
流记录到控制台(特别是标准错误)。
在此处发生的进程包括 Alembic 首先检查数据库是否有一个名为 alembic_version
的表,如果没有,则创建它。它在此表中查找当前版本(如果有),然后计算从该版本到请求的版本(在本例中为 head
)的路径,已知该版本为 1975ea83b712
。然后它在每个文件中调用 upgrade()
方法以到达目标版本。
运行我们的第二次迁移#
我们再做一次,这样我们就可以有一些东西可以玩了。我们再次创建一个修订文件
$ alembic revision -m "Add a column"
Generating /path/to/yourapp/alembic/versions/ae1027a6acf_add_a_column.py...
done
让我们编辑此文件,并向 account
表中添加一个新列
"""Add a column
Revision ID: ae1027a6acf
Revises: 1975ea83b712
Create Date: 2011-11-08 12:37:36.714947
"""
# revision identifiers, used by Alembic.
revision = 'ae1027a6acf'
down_revision = '1975ea83b712'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('account', sa.Column('last_transaction_date', sa.DateTime))
def downgrade():
op.drop_column('account', 'last_transaction_date')
再次运行到 head
$ alembic upgrade head
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running upgrade 1975ea83b712 -> ae1027a6acf
我们现在已将 last_transaction_date
列添加到数据库中。
部分修订标识符#
任何时候我们需要明确地引用修订号时,我们都可以选择使用部分数字。只要此数字唯一标识版本,它就可以在任何命令中使用,在任何接受版本号的位置
$ alembic upgrade ae1
上面,我们使用 ae1
来引用修订 ae1027a6acf
。如果不止一个版本以该前缀开头,Alembic 将停止并通知您。
相对迁移标识符#
还支持相对升级/降级。要从当前版本移动两个版本,可以提供十进制值“+N”
$ alembic upgrade +2
接受负值用于降级
$ alembic downgrade -1
相对标识符也可以针对特定修订。例如,要升级到修订 ae1027a6acf
加上两个附加步骤
$ alembic upgrade ae10+2
获取信息#
当存在一些修订时,我们可以获取有关事物状态的一些信息。
首先,我们可以查看当前修订
$ alembic current
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
Current revision for postgresql://scott:XXXXX@localhost/test: 1975ea83b712 -> ae1027a6acf (head), Add a column
head
仅在该数据库的修订标识符与头修订匹配时显示。
我们还可以使用 alembic history
查看历史记录; --verbose
选项(由多个命令接受,包括 history
、current
、heads
和 branches
)将向我们显示有关每个修订的完整信息
$ alembic history --verbose
Rev: ae1027a6acf (head)
Parent: 1975ea83b712
Path: /path/to/yourproject/alembic/versions/ae1027a6acf_add_a_column.py
add a column
Revision ID: ae1027a6acf
Revises: 1975ea83b712
Create Date: 2014-11-20 13:02:54.849677
Rev: 1975ea83b712
Parent: <base>
Path: /path/to/yourproject/alembic/versions/1975ea83b712_add_account_table.py
create account table
Revision ID: 1975ea83b712
Revises:
Create Date: 2014-11-20 13:02:46.257104
查看历史记录范围#
使用 -r
选项到 alembic history
,我们还可以查看历史记录的各个部分。 -r
参数接受一个参数 [start]:[end]
,其中任何一个都可以是修订号、符号(如 head
、heads
或 base
、current
)以指定当前修订,以及 [start]
的负相对范围和 [end]
的正相对范围
$ alembic history -r1975ea:ae1027
从三轮前开始到当前迁移的相对范围,它将调用针对数据库的迁移环境以获取当前迁移
$ alembic history -r-3:current
注意
如上所示,要使用以负数(即破折号)开头的范围,由于 argparse 中的 bug,必须使用语法 -r-<base>:<head>
,没有任何空格,如上所示
$ alembic history -r-3:current
或者如果使用 --rev-range
,则必须使用等号
$ alembic history --rev-range=-3:current
如果参数名称后面有空格,则使用引号或转义符号不起作用。
查看从 1975 年到 head 的所有修订
$ alembic history -r1975ea:
降级#
我们可以通过调用 alembic downgrade
返回到开头来演示降级为无,在 Alembic 中称为 base
$ alembic downgrade base
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running downgrade ae1027a6acf -> 1975ea83b712
INFO [alembic.context] Running downgrade 1975ea83b712 -> None
回到无 - 然后再次向上
$ alembic upgrade head
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running upgrade None -> 1975ea83b712
INFO [alembic.context] Running upgrade 1975ea83b712 -> ae1027a6acf
后续步骤#
绝大多数 Alembic 环境大量使用“自动生成”功能。继续进入下一部分,自动生成迁移。