alembic 1.13.1__py3-none-any.whl → 1.13.3__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 (37) hide show
  1. alembic/__init__.py +1 -1
  2. alembic/autogenerate/api.py +3 -3
  3. alembic/autogenerate/compare.py +1 -1
  4. alembic/autogenerate/render.py +36 -15
  5. alembic/command.py +24 -15
  6. alembic/config.py +5 -10
  7. alembic/ddl/_autogen.py +15 -11
  8. alembic/ddl/base.py +5 -4
  9. alembic/ddl/impl.py +26 -21
  10. alembic/ddl/mysql.py +49 -31
  11. alembic/ddl/oracle.py +5 -3
  12. alembic/ddl/postgresql.py +2 -1
  13. alembic/op.pyi +19 -2
  14. alembic/operations/base.py +24 -10
  15. alembic/operations/ops.py +21 -7
  16. alembic/operations/schemaobj.py +6 -4
  17. alembic/operations/toimpl.py +14 -2
  18. alembic/runtime/environment.py +5 -7
  19. alembic/runtime/migration.py +9 -9
  20. alembic/script/base.py +17 -11
  21. alembic/script/revision.py +25 -18
  22. alembic/templates/async/alembic.ini.mako +4 -3
  23. alembic/templates/generic/alembic.ini.mako +3 -2
  24. alembic/templates/multidb/alembic.ini.mako +3 -2
  25. alembic/testing/assertions.py +13 -4
  26. alembic/testing/fixtures.py +20 -8
  27. alembic/testing/suite/test_autogen_computed.py +1 -0
  28. alembic/testing/suite/test_environment.py +3 -3
  29. alembic/util/langhelpers.py +3 -6
  30. alembic/util/messaging.py +9 -3
  31. alembic/util/sqla_compat.py +3 -5
  32. {alembic-1.13.1.dist-info → alembic-1.13.3.dist-info}/LICENSE +2 -2
  33. {alembic-1.13.1.dist-info → alembic-1.13.3.dist-info}/METADATA +1 -1
  34. {alembic-1.13.1.dist-info → alembic-1.13.3.dist-info}/RECORD +37 -37
  35. {alembic-1.13.1.dist-info → alembic-1.13.3.dist-info}/WHEEL +1 -1
  36. {alembic-1.13.1.dist-info → alembic-1.13.3.dist-info}/entry_points.txt +0 -0
  37. {alembic-1.13.1.dist-info → alembic-1.13.3.dist-info}/top_level.txt +0 -0
@@ -406,8 +406,7 @@ class AbstractOperations(util.ModuleClsProxy):
406
406
  return self.migration_context
407
407
 
408
408
  @overload
409
- def invoke(self, operation: CreateTableOp) -> Table:
410
- ...
409
+ def invoke(self, operation: CreateTableOp) -> Table: ...
411
410
 
412
411
  @overload
413
412
  def invoke(
@@ -427,12 +426,10 @@ class AbstractOperations(util.ModuleClsProxy):
427
426
  DropTableOp,
428
427
  ExecuteSQLOp,
429
428
  ],
430
- ) -> None:
431
- ...
429
+ ) -> None: ...
432
430
 
433
431
  @overload
434
- def invoke(self, operation: MigrateOperation) -> Any:
435
- ...
432
+ def invoke(self, operation: MigrateOperation) -> Any: ...
436
433
 
437
434
  def invoke(self, operation: MigrateOperation) -> Any:
438
435
  """Given a :class:`.MigrateOperation`, invoke it in terms of
@@ -1178,7 +1175,11 @@ class Operations(AbstractOperations):
1178
1175
  ...
1179
1176
 
1180
1177
  def create_table(
1181
- self, table_name: str, *columns: SchemaItem, **kw: Any
1178
+ self,
1179
+ table_name: str,
1180
+ *columns: SchemaItem,
1181
+ if_not_exists: Optional[bool] = None,
1182
+ **kw: Any,
1182
1183
  ) -> Table:
1183
1184
  r"""Issue a "create table" instruction using the current migration
