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