diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-11-05 09:05:42 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-11-05 09:05:42 -0500 |
commit | 85a06a0eeaea89124a632a7edc7dd2353d28af79 (patch) | |
tree | e4420850653c966044aed5622dcebe6516142476 /docs/build/cookbook.rst | |
parent | 9a11c3595aa82e82e3ca2a819a8dda8caf7a0440 (diff) | |
download | alembic-85a06a0eeaea89124a632a7edc7dd2353d28af79.tar.gz |
Add cookbook recipe illustrating autogenerate->invoke directly
Change-Id: I46c0436971f50c0969f70ecf99c191533db65dcf
Diffstat (limited to 'docs/build/cookbook.rst')
-rw-r--r-- | docs/build/cookbook.rst | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/docs/build/cookbook.rst b/docs/build/cookbook.rst index 08306a8..4277ad1 100644 --- a/docs/build/cookbook.rst +++ b/docs/build/cookbook.rst @@ -1292,6 +1292,127 @@ Output:: op.create_index('property_name_users_id', 'user_properties', ['property_name', 'users_id'], unique=True) # ### end Alembic commands ### +Run Alembic Operation Objects Directly (as in from autogenerate) +================================================================ + +The :class:`.Operations` object has a method known as +:meth:`.Operations.invoke` that will generically invoke a particular operation +object. We can therefore use the :func:`.autogenerate.produce_migrations` +function to run an autogenerate comparison, get back a +:class:`.ops.MigrationScript` structure representing the changes, and with a +little bit of insider information we can invoke them directly. + +The traversal through the :class:`.ops.MigrationScript` structure is as +follows:: + + use_batch = engine.name == "sqlite" + + stack = [migrations.upgrade_ops] + while stack: + elem = stack.pop(0) + + if use_batch and isinstance(elem, ModifyTableOps): + with operations.batch_alter_table( + elem.table_name, schema=elem.schema + ) as batch_ops: + for table_elem in elem.ops: + # work around Alembic issue #753 + if hasattr(table_elem, "column"): + table_elem.column = table_elem.column.copy() + batch_ops.invoke(table_elem) + + elif hasattr(elem, "ops"): + stack.extend(elem.ops) + else: + # work around Alembic issue #753 + if hasattr(elem, "column"): + elem.column = elem.column.copy() + operations.invoke(elem) + +Above, we detect elements that have a collection of operations by looking +for the ``.ops`` attribute. A check for :class:`.ModifyTableOps` allows +us to use a batch context if we are supporting that. Finally there's a +workaround for an Alembic issue that exists for SQLAlchemy 1.3.20 and greater +combined with Alembic older than 1.5. + +A full example follows. The overall setup here is copied from the example +at :func:`.autogenerate.compare_metadata`:: + + from sqlalchemy import Column + from sqlalchemy import create_engine + from sqlalchemy import Integer + from sqlalchemy import MetaData + from sqlalchemy import String + from sqlalchemy import Table + + from alembic.autogenerate import produce_migrations + from alembic.migration import MigrationContext + from alembic.operations import Operations + from alembic.operations.ops import ModifyTableOps + + + engine = create_engine("sqlite://", echo=True) + + with engine.connect() as conn: + conn.execute( + """ + create table foo ( + id integer not null primary key, + old_data varchar(50), + x integer + )""" + ) + + conn.execute( + """ + create table bar ( + data varchar(50) + )""" + ) + + metadata = MetaData() + Table( + "foo", + metadata, + Column("id", Integer, primary_key=True), + Column("data", Integer), + Column("x", Integer, nullable=False), + ) + Table("bat", metadata, Column("info", String(100))) + + mc = MigrationContext.configure(engine.connect()) + + migrations = produce_migrations(mc, metadata) + + operations = Operations(mc) + + use_batch = engine.name == "sqlite" + + stack = [migrations.upgrade_ops] + while stack: + elem = stack.pop(0) + + if use_batch and isinstance(elem, ModifyTableOps): + with operations.batch_alter_table( + elem.table_name, schema=elem.schema + ) as batch_ops: + for table_elem in elem.ops: + # work around Alembic issue #753 + if hasattr(table_elem, "column"): + table_elem.column = table_elem.column.copy() + batch_ops.invoke(table_elem) + + elif hasattr(elem, "ops"): + stack.extend(elem.ops) + else: + # work around Alembic issue #753 + if hasattr(elem, "column"): + elem.column = elem.column.copy() + operations.invoke(elem) + + + + Test current database revision is at head(s) ============================================ |