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