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
@@ -0,0 +1,325 @@
1
+ # mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
2
+ # mypy: no-warn-return-any, allow-any-generics
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any
7
+ from typing import ClassVar
8
+ from typing import Dict
9
+ from typing import Generic
10
+ from typing import NamedTuple
11
+ from typing import Optional
12
+ from typing import Sequence
13
+ from typing import Tuple
14
+ from typing import Type
15
+ from typing import TYPE_CHECKING
16
+ from typing import TypeVar
17
+ from typing import Union
18
+
19
+ from sqlalchemy.sql.schema import Constraint
20
+ from sqlalchemy.sql.schema import ForeignKeyConstraint
21
+ from sqlalchemy.sql.schema import Index
22
+ from sqlalchemy.sql.schema import UniqueConstraint
23
+ from typing_extensions import TypeGuard
24
+
25
+ from .. import util
26
+ from ..util import sqla_compat
27
+
28
+ if TYPE_CHECKING:
29
+ from typing import Literal
30
+
31
+ from alembic.autogenerate.api import AutogenContext
32
+ from alembic.ddl.impl import DefaultImpl
33
+
34
+ CompareConstraintType = Union[Constraint, Index]
35
+
36
+ _C = TypeVar("_C", bound=CompareConstraintType)
37
+
38
+ _clsreg: Dict[str, Type[_constraint_sig]] = {}
39
+
40
+
41
+ class ComparisonResult(NamedTuple):
42
+ status: Literal["equal", "different", "skip"]
43
+ message: str
44
+
45
+ @property
46
+ def is_equal(self) -> bool:
47
+ return self.status == "equal"
48
+
49
+ @property
50
+ def is_different(self) -> bool:
51
+ return self.status == "different"
52
+
53
+ @property
54
+ def is_skip(self) -> bool:
55
+ return self.status == "skip"
56
+
57
+ @classmethod
58
+ def Equal(cls) -> ComparisonResult:
59
+ """the constraints are equal."""
60
+ return cls("equal", "The two constraints are equal")
61
+
62
+ @classmethod
63
+ def Different(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult:
64
+ """the constraints are different for the provided reason(s)."""
65
+ return cls("different", ", ".join(util.to_list(reason)))
66
+
67
+ @classmethod
68
+ def Skip(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult:
69
+ """the constraint cannot be compared for the provided reason(s).
70
+
71
+ The message is logged, but the constraints will be otherwise
72
+ considered equal, meaning that no migration command will be
73
+ generated.
74
+ """
75
+ return cls("skip", ", ".join(util.to_list(reason)))
76
+
77
+
78
+ class _constraint_sig(Generic[_C]):
79
+ const: _C
80
+
81
+ _sig: Tuple[Any, ...]
82
+ name: Optional[sqla_compat._ConstraintNameDefined]
83
+
84
+ impl: DefaultImpl
85
+
86
+ _is_index: ClassVar[bool] = False
87
+ _is_fk: ClassVar[bool] = False
88
+ _is_uq: ClassVar[bool] = False
89
+
90
+ _is_metadata: bool
91
+
92
+ def __init_subclass__(cls) -> None:
93
+ cls._register()
94
+
95
+ @classmethod
96
+ def _register(cls):
97
+ raise NotImplementedError()
98
+
99
+ def __init__(
100
+ self, is_metadata: bool, impl: DefaultImpl, const: _C
101
+ ) -> None:
102
+ raise NotImplementedError()
103
+
104
+ def compare_to_reflected(
105
+ self, other: _constraint_sig[Any]
106
+ ) -> ComparisonResult:
107
+ assert self.impl is other.impl
108
+ assert self._is_metadata
109
+ assert not other._is_metadata
110
+
111
+ return self._compare_to_reflected(other)
112
+
113
+ def _compare_to_reflected(
114
+ self, other: _constraint_sig[_C]
115
+ ) -> ComparisonResult:
116
+ raise NotImplementedError()
117
+
118
+ @classmethod
119
+ def from_constraint(
120
+ cls, is_metadata: bool, impl: DefaultImpl, constraint: _C
121
+ ) -> _constraint_sig[_C]:
122
+ # these could be cached by constraint/impl, however, if the
123
+ # constraint is modified in place, then the sig is wrong. the mysql
124
+ # impl currently does this, and if we fixed that we can't be sure
125
+ # someone else might do it too, so play it safe.
126
+ sig = _clsreg[constraint.__visit_name__](is_metadata, impl, constraint)
127
+ return sig
128
+
129
+ def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
130
+ return sqla_compat._get_constraint_final_name(
131
+ self.const, context.dialect
132
+ )
133
+
134
+ @util.memoized_property
135
+ def is_named(self):
136
+ return sqla_compat._constraint_is_named(self.const, self.impl.dialect)
137
+
138
+ @util.memoized_property
139
+ def unnamed(self) -> Tuple[Any, ...]:
140
+ return self._sig
141
+
142
+ @util.memoized_property
143
+ def unnamed_no_options(self) -> Tuple[Any, ...]:
144
+ raise NotImplementedError()
145
+
146
+ @util.memoized_property
147
+ def _full_sig(self) -> Tuple[Any, ...]:
148
+ return (self.name,) + self.unnamed
149
+
150
+ def __eq__(self, other) -> bool:
151
+ return self._full_sig == other._full_sig
152
+
153
+ def __ne__(self, other) -> bool:
154
+ return self._full_sig != other._full_sig
155
+
156
+ def __hash__(self) -> int:
157
+ return hash(self._full_sig)
158
+
159
+
160
+ class _uq_constraint_sig(_constraint_sig[UniqueConstraint]):
161
+ _is_uq = True
162
+
163
+ @classmethod
164
+ def _register(cls) -> None:
165
+ _clsreg["unique_constraint"] = cls
166
+
167
+ is_unique = True
168
+
169
+ def __init__(
170
+ self,
171
+ is_metadata: bool,
172
+ impl: DefaultImpl,
173
+ const: UniqueConstraint,
174
+ ) -> None:
175
+ self.impl = impl
176
+ self.const = const
177
+ self.name = sqla_compat.constraint_name_or_none(const.name)
178
+ self._sig = tuple(sorted([col.name for col in const.columns]))
179
+ self._is_metadata = is_metadata
180
+
181
+ @property
182
+ def column_names(self) -> Tuple[str, ...]:
183
+ return tuple([col.name for col in self.const.columns])
184
+
185
+ def _compare_to_reflected(
186
+ self, other: _constraint_sig[_C]
187
+ ) -> ComparisonResult:
188
+ assert self._is_metadata
189
+ metadata_obj = self
190
+ conn_obj = other
191
+
192
+ assert is_uq_sig(conn_obj)
193
+ return self.impl.compare_unique_constraint(
194
+ metadata_obj.const, conn_obj.const
195
+ )
196
+
197
+
198
+ class _ix_constraint_sig(_constraint_sig[Index]):
199
+ _is_index = True
200
+
201
+ name: sqla_compat._ConstraintName
202
+
203
+ @classmethod
204
+ def _register(cls) -> None:
205
+ _clsreg["index"] = cls
206
+
207
+ def __init__(
208
+ self, is_metadata: bool, impl: DefaultImpl, const: Index
209
+ ) -> None:
210
+ self.impl = impl
211
+ self.const = const
212
+ self.name = const.name
213
+ self.is_unique = bool(const.unique)
214
+ self._is_metadata = is_metadata
215
+
216
+ def _compare_to_reflected(
217
+ self, other: _constraint_sig[_C]
218
+ ) -> ComparisonResult:
219
+ assert self._is_metadata
220
+ metadata_obj = self
221
+ conn_obj = other
222
+
223
+ assert is_index_sig(conn_obj)
224
+ return self.impl.compare_indexes(metadata_obj.const, conn_obj.const)
225
+
226
+ @util.memoized_property
227
+ def has_expressions(self):
228
+ return sqla_compat.is_expression_index(self.const)
229
+
230
+ @util.memoized_property
231
+ def column_names(self) -> Tuple[str, ...]:
232
+ return tuple([col.name for col in self.const.columns])
233
+
234
+ @util.memoized_property
235
+ def column_names_optional(self) -> Tuple[Optional[str], ...]:
236
+ return tuple(
237
+ [getattr(col, "name", None) for col in self.const.expressions]
238
+ )
239
+
240
+ @util.memoized_property
241
+ def is_named(self):
242
+ return True
243
+
244
+ @util.memoized_property
245
+ def unnamed(self):
246
+ return (self.is_unique,) + self.column_names_optional
247
+
248
+
249
+ class _fk_constraint_sig(_constraint_sig[ForeignKeyConstraint]):
250
+ _is_fk = True
251
+
252
+ @classmethod
253
+ def _register(cls) -> None:
254
+ _clsreg["foreign_key_constraint"] = cls
255
+
256
+ def __init__(
257
+ self,
258
+ is_metadata: bool,
259
+ impl: DefaultImpl,
260
+ const: ForeignKeyConstraint,
261
+ ) -> None:
262
+ self._is_metadata = is_metadata
263
+
264
+ self.impl = impl
265
+ self.const = const
266
+
267
+ self.name = sqla_compat.constraint_name_or_none(const.name)
268
+
269
+ (
270
+ self.source_schema,
271
+ self.source_table,
272
+ self.source_columns,
273
+ self.target_schema,
274
+ self.target_table,
275
+ self.target_columns,
276
+ onupdate,
277
+ ondelete,
278
+ deferrable,
279
+ initially,
280
+ ) = sqla_compat._fk_spec(const)
281
+
282
+ self._sig: Tuple[Any, ...] = (
283
+ self.source_schema,
284
+ self.source_table,
285
+ tuple(self.source_columns),
286
+ self.target_schema,
287
+ self.target_table,
288
+ tuple(self.target_columns),
289
+ ) + (
290
+ (None if onupdate.lower() == "no action" else onupdate.lower())
291
+ if onupdate
292
+ else None,
293
+ (None if ondelete.lower() == "no action" else ondelete.lower())
294
+ if ondelete
295
+ else None,
296
+ # convert initially + deferrable into one three-state value
297
+ "initially_deferrable"
298
+ if initially and initially.lower() == "deferred"
299
+ else "deferrable"
300
+ if deferrable
301
+ else "not deferrable",
302
+ )
303
+
304
+ @util.memoized_property
305
+ def unnamed_no_options(self):
306
+ return (
307
+ self.source_schema,
308
+ self.source_table,
309
+ tuple(self.source_columns),
310
+ self.target_schema,
311
+ self.target_table,
312
+ tuple(self.target_columns),
313
+ )
314
+
315
+
316
+ def is_index_sig(sig: _constraint_sig) -> TypeGuard[_ix_constraint_sig]:
317
+ return sig._is_index
318
+
319
+
320
+ def is_uq_sig(sig: _constraint_sig) -> TypeGuard[_uq_constraint_sig]:
321
+ return sig._is_uq
322
+
323
+
324
+ def is_fk_sig(sig: _constraint_sig) -> TypeGuard[_fk_constraint_sig]:
325
+ return sig._is_fk
alembic/ddl/base.py CHANGED
@@ -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 functools
@@ -173,7 +176,7 @@ class ColumnComment(AlterColumn):
173
176
  self.comment = comment
174
177
 
175
178
 
176
- @compiles(RenameTable)
179
+ @compiles(RenameTable) # type: ignore[misc]
177
180
  def visit_rename_table(
178
181
  element: RenameTable, compiler: DDLCompiler, **kw
179
182
  ) -> str:
@@ -183,7 +186,7 @@ def visit_rename_table(
183
186
  )
184
187
 
185
188
 
186
- @compiles(AddColumn)
189
+ @compiles(AddColumn) # type: ignore[misc]
187
190
  def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
188
191
  return "%s %s" % (
189
192
  alter_table(compiler, element.table_name, element.schema),
@@ -191,7 +194,7 @@ def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
191
194
  )
192
195
 
193
196
 
194
- @compiles(DropColumn)
197
+ @compiles(DropColumn) # type: ignore[misc]
195
198
  def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str:
196
199
  return "%s %s" % (
197
200
  alter_table(compiler, element.table_name, element.schema),
@@ -199,7 +202,7 @@ def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str:
199
202
  )
200
203
 
201
204
 
202
- @compiles(ColumnNullable)
205
+ @compiles(ColumnNullable) # type: ignore[misc]
203
206
  def visit_column_nullable(
204
207
  element: ColumnNullable, compiler: DDLCompiler, **kw
205
208
  ) -> str:
@@ -210,7 +213,7 @@ def visit_column_nullable(
210
213
  )
211
214
 
212
215
 
213
- @compiles(ColumnType)
216
+ @compiles(ColumnType) # type: ignore[misc]
214
217
  def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str:
215
218
  return "%s %s %s" % (
216
219
  alter_table(compiler, element.table_name, element.schema),
@@ -219,7 +222,7 @@ def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str:
219
222
  )
220
223
 
221
224
 
222
- @compiles(ColumnName)
225
+ @compiles(ColumnName) # type: ignore[misc]
223
226
  def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
224
227
  return "%s RENAME %s TO %s" % (
225
228
  alter_table(compiler, element.table_name, element.schema),
@@ -228,7 +231,7 @@ def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
228
231
  )
229
232
 
230
233
 
231
- @compiles(ColumnDefault)
234
+ @compiles(ColumnDefault) # type: ignore[misc]
232
235
  def visit_column_default(
233
236
  element: ColumnDefault, compiler: DDLCompiler, **kw
234
237
  ) -> str:
@@ -241,7 +244,7 @@ def visit_column_default(
241
244
  )
242
245
 
243
246
 
244
- @compiles(ComputedColumnDefault)
247
+ @compiles(ComputedColumnDefault) # type: ignore[misc]
245
248
  def visit_computed_column(
246
249
  element: ComputedColumnDefault, compiler: DDLCompiler, **kw
247
250
  ):
@@ -251,7 +254,7 @@ def visit_computed_column(
251
254
  )
252
255
 
253
256
 
254
- @compiles(IdentityColumnDefault)
257
+ @compiles(IdentityColumnDefault) # type: ignore[misc]
255
258
  def visit_identity_column(
256
259
  element: IdentityColumnDefault, compiler: DDLCompiler, **kw
257
260
  ):
alembic/ddl/impl.py CHANGED
@@ -1,6 +1,9 @@
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
- from collections import namedtuple
6
+ import logging
4
7
  import re
5
8
  from typing import Any
6
9
  from typing import Callable
@@ -8,6 +11,7 @@ from typing import Dict
8
11
  from typing import Iterable
9
12
  from typing import List
10
13
  from typing import Mapping
14
+ from typing import NamedTuple
11
15
  from typing import Optional
12
16
  from typing import Sequence
13
17
  from typing import Set
@@ -20,7 +24,10 @@ from sqlalchemy import cast
20
24
  from sqlalchemy import schema
21
25
  from sqlalchemy import text
22
26
 
27
+ from . import _autogen
23
28
  from . import base
29
+ from ._autogen import _constraint_sig as _constraint_sig
30
+ from ._autogen import ComparisonResult as ComparisonResult
24
31
  from .. import util
25
32
  from ..util import sqla_compat
26
33
 
@@ -50,6 +57,8 @@ if TYPE_CHECKING:
50
57
  from ..operations.batch import ApplyBatchImpl
51
58
  from ..operations.batch import BatchOperationsImpl
52
59
 
60
+ log = logging.getLogger(__name__)
61
+
53
62
 
54
63
  class ImplMeta(type):
55
64
  def __init__(
@@ -66,8 +75,6 @@ class ImplMeta(type):
66
75
 
67
76
  _impls: Dict[str, Type[DefaultImpl]] = {}
68
77
 
69
- Params = namedtuple("Params", ["token0", "tokens", "args", "kwargs"])
70
-
71
78
 
72
79
  class DefaultImpl(metaclass=ImplMeta):
73
80
 
@@ -438,6 +445,7 @@ class DefaultImpl(metaclass=ImplMeta):
438
445
  )
439
446
 
440
447
  def _tokenize_column_type(self, column: Column) -> Params:
448
+ definition: str
441
449
  definition = self.dialect.type_compiler.process(column.type).lower()
442
450
 
443
451
  # tokenize the SQLAlchemy-generated version of a type, so that
@@ -452,9 +460,9 @@ class DefaultImpl(metaclass=ImplMeta):
452
460
  # varchar character set utf8
453
461
  #
454
462
 
455
- tokens = re.findall(r"[\w\-_]+|\(.+?\)", definition)
463
+ tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition)
456
464
 
457
- term_tokens = []
465
+ term_tokens: List[str] = []
458
466
  paren_term = None
459
467
 
460
468
  for token in tokens:
@@ -466,6 +474,7 @@ class DefaultImpl(metaclass=ImplMeta):
466
474
  params = Params(term_tokens[0], term_tokens[1:], [], {})
467
475
 
468
476
  if paren_term:
477
+ term: str
469
478
  for term in re.findall("[^(),]+", paren_term):
470
479
  if "=" in term:
471
480
  key, val = term.split("=")
@@ -664,15 +673,96 @@ class DefaultImpl(metaclass=ImplMeta):
664
673
  bool(diff) or bool(metadata_identity) != bool(inspector_identity),
665
674
  )
