有什么方法可以在 Alembic 中生成顺序修订 ID?

Is there any way to generate sequential Revision IDs in Alembic?

我正在使用 Alembic 作为 Python 项目的数据库迁移工具。当我 运行 这样的命令时:

alembic revision -m "adding a column"

...它将添加一个名为 alembic/versions/xxxxxxxxxxxx_adding_a_column.py 的新文件,其中 xxxxxxxxxxxx 是随机生成的 12 位哈希值。

从人类可读的角度来看,这有点问题,因为这意味着在查看 alembic/versions 目录时,所有文件将以随机顺序出现,而不是按顺序出现/ 时间顺序。

Alembic 中是否有任何选项可以确保这些前缀修订 ID 是连续的?我想我可以手动重命名文件,然后更新引用,但我想知道是否已经有这样的功能。

听起来,您对按顺序列出的修订文件比按顺序排列的修订 ID 更感兴趣。前者可以在不改变修订 ID 生成方式的情况下实现。

当你运行alembic init alembic时生成的alembic.ini文件有一个配置修订文件命名的部分:

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

这是来自文档的解释:

file_template - this is the naming scheme used to generate new migration files. The value present is the default, so is commented out. Tokens available include:

  • %%(rev)s - revision id
  • %%(slug)s - a truncated string derived from the revision message
  • %%(year)d, %%(month).2d, %%(day).2d, %%(hour).2d, %%(minute).2d, %%(second).2d - components of the create date, by default datetime.datetime.now() unless the timezone configuration option is also used.

因此将 file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s 添加到 alembic.ini 会将您的修订命名为 2018-11-15_xxxxxxxxxxxx_adding_a_column.py

我发现了这个问题:https://bitbucket.org/zzzeek/alembic/issues/371/add-unixtime-stamp-to-start-of-versions 它为我指明了正确的方向。

A comment from from that issue:

timestamps don't necessarily tell you which file is the most "recent", since branching is allowed. "alembic history" is meant to be the best source of truth on this.

因此,文件命名解决方案不能保证迁移在目录中按逻辑顺序排列(但对 IMO 有帮助)。同样的论点也可以反对使用顺序 ID。

如果您想指定自己的修订标识符,请在命令行中使用 --rev-id 标志。

例如:

alembic revision -m 'a message' --rev-id=1

生成了一个名为 1_a_message.py:

的文件
"""a message

Revision ID: 1
Revises:
Create Date: 2018-11-15 13:40:31.228888

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '1'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    pass


def downgrade():
    pass

因此您绝对可以自己管理修订标识符。编写一个 bash 脚本来触发你的修订生成,自动传递一个基于 rev_id 的日期时间是微不足道的,例如--rev-id=<current datetime> 管理目录中列出的顺序。

如果未指定修订 ID,将调用 alembic.util.langhelpers 处的函数 rev_id()

def rev_id():
    return uuid.uuid4().hex[-12:]

rev_id() 的函数调用在 alembic 源代码中进行了硬编码,因此如果不对函数进行猴子修补,将很难覆盖该行为。您可以创建该库的分支并重新定义该函数,或者使它调用的用于生成 ID 的函数可配置。

我制作了一个脚本,根据 ####_ 模式已经存在的迁移数量自动增加修订号。这是一个 TLDR 版本。我将其保存为 migrations.sh 并更改第 2 行中的路径

#!/usr/bin/env bash
NEXT_ID=`ls kennel/db/versions/* | grep -P '/\d{4}_.*\.py$' | wc -l`
alembic revision -m $@ --rev-id=`printf "%04d" ${NEXT_ID}`

然后你可以像这样使用它:

./migrations.sh migration_name
# or 
./migrations.sh migration_name --autogenerate

完整脚本有文档并使用默认值 --autogenerate,可以使用 --empty 标志禁用。 https://gist.github.com/chriscauley/cf0b038d055076a2a30de43526d4150e

我在我的案例中找到了如何在没有额外的 bash 脚本的情况下做到这一点,只是 env.py 中的一些突变魔法。也许它会对某人有所帮助。

Alembic 具有 customizing generated revisions 的强大功能,因此我们可以在此级别编写覆盖:

# env.py
def process_revision_directives(context, revision, directives):
    # extract Migration
    migration_script = directives[0]
    # extract current head revision
    head_revision = ScriptDirectory.from_config(context.config).get_current_head()
    
    if head_revision is None:
        # edge case with first migration
        new_rev_id = 1
    else:
        # default branch with incrementation
        last_rev_id = int(head_revision.lstrip('0'))
        new_rev_id = last_rev_id + 1
    # fill zeros up to 4 digits: 1 -> 0001
    migration_script.rev_id = '{0:04}'.format(new_rev_id)

...
# then use it context.configure
context.configure(
  ...
  process_revision_directives=process_revision_directives,
)

如果您还想将它用于没有 --autogenerate 创建的修订,您应该在 alembic.ini

中将 revision_environment 设置为 true

是的,但是通过使用日期和时间

以下是 alembic 中可用的动态变量

file_template - 这是用于生成新迁移文件的命名方案。存在的值是默认值,因此被注释掉。

可用的代币包括:

%%(rev)s - revision id
%%(slug)s - a truncated string derived from the revision message
%%(year)d, %%(month).2d, %%(day).2d, %%(hour).2d, %%(minute).2d, %%(second).2d - components of the create date as returned by datetime.datetime.now()

因此,例如,您可以对顺序文件名使用以下配置

# template used to generate migration files
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d-%%(second).2d_%%(rev)s_%%(slug)s

这将生成以下输出

YYYY-mm-dd-HH-MM-SS_<rev>_<message_slug>

虽然我不需要迁移分支,但我使用这个

@writer.rewrites(ops.MigrationScript)
def revid_increment(ctx: migration.MigrationContext, revisions: tuple, op: ops.MigrationScript):
    op.rev_id = '{0:04}'.format(len(tuple(ctx.script.walk_revisions())) + 1)
    return op

它可以轻松替换当前的 rev_id 命名方案,添加时间戳、日期等等...