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 io import StringIO
@@ -164,14 +167,22 @@ def _render_modify_table(
164
167
  def _render_create_table_comment(
165
168
  autogen_context: AutogenContext, op: ops.CreateTableCommentOp
166
169
  ) -> str:
167
- templ = (
168
- "{prefix}create_table_comment(\n"
169
- "{indent}'{tname}',\n"
170
- "{indent}{comment},\n"
171
- "{indent}existing_comment={existing},\n"
172
- "{indent}schema={schema}\n"
173
- ")"
174
- )
170
+ if autogen_context._has_batch:
171
+ templ = (
172
+ "{prefix}create_table_comment(\n"
173
+ "{indent}{comment},\n"
174
+ "{indent}existing_comment={existing}\n"
175
+ ")"
176
+ )
177
+ else:
178
+ templ = (
179
+ "{prefix}create_table_comment(\n"
180
+ "{indent}'{tname}',\n"
181
+ "{indent}{comment},\n"
182
+ "{indent}existing_comment={existing},\n"
183
+ "{indent}schema={schema}\n"
184
+ ")"
185
+ )
175
186
  return templ.format(
176
187
  prefix=_alembic_autogenerate_prefix(autogen_context),
177
188
  tname=op.table_name,
@@ -188,13 +199,20 @@ def _render_create_table_comment(
188
199
  def _render_drop_table_comment(
189
200
  autogen_context: AutogenContext, op: ops.DropTableCommentOp
190
201
  ) -> str:
191
- templ = (
192
- "{prefix}drop_table_comment(\n"
193
- "{indent}'{tname}',\n"
194
- "{indent}existing_comment={existing},\n"
195
- "{indent}schema={schema}\n"
196
- ")"
197
- )
202
+ if autogen_context._has_batch:
203
+ templ = (
204
+ "{prefix}drop_table_comment(\n"
205
+ "{indent}existing_comment={existing}\n"
206
+ ")"
207
+ )
208
+ else:
209
+ templ = (
210
+ "{prefix}drop_table_comment(\n"
211
+ "{indent}'{tname}',\n"
212
+ "{indent}existing_comment={existing},\n"
213
+ "{indent}schema={schema}\n"
214
+ ")"
215
+ )
198
216
  return templ.format(
199
217
  prefix=_alembic_autogenerate_prefix(autogen_context),
200
218
  tname=op.table_name,
@@ -834,7 +852,7 @@ def _render_Variant_type(
834
852
  ) -> str:
835
853
  base_type, variant_mapping = sqla_compat._get_variant_mapping(type_)
836
854
  base = _repr_type(base_type, autogen_context, _skip_variants=True)
837
- assert base is not None and base is not False
855
+ assert base is not None and base is not False # type: ignore[comparison-overlap] # noqa:E501
838
856
  for dialect in sorted(variant_mapping):
839
857
  typ = variant_mapping[dialect]
840
858
  base += ".with_variant(%s, %r)" % (
@@ -931,7 +949,7 @@ def _fk_colspec(
931
949
  won't fail if the remote table can't be resolved.
932
950
 
933
951
  """
934
- colspec = fk._get_colspec() # type:ignore[attr-defined]
952
+ colspec = fk._get_colspec()
935
953
  tokens = colspec.split(".")
936
954
  tname, colname = tokens[-2:]
937
955
 
@@ -1001,8 +1019,7 @@ def _render_foreign_key(
1001
1019
  % {
1002
1020
  "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
1003
1021
  "cols": ", ".join(
1004
- "%r" % _ident(cast("Column", f.parent).name)
1005
- for f in constraint.elements
1022
+ repr(_ident(f.parent.name)) for f in constraint.elements
1006
1023
  ),
1007
1024
  "refcols": ", ".join(
1008
1025
  repr(_fk_colspec(f, apply_metadata_schema, namespace_metadata))
@@ -1043,12 +1060,10 @@ def _render_check_constraint(
1043
1060
  # ideally SQLAlchemy would give us more of a first class
1044
1061
  # way to detect this.
1045
1062
  if (
1046
- constraint._create_rule # type:ignore[attr-defined]
1047
- and hasattr(
1048
- constraint._create_rule, "target" # type:ignore[attr-defined]
1049
- )
1063
+ constraint._create_rule
1064
+ and hasattr(constraint._create_rule, "target")
1050
1065
  and isinstance(
1051
- constraint._create_rule.target, # type:ignore[attr-defined]
1066
+ constraint._create_rule.target,
1052
1067
  sqltypes.TypeEngine,
1053
1068
  )
1054
1069
  ):
@@ -4,7 +4,7 @@ from typing import Any
4
4
  from typing import Callable
5
5
  from typing import Iterator
6
6
  from typing import List
7
- from typing import Optional
7
+ from typing import Tuple
8
8
  from typing import Type
9
9
  from typing import TYPE_CHECKING
10
10
  from typing import Union
@@ -16,12 +16,18 @@ if TYPE_CHECKING:
16
16
  from ..operations.ops import AddColumnOp
17
17
  from ..operations.ops import AlterColumnOp
18
18
  from ..operations.ops import CreateTableOp
19
+ from ..operations.ops import DowngradeOps
19
20
  from ..operations.ops import MigrateOperation
20
21
  from ..operations.ops import MigrationScript
21
22
  from ..operations.ops import ModifyTableOps
22
23
  from ..operations.ops import OpContainer
23
- from ..runtime.environment import _GetRevArg
24
+ from ..operations.ops import UpgradeOps
24
25
  from ..runtime.migration import MigrationContext
26
+ from ..script.revision import _GetRevArg
27
+
28
+ ProcessRevisionDirectiveFn = Callable[
29
+ ["MigrationContext", "_GetRevArg", List["MigrationScript"]], None
30
+ ]
25
31
 
26
32
 
27
33
  class Rewriter:
@@ -52,15 +58,21 @@ class Rewriter:
52
58
 
53
59
  _traverse = util.Dispatcher()
54
60
 
55
- _chained: Optional[Rewriter] = None
61
+ _chained: Tuple[Union[ProcessRevisionDirectiveFn, Rewriter], ...] = ()
56
62
 
57
63
  def __init__(self) -> None:
58
64
  self.dispatch = util.Dispatcher()
59
65
 
60
- def chain(self, other: Rewriter) -> Rewriter:
66
+ def chain(
67
+ self,
68
+ other: Union[
69
+ ProcessRevisionDirectiveFn,
70
+ Rewriter,
71
+ ],
72
+ ) -> Rewriter:
61
73
  """Produce a "chain" of this :class:`.Rewriter` to another.
62
74
 
63
- This allows two rewriters to operate serially on a stream,
75
+ This allows two or more rewriters to operate serially on a stream,
64
76
  e.g.::
65
77
 
66
78
  writer1 = autogenerate.Rewriter()
@@ -89,7 +101,7 @@ class Rewriter:
89
101
  """
90
102
  wr = self.__class__.__new__(self.__class__)
91
103
  wr.__dict__.update(self.__dict__)
92
- wr._chained = other
104
+ wr._chained += (other,)
93
105
  return wr
94
106
 
95
107
  def rewrites(
@@ -101,7 +113,7 @@ class Rewriter:
101
113
  Type[CreateTableOp],
102
114
  Type[ModifyTableOps],
103
115
  ],
104
- ) -> Callable:
116
+ ) -> Callable[..., Any]:
105
117
  """Register a function as rewriter for a given type.
106
118
 
107
119
  The function should receive three arguments, which are
@@ -146,8 +158,8 @@ class Rewriter:
146
158
  directives: List[MigrationScript],
147
159
  ) -> None:
148
160
  self.process_revision_directives(context, revision, directives)
149
- if self._chained:
150
- self._chained(context, revision, directives)
161
+ for process_revision_directives in self._chained:
162
+ process_revision_directives(context, revision, directives)
151
163
 
152
164
  @_traverse.dispatch_for(ops.MigrationScript)
153
165
  def _traverse_script(
@@ -156,7 +168,7 @@ class Rewriter:
156
168
  revision: _GetRevArg,
157
169
  directive: MigrationScript,
158
170
  ) -> None:
159
- upgrade_ops_list = []
171
+ upgrade_ops_list: List[UpgradeOps] = []
160
172
  for upgrade_ops in directive.upgrade_ops_list:
161
173
  ret = self._traverse_for(context, revision, upgrade_ops)
162
174
  if len(ret) != 1:
@@ -164,9 +176,10 @@ class Rewriter:
164
176
  "Can only return single object for UpgradeOps traverse"
165
177
  )
166
178
  upgrade_ops_list.append(ret[0])
167
- directive.upgrade_ops = upgrade_ops_list
168
179
 
169
- downgrade_ops_list = []
180
+ directive.upgrade_ops = upgrade_ops_list # type: ignore
181
+
182
+ downgrade_ops_list: List[DowngradeOps] = []
170
183
  for downgrade_ops in directive.downgrade_ops_list:
171
184
  ret = self._traverse_for(context, revision, downgrade_ops)
172
185
  if len(ret) != 1:
@@ -174,7 +187,7 @@ class Rewriter:
174
187
  "Can only return single object for DowngradeOps traverse"
175
188
  )
176
189
  downgrade_ops_list.append(ret[0])
177
- directive.downgrade_ops = downgrade_ops_list
190
+ directive.downgrade_ops = downgrade_ops_list # type: ignore
178
191
 
179
192
  @_traverse.dispatch_for(ops.OpContainer)
180
193
  def _traverse_op_container(
alembic/command.py CHANGED
@@ -1,3 +1,5 @@
1
+ # mypy: allow-untyped-defs, allow-untyped-calls
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import os
@@ -18,7 +20,7 @@ if TYPE_CHECKING:
18
20
  from .runtime.environment import ProcessRevisionDirectiveFn
19
21
 
20
22
 
21
- def list_templates(config: Config):
23
+ def list_templates(config: Config) -> None:
22
24
  """List available templates.
23
25
 
24
26
  :param config: a :class:`.Config` object.
@@ -290,7 +292,10 @@ def check(config: "Config") -> None:
290
292
  # the revision_context now has MigrationScript structure(s) present.
291
293
 
292
294
  migration_script = revision_context.generated_revisions[-1]
293
- diffs = migration_script.upgrade_ops.as_diffs()
295
+ diffs = []
296
+ for upgrade_ops in migration_script.upgrade_ops_list:
297
+ diffs.extend(upgrade_ops.as_diffs())
298
+
294
299
  if diffs:
295
300
  raise util.AutogenerateDiffsDetected(
296
301
  f"New upgrade operations detected: {diffs}"
alembic/config.py CHANGED
@@ -12,6 +12,7 @@ from typing import Dict
12
12
  from typing import Mapping
13
13
  from typing import Optional
14
14
  from typing import overload
15
+ from typing import Sequence
15
16
  from typing import TextIO
16
17
  from typing import Union
17
18
 
@@ -104,7 +105,7 @@ class Config:
104
105
  stdout: TextIO = sys.stdout,
105
106
  cmd_opts: Optional[Namespace] = None,
106
107
  config_args: Mapping[str, Any] = util.immutabledict(),
107
- attributes: Optional[dict] = None,
108
+ attributes: Optional[Dict[str, Any]] = None,
108
109
  ) -> None:
109
110
  """Construct a new :class:`.Config`"""
110
111
  self.config_file_name = file_
@@ -140,7 +141,7 @@ class Config:
140
141
  """
141
142
 
142
143
  @util.memoized_property
143
- def attributes(self):
144
+ def attributes(self) -> Dict[str, Any]:
144
145
  """A Python dictionary for storage of additional state.
145
146
 
146
147
 
@@ -159,7 +160,7 @@ class Config:
159
160
  """
160
161
  return {}
161
162
 
162
- def print_stdout(self, text: str, *arg) -> None:
163
+ def print_stdout(self, text: str, *arg: Any) -> None:
163
164
  """Render a message to standard out.
164
165
 
165
166
  When :meth:`.Config.print_stdout` is called with additional args
@@ -183,7 +184,7 @@ class Config:
183
184
  util.write_outstream(self.stdout, output, "\n", **self.messaging_opts)
184
185
 
185
186
  @util.memoized_property
186
- def file_config(self):
187
+ def file_config(self) -> ConfigParser:
187
188
  """Return the underlying ``ConfigParser`` object.
188
189
 
189
190
  Direct access to the .ini file is available here,
@@ -321,7 +322,9 @@ class Config:
321
322
  ) -> Optional[str]:
322
323
  ...
323
324
 
324
- def get_main_option(self, name, default=None):
325
+ def get_main_option(
326
+ self, name: str, default: Optional[str] = None
327
+ ) -> Optional[str]:
325
328
  """Return an option from the 'main' section of the .ini file.
326
329
 
327
330
  This defaults to being a key from the ``[alembic]``
@@ -351,7 +354,9 @@ class CommandLine:
351
354
  self._generate_args(prog)
352
355
 
353
356
  def _generate_args(self, prog: Optional[str]) -> None:
354
- def add_options(fn, parser, positional, kwargs):
357
+ def add_options(
358
+ fn: Any, parser: Any, positional: Any, kwargs: Any
359
+ ) -> None:
355
360
  kwargs_opts = {
356
361
  "template": (
357
362
  "-t",
@@ -554,7 +559,9 @@ class CommandLine:
554
559
  )
555
560
  subparsers = parser.add_subparsers()
556
561
 
557
- positional_translations = {command.stamp: {"revision": "revisions"}}
562
+ positional_translations: Dict[Any, Any] = {
563
+ command.stamp: {"revision": "revisions"}
564
+ }
558
565
 
559
566
  for fn in [getattr(command, n) for n in dir(command)]:
560
567
  if (
@@ -609,7 +616,7 @@ class CommandLine:
609
616
  else:
610
617
  util.err(str(e), **config.messaging_opts)
611
618
 
612
- def main(self, argv=None):
619
+ def main(self, argv: Optional[Sequence[str]] = None) -> None:
613
620
  options = self.parser.parse_args(argv)
614
621
  if not hasattr(options, "cmd"):
615
622
  # see http://bugs.python.org/issue9253, argparse
@@ -624,7 +631,11 @@ class CommandLine:
624
631
  self.run_cmd(cfg, options)
625
632
 
626
633
 
627
- def main(argv=None, prog=None, **kwargs):
634
+ def main(
635
+ argv: Optional[Sequence[str]] = None,
636
+ prog: Optional[str] = None,
637
+ **kwargs: Any,
638
+ ) -> None:
628
639
  """The console runner function for Alembic."""
629
640
 
630
641
  CommandLine(prog=prog).main(argv=argv)
alembic/context.pyi CHANGED
@@ -14,6 +14,7 @@ from typing import Mapping
14
14
  from typing import MutableMapping
15
15
  from typing import Optional
16
16
  from typing import overload
17
+ from typing import Sequence
17
18
  from typing import TextIO
18
19
  from typing import Tuple
19
20
  from typing import TYPE_CHECKING
@@ -97,7 +98,7 @@ def configure(
97
98
  tag: Optional[str] = None,
98
99
  template_args: Optional[Dict[str, Any]] = None,
99
100
  render_as_batch: bool = False,
100
- target_metadata: Optional[MetaData] = None,
101
+ target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
101
102
  include_name: Optional[
102
103
  Callable[
103
104
  [
@@ -159,8 +160,8 @@ def configure(
159
160
  MigrationContext,
160
161
  Column[Any],
161
162
  Column[Any],
162
- TypeEngine,
163
- TypeEngine,
163
+ TypeEngine[Any],
164
+ TypeEngine[Any],
164
165
  ],
165
166
  Optional[bool],
166
167
  ],
@@ -635,7 +636,8 @@ def configure(
635
636
  """
636
637
 
637
638
  def execute(
638
- sql: Union[Executable, str], execution_options: Optional[dict] = None
639
+ sql: Union[Executable, str],
640
+ execution_options: Optional[Dict[str, Any]] = None,
639
641
  ) -> None:
640
642
  """Execute the given SQL using the current change context.
641
643
 
@@ -758,7 +760,11 @@ def get_x_argument(
758
760
  The return value is a list, returned directly from the ``argparse``
759
761
  structure. If ``as_dictionary=True`` is passed, the ``x`` arguments
760
762
  are parsed using ``key=value`` format into a dictionary that is
761
- then returned.
763
+ then returned. If there is no ``=`` in the argument, value is an empty
764
+ string.
765
+
766
+ .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when
767
+ arguments are passed without the ``=`` symbol.
762
768
 
763
769
  For example, to support passing a database URL on the command line,
764
770
  the standard ``env.py`` script can be modified like this::
@@ -800,7 +806,7 @@ def is_offline_mode() -> bool:
800
806
 
801
807
  """
802
808
 
803
- def is_transactional_ddl():
809
+ def is_transactional_ddl() -> bool:
804
810
  """Return True if the context is configured to expect a
805
811
  transactional DDL capable backend.
806
812
 
alembic/ddl/__init__.py CHANGED
@@ -3,4 +3,4 @@ from . import mysql
3
3
  from . import oracle
4
4
  from . import postgresql
5
5
  from . import sqlite
6
- from .impl import DefaultImpl
6
+ from .impl import DefaultImpl as DefaultImpl