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
@@ -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
  from typing import Any
@@ -17,7 +20,7 @@ from sqlalchemy import PrimaryKeyConstraint
17
20
  from sqlalchemy import schema as sql_schema
18
21
  from sqlalchemy import Table
19
22
  from sqlalchemy import types as sqltypes
20
- from sqlalchemy.events import SchemaEventTarget
23
+ from sqlalchemy.sql.schema import SchemaEventTarget
21
24
  from sqlalchemy.util import OrderedDict
22
25
  from sqlalchemy.util import topological
23
26
 
@@ -374,7 +377,7 @@ class ApplyBatchImpl:
374
377
  for idx_existing in self.indexes.values():
375
378
  # this is a lift-and-move from Table.to_metadata
376
379
 
377
- if idx_existing._column_flag: # type: ignore
380
+ if idx_existing._column_flag:
378
381
  continue
379
382
 
380
383
  idx_copy = Index(
@@ -403,9 +406,7 @@ class ApplyBatchImpl:
403
406
  def _setup_referent(
404
407
  self, metadata: MetaData, constraint: ForeignKeyConstraint
405
408
  ) -> None:
406
- spec = constraint.elements[
407
- 0
408
- ]._get_colspec() # type:ignore[attr-defined]
409
+ spec = constraint.elements[0]._get_colspec()
409
410
  parts = spec.split(".")
410
411
  tname = parts[-2]
411
412
  if len(parts) == 3:
@@ -546,9 +547,7 @@ class ApplyBatchImpl:
546
547
  else:
547
548
  sql_schema.DefaultClause(
548
549
  server_default # type: ignore[arg-type]
549
- )._set_parent( # type:ignore[attr-defined]
550
- existing
551
- )
550
+ )._set_parent(existing)
552
551
  if autoincrement is not None:
553
552
  existing.autoincrement = bool(autoincrement)
554
553
 
alembic/operations/ops.py CHANGED
@@ -5,6 +5,7 @@ import re
5
5
  from typing import Any
6
6
  from typing import Callable
7
7
  from typing import cast
8
+ from typing import Dict
8
9
  from typing import FrozenSet
9
10
  from typing import Iterator
10
11
  from typing import List
@@ -15,6 +16,7 @@ from typing import Set
15
16
  from typing import Tuple
16
17
  from typing import Type
17
18
  from typing import TYPE_CHECKING
19
+ from typing import TypeVar
18
20
  from typing import Union
19
21
 
20
22
  from sqlalchemy.types import NULLTYPE
@@ -53,6 +55,9 @@ if TYPE_CHECKING:
53
55
  from ..runtime.migration import MigrationContext
54
56
  from ..script.revision import _RevIdType
55
57
 
58
+ _T = TypeVar("_T", bound=Any)
59
+ _AC = TypeVar("_AC", bound="AddConstraintOp")
60
+
56
61
 
57
62
  class MigrateOperation:
58
63
  """base class for migration command and organization objects.
@@ -70,7 +75,7 @@ class MigrateOperation:
70
75
  """
71
76
 
72
77
  @util.memoized_property
