alembic 1.15.2__py3-none-any.whl → 1.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. alembic/__init__.py +1 -1
  2. alembic/autogenerate/compare.py +60 -7
  3. alembic/autogenerate/render.py +25 -4
  4. alembic/command.py +112 -37
  5. alembic/config.py +574 -222
  6. alembic/ddl/base.py +31 -7
  7. alembic/ddl/impl.py +23 -5
  8. alembic/ddl/mssql.py +3 -1
  9. alembic/ddl/mysql.py +8 -4
  10. alembic/ddl/postgresql.py +6 -2
  11. alembic/ddl/sqlite.py +1 -1
  12. alembic/op.pyi +24 -5
  13. alembic/operations/base.py +18 -3
  14. alembic/operations/ops.py +49 -8
  15. alembic/operations/toimpl.py +20 -3
  16. alembic/script/base.py +123 -136
  17. alembic/script/revision.py +1 -1
  18. alembic/script/write_hooks.py +20 -21
  19. alembic/templates/async/alembic.ini.mako +40 -16
  20. alembic/templates/generic/alembic.ini.mako +39 -17
  21. alembic/templates/multidb/alembic.ini.mako +42 -17
  22. alembic/templates/pyproject/README +1 -0
  23. alembic/templates/pyproject/alembic.ini.mako +44 -0
  24. alembic/templates/pyproject/env.py +78 -0
  25. alembic/templates/pyproject/pyproject.toml.mako +76 -0
  26. alembic/templates/pyproject/script.py.mako +28 -0
  27. alembic/testing/__init__.py +2 -0
  28. alembic/testing/assertions.py +4 -0
  29. alembic/testing/env.py +56 -1
  30. alembic/testing/fixtures.py +28 -1
  31. alembic/testing/suite/_autogen_fixtures.py +113 -0
  32. alembic/util/__init__.py +1 -0
  33. alembic/util/compat.py +56 -0
  34. alembic/util/messaging.py +4 -0
  35. alembic/util/pyfiles.py +56 -19
  36. {alembic-1.15.2.dist-info → alembic-1.16.0.dist-info}/METADATA +3 -3
  37. {alembic-1.15.2.dist-info → alembic-1.16.0.dist-info}/RECORD +41 -36
  38. {alembic-1.15.2.dist-info → alembic-1.16.0.dist-info}/WHEEL +1 -1
  39. {alembic-1.15.2.dist-info → alembic-1.16.0.dist-info}/entry_points.txt +0 -0
  40. {alembic-1.15.2.dist-info → alembic-1.16.0.dist-info}/licenses/LICENSE +0 -0
  41. {alembic-1.15.2.dist-info → alembic-1.16.0.dist-info}/top_level.txt +0 -0
alembic/ddl/base.py CHANGED
@@ -154,17 +154,24 @@ class AddColumn(AlterTable):
154
154
  name: str,
155
155
  column: Column[Any],
156
156
  schema: Optional[Union[quoted_name, str]] = None,
157
+ if_not_exists: Optional[bool] = None,
157
158
  ) -> None:
158
159
  super().__init__(name, schema=schema)
159
160
  self.column = column
161
+ self.if_not_exists = if_not_exists
160
162
 
161
163
 
162
164
  class DropColumn(AlterTable):
163
165
  def __init__(
164
- self, name: str, column: Column[Any], schema: Optional[str] = None
166
+ self,
167
+ name: str,
168
+ column: Column[Any],
169
+ schema: Optional[str] = None,
170
+ if_exists: Optional[bool] = None,
165
171
  ) -> None:
166
172
  super().__init__(name, schema=schema)
167
173
  self.column = column
174
+ self.if_exists = if_exists
168
175
 
169
176
 
170
177
  class ColumnComment(AlterColumn):
