alembic 1.12.1__py3-none-any.whl → 1.13.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. alembic/__init__.py +1 -3
  2. alembic/autogenerate/__init__.py +10 -10
  3. alembic/autogenerate/api.py +8 -5
  4. alembic/autogenerate/compare.py +134 -199
  5. alembic/autogenerate/render.py +39 -24
  6. alembic/autogenerate/rewriter.py +26 -13
  7. alembic/command.py +7 -2
  8. alembic/config.py +20 -9
  9. alembic/context.pyi +12 -6
  10. alembic/ddl/__init__.py +1 -1
  11. alembic/ddl/_autogen.py +325 -0
  12. alembic/ddl/base.py +12 -9
  13. alembic/ddl/impl.py +110 -13
  14. alembic/ddl/mssql.py +4 -1
  15. alembic/ddl/mysql.py +9 -6
  16. alembic/ddl/oracle.py +4 -1
  17. alembic/ddl/postgresql.py +147 -73
  18. alembic/ddl/sqlite.py +8 -6
  19. alembic/op.pyi +46 -8
  20. alembic/operations/base.py +70 -14
  21. alembic/operations/batch.py +7 -8
  22. alembic/operations/ops.py +53 -31
  23. alembic/operations/schemaobj.py +5 -4
  24. alembic/operations/toimpl.py +8 -5
  25. alembic/runtime/environment.py +17 -7
  26. alembic/runtime/migration.py +27 -11
  27. alembic/script/base.py +34 -27
  28. alembic/script/revision.py +30 -17
  29. alembic/script/write_hooks.py +3 -0
  30. alembic/templates/async/alembic.ini.mako +3 -3
  31. alembic/templates/generic/alembic.ini.mako +3 -3
  32. alembic/templates/multidb/alembic.ini.mako +3 -3
  33. alembic/testing/requirements.py +12 -0
  34. alembic/testing/schemacompare.py +9 -0
  35. alembic/util/__init__.py +31 -31
  36. alembic/util/compat.py +25 -9
  37. alembic/util/langhelpers.py +78 -33
  38. alembic/util/messaging.py +6 -3
  39. alembic/util/pyfiles.py +7 -3
  40. alembic/util/sqla_compat.py +52 -26
  41. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/METADATA +5 -4
  42. alembic-1.13.1.dist-info/RECORD +83 -0
  43. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/WHEEL +1 -1
  44. alembic-1.12.1.dist-info/RECORD +0 -82
  45. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/LICENSE +0 -0
  46. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/entry_points.txt +0 -0
  47. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/top_level.txt +0 -0
alembic/__init__.py CHANGED
@@ -1,6 +1,4 @@
1
- import sys
2
-
3
1
  from . import context
4
2
  from . import op
5
3
 
6
- __version__ = "1.12.1"
4
+ __version__ = "1.13.1"
@@ -1,10 +1,10 @@
1
- from .api import _render_migration_diffs
2
- from .api import compare_metadata
3
- from .api import produce_migrations
4
- from .api import render_python_code
5
- from .api import RevisionContext
6
- from .compare import _produce_net_changes
7
- from .compare import comparators
8
- from .render import render_op_text
9
- from .render import renderers
10
- from .rewriter import Rewriter
1
+ from .api import _render_migration_diffs as _render_migration_diffs
2
+ from .api import compare_metadata as compare_metadata
3
+ from .api import produce_migrations as produce_migrations
4
+ from .api import render_python_code as render_python_code
5
+ from .api import RevisionContext as RevisionContext
6
+ from .compare import _produce_net_changes as _produce_net_changes
7
+ from .compare import comparators as comparators
8
+ from .render import render_op_text as render_op_text
9
+ from .render import renderers as renderers
10
+ from .rewriter import Rewriter as Rewriter
@@ -17,6 +17,7 @@ from . import compare
17
17
  from . import render
18
18
  from .. import util