73
- def info(self):
78
+ def info(self) -> Dict[Any, Any]:
74
79
  """A dictionary that may be used to store arbitrary information
75
80
  along with this :class:`.MigrateOperation` object.
76
81
 
@@ -92,12 +97,14 @@ class AddConstraintOp(MigrateOperation):
92
97
  add_constraint_ops = util.Dispatcher()
93
98
 
94
99
  @property
95
- def constraint_type(self):
100
+ def constraint_type(self) -> str:
96
101
  raise NotImplementedError()
97
102
 
98
103
  @classmethod
99
- def register_add_constraint(cls, type_: str) -> Callable:
100
- def go(klass):
104
+ def register_add_constraint(
105
+ cls, type_: str
106
+ ) -> Callable[[Type[_AC]], Type[_AC]]:
107
+ def go(klass: Type[_AC]) -> Type[_AC]:
101
108
  cls.add_constraint_ops.dispatch_for(type_)(klass.from_constraint)
102
109
  return klass
103
110
 
@@ -105,7 +112,7 @@ class AddConstraintOp(MigrateOperation):
105
112
 
106
113
  @classmethod
107
114
  def from_constraint(cls, constraint: Constraint) -> AddConstraintOp:
108
- return cls.add_constraint_ops.dispatch(constraint.__visit_name__)(
115
+ return cls.add_constraint_ops.dispatch(constraint.__visit_name__)( # type: ignore[no-any-return] # noqa: E501
109
116
  constraint
110
117
  )
111
118
 
@@ -398,7 +405,7 @@ class CreateUniqueConstraintOp(AddConstraintOp):
398
405
 
399
406
  uq_constraint = cast("UniqueConstraint", constraint)
400
407
 
401
- kw: dict = {}
408
+ kw: Dict[str, Any] = {}
402
409
  if uq_constraint.deferrable:
403
410
  kw["deferrable"] = uq_constraint.deferrable
404
411
  if uq_constraint.initially:
@@ -532,7 +539,7 @@ class CreateForeignKeyOp(AddConstraintOp):
532
539
  @classmethod
533
540
  def from_constraint(cls, constraint: Constraint) -> CreateForeignKeyOp:
534
541
  fk_constraint = cast("ForeignKeyConstraint", constraint)
535
- kw: dict = {}
542
+ kw: Dict[str, Any] = {}
536
543
  if fk_constraint.onupdate:
537
544
  kw["onupdate"] = fk_constraint.onupdate
538
545
  if fk_constraint.ondelete:
@@ -897,9 +904,9 @@ class CreateIndexOp(MigrateOperation):
897
904
  def from_index(cls, index: Index) -> CreateIndexOp:
898
905
  assert index.table is not None
899
906
  return cls(
900
- index.name, # type: ignore[arg-type]
907
+ index.name,
901
908
  index.table.name,
902
- sqla_compat._get_index_expressions(index),
909
+ index.expressions,
903
910
  schema=index.table.schema,
904
911
  unique=index.unique,
905
912
  **index.kwargs,
@@ -1054,6 +1061,7 @@ class DropIndexOp(MigrateOperation):
1054
1061
  table_name=index.table.name,
1055
1062
  schema=index.table.schema,
1056
1063
  _reverse=CreateIndexOp.from_index(index),
1064
+ unique=index.unique,
1057
1065
  **index.kwargs,
1058
1066
  )
1059
1067
 
@@ -1182,7 +1190,7 @@ class CreateTableOp(MigrateOperation):
1182
1190
 
1183
1191
  return cls(
1184
1192
  table.name,
1185
- list(table.c) + list(table.constraints), # type:ignore[arg-type]
1193
+ list(table.c) + list(table.constraints),
1186
1194
  schema=table.schema,
1187
1195
  _namespace_metadata=_namespace_metadata,
1188
1196
  # given a Table() object, this Table will contain full Index()
@@ -1534,7 +1542,7 @@ class CreateTableCommentOp(AlterTableOp):
1534
1542
  )
1535
1543
  return operations.invoke(op)
1536
1544
 
1537
- def reverse(self):
1545
+ def reverse(self) -> Union[CreateTableCommentOp, DropTableCommentOp]:
1538
1546
  """Reverses the COMMENT ON operation against a table."""
1539
1547
  if self.existing_comment is None:
1540
1548
  return DropTableCommentOp(
@@ -1550,14 +1558,16 @@ class CreateTableCommentOp(AlterTableOp):
1550
1558
  schema=self.schema,
1551
1559
  )
1552
1560
 
1553
- def to_table(self, migration_context=None):
1561
+ def to_table(
1562
+ self, migration_context: Optional[MigrationContext] = None
1563
+ ) -> Table:
1554
1564
  schema_obj = schemaobj.SchemaObjects(migration_context)
1555
1565
 
1556
1566
  return schema_obj.table(
1557
1567
  self.table_name, schema=self.schema, comment=self.comment
1558
1568
  )
1559
1569
 
1560
- def to_diff_tuple(self):
1570
+ def to_diff_tuple(self) -> Tuple[Any, ...]:
1561
1571
  return ("add_table_comment", self.to_table(), self.existing_comment)
1562
1572
 
1563
1573
 
@@ -1629,18 +1639,20 @@ class DropTableCommentOp(AlterTableOp):
1629
1639
  )
1630
1640
  return operations.invoke(op)
1631
1641
 
1632
- def reverse(self):
1642
+ def reverse(self) -> CreateTableCommentOp:
1633
1643
  """Reverses the COMMENT ON operation against a table."""
1634
1644
  return CreateTableCommentOp(
1635
1645
  self.table_name, self.existing_comment, schema=self.schema
1636
1646
  )
1637
1647
 
1638
- def to_table(self, migration_context=None):
1648
+ def to_table(
1649
+ self, migration_context: Optional[MigrationContext] = None
1650
+ ) -> Table:
1639
1651
  schema_obj = schemaobj.SchemaObjects(migration_context)
1640
1652
 
1641
1653
  return schema_obj.table(self.table_name, schema=self.schema)
1642
1654
 
1643
- def to_diff_tuple(self):
1655
+ def to_diff_tuple(self) -> Tuple[Any, ...]:
1644
1656
  return ("remove_table_comment", self.to_table())
1645
1657
 
1646
1658
 
@@ -1817,8 +1829,10 @@ class AlterColumnOp(AlterTableOp):
1817
1829
  comment: Optional[Union[str, Literal[False]]] = False,
1818
1830
  server_default: Any = False,
1819
1831
  new_column_name: Optional[str] = None,
1820
- type_: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
1821
- existing_type: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
1832
+ type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
1833
+ existing_type: Optional[
1834
+ Union[TypeEngine[Any], Type[TypeEngine[Any]]]
1835
+ ] = None,
1822
1836
  existing_server_default: Optional[
1823
1837
  Union[str, bool, Identity, Computed]
1824
1838
  ] = False,
@@ -1938,8 +1952,10 @@ class AlterColumnOp(AlterTableOp):
1938
1952
  comment: Optional[Union[str, Literal[False]]] = False,
1939
1953
  server_default: Any = False,
1940
1954
  new_column_name: Optional[str] = None,
1941
- type_: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
1942
- existing_type: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
1955
+ type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
1956
+ existing_type: Optional[
1957
+ Union[TypeEngine[Any], Type[TypeEngine[Any]]]
1958
+ ] = None,
1943
1959
  existing_server_default: Optional[
1944
1960
  Union[str, bool, Identity, Computed]
1945
1961
  ] = False,
@@ -2019,11 +2035,11 @@ class AddColumnOp(AlterTableOp):
2019
2035
  ) -> Tuple[str, Optional[str], str, Column[Any]]:
2020
2036
  return ("add_column", self.schema, self.table_name, self.column)
2021
2037
 
2022
- def to_column(self) -> Column:
2038
+ def to_column(self) -> Column[Any]:
2023
2039
  return self.column
2024
2040
 
2025
2041
  @classmethod
2026
- def from_column(cls, col: Column) -> AddColumnOp:
2042
+ def from_column(cls, col: Column[Any]) -> AddColumnOp:
2027
2043
  return cls(col.table.name, col, schema=col.table.schema)
2028
2044
 
2029
2045
  @classmethod
@@ -2214,7 +2230,7 @@ class DropColumnOp(AlterTableOp):
2214
2230
 
2215
2231
  def to_column(
2216
2232
  self, migration_context: Optional[MigrationContext] = None
2217
- ) -> Column:
2233
+ ) -> Column[Any]:
2218
2234
  if self._reverse is not None:
2219
2235
  return self._reverse.column
2220
2236
  schema_obj = schemaobj.SchemaObjects(migration_context)
@@ -2298,7 +2314,7 @@ class BulkInsertOp(MigrateOperation):
2298
2314
  def __init__(
2299
2315
  self,
2300
2316
  table: Union[Table, TableClause],
2301
- rows: List[dict],
2317
+ rows: List[Dict[str, Any]],
2302
2318
  *,
2303
2319
  multiinsert: bool = True,
2304
2320
  ) -> None:
@@ -2311,7 +2327,7 @@ class BulkInsertOp(MigrateOperation):
2311
2327
  cls,
2312
2328
  operations: Operations,
2313
2329
  table: Union[Table, TableClause],
2314
- rows: List[dict],
2330
+ rows: List[Dict[str, Any]],
2315
2331
  *,
2316
2332
  multiinsert: bool = True,
2317
2333
  ) -> None:
@@ -2607,7 +2623,7 @@ class UpgradeOps(OpContainer):
2607
2623
  self.upgrade_token = upgrade_token
2608
2624
 
2609
2625
  def reverse_into(self, downgrade_ops: DowngradeOps) -> DowngradeOps:
2610
- downgrade_ops.ops[:] = list( # type:ignore[index]
2626
+ downgrade_ops.ops[:] = list(
2611
2627
  reversed([op.reverse() for op in self.ops])
2612
2628
  )
2613
2629
  return downgrade_ops
@@ -2634,7 +2650,7 @@ class DowngradeOps(OpContainer):
2634
2650
  super().__init__(ops=ops)
2635
2651
  self.downgrade_token = downgrade_token
2636
2652
 
2637
- def reverse(self):
2653
+ def reverse(self) -> UpgradeOps:
2638
2654
  return UpgradeOps(
2639
2655
  ops=list(reversed([op.reverse() for op in self.ops]))
2640
2656
  )
@@ -2665,6 +2681,8 @@ class MigrationScript(MigrateOperation):
2665
2681
  """
