Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/alembic/operations/ops.py : 39%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import re
3from sqlalchemy.types import NULLTYPE
5from . import schemaobj
6from .base import BatchOperations
7from .base import Operations
8from .. import util
9from ..util import sqla_compat
12class MigrateOperation(object):
13 """base class for migration command and organization objects.
15 This system is part of the operation extensibility API.
17 .. versionadded:: 0.8.0
19 .. seealso::
21 :ref:`operation_objects`
23 :ref:`operation_plugins`
25 :ref:`customizing_revision`
27 """
29 @util.memoized_property
30 def info(self):
31 """A dictionary that may be used to store arbitrary information
32 along with this :class:`.MigrateOperation` object.
34 """
35 return {}
37 _mutations = frozenset()
40class AddConstraintOp(MigrateOperation):
41 """Represent an add constraint operation."""
43 add_constraint_ops = util.Dispatcher()
45 @property
46 def constraint_type(self):
47 raise NotImplementedError()
49 @classmethod
50 def register_add_constraint(cls, type_):
51 def go(klass):
52 cls.add_constraint_ops.dispatch_for(type_)(klass.from_constraint)
53 return klass
55 return go
57 @classmethod
58 def from_constraint(cls, constraint):
59 return cls.add_constraint_ops.dispatch(constraint.__visit_name__)(
60 constraint
61 )
63 def reverse(self):
64 return DropConstraintOp.from_constraint(self.to_constraint())
66 def to_diff_tuple(self):
67 return ("add_constraint", self.to_constraint())
70@Operations.register_operation("drop_constraint")
71@BatchOperations.register_operation("drop_constraint", "batch_drop_constraint")
72class DropConstraintOp(MigrateOperation):
73 """Represent a drop constraint operation."""
75 def __init__(
76 self,
77 constraint_name,
78 table_name,
79 type_=None,
80 schema=None,
81 _orig_constraint=None,
82 ):
83 self.constraint_name = constraint_name
84 self.table_name = table_name
85 self.constraint_type = type_
86 self.schema = schema
87 self._orig_constraint = _orig_constraint
89 def reverse(self):
90 if self._orig_constraint is None:
91 raise ValueError(
92 "operation is not reversible; "
93 "original constraint is not present"
94 )
95 return AddConstraintOp.from_constraint(self._orig_constraint)
97 def to_diff_tuple(self):
98 if self.constraint_type == "foreignkey":
99 return ("remove_fk", self.to_constraint())
100 else:
101 return ("remove_constraint", self.to_constraint())
103 @classmethod
104 def from_constraint(cls, constraint):
105 types = {
106 "unique_constraint": "unique",
107 "foreign_key_constraint": "foreignkey",
108 "primary_key_constraint": "primary",
109 "check_constraint": "check",
110 "column_check_constraint": "check",
111 "table_or_column_check_constraint": "check",
112 }
114 constraint_table = sqla_compat._table_for_constraint(constraint)
115 return cls(
116 constraint.name,
117 constraint_table.name,
118 schema=constraint_table.schema,
119 type_=types[constraint.__visit_name__],
120 _orig_constraint=constraint,
121 )
123 def to_constraint(self):
124 if self._orig_constraint is not None:
125 return self._orig_constraint
126 else:
127 raise ValueError(
128 "constraint cannot be produced; "
129 "original constraint is not present"
130 )
132 @classmethod
133 @util._with_legacy_names([("type", "type_"), ("name", "constraint_name")])
134 def drop_constraint(
135 cls, operations, constraint_name, table_name, type_=None, schema=None
136 ):
137 r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
139 :param constraint_name: name of the constraint.
140 :param table_name: table name.
141 :param type\_: optional, required on MySQL. can be
142 'foreignkey', 'primary', 'unique', or 'check'.
143 :param schema: Optional schema name to operate within. To control
144 quoting of the schema outside of the default behavior, use
145 the SQLAlchemy construct
146 :class:`~sqlalchemy.sql.elements.quoted_name`.
148 .. versionadded:: 0.7.0 'schema' can now accept a
149 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
151 .. versionchanged:: 0.8.0 The following positional argument names
152 have been changed:
154 * name -> constraint_name
156 """
158 op = cls(constraint_name, table_name, type_=type_, schema=schema)
159 return operations.invoke(op)
161 @classmethod
162 def batch_drop_constraint(cls, operations, constraint_name, type_=None):
163 """Issue a "drop constraint" instruction using the
164 current batch migration context.
166 The batch form of this call omits the ``table_name`` and ``schema``
167 arguments from the call.
169 .. seealso::
171 :meth:`.Operations.drop_constraint`
173 .. versionchanged:: 0.8.0 The following positional argument names
174 have been changed:
176 * name -> constraint_name
178 """
179 op = cls(
180 constraint_name,
181 operations.impl.table_name,
182 type_=type_,
183 schema=operations.impl.schema,
184 )
185 return operations.invoke(op)
188@Operations.register_operation("create_primary_key")
189@BatchOperations.register_operation(
190 "create_primary_key", "batch_create_primary_key"
191)
192@AddConstraintOp.register_add_constraint("primary_key_constraint")
193class CreatePrimaryKeyOp(AddConstraintOp):
194 """Represent a create primary key operation."""
196 constraint_type = "primarykey"
198 def __init__(
199 self,
200 constraint_name,
201 table_name,
202 columns,
203 schema=None,
204 _orig_constraint=None,
205 **kw
206 ):
207 self.constraint_name = constraint_name
208 self.table_name = table_name
209 self.columns = columns
210 self.schema = schema
211 self._orig_constraint = _orig_constraint
212 self.kw = kw
214 @classmethod
215 def from_constraint(cls, constraint):
216 constraint_table = sqla_compat._table_for_constraint(constraint)
218 return cls(
219 constraint.name,
220 constraint_table.name,
221 constraint.columns,
222 schema=constraint_table.schema,
223 _orig_constraint=constraint,
224 )
226 def to_constraint(self, migration_context=None):
227 if self._orig_constraint is not None:
228 return self._orig_constraint
230 schema_obj = schemaobj.SchemaObjects(migration_context)
231 return schema_obj.primary_key_constraint(
232 self.constraint_name,
233 self.table_name,
234 self.columns,
235 schema=self.schema,
236 )
238 @classmethod
239 @util._with_legacy_names(
240 [("name", "constraint_name"), ("cols", "columns")]
241 )
242 def create_primary_key(
243 cls, operations, constraint_name, table_name, columns, schema=None
244 ):
245 """Issue a "create primary key" instruction using the current
246 migration context.
248 e.g.::
250 from alembic import op
251 op.create_primary_key(
252 "pk_my_table", "my_table",
253 ["id", "version"]
254 )
256 This internally generates a :class:`~sqlalchemy.schema.Table` object
257 containing the necessary columns, then generates a new
258 :class:`~sqlalchemy.schema.PrimaryKeyConstraint`
259 object which it then associates with the
260 :class:`~sqlalchemy.schema.Table`.
261 Any event listeners associated with this action will be fired
262 off normally. The :class:`~sqlalchemy.schema.AddConstraint`
263 construct is ultimately used to generate the ALTER statement.
265 :param name: Name of the primary key constraint. The name is necessary
266 so that an ALTER statement can be emitted. For setups that
267 use an automated naming scheme such as that described at
268 :ref:`sqla:constraint_naming_conventions`
269 ``name`` here can be ``None``, as the event listener will
270 apply the name to the constraint object when it is associated
271 with the table.
272 :param table_name: String name of the target table.
273 :param columns: a list of string column names to be applied to the
274 primary key constraint.
275 :param schema: Optional schema name to operate within. To control
276 quoting of the schema outside of the default behavior, use
277 the SQLAlchemy construct
278 :class:`~sqlalchemy.sql.elements.quoted_name`.
280 .. versionadded:: 0.7.0 'schema' can now accept a
281 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
283 .. versionchanged:: 0.8.0 The following positional argument names
284 have been changed:
286 * name -> constraint_name
287 * cols -> columns
289 """
290 op = cls(constraint_name, table_name, columns, schema)
291 return operations.invoke(op)
293 @classmethod
294 def batch_create_primary_key(cls, operations, constraint_name, columns):
295 """Issue a "create primary key" instruction using the
296 current batch migration context.
298 The batch form of this call omits the ``table_name`` and ``schema``
299 arguments from the call.
301 .. seealso::
303 :meth:`.Operations.create_primary_key`
305 """
306 op = cls(
307 constraint_name,
308 operations.impl.table_name,
309 columns,
310 schema=operations.impl.schema,
311 )
312 return operations.invoke(op)
315@Operations.register_operation("create_unique_constraint")
316@BatchOperations.register_operation(
317 "create_unique_constraint", "batch_create_unique_constraint"
318)
319@AddConstraintOp.register_add_constraint("unique_constraint")
320class CreateUniqueConstraintOp(AddConstraintOp):
321 """Represent a create unique constraint operation."""
323 constraint_type = "unique"
325 def __init__(
326 self,
327 constraint_name,
328 table_name,
329 columns,
330 schema=None,
331 _orig_constraint=None,
332 **kw
333 ):
334 self.constraint_name = constraint_name
335 self.table_name = table_name
336 self.columns = columns
337 self.schema = schema
338 self._orig_constraint = _orig_constraint
339 self.kw = kw
341 @classmethod
342 def from_constraint(cls, constraint):
343 constraint_table = sqla_compat._table_for_constraint(constraint)
345 kw = {}
346 if constraint.deferrable:
347 kw["deferrable"] = constraint.deferrable
348 if constraint.initially:
349 kw["initially"] = constraint.initially
351 return cls(
352 constraint.name,
353 constraint_table.name,
354 [c.name for c in constraint.columns],
355 schema=constraint_table.schema,
356 _orig_constraint=constraint,
357 **kw
358 )
360 def to_constraint(self, migration_context=None):
361 if self._orig_constraint is not None:
362 return self._orig_constraint
364 schema_obj = schemaobj.SchemaObjects(migration_context)
365 return schema_obj.unique_constraint(
366 self.constraint_name,
367 self.table_name,
368 self.columns,
369 schema=self.schema,
370 **self.kw
371 )
373 @classmethod
374 @util._with_legacy_names(
375 [
376 ("name", "constraint_name"),
377 ("source", "table_name"),
378 ("local_cols", "columns"),
379 ]
380 )
381 def create_unique_constraint(
382 cls,
383 operations,
384 constraint_name,
385 table_name,
386 columns,
387 schema=None,
388 **kw
389 ):
390 """Issue a "create unique constraint" instruction using the
391 current migration context.
393 e.g.::
395 from alembic import op
396 op.create_unique_constraint("uq_user_name", "user", ["name"])
398 This internally generates a :class:`~sqlalchemy.schema.Table` object
399 containing the necessary columns, then generates a new
400 :class:`~sqlalchemy.schema.UniqueConstraint`
401 object which it then associates with the
402 :class:`~sqlalchemy.schema.Table`.
403 Any event listeners associated with this action will be fired
404 off normally. The :class:`~sqlalchemy.schema.AddConstraint`
405 construct is ultimately used to generate the ALTER statement.
407 :param name: Name of the unique constraint. The name is necessary
408 so that an ALTER statement can be emitted. For setups that
409 use an automated naming scheme such as that described at
410 :ref:`sqla:constraint_naming_conventions`,
411 ``name`` here can be ``None``, as the event listener will
412 apply the name to the constraint object when it is associated
413 with the table.
414 :param table_name: String name of the source table.
415 :param columns: a list of string column names in the
416 source table.
417 :param deferrable: optional bool. If set, emit DEFERRABLE or
418 NOT DEFERRABLE when issuing DDL for this constraint.
419 :param initially: optional string. If set, emit INITIALLY <value>
420 when issuing DDL for this constraint.
421 :param schema: Optional schema name to operate within. To control
422 quoting of the schema outside of the default behavior, use
423 the SQLAlchemy construct
424 :class:`~sqlalchemy.sql.elements.quoted_name`.
426 .. versionadded:: 0.7.0 'schema' can now accept a
427 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
429 .. versionchanged:: 0.8.0 The following positional argument names
430 have been changed:
432 * name -> constraint_name
433 * source -> table_name
434 * local_cols -> columns
436 """
438 op = cls(constraint_name, table_name, columns, schema=schema, **kw)
439 return operations.invoke(op)
441 @classmethod
442 @util._with_legacy_names([("name", "constraint_name")])
443 def batch_create_unique_constraint(
444 cls, operations, constraint_name, columns, **kw
445 ):
446 """Issue a "create unique constraint" instruction using the
447 current batch migration context.
449 The batch form of this call omits the ``source`` and ``schema``
450 arguments from the call.
452 .. seealso::
454 :meth:`.Operations.create_unique_constraint`
456 .. versionchanged:: 0.8.0 The following positional argument names
457 have been changed:
459 * name -> constraint_name
461 """
462 kw["schema"] = operations.impl.schema
463 op = cls(constraint_name, operations.impl.table_name, columns, **kw)
464 return operations.invoke(op)
467@Operations.register_operation("create_foreign_key")
468@BatchOperations.register_operation(
469 "create_foreign_key", "batch_create_foreign_key"
470)
471@AddConstraintOp.register_add_constraint("foreign_key_constraint")
472class CreateForeignKeyOp(AddConstraintOp):
473 """Represent a create foreign key constraint operation."""
475 constraint_type = "foreignkey"
477 def __init__(
478 self,
479 constraint_name,
480 source_table,
481 referent_table,
482 local_cols,
483 remote_cols,
484 _orig_constraint=None,
485 **kw
486 ):
487 self.constraint_name = constraint_name
488 self.source_table = source_table
489 self.referent_table = referent_table
490 self.local_cols = local_cols
491 self.remote_cols = remote_cols
492 self._orig_constraint = _orig_constraint
493 self.kw = kw
495 def to_diff_tuple(self):
496 return ("add_fk", self.to_constraint())
498 @classmethod
499 def from_constraint(cls, constraint):
500 kw = {}
501 if constraint.onupdate:
502 kw["onupdate"] = constraint.onupdate
503 if constraint.ondelete:
504 kw["ondelete"] = constraint.ondelete
505 if constraint.initially:
506 kw["initially"] = constraint.initially
507 if constraint.deferrable:
508 kw["deferrable"] = constraint.deferrable
509 if constraint.use_alter:
510 kw["use_alter"] = constraint.use_alter
512 (
513 source_schema,
514 source_table,
515 source_columns,
516 target_schema,
517 target_table,
518 target_columns,
519 onupdate,
520 ondelete,
521 deferrable,
522 initially,
523 ) = sqla_compat._fk_spec(constraint)
525 kw["source_schema"] = source_schema
526 kw["referent_schema"] = target_schema
528 return cls(
529 constraint.name,
530 source_table,
531 target_table,
532 source_columns,
533 target_columns,
534 _orig_constraint=constraint,
535 **kw
536 )
538 def to_constraint(self, migration_context=None):
539 if self._orig_constraint is not None:
540 return self._orig_constraint
541 schema_obj = schemaobj.SchemaObjects(migration_context)
542 return schema_obj.foreign_key_constraint(
543 self.constraint_name,
544 self.source_table,
545 self.referent_table,
546 self.local_cols,
547 self.remote_cols,
548 **self.kw
549 )
551 @classmethod
552 @util._with_legacy_names(
553 [
554 ("name", "constraint_name"),
555 ("source", "source_table"),
556 ("referent", "referent_table"),
557 ]
558 )
559 def create_foreign_key(
560 cls,
561 operations,
562 constraint_name,
563 source_table,
564 referent_table,
565 local_cols,
566 remote_cols,
567 onupdate=None,
568 ondelete=None,
569 deferrable=None,
570 initially=None,
571 match=None,
572 source_schema=None,
573 referent_schema=None,
574 **dialect_kw
575 ):
576 """Issue a "create foreign key" instruction using the
577 current migration context.
579 e.g.::
581 from alembic import op
582 op.create_foreign_key(
583 "fk_user_address", "address",
584 "user", ["user_id"], ["id"])
586 This internally generates a :class:`~sqlalchemy.schema.Table` object
587 containing the necessary columns, then generates a new
588 :class:`~sqlalchemy.schema.ForeignKeyConstraint`
589 object which it then associates with the
590 :class:`~sqlalchemy.schema.Table`.
591 Any event listeners associated with this action will be fired
592 off normally. The :class:`~sqlalchemy.schema.AddConstraint`
593 construct is ultimately used to generate the ALTER statement.
595 :param name: Name of the foreign key constraint. The name is necessary
596 so that an ALTER statement can be emitted. For setups that
597 use an automated naming scheme such as that described at
598 :ref:`sqla:constraint_naming_conventions`,
599 ``name`` here can be ``None``, as the event listener will
600 apply the name to the constraint object when it is associated
601 with the table.
602 :param source_table: String name of the source table.
603 :param referent_table: String name of the destination table.
604 :param local_cols: a list of string column names in the
605 source table.
606 :param remote_cols: a list of string column names in the
607 remote table.
608 :param onupdate: Optional string. If set, emit ON UPDATE <value> when
609 issuing DDL for this constraint. Typical values include CASCADE,
610 DELETE and RESTRICT.
611 :param ondelete: Optional string. If set, emit ON DELETE <value> when
612 issuing DDL for this constraint. Typical values include CASCADE,
613 DELETE and RESTRICT.
614 :param deferrable: optional bool. If set, emit DEFERRABLE or NOT
615 DEFERRABLE when issuing DDL for this constraint.
616 :param source_schema: Optional schema name of the source table.
617 :param referent_schema: Optional schema name of the destination table.
619 .. versionchanged:: 0.8.0 The following positional argument names
620 have been changed:
622 * name -> constraint_name
623 * source -> source_table
624 * referent -> referent_table
626 """
628 op = cls(
629 constraint_name,
630 source_table,
631 referent_table,
632 local_cols,
633 remote_cols,
634 onupdate=onupdate,
635 ondelete=ondelete,
636 deferrable=deferrable,
637 source_schema=source_schema,
638 referent_schema=referent_schema,
639 initially=initially,
640 match=match,
641 **dialect_kw
642 )
643 return operations.invoke(op)
645 @classmethod
646 @util._with_legacy_names(
647 [("name", "constraint_name"), ("referent", "referent_table")]
648 )
649 def batch_create_foreign_key(
650 cls,
651 operations,
652 constraint_name,
653 referent_table,
654 local_cols,
655 remote_cols,
656 referent_schema=None,
657 onupdate=None,
658 ondelete=None,
659 deferrable=None,
660 initially=None,
661 match=None,
662 **dialect_kw
663 ):
664 """Issue a "create foreign key" instruction using the
665 current batch migration context.
667 The batch form of this call omits the ``source`` and ``source_schema``
668 arguments from the call.
670 e.g.::
672 with batch_alter_table("address") as batch_op:
673 batch_op.create_foreign_key(
674 "fk_user_address",
675 "user", ["user_id"], ["id"])
677 .. seealso::
679 :meth:`.Operations.create_foreign_key`
681 .. versionchanged:: 0.8.0 The following positional argument names
682 have been changed:
684 * name -> constraint_name
685 * referent -> referent_table
687 """
688 op = cls(
689 constraint_name,
690 operations.impl.table_name,
691 referent_table,
692 local_cols,
693 remote_cols,
694 onupdate=onupdate,
695 ondelete=ondelete,
696 deferrable=deferrable,
697 source_schema=operations.impl.schema,
698 referent_schema=referent_schema,
699 initially=initially,
700 match=match,
701 **dialect_kw
702 )
703 return operations.invoke(op)
706@Operations.register_operation("create_check_constraint")
707@BatchOperations.register_operation(
708 "create_check_constraint", "batch_create_check_constraint"
709)
710@AddConstraintOp.register_add_constraint("check_constraint")
711@AddConstraintOp.register_add_constraint("table_or_column_check_constraint")
712@AddConstraintOp.register_add_constraint("column_check_constraint")
713class CreateCheckConstraintOp(AddConstraintOp):
714 """Represent a create check constraint operation."""
716 constraint_type = "check"
718 def __init__(
719 self,
720 constraint_name,
721 table_name,
722 condition,
723 schema=None,
724 _orig_constraint=None,
725 **kw
726 ):
727 self.constraint_name = constraint_name
728 self.table_name = table_name
729 self.condition = condition
730 self.schema = schema
731 self._orig_constraint = _orig_constraint
732 self.kw = kw
734 @classmethod
735 def from_constraint(cls, constraint):
736 constraint_table = sqla_compat._table_for_constraint(constraint)
738 return cls(
739 constraint.name,
740 constraint_table.name,
741 constraint.sqltext,
742 schema=constraint_table.schema,
743 _orig_constraint=constraint,
744 )
746 def to_constraint(self, migration_context=None):
747 if self._orig_constraint is not None:
748 return self._orig_constraint
749 schema_obj = schemaobj.SchemaObjects(migration_context)
750 return schema_obj.check_constraint(
751 self.constraint_name,
752 self.table_name,
753 self.condition,
754 schema=self.schema,
755 **self.kw
756 )
758 @classmethod
759 @util._with_legacy_names(
760 [("name", "constraint_name"), ("source", "table_name")]
761 )
762 def create_check_constraint(
763 cls,
764 operations,
765 constraint_name,
766 table_name,
767 condition,
768 schema=None,
769 **kw
770 ):
771 """Issue a "create check constraint" instruction using the
772 current migration context.
774 e.g.::
776 from alembic import op
777 from sqlalchemy.sql import column, func
779 op.create_check_constraint(
780 "ck_user_name_len",
781 "user",
782 func.len(column('name')) > 5
783 )
785 CHECK constraints are usually against a SQL expression, so ad-hoc
786 table metadata is usually needed. The function will convert the given
787 arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound
788 to an anonymous table in order to emit the CREATE statement.
790 :param name: Name of the check constraint. The name is necessary
791 so that an ALTER statement can be emitted. For setups that
792 use an automated naming scheme such as that described at
793 :ref:`sqla:constraint_naming_conventions`,
794 ``name`` here can be ``None``, as the event listener will
795 apply the name to the constraint object when it is associated
796 with the table.
797 :param table_name: String name of the source table.
798 :param condition: SQL expression that's the condition of the
799 constraint. Can be a string or SQLAlchemy expression language
800 structure.
801 :param deferrable: optional bool. If set, emit DEFERRABLE or
802 NOT DEFERRABLE when issuing DDL for this constraint.
803 :param initially: optional string. If set, emit INITIALLY <value>
804 when issuing DDL for this constraint.
805 :param schema: Optional schema name to operate within. To control
806 quoting of the schema outside of the default behavior, use
807 the SQLAlchemy construct
808 :class:`~sqlalchemy.sql.elements.quoted_name`.
810 .. versionadded:: 0.7.0 'schema' can now accept a
811 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
813 .. versionchanged:: 0.8.0 The following positional argument names
814 have been changed:
816 * name -> constraint_name
817 * source -> table_name
819 """
820 op = cls(constraint_name, table_name, condition, schema=schema, **kw)
821 return operations.invoke(op)
823 @classmethod
824 @util._with_legacy_names([("name", "constraint_name")])
825 def batch_create_check_constraint(
826 cls, operations, constraint_name, condition, **kw
827 ):
828 """Issue a "create check constraint" instruction using the
829 current batch migration context.
831 The batch form of this call omits the ``source`` and ``schema``
832 arguments from the call.
834 .. seealso::
836 :meth:`.Operations.create_check_constraint`
838 .. versionchanged:: 0.8.0 The following positional argument names
839 have been changed:
841 * name -> constraint_name
843 """
844 op = cls(
845 constraint_name,
846 operations.impl.table_name,
847 condition,
848 schema=operations.impl.schema,
849 **kw
850 )
851 return operations.invoke(op)
854@Operations.register_operation("create_index")
855@BatchOperations.register_operation("create_index", "batch_create_index")
856class CreateIndexOp(MigrateOperation):
857 """Represent a create index operation."""
859 def __init__(
860 self,
861 index_name,
862 table_name,
863 columns,
864 schema=None,
865 unique=False,
866 _orig_index=None,
867 **kw
868 ):
869 self.index_name = index_name
870 self.table_name = table_name
871 self.columns = columns
872 self.schema = schema
873 self.unique = unique
874 self.kw = kw
875 self._orig_index = _orig_index
877 def reverse(self):
878 return DropIndexOp.from_index(self.to_index())
880 def to_diff_tuple(self):
881 return ("add_index", self.to_index())
883 @classmethod
884 def from_index(cls, index):
885 return cls(
886 index.name,
887 index.table.name,
888 sqla_compat._get_index_expressions(index),
889 schema=index.table.schema,
890 unique=index.unique,
891 _orig_index=index,
892 **index.kwargs
893 )
895 def to_index(self, migration_context=None):
896 if self._orig_index:
897 return self._orig_index
898 schema_obj = schemaobj.SchemaObjects(migration_context)
899 return schema_obj.index(
900 self.index_name,
901 self.table_name,
902 self.columns,
903 schema=self.schema,
904 unique=self.unique,
905 **self.kw
906 )
908 @classmethod
909 @util._with_legacy_names([("name", "index_name")])
910 def create_index(
911 cls,
912 operations,
913 index_name,
914 table_name,
915 columns,
916 schema=None,
917 unique=False,
918 **kw
919 ):
920 r"""Issue a "create index" instruction using the current
921 migration context.
923 e.g.::
925 from alembic import op
926 op.create_index('ik_test', 't1', ['foo', 'bar'])
928 Functional indexes can be produced by using the
929 :func:`sqlalchemy.sql.expression.text` construct::
931 from alembic import op
932 from sqlalchemy import text
933 op.create_index('ik_test', 't1', [text('lower(foo)')])
935 .. versionadded:: 0.6.7 support for making use of the
936 :func:`~sqlalchemy.sql.expression.text` construct in
937 conjunction with
938 :meth:`.Operations.create_index` in
939 order to produce functional expressions within CREATE INDEX.
941 :param index_name: name of the index.
942 :param table_name: name of the owning table.
943 :param columns: a list consisting of string column names and/or
944 :func:`~sqlalchemy.sql.expression.text` constructs.
945 :param schema: Optional schema name to operate within. To control
946 quoting of the schema outside of the default behavior, use
947 the SQLAlchemy construct
948 :class:`~sqlalchemy.sql.elements.quoted_name`.
950 .. versionadded:: 0.7.0 'schema' can now accept a
951 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
953 :param unique: If True, create a unique index.
955 :param quote:
956 Force quoting of this column's name on or off, corresponding
957 to ``True`` or ``False``. When left at its default
958 of ``None``, the column identifier will be quoted according to
959 whether the name is case sensitive (identifiers with at least one
960 upper case character are treated as case sensitive), or if it's a
961 reserved word. This flag is only needed to force quoting of a
962 reserved word which is not known by the SQLAlchemy dialect.
964 :param \**kw: Additional keyword arguments not mentioned above are
965 dialect specific, and passed in the form
966 ``<dialectname>_<argname>``.
967 See the documentation regarding an individual dialect at
968 :ref:`dialect_toplevel` for detail on documented arguments.
970 .. versionchanged:: 0.8.0 The following positional argument names
971 have been changed:
973 * name -> index_name
975 """
976 op = cls(
977 index_name, table_name, columns, schema=schema, unique=unique, **kw
978 )
979 return operations.invoke(op)
981 @classmethod
982 def batch_create_index(cls, operations, index_name, columns, **kw):
983 """Issue a "create index" instruction using the
984 current batch migration context.
986 .. seealso::
988 :meth:`.Operations.create_index`
990 """
992 op = cls(
993 index_name,
994 operations.impl.table_name,
995 columns,
996 schema=operations.impl.schema,
997 **kw
998 )
999 return operations.invoke(op)
1002@Operations.register_operation("drop_index")
1003@BatchOperations.register_operation("drop_index", "batch_drop_index")
1004class DropIndexOp(MigrateOperation):
1005 """Represent a drop index operation."""
1007 def __init__(
1008 self, index_name, table_name=None, schema=None, _orig_index=None, **kw
1009 ):
1010 self.index_name = index_name
1011 self.table_name = table_name
1012 self.schema = schema
1013 self._orig_index = _orig_index
1014 self.kw = kw
1016 def to_diff_tuple(self):
1017 return ("remove_index", self.to_index())
1019 def reverse(self):
1020 if self._orig_index is None:
1021 raise ValueError(
1022 "operation is not reversible; " "original index is not present"
1023 )
1024 return CreateIndexOp.from_index(self._orig_index)
1026 @classmethod
1027 def from_index(cls, index):
1028 return cls(
1029 index.name,
1030 index.table.name,
1031 schema=index.table.schema,
1032 _orig_index=index,
1033 **index.kwargs
1034 )
1036 def to_index(self, migration_context=None):
1037 if self._orig_index is not None:
1038 return self._orig_index
1040 schema_obj = schemaobj.SchemaObjects(migration_context)
1042 # need a dummy column name here since SQLAlchemy
1043 # 0.7.6 and further raises on Index with no columns
1044 return schema_obj.index(
1045 self.index_name,
1046 self.table_name,
1047 ["x"],
1048 schema=self.schema,
1049 **self.kw
1050 )
1052 @classmethod
1053 @util._with_legacy_names(
1054 [("name", "index_name"), ("tablename", "table_name")]
1055 )
1056 def drop_index(
1057 cls, operations, index_name, table_name=None, schema=None, **kw
1058 ):
1059 r"""Issue a "drop index" instruction using the current
1060 migration context.
1062 e.g.::
1064 drop_index("accounts")
1066 :param index_name: name of the index.
1067 :param table_name: name of the owning table. Some
1068 backends such as Microsoft SQL Server require this.
1069 :param schema: Optional schema name to operate within. To control
1070 quoting of the schema outside of the default behavior, use
1071 the SQLAlchemy construct
1072 :class:`~sqlalchemy.sql.elements.quoted_name`.
1074 .. versionadded:: 0.7.0 'schema' can now accept a
1075 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1077 :param \**kw: Additional keyword arguments not mentioned above are
1078 dialect specific, and passed in the form
1079 ``<dialectname>_<argname>``.
1080 See the documentation regarding an individual dialect at
1081 :ref:`dialect_toplevel` for detail on documented arguments.
1083 .. versionadded:: 0.9.5 Support for dialect-specific keyword
1084 arguments for DROP INDEX
1086 .. versionchanged:: 0.8.0 The following positional argument names
1087 have been changed:
1089 * name -> index_name
1091 """
1092 op = cls(index_name, table_name=table_name, schema=schema, **kw)
1093 return operations.invoke(op)
1095 @classmethod
1096 @util._with_legacy_names([("name", "index_name")])
1097 def batch_drop_index(cls, operations, index_name, **kw):
1098 """Issue a "drop index" instruction using the
1099 current batch migration context.
1101 .. seealso::
1103 :meth:`.Operations.drop_index`
1105 .. versionchanged:: 0.8.0 The following positional argument names
1106 have been changed:
1108 * name -> index_name
1110 """
1112 op = cls(
1113 index_name,
1114 table_name=operations.impl.table_name,
1115 schema=operations.impl.schema,
1116 **kw
1117 )
1118 return operations.invoke(op)
1121@Operations.register_operation("create_table")
1122class CreateTableOp(MigrateOperation):
1123 """Represent a create table operation."""
1125 def __init__(
1126 self, table_name, columns, schema=None, _orig_table=None, **kw
1127 ):
1128 self.table_name = table_name
1129 self.columns = columns
1130 self.schema = schema
1131 self.kw = kw
1132 self._orig_table = _orig_table
1134 def reverse(self):
1135 return DropTableOp.from_table(self.to_table())
1137 def to_diff_tuple(self):
1138 return ("add_table", self.to_table())
1140 @classmethod
1141 def from_table(cls, table):
1142 return cls(
1143 table.name,
1144 list(table.c) + list(table.constraints),
1145 schema=table.schema,
1146 _orig_table=table,
1147 **table.kwargs
1148 )
1150 def to_table(self, migration_context=None):
1151 if self._orig_table is not None:
1152 return self._orig_table
1153 schema_obj = schemaobj.SchemaObjects(migration_context)
1155 return schema_obj.table(
1156 self.table_name, *self.columns, schema=self.schema, **self.kw
1157 )
1159 @classmethod
1160 @util._with_legacy_names([("name", "table_name")])
1161 def create_table(cls, operations, table_name, *columns, **kw):
1162 r"""Issue a "create table" instruction using the current migration
1163 context.
1165 This directive receives an argument list similar to that of the
1166 traditional :class:`sqlalchemy.schema.Table` construct, but without the
1167 metadata::
1169 from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
1170 from alembic import op
1172 op.create_table(
1173 'account',
1174 Column('id', INTEGER, primary_key=True),
1175 Column('name', VARCHAR(50), nullable=False),
1176 Column('description', NVARCHAR(200)),
1177 Column('timestamp', TIMESTAMP, server_default=func.now())
1178 )
1180 Note that :meth:`.create_table` accepts
1181 :class:`~sqlalchemy.schema.Column`
1182 constructs directly from the SQLAlchemy library. In particular,
1183 default values to be created on the database side are
1184 specified using the ``server_default`` parameter, and not
1185 ``default`` which only specifies Python-side defaults::
1187 from alembic import op
1188 from sqlalchemy import Column, TIMESTAMP, func
1190 # specify "DEFAULT NOW" along with the "timestamp" column
1191 op.create_table('account',
1192 Column('id', INTEGER, primary_key=True),
1193 Column('timestamp', TIMESTAMP, server_default=func.now())
1194 )
1196 The function also returns a newly created
1197 :class:`~sqlalchemy.schema.Table` object, corresponding to the table
1198 specification given, which is suitable for
1199 immediate SQL operations, in particular
1200 :meth:`.Operations.bulk_insert`::
1202 from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
1203 from alembic import op
1205 account_table = op.create_table(
1206 'account',
1207 Column('id', INTEGER, primary_key=True),
1208 Column('name', VARCHAR(50), nullable=False),
1209 Column('description', NVARCHAR(200)),
1210 Column('timestamp', TIMESTAMP, server_default=func.now())
1211 )
1213 op.bulk_insert(
1214 account_table,
1215 [
1216 {"name": "A1", "description": "account 1"},
1217 {"name": "A2", "description": "account 2"},
1218 ]
1219 )
1221 .. versionadded:: 0.7.0
1223 :param table_name: Name of the table
1224 :param \*columns: collection of :class:`~sqlalchemy.schema.Column`
1225 objects within
1226 the table, as well as optional :class:`~sqlalchemy.schema.Constraint`
1227 objects
1228 and :class:`~.sqlalchemy.schema.Index` objects.
1229 :param schema: Optional schema name to operate within. To control
1230 quoting of the schema outside of the default behavior, use
1231 the SQLAlchemy construct
1232 :class:`~sqlalchemy.sql.elements.quoted_name`.
1234 .. versionadded:: 0.7.0 'schema' can now accept a
1235 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1236 :param \**kw: Other keyword arguments are passed to the underlying
1237 :class:`sqlalchemy.schema.Table` object created for the command.
1239 :return: the :class:`~sqlalchemy.schema.Table` object corresponding
1240 to the parameters given.
1242 .. versionadded:: 0.7.0 - the :class:`~sqlalchemy.schema.Table`
1243 object is returned.
1245 .. versionchanged:: 0.8.0 The following positional argument names
1246 have been changed:
1248 * name -> table_name
1250 """
1251 op = cls(table_name, columns, **kw)
1252 return operations.invoke(op)
1255@Operations.register_operation("drop_table")
1256class DropTableOp(MigrateOperation):
1257 """Represent a drop table operation."""
1259 def __init__(
1260 self, table_name, schema=None, table_kw=None, _orig_table=None
1261 ):
1262 self.table_name = table_name
1263 self.schema = schema
1264 self.table_kw = table_kw or {}
1265 self._orig_table = _orig_table
1267 def to_diff_tuple(self):
1268 return ("remove_table", self.to_table())
1270 def reverse(self):
1271 if self._orig_table is None:
1272 raise ValueError(
1273 "operation is not reversible; " "original table is not present"
1274 )
1275 return CreateTableOp.from_table(self._orig_table)
1277 @classmethod
1278 def from_table(cls, table):
1279 return cls(table.name, schema=table.schema, _orig_table=table)
1281 def to_table(self, migration_context=None):
1282 if self._orig_table is not None:
1283 return self._orig_table
1284 schema_obj = schemaobj.SchemaObjects(migration_context)
1285 return schema_obj.table(
1286 self.table_name, schema=self.schema, **self.table_kw
1287 )
1289 @classmethod
1290 @util._with_legacy_names([("name", "table_name")])
1291 def drop_table(cls, operations, table_name, schema=None, **kw):
1292 r"""Issue a "drop table" instruction using the current
1293 migration context.
1296 e.g.::
1298 drop_table("accounts")
1300 :param table_name: Name of the table
1301 :param schema: Optional schema name to operate within. To control
1302 quoting of the schema outside of the default behavior, use
1303 the SQLAlchemy construct
1304 :class:`~sqlalchemy.sql.elements.quoted_name`.
1306 .. versionadded:: 0.7.0 'schema' can now accept a
1307 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1309 :param \**kw: Other keyword arguments are passed to the underlying
1310 :class:`sqlalchemy.schema.Table` object created for the command.
1312 .. versionchanged:: 0.8.0 The following positional argument names
1313 have been changed:
1315 * name -> table_name
1317 """
1318 op = cls(table_name, schema=schema, table_kw=kw)
1319 operations.invoke(op)
1322class AlterTableOp(MigrateOperation):
1323 """Represent an alter table operation."""
1325 def __init__(self, table_name, schema=None):
1326 self.table_name = table_name
1327 self.schema = schema
1330@Operations.register_operation("rename_table")
1331class RenameTableOp(AlterTableOp):
1332 """Represent a rename table operation."""
1334 def __init__(self, old_table_name, new_table_name, schema=None):
1335 super(RenameTableOp, self).__init__(old_table_name, schema=schema)
1336 self.new_table_name = new_table_name
1338 @classmethod
1339 def rename_table(
1340 cls, operations, old_table_name, new_table_name, schema=None
1341 ):
1342 """Emit an ALTER TABLE to rename a table.
1344 :param old_table_name: old name.
1345 :param new_table_name: new name.
1346 :param schema: Optional schema name to operate within. To control
1347 quoting of the schema outside of the default behavior, use
1348 the SQLAlchemy construct
1349 :class:`~sqlalchemy.sql.elements.quoted_name`.
1351 .. versionadded:: 0.7.0 'schema' can now accept a
1352 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1354 """
1355 op = cls(old_table_name, new_table_name, schema=schema)
1356 return operations.invoke(op)
1359@Operations.register_operation("create_table_comment")
1360class CreateTableCommentOp(AlterTableOp):
1361 """Represent a COMMENT ON `table` operation.
1362 """
1364 def __init__(
1365 self, table_name, comment, schema=None, existing_comment=None
1366 ):
1367 self.table_name = table_name
1368 self.comment = comment
1369 self.existing_comment = existing_comment
1370 self.schema = schema
1372 @classmethod
1373 def create_table_comment(
1374 cls,
1375 operations,
1376 table_name,
1377 comment,
1378 existing_comment=None,
1379 schema=None,
1380 ):
1381 """Emit a COMMENT ON operation to set the comment for a table.
1383 .. versionadded:: 1.0.6
1385 :param table_name: string name of the target table.
1386 :param comment: string value of the comment being registered against
1387 the specified table.
1388 :param existing_comment: String value of a comment
1389 already registered on the specified table, used within autogenerate
1390 so that the operation is reversible, but not required for direct
1391 use.
1393 .. seealso::
1395 :meth:`.Operations.drop_table_comment`
1397 :paramref:`.Operations.alter_column.comment`
1399 """
1401 op = cls(
1402 table_name,
1403 comment,
1404 existing_comment=existing_comment,
1405 schema=schema,
1406 )
1407 return operations.invoke(op)
1409 def reverse(self):
1410 """Reverses the COMMENT ON operation against a table.
1411 """
1412 if self.existing_comment is None:
1413 return DropTableCommentOp(
1414 self.table_name,
1415 existing_comment=self.comment,
1416 schema=self.schema,
1417 )
1418 else:
1419 return CreateTableCommentOp(
1420 self.table_name,
1421 self.existing_comment,
1422 existing_comment=self.comment,
1423 schema=self.schema,
1424 )
1426 def to_table(self, migration_context=None):
1427 schema_obj = schemaobj.SchemaObjects(migration_context)
1429 return schema_obj.table(
1430 self.table_name, schema=self.schema, comment=self.comment
1431 )
1433 def to_diff_tuple(self):
1434 return ("add_table_comment", self.to_table(), self.existing_comment)
1437@Operations.register_operation("drop_table_comment")
1438class DropTableCommentOp(AlterTableOp):
1439 """Represent an operation to remove the comment from a table.
1440 """
1442 def __init__(self, table_name, schema=None, existing_comment=None):
1443 self.table_name = table_name
1444 self.existing_comment = existing_comment
1445 self.schema = schema
1447 @classmethod
1448 def drop_table_comment(
1449 cls, operations, table_name, existing_comment=None, schema=None
1450 ):
1451 """Issue a "drop table comment" operation to
1452 remove an existing comment set on a table.
1454 .. versionadded:: 1.0.6
1456 :param table_name: string name of the target table.
1457 :param existing_comment: An optional string value of a comment already
1458 registered on the specified table.
1460 .. seealso::
1462 :meth:`.Operations.create_table_comment`
1464 :paramref:`.Operations.alter_column.comment`
1466 """
1468 op = cls(table_name, existing_comment=existing_comment, schema=schema)
1469 return operations.invoke(op)
1471 def reverse(self):
1472 """Reverses the COMMENT ON operation against a table.
1473 """
1474 return CreateTableCommentOp(
1475 self.table_name, self.existing_comment, schema=self.schema
1476 )
1478 def to_table(self, migration_context=None):
1479 schema_obj = schemaobj.SchemaObjects(migration_context)
1481 return schema_obj.table(self.table_name, schema=self.schema)
1483 def to_diff_tuple(self):
1484 return ("remove_table_comment", self.to_table())
1487@Operations.register_operation("alter_column")
1488@BatchOperations.register_operation("alter_column", "batch_alter_column")
1489class AlterColumnOp(AlterTableOp):
1490 """Represent an alter column operation."""
1492 def __init__(
1493 self,
1494 table_name,
1495 column_name,
1496 schema=None,
1497 existing_type=None,
1498 existing_server_default=False,
1499 existing_nullable=None,
1500 existing_comment=None,
1501 modify_nullable=None,
1502 modify_comment=False,
1503 modify_server_default=False,
1504 modify_name=None,
1505 modify_type=None,
1506 **kw
1507 ):
1508 super(AlterColumnOp, self).__init__(table_name, schema=schema)
1509 self.column_name = column_name
1510 self.existing_type = existing_type
1511 self.existing_server_default = existing_server_default
1512 self.existing_nullable = existing_nullable
1513 self.existing_comment = existing_comment
1514 self.modify_nullable = modify_nullable
1515 self.modify_comment = modify_comment
1516 self.modify_server_default = modify_server_default
1517 self.modify_name = modify_name
1518 self.modify_type = modify_type
1519 self.kw = kw
1521 def to_diff_tuple(self):
1522 col_diff = []
1523 schema, tname, cname = self.schema, self.table_name, self.column_name
1525 if self.modify_type is not None:
1526 col_diff.append(
1527 (
1528 "modify_type",
1529 schema,
1530 tname,
1531 cname,
1532 {
1533 "existing_nullable": self.existing_nullable,
1534 "existing_server_default": (
1535 self.existing_server_default
1536 ),
1537 "existing_comment": self.existing_comment,
1538 },
1539 self.existing_type,
1540 self.modify_type,
1541 )
1542 )
1544 if self.modify_nullable is not None:
1545 col_diff.append(
1546 (
1547 "modify_nullable",
1548 schema,
1549 tname,
1550 cname,
1551 {
1552 "existing_type": self.existing_type,
1553 "existing_server_default": (
1554 self.existing_server_default
1555 ),
1556 "existing_comment": self.existing_comment,
1557 },
1558 self.existing_nullable,
1559 self.modify_nullable,
1560 )
1561 )
1563 if self.modify_server_default is not False:
1564 col_diff.append(
1565 (
1566 "modify_default",
1567 schema,
1568 tname,
1569 cname,
1570 {
1571 "existing_nullable": self.existing_nullable,
1572 "existing_type": self.existing_type,
1573 "existing_comment": self.existing_comment,
1574 },
1575 self.existing_server_default,
1576 self.modify_server_default,
1577 )
1578 )
1580 if self.modify_comment is not False:
1581 col_diff.append(
1582 (
1583 "modify_comment",
1584 schema,
1585 tname,
1586 cname,
1587 {
1588 "existing_nullable": self.existing_nullable,
1589 "existing_type": self.existing_type,
1590 "existing_server_default": (
1591 self.existing_server_default
1592 ),
1593 },
1594 self.existing_comment,
1595 self.modify_comment,
1596 )
1597 )
1599 return col_diff
1601 def has_changes(self):
1602 hc1 = (
1603 self.modify_nullable is not None
1604 or self.modify_server_default is not False
1605 or self.modify_type is not None
1606 or self.modify_comment is not False
1607 )
1608 if hc1:
1609 return True
1610 for kw in self.kw:
1611 if kw.startswith("modify_"):
1612 return True
1613 else:
1614 return False
1616 def reverse(self):
1618 kw = self.kw.copy()
1619 kw["existing_type"] = self.existing_type
1620 kw["existing_nullable"] = self.existing_nullable
1621 kw["existing_server_default"] = self.existing_server_default
1622 kw["existing_comment"] = self.existing_comment
1623 if self.modify_type is not None:
1624 kw["modify_type"] = self.modify_type
1625 if self.modify_nullable is not None:
1626 kw["modify_nullable"] = self.modify_nullable
1627 if self.modify_server_default is not False:
1628 kw["modify_server_default"] = self.modify_server_default
1629 if self.modify_comment is not False:
1630 kw["modify_comment"] = self.modify_comment
1632 # TODO: make this a little simpler
1633 all_keys = set(
1634 m.group(1)
1635 for m in [re.match(r"^(?:existing_|modify_)(.+)$", k) for k in kw]
1636 if m
1637 )
1639 for k in all_keys:
1640 if "modify_%s" % k in kw:
1641 swap = kw["existing_%s" % k]
1642 kw["existing_%s" % k] = kw["modify_%s" % k]
1643 kw["modify_%s" % k] = swap
1645 return self.__class__(
1646 self.table_name, self.column_name, schema=self.schema, **kw
1647 )
1649 @classmethod
1650 @util._with_legacy_names([("name", "new_column_name")])
1651 def alter_column(
1652 cls,
1653 operations,
1654 table_name,
1655 column_name,
1656 nullable=None,
1657 comment=False,
1658 server_default=False,
1659 new_column_name=None,
1660 type_=None,
1661 existing_type=None,
1662 existing_server_default=False,
1663 existing_nullable=None,
1664 existing_comment=None,
1665 schema=None,
1666 **kw
1667 ):
1668 r"""Issue an "alter column" instruction using the
1669 current migration context.
1671 Generally, only that aspect of the column which
1672 is being changed, i.e. name, type, nullability,
1673 default, needs to be specified. Multiple changes
1674 can also be specified at once and the backend should
1675 "do the right thing", emitting each change either
1676 separately or together as the backend allows.
1678 MySQL has special requirements here, since MySQL
1679 cannot ALTER a column without a full specification.
1680 When producing MySQL-compatible migration files,
1681 it is recommended that the ``existing_type``,
1682 ``existing_server_default``, and ``existing_nullable``
1683 parameters be present, if not being altered.
1685 Type changes which are against the SQLAlchemy
1686 "schema" types :class:`~sqlalchemy.types.Boolean`
1687 and :class:`~sqlalchemy.types.Enum` may also
1688 add or drop constraints which accompany those
1689 types on backends that don't support them natively.
1690 The ``existing_type`` argument is
1691 used in this case to identify and remove a previous
1692 constraint that was bound to the type object.
1694 :param table_name: string name of the target table.
1695 :param column_name: string name of the target column,
1696 as it exists before the operation begins.
1697 :param nullable: Optional; specify ``True`` or ``False``
1698 to alter the column's nullability.
1699 :param server_default: Optional; specify a string
1700 SQL expression, :func:`~sqlalchemy.sql.expression.text`,
1701 or :class:`~sqlalchemy.schema.DefaultClause` to indicate
1702 an alteration to the column's default value.
1703 Set to ``None`` to have the default removed.
1704 :param comment: optional string text of a new comment to add to the
1705 column.
1707 .. versionadded:: 1.0.6
1709 :param new_column_name: Optional; specify a string name here to
1710 indicate the new name within a column rename operation.
1711 :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine`
1712 type object to specify a change to the column's type.
1713 For SQLAlchemy types that also indicate a constraint (i.e.
1714 :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
1715 the constraint is also generated.
1716 :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column;
1717 currently understood by the MySQL dialect.
1718 :param existing_type: Optional; a
1719 :class:`~sqlalchemy.types.TypeEngine`
1720 type object to specify the previous type. This
1721 is required for all MySQL column alter operations that
1722 don't otherwise specify a new type, as well as for
1723 when nullability is being changed on a SQL Server
1724 column. It is also used if the type is a so-called
1725 SQLlchemy "schema" type which may define a constraint (i.e.
1726 :class:`~sqlalchemy.types.Boolean`,
1727 :class:`~sqlalchemy.types.Enum`),
1728 so that the constraint can be dropped.
1729 :param existing_server_default: Optional; The existing
1730 default value of the column. Required on MySQL if
1731 an existing default is not being changed; else MySQL
1732 removes the default.
1733 :param existing_nullable: Optional; the existing nullability
1734 of the column. Required on MySQL if the existing nullability
1735 is not being changed; else MySQL sets this to NULL.
1736 :param existing_autoincrement: Optional; the existing autoincrement
1737 of the column. Used for MySQL's system of altering a column
1738 that specifies ``AUTO_INCREMENT``.
1739 :param existing_comment: string text of the existing comment on the
1740 column to be maintained. Required on MySQL if the existing comment
1741 on the column is not being changed.
1743 .. versionadded:: 1.0.6
1745 :param schema: Optional schema name to operate within. To control
1746 quoting of the schema outside of the default behavior, use
1747 the SQLAlchemy construct
1748 :class:`~sqlalchemy.sql.elements.quoted_name`.
1750 .. versionadded:: 0.7.0 'schema' can now accept a
1751 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1753 :param postgresql_using: String argument which will indicate a
1754 SQL expression to render within the Postgresql-specific USING clause
1755 within ALTER COLUMN. This string is taken directly as raw SQL which
1756 must explicitly include any necessary quoting or escaping of tokens
1757 within the expression.
1759 .. versionadded:: 0.8.8
1761 """
1763 alt = cls(
1764 table_name,
1765 column_name,
1766 schema=schema,
1767 existing_type=existing_type,
1768 existing_server_default=existing_server_default,
1769 existing_nullable=existing_nullable,
1770 existing_comment=existing_comment,
1771 modify_name=new_column_name,
1772 modify_type=type_,
1773 modify_server_default=server_default,
1774 modify_nullable=nullable,
1775 modify_comment=comment,
1776 **kw
1777 )
1779 return operations.invoke(alt)
1781 @classmethod
1782 def batch_alter_column(
1783 cls,
1784 operations,
1785 column_name,
1786 nullable=None,
1787 comment=False,
1788 server_default=False,
1789 new_column_name=None,
1790 type_=None,
1791 existing_type=None,
1792 existing_server_default=False,
1793 existing_nullable=None,
1794 existing_comment=None,
1795 insert_before=None,
1796 insert_after=None,
1797 **kw
1798 ):
1799 """Issue an "alter column" instruction using the current
1800 batch migration context.
1802 Parameters are the same as that of :meth:`.Operations.alter_column`,
1803 as well as the following option(s):
1805 :param insert_before: String name of an existing column which this
1806 column should be placed before, when creating the new table.
1808 .. versionadded:: 1.4.0
1810 :param insert_before: String name of an existing column which this
1811 column should be placed after, when creating the new table. If
1812 both :paramref:`.BatchOperations.alter_column.insert_before`
1813 and :paramref:`.BatchOperations.alter_column.insert_after` are
1814 omitted, the column is inserted after the last existing column
1815 in the table.
1817 .. versionadded:: 1.4.0
1819 .. seealso::
1821 :meth:`.Operations.alter_column`
1824 """
1825 alt = cls(
1826 operations.impl.table_name,
1827 column_name,
1828 schema=operations.impl.schema,
1829 existing_type=existing_type,
1830 existing_server_default=existing_server_default,
1831 existing_nullable=existing_nullable,
1832 existing_comment=existing_comment,
1833 modify_name=new_column_name,
1834 modify_type=type_,
1835 modify_server_default=server_default,
1836 modify_nullable=nullable,
1837 modify_comment=comment,
1838 **kw
1839 )
1841 return operations.invoke(alt)
1844@Operations.register_operation("add_column")
1845@BatchOperations.register_operation("add_column", "batch_add_column")
1846class AddColumnOp(AlterTableOp):
1847 """Represent an add column operation."""
1849 def __init__(self, table_name, column, schema=None, **kw):
1850 super(AddColumnOp, self).__init__(table_name, schema=schema)
1851 self.column = column
1852 self.kw = kw
1854 def reverse(self):
1855 return DropColumnOp.from_column_and_tablename(
1856 self.schema, self.table_name, self.column
1857 )
1859 def to_diff_tuple(self):
1860 return ("add_column", self.schema, self.table_name, self.column)
1862 def to_column(self):
1863 return self.column
1865 @classmethod
1866 def from_column(cls, col):
1867 return cls(col.table.name, col, schema=col.table.schema)
1869 @classmethod
1870 def from_column_and_tablename(cls, schema, tname, col):
1871 return cls(tname, col, schema=schema)
1873 @classmethod
1874 def add_column(cls, operations, table_name, column, schema=None):
1875 """Issue an "add column" instruction using the current
1876 migration context.
1878 e.g.::
1880 from alembic import op
1881 from sqlalchemy import Column, String
1883 op.add_column('organization',
1884 Column('name', String())
1885 )
1887 The provided :class:`~sqlalchemy.schema.Column` object can also
1888 specify a :class:`~sqlalchemy.schema.ForeignKey`, referencing
1889 a remote table name. Alembic will automatically generate a stub
1890 "referenced" table and emit a second ALTER statement in order
1891 to add the constraint separately::
1893 from alembic import op
1894 from sqlalchemy import Column, INTEGER, ForeignKey
1896 op.add_column('organization',
1897 Column('account_id', INTEGER, ForeignKey('accounts.id'))
1898 )
1900 Note that this statement uses the :class:`~sqlalchemy.schema.Column`
1901 construct as is from the SQLAlchemy library. In particular,
1902 default values to be created on the database side are
1903 specified using the ``server_default`` parameter, and not
1904 ``default`` which only specifies Python-side defaults::
1906 from alembic import op
1907 from sqlalchemy import Column, TIMESTAMP, func
1909 # specify "DEFAULT NOW" along with the column add
1910 op.add_column('account',
1911 Column('timestamp', TIMESTAMP, server_default=func.now())
1912 )
1914 :param table_name: String name of the parent table.
1915 :param column: a :class:`sqlalchemy.schema.Column` object
1916 representing the new column.
1917 :param schema: Optional schema name to operate within. To control
1918 quoting of the schema outside of the default behavior, use
1919 the SQLAlchemy construct
1920 :class:`~sqlalchemy.sql.elements.quoted_name`.
1922 .. versionadded:: 0.7.0 'schema' can now accept a
1923 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1926 """
1928 op = cls(table_name, column, schema=schema)
1929 return operations.invoke(op)
1931 @classmethod
1932 def batch_add_column(
1933 cls, operations, column, insert_before=None, insert_after=None
1934 ):
1935 """Issue an "add column" instruction using the current
1936 batch migration context.
1938 .. seealso::
1940 :meth:`.Operations.add_column`
1942 """
1944 kw = {}
1945 if insert_before:
1946 kw["insert_before"] = insert_before
1947 if insert_after:
1948 kw["insert_after"] = insert_after
1950 op = cls(
1951 operations.impl.table_name,
1952 column,
1953 schema=operations.impl.schema,
1954 **kw
1955 )
1956 return operations.invoke(op)
1959@Operations.register_operation("drop_column")
1960@BatchOperations.register_operation("drop_column", "batch_drop_column")
1961class DropColumnOp(AlterTableOp):
1962 """Represent a drop column operation."""
1964 def __init__(
1965 self, table_name, column_name, schema=None, _orig_column=None, **kw
1966 ):
1967 super(DropColumnOp, self).__init__(table_name, schema=schema)
1968 self.column_name = column_name
1969 self.kw = kw
1970 self._orig_column = _orig_column
1972 def to_diff_tuple(self):
1973 return (
1974 "remove_column",
1975 self.schema,
1976 self.table_name,
1977 self.to_column(),
1978 )
1980 def reverse(self):
1981 if self._orig_column is None:
1982 raise ValueError(
1983 "operation is not reversible; "
1984 "original column is not present"
1985 )
1987 return AddColumnOp.from_column_and_tablename(
1988 self.schema, self.table_name, self._orig_column
1989 )
1991 @classmethod
1992 def from_column_and_tablename(cls, schema, tname, col):
1993 return cls(tname, col.name, schema=schema, _orig_column=col)
1995 def to_column(self, migration_context=None):
1996 if self._orig_column is not None:
1997 return self._orig_column
1998 schema_obj = schemaobj.SchemaObjects(migration_context)
1999 return schema_obj.column(self.column_name, NULLTYPE)
2001 @classmethod
2002 def drop_column(
2003 cls, operations, table_name, column_name, schema=None, **kw
2004 ):
2005 """Issue a "drop column" instruction using the current
2006 migration context.
2008 e.g.::
2010 drop_column('organization', 'account_id')
2012 :param table_name: name of table
2013 :param column_name: name of column
2014 :param schema: Optional schema name to operate within. To control
2015 quoting of the schema outside of the default behavior, use
2016 the SQLAlchemy construct
2017 :class:`~sqlalchemy.sql.elements.quoted_name`.
2019 .. versionadded:: 0.7.0 'schema' can now accept a
2020 :class:`~sqlalchemy.sql.elements.quoted_name` construct.
2022 :param mssql_drop_check: Optional boolean. When ``True``, on
2023 Microsoft SQL Server only, first
2024 drop the CHECK constraint on the column using a
2025 SQL-script-compatible
2026 block that selects into a @variable from sys.check_constraints,
2027 then exec's a separate DROP CONSTRAINT for that constraint.
2028 :param mssql_drop_default: Optional boolean. When ``True``, on
2029 Microsoft SQL Server only, first
2030 drop the DEFAULT constraint on the column using a
2031 SQL-script-compatible
2032 block that selects into a @variable from sys.default_constraints,
2033 then exec's a separate DROP CONSTRAINT for that default.
2034 :param mssql_drop_foreign_key: Optional boolean. When ``True``, on
2035 Microsoft SQL Server only, first
2036 drop a single FOREIGN KEY constraint on the column using a
2037 SQL-script-compatible
2038 block that selects into a @variable from
2039 sys.foreign_keys/sys.foreign_key_columns,
2040 then exec's a separate DROP CONSTRAINT for that default. Only
2041 works if the column has exactly one FK constraint which refers to
2042 it, at the moment.
2044 .. versionadded:: 0.6.2
2046 """
2048 op = cls(table_name, column_name, schema=schema, **kw)
2049 return operations.invoke(op)
2051 @classmethod
2052 def batch_drop_column(cls, operations, column_name, **kw):
2053 """Issue a "drop column" instruction using the current
2054 batch migration context.
2056 .. seealso::
2058 :meth:`.Operations.drop_column`
2060 """
2061 op = cls(
2062 operations.impl.table_name,
2063 column_name,
2064 schema=operations.impl.schema,
2065 **kw
2066 )
2067 return operations.invoke(op)
2070@Operations.register_operation("bulk_insert")
2071class BulkInsertOp(MigrateOperation):
2072 """Represent a bulk insert operation."""
2074 def __init__(self, table, rows, multiinsert=True):
2075 self.table = table
2076 self.rows = rows
2077 self.multiinsert = multiinsert
2079 @classmethod
2080 def bulk_insert(cls, operations, table, rows, multiinsert=True):
2081 """Issue a "bulk insert" operation using the current
2082 migration context.
2084 This provides a means of representing an INSERT of multiple rows
2085 which works equally well in the context of executing on a live
2086 connection as well as that of generating a SQL script. In the
2087 case of a SQL script, the values are rendered inline into the
2088 statement.
2090 e.g.::
2092 from alembic import op
2093 from datetime import date
2094 from sqlalchemy.sql import table, column
2095 from sqlalchemy import String, Integer, Date
2097 # Create an ad-hoc table to use for the insert statement.
2098 accounts_table = table('account',
2099 column('id', Integer),
2100 column('name', String),
2101 column('create_date', Date)
2102 )
2104 op.bulk_insert(accounts_table,
2105 [
2106 {'id':1, 'name':'John Smith',
2107 'create_date':date(2010, 10, 5)},
2108 {'id':2, 'name':'Ed Williams',
2109 'create_date':date(2007, 5, 27)},
2110 {'id':3, 'name':'Wendy Jones',
2111 'create_date':date(2008, 8, 15)},
2112 ]
2113 )
2115 When using --sql mode, some datatypes may not render inline
2116 automatically, such as dates and other special types. When this
2117 issue is present, :meth:`.Operations.inline_literal` may be used::
2119 op.bulk_insert(accounts_table,
2120 [
2121 {'id':1, 'name':'John Smith',
2122 'create_date':op.inline_literal("2010-10-05")},
2123 {'id':2, 'name':'Ed Williams',
2124 'create_date':op.inline_literal("2007-05-27")},
2125 {'id':3, 'name':'Wendy Jones',
2126 'create_date':op.inline_literal("2008-08-15")},
2127 ],
2128 multiinsert=False
2129 )
2131 When using :meth:`.Operations.inline_literal` in conjunction with
2132 :meth:`.Operations.bulk_insert`, in order for the statement to work
2133 in "online" (e.g. non --sql) mode, the
2134 :paramref:`~.Operations.bulk_insert.multiinsert`
2135 flag should be set to ``False``, which will have the effect of
2136 individual INSERT statements being emitted to the database, each
2137 with a distinct VALUES clause, so that the "inline" values can
2138 still be rendered, rather than attempting to pass the values
2139 as bound parameters.
2141 .. versionadded:: 0.6.4 :meth:`.Operations.inline_literal` can now
2142 be used with :meth:`.Operations.bulk_insert`, and the
2143 :paramref:`~.Operations.bulk_insert.multiinsert` flag has
2144 been added to assist in this usage when running in "online"
2145 mode.
2147 :param table: a table object which represents the target of the INSERT.
2149 :param rows: a list of dictionaries indicating rows.
2151 :param multiinsert: when at its default of True and --sql mode is not
2152 enabled, the INSERT statement will be executed using
2153 "executemany()" style, where all elements in the list of
2154 dictionaries are passed as bound parameters in a single
2155 list. Setting this to False results in individual INSERT
2156 statements being emitted per parameter set, and is needed
2157 in those cases where non-literal values are present in the
2158 parameter sets.
2160 .. versionadded:: 0.6.4
2162 """
2164 op = cls(table, rows, multiinsert=multiinsert)
2165 operations.invoke(op)
2168@Operations.register_operation("execute")
2169class ExecuteSQLOp(MigrateOperation):
2170 """Represent an execute SQL operation."""
2172 def __init__(self, sqltext, execution_options=None):
2173 self.sqltext = sqltext
2174 self.execution_options = execution_options
2176 @classmethod
2177 def execute(cls, operations, sqltext, execution_options=None):
2178 r"""Execute the given SQL using the current migration context.
2180 The given SQL can be a plain string, e.g.::
2182 op.execute("INSERT INTO table (foo) VALUES ('some value')")
2184 Or it can be any kind of Core SQL Expression construct, such as
2185 below where we use an update construct::
2187 from sqlalchemy.sql import table, column
2188 from sqlalchemy import String
2189 from alembic import op
2191 account = table('account',
2192 column('name', String)
2193 )
2194 op.execute(
2195 account.update().\\
2196 where(account.c.name==op.inline_literal('account 1')).\\
2197 values({'name':op.inline_literal('account 2')})
2198 )
2200 Above, we made use of the SQLAlchemy
2201 :func:`sqlalchemy.sql.expression.table` and
2202 :func:`sqlalchemy.sql.expression.column` constructs to make a brief,
2203 ad-hoc table construct just for our UPDATE statement. A full
2204 :class:`~sqlalchemy.schema.Table` construct of course works perfectly
2205 fine as well, though note it's a recommended practice to at least
2206 ensure the definition of a table is self-contained within the migration
2207 script, rather than imported from a module that may break compatibility
2208 with older migrations.
2210 In a SQL script context, the statement is emitted directly to the
2211 output stream. There is *no* return result, however, as this
2212 function is oriented towards generating a change script
2213 that can run in "offline" mode. Additionally, parameterized
2214 statements are discouraged here, as they *will not work* in offline
2215 mode. Above, we use :meth:`.inline_literal` where parameters are
2216 to be used.
2218 For full interaction with a connected database where parameters can
2219 also be used normally, use the "bind" available from the context::
2221 from alembic import op
2222 connection = op.get_bind()
2224 connection.execute(
2225 account.update().where(account.c.name=='account 1').
2226 values({"name": "account 2"})
2227 )
2229 Additionally, when passing the statement as a plain string, it is first
2230 coerceed into a :func:`sqlalchemy.sql.expression.text` construct
2231 before being passed along. In the less likely case that the
2232 literal SQL string contains a colon, it must be escaped with a
2233 backslash, as::
2235 op.execute("INSERT INTO table (foo) VALUES ('\:colon_value')")
2238 :param sql: Any legal SQLAlchemy expression, including:
2240 * a string
2241 * a :func:`sqlalchemy.sql.expression.text` construct.
2242 * a :func:`sqlalchemy.sql.expression.insert` construct.
2243 * a :func:`sqlalchemy.sql.expression.update`,
2244 :func:`sqlalchemy.sql.expression.insert`,
2245 or :func:`sqlalchemy.sql.expression.delete` construct.
2246 * Pretty much anything that's "executable" as described
2247 in :ref:`sqlexpression_toplevel`.
2249 .. note:: when passing a plain string, the statement is coerced into
2250 a :func:`sqlalchemy.sql.expression.text` construct. This construct
2251 considers symbols with colons, e.g. ``:foo`` to be bound parameters.
2252 To avoid this, ensure that colon symbols are escaped, e.g.
2253 ``\:foo``.
2255 :param execution_options: Optional dictionary of
2256 execution options, will be passed to
2257 :meth:`sqlalchemy.engine.Connection.execution_options`.
2258 """
2259 op = cls(sqltext, execution_options=execution_options)
2260 return operations.invoke(op)
2263class OpContainer(MigrateOperation):
2264 """Represent a sequence of operations operation."""
2266 def __init__(self, ops=()):
2267 self.ops = ops
2269 def is_empty(self):
2270 return not self.ops
2272 def as_diffs(self):
2273 return list(OpContainer._ops_as_diffs(self))
2275 @classmethod
2276 def _ops_as_diffs(cls, migrations):
2277 for op in migrations.ops:
2278 if hasattr(op, "ops"):
2279 for sub_op in cls._ops_as_diffs(op):
2280 yield sub_op
2281 else:
2282 yield op.to_diff_tuple()
2285class ModifyTableOps(OpContainer):
2286 """Contains a sequence of operations that all apply to a single Table."""
2288 def __init__(self, table_name, ops, schema=None):
2289 super(ModifyTableOps, self).__init__(ops)
2290 self.table_name = table_name
2291 self.schema = schema
2293 def reverse(self):
2294 return ModifyTableOps(
2295 self.table_name,
2296 ops=list(reversed([op.reverse() for op in self.ops])),
2297 schema=self.schema,
2298 )
2301class UpgradeOps(OpContainer):
2302 """contains a sequence of operations that would apply to the
2303 'upgrade' stream of a script.
2305 .. seealso::
2307 :ref:`customizing_revision`
2309 """
2311 def __init__(self, ops=(), upgrade_token="upgrades"):
2312 super(UpgradeOps, self).__init__(ops=ops)
2313 self.upgrade_token = upgrade_token
2315 def reverse_into(self, downgrade_ops):
2316 downgrade_ops.ops[:] = list(
2317 reversed([op.reverse() for op in self.ops])
2318 )
2319 return downgrade_ops
2321 def reverse(self):
2322 return self.reverse_into(DowngradeOps(ops=[]))
2325class DowngradeOps(OpContainer):
2326 """contains a sequence of operations that would apply to the
2327 'downgrade' stream of a script.
2329 .. seealso::
2331 :ref:`customizing_revision`
2333 """
2335 def __init__(self, ops=(), downgrade_token="downgrades"):
2336 super(DowngradeOps, self).__init__(ops=ops)
2337 self.downgrade_token = downgrade_token
2339 def reverse(self):
2340 return UpgradeOps(
2341 ops=list(reversed([op.reverse() for op in self.ops]))
2342 )
2345class MigrationScript(MigrateOperation):
2346 """represents a migration script.
2348 E.g. when autogenerate encounters this object, this corresponds to the
2349 production of an actual script file.
2351 A normal :class:`.MigrationScript` object would contain a single
2352 :class:`.UpgradeOps` and a single :class:`.DowngradeOps` directive.
2353 These are accessible via the ``.upgrade_ops`` and ``.downgrade_ops``
2354 attributes.
2356 In the case of an autogenerate operation that runs multiple times,
2357 such as the multiple database example in the "multidb" template,
2358 the ``.upgrade_ops`` and ``.downgrade_ops`` attributes are disabled,
2359 and instead these objects should be accessed via the ``.upgrade_ops_list``
2360 and ``.downgrade_ops_list`` list-based attributes. These latter
2361 attributes are always available at the very least as single-element lists.
2363 .. versionchanged:: 0.8.1 the ``.upgrade_ops`` and ``.downgrade_ops``
2364 attributes should be accessed via the ``.upgrade_ops_list``
2365 and ``.downgrade_ops_list`` attributes if multiple autogenerate
2366 passes proceed on the same :class:`.MigrationScript` object.
2368 .. seealso::
2370 :ref:`customizing_revision`
2372 """
2374 def __init__(
2375 self,
2376 rev_id,
2377 upgrade_ops,
2378 downgrade_ops,
2379 message=None,
2380 imports=set(),
2381 head=None,
2382 splice=None,
2383 branch_label=None,
2384 version_path=None,
2385 depends_on=None,
2386 ):
2387 self.rev_id = rev_id
2388 self.message = message
2389 self.imports = imports
2390 self.head = head
2391 self.splice = splice
2392 self.branch_label = branch_label
2393 self.version_path = version_path
2394 self.depends_on = depends_on
2395 self.upgrade_ops = upgrade_ops
2396 self.downgrade_ops = downgrade_ops
2398 @property
2399 def upgrade_ops(self):
2400 """An instance of :class:`.UpgradeOps`.
2402 .. seealso::
2404 :attr:`.MigrationScript.upgrade_ops_list`
2405 """
2406 if len(self._upgrade_ops) > 1:
2407 raise ValueError(
2408 "This MigrationScript instance has a multiple-entry "
2409 "list for UpgradeOps; please use the "
2410 "upgrade_ops_list attribute."
2411 )
2412 elif not self._upgrade_ops:
2413 return None
2414 else:
2415 return self._upgrade_ops[0]
2417 @upgrade_ops.setter
2418 def upgrade_ops(self, upgrade_ops):
2419 self._upgrade_ops = util.to_list(upgrade_ops)
2420 for elem in self._upgrade_ops:
2421 assert isinstance(elem, UpgradeOps)
2423 @property
2424 def downgrade_ops(self):
2425 """An instance of :class:`.DowngradeOps`.
2427 .. seealso::
2429 :attr:`.MigrationScript.downgrade_ops_list`
2430 """
2431 if len(self._downgrade_ops) > 1:
2432 raise ValueError(
2433 "This MigrationScript instance has a multiple-entry "
2434 "list for DowngradeOps; please use the "
2435 "downgrade_ops_list attribute."
2436 )
2437 elif not self._downgrade_ops:
2438 return None
2439 else:
2440 return self._downgrade_ops[0]
2442 @downgrade_ops.setter
2443 def downgrade_ops(self, downgrade_ops):
2444 self._downgrade_ops = util.to_list(downgrade_ops)
2445 for elem in self._downgrade_ops:
2446 assert isinstance(elem, DowngradeOps)
2448 @property
2449 def upgrade_ops_list(self):
2450 """A list of :class:`.UpgradeOps` instances.
2452 This is used in place of the :attr:`.MigrationScript.upgrade_ops`
2453 attribute when dealing with a revision operation that does
2454 multiple autogenerate passes.
2456 .. versionadded:: 0.8.1
2458 """
2459 return self._upgrade_ops
2461 @property
2462 def downgrade_ops_list(self):
2463 """A list of :class:`.DowngradeOps` instances.
2465 This is used in place of the :attr:`.MigrationScript.downgrade_ops`
2466 attribute when dealing with a revision operation that does
2467 multiple autogenerate passes.
2469 .. versionadded:: 0.8.1
2471 """
2472 return self._downgrade_ops