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.
- alembic/__init__.py +1 -3
- alembic/autogenerate/__init__.py +10 -10
- alembic/autogenerate/api.py +8 -5
- alembic/autogenerate/compare.py +134 -199
- alembic/autogenerate/render.py +39 -24
- alembic/autogenerate/rewriter.py +26 -13
- alembic/command.py +7 -2
- alembic/config.py +20 -9
- alembic/context.pyi +12 -6
- alembic/ddl/__init__.py +1 -1
- alembic/ddl/_autogen.py +325 -0
- alembic/ddl/base.py +12 -9
- alembic/ddl/impl.py +110 -13
- alembic/ddl/mssql.py +4 -1
- alembic/ddl/mysql.py +9 -6
- alembic/ddl/oracle.py +4 -1
- alembic/ddl/postgresql.py +147 -73
- alembic/ddl/sqlite.py +8 -6
- alembic/op.pyi +46 -8
- alembic/operations/base.py +70 -14
- alembic/operations/batch.py +7 -8
- alembic/operations/ops.py +53 -31
- alembic/operations/schemaobj.py +5 -4
- alembic/operations/toimpl.py +8 -5
- alembic/runtime/environment.py +17 -7
- alembic/runtime/migration.py +27 -11
- alembic/script/base.py +34 -27
- alembic/script/revision.py +30 -17
- alembic/script/write_hooks.py +3 -0
- 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/__init__.py +31 -31
- alembic/util/compat.py +25 -9
- alembic/util/langhelpers.py +78 -33
- alembic/util/messaging.py +6 -3
- alembic/util/pyfiles.py +7 -3
- alembic/util/sqla_compat.py +52 -26
- {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/METADATA +5 -4
- alembic-1.13.1.dist-info/RECORD +83 -0
- {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/WHEEL +1 -1
- alembic-1.12.1.dist-info/RECORD +0 -82
- {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/LICENSE +0 -0
- {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/entry_points.txt +0 -0
- {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/top_level.txt +0 -0
alembic/ddl/_autogen.py
ADDED
@@ -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
|
-
|
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
|
668
|
-
|
669
|
-
|
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
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|