666
675
 
667
- def create_index_sig(self, index: Index) -> Tuple[Any, ...]:
668
- # order of col matters in an index
669
- return tuple(col.name for col in index.columns)
676
+ def _compare_index_unique(
677
+ self, metadata_index: Index, reflected_index: Index
678
+ ) -> Optional[str]:
679
+ conn_unique = bool(reflected_index.unique)
680
+ meta_unique = bool(metadata_index.unique)
681
+ if conn_unique != meta_unique:
682
+ return f"unique={conn_unique} to unique={meta_unique}"
683
+ else:
684
+ return None
685
+
686
+ def _create_metadata_constraint_sig(
687
+ self, constraint: _autogen._C, **opts: Any
688
+ ) -> _constraint_sig[_autogen._C]:
689
+ return _constraint_sig.from_constraint(True, self, constraint, **opts)
690
+
691
+ def _create_reflected_constraint_sig(
692
+ self, constraint: _autogen._C, **opts: Any
693
+ ) -> _constraint_sig[_autogen._C]:
694
+ return _constraint_sig.from_constraint(False, self, constraint, **opts)
695
+
696
+ def compare_indexes(
697
+ self,
698
+ metadata_index: Index,
699
+ reflected_index: Index,
700
+ ) -> ComparisonResult:
701
+ """Compare two indexes by comparing the signature generated by
702
+ ``create_index_sig``.
703
+
704
+ This method returns a ``ComparisonResult``.
705
+ """
706
+ msg: List[str] = []
707
+ unique_msg = self._compare_index_unique(
708
+ metadata_index, reflected_index
709
+ )
710
+ if unique_msg:
711
+ msg.append(unique_msg)
712
+ m_sig = self._create_metadata_constraint_sig(metadata_index)
713
+ r_sig = self._create_reflected_constraint_sig(reflected_index)
714
+
715
+ assert _autogen.is_index_sig(m_sig)
716
+ assert _autogen.is_index_sig(r_sig)
717
+
718
+ # The assumption is that the index have no expression
719
+ for sig in m_sig, r_sig:
720
+ if sig.has_expressions:
721
+ log.warning(
722
+ "Generating approximate signature for index %s. "
723
+ "The dialect "
724
+ "implementation should either skip expression indexes "
725
+ "or provide a custom implementation.",
726
+ sig.const,
727
+ )
728
+
729
+ if m_sig.column_names != r_sig.column_names:
730
+ msg.append(
731
+ f"expression {r_sig.column_names} to {m_sig.column_names}"
732
+ )
733
+
734
+ if msg:
735
+ return ComparisonResult.Different(msg)
736
+ else:
737
+ return ComparisonResult.Equal()
738
+
739
+ def compare_unique_constraint(
740
+ self,
741
+ metadata_constraint: UniqueConstraint,
742
+ reflected_constraint: UniqueConstraint,
743
+ ) -> ComparisonResult:
744
+ """Compare two unique constraints by comparing the two signatures.
745
+
746
+ The arguments are two tuples that contain the unique constraint and
747
+ the signatures generated by ``create_unique_constraint_sig``.
748
+
749
+ This method returns a ``ComparisonResult``.
750
+ """
751
+ metadata_tup = self._create_metadata_constraint_sig(
752
+ metadata_constraint
753
+ )
754
+ reflected_tup = self._create_reflected_constraint_sig(
755
+ reflected_constraint
756
+ )
670
757
 