1184
1185
  context.
@@ -1250,6 +1251,10 @@ class Operations(AbstractOperations):
1250
1251
  quoting of the schema outside of the default behavior, use
1251
1252
  the SQLAlchemy construct
1252
1253
  :class:`~sqlalchemy.sql.elements.quoted_name`.
1254
+ :param if_not_exists: If True, adds IF NOT EXISTS operator when
1255
+ creating the new table.
1256
+
1257
+ .. versionadded:: 1.13.3
1253
1258
  :param \**kw: Other keyword arguments are passed to the underlying
1254
1259
  :class:`sqlalchemy.schema.Table` object created for the command.
1255
1260
 
@@ -1441,7 +1446,12 @@ class Operations(AbstractOperations):
1441
1446
  ...
1442
1447
 
1443
1448
  def drop_table(
1444
- self, table_name: str, *, schema: Optional[str] = None, **kw: Any
1449
+ self,
1450
+ table_name: str,
1451
+ *,
1452
+ schema: Optional[str] = None,
1453
+ if_exists: Optional[bool] = None,
1454
+ **kw: Any,
1445
1455
  ) -> None:
1446
1456
  r"""Issue a "drop table" instruction using the current
1447
1457
  migration context.
@@ -1456,6 +1466,10 @@ class Operations(AbstractOperations):
1456
1466
  quoting of the schema outside of the default behavior, use
1457
1467
  the SQLAlchemy construct
1458
1468
  :class:`~sqlalchemy.sql.elements.quoted_name`.
1469
+ :param if_exists: If True, adds IF EXISTS operator when
1470
+ dropping the table.
1471
+
1472
+ .. versionadded:: 1.13.3
1459
1473
  :param \**kw: Other keyword arguments are passed to the underlying
1460
1474
  :class:`sqlalchemy.schema.Table` object created for the command.
1461
1475
 
@@ -1724,7 +1738,7 @@ class BatchOperations(AbstractOperations):
1724
1738
 
1725
1739
  def create_foreign_key(
1726
1740
  self,
1727
- constraint_name: str,
1741
+ constraint_name: Optional[str],
1728
1742
  referent_table: str,
1729
1743
  local_cols: List[str],
1730
1744
  remote_cols: List[str],
@@ -1774,7 +1788,7 @@ class BatchOperations(AbstractOperations):
1774
1788
  ...
1775
1789
 
1776
1790
  def create_primary_key(
1777
- self, constraint_name: str, columns: List[str]
1791
+ self, constraint_name: Optional[str], columns: List[str]
1778
1792
  ) -> None:
1779
1793
  """Issue a "create primary key" instruction using the
1780
1794
  current batch migration context.
alembic/operations/ops.py CHANGED
@@ -349,7 +349,7 @@ class CreatePrimaryKeyOp(AddConstraintOp):
349
349
  def batch_create_primary_key(
350
350
  cls,
351
351
  operations: BatchOperations,
352
- constraint_name: str,
352
+ constraint_name: Optional[str],
353
353
  columns: List[str],
354
354
  ) -> None:
355
355
  """Issue a "create primary key" instruction using the
@@ -681,7 +681,7 @@ class CreateForeignKeyOp(AddConstraintOp):
681
681
  def batch_create_foreign_key(
682
682
  cls,
683
683
  operations: BatchOperations,
684
- constraint_name: str,
684
+ constraint_name: Optional[str],
685
685
  referent_table: str,
686
686
  local_cols: List[str],
687
687
  remote_cols: List[str],
@@ -1159,6 +1159,7 @@ class CreateTableOp(MigrateOperation):
1159
1159
  columns: Sequence[SchemaItem],
1160
1160
  *,
1161
1161
  schema: Optional[str] = None,
1162
+ if_not_exists: Optional[bool] = None,
1162
1163
  _namespace_metadata: Optional[MetaData] = None,
1163
1164
  _constraints_included: bool = False,
1164
1165
  **kw: Any,
@@ -1166,6 +1167,7 @@ class CreateTableOp(MigrateOperation):
1166
1167
  self.table_name = table_name
1167
1168
  self.columns = columns
1168
1169
  self.schema = schema
1170
+ self.if_not_exists = if_not_exists
1169
1171
  self.info = kw.pop("info", {})
1170
1172
  self.comment = kw.pop("comment", None)
1171
1173
  self.prefixes = kw.pop("prefixes", None)
@@ -1228,6 +1230,7 @@ class CreateTableOp(MigrateOperation):
1228
1230
  operations: Operations,
1229
1231
  table_name: str,
1230
1232
  *columns: SchemaItem,
1233
+ if_not_exists: Optional[bool] = None,
1231
1234
  **kw: Any,
1232
1235
  ) -> Table:
1233
1236
  r"""Issue a "create table" instruction using the current migration
@@ -1300,6 +1303,10 @@ class CreateTableOp(MigrateOperation):
1300
1303
  quoting of the schema outside of the default behavior, use
1301
1304
  the SQLAlchemy construct
1302
1305
  :class:`~sqlalchemy.sql.elements.quoted_name`.
1306
+ :param if_not_exists: If True, adds IF NOT EXISTS operator when
1307
+ creating the new table.
1308
+
1309
+ .. versionadded:: 1.13.3
1303
1310
  :param \**kw: Other keyword arguments are passed to the underlying
1304
1311
  :class:`sqlalchemy.schema.Table` object created for the command.
1305
1312
 
@@ -1307,7 +1314,7 @@ class CreateTableOp(MigrateOperation):
1307
1314
  to the parameters given.
1308
1315
 
1309
1316
  """
1310
- op = cls(table_name, columns, **kw)
1317
+ op = cls(table_name, columns, if_not_exists=if_not_exists, **kw)
1311
1318
  return operations.invoke(op)
1312
1319
 
1313
1320
 
@@ -1320,11 +1327,13 @@ class DropTableOp(MigrateOperation):
1320
1327
  table_name: str,
1321
1328
  *,
1322
1329
  schema: Optional[str] = None,
1330
+ if_exists: Optional[bool] = None,
1323
1331
  table_kw: Optional[MutableMapping[Any, Any]] = None,
1324
1332
  _reverse: Optional[CreateTableOp] = None,
1325
1333
  ) -> None:
1326
1334
  self.table_name = table_name
1327
1335
  self.schema = schema
1336
+ self.if_exists = if_exists
1328
1337
  self.table_kw = table_kw or {}
1329
1338
  self.comment = self.table_kw.pop("comment", None)
1330
1339
  self.info = self.table_kw.pop("info", None)
@@ -1371,9 +1380,9 @@ class DropTableOp(MigrateOperation):
1371
1380
  info=self.info.copy() if self.info else {},
1372
1381
  prefixes=list(self.prefixes) if self.prefixes else [],
1373
1382
  schema=self.schema,
1374
- _constraints_included=self._reverse._constraints_included
1375
- if self._reverse
1376
- else False,
1383
+ _constraints_included=(
1384
+ self._reverse._constraints_included if self._reverse else False
1385
+ ),
1377
1386
  **self.table_kw,
1378
1387
  )
1379
1388
  return t
@@ -1385,6 +1394,7 @@ class DropTableOp(MigrateOperation):
1385
1394
  table_name: str,
1386
1395
  *,
1387
1396
  schema: Optional[str] = None,
1397
+ if_exists: Optional[bool] = None,
1388
1398
  **kw: Any,
1389
1399
  ) -> None:
1390
1400
  r"""Issue a "drop table" instruction using the current
@@ -1400,11 +1410,15 @@ class DropTableOp(MigrateOperation):
1400
1410
  quoting of the schema outside of the default behavior, use
1401
1411
  the SQLAlchemy construct