2666
2682
 
2667
2683
  _needs_render: Optional[bool]
2684
+ _upgrade_ops: List[UpgradeOps]
2685
+ _downgrade_ops: List[DowngradeOps]
2668
2686
 
2669
2687
  def __init__(
2670
2688
  self,
@@ -2692,7 +2710,7 @@ class MigrationScript(MigrateOperation):
2692
2710
  self.downgrade_ops = downgrade_ops
2693
2711
 
2694
2712
  @property
2695
- def upgrade_ops(self):
2713
+ def upgrade_ops(self) -> Optional[UpgradeOps]:
2696
2714
  """An instance of :class:`.UpgradeOps`.
2697
2715
 
2698
2716
  .. seealso::
@@ -2711,13 +2729,15 @@ class MigrationScript(MigrateOperation):
2711
2729
  return self._upgrade_ops[0]
2712
2730
 
2713
2731
  @upgrade_ops.setter
2714
- def upgrade_ops(self, upgrade_ops):
2732
+ def upgrade_ops(
2733
+ self, upgrade_ops: Union[UpgradeOps, List[UpgradeOps]]
2734
+ ) -> None:
2715
2735
  self._upgrade_ops = util.to_list(upgrade_ops)
2716
2736
  for elem in self._upgrade_ops:
2717
2737
  assert isinstance(elem, UpgradeOps)
2718
2738
 
2719
2739
  @property
2720
- def downgrade_ops(self):
2740
+ def downgrade_ops(self) -> Optional[DowngradeOps]:
2721
2741
  """An instance of :class:`.DowngradeOps`.
2722
2742
 
2723
2743
  .. seealso::
@@ -2736,7 +2756,9 @@ class MigrationScript(MigrateOperation):
2736
2756
  return self._downgrade_ops[0]
2737
2757
 
2738
2758
  @downgrade_ops.setter
2739
- def downgrade_ops(self, downgrade_ops):
2759
+ def downgrade_ops(
2760
+ self, downgrade_ops: Union[DowngradeOps, List[DowngradeOps]]
2761
+ ) -> None:
2740
2762
  self._downgrade_ops = util.to_list(downgrade_ops)
2741
2763
  for elem in self._downgrade_ops:
2742
2764
  assert isinstance(elem, DowngradeOps)
@@ -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
  from typing import Any
@@ -274,10 +277,8 @@ class SchemaObjects:
274
277
  ForeignKey.
275
278
 
276
279
  """
277
- if isinstance(fk._colspec, str): # type:ignore[attr-defined]
278
- table_key, cname = fk._colspec.rsplit( # type:ignore[attr-defined]
279
- ".", 1
280
- )
280
+ if isinstance(fk._colspec, str):
281
+ table_key, cname = fk._colspec.rsplit(".", 1)
281
282
  sname, tname = self._parse_table_key(table_key)
282
283
  if table_key not in metadata.tables:
283
284
  rel_t = sa_schema.Table(tname, metadata, schema=sname)
@@ -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 typing import TYPE_CHECKING
2
5
 
3
6
  from sqlalchemy import schema as sa_schema
@@ -5,7 +8,7 @@ from sqlalchemy import schema as sa_schema
5
8
  from . import ops
6
9
  from .base import Operations
7
10
  from ..util.sqla_compat import _copy
8
- from ..util.sqla_compat import sqla_2
11
+ from ..util.sqla_compat import sqla_14
9
12
 
10
13
  if TYPE_CHECKING:
11
14
  from sqlalchemy.sql.schema import Table
@@ -98,8 +101,8 @@ def create_index(
98
101
  idx = operation.to_index(operations.migration_context)
99
102
  kw = {}
100
103
  if operation.if_not_exists is not None:
101
- if not sqla_2:
102
- raise NotImplementedError("SQLAlchemy 2.0+ required")
104
+ if not sqla_14:
105
+ raise NotImplementedError("SQLAlchemy 1.4+ required")
103
106
 
104
107
  kw["if_not_exists"] = operation.if_not_exists
105
108
  operations.impl.create_index(idx, **kw)
@@ -109,8 +112,8 @@ def create_index(
109
112
  def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
110
113
  kw = {}
111
114
  if operation.if_exists is not None:
112
- if not sqla_2:
113
- raise NotImplementedError("SQLAlchemy 2.0+ required")
115
+ if not sqla_14:
116
+ raise NotImplementedError("SQLAlchemy 1.4+ required")
114
117
 
115
118
  kw["if_exists"] = operation.if_exists
116
119
 
@@ -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
@@ -227,9 +228,9 @@ class EnvironmentContext(util.ModuleClsProxy):
227
228
  has been configured.
228
229
 
229
230
  """
230
- return self.context_opts.get("as_sql", False)
231
+ return self.context_opts.get("as_sql", False) # type: ignore[no-any-return] # noqa: E501
231
232
 
232
- def is_transactional_ddl(self):
233
+ def is_transactional_ddl(self) -> bool:
233
234
  """Return True if the context is configured to expect a
234
235
  transactional DDL capable backend.
235
236
 
@@ -338,7 +339,7 @@ class EnvironmentContext(util.ModuleClsProxy):
338
339
  line.
339
340
 
340
341
  """
341
- return self.context_opts.get("tag", None)
342
+ return self.context_opts.get("tag", None) # type: ignore[no-any-return] # noqa: E501
342
343
 
343
344
  @overload
344
345
  def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]:
@@ -366,7 +367,11 @@ class EnvironmentContext(util.ModuleClsProxy):
366
367
  The return value is a list, returned directly from the ``argparse``
367
368
  structure. If ``as_dictionary=True`` is passed, the ``x`` arguments
368
369
  are parsed using ``key=value`` format into a dictionary that is
369
- then returned.
370
+ then returned. If there is no ``=`` in the argument, value is an empty
371
+ string.
372
+
373
+ .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when
374
+ arguments are passed without the ``=`` symbol.
370
375
 
371
376
  For example, to support passing a database URL on the command line,
372
377
  the standard ``env.py`` script can be modified like this::
@@ -400,7 +405,12 @@ class EnvironmentContext(util.ModuleClsProxy):
400
405
  else:
401
406
  value = []
402
407
  if as_dictionary:
403
- value = dict(arg.split("=", 1) for arg in value)
408
+ dict_value = {}
409
+ for arg in value:
410
+ x_key, _, x_value = arg.partition("=")
411
+ dict_value[x_key] = x_value
412
+ value = dict_value
413
+
404
414
  return value
405
415
 
406
416
  def configure(
@@ -416,7 +426,7 @@ class EnvironmentContext(util.ModuleClsProxy):
416
426
  tag: Optional[str] = None,
417
427
  template_args: Optional[Dict[str, Any]] = None,
418
428
  render_as_batch: bool = False,
419
- target_metadata: Optional[MetaData] = None,
429
+ target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
420
430
  include_name: Optional[IncludeNameFn] = None,
421
431
  include_object: Optional[IncludeObjectFn] = None,
422
432
  include_schemas: bool = False,
@@ -940,7 +950,7 @@ class EnvironmentContext(util.ModuleClsProxy):
940
950
  def execute(
941
951
  self,
942
952
  sql: Union[Executable, str],
943
- execution_options: Optional[dict] = None,
953
+ execution_options: Optional[Dict[str, Any]] = None,
944
954
  ) -> None:
945
955
  """Execute the given SQL using the current change context.
946
956
 
@@ -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
  from contextlib import contextmanager
@@ -521,7 +524,7 @@ class MigrationContext:
521
524
  start_from_rev = None
522
525
  elif start_from_rev is not None and self.script:
523
526
  start_from_rev = [
524
- cast("Script", self.script.get_revision(sfr)).revision
527
+ self.script.get_revision(sfr).revision
525
528
  for sfr in util.to_list(start_from_rev)
526
529
  if sfr not in (None, "base")
527
530
  ]
@@ -652,7 +655,7 @@ class MigrationContext:
652
655
  def execute(
653
656
  self,
654
657
  sql: Union[Executable, str],
655
- execution_options: Optional[dict] = None,
658
+ execution_options: Optional[Dict[str, Any]] = None,
656
659
  ) -> None:
657
660
  """Execute a SQL construct or string statement.
658
661
 
@@ -1000,6 +1003,12 @@ class MigrationStep:
1000
1003
  is_upgrade: bool
1001
1004
  migration_fn: Any
1002
1005
 
1006
+ if TYPE_CHECKING:
1007
+
1008
+ @property
1009
+ def doc(self) -> Optional[str]:
1010
+ ...
1011
+
1003
1012
  @property
1004
1013
  def name(self) -> str:
1005
1014
  return self.migration_fn.__name__
@@ -1048,13 +1057,9 @@ class RevisionStep(MigrationStep):
1048
1057
  self.revision = revision
1049
1058
  self.is_upgrade = is_upgrade
1050
1059
  if is_upgrade:
1051
- self.migration_fn = (
1052
- revision.module.upgrade # type:ignore[attr-defined]
1053
- )
1060
+ self.migration_fn = revision.module.upgrade
1054
1061
  else:
1055
- self.migration_fn = (
1056
- revision.module.downgrade # type:ignore[attr-defined]
1057
- )
1062
+ self.migration_fn = revision.module.downgrade
1058
1063
 
1059
1064
  def __repr__(self):
1060
1065
  return "RevisionStep(%r, is_upgrade=%r)" % (
@@ -1070,7 +1075,7 @@ class RevisionStep(MigrationStep):
1070
1075
  )
1071
1076
 
1072
1077
  @property
1073
- def doc(self) -> str:
1078
+ def doc(self) -> Optional[str]:
1074
1079
  return self.revision.doc
1075
1080
 
1076
1081
  @property
@@ -1168,7 +1173,18 @@ class RevisionStep(MigrationStep):
1168
1173
  }
1169
1174
  return tuple(set(self.to_revisions).difference(ancestors))
1170
1175
  else:
1171
- return self.to_revisions
1176
+ # for each revision we plan to return, compute its ancestors
1177
+ # (excluding self), and remove those from the final output since
1178
+ # they are already accounted for.
1179
+ ancestors = {
1180
+ r.revision
1181
+ for to_revision in self.to_revisions
1182
+ for r in self.revision_map._get_ancestor_nodes(
1183
+ self.revision_map.get_revisions(to_revision), check=False
1184
+ )
1185
+ if r.revision != to_revision
1186
+ }
1187
+ return tuple(set(self.to_revisions).difference(ancestors))
1172
1188
 
1173
1189
  def unmerge_branch_idents(
1174
1190
  self, heads: Set[str]
@@ -1283,7 +1299,7 @@ class StampStep(MigrationStep):
1283
1299
  def __eq__(self, other):
1284
1300
  return (
1285
1301
  isinstance(other, StampStep)
1286
- and other.from_revisions == self.revisions
1302
+ and other.from_revisions == self.from_revisions
1287
1303
  and other.to_revisions == self.to_revisions
1288
1304
  and other.branch_move == self.branch_move
1289
1305
  and self.is_upgrade == other.is_upgrade