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/ddl/postgresql.py
CHANGED
@@ -21,10 +21,8 @@ from sqlalchemy.dialects.postgresql import BIGINT
|
|
21
21
|
from sqlalchemy.dialects.postgresql import ExcludeConstraint
|
22
22
|
from sqlalchemy.dialects.postgresql import INTEGER
|
23
23
|
from sqlalchemy.schema import CreateIndex
|
24
|
-
from sqlalchemy.sql import operators
|
25
24
|
from sqlalchemy.sql.elements import ColumnClause
|
26
25
|
from sqlalchemy.sql.elements import TextClause
|
27
|
-
from sqlalchemy.sql.elements import UnaryExpression
|
28
26
|
from sqlalchemy.sql.functions import FunctionElement
|
29
27
|
from sqlalchemy.types import NULLTYPE
|
30
28
|
|
@@ -38,6 +36,7 @@ from .base import format_table_name
|
|
38
36
|
from .base import format_type
|
39
37
|
from .base import IdentityColumnDefault
|
40
38
|
from .base import RenameTable
|
39
|
+
from .impl import ComparisonResult
|
41
40
|
from .impl import DefaultImpl
|
42
41
|
from .. import util
|
43
42
|
from ..autogenerate import render
|
@@ -57,8 +56,8 @@ if TYPE_CHECKING:
|
|
57
56
|
from sqlalchemy.dialects.postgresql.hstore import HSTORE
|
58
57
|
from sqlalchemy.dialects.postgresql.json import JSON
|
59
58
|
from sqlalchemy.dialects.postgresql.json import JSONB
|
60
|
-
from sqlalchemy.sql.elements import BinaryExpression
|
61
59
|
from sqlalchemy.sql.elements import ClauseElement
|
60
|
+
from sqlalchemy.sql.elements import ColumnElement
|
62
61
|
from sqlalchemy.sql.elements import quoted_name
|
63
62
|
from sqlalchemy.sql.schema import MetaData
|
64
63
|
from sqlalchemy.sql.schema import Table
|
@@ -79,7 +78,6 @@ class PostgresqlImpl(DefaultImpl):
|
|
79
78
|
type_synonyms = DefaultImpl.type_synonyms + (
|
80
79
|
{"FLOAT", "DOUBLE PRECISION"},
|
81
80
|
)
|
82
|
-
identity_attrs_ignore = ("on_null", "order")
|
83
81
|
|
84
82
|
def create_index(self, index: Index, **kw: Any) -> None:
|
85
83
|
# this likely defaults to None if not present, so get()
|
@@ -253,62 +251,60 @@ class PostgresqlImpl(DefaultImpl):
|
|
253
251
|
if not sqla_compat.sqla_2:
|
254
252
|
self._skip_functional_indexes(metadata_indexes, conn_indexes)
|
255
253
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
254
|
+
# pg behavior regarding modifiers
|
255
|
+
# | # | compiled sql | returned sql | regexp. group is removed |
|
256
|
+
# | - | ---------------- | -----------------| ------------------------ |
|
257
|
+
# | 1 | nulls first | nulls first | - |
|
258
|
+
# | 2 | nulls last | | (?<! desc)( nulls last)$ |
|
259
|
+
# | 3 | asc | | ( asc)$ |
|
260
|
+
# | 4 | asc nulls first | nulls first | ( asc) nulls first$ |
|
261
|
+
# | 5 | asc nulls last | | ( asc nulls last)$ |
|
262
|
+
# | 6 | desc | desc | - |
|
263
|
+
# | 7 | desc nulls first | desc | desc( nulls first)$ |
|
264
|
+
# | 8 | desc nulls last | desc nulls last | - |
|
265
|
+
_default_modifiers_re = ( # order of case 2 and 5 matters
|
266
|
+
re.compile("( asc nulls last)$"), # case 5
|
267
|
+
re.compile("(?<! desc)( nulls last)$"), # case 2
|
268
|
+
re.compile("( asc)$"), # case 3
|
269
|
+
re.compile("( asc) nulls first$"), # case 4
|
270
|
+
re.compile(" desc( nulls first)$"), # case 7
|
271
|
+
)
|
272
|
+
|
273
|
+
def _cleanup_index_expr(self, index: Index, expr: str) -> str:
|
260
274
|
expr = expr.lower().replace('"', "").replace("'", "")
|
261
275
|
if index.table is not None:
|
262
276
|
# should not be needed, since include_table=False is in compile
|
263
277
|
expr = expr.replace(f"{index.table.name.lower()}.", "")
|
264
278
|
|
265
|
-
while expr and expr[0] == "(" and expr[-1] == ")":
|
266
|
-
expr = expr[1:-1]
|
267
279
|
if "::" in expr:
|
268
280
|
# strip :: cast. types can have spaces in them
|
269
281
|
expr = re.sub(r"(::[\w ]+\w)", "", expr)
|
270
282
|
|
271
|
-
|
272
|
-
expr = expr[
|
273
|
-
|
274
|
-
# print(f"START: {start} END: {expr}")
|
275
|
-
return expr
|
283
|
+
while expr and expr[0] == "(" and expr[-1] == ")":
|
284
|
+
expr = expr[1:-1]
|
276
285
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
if
|
281
|
-
|
282
|
-
|
283
|
-
op = exp.modifier
|
284
|
-
if isinstance(exp.element, UnaryExpression):
|
285
|
-
inner_op = exp.element.modifier
|
286
|
-
else:
|
287
|
-
inner_op = None
|
288
|
-
if inner_op is None:
|
289
|
-
if op == operators.asc_op:
|
290
|
-
# default is asc
|
291
|
-
to_remove = " asc"
|
292
|
-
elif op == operators.nullslast_op:
|
293
|
-
# default is nulls last
|
294
|
-
to_remove = " nulls last"
|
295
|
-
else:
|
296
|
-
if (
|
297
|
-
inner_op == operators.asc_op
|
298
|
-
and op == operators.nullslast_op
|
299
|
-
):
|
300
|
-
# default is asc nulls last
|
301
|
-
to_remove = " asc nulls last"
|
302
|
-
elif (
|
303
|
-
inner_op == operators.desc_op
|
304
|
-
and op == operators.nullsfirst_op
|
305
|
-
):
|
306
|
-
# default for desc is nulls first
|
307
|
-
to_remove = " nulls first"
|
286
|
+
# NOTE: when parsing the connection expression this cleanup could
|
287
|
+
# be skipped
|
288
|
+
for rs in self._default_modifiers_re:
|
289
|
+
if match := rs.search(expr):
|
290
|
+
start, end = match.span(1)
|
291
|
+
expr = expr[:start] + expr[end:]
|
308
292
|
break
|
309
|
-
return to_remove
|
310
293
|
|
311
|
-
|
294
|
+
while expr and expr[0] == "(" and expr[-1] == ")":
|
295
|
+
expr = expr[1:-1]
|
296
|
+
|
297
|
+
# strip casts
|
298
|
+
cast_re = re.compile(r"cast\s*\(")
|
299
|
+
if cast_re.match(expr):
|
300
|
+
expr = cast_re.sub("", expr)
|
301
|
+
# remove the as type
|
302
|
+
expr = re.sub(r"as\s+[^)]+\)", "", expr)
|
303
|
+
# remove spaces
|
304
|
+
expr = expr.replace(" ", "")
|
305
|
+
return expr
|
306
|
+
|
307
|
+
def _dialect_options(
|
312
308
|
self, item: Union[Index, UniqueConstraint]
|
313
309
|
) -> Tuple[Any, ...]:
|
314
310
|
# only the positive case is returned by sqlalchemy reflection so
|
@@ -317,25 +313,93 @@ class PostgresqlImpl(DefaultImpl):
|
|
317
313
|
return ("nulls_not_distinct",)
|
318
314
|
return ()
|
319
315
|
|
320
|
-
def
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
316
|
+
def compare_indexes(
|
317
|
+
self,
|
318
|
+
metadata_index: Index,
|
319
|
+
reflected_index: Index,
|
320
|
+
) -> ComparisonResult:
|
321
|
+
msg = []
|
322
|
+
unique_msg = self._compare_index_unique(
|
323
|
+
metadata_index, reflected_index
|
324
|
+
)
|
325
|
+
if unique_msg:
|
326
|
+
msg.append(unique_msg)
|
327
|
+
m_exprs = metadata_index.expressions
|
328
|
+
r_exprs = reflected_index.expressions
|
329
|
+
if len(m_exprs) != len(r_exprs):
|
330
|
+
msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}")
|
331
|
+
if msg:
|
332
|
+
# no point going further, return early
|
333
|
+
return ComparisonResult.Different(msg)
|
334
|
+
skip = []
|
335
|
+
for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1):
|
336
|
+
m_compile = self._compile_element(m_e)
|
337
|
+
m_text = self._cleanup_index_expr(metadata_index, m_compile)
|
338
|
+
# print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}")
|
339
|
+
r_compile = self._compile_element(r_e)
|
340
|
+
r_text = self._cleanup_index_expr(metadata_index, r_compile)
|
341
|
+
# print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}")
|
342
|
+
if m_text == r_text:
|
343
|
+
continue # expressions these are equal
|
344
|
+
elif m_compile.strip().endswith("_ops") and (
|
345
|
+
" " in m_compile or ")" in m_compile # is an expression
|
346
|
+
):
|
347
|
+
skip.append(
|
348
|
+
f"expression #{pos} {m_compile!r} detected "
|
349
|
+
"as including operator clause."
|
350
|
+
)
|
351
|
+
util.warn(
|
352
|
+
f"Expression #{pos} {m_compile!r} in index "
|
353
|
+
f"{reflected_index.name!r} detected to include "
|
354
|
+
"an operator clause. Expression compare cannot proceed. "
|
355
|
+
"Please move the operator clause to the "
|
356
|
+
"``postgresql_ops`` dict to enable proper compare "
|
357
|
+
"of the index expressions: "
|
358
|
+
"https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes", # noqa: E501
|
359
|
+
)
|
360
|
+
else:
|
361
|
+
msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}")
|
362
|
+
|
363
|
+
m_options = self._dialect_options(metadata_index)
|
364
|
+
r_options = self._dialect_options(reflected_index)
|
365
|
+
if m_options != r_options:
|
366
|
+
msg.extend(f"options {r_options} to {m_options}")
|
367
|
+
|
368
|
+
if msg:
|
369
|
+
return ComparisonResult.Different(msg)
|
370
|
+
elif skip:
|
371
|
+
# if there are other changes detected don't skip the index
|
372
|
+
return ComparisonResult.Skip(skip)
|
373
|
+
else:
|
374
|
+
return ComparisonResult.Equal()
|
375
|
+
|
376
|
+
def compare_unique_constraint(
|
377
|
+
self,
|
378
|
+
metadata_constraint: UniqueConstraint,
|
379
|
+
reflected_constraint: UniqueConstraint,
|
380
|
+
) -> ComparisonResult:
|
381
|
+
metadata_tup = self._create_metadata_constraint_sig(
|
382
|
+
metadata_constraint
|
383
|
+
)
|
384
|
+
reflected_tup = self._create_reflected_constraint_sig(
|
385
|
+
reflected_constraint
|
386
|
+
)
|
387
|
+
|
388
|
+
meta_sig = metadata_tup.unnamed
|
389
|
+
conn_sig = reflected_tup.unnamed
|
390
|
+
if conn_sig != meta_sig:
|
391
|
+
return ComparisonResult.Different(
|
392
|
+
f"expression {conn_sig} to {meta_sig}"
|
329
393
|
)
|
330
|
-
for e in index.expressions
|
331
|
-
) + self._dialect_sig(index)
|
332
394
|
|
333
|
-
|
334
|
-
self
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
395
|
+
metadata_do = self._dialect_options(metadata_tup.const)
|
396
|
+
conn_do = self._dialect_options(reflected_tup.const)
|
397
|
+
if metadata_do != conn_do:
|
398
|
+
return ComparisonResult.Different(
|
399
|
+
f"expression {conn_do} to {metadata_do}"
|
400
|
+
)
|
401
|
+
|
402
|
+
return ComparisonResult.Equal()
|
339
403
|
|
340
404
|
def adjust_reflected_dialect_options(
|
341
405
|
self, reflected_options: Dict[str, Any], kind: str
|
@@ -346,12 +410,37 @@ class PostgresqlImpl(DefaultImpl):
|
|
346
410
|
options.pop("postgresql_include", None)
|
347
411
|
return options
|
348
412
|
|
349
|
-
def _compile_element(self, element: ClauseElement) -> str:
|
413
|
+
def _compile_element(self, element: Union[ClauseElement, str]) -> str:
|
414
|
+
if isinstance(element, str):
|
415
|
+
return element
|
350
416
|
return element.compile(
|
351
417
|
dialect=self.dialect,
|
352
418
|
compile_kwargs={"literal_binds": True, "include_table": False},
|
353
419
|
).string
|
354
420
|
|
421
|
+
def render_ddl_sql_expr(
|
422
|
+
self,
|
423
|
+
expr: ClauseElement,
|
424
|
+
is_server_default: bool = False,
|
425
|
+
is_index: bool = False,
|
426
|
+
**kw: Any,
|
427
|
+
) -> str:
|
428
|
+
"""Render a SQL expression that is typically a server default,
|
429
|
+
index expression, etc.
|
430
|
+
|
431
|
+
"""
|
432
|
+
|
433
|
+
# apply self_group to index expressions;
|
434
|
+
# see https://github.com/sqlalchemy/sqlalchemy/blob/
|
435
|
+
# 82fa95cfce070fab401d020c6e6e4a6a96cc2578/
|
436
|
+
# lib/sqlalchemy/dialects/postgresql/base.py#L2261
|
437
|
+
if is_index and not isinstance(expr, ColumnClause):
|
438
|
+
expr = expr.self_group()
|
439
|
+
|
440
|
+
return super().render_ddl_sql_expr(
|
441
|
+
expr, is_server_default=is_server_default, is_index=is_index, **kw
|
442
|
+
)
|
443
|
+
|
355
444
|
def render_type(
|
356
445
|
self, type_: TypeEngine, autogen_context: AutogenContext
|
357
446
|
) -> Union[str, Literal[False]]:
|
@@ -513,7 +602,7 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
|
|
513
602
|
Sequence[Tuple[str, str]],
|
514
603
|
Sequence[Tuple[ColumnClause[Any], str]],
|
515
604
|
],
|
516
|
-
where: Optional[Union[
|
605
|
+
where: Optional[Union[ColumnElement[bool], str]] = None,
|
517
606
|
schema: Optional[str] = None,
|
518
607
|
_orig_constraint: Optional[ExcludeConstraint] = None,
|
519
608
|
**kw,
|
@@ -538,9 +627,7 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
|
|
538
627
|
(expr, op)
|
539
628
|
for expr, name, op in constraint._render_exprs # type:ignore[attr-defined] # noqa
|
540
629
|
],
|
541
|
-
where=cast(
|
542
|
-
"Optional[Union[BinaryExpression, str]]", constraint.where
|
543
|
-
),
|
630
|
+
where=cast("ColumnElement[bool] | None", constraint.where),
|
544
631
|
schema=constraint_table.schema,
|
545
632
|
_orig_constraint=constraint,
|
546
633
|
deferrable=constraint.deferrable,
|
alembic/op.pyi
CHANGED
@@ -19,14 +19,13 @@ from typing import TYPE_CHECKING
|
|
19
19
|
from typing import TypeVar
|
20
20
|
from typing import Union
|
21
21
|
|
22
|
-
from sqlalchemy.sql.expression import TableClause
|
23
|
-
from sqlalchemy.sql.expression import Update
|
24
|
-
|
25
22
|
if TYPE_CHECKING:
|
26
23
|
from sqlalchemy.engine import Connection
|
27
|
-
from sqlalchemy.sql
|
24
|
+
from sqlalchemy.sql import Executable
|
25
|
+
from sqlalchemy.sql.elements import ColumnElement
|
28
26
|
from sqlalchemy.sql.elements import conv
|
29
27
|
from sqlalchemy.sql.elements import TextClause
|
28
|
+
from sqlalchemy.sql.expression import TableClause
|
30
29
|
from sqlalchemy.sql.functions import Function
|
31
30
|
from sqlalchemy.sql.schema import Column
|
32
31
|
from sqlalchemy.sql.schema import Computed
|
@@ -197,7 +196,7 @@ def alter_column(
|
|
197
196
|
don't otherwise specify a new type, as well as for
|
198
197
|
when nullability is being changed on a SQL Server
|
199
198
|
column. It is also used if the type is a so-called
|
200
|
-
|
199
|
+
SQLAlchemy "schema" type which may define a constraint (i.e.
|
201
200
|
:class:`~sqlalchemy.types.Boolean`,
|
202
201
|
:class:`~sqlalchemy.types.Enum`),
|
203
202
|
so that the constraint can be dropped.
|
@@ -481,7 +480,7 @@ def bulk_insert(
|
|
481
480
|
def create_check_constraint(
|
482
481
|
constraint_name: Optional[str],
|
483
482
|
table_name: str,
|
484
|
-
condition: Union[str,
|
483
|
+
condition: Union[str, ColumnElement[bool], TextClause],
|
485
484
|
*,
|
486
485
|
schema: Optional[str] = None,
|
487
486
|
**kw: Any,
|
@@ -1024,7 +1023,7 @@ def drop_table_comment(
|
|
1024
1023
|
"""
|
1025
1024
|
|
1026
1025
|
def execute(
|
1027
|
-
sqltext: Union[
|
1026
|
+
sqltext: Union[Executable, str],
|
1028
1027
|
*,
|
1029
1028
|
execution_options: Optional[dict[str, Any]] = None,
|
1030
1029
|
) -> None:
|
@@ -1080,7 +1079,7 @@ def execute(
|
|
1080
1079
|
)
|
1081
1080
|
|
1082
1081
|
Additionally, when passing the statement as a plain string, it is first
|
1083
|
-
|
1082
|
+
coerced into a :func:`sqlalchemy.sql.expression.text` construct
|
1084
1083
|
before being passed along. In the less likely case that the
|
1085
1084
|
literal SQL string contains a colon, it must be escaped with a
|
1086
1085
|
backslash, as::
|
@@ -1093,9 +1092,8 @@ def execute(
|
|
1093
1092
|
* a string
|
1094
1093
|
* a :func:`sqlalchemy.sql.expression.text` construct.
|
1095
1094
|
* a :func:`sqlalchemy.sql.expression.insert` construct.
|
1096
|
-
* a :func:`sqlalchemy.sql.expression.update
|
1097
|
-
|
1098
|
-
or :func:`sqlalchemy.sql.expression.delete` construct.
|
1095
|
+
* a :func:`sqlalchemy.sql.expression.update` construct.
|
1096
|
+
* a :func:`sqlalchemy.sql.expression.delete` construct.
|
1099
1097
|
* Any "executable" described in SQLAlchemy Core documentation,
|
1100
1098
|
noting that no result set is returned.
|
1101
1099
|
|
alembic/operations/__init__.py
CHANGED
@@ -3,6 +3,7 @@ from .base import AbstractOperations
|
|
3
3
|
from .base import BatchOperations
|
4
4
|
from .base import Operations
|
5
5
|
from .ops import MigrateOperation
|
6
|
+
from .ops import MigrationScript
|
6
7
|
|
7
8
|
|
8
9
|
__all__ = [
|
@@ -10,4 +11,5 @@ __all__ = [
|
|
10
11
|
"Operations",
|
11
12
|
"BatchOperations",
|
12
13
|
"MigrateOperation",
|
14
|
+
"MigrationScript",
|
13
15
|
]
|
alembic/operations/base.py
CHANGED
@@ -35,10 +35,10 @@ if TYPE_CHECKING:
|
|
35
35
|
|
36
36
|
from sqlalchemy import Table
|
37
37
|
from sqlalchemy.engine import Connection
|
38
|
-
from sqlalchemy.sql
|
38
|
+
from sqlalchemy.sql import Executable
|
39
|
+
from sqlalchemy.sql.expression import ColumnElement
|
39
40
|
from sqlalchemy.sql.expression import TableClause
|
40
41
|
from sqlalchemy.sql.expression import TextClause
|
41
|
-
from sqlalchemy.sql.expression import Update
|
42
42
|
from sqlalchemy.sql.functions import Function
|
43
43
|
from sqlalchemy.sql.schema import Column
|
44
44
|
from sqlalchemy.sql.schema import Computed
|
@@ -723,7 +723,7 @@ class Operations(AbstractOperations):
|
|
723
723
|
don't otherwise specify a new type, as well as for
|
724
724
|
when nullability is being changed on a SQL Server
|
725
725
|
column. It is also used if the type is a so-called
|
726
|
-
|
726
|
+
SQLAlchemy "schema" type which may define a constraint (i.e.
|
727
727
|
:class:`~sqlalchemy.types.Boolean`,
|
728
728
|
:class:`~sqlalchemy.types.Enum`),
|
729
729
|
so that the constraint can be dropped.
|
@@ -861,7 +861,7 @@ class Operations(AbstractOperations):
|
|
861
861
|
self,
|
862
862
|
constraint_name: Optional[str],
|
863
863
|
table_name: str,
|
864
|
-
condition: Union[str,
|
864
|
+
condition: Union[str, ColumnElement[bool], TextClause],
|
865
865
|
*,
|
866
866
|
schema: Optional[str] = None,
|
867
867
|
**kw: Any,
|
@@ -1433,7 +1433,7 @@ class Operations(AbstractOperations):
|
|
1433
1433
|
|
1434
1434
|
def execute(
|
1435
1435
|
self,
|
1436
|
-
sqltext: Union[
|
1436
|
+
sqltext: Union[Executable, str],
|
1437
1437
|
*,
|
1438
1438
|
execution_options: Optional[dict[str, Any]] = None,
|
1439
1439
|
) -> None:
|
@@ -1489,7 +1489,7 @@ class Operations(AbstractOperations):
|
|
1489
1489
|
)
|
1490
1490
|
|
1491
1491
|
Additionally, when passing the statement as a plain string, it is first
|
1492
|
-
|
1492
|
+
coerced into a :func:`sqlalchemy.sql.expression.text` construct
|
1493
1493
|
before being passed along. In the less likely case that the
|
1494
1494
|
literal SQL string contains a colon, it must be escaped with a
|
1495
1495
|
backslash, as::
|
@@ -1502,9 +1502,8 @@ class Operations(AbstractOperations):
|
|
1502
1502
|
* a string
|
1503
1503
|
* a :func:`sqlalchemy.sql.expression.text` construct.
|
1504
1504
|
* a :func:`sqlalchemy.sql.expression.insert` construct.
|
1505
|
-
* a :func:`sqlalchemy.sql.expression.update
|
1506
|
-
|
1507
|
-
or :func:`sqlalchemy.sql.expression.delete` construct.
|
1505
|
+
* a :func:`sqlalchemy.sql.expression.update` construct.
|
1506
|
+
* a :func:`sqlalchemy.sql.expression.delete` construct.
|
1508
1507
|
* Any "executable" described in SQLAlchemy Core documentation,
|
1509
1508
|
noting that no result set is returned.
|
1510
1509
|
|
@@ -1635,7 +1634,7 @@ class BatchOperations(AbstractOperations):
|
|
1635
1634
|
def create_check_constraint(
|
1636
1635
|
self,
|
1637
1636
|
constraint_name: str,
|
1638
|
-
condition: Union[str,
|
1637
|
+
condition: Union[str, ColumnElement[bool], TextClause],
|
1639
1638
|
**kw: Any,
|
1640
1639
|
) -> None:
|
1641
1640
|
"""Issue a "create check constraint" instruction using the
|
@@ -1822,7 +1821,7 @@ class BatchOperations(AbstractOperations):
|
|
1822
1821
|
|
1823
1822
|
def execute(
|
1824
1823
|
self,
|
1825
|
-
sqltext: Union[
|
1824
|
+
sqltext: Union[Executable, str],
|
1826
1825
|
*,
|
1827
1826
|
execution_options: Optional[dict[str, Any]] = None,
|
1828
1827
|
) -> None:
|
alembic/operations/ops.py
CHANGED
@@ -28,9 +28,7 @@ from ..util import sqla_compat
|
|
28
28
|
if TYPE_CHECKING:
|
29
29
|
from typing import Literal
|
30
30
|
|
31
|
-
from sqlalchemy.sql
|
32
|
-
from sqlalchemy.sql.dml import Update
|
33
|
-
from sqlalchemy.sql.elements import BinaryExpression
|
31
|
+
from sqlalchemy.sql import Executable
|
34
32
|
from sqlalchemy.sql.elements import ColumnElement
|
35
33
|
from sqlalchemy.sql.elements import conv
|
36
34
|
from sqlalchemy.sql.elements import quoted_name
|
@@ -788,7 +786,7 @@ class CreateCheckConstraintOp(AddConstraintOp):
|
|
788
786
|
operations: Operations,
|
789
787
|
constraint_name: Optional[str],
|
790
788
|
table_name: str,
|
791
|
-
condition: Union[str,
|
789
|
+
condition: Union[str, ColumnElement[bool], TextClause],
|
792
790
|
*,
|
793
791
|
schema: Optional[str] = None,
|
794
792
|
**kw: Any,
|
@@ -841,7 +839,7 @@ class CreateCheckConstraintOp(AddConstraintOp):
|
|
841
839
|
cls,
|
842
840
|
operations: BatchOperations,
|
843
841
|
constraint_name: str,
|
844
|
-
condition: Union[str,
|
842
|
+
condition: Union[str, ColumnElement[bool], TextClause],
|
845
843
|
**kw: Any,
|
846
844
|
) -> None:
|
847
845
|
"""Issue a "create check constraint" instruction using the
|
@@ -901,7 +899,7 @@ class CreateIndexOp(MigrateOperation):
|
|
901
899
|
return cls(
|
902
900
|
index.name, # type: ignore[arg-type]
|
903
901
|
index.table.name,
|
904
|
-
|
902
|
+
index.expressions,
|
905
903
|
schema=index.table.schema,
|
906
904
|
unique=index.unique,
|
907
905
|
**index.kwargs,
|
@@ -1883,7 +1881,7 @@ class AlterColumnOp(AlterTableOp):
|
|
1883
1881
|
don't otherwise specify a new type, as well as for
|
1884
1882
|
when nullability is being changed on a SQL Server
|
1885
1883
|
column. It is also used if the type is a so-called
|
1886
|
-
|
1884
|
+
SQLAlchemy "schema" type which may define a constraint (i.e.
|
1887
1885
|
:class:`~sqlalchemy.types.Boolean`,
|
1888
1886
|
:class:`~sqlalchemy.types.Enum`),
|
1889
1887
|
so that the constraint can be dropped.
|
@@ -2424,7 +2422,7 @@ class ExecuteSQLOp(MigrateOperation):
|
|
2424
2422
|
|
2425
2423
|
def __init__(
|
2426
2424
|
self,
|
2427
|
-
sqltext: Union[
|
2425
|
+
sqltext: Union[Executable, str],
|
2428
2426
|
*,
|
2429
2427
|
execution_options: Optional[dict[str, Any]] = None,
|
2430
2428
|
) -> None:
|
@@ -2435,7 +2433,7 @@ class ExecuteSQLOp(MigrateOperation):
|
|
2435
2433
|
def execute(
|
2436
2434
|
cls,
|
2437
2435
|
operations: Operations,
|
2438
|
-
sqltext: Union[
|
2436
|
+
sqltext: Union[Executable, str],
|
2439
2437
|
*,
|
2440
2438
|
execution_options: Optional[dict[str, Any]] = None,
|
2441
2439
|
) -> None:
|
@@ -2491,7 +2489,7 @@ class ExecuteSQLOp(MigrateOperation):
|
|
2491
2489
|
)
|
2492
2490
|
|
2493
2491
|
Additionally, when passing the statement as a plain string, it is first
|
2494
|
-
|
2492
|
+
coerced into a :func:`sqlalchemy.sql.expression.text` construct
|
2495
2493
|
before being passed along. In the less likely case that the
|
2496
2494
|
literal SQL string contains a colon, it must be escaped with a
|
2497
2495
|
backslash, as::
|
@@ -2504,9 +2502,8 @@ class ExecuteSQLOp(MigrateOperation):
|
|
2504
2502
|
* a string
|
2505
2503
|
* a :func:`sqlalchemy.sql.expression.text` construct.
|
2506
2504
|
* a :func:`sqlalchemy.sql.expression.insert` construct.
|
2507
|
-
* a :func:`sqlalchemy.sql.expression.update
|
2508
|
-
|
2509
|
-
or :func:`sqlalchemy.sql.expression.delete` construct.
|
2505
|
+
* a :func:`sqlalchemy.sql.expression.update` construct.
|
2506
|
+
* a :func:`sqlalchemy.sql.expression.delete` construct.
|
2510
2507
|
* Any "executable" described in SQLAlchemy Core documentation,
|
2511
2508
|
noting that no result set is returned.
|
2512
2509
|
|
@@ -2527,7 +2524,7 @@ class ExecuteSQLOp(MigrateOperation):
|
|
2527
2524
|
def batch_execute(
|
2528
2525
|
cls,
|
2529
2526
|
operations: Operations,
|
2530
|
-
sqltext: Union[
|
2527
|
+
sqltext: Union[Executable, str],
|
2531
2528
|
*,
|
2532
2529
|
execution_options: Optional[dict[str, Any]] = None,
|
2533
2530
|
) -> None:
|
@@ -2542,6 +2539,9 @@ class ExecuteSQLOp(MigrateOperation):
|
|
2542
2539
|
operations, sqltext, execution_options=execution_options
|
2543
2540
|
)
|
2544
2541
|
|
2542
|
+
def to_diff_tuple(self) -> Tuple[str, Union[Executable, str]]:
|
2543
|
+
return ("execute", self.sqltext)
|
2544
|
+
|
2545
2545
|
|
2546
2546
|
class OpContainer(MigrateOperation):
|
2547
2547
|
"""Represent a sequence of operations operation."""
|
alembic/operations/toimpl.py
CHANGED
@@ -5,7 +5,7 @@ from sqlalchemy import schema as sa_schema
|
|
5
5
|
from . import ops
|
6
6
|
from .base import Operations
|
7
7
|
from ..util.sqla_compat import _copy
|
8
|
-
from ..util.sqla_compat import
|
8
|
+
from ..util.sqla_compat import sqla_14
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from sqlalchemy.sql.schema import Table
|
@@ -98,8 +98,8 @@ def create_index(
|
|
98
98
|
idx = operation.to_index(operations.migration_context)
|
99
99
|
kw = {}
|
100
100
|
if operation.if_not_exists is not None:
|
101
|
-
if not
|
102
|
-
raise NotImplementedError("SQLAlchemy
|
101
|
+
if not sqla_14:
|
102
|
+
raise NotImplementedError("SQLAlchemy 1.4+ required")
|
103
103
|
|
104
104
|
kw["if_not_exists"] = operation.if_not_exists
|
105
105
|
operations.impl.create_index(idx, **kw)
|
@@ -109,8 +109,8 @@ def create_index(
|
|
109
109
|
def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
|
110
110
|
kw = {}
|
111
111
|
if operation.if_exists is not None:
|
112
|
-
if not
|
113
|
-
raise NotImplementedError("SQLAlchemy
|
112
|
+
if not sqla_14:
|
113
|
+
raise NotImplementedError("SQLAlchemy 1.4+ required")
|
114
114
|
|
115
115
|
kw["if_exists"] = operation.if_exists
|
116
116
|
|
alembic/runtime/environment.py
CHANGED
@@ -10,6 +10,7 @@ from typing import Mapping
|
|
10
10
|
from typing import MutableMapping
|
11
11
|
from typing import Optional
|
12
12
|
from typing import overload
|
13
|
+
from typing import Sequence
|
13
14
|
from typing import TextIO
|
14
15
|
from typing import Tuple
|
15
16
|
from typing import TYPE_CHECKING
|
@@ -23,11 +24,12 @@ from .migration import _ProxyTransaction
|
|
23
24
|
from .migration import MigrationContext
|
24
25
|
from .. import util
|
25
26
|
from ..operations import Operations
|
27
|
+
from ..script.revision import _GetRevArg
|
26
28
|
|
27
29
|
if TYPE_CHECKING:
|
28
30
|
from sqlalchemy.engine import URL
|
29
31
|
from sqlalchemy.engine.base import Connection
|
30
|
-
from sqlalchemy.sql
|
32
|
+
from sqlalchemy.sql import Executable
|
31
33
|
from sqlalchemy.sql.schema import MetaData
|
32
34
|
from sqlalchemy.sql.schema import SchemaItem
|
33
35
|
from sqlalchemy.sql.type_api import TypeEngine
|
@@ -36,13 +38,13 @@ if TYPE_CHECKING:
|
|
36
38
|
from ..autogenerate.api import AutogenContext
|
37
39
|
from ..config import Config
|
38
40
|
from ..ddl import DefaultImpl
|
39
|
-
from ..operations.ops import
|
41
|
+
from ..operations.ops import MigrationScript
|
40
42
|
from ..script.base import ScriptDirectory
|
41
43
|
|
42
44
|
_RevNumber = Optional[Union[str, Tuple[str, ...]]]
|
43
45
|
|
44
46
|
ProcessRevisionDirectiveFn = Callable[
|
45
|
-
[MigrationContext,
|
47
|
+
[MigrationContext, _GetRevArg, List["MigrationScript"]], None
|
46
48
|
]
|
47
49
|
|
48
50
|
RenderItemFn = Callable[
|
@@ -415,7 +417,7 @@ class EnvironmentContext(util.ModuleClsProxy):
|
|
415
417
|
tag: Optional[str] = None,
|
416
418
|
template_args: Optional[Dict[str, Any]] = None,
|
417
419
|
render_as_batch: bool = False,
|
418
|
-
target_metadata:
|
420
|
+
target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
419
421
|
include_name: Optional[IncludeNameFn] = None,
|
420
422
|
include_object: Optional[IncludeObjectFn] = None,
|
421
423
|
include_schemas: bool = False,
|
@@ -938,7 +940,7 @@ class EnvironmentContext(util.ModuleClsProxy):
|
|
938
940
|
|
939
941
|
def execute(
|
940
942
|
self,
|
941
|
-
sql: Union[
|
943
|
+
sql: Union[Executable, str],
|
942
944
|
execution_options: Optional[dict] = None,
|
943
945
|
) -> None:
|
944
946
|
"""Execute the given SQL using the current change context.
|