19
19
  from ..operations import ops
20
+ from ..util import sqla_compat
20
21
 
21
22
  """Provide the 'autogenerate' feature which can produce migration operations
22
23
  automatically."""
@@ -27,6 +28,7 @@ if TYPE_CHECKING:
27
28
  from sqlalchemy.engine import Inspector
28
29
  from sqlalchemy.sql.schema import MetaData
29
30
  from sqlalchemy.sql.schema import SchemaItem
31
+ from sqlalchemy.sql.schema import Table
30
32
 
31
33
  from ..config import Config
32
34
  from ..operations.ops import DowngradeOps
@@ -164,6 +166,7 @@ def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any:
164
166
  """
165
167
 
166
168
  migration_script = produce_migrations(context, metadata)
169
+ assert migration_script.upgrade_ops is not None
167
170
  return migration_script.upgrade_ops.as_diffs()
168
171
 
169
172
 
@@ -330,7 +333,7 @@ class AutogenContext:
330
333
  self,
331
334
  migration_context: MigrationContext,
332
335
  metadata: Optional[MetaData] = None,
333
- opts: Optional[dict] = None,
336
+ opts: Optional[Dict[str, Any]] = None,
334
337
  autogenerate: bool = True,
335
338
  ) -> None:
336
339
  if (
@@ -440,7 +443,7 @@ class AutogenContext:
440
443
  def run_object_filters(
441
444
  self,
442
445
  object_: SchemaItem,
443
- name: Optional[str],
446
+ name: sqla_compat._ConstraintName,
444
447
  type_: NameFilterType,
445
448
  reflected: bool,
446
449
  compare_to: Optional[SchemaItem],
@@ -464,7 +467,7 @@ class AutogenContext:
464
467
  run_filters = run_object_filters
465
468
 
466
469
  @util.memoized_property
467
- def sorted_tables(self):
470
+ def sorted_tables(self) -> List[Table]:
468
471
  """Return an aggregate of the :attr:`.MetaData.sorted_tables`
469
472
  collection(s).
470
473
 
@@ -480,7 +483,7 @@ class AutogenContext:
480
483
  return result
481
484
 
482
485
  @util.memoized_property
483
- def table_key_to_table(self):
486
+ def table_key_to_table(self) -> Dict[str, Table]:
484
487
  """Return an aggregate of the :attr:`.MetaData.tables` dictionaries.
485
488
 
486
489
  The :attr:`.MetaData.tables` collection is a dictionary of table key
@@ -491,7 +494,7 @@ class AutogenContext:
491
494
  objects contain the same table key, an exception is raised.
492
495
 
493
496
  """
494
- result = {}
497
+ result: Dict[str, Table] = {}
495
498
  for m in util.to_list(self.metadata):
496
499
  intersect = set(result).intersection(set(m.tables))
497
500
  if intersect:
@@ -1,3 +1,6 @@
1
+ # mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
2
+ # mypy: no-warn-return-any, allow-any-generics
3
+
1
4
  from __future__ import annotations
2
5
 
3
6
  import contextlib
@@ -7,12 +10,12 @@ from typing import Any
7
10
  from typing import cast
8
11
  from typing import Dict
9
12
  from typing import Iterator
10
- from typing import List
11
13
  from typing import Mapping
12
14
  from typing import Optional
13
15
  from typing import Set
14
16
  from typing import Tuple
15
17
  from typing import TYPE_CHECKING
18
+ from typing import TypeVar
16
19
  from typing import Union
17
20
 
18
21
  from sqlalchemy import event
@@ -21,10 +24,14 @@ from sqlalchemy import schema as sa_schema
21
24
  from sqlalchemy import text
22
25
  from sqlalchemy import types as sqltypes
23
26
  from sqlalchemy.sql import expression
27
+ from sqlalchemy.sql.schema import ForeignKeyConstraint
28
+ from sqlalchemy.sql.schema import Index
29
+ from sqlalchemy.sql.schema import UniqueConstraint
24
30
  from sqlalchemy.util import OrderedSet