1402
1412
  :class:`~sqlalchemy.sql.elements.quoted_name`.
1413
+ :param if_exists: If True, adds IF EXISTS operator when
1414
+ dropping the table.
1415
+
1416
+ .. versionadded:: 1.13.3
1403
1417
  :param \**kw: Other keyword arguments are passed to the underlying
1404
1418
  :class:`sqlalchemy.schema.Table` object created for the command.
1405
1419
 
1406
1420
  """
1407
- op = cls(table_name, schema=schema, table_kw=kw)
1421
+ op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw)
1408
1422
  operations.invoke(op)
1409
1423
 
1410
1424
 
@@ -223,10 +223,12 @@ class SchemaObjects:
223
223
  t = sa_schema.Table(name, m, *cols, **kw)
224
224
 
225
225
  constraints = [
226
- sqla_compat._copy(elem, target_table=t)
227
- if getattr(elem, "parent", None) is not t
228
- and getattr(elem, "parent", None) is not None
229
- else elem
226
+ (
227
+ sqla_compat._copy(elem, target_table=t)
228
+ if getattr(elem, "parent", None) is not t
229
+ and getattr(elem, "parent", None) is not None
230
+ else elem
231
+ )
230
232
  for elem in columns
231
233
  if isinstance(elem, (Constraint, Index))
232
234
  ]
@@ -79,8 +79,14 @@ def alter_column(
79
79
 
80
80
  @Operations.implementation_for(ops.DropTableOp)
81
81
  def drop_table(operations: "Operations", operation: "ops.DropTableOp") -> None:
82
+ kw = {}
83
+ if operation.if_exists is not None:
84
+ if not sqla_14:
85
+ raise NotImplementedError("SQLAlchemy 1.4+ required")
86
+
87
+ kw["if_exists"] = operation.if_exists
82
88
  operations.impl.drop_table(
83
- operation.to_table(operations.migration_context)
89
+ operation.to_table(operations.migration_context), **kw
84
90
  )
85
91
 
86
92
 
@@ -127,8 +133,14 @@ def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
127
133
  def create_table(
128
134
  operations: "Operations", operation: "ops.CreateTableOp"
129
135
  ) -> "Table":
136
+ kw = {}
137
+ if operation.if_not_exists is not None:
138
+ if not sqla_14:
139
+ raise NotImplementedError("SQLAlchemy 1.4+ required")
140
+
141
+ kw["if_not_exists"] = operation.if_not_exists
130
142
  table = operation.to_table(operations.migration_context)
131
- operations.impl.create_table(table)
143
+ operations.impl.create_table(table, **kw)
132
144
  return table
133
145
 
134
146
 
@@ -108,7 +108,6 @@ CompareType = Callable[
108
108
 
109
109
 
110
110
  class EnvironmentContext(util.ModuleClsProxy):
111
-
112
111
  """A configurational facade made available in an ``env.py`` script.
113
112
 
114
113
  The :class:`.EnvironmentContext` acts as a *facade* to the more
@@ -342,18 +341,17 @@ class EnvironmentContext(util.ModuleClsProxy):
342
341
  return self.context_opts.get("tag", None) # type: ignore[no-any-return] # noqa: E501
343
342
 
344
343
  @overload
345
- def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]:
346
- ...
344
+ def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]: ...
347
345
 
348
346
  @overload
349
- def get_x_argument(self, as_dictionary: Literal[True]) -> Dict[str, str]:
350
- ...
347
+ def get_x_argument(
348
+ self, as_dictionary: Literal[True]
349
+ ) -> Dict[str, str]: ...
351
350
 
352
351
  @overload
353
352
  def get_x_argument(
354
353
  self, as_dictionary: bool = ...
355
- ) -> Union[List[str], Dict[str, str]]:
356
- ...
354
+ ) -> Union[List[str], Dict[str, str]]: ...
357
355
 