671
- def create_unique_constraint_sig(
672
- self, const: UniqueConstraint
673
- ) -> Tuple[Any, ...]:
674
- # order of col does not matters in an unique constraint
675
- return tuple(sorted([col.name for col in const.columns]))
758
+ meta_sig = metadata_tup.unnamed
759
+ conn_sig = reflected_tup.unnamed
760
+ if conn_sig != meta_sig:
761
+ return ComparisonResult.Different(
762
+ f"expression {conn_sig} to {meta_sig}"
763
+ )
764
+ else:
765
+ return ComparisonResult.Equal()
676
766
 
677
767
  def _skip_functional_indexes(self, metadata_indexes, conn_indexes):
678
768
  conn_indexes_by_name = {c.name: c for c in conn_indexes}
@@ -697,6 +787,13 @@ class DefaultImpl(metaclass=ImplMeta):
697
787
  return reflected_object.get("dialect_options", {})
698
788
 
699
789
 
790
+ class Params(NamedTuple):
791
+ token0: str
792
+ tokens: List[str]
793
+ args: List[str]
794
+ kwargs: Dict[str, str]
795
+
796
+
700
797
  def _compare_identity_options(
701
798
  metadata_io: Union[schema.Identity, schema.Sequence, None],
702
799
  inspector_io: Union[schema.Identity, schema.Sequence, None],
alembic/ddl/mssql.py CHANGED
@@ -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 re
@@ -9,7 +12,6 @@ from typing import TYPE_CHECKING
9
12
  from typing import Union
10
13
 
11
14
  from sqlalchemy import types as sqltypes
12
- from sqlalchemy.ext.compiler import compiles
13
15
  from sqlalchemy.schema import Column
14
16
  from sqlalchemy.schema import CreateIndex
15
17
  from sqlalchemy.sql.base import Executable
@@ -30,6 +32,7 @@ from .base import RenameTable
30
32
  from .impl import DefaultImpl
31
33
  from .. import util
32
34
  from ..util import sqla_compat
35
+ from ..util.sqla_compat import compiles
33
36
 
34
37
  if TYPE_CHECKING:
35
38
  from typing import Literal
alembic/ddl/mysql.py CHANGED
@@ -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 re
@@ -8,7 +11,6 @@ from typing import Union
8
11
 
9
12
  from sqlalchemy import schema
10
13
  from sqlalchemy import types as sqltypes
11
- from sqlalchemy.ext.compiler import compiles
12
14
 
13
15
  from .base import alter_table
14
16
  from .base import AlterColumn
@@ -20,10 +22,10 @@ from .base import format_column_name
20
22
  from .base import format_server_default
21
23
  from .impl import DefaultImpl
22
24
  from .. import util
23
- from ..autogenerate import compare
24
25
  from ..util import sqla_compat
25
26
  from ..util.sqla_compat import _is_mariadb
26
27
  from ..util.sqla_compat import _is_type_bound
28
+ from ..util.sqla_compat import compiles
27
29
 
28
30
  if TYPE_CHECKING:
29
31
  from typing import Literal
@@ -161,8 +163,7 @@ class MySQLImpl(DefaultImpl):
161
163
  ) -> bool:
162
164
  return (
163
165
  type_ is not None
164
- and type_._type_affinity # type:ignore[attr-defined]
165
- is sqltypes.DateTime
166
+ and type_._type_affinity is sqltypes.DateTime
166
167
  and server_default is not None
167
168
  )
168
169
 
@@ -272,10 +273,12 @@ class MySQLImpl(DefaultImpl):
272
273
 
273
274
  def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks):
274
275
  conn_fk_by_sig = {
275
- compare._fk_constraint_sig(fk).sig: fk for fk in conn_fks
276
+ self._create_reflected_constraint_sig(fk).unnamed_no_options: fk
277
+ for fk in conn_fks
276
278
  }
277
279
  metadata_fk_by_sig = {
278
- compare._fk_constraint_sig(fk).sig: fk for fk in metadata_fks
280
+ self._create_metadata_constraint_sig(fk).unnamed_no_options: fk
281
+ for fk in metadata_fks
279
282
  }
280
283
 
281
284
  for sig in set(conn_fk_by_sig).intersection(metadata_fk_by_sig):
alembic/ddl/oracle.py CHANGED
@@ -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 re
@@ -5,7 +8,6 @@ from typing import Any
5
8
  from typing import Optional
6
9
  from typing import TYPE_CHECKING
7
10
 
8
- from sqlalchemy.ext.compiler import compiles
9
11
  from sqlalchemy.sql import sqltypes
10
12
 
11
13
  from .base import AddColumn
@@ -22,6 +24,7 @@ from .base import format_type
22
24
  from .base import IdentityColumnDefault
23
25
  from .base import RenameTable
24
26
  from .impl import DefaultImpl
27
+ from ..util.sqla_compat import compiles
25
28
 
26
29
  if TYPE_CHECKING:
27
30
  from sqlalchemy.dialects.oracle.base import OracleDDLCompiler