@@ -189,7 +196,9 @@ def visit_rename_table(
189
196
  def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
190
197
  return "%s %s" % (
191
198
  alter_table(compiler, element.table_name, element.schema),
192
- add_column(compiler, element.column, **kw),
199
+ add_column(
200
+ compiler, element.column, if_not_exists=element.if_not_exists, **kw
201
+ ),
193
202
  )
194
203
 
195
204
 
@@ -197,7 +206,9 @@ def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
197
206
  def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str:
198
207
  return "%s %s" % (
199
208
  alter_table(compiler, element.table_name, element.schema),
200
- drop_column(compiler, element.column.name, **kw),
209
+ drop_column(
210
+ compiler, element.column.name, if_exists=element.if_exists, **kw
211
+ ),
201
212
  )
202
213
 
203
214
 
@@ -320,16 +331,29 @@ def alter_table(
320
331
  return "ALTER TABLE %s" % format_table_name(compiler, name, schema)
321
332
 
322
333
 
323
- def drop_column(compiler: DDLCompiler, name: str, **kw) -> str:
324
- return "DROP COLUMN %s" % format_column_name(compiler, name)
334
+ def drop_column(
335
+ compiler: DDLCompiler, name: str, if_exists: Optional[bool] = None, **kw
336
+ ) -> str:
337
+ return "DROP COLUMN %s%s" % (
338
+ "IF EXISTS " if if_exists else "",
339
+ format_column_name(compiler, name),
340
+ )
325
341
 
326
342
 
327
343
  def alter_column(compiler: DDLCompiler, name: str) -> str:
328
344
  return "ALTER COLUMN %s" % format_column_name(compiler, name)
329
345
 
330
346
 
331
- def add_column(compiler: DDLCompiler, column: Column[Any], **kw) -> str:
332
- text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw)
347
+ def add_column(
348
+ compiler: DDLCompiler,
349
+ column: Column[Any],
350
+ if_not_exists: Optional[bool] = None,
351
+ **kw,
352
+ ) -> str:
353
+ text = "ADD COLUMN %s%s" % (
354
+ "IF NOT EXISTS " if if_not_exists else "",
355
+ compiler.get_column_specification(column, **kw),
356
+ )
333
357
 
334
358
  const = " ".join(
335
359
  compiler.process(constraint) for constraint in column.constraints
alembic/ddl/impl.py CHANGED
@@ -256,8 +256,11 @@ class DefaultImpl(metaclass=ImplMeta):
256
256
  self,
257
257
  table_name: str,
258
258
  column_name: str,
259
+ *,
259
260
  nullable: Optional[bool] = None,
260
- server_default: Union[_ServerDefault, Literal[False]] = False,
261
+ server_default: Optional[
262
+ Union[_ServerDefault, Literal[False]]
263
+ ] = False,
261
264
  name: Optional[str] = None,
262
265
  type_: Optional[TypeEngine] = None,
263
266
  schema: Optional[str] = None,
@@ -368,25 +371,40 @@ class DefaultImpl(metaclass=ImplMeta):
368
371
  self,
369
372
  table_name: str,
370
373
  column: Column[Any],
374
+ *,
371
375
  schema: Optional[Union[str, quoted_name]] = None,
376
+ if_not_exists: Optional[bool] = None,
372
377
  ) -> None:
373
- self._exec(base.AddColumn(table_name, column, schema=schema))
378
+ self._exec(
379
+ base.AddColumn(
380
+ table_name,
381
+ column,
382
+ schema=schema,
383
+ if_not_exists=if_not_exists,
384
+ )
385
+ )
374
386
 
375
387
  def drop_column(
376
388
  self,
377
389
  table_name: str,
378
390
  column: Column[Any],
391
+ *,
379
392
  schema: Optional[str] = None,
393
+ if_exists: Optional[bool] = None,
380
394
  **kw,
381
395
  ) -> None:
382
- self._exec(base.DropColumn(table_name, column, schema=schema))
396
+ self._exec(
397
+ base.DropColumn(
398
+ table_name, column, schema=schema, if_exists=if_exists
399
+ )
400
+ )
383
401
 
384
402
  def add_constraint(self, const: Any) -> None:
385
403
  if const._create_rule is None or const._create_rule(self):
386
404
  self._exec(schema.AddConstraint(const))
387
405
 
388
- def drop_constraint(self, const: Constraint) -> None:
389
- self._exec(schema.DropConstraint(const))
406
+ def drop_constraint(self, const: Constraint, **kw: Any) -> None:
407
+ self._exec(schema.DropConstraint(const, **kw))
390
408
 
391
409
  def rename_table(
392
410
  self,
alembic/ddl/mssql.py CHANGED
@@ -83,10 +83,11 @@ class MSSQLImpl(DefaultImpl):
83
83
  if self.as_sql and self.batch_separator:
84
84
  self.static_output(self.batch_separator)
85
85
 
86
- def alter_column( # type:ignore[override]
86
+ def alter_column(
87
87
  self,
88
88
  table_name: str,
89
89
  column_name: str,
90
+ *,
90
91
  nullable: Optional[bool] = None,
91
92
  server_default: Optional[
92
93
  Union[_ServerDefault, Literal[False]]
@@ -202,6 +203,7 @@ class MSSQLImpl(DefaultImpl):
202
203
  self,
203
204
  table_name: str,
204
205
  column: Column[Any],
206
+ *,
205
207
  schema: Optional[str] = None,
206
208
  **kw,
207
209
  ) -> None:
alembic/ddl/mysql.py CHANGED
@@ -47,12 +47,15 @@ class MySQLImpl(DefaultImpl):
47
47
  )
48
48
  type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"]
49
49
 
50
- def alter_column( # type:ignore[override]
50
+ def alter_column(
51
51
  self,
52
52
  table_name: str,
53
53
  column_name: str,
54
+ *,
54
55
  nullable: Optional[bool] = None,
55
- server_default: Union[_ServerDefault, Literal[False]] = False,
56
+ server_default: Optional[
57
+ Union[_ServerDefault, Literal[False]]
58
+ ] = False,
56
59
  name: Optional[str] = None,
57
60
  type_: Optional[TypeEngine] = None,
58
61
  schema: Optional[str] = None,
@@ -165,6 +168,7 @@ class MySQLImpl(DefaultImpl):
165
168
  def drop_constraint(
166
169
  self,
167
170
  const: Constraint,
171
+ **kw: Any,
168
172
  ) -> None:
169
173
  if isinstance(const, schema.CheckConstraint) and _is_type_bound(const):
170
174
  return
@@ -174,7 +178,7 @@ class MySQLImpl(DefaultImpl):
174
178
  def _is_mysql_allowed_functional_default(
175
179
  self,
176
180
  type_: Optional[TypeEngine],
177
- server_default: Union[_ServerDefault, Literal[False]],
181
+ server_default: Optional[Union[_ServerDefault, Literal[False]]],
178
182
  ) -> bool:
179
183
  return (
180
184
  type_ is not None
@@ -325,7 +329,7 @@ class MySQLAlterDefault(AlterColumn):
325
329
  self,
326
330
  name: str,
327
331
  column_name: str,
328
- default: _ServerDefault,
332
+ default: Optional[_ServerDefault],
329
333
  schema: Optional[str] = None,
330
334
  ) -> None:
331
335
  super(AlterColumn, self).__init__(name, schema=schema)
alembic/ddl/postgresql.py CHANGED
@@ -52,6 +52,7 @@ from ..operations.base import Operations
52
52
  from ..util import sqla_compat
53
53
  from ..util.sqla_compat import compiles
54
54
 
55
+
55
56
  if TYPE_CHECKING:
56
57
  from typing import Literal
57
58
 
@@ -148,12 +149,15 @@ class PostgresqlImpl(DefaultImpl):
148
149
  select(literal_column(conn_col_default) == metadata_default)
149
150
  )
150
151
 
151
- def alter_column( # type:ignore[override]
152
+ def alter_column(
152
153
  self,
153
154
  table_name: str,
154
155
  column_name: str,
156
+ *,
155
157
  nullable: Optional[bool] = None,
156
- server_default: Union[_ServerDefault, Literal[False]] = False,
158
+ server_default: Optional[
159
+ Union[_ServerDefault, Literal[False]]
160
+ ] = False,
157
161
  name: Optional[str] = None,
158
162
  type_: Optional[TypeEngine] = None,
159
163
  schema: Optional[str] = None,
alembic/ddl/sqlite.py CHANGED
@@ -91,7 +91,7 @@ class SQLiteImpl(DefaultImpl):
91
91
  "SQLite migrations using a copy-and-move strategy."
92
92
  )
93
93
 
94
- def drop_constraint(self, const: Constraint):
94
+ def drop_constraint(self, const: Constraint, **kw: Any):
95
95
  if const._create_rule is None:
96
96
  raise NotImplementedError(
97
97
  "No support for ALTER of constraints in SQLite dialect. "
alembic/op.pyi CHANGED
@@ -60,7 +60,11 @@ _C = TypeVar("_C", bound=Callable[..., Any])
60
60
  ### end imports ###
61
61
 
62
62
  def add_column(
63
- table_name: str, column: Column[Any], *, schema: Optional[str] = None
63
+ table_name: str,
64
+ column: Column[Any],
65
+ *,
66
+ schema: Optional[str] = None,
67
+ if_not_exists: Optional[bool] = None,
64
68
  ) -> None:
65
69
  """Issue an "add column" instruction using the current
66
70
  migration context.
@@ -137,6 +141,10 @@ def add_column(
137
141
  quoting of the schema outside of the default behavior, use
138
142
  the SQLAlchemy construct
139
143
  :class:`~sqlalchemy.sql.elements.quoted_name`.
144
+ :param if_not_exists: If True, adds IF NOT EXISTS operator
145
+ when creating the new column for compatible dialects
146
+
147
+ .. versionadded:: 1.16.0
140
148
 
141
149
  """
142
150
 
@@ -146,7 +154,9 @@ def alter_column(
146
154
  *,
147
155
  nullable: Optional[bool] = None,
148
156
  comment: Union[str, Literal[False], None] = False,
149
- server_default: Union[str, bool, Identity, Computed, TextClause] = False,
157
+ server_default: Union[
158
+ str, bool, Identity, Computed, TextClause, None
159
+ ] = False,
150
160
  new_column_name: Optional[str] = None,
151
161
  type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
152
162
  existing_type: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
@@ -925,6 +935,11 @@ def drop_column(
925
935
  quoting of the schema outside of the default behavior, use
926
936
  the SQLAlchemy construct
927
937
  :class:`~sqlalchemy.sql.elements.quoted_name`.
938
+ :param if_exists: If True, adds IF EXISTS operator when
939
+ dropping the new column for compatible dialects
940
+
941
+ .. versionadded:: 1.16.0
942
+
928
943
  :param mssql_drop_check: Optional boolean. When ``True``, on
929
944
  Microsoft SQL Server only, first
930
945
  drop the CHECK constraint on the column using a
@@ -946,7 +961,6 @@ def drop_column(
946
961
  then exec's a separate DROP CONSTRAINT for that default. Only
947
962
  works if the column has exactly one FK constraint which refers to
948
963
  it, at the moment.
949
-
950
964
  """
951
965
 
952
966
  def drop_constraint(
@@ -955,6 +969,7 @@ def drop_constraint(
955
969
  type_: Optional[str] = None,
956
970
  *,
957
971
  schema: Optional[str] = None,
972
+ if_exists: Optional[bool] = None,
958
973
  ) -> None:
959
974
  r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
960
975
 
@@ -966,6 +981,10 @@ def drop_constraint(
966
981
  quoting of the schema outside of the default behavior, use
967
982
  the SQLAlchemy construct
968
983
  :class:`~sqlalchemy.sql.elements.quoted_name`.
984
+ :param if_exists: If True, adds IF EXISTS operator when
985
+ dropping the constraint
986
+
987
+ .. versionadded:: 1.16.0
969
988
 
970
989
  """
971
990
 
@@ -1165,7 +1184,7 @@ def f(name: str) -> conv:
1165
1184
  names will be converted along conventions. If the ``target_metadata``
1166
1185
  contains the naming convention
1167
1186
  ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the
1168
- output of the following:
1187
+ output of the following::
1169
1188
 
1170
1189
  op.add_column("t", "x", Boolean(name="x"))
1171
1190
 
@@ -1269,7 +1288,7 @@ def invoke(
1269
1288
  BulkInsertOp,
1270
1289
  DropTableOp,
1271
1290
  ExecuteSQLOp,
1272
- ]
1291
+ ],
1273
1292
  ) -> None: ...
1274
1293
  @overload
1275
1294
  def invoke(operation: MigrateOperation) -> Any:
@@ -464,7 +464,7 @@ class AbstractOperations(util.ModuleClsProxy):
464
464
  names will be converted along conventions. If the ``target_metadata``
465
465
  contains the naming convention
466
466
  ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the
467
- output of the following:
467
+ output of the following::
468
468
 
469
469
  op.add_column("t", "x", Boolean(name="x"))
470
470
 
@@ -618,6 +618,7 @@ class Operations(AbstractOperations):
618
618
  column: Column[Any],
619
619
  *,
620
620
  schema: Optional[str] = None,
621
+ if_not_exists: Optional[bool] = None,
621
622
  ) -> None:
622
623
  """Issue an "add column" instruction using the current
623
624
  migration context.
@@ -694,6 +695,10 @@ class Operations(AbstractOperations):
694
695
  quoting of the schema outside of the default behavior, use
695
696
  the SQLAlchemy construct
696
697
  :class:`~sqlalchemy.sql.elements.quoted_name`.
698
+ :param if_not_exists: If True, adds IF NOT EXISTS operator
699
+ when creating the new column for compatible dialects
700
+
701
+ .. versionadded:: 1.16.0
697
702
 
698
703
  """ # noqa: E501
699
704
  ...
@@ -706,7 +711,7 @@ class Operations(AbstractOperations):
706
711
  nullable: Optional[bool] = None,
707
712
  comment: Union[str, Literal[False], None] = False,
708
713
  server_default: Union[
709
- str, bool, Identity, Computed, TextClause
714
+ str, bool, Identity, Computed, TextClause, None
710
715
  ] = False,
711
716
  new_column_name: Optional[str] = None,
712
717
  type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
@@ -1361,6 +1366,11 @@ class Operations(AbstractOperations):
1361
1366
  quoting of the schema outside of the default behavior, use
1362
1367
  the SQLAlchemy construct
1363
1368
  :class:`~sqlalchemy.sql.elements.quoted_name`.
1369
+ :param if_exists: If True, adds IF EXISTS operator when
1370
+ dropping the new column for compatible dialects
1371
+
1372
+ .. versionadded:: 1.16.0
1373
+
1364
1374
  :param mssql_drop_check: Optional boolean. When ``True``, on
1365
1375
  Microsoft SQL Server only, first
1366
1376
  drop the CHECK constraint on the column using a
@@ -1382,7 +1392,6 @@ class Operations(AbstractOperations):
1382
1392
  then exec's a separate DROP CONSTRAINT for that default. Only
1383
1393
  works if the column has exactly one FK constraint which refers to
1384
1394
  it, at the moment.
1385
-
1386
1395
  """ # noqa: E501
1387
1396
  ...
1388
1397
 
@@ -1393,6 +1402,7 @@ class Operations(AbstractOperations):
1393
1402
  type_: Optional[str] = None,
1394
1403
  *,
1395
1404
  schema: Optional[str] = None,
1405
+ if_exists: Optional[bool] = None,
1396
1406
  ) -> None:
1397
1407
  r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
1398
1408
 
@@ -1404,6 +1414,10 @@ class Operations(AbstractOperations):
1404
1414
  quoting of the schema outside of the default behavior, use
1405
1415
  the SQLAlchemy construct
1406
1416
  :class:`~sqlalchemy.sql.elements.quoted_name`.
1417
+ :param if_exists: If True, adds IF EXISTS operator when
1418
+ dropping the constraint
1419
+
1420
+ .. versionadded:: 1.16.0
1407
1421
 
1408
1422
  """ # noqa: E501
1409
1423
  ...
@@ -1646,6 +1660,7 @@ class BatchOperations(AbstractOperations):
1646
1660
  *,
1647
1661
  insert_before: Optional[str] = None,
1648
1662
  insert_after: Optional[str] = None,
1663
+ if_not_exists: Optional[bool] = None,
1649
1664
  ) -> None:
1650
1665
  """Issue an "add column" instruction using the current
1651
1666
  batch migration context.
alembic/operations/ops.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import abstractmethod
4
+ import os
5
+ import pathlib
4
6
  import re
5
7
  from typing import Any
6
8
  from typing import Callable
@@ -140,12 +142,14 @@ class DropConstraintOp(MigrateOperation):
140
142
  type_: Optional[str] = None,
141
143
  *,
142
144
  schema: Optional[str] = None,
145
+ if_exists: Optional[bool] = None,
143
146
  _reverse: Optional[AddConstraintOp] = None,
144
147
  ) -> None:
145
148
  self.constraint_name = constraint_name
146
149
  self.table_name = table_name
147
150
  self.constraint_type = type_
148
151
  self.schema = schema
152
+ self.if_exists = if_exists
149
153
  self._reverse = _reverse
150
154
 
151
155
  def reverse(self) -> AddConstraintOp:
@@ -203,6 +207,7 @@ class DropConstraintOp(MigrateOperation):
203
207
  type_: Optional[str] = None,
204
208
  *,
205
209
  schema: Optional[str] = None,
210
+ if_exists: Optional[bool] = None,
206
211
  ) -> None:
207
212
  r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
208
213
 
@@ -214,10 +219,20 @@ class DropConstraintOp(MigrateOperation):
214
219
  quoting of the schema outside of the default behavior, use
215
220
  the SQLAlchemy construct
216
221
  :class:`~sqlalchemy.sql.elements.quoted_name`.
222
+ :param if_exists: If True, adds IF EXISTS operator when
223
+ dropping the constraint
224
+
225
+ .. versionadded:: 1.16.0
217
226
 
218
227
  """
219
228
 
220
- op = cls(constraint_name, table_name, type_=type_, schema=schema)
229
+ op = cls(
230
+ constraint_name,
231
+ table_name,
232
+ type_=type_,
233
+ schema=schema,
234
+ if_exists=if_exists,
235
+ )
221
236
  return operations.invoke(op)
222
237
 
223
238
  @classmethod
@@ -1841,7 +1856,7 @@ class AlterColumnOp(AlterTableOp):
1841
1856
  nullable: Optional[bool] = None,
1842
1857
  comment: Optional[Union[str, Literal[False]]] = False,
1843
1858
  server_default: Union[
1844
- str, bool, Identity, Computed, TextClause
1859
+ str, bool, Identity, Computed, TextClause, None
1845
1860
  ] = False,
1846
1861
  new_column_name: Optional[str] = None,
1847
1862
  type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
@@ -2034,16 +2049,20 @@ class AddColumnOp(AlterTableOp):
2034
2049
  column: Column[Any],
2035
2050
  *,
2036
2051
  schema: Optional[str] = None,
2052
+ if_not_exists: Optional[bool] = None,
2037
2053
  **kw: Any,
2038
2054
  ) -> None:
2039
2055
  super().__init__(table_name, schema=schema)
2040
2056
  self.column = column
2057
+ self.if_not_exists = if_not_exists
2041
2058
  self.kw = kw
2042
2059
 
2043
2060
  def reverse(self) -> DropColumnOp:
2044
- return DropColumnOp.from_column_and_tablename(
2061
+ op = DropColumnOp.from_column_and_tablename(
2045
2062
  self.schema, self.table_name, self.column
2046
2063
  )
2064
+ op.if_exists = self.if_not_exists
2065
+ return op
2047
2066
 
2048
2067
  def to_diff_tuple(
2049
2068
  self,
@@ -2074,6 +2093,7 @@ class AddColumnOp(AlterTableOp):
2074
2093
  column: Column[Any],
2075
2094
  *,
2076
2095
  schema: Optional[str] = None,
2096
+ if_not_exists: Optional[bool] = None,
2077
2097
  ) -> None:
2078
2098
  """Issue an "add column" instruction using the current
2079
2099
  migration context.
@@ -2150,10 +2170,19 @@ class AddColumnOp(AlterTableOp):
2150
2170
  quoting of the schema outside of the default behavior, use
2151
2171
  the SQLAlchemy construct
2152
2172
  :class:`~sqlalchemy.sql.elements.quoted_name`.
2173
+ :param if_not_exists: If True, adds IF NOT EXISTS operator
2174
+ when creating the new column for compatible dialects
2175
+
2176
+ .. versionadded:: 1.16.0
2153
2177
 
2154
2178
  """
2155
2179
 
2156
- op = cls(table_name, column, schema=schema)
2180
+ op = cls(
2181
+ table_name,
2182
+ column,
2183
+ schema=schema,
2184
+ if_not_exists=if_not_exists,
2185
+ )
2157
2186
  return operations.invoke(op)
2158
2187
 
2159
2188
  @classmethod
@@ -2164,6 +2193,7 @@ class AddColumnOp(AlterTableOp):
2164
2193
  *,
2165
2194
  insert_before: Optional[str] = None,
2166
2195
  insert_after: Optional[str] = None,
2196
+ if_not_exists: Optional[bool] = None,
2167
2197
  ) -> None:
2168
2198
  """Issue an "add column" instruction using the current
2169
2199
  batch migration context.
@@ -2184,6 +2214,7 @@ class AddColumnOp(AlterTableOp):
2184
2214
  operations.impl.table_name,
2185
2215
  column,
2186
2216
  schema=operations.impl.schema,
2217
+ if_not_exists=if_not_exists,
2187
2218
  **kw,
2188
2219
  )
2189
2220
  return operations.invoke(op)
@@ -2200,12 +2231,14 @@ class DropColumnOp(AlterTableOp):
2200
2231
  column_name: str,
2201
2232
  *,
2202
2233
  schema: Optional[str] = None,
2234
+ if_exists: Optional[bool] = None,
2203
2235
  _reverse: Optional[AddColumnOp] = None,
2204
2236
  **kw: Any,
2205
2237
  ) -> None:
2206
2238
  super().__init__(table_name, schema=schema)
2207
2239
  self.column_name = column_name
2208
2240
  self.kw = kw
2241
+ self.if_exists = if_exists
2209
2242
  self._reverse = _reverse
2210
2243
 
2211
2244
  def to_diff_tuple(
@@ -2225,9 +2258,11 @@ class DropColumnOp(AlterTableOp):
2225
2258
  "original column is not present"
2226
2259
  )
2227
2260
 
2228
- return AddColumnOp.from_column_and_tablename(
2261
+ op = AddColumnOp.from_column_and_tablename(
2229
2262
  self.schema, self.table_name, self._reverse.column
2230
2263
  )
2264
+ op.if_not_exists = self.if_exists
2265
+ return op
2231
2266
 
2232
2267
  @classmethod
2233
2268
  def from_column_and_tablename(
@@ -2274,6 +2309,11 @@ class DropColumnOp(AlterTableOp):
2274
2309
  quoting of the schema outside of the default behavior, use
2275
2310
  the SQLAlchemy construct
2276
2311
  :class:`~sqlalchemy.sql.elements.quoted_name`.
2312
+ :param if_exists: If True, adds IF EXISTS operator when
2313
+ dropping the new column for compatible dialects
2314
+
2315
+ .. versionadded:: 1.16.0
2316
+
2277
2317
  :param mssql_drop_check: Optional boolean. When ``True``, on
2278
2318
  Microsoft SQL Server only, first
2279
2319
  drop the CHECK constraint on the column using a
@@ -2295,7 +2335,6 @@ class DropColumnOp(AlterTableOp):
2295
2335
  then exec's a separate DROP CONSTRAINT for that default. Only
2296
2336
  works if the column has exactly one FK constraint which refers to
2297
2337
  it, at the moment.
2298
-
2299
2338
  """
2300
2339
 
2301
2340
  op = cls(table_name, column_name, schema=schema, **kw)
@@ -2710,7 +2749,7 @@ class MigrationScript(MigrateOperation):
2710
2749
  head: Optional[str] = None,
2711
2750
  splice: Optional[bool] = None,
2712
2751
  branch_label: Optional[_RevIdType] = None,
2713
- version_path: Optional[str] = None,
2752
+ version_path: Union[str, os.PathLike[str], None] = None,
2714
2753
  depends_on: Optional[_RevIdType] = None,
2715
2754
  ) -> None:
2716
2755
  self.rev_id = rev_id
@@ -2719,7 +2758,9 @@ class MigrationScript(MigrateOperation):
2719
2758
  self.head = head
2720
2759
  self.splice = splice
2721
2760
  self.branch_label = branch_label
2722
- self.version_path = version_path
2761
+ self.version_path = (
2762
+ pathlib.Path(version_path).as_posix() if version_path else None
2763
+ )
2723
2764
  self.depends_on = depends_on
2724
2765
  self.upgrade_ops = upgrade_ops
2725
2766
  self.downgrade_ops = downgrade_ops
@@ -8,6 +8,7 @@ from sqlalchemy import schema as sa_schema
8
8
  from . import ops
9
9
  from .base import Operations
10
10
  from ..util.sqla_compat import _copy
11
+ from ..util.sqla_compat import sqla_2
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from sqlalchemy.sql.schema import Table
@@ -92,7 +93,11 @@ def drop_column(
92
93
  ) -> None:
93
94
  column = operation.to_column(operations.migration_context)
94
95
  operations.impl.drop_column(
95
- operation.table_name, column, schema=operation.schema, **operation.kw
96
+ operation.table_name,
97
+ column,
98
+ schema=operation.schema,
99
+ if_exists=operation.if_exists,
100
+ **operation.kw,
96
101
  )
97
102
 
98
103
 
@@ -167,7 +172,13 @@ def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None:
167
172
  column = _copy(column)
168
173
 
169
174
  t = operations.schema_obj.table(table_name, column, schema=schema)
170
- operations.impl.add_column(table_name, column, schema=schema, **kw)
175
+ operations.impl.add_column(
176
+ table_name,
177
+ column,
178
+ schema=schema,
179
+ if_not_exists=operation.if_not_exists,
180
+ **kw,
181
+ )
171
182
 
172
183
  for constraint in t.constraints:
173
184
  if not isinstance(constraint, sa_schema.PrimaryKeyConstraint):
@@ -197,13 +208,19 @@ def create_constraint(
197
208
  def drop_constraint(
198
209
  operations: "Operations", operation: "ops.DropConstraintOp"
199
210
  ) -> None:
211
+ kw = {}
212
+ if operation.if_exists is not None:
213
+ if not sqla_2:
214
+ raise NotImplementedError("SQLAlchemy 2.0 required")
215
+ kw["if_exists"] = operation.if_exists
200
216
  operations.impl.drop_constraint(
201
217
  operations.schema_obj.generic_constraint(
202
218
  operation.constraint_name,
203
219
  operation.table_name,
204
220
  operation.constraint_type,
205
221
  schema=operation.schema,
206
- )
222
+ ),
223
+ **kw,
207
224
  )
208
225
 
209
226