alembic 1.12.1__py3-none-any.whl → 1.13.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. alembic/__init__.py +1 -3
  2. alembic/autogenerate/__init__.py +10 -10
  3. alembic/autogenerate/api.py +8 -5
  4. alembic/autogenerate/compare.py +134 -199
  5. alembic/autogenerate/render.py +39 -24
  6. alembic/autogenerate/rewriter.py +26 -13
  7. alembic/command.py +7 -2
  8. alembic/config.py +20 -9
  9. alembic/context.pyi +12 -6
  10. alembic/ddl/__init__.py +1 -1
  11. alembic/ddl/_autogen.py +325 -0
  12. alembic/ddl/base.py +12 -9
  13. alembic/ddl/impl.py +110 -13
  14. alembic/ddl/mssql.py +4 -1
  15. alembic/ddl/mysql.py +9 -6
  16. alembic/ddl/oracle.py +4 -1
  17. alembic/ddl/postgresql.py +147 -73
  18. alembic/ddl/sqlite.py +8 -6
  19. alembic/op.pyi +46 -8
  20. alembic/operations/base.py +70 -14
  21. alembic/operations/batch.py +7 -8
  22. alembic/operations/ops.py +53 -31
  23. alembic/operations/schemaobj.py +5 -4
  24. alembic/operations/toimpl.py +8 -5
  25. alembic/runtime/environment.py +17 -7
  26. alembic/runtime/migration.py +27 -11
  27. alembic/script/base.py +34 -27
  28. alembic/script/revision.py +30 -17
  29. alembic/script/write_hooks.py +3 -0
  30. alembic/templates/async/alembic.ini.mako +3 -3
  31. alembic/templates/generic/alembic.ini.mako +3 -3
  32. alembic/templates/multidb/alembic.ini.mako +3 -3
  33. alembic/testing/requirements.py +12 -0
  34. alembic/testing/schemacompare.py +9 -0
  35. alembic/util/__init__.py +31 -31
  36. alembic/util/compat.py +25 -9
  37. alembic/util/langhelpers.py +78 -33
  38. alembic/util/messaging.py +6 -3
  39. alembic/util/pyfiles.py +7 -3
  40. alembic/util/sqla_compat.py +52 -26
  41. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/METADATA +5 -4
  42. alembic-1.13.1.dist-info/RECORD +83 -0
  43. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/WHEEL +1 -1
  44. alembic-1.12.1.dist-info/RECORD +0 -82
  45. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/LICENSE +0 -0
  46. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/entry_points.txt +0 -0
  47. {alembic-1.12.1.dist-info → alembic-1.13.1.dist-info}/top_level.txt +0 -0
alembic/ddl/postgresql.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 logging
@@ -21,10 +24,8 @@ from sqlalchemy.dialects.postgresql import BIGINT
21
24
  from sqlalchemy.dialects.postgresql import ExcludeConstraint
22
25
  from sqlalchemy.dialects.postgresql import INTEGER
23
26
  from sqlalchemy.schema import CreateIndex
24
- from sqlalchemy.sql import operators
25
27
  from sqlalchemy.sql.elements import ColumnClause
26
28
  from sqlalchemy.sql.elements import TextClause
27
- from sqlalchemy.sql.elements import UnaryExpression
28
29
  from sqlalchemy.sql.functions import FunctionElement
29
30
  from sqlalchemy.types import NULLTYPE
30
31
 
@@ -32,12 +33,12 @@ from .base import alter_column
32
33
  from .base import alter_table
33
34
  from .base import AlterColumn
34
35
  from .base import ColumnComment
35
- from .base import compiles
36
36
  from .base import format_column_name
37
37
  from .base import format_table_name
38
38
  from .base import format_type
39
39
  from .base import IdentityColumnDefault
40
40
  from .base import RenameTable
41
+ from .impl import ComparisonResult
41
42
  from .impl import DefaultImpl
42
43
  from .. import util
43
44
  from ..autogenerate import render
@@ -46,6 +47,7 @@ from ..operations import schemaobj
46
47
  from ..operations.base import BatchOperations
47
48
  from ..operations.base import Operations
48
49
  from ..util import sqla_compat
