63

我使用 SQLite 作为基于 PySide 的桌面应用程序的应用程序文件格式(请参阅此处了解为什么要这样做)。也就是说,当用户使用我的应用程序时,他们的数据会保存在他们机器上的单个数据库文件中。我正在使用 SQLAlchemy ORM 与数据库进行通信。

当我发布应用程序的新版本时,我可能会修改数据库模式。我不希望用户每次更改架构时都必须丢弃他们的数据,因此我需要将他们的数据库迁移到最新格式。此外,我创建了很多临时数据库来保存数据的子集以供某些外部进程使用。我想用 alembic 创建这些数据库,以便用正确的版本标记它们。

我有几个问题:

  • 有没有办法从我的 Python 代码中调用 alembic?我认为必须使用Popen纯 Python 模块很奇怪,但文档只使用命令行中的 alembic。主要是,我需要将数据库位置更改为用户数据库所在的位置。

  • 如果这不可能,我可以在不编辑 .ini 文件的情况下从命令行指定新的数据库位置吗?这将使调用 alembicPopen没什么大不了的。

  • 我看到 alembic 将其版本信息保存在一个名为 的简单表下alembic_version,其中一列称为version_num,单行指定版本。我可以在我的架构中添加一个alembic_version表并在创建新数据库时用最新版本填充它,这样就没有开销了吗?这还是个好主意吗?我应该只使用 alembic 创建所有数据库吗?

我在项目目录中用于开发的单个数据库非常适合使用 alembic。我想使用 alembic 在任意位置方便地迁移和创建数据库,最好是通过某种 Python API,而不是命令行。这个应用程序也被 cx_Freeze 冻结,以防万一。

谢谢!

4

9 回答 9

63

以下是我将软件连接到以下内容后学到的内容alembic

有没有办法从我的 Python 代码中调用 alembic?

是的。在撰写本文时,alembic 的主要入口点是alembic.config.main,因此您可以导入它并自己调用它,例如:

import alembic.config
alembicArgs = [
    '--raiseerr',
    'upgrade', 'head',
]
alembic.config.main(argv=alembicArgs)

请注意,alembic 在当前目录(即 os.getcwd())中查找迁移。我已经通过os.chdir(migration_directory)在调用 alembic 之前使用来处理这个问题,但可能有更好的解决方案。


我可以在不编辑 .ini 文件的情况下从命令行指定新的数据库位置吗?

是的。关键在于-x命令行参数。来自alembic -h(令人惊讶的是,我无法在文档中找到命令行参数参考):

optional arguments:
 -x X                  Additional arguments consumed by custom env.py
                       scripts, e.g. -x setting1=somesetting -x
                       setting2=somesetting

因此,您可以创建自己的参数,例如dbPath,然后将其截取env.py

alembic -x dbPath=/path/to/sqlite.db upgrade head

然后例如在env.py

def run_migrations_online():   
    # get the alembic section of the config file
    ini_section = config.get_section(config.config_ini_section)

    # if a database path was provided, override the one in alembic.ini
    db_path = context.get_x_argument(as_dictionary=True).get('dbPath')
    if db_path:
        ini_section['sqlalchemy.url'] = db_path

    # establish a connectable object as normal
    connectable = engine_from_config(
        ini_section,
        prefix='sqlalchemy.',
        poolclass=pool.NullPool)

    # etc

当然,您也可以使用argvin提供 -x 参数alembic.config.main

我同意@davidism关于使用迁移 vs metadata.create_all():)

于 2016-02-04T20:51:34.213 回答
18

如果您查看 alembic 文档中的命令 API页面,您会看到一个如何直接从 Python 应用程序运行 CLI 命令的示例。无需通过 CLI 代码。

运行alembic.config.main的缺点env.py是执行的脚本可能不是您想要的。例如,它将修改您的日志记录配置。

另一种非常简单的方法是使用上面链接的“命令 API”。例如,这是我最终编写的一个小辅助函数:

from alembic.config import Config
from alembic import command

def run_migrations(script_location: str, dsn: str) -> None:
    LOG.info('Running DB migrations in %r on %r', script_location, dsn)
    alembic_cfg = Config()
    alembic_cfg.set_main_option('script_location', script_location)
    alembic_cfg.set_main_option('sqlalchemy.url', dsn)
    command.upgrade(alembic_cfg, 'head')

如果需要,我使用set_main_option这里的方法能够在不同的数据库上运行迁移。所以我可以简单地这样称呼它:

