alembic 1.12.0__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 +14 -6
- alembic/autogenerate/compare.py +129 -195
- alembic/autogenerate/render.py +42 -32
- alembic/autogenerate/rewriter.py +19 -19
- alembic/command.py +11 -9
- alembic/config.py +1 -1
- alembic/context.pyi +12 -5
- alembic/ddl/_autogen.py +323 -0
- alembic/ddl/impl.py +167 -41
- alembic/ddl/mssql.py +1 -4
- alembic/ddl/mysql.py +4 -3
- alembic/ddl/postgresql.py +157 -70
- alembic/op.pyi +9 -11
- alembic/operations/__init__.py +2 -0
- alembic/operations/base.py +10 -11
- alembic/operations/ops.py +14 -14
- alembic/operations/toimpl.py +5 -5
- alembic/runtime/environment.py +7 -5
- alembic/runtime/migration.py +4 -4
- alembic/script/base.py +25 -17
- alembic/script/revision.py +30 -25
- 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 -4
- alembic/testing/schemacompare.py +9 -0
- alembic/testing/suite/test_autogen_identity.py +23 -38
- alembic/util/compat.py +0 -1
- alembic/util/sqla_compat.py +58 -30
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/METADATA +8 -6
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/RECORD +36 -35
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/WHEEL +1 -1
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/LICENSE +0 -0
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/entry_points.txt +0 -0
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/top_level.txt +0 -0
alembic/autogenerate/render.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from collections import OrderedDict
|
4
3
|
from io import StringIO
|
5
4
|
import re
|
6
5
|
from typing import Any
|
@@ -165,14 +164,22 @@ def _render_modify_table(
|
|
165
164
|
def _render_create_table_comment(
|
166
165
|
autogen_context: AutogenContext, op: ops.CreateTableCommentOp
|
167
166
|
) -> str:
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
167
|
+
if autogen_context._has_batch:
|
168
|
+
templ = (
|
169
|
+
"{prefix}create_table_comment(\n"
|
170
|
+
"{indent}{comment},\n"
|
171
|
+
"{indent}existing_comment={existing}\n"
|
172
|
+
")"
|
173
|
+
)
|
174
|
+
else:
|
175
|
+
templ = (
|
176
|
+
"{prefix}create_table_comment(\n"
|
177
|
+
"{indent}'{tname}',\n"
|
178
|
+
"{indent}{comment},\n"
|
179
|
+
"{indent}existing_comment={existing},\n"
|
180
|
+
"{indent}schema={schema}\n"
|
181
|
+
")"
|
182
|
+
)
|
176
183
|
return templ.format(
|
177
184
|
prefix=_alembic_autogenerate_prefix(autogen_context),
|
178
185
|
tname=op.table_name,
|
@@ -189,13 +196,20 @@ def _render_create_table_comment(
|
|
189
196
|
def _render_drop_table_comment(
|
190
197
|
autogen_context: AutogenContext, op: ops.DropTableCommentOp
|
191
198
|
) -> str:
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
+
if autogen_context._has_batch:
|
200
|
+
templ = (
|
201
|
+
"{prefix}drop_table_comment(\n"
|
202
|
+
"{indent}existing_comment={existing}\n"
|
203
|
+
")"
|
204
|
+
)
|
205
|
+
else:
|
206
|
+
templ = (
|
207
|
+
"{prefix}drop_table_comment(\n"
|
208
|
+
"{indent}'{tname}',\n"
|
209
|
+
"{indent}existing_comment={existing},\n"
|
210
|
+
"{indent}schema={schema}\n"
|
211
|
+
")"
|
212
|
+
)
|
199
213
|
return templ.format(
|
200
214
|
prefix=_alembic_autogenerate_prefix(autogen_context),
|
201
215
|
tname=op.table_name,
|
@@ -246,6 +260,11 @@ def _add_table(autogen_context: AutogenContext, op: ops.CreateTableOp) -> str:
|
|
246
260
|
comment = table.comment
|
247
261
|
if comment:
|
248
262
|
text += ",\ncomment=%r" % _ident(comment)
|
263
|
+
|
264
|
+
info = table.info
|
265
|
+
if info:
|
266
|
+
text += f",\ninfo={info!r}"
|
267
|
+
|
249
268
|
for k in sorted(op.kw):
|
250
269
|
text += ",\n%s=%r" % (k.replace(" ", "_"), op.kw[k])
|
251
270
|
|
@@ -544,8 +563,10 @@ def _ident(name: Optional[Union[quoted_name, str]]) -> Optional[str]:
|
|
544
563
|
def _render_potential_expr(
|
545
564
|
value: Any,
|
546
565
|
autogen_context: AutogenContext,
|
566
|
+
*,
|
547
567
|
wrap_in_text: bool = True,
|
548
568
|
is_server_default: bool = False,
|
569
|
+
is_index: bool = False,
|
549
570
|
) -> str:
|
550
571
|
if isinstance(value, sql.ClauseElement):
|
551
572
|
if wrap_in_text:
|
@@ -556,7 +577,7 @@ def _render_potential_expr(
|
|
556
577
|
return template % {
|
557
578
|
"prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
|
558
579
|
"sql": autogen_context.migration_context.impl.render_ddl_sql_expr(
|
559
|
-
value, is_server_default=is_server_default
|
580
|
+
value, is_server_default=is_server_default, is_index=is_index
|
560
581
|
),
|
561
582
|
}
|
562
583
|
|
@@ -570,7 +591,7 @@ def _get_index_rendered_expressions(
|
|
570
591
|
return [
|
571
592
|
repr(_ident(getattr(exp, "name", None)))
|
572
593
|
if isinstance(exp, sa_schema.Column)
|
573
|
-
else _render_potential_expr(exp, autogen_context)
|
594
|
+
else _render_potential_expr(exp, autogen_context, is_index=True)
|
574
595
|
for exp in idx.expressions
|
575
596
|
]
|
576
597
|
|
@@ -760,11 +781,9 @@ def _render_computed(
|
|
760
781
|
def _render_identity(
|
761
782
|
identity: Identity, autogen_context: AutogenContext
|
762
783
|
) -> str:
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
kwargs["on_null"] = identity.on_null
|
767
|
-
kwargs.update(_get_identity_options(identity))
|
784
|
+
kwargs = sqla_compat._get_identity_options_dict(
|
785
|
+
identity, dialect_kwargs=True
|
786
|
+
)
|
768
787
|
|
769
788
|
return "%(prefix)sIdentity(%(kwargs)s)" % {
|
770
789
|
"prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
|
@@ -772,15 +791,6 @@ def _render_identity(
|
|
772
791
|
}
|
773
792
|
|
774
793
|
|
775
|
-
def _get_identity_options(identity_options: Identity) -> OrderedDict:
|
776
|
-
kwargs = OrderedDict()
|
777
|
-
for attr in sqla_compat._identity_options_attrs:
|
778
|
-
value = getattr(identity_options, attr, None)
|
779
|
-
if value is not None:
|
780
|
-
kwargs[attr] = value
|
781
|
-
return kwargs
|
782
|
-
|
783
|
-
|
784
794
|
def _repr_type(
|
785
795
|
type_: TypeEngine,
|
786
796
|
autogen_context: AutogenContext,
|
alembic/autogenerate/rewriter.py
CHANGED
@@ -9,19 +9,19 @@ from typing import Type
|
|
9
9
|
from typing import TYPE_CHECKING
|
10
10
|
from typing import Union
|
11
11
|
|
12
|
-
from
|
13
|
-
from
|
12
|
+
from .. import util
|
13
|
+
from ..operations import ops
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
16
|
+
from ..operations.ops import AddColumnOp
|
17
|
+
from ..operations.ops import AlterColumnOp
|
18
|
+
from ..operations.ops import CreateTableOp
|
19
|
+
from ..operations.ops import MigrateOperation
|
20
|
+
from ..operations.ops import MigrationScript
|
21
|
+
from ..operations.ops import ModifyTableOps
|
22
|
+
from ..operations.ops import OpContainer
|
23
|
+
from ..runtime.environment import _GetRevArg
|
24
|
+
from ..runtime.migration import MigrationContext
|
25
25
|
|
26
26
|
|
27
27
|
class Rewriter:
|
@@ -119,7 +119,7 @@ class Rewriter:
|
|
119
119
|
def _rewrite(
|
120
120
|
self,
|
121
121
|
context: MigrationContext,
|
122
|
-
revision:
|
122
|
+
revision: _GetRevArg,
|
123
123
|
directive: MigrateOperation,
|
124
124
|
) -> Iterator[MigrateOperation]:
|
125
125
|
try:
|
@@ -142,7 +142,7 @@ class Rewriter:
|
|
142
142
|
def __call__(
|
143
143
|
self,
|
144
144
|
context: MigrationContext,
|
145
|
-
revision:
|
145
|
+
revision: _GetRevArg,
|
146
146
|
directives: List[MigrationScript],
|
147
147
|
) -> None:
|
148
148
|
self.process_revision_directives(context, revision, directives)
|
@@ -153,7 +153,7 @@ class Rewriter:
|
|
153
153
|
def _traverse_script(
|
154
154
|
self,
|
155
155
|
context: MigrationContext,
|
156
|
-
revision:
|
156
|
+
revision: _GetRevArg,
|
157
157
|
directive: MigrationScript,
|
158
158
|
) -> None:
|
159
159
|
upgrade_ops_list = []
|
@@ -180,7 +180,7 @@ class Rewriter:
|
|
180
180
|
def _traverse_op_container(
|
181
181
|
self,
|
182
182
|
context: MigrationContext,
|
183
|
-
revision:
|
183
|
+
revision: _GetRevArg,
|
184
184
|
directive: OpContainer,
|
185
185
|
) -> None:
|
186
186
|
self._traverse_list(context, revision, directive.ops)
|
@@ -189,7 +189,7 @@ class Rewriter:
|
|
189
189
|
def _traverse_any_directive(
|
190
190
|
self,
|
191
191
|
context: MigrationContext,
|
192
|
-
revision:
|
192
|
+
revision: _GetRevArg,
|
193
193
|
directive: MigrateOperation,
|
194
194
|
) -> None:
|
195
195
|
pass
|
@@ -197,7 +197,7 @@ class Rewriter:
|
|
197
197
|
def _traverse_for(
|
198
198
|
self,
|
199
199
|
context: MigrationContext,
|
200
|
-
revision:
|
200
|
+
revision: _GetRevArg,
|
201
201
|
directive: MigrateOperation,
|
202
202
|
) -> Any:
|
203
203
|
directives = list(self._rewrite(context, revision, directive))
|
@@ -209,7 +209,7 @@ class Rewriter:
|
|
209
209
|
def _traverse_list(
|
210
210
|
self,
|
211
211
|
context: MigrationContext,
|
212
|
-
revision:
|
212
|
+
revision: _GetRevArg,
|
213
213
|
directives: Any,
|
214
214
|
) -> None:
|
215
215
|
dest = []
|
@@ -221,7 +221,7 @@ class Rewriter:
|
|
221
221
|
def process_revision_directives(
|
222
222
|
self,
|
223
223
|
context: MigrationContext,
|
224
|
-
revision:
|
224
|
+
revision: _GetRevArg,
|
225
225
|
directives: List[MigrationScript],
|
226
226
|
) -> None:
|
227
227
|
self._traverse_list(context, revision, directives)
|
alembic/command.py
CHANGED
@@ -14,6 +14,7 @@ from .script import ScriptDirectory
|
|
14
14
|
if TYPE_CHECKING:
|
15
15
|
from alembic.config import Config
|
16
16
|
from alembic.script.base import Script
|
17
|
+
from alembic.script.revision import _RevIdType
|
17
18
|
from .runtime.environment import ProcessRevisionDirectiveFn
|
18
19
|
|
19
20
|
|
@@ -105,7 +106,7 @@ def init(
|
|
105
106
|
os.path.join(os.path.abspath(directory), "__init__.py"),
|
106
107
|
os.path.join(os.path.abspath(versions), "__init__.py"),
|
107
108
|
]:
|
108
|
-
with util.status("Adding {path!r}", **config.messaging_opts):
|
109
|
+
with util.status(f"Adding {path!r}", **config.messaging_opts):
|
109
110
|
with open(path, "w"):
|
110
111
|
pass
|
111
112
|
|
@@ -124,7 +125,7 @@ def revision(
|
|
124
125
|
sql: bool = False,
|
125
126
|
head: str = "head",
|
126
127
|
splice: bool = False,
|
127
|
-
branch_label: Optional[
|
128
|
+
branch_label: Optional[_RevIdType] = None,
|
128
129
|
version_path: Optional[str] = None,
|
129
130
|
rev_id: Optional[str] = None,
|
130
131
|
depends_on: Optional[str] = None,
|
@@ -244,9 +245,7 @@ def revision(
|
|
244
245
|
return scripts
|
245
246
|
|
246
247
|
|
247
|
-
def check(
|
248
|
-
config: "Config",
|
249
|
-
) -> None:
|
248
|
+
def check(config: "Config") -> None:
|
250
249
|
"""Check if revision command with autogenerate has pending upgrade ops.
|
251
250
|
|
252
251
|
:param config: a :class:`.Config` object.
|
@@ -291,7 +290,10 @@ def check(
|
|
291
290
|
# the revision_context now has MigrationScript structure(s) present.
|
292
291
|
|
293
292
|
migration_script = revision_context.generated_revisions[-1]
|
294
|
-
diffs =
|
293
|
+
diffs = []
|
294
|
+
for upgrade_ops in migration_script.upgrade_ops_list:
|
295
|
+
diffs.extend(upgrade_ops.as_diffs())
|
296
|
+
|
295
297
|
if diffs:
|
296
298
|
raise util.AutogenerateDiffsDetected(
|
297
299
|
f"New upgrade operations detected: {diffs}"
|
@@ -302,9 +304,9 @@ def check(
|
|
302
304
|
|
303
305
|
def merge(
|
304
306
|
config: Config,
|
305
|
-
revisions:
|
307
|
+
revisions: _RevIdType,
|
306
308
|
message: Optional[str] = None,
|
307
|
-
branch_label: Optional[
|
309
|
+
branch_label: Optional[_RevIdType] = None,
|
308
310
|
rev_id: Optional[str] = None,
|
309
311
|
) -> Optional[Script]:
|
310
312
|
"""Merge two revisions together. Creates a new migration file.
|
@@ -623,7 +625,7 @@ def current(config: Config, verbose: bool = False) -> None:
|
|
623
625
|
|
624
626
|
def stamp(
|
625
627
|
config: Config,
|
626
|
-
revision:
|
628
|
+
revision: _RevIdType,
|
627
629
|
sql: bool = False,
|
628
630
|
tag: Optional[str] = None,
|
629
631
|
purge: bool = False,
|
alembic/config.py
CHANGED
@@ -34,7 +34,7 @@ class Config:
|
|
34
34
|
|
35
35
|
some_param = context.config.get_main_option("my option")
|
36
36
|
|
37
|
-
When invoking Alembic
|
37
|
+
When invoking Alembic programmatically, a new
|
38
38
|
:class:`.Config` can be created by passing
|
39
39
|
the name of an .ini file to the constructor::
|
40
40
|
|
alembic/context.pyi
CHANGED
@@ -7,12 +7,14 @@ from typing import Callable
|
|
7
7
|
from typing import Collection
|
8
8
|
from typing import ContextManager
|
9
9
|
from typing import Dict
|
10
|
+
from typing import Iterable
|
10
11
|
from typing import List
|
11
12
|
from typing import Literal
|
12
13
|
from typing import Mapping
|
13
14
|
from typing import MutableMapping
|
14
15
|
from typing import Optional
|
15
16
|
from typing import overload
|
17
|
+
from typing import Sequence
|
16
18
|
from typing import TextIO
|
17
19
|
from typing import Tuple
|
18
20
|
from typing import TYPE_CHECKING
|
@@ -21,7 +23,7 @@ from typing import Union
|
|
21
23
|
if TYPE_CHECKING:
|
22
24
|
from sqlalchemy.engine.base import Connection
|
23
25
|
from sqlalchemy.engine.url import URL
|
24
|
-
from sqlalchemy.sql
|
26
|
+
from sqlalchemy.sql import Executable
|
25
27
|
from sqlalchemy.sql.schema import Column
|
26
28
|
from sqlalchemy.sql.schema import FetchedValue
|
27
29
|
from sqlalchemy.sql.schema import MetaData
|
@@ -30,7 +32,7 @@ if TYPE_CHECKING:
|
|
30
32
|
|
31
33
|
from .autogenerate.api import AutogenContext
|
32
34
|
from .config import Config
|
33
|
-
from .operations.ops import
|
35
|
+
from .operations.ops import MigrationScript
|
34
36
|
from .runtime.migration import _ProxyTransaction
|
35
37
|
from .runtime.migration import MigrationContext
|
36
38
|
from .runtime.migration import MigrationInfo
|
@@ -96,7 +98,7 @@ def configure(
|
|
96
98
|
tag: Optional[str] = None,
|
97
99
|
template_args: Optional[Dict[str, Any]] = None,
|
98
100
|
render_as_batch: bool = False,
|
99
|
-
target_metadata:
|
101
|
+
target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
100
102
|
include_name: Optional[
|
101
103
|
Callable[
|
102
104
|
[
|
@@ -143,7 +145,12 @@ def configure(
|
|
143
145
|
include_schemas: bool = False,
|
144
146
|
process_revision_directives: Optional[
|
145
147
|
Callable[
|
146
|
-
[
|
148
|
+
[
|
149
|
+
MigrationContext,
|
150
|
+
Union[str, Iterable[Optional[str]], Iterable[str]],
|
151
|
+
List[MigrationScript],
|
152
|
+
],
|
153
|
+
None,
|
147
154
|
]
|
148
155
|
] = None,
|
149
156
|
compare_type: Union[
|
@@ -629,7 +636,7 @@ def configure(
|
|
629
636
|
"""
|
630
637
|
|
631
638
|
def execute(
|
632
|
-
sql: Union[
|
639
|
+
sql: Union[Executable, str], execution_options: Optional[dict] = None
|
633
640
|
) -> None:
|
634
641
|
"""Execute the given SQL using the current change context.
|
635
642
|
|
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
|