358
356
  def get_x_argument(
359
357
  self, as_dictionary: bool = False
@@ -86,7 +86,6 @@ class _ProxyTransaction:
86
86
 
87
87
 
88
88
  class MigrationContext:
89
-
90
89
  """Represent the database state made available to a migration
91
90
  script.
92
91
 
@@ -218,9 +217,11 @@ class MigrationContext:
218
217
  log.info("Generating static SQL")
219
218
  log.info(
220
219
  "Will assume %s DDL.",
221
- "transactional"
222
- if self.impl.transactional_ddl
223
- else "non-transactional",
220
+ (
221
+ "transactional"
222
+ if self.impl.transactional_ddl
223
+ else "non-transactional"
224
+ ),
224
225
  )
225
226
 
226
227
  @classmethod
@@ -345,9 +346,9 @@ class MigrationContext:
345
346
  # except that it will not know it's in "autocommit" and will
346
347
  # emit deprecation warnings when an autocommit action takes
347
348
  # place.
348
- self.connection = (
349
- self.impl.connection
350
- ) = base_connection.execution_options(isolation_level="AUTOCOMMIT")
349
+ self.connection = self.impl.connection = (
350
+ base_connection.execution_options(isolation_level="AUTOCOMMIT")
351
+ )
351
352
 
352
353
  # sqlalchemy future mode will "autobegin" in any case, so take
353
354
  # control of that "transaction" here
@@ -1006,8 +1007,7 @@ class MigrationStep:
1006
1007
  if TYPE_CHECKING:
1007
1008
 
1008
1009
  @property
1009
- def doc(self) -> Optional[str]:
1010
- ...
1010
+ def doc(self) -> Optional[str]: ...
1011
1011
 
1012
1012
  @property
1013
1013
  def name(self) -> str:
alembic/script/base.py CHANGED
@@ -56,7 +56,6 @@ _split_on_space_comma_colon = re.compile(r", *|(?: +)|\:")
56
56
 
57
57
 
58
58
  class ScriptDirectory:
59
-
60
59
  """Provides operations upon an Alembic script directory.
61
60
 
62
61
  This object is useful to get information as to current revisions,
@@ -188,6 +187,7 @@ class ScriptDirectory:
188
187
  split_on_path = {
189
188
  None: None,
190
189
  "space": " ",
190
+ "newline": "\n",
191
191
  "os": os.pathsep,
192
192
  ":": ":",
193
193
  ";": ";",
@@ -201,7 +201,8 @@ class ScriptDirectory:
201
201
  raise ValueError(
202
202
  "'%s' is not a valid value for "
203
203
  "version_path_separator; "
204
- "expected 'space', 'os', ':', ';'" % version_path_separator
204
+ "expected 'space', 'newline', 'os', ':', ';'"
205
+ % version_path_separator
205
206
  ) from ke
206
207
  else:
207
208
  if split_char is None:
@@ -211,7 +212,9 @@ class ScriptDirectory:
211
212
  )
212
213
  else:
213
214
  version_locations = [
214
- x for x in version_locations_str.split(split_char) if x
215
+ x.strip()
216
+ for x in version_locations_str.split(split_char)
217
+ if x
215
218
  ]
216
219
  else:
217
220
  version_locations = None
@@ -610,7 +613,7 @@ class ScriptDirectory:
610
613
  if self.timezone is not None:
611
614
  if ZoneInfo is None:
612
615
  raise util.CommandError(
613
- "Python >= 3.9 is required for timezone support or"
616
+ "Python >= 3.9 is required for timezone support or "
614
617
  "the 'backports.zoneinfo' package must be installed."
615
618
  )
616
619
  # First, assume correct capitalization
@@ -732,9 +735,11 @@ class ScriptDirectory:
732
735
  if depends_on:
733
736
  with self._catch_revision_errors():
734
737
  resolved_depends_on = [
735
- dep
736
- if dep in rev.branch_labels # maintain branch labels
737
- else rev.revision # resolve partial revision identifiers
738
+ (
739
+ dep
740
+ if dep in rev.branch_labels # maintain branch labels
741
+ else rev.revision
742
+ ) # resolve partial revision identifiers
738
743
  for rev, dep in [
739
744
  (not_none(self.revision_map.get_revision(dep)), dep)
740
745
  for dep in util.to_list(depends_on)
@@ -808,7 +813,6 @@ class ScriptDirectory:
808
813
 
809
814
 
810
815
  class Script(revision.Revision):
811
-
812
816
  """Represent a single revision file in a ``versions/`` directory.
813
817
 
814
818
  The :class:`.Script` instance is returned by methods
@@ -930,9 +934,11 @@ class Script(revision.Revision):
930
934
  if head_indicators or tree_indicators:
931
935
  text += "%s%s%s" % (
932
936
  " (head)" if self._is_real_head else "",
933
- " (effective head)"
934
- if self.is_head and not self._is_real_head
935
- else "",
937
+ (
938
+ " (effective head)"
939
+ if self.is_head and not self._is_real_head
940
+ else ""
941
+ ),
936
942
  " (current)" if self._db_current_indicator else "",
937
943
  )
938
944
  if tree_indicators:
@@ -56,8 +56,7 @@ class _CollectRevisionsProtocol(Protocol):
56
56
  inclusive: bool,
57
57
  implicit_base: bool,
58
58
  assert_relative_length: bool,
59
- ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]:
60
- ...
59
+ ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: ...
61
60
 
62
61
 
63
62
  class RevisionError(Exception):
@@ -720,9 +719,11 @@ class RevisionMap:
720
719
  resolved_target = target
721
720
 
722
721
  resolved_test_against_revs = [
723
- self._revision_for_ident(test_against_rev)
724
- if not isinstance(test_against_rev, Revision)
725
- else test_against_rev
722
+ (
723
+ self._revision_for_ident(test_against_rev)
724
+ if not isinstance(test_against_rev, Revision)
725
+ else test_against_rev
726
+ )
726
727
  for test_against_rev in util.to_tuple(
727
728
  test_against_revs, default=()
728
729
  )
@@ -1016,9 +1017,9 @@ class RevisionMap:
1016
1017
  # each time but it was getting complicated
1017
1018
  current_heads[current_candidate_idx] = heads_to_add[0]
1018
1019
  current_heads.extend(heads_to_add[1:])
1019
- ancestors_by_idx[
1020
- current_candidate_idx
1021
- ] = get_ancestors(heads_to_add[0])
1020
+ ancestors_by_idx[current_candidate_idx] = (
1021
+ get_ancestors(heads_to_add[0])
1022
+ )
1022
1023
  ancestors_by_idx.extend(
1023
1024
  get_ancestors(head) for head in heads_to_add[1:]
1024
1025
  )
@@ -1183,9 +1184,13 @@ class RevisionMap:
1183
1184
  branch_label = symbol
1184
1185
  # Walk down the tree to find downgrade target.
1185
1186
  rev = self._walk(
1186
- start=self.get_revision(symbol)
1187
- if branch_label is None
1188
- else self.get_revision("%s@%s" % (branch_label, symbol)),
1187
+ start=(
1188
+ self.get_revision(symbol)
1189
+ if branch_label is None
1190
+ else self.get_revision(
1191
+ "%s@%s" % (branch_label, symbol)
1192
+ )
1193
+ ),
1189
1194
  steps=rel_int,
1190
1195
  no_overwalk=assert_relative_length,
1191
1196
  )
@@ -1303,9 +1308,13 @@ class RevisionMap:
1303
1308
  )
1304
1309
  return (
1305
1310
  self._walk(
1306
- start=self.get_revision(symbol)
1307
- if branch_label is None
1308
- else self.get_revision("%s@%s" % (branch_label, symbol)),
1311
+ start=(
1312
+ self.get_revision(symbol)
1313
+ if branch_label is None
1314
+ else self.get_revision(
1315
+ "%s@%s" % (branch_label, symbol)
1316
+ )
1317
+ ),
1309
1318
  steps=relative,
1310
1319
  no_overwalk=assert_relative_length,
1311
1320
  ),
@@ -1694,15 +1703,13 @@ class Revision:
1694
1703
 
1695
1704
 
1696
1705
  @overload
1697
- def tuple_rev_as_scalar(rev: None) -> None:
1698
- ...
1706
+ def tuple_rev_as_scalar(rev: None) -> None: ...
1699
1707
 
1700
1708
 
1701
1709
  @overload
1702
1710
  def tuple_rev_as_scalar(
1703
1711
  rev: Union[Tuple[_T, ...], List[_T]]
1704
- ) -> Union[_T, Tuple[_T, ...], List[_T]]:
1705
- ...
1712
+ ) -> Union[_T, Tuple[_T, ...], List[_T]]: ...
1706
1713
 
1707
1714
 
1708
1715
  def tuple_rev_as_scalar(
@@ -1,7 +1,8 @@
1
1
  # A generic, single database configuration.
2
2
 
3
3
  [alembic]
4
- # path to migration scripts
4
+ # path to migration scripts.
5
+ # Use forward slashes (/) also on windows to provide an os agnostic path
5
6
  script_location = ${script_location}
6
7
 
7
8
  # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
@@ -20,8 +21,7 @@ prepend_sys_path = .
20
21
  # leave blank for localtime
21
22
  # timezone =
22
23
 
23
- # max length of characters to apply to the
24
- # "slug" field
24
+ # max length of characters to apply to the "slug" field
25
25
  # truncate_slug_length = 40
26
26
 
27
27
  # set to 'true' to run the environment during
@@ -47,6 +47,7 @@ prepend_sys_path = .
47
47
  # version_path_separator = :
48
48
  # version_path_separator = ;
49
49
  # version_path_separator = space
50
+ # version_path_separator = newline
50
51
  version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
51
52
 
52
53
  # set to 'true' to search source files recursively
@@ -2,6 +2,7 @@
2
2
 
3
3
  [alembic]
4
4
  # path to migration scripts
5
+ # Use forward slashes (/) also on windows to provide an os agnostic path
5
6
  script_location = ${script_location}
6
7
 
7
8
  # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
@@ -22,8 +23,7 @@ prepend_sys_path = .
22
23
  # leave blank for localtime
23
24
  # timezone =
24
25
 
25
- # max length of characters to apply to the
26
- # "slug" field
26
+ # max length of characters to apply to the "slug" field
27
27
  # truncate_slug_length = 40
28
28
 
29
29
  # set to 'true' to run the environment during
@@ -49,6 +49,7 @@ prepend_sys_path = .
49
49
  # version_path_separator = :
50
50
  # version_path_separator = ;
51
51
  # version_path_separator = space
52
+ # version_path_separator = newline
52
53
  version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53
54
 
54
55
  # set to 'true' to search source files recursively
@@ -2,6 +2,7 @@
2
2
 
3
3
  [alembic]
4
4
  # path to migration scripts
5
+ # Use forward slashes (/) also on windows to provide an os agnostic path
5
6
  script_location = ${script_location}
6
7
 
7
8
  # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
@@ -22,8 +23,7 @@ prepend_sys_path = .
22
23
  # leave blank for localtime
23
24
  # timezone =
24
25
 
25
- # max length of characters to apply to the
26
- # "slug" field
26
+ # max length of characters to apply to the "slug" field
27
27
  # truncate_slug_length = 40
28
28
 
29
29
  # set to 'true' to run the environment during
@@ -49,6 +49,7 @@ prepend_sys_path = .
49
49
  # version_path_separator = :
50
50
  # version_path_separator = ;
51
51
  # version_path_separator = space
52
+ # version_path_separator = newline
52
53
  version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53
54
 
54
55
  # set to 'true' to search source files recursively
@@ -74,7 +74,9 @@ class _ErrorContainer:
74
74
 
75
75
 
76
76
  @contextlib.contextmanager
77
- def _expect_raises(except_cls, msg=None, check_context=False):
77
+ def _expect_raises(
78
+ except_cls, msg=None, check_context=False, text_exact=False
79
+ ):
78
80
  ec = _ErrorContainer()
79
81
  if check_context:
80
82
  are_we_already_in_a_traceback = sys.exc_info()[0]
@@ -85,7 +87,10 @@ def _expect_raises(except_cls, msg=None, check_context=False):
85
87
  ec.error = err
86
88
  success = True
87
89
  if msg is not None:
88
- assert re.search(msg, str(err), re.UNICODE), f"{msg} !~ {err}"
90
+ if text_exact:
91
+ assert str(err) == msg, f"{msg} != {err}"
92
+ else:
93
+ assert re.search(msg, str(err), re.UNICODE), f"{msg} !~ {err}"
89
94
  if check_context and not are_we_already_in_a_traceback:
90
95
  _assert_proper_exception_context(err)
91
96
  print(str(err).encode("utf-8"))
@@ -98,8 +103,12 @@ def expect_raises(except_cls, check_context=True):
98
103
  return _expect_raises(except_cls, check_context=check_context)
99
104
 
100
105
 
101
- def expect_raises_message(except_cls, msg, check_context=True):
102
- return _expect_raises(except_cls, msg=msg, check_context=check_context)
106
+ def expect_raises_message(
107
+ except_cls, msg, check_context=True, text_exact=False
108
+ ):
109
+ return _expect_raises(
110
+ except_cls, msg=msg, check_context=check_context, text_exact=text_exact
111
+ )
103
112
 
104
113
 
105
114
  def eq_ignore_whitespace(a, b, msg=None):
@@ -49,6 +49,12 @@ class TestBase(SQLAlchemyTestBase):
49
49
  connection, opts=dict(transaction_per_migration=True)
50
50
  )
51
51
 
52
+ @testing.fixture
53
+ def as_sql_migration_context(self, connection):
54
+ return MigrationContext.configure(
55
+ connection, opts=dict(transaction_per_migration=True, as_sql=True)
56
+ )
57
+
52
58
  @testing.fixture
53
59
  def connection(self):
54
60
  with config.db.connect() as conn:
@@ -268,9 +274,11 @@ class AlterColRoundTripFixture:
268
274
  "x",
269
275
  column.name,
270
276
  existing_type=column.type,
271
- existing_server_default=column.server_default
272
- if column.server_default is not None
273
- else False,
277
+ existing_server_default=(
278
+ column.server_default
279
+ if column.server_default is not None
280
+ else False
281
+ ),
274
282
  existing_nullable=True if column.nullable else False,
275
283
  # existing_comment=column.comment,
276
284
  nullable=to_.get("nullable", None),
@@ -298,9 +306,13 @@ class AlterColRoundTripFixture:
298
306
  new_col["type"],
299
307
  new_col.get("default", None),
300
308
  compare.get("type", old_col["type"]),
301
- compare["server_default"].text
302
- if "server_default" in compare
303
- else column.server_default.arg.text
304
- if column.server_default is not None
305
- else None,
309
+ (
310
+ compare["server_default"].text
311
+ if "server_default" in compare
312
+ else (
313
+ column.server_default.arg.text
314
+ if column.server_default is not None
315
+ else None
316
+ )
317
+ ),
306
318
  )
@@ -124,6 +124,7 @@ class AutogenerateComputedTest(AutogenFixtureTest, TestBase):
124
124
  lambda: (None, None),
125
125
  lambda: (sa.Computed("5"), sa.Computed("5")),
126
126
  lambda: (sa.Computed("bar*5"), sa.Computed("bar*5")),
127
+ lambda: (sa.Computed("bar*5"), sa.Computed("bar * \r\n\t5")),
127
128
  (
128
129
  lambda: (sa.Computed("bar*5"), None),
129
130
  config.requirements.computed_doesnt_reflect_as_server_default,