25
31
 
26
- from alembic.ddl.base import _fk_spec
27
32
  from .. import util
33
+ from ..ddl._autogen import is_index_sig
34
+ from ..ddl._autogen import is_uq_sig
28
35
  from ..operations import ops
29
36
  from ..util import sqla_compat
30
37
 
@@ -35,10 +42,7 @@ if TYPE_CHECKING:
35
42
  from sqlalchemy.sql.elements import quoted_name
36
43
  from sqlalchemy.sql.elements import TextClause
37
44
  from sqlalchemy.sql.schema import Column
38
- from sqlalchemy.sql.schema import ForeignKeyConstraint
39
- from sqlalchemy.sql.schema import Index
40
45
  from sqlalchemy.sql.schema import Table
41
- from sqlalchemy.sql.schema import UniqueConstraint
42
46
 
43
47
  from alembic.autogenerate.api import AutogenContext
44
48
  from alembic.ddl.impl import DefaultImpl
@@ -46,6 +50,8 @@ if TYPE_CHECKING:
46
50
  from alembic.operations.ops import MigrationScript
47
51
  from alembic.operations.ops import ModifyTableOps
48
52
  from alembic.operations.ops import UpgradeOps
53
+ from ..ddl._autogen import _constraint_sig
54
+
49
55
 
50
56
  log = logging.getLogger(__name__)
51
57
 
@@ -429,102 +435,7 @@ def _compare_columns(
429
435
  log.info("Detected removed column '%s.%s'", name, cname)
430
436
 
431
437
 
432
- class _constraint_sig:
433
- const: Union[UniqueConstraint, ForeignKeyConstraint, Index]
434
-
435
- def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
436
- return sqla_compat._get_constraint_final_name(
437
- self.const, context.dialect
438
- )
439
-
440
- def __eq__(self, other):
441
- return self.const == other.const
442
-
443
- def __ne__(self, other):
444
- return self.const != other.const
445
-
446
- def __hash__(self) -> int:
447
- return hash(self.const)
448
-
449
-
450
- class _uq_constraint_sig(_constraint_sig):
451
- is_index = False
452
- is_unique = True
453
-
454
- def __init__(self, const: UniqueConstraint, impl: DefaultImpl) -> None:
455
- self.const = const
456
- self.name = const.name
457
- self.sig = ("UNIQUE_CONSTRAINT",) + impl.create_unique_constraint_sig(
458
- const
459
- )
460
-
461
- @property
462
- def column_names(self) -> List[str]:
463
- return [col.name for col in self.const.columns]
464
-
465
-
466
- class _ix_constraint_sig(_constraint_sig):
467
- is_index = True
468
-
469
- def __init__(self, const: Index, impl: DefaultImpl) -> None:
470
- self.const = const
471
- self.name = const.name
472
- self.sig = ("INDEX",) + impl.create_index_sig(const)
473
- self.is_unique = bool(const.unique)
474
-
475
- def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
476
- return sqla_compat._get_constraint_final_name(
477
- self.const, context.dialect
478
- )
479
-
480
- @property
481
- def column_names(self) -> Union[List[quoted_name], List[None]]:
482
- return sqla_compat._get_index_column_names(self.const)
483
-
484
-
485
- class _fk_constraint_sig(_constraint_sig):
486
- def __init__(
487
- self, const: ForeignKeyConstraint, include_options: bool = False
488
- ) -> None:
489
- self.const = const
490
- self.name = const.name
491
-
492
- (
493
- self.source_schema,
494
- self.source_table,
495
- self.source_columns,
496
- self.target_schema,
497
- self.target_table,
498
- self.target_columns,
499
- onupdate,
500
- ondelete,
501
- deferrable,
502
- initially,
503
- ) = _fk_spec(const)
504
-
505
- self.sig: Tuple[Any, ...] = (
506
- self.source_schema,
507
- self.source_table,
508
- tuple(self.source_columns),
509
- self.target_schema,
510
- self.target_table,
511
- tuple(self.target_columns),
512
- )
513
- if include_options:
514
- self.sig += (
515
- (None if onupdate.lower() == "no action" else onupdate.lower())
516
- if onupdate
517
- else None,
518
- (None if ondelete.lower() == "no action" else ondelete.lower())
519
- if ondelete
520
- else None,
521
- # convert initially + deferrable into one three-state value
522
- "initially_deferrable"
523
- if initially and initially.lower() == "deferred"
524
- else "deferrable"
525
- if deferrable
526
- else "not deferrable",
527
- )
438
+ _C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index])
528
439
 