50
+ from ..util.sqla_compat import compiles
49
51
 
50
52
  if TYPE_CHECKING:
51
53
  from typing import Literal
@@ -137,7 +139,9 @@ class PostgresqlImpl(DefaultImpl):
137
139
  metadata_default = literal_column(metadata_default)
138
140
 
139
141
  # run a real compare against the server
140
- return not self.connection.scalar(
142
+ conn = self.connection
143
+ assert conn is not None
144
+ return not conn.scalar(
141
145
  sqla_compat._select(
142
146
  literal_column(conn_col_default) == metadata_default
143
147
  )
@@ -252,62 +256,60 @@ class PostgresqlImpl(DefaultImpl):
252
256
  if not sqla_compat.sqla_2:
253
257
  self._skip_functional_indexes(metadata_indexes, conn_indexes)
254
258
 
255
- def _cleanup_index_expr(
256
- self, index: Index, expr: str, remove_suffix: str
257
- ) -> str:
258
- # start = expr
259
+ # pg behavior regarding modifiers
260
+ # | # | compiled sql | returned sql | regexp. group is removed |
261
+ # | - | ---------------- | -----------------| ------------------------ |
262
+ # | 1 | nulls first | nulls first | - |
263
+ # | 2 | nulls last | | (?<! desc)( nulls last)$ |
264
+ # | 3 | asc | | ( asc)$ |
265
+ # | 4 | asc nulls first | nulls first | ( asc) nulls first$ |
266
+ # | 5 | asc nulls last | | ( asc nulls last)$ |
267
+ # | 6 | desc | desc | - |
268
+ # | 7 | desc nulls first | desc | desc( nulls first)$ |
269
+ # | 8 | desc nulls last | desc nulls last | - |
270
+ _default_modifiers_re = ( # order of case 2 and 5 matters
271
+ re.compile("( asc nulls last)$"), # case 5
272
+ re.compile("(?<! desc)( nulls last)$"), # case 2
273
+ re.compile("( asc)$"), # case 3
274
+ re.compile("( asc) nulls first$"), # case 4
275
+ re.compile(" desc( nulls first)$"), # case 7
276
+ )
277
+
278
+ def _cleanup_index_expr(self, index: Index, expr: str) -> str:
259
279
  expr = expr.lower().replace('"', "").replace("'", "")
260
280
  if index.table is not None:
261
281
  # should not be needed, since include_table=False is in compile
262
282
  expr = expr.replace(f"{index.table.name.lower()}.", "")
263
283
 
264
- while expr and expr[0] == "(" and expr[-1] == ")":
265
- expr = expr[1:-1]
266
284
  if "::" in expr:
267
285
  # strip :: cast. types can have spaces in them
268
286
  expr = re.sub(r"(::[\w ]+\w)", "", expr)
269
287
 
270
- if remove_suffix and expr.endswith(remove_suffix):
271
- expr = expr[: -len(remove_suffix)]
272
-
273
- # print(f"START: {start} END: {expr}")
274
- return expr
288
+ while expr and expr[0] == "(" and expr[-1] == ")":
289
+ expr = expr[1:-1]
275
290
 
276
- def _default_modifiers(self, exp: ClauseElement) -> str:
277
- to_remove = ""
278
- while isinstance(exp, UnaryExpression):
279
- if exp.modifier is None:
280
- exp = exp.element
281
- else:
282
- op = exp.modifier
283
- if isinstance(exp.element, UnaryExpression):
284
- inner_op = exp.element.modifier
285
- else:
286
- inner_op = None
287
- if inner_op is None:
288
- if op == operators.asc_op:
289
- # default is asc
290
- to_remove = " asc"
291
- elif op == operators.nullslast_op:
292
- # default is nulls last
293
- to_remove = " nulls last"
294
- else:
295
- if (
296
- inner_op == operators.asc_op
297
- and op == operators.nullslast_op
298
- ):
299
- # default is asc nulls last
300
- to_remove = " asc nulls last"
301
- elif (
302
- inner_op == operators.desc_op
303
- and op == operators.nullsfirst_op
304
- ):
305
- # default for desc is nulls first
306
- to_remove = " nulls first"
291
+ # NOTE: when parsing the connection expression this cleanup could
292
+ # be skipped
293
+ for rs in self._default_modifiers_re:
294
+ if match := rs.search(expr):
295
+ start, end = match.span(1)
296
+ expr = expr[:start] + expr[end:]
307
297
  break
308
- return to_remove
309
298
 
310
- def _dialect_sig(
299
+ while expr and expr[0] == "(" and expr[-1] == ")":
300
+ expr = expr[1:-1]
301
+
302
+ # strip casts
303
+ cast_re = re.compile(r"cast\s*\(")
304
+ if cast_re.match(expr):
305
+ expr = cast_re.sub("", expr)
306
+ # remove the as type
307
+ expr = re.sub(r"as\s+[^)]+\)", "", expr)
308
+ # remove spaces
309
+ expr = expr.replace(" ", "")
310
+ return expr
311
+
312
+ def _dialect_options(
311
313
  self, item: Union[Index, UniqueConstraint]
312
314
  ) -> Tuple[Any, ...]:
313
315
  # only the positive case is returned by sqlalchemy reflection so
@@ -316,25 +318,93 @@ class PostgresqlImpl(DefaultImpl):
316
318
  return ("nulls_not_distinct",)
317
319
  return ()
318
320
 
319
- def create_index_sig(self, index: Index) -> Tuple[Any, ...]:
320
- return tuple(
321
- self._cleanup_index_expr(
322
- index,
323
- *(
324
- (e, "")
325
- if isinstance(e, str)
326
- else (self._compile_element(e), self._default_modifiers(e))
327
- ),
321
+ def compare_indexes(
322
+ self,
323
+ metadata_index: Index,
324
+ reflected_index: Index,
325
+ ) -> ComparisonResult:
326
+ msg = []
327
+ unique_msg = self._compare_index_unique(
328
+ metadata_index, reflected_index
329
+ )
330
+ if unique_msg:
331
+ msg.append(unique_msg)
332
+ m_exprs = metadata_index.expressions
333
+ r_exprs = reflected_index.expressions
334
+ if len(m_exprs) != len(r_exprs):
335
+ msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}")
336
+ if msg:
337
+ # no point going further, return early
338
+ return ComparisonResult.Different(msg)
339
+ skip = []
340
+ for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1):
341
+ m_compile = self._compile_element(m_e)
342
+ m_text = self._cleanup_index_expr(metadata_index, m_compile)
343
+ # print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}")
344
+ r_compile = self._compile_element(r_e)
345
+ r_text = self._cleanup_index_expr(metadata_index, r_compile)
346
+ # print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}")
347
+ if m_text == r_text:
348
+ continue # expressions these are equal
349
+ elif m_compile.strip().endswith("_ops") and (
350
+ " " in m_compile or ")" in m_compile # is an expression
351
+ ):
352
+ skip.append(
353
+ f"expression #{pos} {m_compile!r} detected "
354
+ "as including operator clause."
355
+ )
356
+ util.warn(
357
+ f"Expression #{pos} {m_compile!r} in index "
358
+ f"{reflected_index.name!r} detected to include "
359
+ "an operator clause. Expression compare cannot proceed. "
360
+ "Please move the operator clause to the "
361
+ "``postgresql_ops`` dict to enable proper compare "
362
+ "of the index expressions: "
363
+ "https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes", # noqa: E501
364
+ )
365
+ else:
366
+ msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}")
367
+
368
+ m_options = self._dialect_options(metadata_index)
369
+ r_options = self._dialect_options(reflected_index)
370
+ if m_options != r_options:
371
+ msg.extend(f"options {r_options} to {m_options}")
372
+
373
+ if msg:
374
+ return ComparisonResult.Different(msg)
375
+ elif skip:
376
+ # if there are other changes detected don't skip the index
377
+ return ComparisonResult.Skip(skip)
378
+ else:
379
+ return ComparisonResult.Equal()
380
+
381
+ def compare_unique_constraint(
382
+ self,
383
+ metadata_constraint: UniqueConstraint,
384
+ reflected_constraint: UniqueConstraint,
385
+ ) -> ComparisonResult:
386
+ metadata_tup = self._create_metadata_constraint_sig(
387
+ metadata_constraint
388
+ )
389
+ reflected_tup = self._create_reflected_constraint_sig(
390
+ reflected_constraint
391
+ )
392
+
393
+ meta_sig = metadata_tup.unnamed
394
+ conn_sig = reflected_tup.unnamed
395
+ if conn_sig != meta_sig:
396
+ return ComparisonResult.Different(
397
+ f"expression {conn_sig} to {meta_sig}"
328
398
  )
329
- for e in index.expressions
330
- ) + self._dialect_sig(index)
331
399
 
332
- def create_unique_constraint_sig(
333
- self, const: UniqueConstraint
334
- ) -> Tuple[Any, ...]:
335
- return tuple(
336
- sorted([col.name for col in const.columns])
337
- ) + self._dialect_sig(const)
400
+ metadata_do = self._dialect_options(metadata_tup.const)
401
+ conn_do = self._dialect_options(reflected_tup.const)
402
+ if metadata_do != conn_do:
403
+ return ComparisonResult.Different(
404
+ f"expression {conn_do} to {metadata_do}"
405
+ )
406
+
407
+ return ComparisonResult.Equal()
338
408
 
339
409
  def adjust_reflected_dialect_options(
340
410
  self, reflected_options: Dict[str, Any], kind: str
@@ -345,7 +415,9 @@ class PostgresqlImpl(DefaultImpl):
345
415
  options.pop("postgresql_include", None)
346
416
  return options
347
417
 
348
- def _compile_element(self, element: ClauseElement) -> str:
418
+ def _compile_element(self, element: Union[ClauseElement, str]) -> str:
419
+ if isinstance(element, str):
420
+ return element
349
421
  return element.compile(
350
422
  dialect=self.dialect,
351
423
  compile_kwargs={"literal_binds": True, "include_table": False},
@@ -556,9 +628,8 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
556
628
  return cls(
557
629
  constraint.name,
558
630
  constraint_table.name,
559
- [
560
- (expr, op)
561
- for expr, name, op in constraint._render_exprs # type:ignore[attr-defined] # noqa
631
+ [ # type: ignore
632
+ (expr, op) for expr, name, op in constraint._render_exprs
562
633
  ],
563
634
  where=cast("ColumnElement[bool] | None", constraint.where),
564
635
  schema=constraint_table.schema,
@@ -585,7 +656,7 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
585
656
  expr,
586
657
  name,
587
658
  oper,
588
- ) in excl._render_exprs: # type:ignore[attr-defined]
659
+ ) in excl._render_exprs:
589
660
  t.append_column(Column(name, NULLTYPE))
590
661
  t.append_constraint(excl)
591
662
  return excl
@@ -643,7 +714,7 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
643
714
  constraint_name: str,
644
715
  *elements: Any,
645
716
  **kw: Any,
646
- ):
717
+ ) -> Optional[Table]:
647
718
  """Issue a "create exclude constraint" instruction using the
648
719
  current batch migration context.
649
720
 
@@ -715,10 +786,13 @@ def _exclude_constraint(
715
786
  args = [
716
787
  "(%s, %r)"
717
788
  % (
718
- _render_potential_column(sqltext, autogen_context),
789
+ _render_potential_column(
790
+ sqltext, # type:ignore[arg-type]
791
+ autogen_context,
792
+ ),
719
793
  opstring,
720
794
  )
721
- for sqltext, name, opstring in constraint._render_exprs # type:ignore[attr-defined] # noqa
795
+ for sqltext, name, opstring in constraint._render_exprs
722
796
  ]
723
797
  if constraint.where is not None:
724
798
  args.append(
alembic/ddl/sqlite.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
@@ -11,13 +14,13 @@ from sqlalchemy import cast
11
14
  from sqlalchemy import JSON
12
15
  from sqlalchemy import schema
13
16
  from sqlalchemy import sql
14
- from sqlalchemy.ext.compiler import compiles
15
17
 
16
18
  from .base import alter_table
17
19
  from .base import format_table_name
18
20
  from .base import RenameTable
19
21
  from .impl import DefaultImpl
20
22
  from .. import util
23
+ from ..util.sqla_compat import compiles
21
24
 
22
25
  if TYPE_CHECKING:
23
26
  from sqlalchemy.engine.reflection import Inspector
@@ -71,13 +74,13 @@ class SQLiteImpl(DefaultImpl):
71
74
  def add_constraint(self, const: Constraint):
72
75
  # attempt to distinguish between an
73
76
  # auto-gen constraint and an explicit one
74
- if const._create_rule is None: # type:ignore[attr-defined]
77
+ if const._create_rule is None:
75
78
  raise NotImplementedError(
76
79
  "No support for ALTER of constraints in SQLite dialect. "
77
80
  "Please refer to the batch mode feature which allows for "
78
81
  "SQLite migrations using a copy-and-move strategy."
79
82
  )
80
- elif const._create_rule(self): # type:ignore[attr-defined]
83
+ elif const._create_rule(self):
81
84
  util.warn(
82
85
  "Skipping unsupported ALTER for "
83
86
  "creation of implicit constraint. "
@@ -86,7 +89,7 @@ class SQLiteImpl(DefaultImpl):
86
89
  )
87
90
 
88
91
  def drop_constraint(self, const: Constraint):
89
- if const._create_rule is None: # type:ignore[attr-defined]
92
+ if const._create_rule is None:
90
93
  raise NotImplementedError(
91
94
  "No support for ALTER of constraints in SQLite dialect. "
92
95
  "Please refer to the batch mode feature which allows for "
@@ -177,8 +180,7 @@ class SQLiteImpl(DefaultImpl):
177
180
  new_type: TypeEngine,
178
181
  ) -> None:
179
182
  if (
180
- existing.type._type_affinity # type:ignore[attr-defined]
181
- is not new_type._type_affinity # type:ignore[attr-defined]
183
+ existing.type._type_affinity is not new_type._type_affinity
182
184
  and not isinstance(new_type, JSON)
183
185
  ):
184
186
  existing_transfer["expr"] = cast(
alembic/op.pyi CHANGED
@@ -12,6 +12,7 @@ from typing import List
12
12
  from typing import Literal
13
13
  from typing import Mapping
14
14
  from typing import Optional
15
+ from typing import overload
15
16
  from typing import Sequence
16
17
  from typing import Tuple
17
18
  from typing import Type
@@ -35,12 +36,28 @@ if TYPE_CHECKING:
35
36
  from sqlalchemy.sql.type_api import TypeEngine
36
37
  from sqlalchemy.util import immutabledict
37
38
 
38
- from .operations.ops import BatchOperations
39
+ from .operations.base import BatchOperations
40
+ from .operations.ops import AddColumnOp
41
+ from .operations.ops import AddConstraintOp
42
+ from .operations.ops import AlterColumnOp
43
+ from .operations.ops import AlterTableOp
44
+ from .operations.ops import BulkInsertOp
45
+ from .operations.ops import CreateIndexOp
46
+ from .operations.ops import CreateTableCommentOp
47
+ from .operations.ops import CreateTableOp
48
+ from .operations.ops import DropColumnOp
49
+ from .operations.ops import DropConstraintOp
50
+ from .operations.ops import DropIndexOp
51
+ from .operations.ops import DropTableCommentOp
52
+ from .operations.ops import DropTableOp
53
+ from .operations.ops import ExecuteSQLOp
39
54
  from .operations.ops import MigrateOperation
40
55
  from .runtime.migration import MigrationContext
41
56
  from .util.sqla_compat import _literal_bindparam
42
57
 
43
58
  _T = TypeVar("_T")
59
+ _C = TypeVar("_C", bound=Callable[..., Any])
60
+
44
61
  ### end imports ###
45
62
 
46
63
  def add_column(
@@ -132,8 +149,8 @@ def alter_column(
132
149
  comment: Union[str, Literal[False], None] = False,
133
150
  server_default: Any = False,
134
151
  new_column_name: Optional[str] = None,
135
- type_: Union[TypeEngine, Type[TypeEngine], None] = None,
136
- existing_type: Union[TypeEngine, Type[TypeEngine], None] = None,
152
+ type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
153
+ existing_type: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
137
154
  existing_server_default: Union[
138
155
  str, bool, Identity, Computed, None
139
156
  ] = False,
@@ -230,7 +247,7 @@ def batch_alter_table(
230
247
  table_name: str,
231
248
  schema: Optional[str] = None,
232
249
  recreate: Literal["auto", "always", "never"] = "auto",
233
- partial_reordering: Optional[tuple] = None,
250
+ partial_reordering: Optional[Tuple[Any, ...]] = None,
234
251
  copy_from: Optional[Table] = None,
235
252
  table_args: Tuple[Any, ...] = (),
236
253
  table_kwargs: Mapping[str, Any] = immutabledict({}),
@@ -377,7 +394,7 @@ def batch_alter_table(
377
394
 
378
395
  def bulk_insert(
379
396
  table: Union[Table, TableClause],
380
- rows: List[dict],
397
+ rows: List[Dict[str, Any]],
381
398
  *,
382
399
  multiinsert: bool = True,
383
400
  ) -> None:
@@ -1162,7 +1179,7 @@ def get_context() -> MigrationContext:
1162
1179
 
1163
1180
  """
1164
1181
 
1165
- def implementation_for(op_cls: Any) -> Callable[..., Any]:
1182
+ def implementation_for(op_cls: Any) -> Callable[[_C], _C]:
1166
1183
  """Register an implementation for a given :class:`.MigrateOperation`.
1167
1184
 
1168
1185
  This is part of the operation extensibility API.
@@ -1174,7 +1191,7 @@ def implementation_for(op_cls: Any) -> Callable[..., Any]:
1174
1191
  """
1175
1192
 
1176
1193
  def inline_literal(
1177
- value: Union[str, int], type_: Optional[TypeEngine] = None
1194
+ value: Union[str, int], type_: Optional[TypeEngine[Any]] = None
1178
1195
  ) -> _literal_bindparam:
1179
1196
  r"""Produce an 'inline literal' expression, suitable for
1180
1197
  using in an INSERT, UPDATE, or DELETE statement.
@@ -1218,6 +1235,27 @@ def inline_literal(
1218
1235
 
1219
1236
  """
1220
1237
 
1238
+ @overload
1239
+ def invoke(operation: CreateTableOp) -> Table: ...
1240
+ @overload
1241
+ def invoke(
1242
+ operation: Union[
1243
+ AddConstraintOp,
1244
+ DropConstraintOp,
1245
+ CreateIndexOp,
1246
+ DropIndexOp,
1247
+ AddColumnOp,
1248
+ AlterColumnOp,
1249
+ AlterTableOp,
1250
+ CreateTableCommentOp,
1251
+ DropTableCommentOp,
1252
+ DropColumnOp,
1253
+ BulkInsertOp,
1254
+ DropTableOp,
1255
+ ExecuteSQLOp,
1256
+ ]
1257
+ ) -> None: ...
1258
+ @overload
1221
1259
  def invoke(operation: MigrateOperation) -> Any:
1222
1260
  """Given a :class:`.MigrateOperation`, invoke it in terms of
1223
1261
  this :class:`.Operations` instance.
@@ -1226,7 +1264,7 @@ def invoke(operation: MigrateOperation) -> Any:
1226
1264
 
1227
1265
  def register_operation(
1228
1266
  name: str, sourcename: Optional[str] = None
1229
- ) -> Callable[[_T], _T]:
1267
+ ) -> Callable[[Type[_T]], Type[_T]]:
1230
1268
  """Register a new operation for this class.
1231
1269
 
1232
1270
  This method is normally used to add new operations
@@ -1,3 +1,5 @@
1
+ # mypy: allow-untyped-calls
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  from contextlib import contextmanager
@@ -10,7 +12,9 @@ from typing import Dict
10
12
  from typing import Iterator
11
13
  from typing import List # noqa
12
14
  from typing import Mapping
15
+ from typing import NoReturn
13
16
  from typing import Optional
17
+ from typing import overload
14
18
  from typing import Sequence # noqa
15
19
  from typing import Tuple
16
20
  from typing import Type # noqa
@@ -47,12 +51,28 @@ if TYPE_CHECKING:
47
51
  from sqlalchemy.types import TypeEngine
48
52
 
49
53
  from .batch import BatchOperationsImpl
54
+ from .ops import AddColumnOp
55
+ from .ops import AddConstraintOp
56
+ from .ops import AlterColumnOp
57
+ from .ops import AlterTableOp
58
+ from .ops import BulkInsertOp
59
+ from .ops import CreateIndexOp
60
+ from .ops import CreateTableCommentOp
61
+ from .ops import CreateTableOp
62
+ from .ops import DropColumnOp
63
+ from .ops import DropConstraintOp
64
+ from .ops import DropIndexOp
65
+ from .ops import DropTableCommentOp
66
+ from .ops import DropTableOp
67
+ from .ops import ExecuteSQLOp
50
68
  from .ops import MigrateOperation
51
69
  from ..ddl import DefaultImpl
52
70
  from ..runtime.migration import MigrationContext
53
71
  __all__ = ("Operations", "BatchOperations")
54
72
  _T = TypeVar("_T")
55
73
 
74
+ _C = TypeVar("_C", bound=Callable[..., Any])
75
+
56
76
 
57
77
  class AbstractOperations(util.ModuleClsProxy):
58
78
  """Base class for Operations and BatchOperations.
@@ -86,7 +106,7 @@ class AbstractOperations(util.ModuleClsProxy):
86
106
  @classmethod
87
107
  def register_operation(
88
108
  cls, name: str, sourcename: Optional[str] = None
89
- ) -> Callable[[_T], _T]:
109
+ ) -> Callable[[Type[_T]], Type[_T]]:
90
110
  """Register a new operation for this class.
91
111
 
92
112
  This method is normally used to add new operations
@@ -103,7 +123,7 @@ class AbstractOperations(util.ModuleClsProxy):
103
123
 
104
124
  """
105
125
 
106
- def register(op_cls):
126
+ def register(op_cls: Type[_T]) -> Type[_T]:
107
127
  if sourcename is None:
108
128
  fn = getattr(op_cls, name)
109
129
  source_name = fn.__name__
@@ -122,8 +142,11 @@ class AbstractOperations(util.ModuleClsProxy):
122
142
  *spec, formatannotation=formatannotation_fwdref
123
143
  )
124
144
  num_defaults = len(spec[3]) if spec[3] else 0
145
+
146
+ defaulted_vals: Tuple[Any, ...]
147
+
125
148
  if num_defaults:
126
- defaulted_vals = name_args[0 - num_defaults :]
149
+ defaulted_vals = tuple(name_args[0 - num_defaults :])
127
150
  else:
128
151
  defaulted_vals = ()
129
152
 
@@ -164,7 +187,7 @@ class AbstractOperations(util.ModuleClsProxy):
164
187
 
165
188
  globals_ = dict(globals())
166
189
  globals_.update({"op_cls": op_cls})
167
- lcl = {}
190
+ lcl: Dict[str, Any] = {}
168
191
 
169
192
  exec(func_text, globals_, lcl)
170
193
  setattr(cls, name, lcl[name])
@@ -180,7 +203,7 @@ class AbstractOperations(util.ModuleClsProxy):
180
203
  return register
181
204
 
182
205
  @classmethod
183
- def implementation_for(cls, op_cls: Any) -> Callable[..., Any]:
206
+ def implementation_for(cls, op_cls: Any) -> Callable[[_C], _C]:
184
207
  """Register an implementation for a given :class:`.MigrateOperation`.
185
208
 
186
209
  This is part of the operation extensibility API.
@@ -191,7 +214,7 @@ class AbstractOperations(util.ModuleClsProxy):
191
214
 
192
215
  """
193
216
 
194
- def decorate(fn):
217
+ def decorate(fn: _C) -> _C:
195
218
  cls._to_impl.dispatch_for(op_cls)(fn)
196
219
  return fn
197
220
 
@@ -213,7 +236,7 @@ class AbstractOperations(util.ModuleClsProxy):
213
236
  table_name: str,
214
237
  schema: Optional[str] = None,
215
238
  recreate: Literal["auto", "always", "never"] = "auto",
216
- partial_reordering: Optional[tuple] = None,
239
+ partial_reordering: Optional[Tuple[Any, ...]] = None,
217
240
  copy_from: Optional[Table] = None,
218
241
  table_args: Tuple[Any, ...] = (),
219
242
  table_kwargs: Mapping[str, Any] = util.immutabledict(),
@@ -382,6 +405,35 @@ class AbstractOperations(util.ModuleClsProxy):
382
405
 
383
406
  return self.migration_context
384
407
 
408
+ @overload
409
+ def invoke(self, operation: CreateTableOp) -> Table:
410
+ ...
411
+
412
+ @overload
413
+ def invoke(
414
+ self,
415
+ operation: Union[
416
+ AddConstraintOp,
417
+ DropConstraintOp,
418
+ CreateIndexOp,
419
+ DropIndexOp,
420
+ AddColumnOp,
421
+ AlterColumnOp,
422
+ AlterTableOp,
423
+ CreateTableCommentOp,
424
+ DropTableCommentOp,
425
+ DropColumnOp,
426
+ BulkInsertOp,
427
+ DropTableOp,
428
+ ExecuteSQLOp,
429
+ ],
430
+ ) -> None:
431
+ ...
432
+
433
+ @overload
434
+ def invoke(self, operation: MigrateOperation) -> Any:
435
+ ...
436
+
385
437
  def invoke(self, operation: MigrateOperation) -> Any:
386
438
  """Given a :class:`.MigrateOperation`, invoke it in terms of
387
439
  this :class:`.Operations` instance.
@@ -659,8 +711,10 @@ class Operations(AbstractOperations):
659
711
  comment: Union[str, Literal[False], None] = False,
660
712
  server_default: Any = False,
661
713
  new_column_name: Optional[str] = None,
662
- type_: Union[TypeEngine, Type[TypeEngine], None] = None,
663
- existing_type: Union[TypeEngine, Type[TypeEngine], None] = None,
714
+ type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
715
+ existing_type: Union[
716
+ TypeEngine[Any], Type[TypeEngine[Any]], None
717
+ ] = None,
664
718
  existing_server_default: Union[
665
719
  str, bool, Identity, Computed, None
666
720
  ] = False,
@@ -756,7 +810,7 @@ class Operations(AbstractOperations):
756
810
  def bulk_insert(
757
811
  self,
758
812
  table: Union[Table, TableClause],
759
- rows: List[dict],
813
+ rows: List[Dict[str, Any]],
760
814
  *,
761
815
  multiinsert: bool = True,
762
816
  ) -> None:
@@ -1560,7 +1614,7 @@ class BatchOperations(AbstractOperations):
1560
1614
 
1561
1615
  impl: BatchOperationsImpl
1562
1616
 
1563
- def _noop(self, operation):
1617
+ def _noop(self, operation: Any) -> NoReturn:
1564
1618
  raise NotImplementedError(
1565
1619
  "The %s method does not apply to a batch table alter operation."
1566
1620
  % operation
@@ -1596,8 +1650,10 @@ class BatchOperations(AbstractOperations):
1596
1650
  comment: Union[str, Literal[False], None] = False,
1597
1651
  server_default: Any = False,
1598
1652
  new_column_name: Optional[str] = None,
1599
- type_: Union[TypeEngine, Type[TypeEngine], None] = None,
1600
- existing_type: Union[TypeEngine, Type[TypeEngine], None] = None,
1653
+ type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
1654
+ existing_type: Union[
1655
+ TypeEngine[Any], Type[TypeEngine[Any]], None
1656
+ ] = None,
1601
1657
  existing_server_default: Union[
1602
1658
  str, bool, Identity, Computed, None
1603
1659
  ] = False,
@@ -1652,7 +1708,7 @@ class BatchOperations(AbstractOperations):
1652
1708
 
1653
1709
  def create_exclude_constraint(
1654
1710
  self, constraint_name: str, *elements: Any, **kw: Any
1655
- ):
1711
+ ) -> Optional[Table]:
1656
1712
  """Issue a "create exclude constraint" instruction using the
1657
1713
  current batch migration context.
1658
1714