run_migrations('/path/to/migrations', 'postgresql:///my_database')

从哪里获得这两个值(路径和 DSN)取决于您。但这似乎与您想要实现的目标非常接近。命令 API 还具有stamp()方法,允许您将给定的数据库标记为特定版本。上面的例子可以很容易地适应调用它。

于 2019-01-28T13:15:06.573 回答
12

这是一个非常广泛的问题,实际实施您的想法将取决于您,但这是可能的。

您可以在不使用命令的情况下从 Python 代码中调用 Alembic,因为它也是在 Python 中实现的!您只需要重新创建命令在幕后执行的操作。

诚然,这些文档的状态不是很好,因为这些仍然是该库的相对较早的版本,但是稍微挖掘一下,您会发现以下内容:

  1. 创建配置
  2. 使用 Config 创建一个ScriptDirectory
  3. 使用 Config 和 ScriptDirectory 创建EnvironmentContext
  4. 使用 EnvironmentContext 创建一个MigrationContext
  5. 大多数命令使用 Config 和 MigrationContext 中的一些方法组合

我编写了一个扩展程序来提供对 Flask-SQLAlchemy 数据库的编程 Alembic 访问。该实现与 Flask 和 Flask-SQLAlchemy 相关联,但它应该是一个很好的起点。 请参阅此处的 Flask-Alembic。

关于您关于如何创建新数据库的最后一点,您可以使用 Alembic 创建表,也可以使用metadata.create_all()then alembic stamp head(或等效的 python 代码)。我建议始终使用迁移路径来创建表,而忽略原始的metadata.create_all().

我对 cx_freeze 没有任何经验,但只要迁移包含在发行版中并且代码中该目录的路径正确,就应该没问题。

于 2014-07-08T02:59:10.700 回答
8

这是一个纯粹的程序示例,说明如何以编程方式配置和调用 alembic 命令。

目录设置(便于代码阅读)

.                         # root dir
|- alembic/               # directory with migrations
|- tests/diy_alembic.py   # example script
|- alembic.ini            # ini file

这里是 diy_alembic.py

import os
import argparse
from alembic.config import Config
from alembic import command
import inspect

def alembic_set_stamp_head(user_parameter):
    # set the paths values
    this_file_directory = os.path.dirname(os.path.abspath(inspect.stack()[0][1]))
    root_directory      = os.path.join(this_file_directory, '..')
    alembic_directory   = os.path.join(root_directory, 'alembic')
    ini_path            = os.path.join(root_directory, 'alembic.ini')

    # create Alembic config and feed it with paths
    config = Config(ini_path)
    config.set_main_option('script_location', alembic_directory)    
    config.cmd_opts = argparse.Namespace()   # arguments stub

    # If it is required to pass -x parameters to alembic
    x_arg = 'user_parameter=' + user_parameter
    if not hasattr(config.cmd_opts, 'x'):
        if x_arg is not None:
            setattr(config.cmd_opts, 'x', [])
            if isinstance(x_arg, list) or isinstance(x_arg, tuple):
                for x in x_arg:
                    config.cmd_opts.x.append(x)
            else:
                config.cmd_opts.x.append(x_arg)
        else:
            setattr(config.cmd_opts, 'x', None)

    #prepare and run the command
    revision = 'head'
    sql = False
    tag = None
    command.stamp(config, revision, sql=sql, tag=tag)

    #upgrade command
    command.upgrade(config, revision, sql=sql, tag=tag)

该代码或多或少是从这个 Flask-Alembic 文件中删减的。这是查看其他命令用法和详细信息的好地方。

为什么这个解决方案?- 它是为了在运行自动化测试时创建 alembic 标记、升级和降级而编写的。

  • os.chdir(migration_directory) 干扰了一些测试。
  • 我们希望有一个数据库创建和操作的来源。“如果我们使用 alembic 创建和管理数据库,alembic 但不是 metadata.create_all() shell 也可用于测试”。
  • 即使上面的代码超过 4 行,如果以这种方式驱动,alembic 也显示出自己是一个很好的可控野兽。
于 2017-04-20T21:47:56.323 回答
4

请参阅 alembic.operations.base.Operations 的文档:

    from alembic.runtime.migration import MigrationContext
    from alembic.operations import Operations

    conn = myengine.connect()
    ctx = MigrationContext.configure(conn)
    op = Operations(ctx)

    op.alter_column("t", "c", nullable=True)