529
440
 
530
441
  @comparators.dispatch_for("table")
@@ -561,32 +472,31 @@ def _compare_indexes_and_uniques(
561
472
 
562
473
  if conn_table is not None:
563
474
  # 1b. ... and from connection, if the table exists
564
- if hasattr(inspector, "get_unique_constraints"):
565
- try:
566
- conn_uniques = inspector.get_unique_constraints( # type:ignore[assignment] # noqa
567
- tname, schema=schema
475
+ try:
476
+ conn_uniques = inspector.get_unique_constraints( # type:ignore[assignment] # noqa
477
+ tname, schema=schema
478
+ )
479
+ supports_unique_constraints = True
480
+ except NotImplementedError:
481
+ pass
482
+ except TypeError:
483
+ # number of arguments is off for the base
484
+ # method in SQLAlchemy due to the cache decorator
485
+ # not being present
486
+ pass
487
+ else:
488
+ conn_uniques = [ # type:ignore[assignment]
489
+ uq
490
+ for uq in conn_uniques
491
+ if autogen_context.run_name_filters(
492
+ uq["name"],
493
+ "unique_constraint",
494
+ {"table_name": tname, "schema_name": schema},
568
495
  )
569
- supports_unique_constraints = True
570
- except NotImplementedError:
571
- pass
572
- except TypeError:
573
- # number of arguments is off for the base
574
- # method in SQLAlchemy due to the cache decorator
575
- # not being present
576
- pass
577
- else:
578
- conn_uniques = [ # type:ignore[assignment]
579
- uq
580
- for uq in conn_uniques
581
- if autogen_context.run_name_filters(
582
- uq["name"],
583
- "unique_constraint",
584
- {"table_name": tname, "schema_name": schema},
585
- )
586
- ]
587
- for uq in conn_uniques:
588
- if uq.get("duplicates_index"):
589
- unique_constraints_duplicate_unique_indexes = True
496
+ ]
497
+ for uq in conn_uniques:
498
+ if uq.get("duplicates_index"):
499
+ unique_constraints_duplicate_unique_indexes = True
590
500
  try:
591
501
  conn_indexes = inspector.get_indexes( # type:ignore[assignment]
592
502
  tname, schema=schema
@@ -639,7 +549,7 @@ def _compare_indexes_and_uniques(
639
549
  # 3. give the dialect a chance to omit indexes and constraints that
640
550
  # we know are either added implicitly by the DB or that the DB
641
551
  # can't accurately report on
642
- autogen_context.migration_context.impl.correct_for_autogen_constraints(
552
+ impl.correct_for_autogen_constraints(
643
553
  conn_uniques, # type: ignore[arg-type]
644
554
  conn_indexes, # type: ignore[arg-type]
645
555
  metadata_unique_constraints,
@@ -651,31 +561,31 @@ def _compare_indexes_and_uniques(
651
561
  # Index and UniqueConstraint so we can easily work with them
652
562
  # interchangeably
653
563
  metadata_unique_constraints_sig = {
654
- _uq_constraint_sig(uq, impl) for uq in metadata_unique_constraints
564
+ impl._create_metadata_constraint_sig(uq)
565
+ for uq in metadata_unique_constraints
655
566
  }
656
567
 
657
568
  metadata_indexes_sig = {
658
- _ix_constraint_sig(ix, impl) for ix in metadata_indexes
569
+ impl._create_metadata_constraint_sig(ix) for ix in metadata_indexes
659
570
  }
660
571
 
661
572
  conn_unique_constraints = {
662
- _uq_constraint_sig(uq, impl) for uq in conn_uniques
573
+ impl._create_reflected_constraint_sig(uq) for uq in conn_uniques
663
574
  }
664
575
 
665
- conn_indexes_sig = {_ix_constraint_sig(ix, impl) for ix in conn_indexes}
576
+ conn_indexes_sig = {
577
+ impl._create_reflected_constraint_sig(ix) for ix in conn_indexes
578
+ }
666
579
 
667
580
  # 5. index things by name, for those objects that have names
668
581
  metadata_names = {
669
582
  cast(str, c.md_name_to_sql_name(autogen_context)): c
670
- for c in metadata_unique_constraints_sig.union(
671
- metadata_indexes_sig # type:ignore[arg-type]
672
- )
673
- if isinstance(c, _ix_constraint_sig)
674
- or sqla_compat._constraint_is_named(c.const, autogen_context.dialect)
583
+ for c in metadata_unique_constraints_sig.union(metadata_indexes_sig)
584
+ if c.is_named
675
585
  }
676
586
 
677
- conn_uniques_by_name: Dict[sqla_compat._ConstraintName, _uq_constraint_sig]
678
- conn_indexes_by_name: Dict[sqla_compat._ConstraintName, _ix_constraint_sig]
587
+ conn_uniques_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig]
588
+ conn_indexes_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig]
679
589
 
680
590
  conn_uniques_by_name = {c.name: c for c in conn_unique_constraints}
681
591
  conn_indexes_by_name = {c.name: c for c in conn_indexes_sig}
@@ -694,13 +604,12 @@ def _compare_indexes_and_uniques(
694
604
 
695
605
  # 6. index things by "column signature", to help with unnamed unique
696
606
  # constraints.
697
- conn_uniques_by_sig = {uq.sig: uq for uq in conn_unique_constraints}
607
+ conn_uniques_by_sig = {uq.unnamed: uq for uq in conn_unique_constraints}
698
608
  metadata_uniques_by_sig = {
699
- uq.sig: uq for uq in metadata_unique_constraints_sig
609
+ uq.unnamed: uq for uq in metadata_unique_constraints_sig
700
610
  }
701
- metadata_indexes_by_sig = {ix.sig: ix for ix in metadata_indexes_sig}
702
611
  unnamed_metadata_uniques = {
703
- uq.sig: uq
612
+ uq.unnamed: uq
704
613
  for uq in metadata_unique_constraints_sig
705
614
  if not sqla_compat._constraint_is_named(
706
615
  uq.const, autogen_context.dialect
@@ -715,18 +624,18 @@ def _compare_indexes_and_uniques(
715
624
  # 4. The backend may double up indexes as unique constraints and
716
625
  # vice versa (e.g. MySQL, Postgresql)
717
626
 
718
- def obj_added(obj):
719
- if obj.is_index:
627
+ def obj_added(obj: _constraint_sig):
628
+ if is_index_sig(obj):
720
629
  if autogen_context.run_object_filters(
721
630
  obj.const, obj.name, "index", False, None
722
631
  ):
723
632
  modify_ops.ops.append(ops.CreateIndexOp.from_index(obj.const))
724
633
  log.info(
725
- "Detected added index '%s' on %s",
634
+ "Detected added index '%r' on '%s'",
726
635
  obj.name,
727
- ", ".join(["'%s'" % obj.column_names]),
636
+ obj.column_names,
728
637
  )
729
- else:
638
+ elif is_uq_sig(obj):
730
639
  if not supports_unique_constraints:
731
640
  # can't report unique indexes as added if we don't
732
641
  # detect them
@@ -741,13 +650,15 @@ def _compare_indexes_and_uniques(
741
650
  ops.AddConstraintOp.from_constraint(obj.const)
742
651
  )
743
652
  log.info(
744
- "Detected added unique constraint '%s' on %s",
653
+ "Detected added unique constraint %r on '%s'",
745
654
  obj.name,
746
- ", ".join(["'%s'" % obj.column_names]),
655
+ obj.column_names,
747
656
  )
657
+ else:
658
+ assert False
748
659
 
749
- def obj_removed(obj):
750
- if obj.is_index:
660
+ def obj_removed(obj: _constraint_sig):
661
+ if is_index_sig(obj):
751
662
  if obj.is_unique and not supports_unique_constraints:
752
663
  # many databases double up unique constraints
753
664
  # as unique indexes. without that list we can't
@@ -758,10 +669,8 @@ def _compare_indexes_and_uniques(
758
669
  obj.const, obj.name, "index", True, None
759
670
  ):
760
671
  modify_ops.ops.append(ops.DropIndexOp.from_index(obj.const))
761
- log.info(
762
- "Detected removed index '%s' on '%s'", obj.name, tname
763
- )
764
- else:
672
+ log.info("Detected removed index %r on %r", obj.name, tname)
673
+ elif is_uq_sig(obj):
765
674
  if is_create_table or is_drop_table:
766
675
  # if the whole table is being dropped, we don't need to
767
676
  # consider unique constraint separately
@@ -773,33 +682,40 @@ def _compare_indexes_and_uniques(
773
682
  ops.DropConstraintOp.from_constraint(obj.const)
774
683
  )
775
684
  log.info(
776
- "Detected removed unique constraint '%s' on '%s'",
685
+ "Detected removed unique constraint %r on %r",
777
686
  obj.name,
778
687
  tname,
779
688
  )
689
+ else:
690
+ assert False
691
+
692
+ def obj_changed(
693
+ old: _constraint_sig,
694
+ new: _constraint_sig,
695
+ msg: str,
696
+ ):
697
+ if is_index_sig(old):
698
+ assert is_index_sig(new)
780
699
 
781
- def obj_changed(old, new, msg):
782
- if old.is_index:
783
700
  if autogen_context.run_object_filters(
784
701
  new.const, new.name, "index", False, old.const
785
702
  ):
786
703
  log.info(
787
- "Detected changed index '%s' on '%s':%s",
788
- old.name,
789
- tname,
790
- ", ".join(msg),
704
+ "Detected changed index %r on %r: %s", old.name, tname, msg
791
705
  )
792
706
  modify_ops.ops.append(ops.DropIndexOp.from_index(old.const))
793
707
  modify_ops.ops.append(ops.CreateIndexOp.from_index(new.const))
794
- else:
708
+ elif is_uq_sig(old):
709
+ assert is_uq_sig(new)
710
+
795
711
  if autogen_context.run_object_filters(
796
712
  new.const, new.name, "unique_constraint", False, old.const
797
713
  ):
798
714
  log.info(
799
- "Detected changed unique constraint '%s' on '%s':%s",
715
+ "Detected changed unique constraint %r on %r: %s",
800
716
  old.name,
801
717
  tname,
802
- ", ".join(msg),
718
+ msg,
803
719
  )
804
720
  modify_ops.ops.append(
805
721
  ops.DropConstraintOp.from_constraint(old.const)
@@ -807,18 +723,24 @@ def _compare_indexes_and_uniques(
807
723
  modify_ops.ops.append(
808
724
  ops.AddConstraintOp.from_constraint(new.const)
809
725
  )
726
+ else:
727
+ assert False
810
728
 
811
729
  for removed_name in sorted(set(conn_names).difference(metadata_names)):
812
- conn_obj: Union[_ix_constraint_sig, _uq_constraint_sig] = conn_names[
813
- removed_name
814
- ]
815
- if not conn_obj.is_index and conn_obj.sig in unnamed_metadata_uniques:
730
+ conn_obj = conn_names[removed_name]
731
+ if (
732
+ is_uq_sig(conn_obj)
733
+ and conn_obj.unnamed in unnamed_metadata_uniques
734
+ ):
816
735
  continue
817
736
  elif removed_name in doubled_constraints:
818
737
  conn_uq, conn_idx = doubled_constraints[removed_name]
819
738
  if (
820
- conn_idx.sig not in metadata_indexes_by_sig
821
- and conn_uq.sig not in metadata_uniques_by_sig
739
+ all(
740
+ conn_idx.unnamed != meta_idx.unnamed
741
+ for meta_idx in metadata_indexes_sig
742
+ )
743
+ and conn_uq.unnamed not in metadata_uniques_by_sig
822
744
  ):
823
745
  obj_removed(conn_uq)
824
746
  obj_removed(conn_idx)
@@ -830,30 +752,36 @@ def _compare_indexes_and_uniques(
830
752
 
831
753
  if existing_name in doubled_constraints:
832
754
  conn_uq, conn_idx = doubled_constraints[existing_name]
833
- if metadata_obj.is_index:
755
+ if is_index_sig(metadata_obj):
834
756
  conn_obj = conn_idx
835
757
  else:
836
758
  conn_obj = conn_uq
837
759
  else:
838
760
  conn_obj = conn_names[existing_name]
839
761
 
840
- if conn_obj.is_index != metadata_obj.is_index:
762
+ if type(conn_obj) != type(metadata_obj):
841
763
  obj_removed(conn_obj)
842
764
  obj_added(metadata_obj)
843
765
  else:
844
- msg = []
845
- if conn_obj.is_unique != metadata_obj.is_unique:
846
- msg.append(
847
- " unique=%r to unique=%r"
848
- % (conn_obj.is_unique, metadata_obj.is_unique)
766
+ comparison = metadata_obj.compare_to_reflected(conn_obj)
767
+
768
+ if comparison.is_different:
769
+ # constraint are different
770
+ obj_changed(conn_obj, metadata_obj, comparison.message)
771
+ elif comparison.is_skip:
772
+ # constraint cannot be compared, skip them
773
+ thing = (
774
+ "index" if is_index_sig(conn_obj) else "unique constraint"
849
775
  )
850
- if conn_obj.sig != metadata_obj.sig:
851
- msg.append(
852
- " expression %r to %r" % (conn_obj.sig, metadata_obj.sig)
776
+ log.info(
777
+ "Cannot compare %s %r, assuming equal and skipping. %s",
778
+ thing,
779
+ conn_obj.name,
780
+ comparison.message,
853
781
  )
854
-
855
- if msg:
856
- obj_changed(conn_obj, metadata_obj, msg)
782
+ else:
783
+ # constraint are equal
784
+ assert comparison.is_equal
857
785
 
858
786
  for added_name in sorted(set(metadata_names).difference(conn_names)):
859
787
  obj = metadata_names[added_name]
@@ -893,7 +821,7 @@ def _correct_for_uq_duplicates_uix(
893
821
  }
894
822
 
895
823
  unnamed_metadata_uqs = {
896
- _uq_constraint_sig(cons, impl).sig
824
+ impl._create_metadata_constraint_sig(cons).unnamed
897
825
  for name, cons in metadata_cons_names
898
826
  if name is None
899
827
  }
@@ -917,7 +845,9 @@ def _correct_for_uq_duplicates_uix(
917
845
  for overlap in uqs_dupe_indexes:
918
846
  if overlap not in metadata_uq_names:
919
847
  if (
920
- _uq_constraint_sig(uqs_dupe_indexes[overlap], impl).sig
848
+ impl._create_reflected_constraint_sig(
849
+ uqs_dupe_indexes[overlap]
850
+ ).unnamed
921
851
  not in unnamed_metadata_uqs
922
852
  ):
923
853
  conn_unique_constraints.discard(uqs_dupe_indexes[overlap])
@@ -1243,8 +1173,8 @@ def _compare_foreign_keys(
1243
1173
  modify_table_ops: ModifyTableOps,
1244
1174
  schema: Optional[str],
1245
1175
  tname: Union[quoted_name, str],
1246
- conn_table: Optional[Table],
1247
- metadata_table: Optional[Table],
1176
+ conn_table: Table,
1177
+ metadata_table: Table,
1248
1178
  ) -> None:
1249
1179
  # if we're doing CREATE TABLE, all FKs are created
1250
1180
  # inline within the table def
@@ -1268,15 +1198,13 @@ def _compare_foreign_keys(
1268
1198
  )
1269
1199
  ]
1270
1200
 
1271
- backend_reflects_fk_options = bool(
1272
- conn_fks_list and "options" in conn_fks_list[0]
1273
- )
1274
-
1275
1201
  conn_fks = {
1276
1202
  _make_foreign_key(const, conn_table) # type: ignore[arg-type]
1277
1203
  for const in conn_fks_list
1278
1204
  }
1279
1205
 
1206
+ impl = autogen_context.migration_context.impl
1207
+
1280
1208
  # give the dialect a chance to correct the FKs to match more
1281
1209
  # closely
1282
1210
  autogen_context.migration_context.impl.correct_for_autogen_foreignkeys(
@@ -1284,17 +1212,24 @@ def _compare_foreign_keys(
1284
1212
  )
1285
1213
 
1286
1214
  metadata_fks_sig = {
1287
- _fk_constraint_sig(fk, include_options=backend_reflects_fk_options)
1288
- for fk in metadata_fks
1215
+ impl._create_metadata_constraint_sig(fk) for fk in metadata_fks
1289
1216
  }
1290
1217
 
1291
1218
  conn_fks_sig = {
1292
- _fk_constraint_sig(fk, include_options=backend_reflects_fk_options)
1293
- for fk in conn_fks
1219
+ impl._create_reflected_constraint_sig(fk) for fk in conn_fks
1294
1220
  }
1295
1221
 
1296
- conn_fks_by_sig = {c.sig: c for c in conn_fks_sig}
1297
- metadata_fks_by_sig = {c.sig: c for c in metadata_fks_sig}
1222
+ # check if reflected FKs include options, indicating the backend
1223
+ # can reflect FK options
1224
+ if conn_fks_list and "options" in conn_fks_list[0]:
1225
+ conn_fks_by_sig = {c.unnamed: c for c in conn_fks_sig}
1226
+ metadata_fks_by_sig = {c.unnamed: c for c in metadata_fks_sig}
1227
+ else:
1228
+ # otherwise compare by sig without options added
1229
+ conn_fks_by_sig = {c.unnamed_no_options: c for c in conn_fks_sig}
1230
+ metadata_fks_by_sig = {
1231
+ c.unnamed_no_options: c for c in metadata_fks_sig
1232
+ }
1298
1233
 
1299
1234
  metadata_fks_by_name = {
1300
1235
  c.name: c for c in metadata_fks_sig if c.name is not None
@@ -1306,7 +1241,7 @@ def _compare_foreign_keys(
1306
1241
  obj.const, obj.name, "foreign_key_constraint", False, compare_to
1307
1242
  ):
1308
1243
  modify_table_ops.ops.append(
1309
- ops.CreateForeignKeyOp.from_constraint(const.const)
1244
+ ops.CreateForeignKeyOp.from_constraint(const.const) # type: ignore[has-type] # noqa: E501
1310
1245
  )
1311
1246
 
1312
1247
  log.info(