web-dev-qa-db-ja.com

アプリケーションコード内からAlembic APIを使用する

私のPySideベースのデスクトップアプリケーションでは、SQLiteをアプリケーションファイル形式として使用しています(これを行う理由については here を参照)。つまり、ユーザーが私のアプリを使用すると、ユーザーのデータはマシン上の単一のデータベースファイルに保存されます。 SQLAlchemy ORMを使用してデータベースと通信しています。

アプリケーションの新しいバージョンをリリースするときに、データベーススキーマを変更する場合があります。スキーマを変更するたびにユーザーがデータを破棄する必要がないようにしたいので、データベースを最新の形式に移行する必要があります。また、いくつかの外部プロセスで使用するためにデータのサブセットを保存するために、一時データベースをたくさん作成しています。これらのデータベースをalembicで作成して、適切なバージョンのタグが付けられるようにしたいと考えています。

少し質問があります:

  • 私のPythonコード内からalembicを呼び出す方法はありますか?純粋なPythonモジュールにPopenを使用する必要があるのは奇妙だと思います、しかし、ドキュメントはコマンドラインからalembicを使用するだけです。主に、ユーザーのデータベースがある場所にデータベースの場所を変更する必要があります。

  • それが不可能な場合、.iniファイルを編集せずにコマンドラインから新しいデータベースの場所を指定できますか?これはPopenを介してalembicを呼び出すことを大したことにはしません。

  • Alembicはバージョン情報をalembic_versionという単純なテーブルの下に保持し、version_numという1つの列とバージョンを指定する1つの行があることがわかります。新しいデータベースを作成するときにalembic_versionテーブルをスキーマに追加し、最新バージョンを設定してオーバーヘッドをなくすことはできますか?それも良い考えです。 alembicを使用してすべてのデータベースを作成する必要がありますか?

私はプロジェクトのディレクトリで開発に使用する単一のデータベースに対して優れた作業をしています。私はalembicを使用して、データベースを任意の場所に便利に移行および作成します。できれば、コマンドラインではなく、ある種のPython APIを使用して行います。このアプリケーションもcx_Freezeでフリーズされています。違い。

ありがとう!

38

ソフトウェアをalembicに接続した後に私が学んだことは次のとおりです。

私のPythonコード内から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)を使用してこれを処理しましたが、もっと良い解決策があるかもしれません。


.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
_

もちろん、_alembic.config.main_でargvを使用して-xパラメータを指定することもできます。

私は @ davidism に対抗してマイグレーションの使用について同意しますmetadata.create_all() :)

33
ForeverWintr

これは非常に広範な質問であり、実際にアイデアを実装するかどうかはあなた次第ですが、それは可能です。

Pythonにも実装されているため、コマンドを使用せずにPythonコードからAlembicを呼び出すことができます!コマンドの背後で実行されているコマンドを再作成する必要があるだけです。シーン。

確かに、これらはまだライブラリの比較的初期のリリースであるため、ドキュメントはあまり良い状態ではありませんが、少し掘り下げると、次のことがわかります。

  1. Config を作成します
  2. 設定を使用して ScriptDirectory を作成します
  3. ConfigとScriptDirectoryを使用して EnvironmentContext を作成します
  4. EnvironmentContextを使用して MigrationContext を作成します
  5. ほとんどのコマンドは、ConfigとMigrationContextのメソッドのいくつかの組み合わせを使用します

このプログラムによるAlembicによるFlask-SQLAlchemyデータベースへのアクセスを提供する拡張機能を作成しました。実装はFlaskとFlask-SQLAlchemyに関連付けられていますが、開始するには適切な場所であるはずです。 こちらのFlask-Alembicを参照してください。

新しいデータベースの作成方法に関する最後のポイントについては、Alembicを使用してテーブルを作成するか、metadata.create_all()を使用して_alembic stamp head_(または同等のpython)を使用することができます。 =コード)テーブルの作成には常に移行パスを使用し、生のmetadata.create_all()は無視することをお勧めします。

私はcx_freezeの経験はありませんが、マイグレーションがディストリビューションに含まれていて、コード内のそのディレクトリへのパスが正しい限り、問題ありません。

11
davidism

Alembic docsの 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メソッドを使用して、必要に応じて別のDBで移行を実行できるようにしています。だから私はこれを次のように呼び出すだけです:

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

これらの2つの値(パスとDSN)をどこから取得するかはユーザー次第です。しかし、これはあなたが達成したいことに非常に近いようです。コマンドAPIには stamp() メソッドもあり、特定のDBを特定のバージョンとしてマークできます。上記の例は、これを呼び出すように簡単に調整できます。

9
exhuma

これは、プログラムで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ファイル からのカットです。他のコマンドの使用法と詳細を確認するのに良い場所です。

なぜこのソリューションですか?-自動テストの実行時に、アレンビックスタンプ、アップグレード、およびダウングレードを作成する必要があるために作成されました。

  • os.chdir(migration_directory)が一部のテストを妨害しました。
  • データベースの作成と操作のソースを1つにしたかったのです。 「データベースをalembicで作成および管理する場合、alembicではなくmetadata.create_all()シェルをテストにも使用できます」。
  • 上記のコードが4行よりも長い場合でも、alenbicは、この方法で操作すると、制御可能な優れた獣としての地位を示しました。
8
MajesticRa

Flaskを使用していないので、既に推奨されているFlask-Alembicライブラリを使用できませんでした。代わりに、かなりいじくり回した後、すべてを実行する次の短い関数をコード化しました。移行と呼ばれるサブモジュール(フォルダー)の下にすべてのalembic関連ファイルを保管します。実際には、alembic.inienv.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()
2
soapergem

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がそれらを取得するには、それらをmigrationsディレクトリにコピーする必要がありました。

├── 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

何か設定が間違っている可能性がありますが、現在は機能しています。

2
Matthew

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)
1
moomima