于 2018-10-08T10:38:17.063 回答
3

对于其他试图使用 SQLAlchemy 实现飞行式结果的人来说,这对我有用:

migration.py添加到您的项目中:

from flask_alembic import Alembic

def migrate(app):
    alembic = Alembic()
    alembic.init_app(app)
    with app.app_context():
        alembic.upgrade()

数据库初始化后在应用程序启动时调用它

application = Flask(__name__)
db = SQLAlchemy()
db.init_app(application)
migration.migrate(application)

然后你只需要做剩下的标准alembic步骤:

将您的项目初始化为 alembic

alembic init alembic

更新 env.py:

from models import MyModel
target_metadata = [MyModel.Base.metadata]

更新 alembic.ini

sqlalchemy.url = postgresql://postgres:postgres@localhost:5432/my_db

假设您的 SQLAlchemy 模型已经定义,您现在可以自动生成脚本:

alembic revision --autogenerate -m "descriptive migration message"

如果您收到有关无法在 env.py 中导入模型的错误,您可以在终端中运行以下命令进行修复

export PYTHONPATH=/path/to/your/project

最后,我的迁移脚本在 alembic/versions 目录中生成,我必须将它们复制到迁移目录以便 alembic 获取它们。

├── alembic
│   ├── env.py
│   ├── README
│   ├── script.py.mako
│   └── versions
│       ├── a5402f383da8_01_init.py  # generated here...
│       └── __pycache__
├── alembic.ini
├── migrations
│   ├── a5402f383da8_01_init.py  # manually copied here
│   └── script.py.mako

我可能有一些错误配置,但它现在正在工作。

于 2018-10-06T23:25:10.413 回答
2

我没有使用 Flask,所以我无法使用已经推荐的 Flask-Alembic 库。相反,经过相当多的修改后,我编写了以下简短函数来运行所有适用的迁移。我将所有与 alembic 相关的文件保存在名为迁移的子模块(文件夹)下。我实际上将 与 保持alembic.ini在一起env.py,这可能有点不正统。这是我文件中的一个片段,alembic.ini用于调整:

[alembic]
script_location = .

然后我在同一目录中添加了以下文件并将其命名为run.py. 但是,无论您保留脚本,您需要做的就是修改下面的代码以指向正确的路径:

from alembic.command import upgrade
from alembic.config import Config
import os


def run_sql_migrations():
    # retrieves the directory that *this* file is in
    migrations_dir = os.path.dirname(os.path.realpath(__file__))
    # this assumes the alembic.ini is also contained in this same directory
    config_file = os.path.join(migrations_dir, "alembic.ini")

    config = Config(file_=config_file)
    config.set_main_option("script_location", migrations_dir)

    # upgrade the database to the latest revision
    upgrade(config, "head")

然后有了该run.py文件,它允许我在我的主代码中执行此操作:

from mymodule.migrations.run import run_sql_migrations


run_sql_migrations()
于 2019-01-22T22:50:56.810 回答
1

Alembic 将其所有命令公开为可导入的可调用对象alembic.command

https://alembic.sqlalchemy.org/en/latest/api/commands.html

我编写了这个包装器,这样我就可以通过 python 代码设置自定义日志记录。

import logging

import alembic.command
import alembic.config

from somewhere import config_logging


def run():
    config_logging()

    log = logging.getLogger(__name__)

    if len(sys.argv) < 3:
        log.error("command must be specified")
        exit(1)

    else:
        command_name = sys.argv[2]

    try:
        command = getattr(alembic.command, name)

    except AttributeError:
        log.error(f"{name} is not a valid alembic command")
        exit(2)

    config = alembic.config.Config()
    config.set_main_option("script_location", "path/to/alembic")
    config.set_main_option("sqlalchemy.url", "postgres://...")

    command(config, *sys.argv[3:])
于 2021-03-30T17:13:25.237 回答
0

不是一个真正的答案,但我很难接受,所以我想分享:

如何使用 alembic.command.upgrade 以编程方式传递 x_argument:

class CmdOpts:
    x = {"data=true"}

这里 data=true 是我在命令行中作为 x_argument 传递的

    alembic_config = AlembicConfig(ini_location)
    setattr(alembic_config, "cmd_opts", CmdOpts())
    alembic_config.cmd_opts.x = {"data": True}

于 2020-09-18T09:51:47.527 回答