我正在使用SQLite作为应用程序文件格式(请参阅here,了解为什么要这样做)为我的基于PySide的桌面应用程序。也就是说,当用户使用我的应用程序时,他们的数据将保存在其计算机上的单个数据库文件中。我正在使用SQLAlchemy ORM与数据库进行通信。
当我发布应用程序的新版本时,我可能会修改数据库架构。我不希望用户每次更改架构时都要丢弃他们的数据,所以我需要将他们的数据库迁移到最新的格式。此外,我创建了很多临时数据库来保存数据的子集,以便与某些外部进程一起使用。我想用alembic创建这些数据库,以便用正确的版本标记它们。
我有几个问题:
Popen
用于纯Python模块是很奇怪的,但是文档只是使用命令行中的alembic。主要是,我需要将数据库位置更改为用户数据库所在的位置。Popen
调用alembic不是什么大不了的事。alembic_version
的简单表中,其中一列名为version_num
,一行指定版本。我可以在我的架构中添加alembic_version
表,并在创建新数据库时使用最新版本填充它,这样就没有开销吗?这甚至是个好主意;我应该使用alembic来创建所有数据库吗?我的alembic非常适合我在项目目录中使用的单个数据库。我想使用alembic在任意位置方便地迁移和创建数据库,最好是通过某种Python API,而不是命令行。此应用程序也被cx_Freeze冻结,以防有所作为。
谢谢!
以下是我将我的软件连接到alembic
后学到的内容:
是。在撰写本文时,alembic的主要切入点是alembic.config.main
,因此您可以导入它并自己调用它,例如:
import alembic.config
alembicArgs = [
'--raiseerr',
'upgrade', 'head',
]
alembic.config.main(argv=alembicArgs)
请注意,alembic在当前目录中查找迁移(即os.getcwd())。我在调用alembic之前使用os.chdir(migration_directory)
来处理这个问题,但可能有更好的解决方案。
是。关键在于-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
当然,您也可以在argv
中使用alembic.config.main
提供-x参数。
我同意@davidism关于使用迁移vs metadata.create_all()
:)
这是一个非常广泛的问题,实际上实现你的想法将取决于你,但它是可能的。
您可以在不使用命令的情况下从Python代码调用Alembic,因为它也是用Python实现的!您只需要重新创建命令在幕后执行的操作。
不可否认,这些文档并不是很好,因为这些文档仍然是相对早期版本的库,但只需稍加挖掘,您就会发现以下内容:
我编写了一个扩展来为Flask-SQLAlchemy数据库提供这种程序化的Alembic访问。实现与Flask和Flask-SQLAlchemy绑定,但它应该是一个很好的起点。 See Flask-Alembic here.
关于如何创建新数据库的最后一点,您可以使用Alembic创建表,也可以使用metadata.create_all()
然后使用alembic stamp head
(或等效的python代码)。我建议始终使用迁移路径来创建表,并忽略原始的metadata.create_all()
。
我对cx_freeze没有任何经验,但只要迁移包含在发行版中并且代码中该目录的路径正确,它就应该没问题。
这是一个纯编程的示例,说明如何以编程方式配置和调用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)
代码或多或少是从this Flask-Alembic file切割而来的。这是查看其他命令用法和详细信息的好地方。
为什么这个解决方 - 它的编写需要在运行自动化测试时创建一个alembic邮票,升级和降级。
如果您查看alembic文档中的commands 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()方法,允许您将给定的DB标记为特定版本。上面的例子很容易适应这个。
对于其他试图用SQLAlchemy实现flyway-esque结果的人来说,这对我有用:
将migration.py添加到项目中:
from flask_alembic import Alembic
def migrate(app):
alembic = Alembic()
alembic.init_app(app)
with app.app_context():
alembic.upgrade()
在db初始化之后,在应用程序启动时调用它
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
我可能有一些配置错误,但它现在正在运作。
我没有使用Flask,所以我无法使用已经推荐的Flask-Alembic库。相反,在经过相当多的修补之后,我编写了以下简短函数来运行所有适用的迁移。我将所有与alembic相关的文件保存在名为migrations的子模块(文件夹)下。我实际上将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()
请参阅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)