summaryrefslogtreecommitdiff
path: root/docs/build/cookbook.rst
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-11-05 09:05:42 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2020-11-05 09:05:42 -0500
commit85a06a0eeaea89124a632a7edc7dd2353d28af79 (patch)
treee4420850653c966044aed5622dcebe6516142476 /docs/build/cookbook.rst
parent9a11c3595aa82e82e3ca2a819a8dda8caf7a0440 (diff)
downloadalembic-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.rst121
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)
============================================