1import re
2
3from sqlalchemy.types import NULLTYPE
4
5from . import schemaobj
6from .base import BatchOperations
7from .base import Operations
8from .. import util
9from ..util import sqla_compat
10
11
12class MigrateOperation(object):
13    """base class for migration command and organization objects.
14
15    This system is part of the operation extensibility API.
16
17    .. versionadded:: 0.8.0
18
19    .. seealso::
20
21        :ref:`operation_objects`
22
23        :ref:`operation_plugins`
24
25        :ref:`customizing_revision`
26
27    """
28
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.
33
34        """
35        return {}
36
37    _mutations = frozenset()
38
39
40class AddConstraintOp(MigrateOperation):
41    """Represent an add constraint operation."""
42
43    add_constraint_ops = util.Dispatcher()
44
45    @property
46    def constraint_type(self):
47        raise NotImplementedError()
48
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
54
55        return go
56
57    @classmethod
58    def from_constraint(cls, constraint):
59        return cls.add_constraint_ops.dispatch(constraint.__visit_name__)(
60            constraint
61        )
62
63    def reverse(self):
64        return DropConstraintOp.from_constraint(self.to_constraint())
65
66    def to_diff_tuple(self):
67        return ("add_constraint", self.to_constraint())
68
69
70@Operations.register_operation("drop_constraint")
71@BatchOperations.register_operation("drop_constraint", "batch_drop_constraint")
72class DropConstraintOp(MigrateOperation):
73    """Represent a drop constraint operation."""
74
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
88
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)
96
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())
102
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        }
113
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        )
122
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            )
131
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.
138
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`.
147
148         .. versionadded:: 0.7.0 'schema' can now accept a
149            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
150
151        .. versionchanged:: 0.8.0 The following positional argument names
152           have been changed:
153
154           * name -> constraint_name
155
156        """
157
158        op = cls(constraint_name, table_name, type_=type_, schema=schema)
159        return operations.invoke(op)
160
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.
165
166        The batch form of this call omits the ``table_name`` and ``schema``
167        arguments from the call.
168
169        .. seealso::
170
171            :meth:`.Operations.drop_constraint`
172
173        .. versionchanged:: 0.8.0 The following positional argument names
174           have been changed:
175
176           * name -> constraint_name
177
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)
186
187
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."""
195
196    constraint_type = "primarykey"
197
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
213
214    @classmethod
215    def from_constraint(cls, constraint):
216        constraint_table = sqla_compat._table_for_constraint(constraint)
217
218        return cls(
219            constraint.name,
220            constraint_table.name,
221            constraint.columns,
222            schema=constraint_table.schema,
223            _orig_constraint=constraint,
224        )
225
226    def to_constraint(self, migration_context=None):
227        if self._orig_constraint is not None:
228            return self._orig_constraint
229
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        )
237
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.
247
248        e.g.::
249
250            from alembic import op
251            op.create_primary_key(
252                        "pk_my_table", "my_table",
253                        ["id", "version"]
254                    )
255
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.
264
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`.
279
280         .. versionadded:: 0.7.0 'schema' can now accept a
281            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
282
283        .. versionchanged:: 0.8.0 The following positional argument names
284           have been changed:
285
286           * name -> constraint_name
287           * cols -> columns
288
289        """
290        op = cls(constraint_name, table_name, columns, schema)
291        return operations.invoke(op)
292
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.
297
298        The batch form of this call omits the ``table_name`` and ``schema``
299        arguments from the call.
300
301        .. seealso::
302
303            :meth:`.Operations.create_primary_key`
304
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)
313
314
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."""
322
323    constraint_type = "unique"
324
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
340
341    @classmethod
342    def from_constraint(cls, constraint):
343        constraint_table = sqla_compat._table_for_constraint(constraint)
344
345        kw = {}
346        if constraint.deferrable:
347            kw["deferrable"] = constraint.deferrable
348        if constraint.initially:
349            kw["initially"] = constraint.initially
350
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        )
359
360    def to_constraint(self, migration_context=None):
361        if self._orig_constraint is not None:
362            return self._orig_constraint
363
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        )
372
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.
392
393        e.g.::
394
395            from alembic import op
396            op.create_unique_constraint("uq_user_name", "user", ["name"])
397
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.
406
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`.
425
426         .. versionadded:: 0.7.0 'schema' can now accept a
427            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
428
429        .. versionchanged:: 0.8.0 The following positional argument names
430           have been changed:
431
432           * name -> constraint_name
433           * source -> table_name
434           * local_cols -> columns
435
436        """
437
438        op = cls(constraint_name, table_name, columns, schema=schema, **kw)
439        return operations.invoke(op)
440
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.
448
449        The batch form of this call omits the ``source`` and ``schema``
450        arguments from the call.
451
452        .. seealso::
453
454            :meth:`.Operations.create_unique_constraint`
455
456        .. versionchanged:: 0.8.0 The following positional argument names
457           have been changed:
458
459           * name -> constraint_name
460
461        """
462        kw["schema"] = operations.impl.schema
463        op = cls(constraint_name, operations.impl.table_name, columns, **kw)
464        return operations.invoke(op)
465
466
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."""
474
475    constraint_type = "foreignkey"
476
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
494
495    def to_diff_tuple(self):
496        return ("add_fk", self.to_constraint())
497
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
511
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)
524
525        kw["source_schema"] = source_schema
526        kw["referent_schema"] = target_schema
527
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        )
537
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        )
550
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.
578
579        e.g.::
580
581            from alembic import op
582            op.create_foreign_key(
583                        "fk_user_address", "address",
584                        "user", ["user_id"], ["id"])
585
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.
594
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.
618
619        .. versionchanged:: 0.8.0 The following positional argument names
620           have been changed:
621
622           * name -> constraint_name
623           * source -> source_table
624           * referent -> referent_table
625
626        """
627
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)
644
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.
666
667        The batch form of this call omits the ``source`` and ``source_schema``
668        arguments from the call.
669
670        e.g.::
671
672            with batch_alter_table("address") as batch_op:
673                batch_op.create_foreign_key(
674                            "fk_user_address",
675                            "user", ["user_id"], ["id"])
676
677        .. seealso::
678
679            :meth:`.Operations.create_foreign_key`
680
681        .. versionchanged:: 0.8.0 The following positional argument names
682           have been changed:
683
684           * name -> constraint_name
685           * referent -> referent_table
686
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)
704
705
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."""
715
716    constraint_type = "check"
717
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
733
734    @classmethod
735    def from_constraint(cls, constraint):
736        constraint_table = sqla_compat._table_for_constraint(constraint)
737
738        return cls(
739            constraint.name,
740            constraint_table.name,
741            constraint.sqltext,
742            schema=constraint_table.schema,
743            _orig_constraint=constraint,
744        )
745
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        )
757
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.
773
774        e.g.::
775
776            from alembic import op
777            from sqlalchemy.sql import column, func
778
779            op.create_check_constraint(
780                "ck_user_name_len",
781                "user",
782                func.len(column('name')) > 5
783            )
784
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.
789
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`.
809
810         .. versionadded:: 0.7.0 'schema' can now accept a
811            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
812
813        .. versionchanged:: 0.8.0 The following positional argument names
814           have been changed:
815
816           * name -> constraint_name
817           * source -> table_name
818
819        """
820        op = cls(constraint_name, table_name, condition, schema=schema, **kw)
821        return operations.invoke(op)
822
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.
830
831        The batch form of this call omits the ``source`` and ``schema``
832        arguments from the call.
833
834        .. seealso::
835
836            :meth:`.Operations.create_check_constraint`
837
838        .. versionchanged:: 0.8.0 The following positional argument names
839           have been changed:
840
841           * name -> constraint_name
842
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)
852
853
854@Operations.register_operation("create_index")
855@BatchOperations.register_operation("create_index", "batch_create_index")
856class CreateIndexOp(MigrateOperation):
857    """Represent a create index operation."""
858
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
876
877    def reverse(self):
878        return DropIndexOp.from_index(self.to_index())
879
880    def to_diff_tuple(self):
881        return ("add_index", self.to_index())
882
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        )
894
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        )
907
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.
922
923        e.g.::
924
925            from alembic import op
926            op.create_index('ik_test', 't1', ['foo', 'bar'])
927
928        Functional indexes can be produced by using the
929        :func:`sqlalchemy.sql.expression.text` construct::
930
931            from alembic import op
932            from sqlalchemy import text
933            op.create_index('ik_test', 't1', [text('lower(foo)')])
934
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.
940
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`.
949
950         .. versionadded:: 0.7.0 'schema' can now accept a
951            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
952
953        :param unique: If True, create a unique index.
954
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.
963
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.
969
970        .. versionchanged:: 0.8.0 The following positional argument names
971           have been changed:
972
973           * name -> index_name
974
975        """
976        op = cls(
977            index_name, table_name, columns, schema=schema, unique=unique, **kw
978        )
979        return operations.invoke(op)
980
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.
985
986        .. seealso::
987
988            :meth:`.Operations.create_index`
989
990        """
991
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)
1000
1001
1002@Operations.register_operation("drop_index")
1003@BatchOperations.register_operation("drop_index", "batch_drop_index")
1004class DropIndexOp(MigrateOperation):
1005    """Represent a drop index operation."""
1006
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
1015
1016    def to_diff_tuple(self):
1017        return ("remove_index", self.to_index())
1018
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)
1025
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        )
1035
1036    def to_index(self, migration_context=None):
1037        if self._orig_index is not None:
1038            return self._orig_index
1039
1040        schema_obj = schemaobj.SchemaObjects(migration_context)
1041
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        )
1051
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.
1061
1062        e.g.::
1063
1064            drop_index("accounts")
1065
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`.
1073
1074         .. versionadded:: 0.7.0 'schema' can now accept a
1075            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1076
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.
1082
1083            .. versionadded:: 0.9.5 Support for dialect-specific keyword
1084               arguments for DROP INDEX
1085
1086        .. versionchanged:: 0.8.0 The following positional argument names
1087           have been changed:
1088
1089           * name -> index_name
1090
1091        """
1092        op = cls(index_name, table_name=table_name, schema=schema, **kw)
1093        return operations.invoke(op)
1094
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.
1100
1101        .. seealso::
1102
1103            :meth:`.Operations.drop_index`
1104
1105        .. versionchanged:: 0.8.0 The following positional argument names
1106           have been changed:
1107
1108           * name -> index_name
1109
1110        """
1111
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)
1119
1120
1121@Operations.register_operation("create_table")
1122class CreateTableOp(MigrateOperation):
1123    """Represent a create table operation."""
1124
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
1133
1134    def reverse(self):
1135        return DropTableOp.from_table(self.to_table())
1136
1137    def to_diff_tuple(self):
1138        return ("add_table", self.to_table())
1139
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        )
1149
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)
1154
1155        return schema_obj.table(
1156            self.table_name, *self.columns, schema=self.schema, **self.kw
1157        )
1158
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.
1164
1165        This directive receives an argument list similar to that of the
1166        traditional :class:`sqlalchemy.schema.Table` construct, but without the
1167        metadata::
1168
1169            from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
1170            from alembic import op
1171
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            )
1179
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::
1186
1187            from alembic import op
1188            from sqlalchemy import Column, TIMESTAMP, func
1189
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            )
1195
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`::
1201
1202            from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
1203            from alembic import op
1204
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            )
1212
1213            op.bulk_insert(
1214                account_table,
1215                [
1216                    {"name": "A1", "description": "account 1"},
1217                    {"name": "A2", "description": "account 2"},
1218                ]
1219            )
1220
1221        .. versionadded:: 0.7.0
1222
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`.
1233
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.
1238
1239        :return: the :class:`~sqlalchemy.schema.Table` object corresponding
1240         to the parameters given.
1241
1242         .. versionadded:: 0.7.0 - the :class:`~sqlalchemy.schema.Table`
1243            object is returned.
1244
1245        .. versionchanged:: 0.8.0 The following positional argument names
1246           have been changed:
1247
1248           * name -> table_name
1249
1250        """
1251        op = cls(table_name, columns, **kw)
1252        return operations.invoke(op)
1253
1254
1255@Operations.register_operation("drop_table")
1256class DropTableOp(MigrateOperation):
1257    """Represent a drop table operation."""
1258
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
1266
1267    def to_diff_tuple(self):
1268        return ("remove_table", self.to_table())
1269
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)
1276
1277    @classmethod
1278    def from_table(cls, table):
1279        return cls(table.name, schema=table.schema, _orig_table=table)
1280
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        )
1288
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.
1294
1295
1296        e.g.::
1297
1298            drop_table("accounts")
1299
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`.
1305
1306         .. versionadded:: 0.7.0 'schema' can now accept a
1307            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1308
1309        :param \**kw: Other keyword arguments are passed to the underlying
1310         :class:`sqlalchemy.schema.Table` object created for the command.
1311
1312        .. versionchanged:: 0.8.0 The following positional argument names
1313           have been changed:
1314
1315           * name -> table_name
1316
1317        """
1318        op = cls(table_name, schema=schema, table_kw=kw)
1319        operations.invoke(op)
1320
1321
1322class AlterTableOp(MigrateOperation):
1323    """Represent an alter table operation."""
1324
1325    def __init__(self, table_name, schema=None):
1326        self.table_name = table_name
1327        self.schema = schema
1328
1329
1330@Operations.register_operation("rename_table")
1331class RenameTableOp(AlterTableOp):
1332    """Represent a rename table operation."""
1333
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
1337
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.
1343
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`.
1350
1351         .. versionadded:: 0.7.0 'schema' can now accept a
1352            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1353
1354        """
1355        op = cls(old_table_name, new_table_name, schema=schema)
1356        return operations.invoke(op)
1357
1358
1359@Operations.register_operation("create_table_comment")
1360class CreateTableCommentOp(AlterTableOp):
1361    """Represent a COMMENT ON `table` operation.
1362    """
1363
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
1371
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.
1382
1383        .. versionadded:: 1.0.6
1384
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.
1392
1393        .. seealso::
1394
1395            :meth:`.Operations.drop_table_comment`
1396
1397            :paramref:`.Operations.alter_column.comment`
1398
1399        """
1400
1401        op = cls(
1402            table_name,
1403            comment,
1404            existing_comment=existing_comment,
1405            schema=schema,
1406        )
1407        return operations.invoke(op)
1408
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            )
1425
1426    def to_table(self, migration_context=None):
1427        schema_obj = schemaobj.SchemaObjects(migration_context)
1428
1429        return schema_obj.table(
1430            self.table_name, schema=self.schema, comment=self.comment
1431        )
1432
1433    def to_diff_tuple(self):
1434        return ("add_table_comment", self.to_table(), self.existing_comment)
1435
1436
1437@Operations.register_operation("drop_table_comment")
1438class DropTableCommentOp(AlterTableOp):
1439    """Represent an operation to remove the comment from a table.
1440    """
1441
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
1446
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.
1453
1454        .. versionadded:: 1.0.6
1455
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.
1459
1460        .. seealso::
1461
1462            :meth:`.Operations.create_table_comment`
1463
1464            :paramref:`.Operations.alter_column.comment`
1465
1466        """
1467
1468        op = cls(table_name, existing_comment=existing_comment, schema=schema)
1469        return operations.invoke(op)
1470
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        )
1477
1478    def to_table(self, migration_context=None):
1479        schema_obj = schemaobj.SchemaObjects(migration_context)
1480
1481        return schema_obj.table(self.table_name, schema=self.schema)
1482
1483    def to_diff_tuple(self):
1484        return ("remove_table_comment", self.to_table())
1485
1486
1487@Operations.register_operation("alter_column")
1488@BatchOperations.register_operation("alter_column", "batch_alter_column")
1489class AlterColumnOp(AlterTableOp):
1490    """Represent an alter column operation."""
1491
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
1520
1521    def to_diff_tuple(self):
1522        col_diff = []
1523        schema, tname, cname = self.schema, self.table_name, self.column_name
1524
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            )
1543
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            )
1562
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            )
1579
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            )
1598
1599        return col_diff
1600
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
1615
1616    def reverse(self):
1617
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
1631
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        )
1638
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
1644
1645        return self.__class__(
1646            self.table_name, self.column_name, schema=self.schema, **kw
1647        )
1648
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.
1670
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.
1677
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.
1684
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.
1693
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.
1706
1707         .. versionadded:: 1.0.6
1708
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.
1742
1743         .. versionadded:: 1.0.6
1744
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`.
1749
1750         .. versionadded:: 0.7.0 'schema' can now accept a
1751            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1752
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.
1758
1759         .. versionadded:: 0.8.8
1760
1761        """
1762
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        )
1778
1779        return operations.invoke(alt)
1780
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.
1801
1802        Parameters are the same as that of :meth:`.Operations.alter_column`,
1803        as well as the following option(s):
1804
1805        :param insert_before: String name of an existing column which this
1806         column should be placed before, when creating the new table.
1807
1808         .. versionadded:: 1.4.0
1809
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.
1816
1817         .. versionadded:: 1.4.0
1818
1819        .. seealso::
1820
1821            :meth:`.Operations.alter_column`
1822
1823
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        )
1840
1841        return operations.invoke(alt)
1842
1843
1844@Operations.register_operation("add_column")
1845@BatchOperations.register_operation("add_column", "batch_add_column")
1846class AddColumnOp(AlterTableOp):
1847    """Represent an add column operation."""
1848
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
1853
1854    def reverse(self):
1855        return DropColumnOp.from_column_and_tablename(
1856            self.schema, self.table_name, self.column
1857        )
1858
1859    def to_diff_tuple(self):
1860        return ("add_column", self.schema, self.table_name, self.column)
1861
1862    def to_column(self):
1863        return self.column
1864
1865    @classmethod
1866    def from_column(cls, col):
1867        return cls(col.table.name, col, schema=col.table.schema)
1868
1869    @classmethod
1870    def from_column_and_tablename(cls, schema, tname, col):
1871        return cls(tname, col, schema=schema)
1872
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.
1877
1878        e.g.::
1879
1880            from alembic import op
1881            from sqlalchemy import Column, String
1882
1883            op.add_column('organization',
1884                Column('name', String())
1885            )
1886
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::
1892
1893            from alembic import op
1894            from sqlalchemy import Column, INTEGER, ForeignKey
1895
1896            op.add_column('organization',
1897                Column('account_id', INTEGER, ForeignKey('accounts.id'))
1898            )
1899
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::
1905
1906            from alembic import op
1907            from sqlalchemy import Column, TIMESTAMP, func
1908
1909            # specify "DEFAULT NOW" along with the column add
1910            op.add_column('account',
1911                Column('timestamp', TIMESTAMP, server_default=func.now())
1912            )
1913
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`.
1921
1922         .. versionadded:: 0.7.0 'schema' can now accept a
1923            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
1924
1925
1926        """
1927
1928        op = cls(table_name, column, schema=schema)
1929        return operations.invoke(op)
1930
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.
1937
1938        .. seealso::
1939
1940            :meth:`.Operations.add_column`
1941
1942        """
1943
1944        kw = {}
1945        if insert_before:
1946            kw["insert_before"] = insert_before
1947        if insert_after:
1948            kw["insert_after"] = insert_after
1949
1950        op = cls(
1951            operations.impl.table_name,
1952            column,
1953            schema=operations.impl.schema,
1954            **kw
1955        )
1956        return operations.invoke(op)
1957
1958
1959@Operations.register_operation("drop_column")
1960@BatchOperations.register_operation("drop_column", "batch_drop_column")
1961class DropColumnOp(AlterTableOp):
1962    """Represent a drop column operation."""
1963
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
1971
1972    def to_diff_tuple(self):
1973        return (
1974            "remove_column",
1975            self.schema,
1976            self.table_name,
1977            self.to_column(),
1978        )
1979
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            )
1986
1987        return AddColumnOp.from_column_and_tablename(
1988            self.schema, self.table_name, self._orig_column
1989        )
1990
1991    @classmethod
1992    def from_column_and_tablename(cls, schema, tname, col):
1993        return cls(tname, col.name, schema=schema, _orig_column=col)
1994
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)
2000
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.
2007
2008        e.g.::
2009
2010            drop_column('organization', 'account_id')
2011
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`.
2018
2019         .. versionadded:: 0.7.0 'schema' can now accept a
2020            :class:`~sqlalchemy.sql.elements.quoted_name` construct.
2021
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.
2043
2044         .. versionadded:: 0.6.2
2045
2046        """
2047
2048        op = cls(table_name, column_name, schema=schema, **kw)
2049        return operations.invoke(op)
2050
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.
2055
2056        .. seealso::
2057
2058            :meth:`.Operations.drop_column`
2059
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)
2068
2069
2070@Operations.register_operation("bulk_insert")
2071class BulkInsertOp(MigrateOperation):
2072    """Represent a bulk insert operation."""
2073
2074    def __init__(self, table, rows, multiinsert=True):
2075        self.table = table
2076        self.rows = rows
2077        self.multiinsert = multiinsert
2078
2079    @classmethod
2080    def bulk_insert(cls, operations, table, rows, multiinsert=True):
2081        """Issue a "bulk insert" operation using the current
2082        migration context.
2083
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.
2089
2090        e.g.::
2091
2092            from alembic import op
2093            from datetime import date
2094            from sqlalchemy.sql import table, column
2095            from sqlalchemy import String, Integer, Date
2096
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            )
2103
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            )
2114
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::
2118
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            )
2130
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.
2140
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.
2146
2147        :param table: a table object which represents the target of the INSERT.
2148
2149        :param rows: a list of dictionaries indicating rows.
2150
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.
2159
2160           .. versionadded:: 0.6.4
2161
2162          """
2163
2164        op = cls(table, rows, multiinsert=multiinsert)
2165        operations.invoke(op)
2166
2167
2168@Operations.register_operation("execute")
2169class ExecuteSQLOp(MigrateOperation):
2170    """Represent an execute SQL operation."""
2171
2172    def __init__(self, sqltext, execution_options=None):
2173        self.sqltext = sqltext
2174        self.execution_options = execution_options
2175
2176    @classmethod
2177    def execute(cls, operations, sqltext, execution_options=None):
2178        r"""Execute the given SQL using the current migration context.
2179
2180        The given SQL can be a plain string, e.g.::
2181
2182            op.execute("INSERT INTO table (foo) VALUES ('some value')")
2183
2184        Or it can be any kind of Core SQL Expression construct, such as
2185        below where we use an update construct::
2186
2187            from sqlalchemy.sql import table, column
2188            from sqlalchemy import String
2189            from alembic import op
2190
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                    )
2199
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.
2209
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.
2217
2218        For full interaction with a connected database where parameters can
2219        also be used normally, use the "bind" available from the context::
2220
2221            from alembic import op
2222            connection = op.get_bind()
2223
2224            connection.execute(
2225                account.update().where(account.c.name=='account 1').
2226                values({"name": "account 2"})
2227            )
2228
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::
2234
2235           op.execute("INSERT INTO table (foo) VALUES ('\:colon_value')")
2236
2237
2238        :param sql: Any legal SQLAlchemy expression, including:
2239
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`.
2248
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``.
2254
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)
2261
2262
2263class OpContainer(MigrateOperation):
2264    """Represent a sequence of operations operation."""
2265
2266    def __init__(self, ops=()):
2267        self.ops = ops
2268
2269    def is_empty(self):
2270        return not self.ops
2271
2272    def as_diffs(self):
2273        return list(OpContainer._ops_as_diffs(self))
2274
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()
2283
2284
2285class ModifyTableOps(OpContainer):
2286    """Contains a sequence of operations that all apply to a single Table."""
2287
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
2292
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        )
2299
2300
2301class UpgradeOps(OpContainer):
2302    """contains a sequence of operations that would apply to the
2303    'upgrade' stream of a script.
2304
2305    .. seealso::
2306
2307        :ref:`customizing_revision`
2308
2309    """
2310
2311    def __init__(self, ops=(), upgrade_token="upgrades"):
2312        super(UpgradeOps, self).__init__(ops=ops)
2313        self.upgrade_token = upgrade_token
2314
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
2320
2321    def reverse(self):
2322        return self.reverse_into(DowngradeOps(ops=[]))
2323
2324
2325class DowngradeOps(OpContainer):
2326    """contains a sequence of operations that would apply to the
2327    'downgrade' stream of a script.
2328
2329    .. seealso::
2330
2331        :ref:`customizing_revision`
2332
2333    """
2334
2335    def __init__(self, ops=(), downgrade_token="downgrades"):
2336        super(DowngradeOps, self).__init__(ops=ops)
2337        self.downgrade_token = downgrade_token
2338
2339    def reverse(self):
2340        return UpgradeOps(
2341            ops=list(reversed([op.reverse() for op in self.ops]))
2342        )
2343
2344
2345class MigrationScript(MigrateOperation):
2346    """represents a migration script.
2347
2348    E.g. when autogenerate encounters this object, this corresponds to the
2349    production of an actual script file.
2350
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.
2355
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.
2362
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.
2367
2368    .. seealso::
2369
2370        :ref:`customizing_revision`
2371
2372    """
2373
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
2397
2398    @property
2399    def upgrade_ops(self):
2400        """An instance of :class:`.UpgradeOps`.
2401
2402        .. seealso::
2403
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]
2416
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)
2422
2423    @property
2424    def downgrade_ops(self):
2425        """An instance of :class:`.DowngradeOps`.
2426
2427        .. seealso::
2428
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]
2441
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)
2447
2448    @property
2449    def upgrade_ops_list(self):
2450        """A list of :class:`.UpgradeOps` instances.
2451
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.
2455
2456        .. versionadded:: 0.8.1
2457
2458        """
2459        return self._upgrade_ops
2460
2461    @property
2462    def downgrade_ops_list(self):
2463        """A list of :class:`.DowngradeOps` instances.
2464
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.
2468
2469        .. versionadded:: 0.8.1
2470
2471        """
2472        return self._downgrade_ops
2473