ops.py 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842
  1. from __future__ import annotations
  2. from abc import abstractmethod
  3. import os
  4. import pathlib
  5. import re
  6. from typing import Any
  7. from typing import Callable
  8. from typing import cast
  9. from typing import Dict
  10. from typing import FrozenSet
  11. from typing import Iterator
  12. from typing import List
  13. from typing import MutableMapping
  14. from typing import Optional
  15. from typing import Sequence
  16. from typing import Set
  17. from typing import Tuple
  18. from typing import Type
  19. from typing import TYPE_CHECKING
  20. from typing import TypeVar
  21. from typing import Union
  22. from sqlalchemy.types import NULLTYPE
  23. from . import schemaobj
  24. from .base import BatchOperations
  25. from .base import Operations
  26. from .. import util
  27. from ..util import sqla_compat
  28. if TYPE_CHECKING:
  29. from typing import Literal
  30. from sqlalchemy.sql import Executable
  31. from sqlalchemy.sql.elements import ColumnElement
  32. from sqlalchemy.sql.elements import conv
  33. from sqlalchemy.sql.elements import quoted_name
  34. from sqlalchemy.sql.elements import TextClause
  35. from sqlalchemy.sql.schema import CheckConstraint
  36. from sqlalchemy.sql.schema import Column
  37. from sqlalchemy.sql.schema import Computed
  38. from sqlalchemy.sql.schema import Constraint
  39. from sqlalchemy.sql.schema import ForeignKeyConstraint
  40. from sqlalchemy.sql.schema import Identity
  41. from sqlalchemy.sql.schema import Index
  42. from sqlalchemy.sql.schema import MetaData
  43. from sqlalchemy.sql.schema import PrimaryKeyConstraint
  44. from sqlalchemy.sql.schema import SchemaItem
  45. from sqlalchemy.sql.schema import Table
  46. from sqlalchemy.sql.schema import UniqueConstraint
  47. from sqlalchemy.sql.selectable import TableClause
  48. from sqlalchemy.sql.type_api import TypeEngine
  49. from ..autogenerate.rewriter import Rewriter
  50. from ..runtime.migration import MigrationContext
  51. from ..script.revision import _RevIdType
  52. _T = TypeVar("_T", bound=Any)
  53. _AC = TypeVar("_AC", bound="AddConstraintOp")
  54. class MigrateOperation:
  55. """base class for migration command and organization objects.
  56. This system is part of the operation extensibility API.
  57. .. seealso::
  58. :ref:`operation_objects`
  59. :ref:`operation_plugins`
  60. :ref:`customizing_revision`
  61. """
  62. @util.memoized_property
  63. def info(self) -> Dict[Any, Any]:
  64. """A dictionary that may be used to store arbitrary information
  65. along with this :class:`.MigrateOperation` object.
  66. """
  67. return {}
  68. _mutations: FrozenSet[Rewriter] = frozenset()
  69. def reverse(self) -> MigrateOperation:
  70. raise NotImplementedError
  71. def to_diff_tuple(self) -> Tuple[Any, ...]:
  72. raise NotImplementedError
  73. class AddConstraintOp(MigrateOperation):
  74. """Represent an add constraint operation."""
  75. add_constraint_ops = util.Dispatcher()
  76. @property
  77. def constraint_type(self) -> str:
  78. raise NotImplementedError()
  79. @classmethod
  80. def register_add_constraint(
  81. cls, type_: str
  82. ) -> Callable[[Type[_AC]], Type[_AC]]:
  83. def go(klass: Type[_AC]) -> Type[_AC]:
  84. cls.add_constraint_ops.dispatch_for(type_)(klass.from_constraint)
  85. return klass
  86. return go
  87. @classmethod
  88. def from_constraint(cls, constraint: Constraint) -> AddConstraintOp:
  89. return cls.add_constraint_ops.dispatch(constraint.__visit_name__)( # type: ignore[no-any-return] # noqa: E501
  90. constraint
  91. )
  92. @abstractmethod
  93. def to_constraint(
  94. self, migration_context: Optional[MigrationContext] = None
  95. ) -> Constraint:
  96. pass
  97. def reverse(self) -> DropConstraintOp:
  98. return DropConstraintOp.from_constraint(self.to_constraint())
  99. def to_diff_tuple(self) -> Tuple[str, Constraint]:
  100. return ("add_constraint", self.to_constraint())
  101. @Operations.register_operation("drop_constraint")
  102. @BatchOperations.register_operation("drop_constraint", "batch_drop_constraint")
  103. class DropConstraintOp(MigrateOperation):
  104. """Represent a drop constraint operation."""
  105. def __init__(
  106. self,
  107. constraint_name: Optional[sqla_compat._ConstraintNameDefined],
  108. table_name: str,
  109. type_: Optional[str] = None,
  110. *,
  111. schema: Optional[str] = None,
  112. if_exists: Optional[bool] = None,
  113. _reverse: Optional[AddConstraintOp] = None,
  114. ) -> None:
  115. self.constraint_name = constraint_name
  116. self.table_name = table_name
  117. self.constraint_type = type_
  118. self.schema = schema
  119. self.if_exists = if_exists
  120. self._reverse = _reverse
  121. def reverse(self) -> AddConstraintOp:
  122. return AddConstraintOp.from_constraint(self.to_constraint())
  123. def to_diff_tuple(
  124. self,
  125. ) -> Tuple[str, SchemaItem]:
  126. if self.constraint_type == "foreignkey":
  127. return ("remove_fk", self.to_constraint())
  128. else:
  129. return ("remove_constraint", self.to_constraint())
  130. @classmethod
  131. def from_constraint(cls, constraint: Constraint) -> DropConstraintOp:
  132. types = {
  133. "unique_constraint": "unique",
  134. "foreign_key_constraint": "foreignkey",
  135. "primary_key_constraint": "primary",
  136. "check_constraint": "check",
  137. "column_check_constraint": "check",
  138. "table_or_column_check_constraint": "check",
  139. }
  140. constraint_table = sqla_compat._table_for_constraint(constraint)
  141. return cls(
  142. sqla_compat.constraint_name_or_none(constraint.name),
  143. constraint_table.name,
  144. schema=constraint_table.schema,
  145. type_=types.get(constraint.__visit_name__),
  146. _reverse=AddConstraintOp.from_constraint(constraint),
  147. )
  148. def to_constraint(self) -> Constraint:
  149. if self._reverse is not None:
  150. constraint = self._reverse.to_constraint()
  151. constraint.name = self.constraint_name
  152. constraint_table = sqla_compat._table_for_constraint(constraint)
  153. constraint_table.name = self.table_name
  154. constraint_table.schema = self.schema
  155. return constraint
  156. else:
  157. raise ValueError(
  158. "constraint cannot be produced; "
  159. "original constraint is not present"
  160. )
  161. @classmethod
  162. def drop_constraint(
  163. cls,
  164. operations: Operations,
  165. constraint_name: str,
  166. table_name: str,
  167. type_: Optional[str] = None,
  168. *,
  169. schema: Optional[str] = None,
  170. if_exists: Optional[bool] = None,
  171. ) -> None:
  172. r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
  173. :param constraint_name: name of the constraint.
  174. :param table_name: table name.
  175. :param type\_: optional, required on MySQL. can be
  176. 'foreignkey', 'primary', 'unique', or 'check'.
  177. :param schema: Optional schema name to operate within. To control
  178. quoting of the schema outside of the default behavior, use
  179. the SQLAlchemy construct
  180. :class:`~sqlalchemy.sql.elements.quoted_name`.
  181. :param if_exists: If True, adds IF EXISTS operator when
  182. dropping the constraint
  183. .. versionadded:: 1.16.0
  184. """
  185. op = cls(
  186. constraint_name,
  187. table_name,
  188. type_=type_,
  189. schema=schema,
  190. if_exists=if_exists,
  191. )
  192. return operations.invoke(op)
  193. @classmethod
  194. def batch_drop_constraint(
  195. cls,
  196. operations: BatchOperations,
  197. constraint_name: str,
  198. type_: Optional[str] = None,
  199. ) -> None:
  200. """Issue a "drop constraint" instruction using the
  201. current batch migration context.
  202. The batch form of this call omits the ``table_name`` and ``schema``
  203. arguments from the call.
  204. .. seealso::
  205. :meth:`.Operations.drop_constraint`
  206. """
  207. op = cls(
  208. constraint_name,
  209. operations.impl.table_name,
  210. type_=type_,
  211. schema=operations.impl.schema,
  212. )
  213. return operations.invoke(op)
  214. @Operations.register_operation("create_primary_key")
  215. @BatchOperations.register_operation(
  216. "create_primary_key", "batch_create_primary_key"
  217. )
  218. @AddConstraintOp.register_add_constraint("primary_key_constraint")
  219. class CreatePrimaryKeyOp(AddConstraintOp):
  220. """Represent a create primary key operation."""
  221. constraint_type = "primarykey"
  222. def __init__(
  223. self,
  224. constraint_name: Optional[sqla_compat._ConstraintNameDefined],
  225. table_name: str,
  226. columns: Sequence[str],
  227. *,
  228. schema: Optional[str] = None,
  229. **kw: Any,
  230. ) -> None:
  231. self.constraint_name = constraint_name
  232. self.table_name = table_name
  233. self.columns = columns
  234. self.schema = schema
  235. self.kw = kw
  236. @classmethod
  237. def from_constraint(cls, constraint: Constraint) -> CreatePrimaryKeyOp:
  238. constraint_table = sqla_compat._table_for_constraint(constraint)
  239. pk_constraint = cast("PrimaryKeyConstraint", constraint)
  240. return cls(
  241. sqla_compat.constraint_name_or_none(pk_constraint.name),
  242. constraint_table.name,
  243. pk_constraint.columns.keys(),
  244. schema=constraint_table.schema,
  245. **pk_constraint.dialect_kwargs,
  246. )
  247. def to_constraint(
  248. self, migration_context: Optional[MigrationContext] = None
  249. ) -> PrimaryKeyConstraint:
  250. schema_obj = schemaobj.SchemaObjects(migration_context)
  251. return schema_obj.primary_key_constraint(
  252. self.constraint_name,
  253. self.table_name,
  254. self.columns,
  255. schema=self.schema,
  256. **self.kw,
  257. )
  258. @classmethod
  259. def create_primary_key(
  260. cls,
  261. operations: Operations,
  262. constraint_name: Optional[str],
  263. table_name: str,
  264. columns: List[str],
  265. *,
  266. schema: Optional[str] = None,
  267. ) -> None:
  268. """Issue a "create primary key" instruction using the current
  269. migration context.
  270. e.g.::
  271. from alembic import op
  272. op.create_primary_key("pk_my_table", "my_table", ["id", "version"])
  273. This internally generates a :class:`~sqlalchemy.schema.Table` object
  274. containing the necessary columns, then generates a new
  275. :class:`~sqlalchemy.schema.PrimaryKeyConstraint`
  276. object which it then associates with the
  277. :class:`~sqlalchemy.schema.Table`.
  278. Any event listeners associated with this action will be fired
  279. off normally. The :class:`~sqlalchemy.schema.AddConstraint`
  280. construct is ultimately used to generate the ALTER statement.
  281. :param constraint_name: Name of the primary key constraint. The name
  282. is necessary so that an ALTER statement can be emitted. For setups
  283. that use an automated naming scheme such as that described at
  284. :ref:`sqla:constraint_naming_conventions`
  285. ``name`` here can be ``None``, as the event listener will
  286. apply the name to the constraint object when it is associated
  287. with the table.
  288. :param table_name: String name of the target table.
  289. :param columns: a list of string column names to be applied to the
  290. primary key constraint.
  291. :param schema: Optional schema name to operate within. To control
  292. quoting of the schema outside of the default behavior, use
  293. the SQLAlchemy construct
  294. :class:`~sqlalchemy.sql.elements.quoted_name`.
  295. """
  296. op = cls(constraint_name, table_name, columns, schema=schema)
  297. return operations.invoke(op)
  298. @classmethod
  299. def batch_create_primary_key(
  300. cls,
  301. operations: BatchOperations,
  302. constraint_name: Optional[str],
  303. columns: List[str],
  304. ) -> None:
  305. """Issue a "create primary key" instruction using the
  306. current batch migration context.
  307. The batch form of this call omits the ``table_name`` and ``schema``
  308. arguments from the call.
  309. .. seealso::
  310. :meth:`.Operations.create_primary_key`
  311. """
  312. op = cls(
  313. constraint_name,
  314. operations.impl.table_name,
  315. columns,
  316. schema=operations.impl.schema,
  317. )
  318. return operations.invoke(op)
  319. @Operations.register_operation("create_unique_constraint")
  320. @BatchOperations.register_operation(
  321. "create_unique_constraint", "batch_create_unique_constraint"
  322. )
  323. @AddConstraintOp.register_add_constraint("unique_constraint")
  324. class CreateUniqueConstraintOp(AddConstraintOp):
  325. """Represent a create unique constraint operation."""
  326. constraint_type = "unique"
  327. def __init__(
  328. self,
  329. constraint_name: Optional[sqla_compat._ConstraintNameDefined],
  330. table_name: str,
  331. columns: Sequence[str],
  332. *,
  333. schema: Optional[str] = None,
  334. **kw: Any,
  335. ) -> None:
  336. self.constraint_name = constraint_name
  337. self.table_name = table_name
  338. self.columns = columns
  339. self.schema = schema
  340. self.kw = kw
  341. @classmethod
  342. def from_constraint(
  343. cls, constraint: Constraint
  344. ) -> CreateUniqueConstraintOp:
  345. constraint_table = sqla_compat._table_for_constraint(constraint)
  346. uq_constraint = cast("UniqueConstraint", constraint)
  347. kw: Dict[str, Any] = {}
  348. if uq_constraint.deferrable:
  349. kw["deferrable"] = uq_constraint.deferrable
  350. if uq_constraint.initially:
  351. kw["initially"] = uq_constraint.initially
  352. kw.update(uq_constraint.dialect_kwargs)
  353. return cls(
  354. sqla_compat.constraint_name_or_none(uq_constraint.name),
  355. constraint_table.name,
  356. [c.name for c in uq_constraint.columns],
  357. schema=constraint_table.schema,
  358. **kw,
  359. )
  360. def to_constraint(
  361. self, migration_context: Optional[MigrationContext] = None
  362. ) -> UniqueConstraint:
  363. schema_obj = schemaobj.SchemaObjects(migration_context)
  364. return schema_obj.unique_constraint(
  365. self.constraint_name,
  366. self.table_name,
  367. self.columns,
  368. schema=self.schema,
  369. **self.kw,
  370. )
  371. @classmethod
  372. def create_unique_constraint(
  373. cls,
  374. operations: Operations,
  375. constraint_name: Optional[str],
  376. table_name: str,
  377. columns: Sequence[str],
  378. *,
  379. schema: Optional[str] = None,
  380. **kw: Any,
  381. ) -> Any:
  382. """Issue a "create unique constraint" instruction using the
  383. current migration context.
  384. e.g.::
  385. from alembic import op
  386. op.create_unique_constraint("uq_user_name", "user", ["name"])
  387. This internally generates a :class:`~sqlalchemy.schema.Table` object
  388. containing the necessary columns, then generates a new
  389. :class:`~sqlalchemy.schema.UniqueConstraint`
  390. object which it then associates with the
  391. :class:`~sqlalchemy.schema.Table`.
  392. Any event listeners associated with this action will be fired
  393. off normally. The :class:`~sqlalchemy.schema.AddConstraint`
  394. construct is ultimately used to generate the ALTER statement.
  395. :param name: Name of the unique constraint. The name is necessary
  396. so that an ALTER statement can be emitted. For setups that
  397. use an automated naming scheme such as that described at
  398. :ref:`sqla:constraint_naming_conventions`,
  399. ``name`` here can be ``None``, as the event listener will
  400. apply the name to the constraint object when it is associated
  401. with the table.
  402. :param table_name: String name of the source table.
  403. :param columns: a list of string column names in the
  404. source table.
  405. :param deferrable: optional bool. If set, emit DEFERRABLE or
  406. NOT DEFERRABLE when issuing DDL for this constraint.
  407. :param initially: optional string. If set, emit INITIALLY <value>
  408. when issuing DDL for this constraint.
  409. :param schema: Optional schema name to operate within. To control
  410. quoting of the schema outside of the default behavior, use
  411. the SQLAlchemy construct
  412. :class:`~sqlalchemy.sql.elements.quoted_name`.
  413. """
  414. op = cls(constraint_name, table_name, columns, schema=schema, **kw)
  415. return operations.invoke(op)
  416. @classmethod
  417. def batch_create_unique_constraint(
  418. cls,
  419. operations: BatchOperations,
  420. constraint_name: str,
  421. columns: Sequence[str],
  422. **kw: Any,
  423. ) -> Any:
  424. """Issue a "create unique constraint" instruction using the
  425. current batch migration context.
  426. The batch form of this call omits the ``source`` and ``schema``
  427. arguments from the call.
  428. .. seealso::
  429. :meth:`.Operations.create_unique_constraint`
  430. """
  431. kw["schema"] = operations.impl.schema
  432. op = cls(constraint_name, operations.impl.table_name, columns, **kw)
  433. return operations.invoke(op)
  434. @Operations.register_operation("create_foreign_key")
  435. @BatchOperations.register_operation(
  436. "create_foreign_key", "batch_create_foreign_key"
  437. )
  438. @AddConstraintOp.register_add_constraint("foreign_key_constraint")
  439. class CreateForeignKeyOp(AddConstraintOp):
  440. """Represent a create foreign key constraint operation."""
  441. constraint_type = "foreignkey"
  442. def __init__(
  443. self,
  444. constraint_name: Optional[sqla_compat._ConstraintNameDefined],
  445. source_table: str,
  446. referent_table: str,
  447. local_cols: List[str],
  448. remote_cols: List[str],
  449. **kw: Any,
  450. ) -> None:
  451. self.constraint_name = constraint_name
  452. self.source_table = source_table
  453. self.referent_table = referent_table
  454. self.local_cols = local_cols
  455. self.remote_cols = remote_cols
  456. self.kw = kw
  457. def to_diff_tuple(self) -> Tuple[str, ForeignKeyConstraint]:
  458. return ("add_fk", self.to_constraint())
  459. @classmethod
  460. def from_constraint(cls, constraint: Constraint) -> CreateForeignKeyOp:
  461. fk_constraint = cast("ForeignKeyConstraint", constraint)
  462. kw: Dict[str, Any] = {}
  463. if fk_constraint.onupdate:
  464. kw["onupdate"] = fk_constraint.onupdate
  465. if fk_constraint.ondelete:
  466. kw["ondelete"] = fk_constraint.ondelete
  467. if fk_constraint.initially:
  468. kw["initially"] = fk_constraint.initially
  469. if fk_constraint.deferrable:
  470. kw["deferrable"] = fk_constraint.deferrable
  471. if fk_constraint.use_alter:
  472. kw["use_alter"] = fk_constraint.use_alter
  473. if fk_constraint.match:
  474. kw["match"] = fk_constraint.match
  475. (
  476. source_schema,
  477. source_table,
  478. source_columns,
  479. target_schema,
  480. target_table,
  481. target_columns,
  482. onupdate,
  483. ondelete,
  484. deferrable,
  485. initially,
  486. ) = sqla_compat._fk_spec(fk_constraint)
  487. kw["source_schema"] = source_schema
  488. kw["referent_schema"] = target_schema
  489. kw.update(fk_constraint.dialect_kwargs)
  490. return cls(
  491. sqla_compat.constraint_name_or_none(fk_constraint.name),
  492. source_table,
  493. target_table,
  494. source_columns,
  495. target_columns,
  496. **kw,
  497. )
  498. def to_constraint(
  499. self, migration_context: Optional[MigrationContext] = None
  500. ) -> ForeignKeyConstraint:
  501. schema_obj = schemaobj.SchemaObjects(migration_context)
  502. return schema_obj.foreign_key_constraint(
  503. self.constraint_name,
  504. self.source_table,
  505. self.referent_table,
  506. self.local_cols,
  507. self.remote_cols,
  508. **self.kw,
  509. )
  510. @classmethod
  511. def create_foreign_key(
  512. cls,
  513. operations: Operations,
  514. constraint_name: Optional[str],
  515. source_table: str,
  516. referent_table: str,
  517. local_cols: List[str],
  518. remote_cols: List[str],
  519. *,
  520. onupdate: Optional[str] = None,
  521. ondelete: Optional[str] = None,
  522. deferrable: Optional[bool] = None,
  523. initially: Optional[str] = None,
  524. match: Optional[str] = None,
  525. source_schema: Optional[str] = None,
  526. referent_schema: Optional[str] = None,
  527. **dialect_kw: Any,
  528. ) -> None:
  529. """Issue a "create foreign key" instruction using the
  530. current migration context.
  531. e.g.::
  532. from alembic import op
  533. op.create_foreign_key(
  534. "fk_user_address",
  535. "address",
  536. "user",
  537. ["user_id"],
  538. ["id"],
  539. )
  540. This internally generates a :class:`~sqlalchemy.schema.Table` object
  541. containing the necessary columns, then generates a new
  542. :class:`~sqlalchemy.schema.ForeignKeyConstraint`
  543. object which it then associates with the
  544. :class:`~sqlalchemy.schema.Table`.
  545. Any event listeners associated with this action will be fired
  546. off normally. The :class:`~sqlalchemy.schema.AddConstraint`
  547. construct is ultimately used to generate the ALTER statement.
  548. :param constraint_name: Name of the foreign key constraint. The name
  549. is necessary so that an ALTER statement can be emitted. For setups
  550. that use an automated naming scheme such as that described at
  551. :ref:`sqla:constraint_naming_conventions`,
  552. ``name`` here can be ``None``, as the event listener will
  553. apply the name to the constraint object when it is associated
  554. with the table.
  555. :param source_table: String name of the source table.
  556. :param referent_table: String name of the destination table.
  557. :param local_cols: a list of string column names in the
  558. source table.
  559. :param remote_cols: a list of string column names in the
  560. remote table.
  561. :param onupdate: Optional string. If set, emit ON UPDATE <value> when
  562. issuing DDL for this constraint. Typical values include CASCADE,
  563. DELETE and RESTRICT.
  564. :param ondelete: Optional string. If set, emit ON DELETE <value> when
  565. issuing DDL for this constraint. Typical values include CASCADE,
  566. DELETE and RESTRICT.
  567. :param deferrable: optional bool. If set, emit DEFERRABLE or NOT
  568. DEFERRABLE when issuing DDL for this constraint.
  569. :param source_schema: Optional schema name of the source table.
  570. :param referent_schema: Optional schema name of the destination table.
  571. """
  572. op = cls(
  573. constraint_name,
  574. source_table,
  575. referent_table,
  576. local_cols,
  577. remote_cols,
  578. onupdate=onupdate,
  579. ondelete=ondelete,
  580. deferrable=deferrable,
  581. source_schema=source_schema,
  582. referent_schema=referent_schema,
  583. initially=initially,
  584. match=match,
  585. **dialect_kw,
  586. )
  587. return operations.invoke(op)
  588. @classmethod
  589. def batch_create_foreign_key(
  590. cls,
  591. operations: BatchOperations,
  592. constraint_name: Optional[str],
  593. referent_table: str,
  594. local_cols: List[str],
  595. remote_cols: List[str],
  596. *,
  597. referent_schema: Optional[str] = None,
  598. onupdate: Optional[str] = None,
  599. ondelete: Optional[str] = None,
  600. deferrable: Optional[bool] = None,
  601. initially: Optional[str] = None,
  602. match: Optional[str] = None,
  603. **dialect_kw: Any,
  604. ) -> None:
  605. """Issue a "create foreign key" instruction using the
  606. current batch migration context.
  607. The batch form of this call omits the ``source`` and ``source_schema``
  608. arguments from the call.
  609. e.g.::
  610. with batch_alter_table("address") as batch_op:
  611. batch_op.create_foreign_key(
  612. "fk_user_address",
  613. "user",
  614. ["user_id"],
  615. ["id"],
  616. )
  617. .. seealso::
  618. :meth:`.Operations.create_foreign_key`
  619. """
  620. op = cls(
  621. constraint_name,
  622. operations.impl.table_name,
  623. referent_table,
  624. local_cols,
  625. remote_cols,
  626. onupdate=onupdate,
  627. ondelete=ondelete,
  628. deferrable=deferrable,
  629. source_schema=operations.impl.schema,
  630. referent_schema=referent_schema,
  631. initially=initially,
  632. match=match,
  633. **dialect_kw,
  634. )
  635. return operations.invoke(op)
  636. @Operations.register_operation("create_check_constraint")
  637. @BatchOperations.register_operation(
  638. "create_check_constraint", "batch_create_check_constraint"
  639. )
  640. @AddConstraintOp.register_add_constraint("check_constraint")
  641. @AddConstraintOp.register_add_constraint("table_or_column_check_constraint")
  642. @AddConstraintOp.register_add_constraint("column_check_constraint")
  643. class CreateCheckConstraintOp(AddConstraintOp):
  644. """Represent a create check constraint operation."""
  645. constraint_type = "check"
  646. def __init__(
  647. self,
  648. constraint_name: Optional[sqla_compat._ConstraintNameDefined],
  649. table_name: str,
  650. condition: Union[str, TextClause, ColumnElement[Any]],
  651. *,
  652. schema: Optional[str] = None,
  653. **kw: Any,
  654. ) -> None:
  655. self.constraint_name = constraint_name
  656. self.table_name = table_name
  657. self.condition = condition
  658. self.schema = schema
  659. self.kw = kw
  660. @classmethod
  661. def from_constraint(
  662. cls, constraint: Constraint
  663. ) -> CreateCheckConstraintOp:
  664. constraint_table = sqla_compat._table_for_constraint(constraint)
  665. ck_constraint = cast("CheckConstraint", constraint)
  666. return cls(
  667. sqla_compat.constraint_name_or_none(ck_constraint.name),
  668. constraint_table.name,
  669. cast("ColumnElement[Any]", ck_constraint.sqltext),
  670. schema=constraint_table.schema,
  671. **ck_constraint.dialect_kwargs,
  672. )
  673. def to_constraint(
  674. self, migration_context: Optional[MigrationContext] = None
  675. ) -> CheckConstraint:
  676. schema_obj = schemaobj.SchemaObjects(migration_context)
  677. return schema_obj.check_constraint(
  678. self.constraint_name,
  679. self.table_name,
  680. self.condition,
  681. schema=self.schema,
  682. **self.kw,
  683. )
  684. @classmethod
  685. def create_check_constraint(
  686. cls,
  687. operations: Operations,
  688. constraint_name: Optional[str],
  689. table_name: str,
  690. condition: Union[str, ColumnElement[bool], TextClause],
  691. *,
  692. schema: Optional[str] = None,
  693. **kw: Any,
  694. ) -> None:
  695. """Issue a "create check constraint" instruction using the
  696. current migration context.
  697. e.g.::
  698. from alembic import op
  699. from sqlalchemy.sql import column, func
  700. op.create_check_constraint(
  701. "ck_user_name_len",
  702. "user",
  703. func.len(column("name")) > 5,
  704. )
  705. CHECK constraints are usually against a SQL expression, so ad-hoc
  706. table metadata is usually needed. The function will convert the given
  707. arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound
  708. to an anonymous table in order to emit the CREATE statement.
  709. :param name: Name of the check constraint. The name is necessary
  710. so that an ALTER statement can be emitted. For setups that
  711. use an automated naming scheme such as that described at
  712. :ref:`sqla:constraint_naming_conventions`,
  713. ``name`` here can be ``None``, as the event listener will
  714. apply the name to the constraint object when it is associated
  715. with the table.
  716. :param table_name: String name of the source table.
  717. :param condition: SQL expression that's the condition of the
  718. constraint. Can be a string or SQLAlchemy expression language
  719. structure.
  720. :param deferrable: optional bool. If set, emit DEFERRABLE or
  721. NOT DEFERRABLE when issuing DDL for this constraint.
  722. :param initially: optional string. If set, emit INITIALLY <value>
  723. when issuing DDL for this constraint.
  724. :param schema: Optional schema name to operate within. To control
  725. quoting of the schema outside of the default behavior, use
  726. the SQLAlchemy construct
  727. :class:`~sqlalchemy.sql.elements.quoted_name`.
  728. """
  729. op = cls(constraint_name, table_name, condition, schema=schema, **kw)
  730. return operations.invoke(op)
  731. @classmethod
  732. def batch_create_check_constraint(
  733. cls,
  734. operations: BatchOperations,
  735. constraint_name: str,
  736. condition: Union[str, ColumnElement[bool], TextClause],
  737. **kw: Any,
  738. ) -> None:
  739. """Issue a "create check constraint" instruction using the
  740. current batch migration context.
  741. The batch form of this call omits the ``source`` and ``schema``
  742. arguments from the call.
  743. .. seealso::
  744. :meth:`.Operations.create_check_constraint`
  745. """
  746. op = cls(
  747. constraint_name,
  748. operations.impl.table_name,
  749. condition,
  750. schema=operations.impl.schema,
  751. **kw,
  752. )
  753. return operations.invoke(op)
  754. @Operations.register_operation("create_index")
  755. @BatchOperations.register_operation("create_index", "batch_create_index")
  756. class CreateIndexOp(MigrateOperation):
  757. """Represent a create index operation."""
  758. def __init__(
  759. self,
  760. index_name: Optional[str],
  761. table_name: str,
  762. columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
  763. *,
  764. schema: Optional[str] = None,
  765. unique: bool = False,
  766. if_not_exists: Optional[bool] = None,
  767. **kw: Any,
  768. ) -> None:
  769. self.index_name = index_name
  770. self.table_name = table_name
  771. self.columns = columns
  772. self.schema = schema
  773. self.unique = unique
  774. self.if_not_exists = if_not_exists
  775. self.kw = kw
  776. def reverse(self) -> DropIndexOp:
  777. return DropIndexOp.from_index(self.to_index())
  778. def to_diff_tuple(self) -> Tuple[str, Index]:
  779. return ("add_index", self.to_index())
  780. @classmethod
  781. def from_index(cls, index: Index) -> CreateIndexOp:
  782. assert index.table is not None
  783. return cls(
  784. index.name,
  785. index.table.name,
  786. index.expressions,
  787. schema=index.table.schema,
  788. unique=index.unique,
  789. **index.kwargs,
  790. )
  791. def to_index(
  792. self, migration_context: Optional[MigrationContext] = None
  793. ) -> Index:
  794. schema_obj = schemaobj.SchemaObjects(migration_context)
  795. idx = schema_obj.index(
  796. self.index_name,
  797. self.table_name,
  798. self.columns,
  799. schema=self.schema,
  800. unique=self.unique,
  801. **self.kw,
  802. )
  803. return idx
  804. @classmethod
  805. def create_index(
  806. cls,
  807. operations: Operations,
  808. index_name: Optional[str],
  809. table_name: str,
  810. columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
  811. *,
  812. schema: Optional[str] = None,
  813. unique: bool = False,
  814. if_not_exists: Optional[bool] = None,
  815. **kw: Any,
  816. ) -> None:
  817. r"""Issue a "create index" instruction using the current
  818. migration context.
  819. e.g.::
  820. from alembic import op
  821. op.create_index("ik_test", "t1", ["foo", "bar"])
  822. Functional indexes can be produced by using the
  823. :func:`sqlalchemy.sql.expression.text` construct::
  824. from alembic import op
  825. from sqlalchemy import text
  826. op.create_index("ik_test", "t1", [text("lower(foo)")])
  827. :param index_name: name of the index.
  828. :param table_name: name of the owning table.
  829. :param columns: a list consisting of string column names and/or
  830. :func:`~sqlalchemy.sql.expression.text` constructs.
  831. :param schema: Optional schema name to operate within. To control
  832. quoting of the schema outside of the default behavior, use
  833. the SQLAlchemy construct
  834. :class:`~sqlalchemy.sql.elements.quoted_name`.
  835. :param unique: If True, create a unique index.
  836. :param quote: Force quoting of this column's name on or off,
  837. corresponding to ``True`` or ``False``. When left at its default
  838. of ``None``, the column identifier will be quoted according to
  839. whether the name is case sensitive (identifiers with at least one
  840. upper case character are treated as case sensitive), or if it's a
  841. reserved word. This flag is only needed to force quoting of a
  842. reserved word which is not known by the SQLAlchemy dialect.
  843. :param if_not_exists: If True, adds IF NOT EXISTS operator when
  844. creating the new index.
  845. .. versionadded:: 1.12.0
  846. :param \**kw: Additional keyword arguments not mentioned above are
  847. dialect specific, and passed in the form
  848. ``<dialectname>_<argname>``.
  849. See the documentation regarding an individual dialect at
  850. :ref:`dialect_toplevel` for detail on documented arguments.
  851. """
  852. op = cls(
  853. index_name,
  854. table_name,
  855. columns,
  856. schema=schema,
  857. unique=unique,
  858. if_not_exists=if_not_exists,
  859. **kw,
  860. )
  861. return operations.invoke(op)
  862. @classmethod
  863. def batch_create_index(
  864. cls,
  865. operations: BatchOperations,
  866. index_name: str,
  867. columns: List[str],
  868. **kw: Any,
  869. ) -> None:
  870. """Issue a "create index" instruction using the
  871. current batch migration context.
  872. .. seealso::
  873. :meth:`.Operations.create_index`
  874. """
  875. op = cls(
  876. index_name,
  877. operations.impl.table_name,
  878. columns,
  879. schema=operations.impl.schema,
  880. **kw,
  881. )
  882. return operations.invoke(op)
  883. @Operations.register_operation("drop_index")
  884. @BatchOperations.register_operation("drop_index", "batch_drop_index")
  885. class DropIndexOp(MigrateOperation):
  886. """Represent a drop index operation."""
  887. def __init__(
  888. self,
  889. index_name: Union[quoted_name, str, conv],
  890. table_name: Optional[str] = None,
  891. *,
  892. schema: Optional[str] = None,
  893. if_exists: Optional[bool] = None,
  894. _reverse: Optional[CreateIndexOp] = None,
  895. **kw: Any,
  896. ) -> None:
  897. self.index_name = index_name
  898. self.table_name = table_name
  899. self.schema = schema
  900. self.if_exists = if_exists
  901. self._reverse = _reverse
  902. self.kw = kw
  903. def to_diff_tuple(self) -> Tuple[str, Index]:
  904. return ("remove_index", self.to_index())
  905. def reverse(self) -> CreateIndexOp:
  906. return CreateIndexOp.from_index(self.to_index())
  907. @classmethod
  908. def from_index(cls, index: Index) -> DropIndexOp:
  909. assert index.table is not None
  910. return cls(
  911. index.name, # type: ignore[arg-type]
  912. table_name=index.table.name,
  913. schema=index.table.schema,
  914. _reverse=CreateIndexOp.from_index(index),
  915. unique=index.unique,
  916. **index.kwargs,
  917. )
  918. def to_index(
  919. self, migration_context: Optional[MigrationContext] = None
  920. ) -> Index:
  921. schema_obj = schemaobj.SchemaObjects(migration_context)
  922. # need a dummy column name here since SQLAlchemy
  923. # 0.7.6 and further raises on Index with no columns
  924. return schema_obj.index(
  925. self.index_name,
  926. self.table_name,
  927. self._reverse.columns if self._reverse else ["x"],
  928. schema=self.schema,
  929. **self.kw,
  930. )
  931. @classmethod
  932. def drop_index(
  933. cls,
  934. operations: Operations,
  935. index_name: str,
  936. table_name: Optional[str] = None,
  937. *,
  938. schema: Optional[str] = None,
  939. if_exists: Optional[bool] = None,
  940. **kw: Any,
  941. ) -> None:
  942. r"""Issue a "drop index" instruction using the current
  943. migration context.
  944. e.g.::
  945. drop_index("accounts")
  946. :param index_name: name of the index.
  947. :param table_name: name of the owning table. Some
  948. backends such as Microsoft SQL Server require this.
  949. :param schema: Optional schema name to operate within. To control
  950. quoting of the schema outside of the default behavior, use
  951. the SQLAlchemy construct
  952. :class:`~sqlalchemy.sql.elements.quoted_name`.
  953. :param if_exists: If True, adds IF EXISTS operator when
  954. dropping the index.
  955. .. versionadded:: 1.12.0
  956. :param \**kw: Additional keyword arguments not mentioned above are
  957. dialect specific, and passed in the form
  958. ``<dialectname>_<argname>``.
  959. See the documentation regarding an individual dialect at
  960. :ref:`dialect_toplevel` for detail on documented arguments.
  961. """
  962. op = cls(
  963. index_name,
  964. table_name=table_name,
  965. schema=schema,
  966. if_exists=if_exists,
  967. **kw,
  968. )
  969. return operations.invoke(op)
  970. @classmethod
  971. def batch_drop_index(
  972. cls, operations: BatchOperations, index_name: str, **kw: Any
  973. ) -> None:
  974. """Issue a "drop index" instruction using the
  975. current batch migration context.
  976. .. seealso::
  977. :meth:`.Operations.drop_index`
  978. """
  979. op = cls(
  980. index_name,
  981. table_name=operations.impl.table_name,
  982. schema=operations.impl.schema,
  983. **kw,
  984. )
  985. return operations.invoke(op)
  986. @Operations.register_operation("create_table")
  987. class CreateTableOp(MigrateOperation):
  988. """Represent a create table operation."""
  989. def __init__(
  990. self,
  991. table_name: str,
  992. columns: Sequence[SchemaItem],
  993. *,
  994. schema: Optional[str] = None,
  995. if_not_exists: Optional[bool] = None,
  996. _namespace_metadata: Optional[MetaData] = None,
  997. _constraints_included: bool = False,
  998. **kw: Any,
  999. ) -> None:
  1000. self.table_name = table_name
  1001. self.columns = columns
  1002. self.schema = schema
  1003. self.if_not_exists = if_not_exists
  1004. self.info = kw.pop("info", {})
  1005. self.comment = kw.pop("comment", None)
  1006. self.prefixes = kw.pop("prefixes", None)
  1007. self.kw = kw
  1008. self._namespace_metadata = _namespace_metadata
  1009. self._constraints_included = _constraints_included
  1010. def reverse(self) -> DropTableOp:
  1011. return DropTableOp.from_table(
  1012. self.to_table(), _namespace_metadata=self._namespace_metadata
  1013. )
  1014. def to_diff_tuple(self) -> Tuple[str, Table]:
  1015. return ("add_table", self.to_table())
  1016. @classmethod
  1017. def from_table(
  1018. cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None
  1019. ) -> CreateTableOp:
  1020. if _namespace_metadata is None:
  1021. _namespace_metadata = table.metadata
  1022. return cls(
  1023. table.name,
  1024. list(table.c) + list(table.constraints),
  1025. schema=table.schema,
  1026. _namespace_metadata=_namespace_metadata,
  1027. # given a Table() object, this Table will contain full Index()
  1028. # and UniqueConstraint objects already constructed in response to
  1029. # each unique=True / index=True flag on a Column. Carry this
  1030. # state along so that when we re-convert back into a Table, we
  1031. # skip unique=True/index=True so that these constraints are
  1032. # not doubled up. see #844 #848
  1033. _constraints_included=True,
  1034. comment=table.comment,
  1035. info=dict(table.info),
  1036. prefixes=list(table._prefixes),
  1037. **table.kwargs,
  1038. )
  1039. def to_table(
  1040. self, migration_context: Optional[MigrationContext] = None
  1041. ) -> Table:
  1042. schema_obj = schemaobj.SchemaObjects(migration_context)
  1043. return schema_obj.table(
  1044. self.table_name,
  1045. *self.columns,
  1046. schema=self.schema,
  1047. prefixes=list(self.prefixes) if self.prefixes else [],
  1048. comment=self.comment,
  1049. info=self.info.copy() if self.info else {},
  1050. _constraints_included=self._constraints_included,
  1051. **self.kw,
  1052. )
  1053. @classmethod
  1054. def create_table(
  1055. cls,
  1056. operations: Operations,
  1057. table_name: str,
  1058. *columns: SchemaItem,
  1059. if_not_exists: Optional[bool] = None,
  1060. **kw: Any,
  1061. ) -> Table:
  1062. r"""Issue a "create table" instruction using the current migration
  1063. context.
  1064. This directive receives an argument list similar to that of the
  1065. traditional :class:`sqlalchemy.schema.Table` construct, but without the
  1066. metadata::
  1067. from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
  1068. from alembic import op
  1069. op.create_table(
  1070. "account",
  1071. Column("id", INTEGER, primary_key=True),
  1072. Column("name", VARCHAR(50), nullable=False),
  1073. Column("description", NVARCHAR(200)),
  1074. Column("timestamp", TIMESTAMP, server_default=func.now()),
  1075. )
  1076. Note that :meth:`.create_table` accepts
  1077. :class:`~sqlalchemy.schema.Column`
  1078. constructs directly from the SQLAlchemy library. In particular,
  1079. default values to be created on the database side are
  1080. specified using the ``server_default`` parameter, and not
  1081. ``default`` which only specifies Python-side defaults::
  1082. from alembic import op
  1083. from sqlalchemy import Column, TIMESTAMP, func
  1084. # specify "DEFAULT NOW" along with the "timestamp" column
  1085. op.create_table(
  1086. "account",
  1087. Column("id", INTEGER, primary_key=True),
  1088. Column("timestamp", TIMESTAMP, server_default=func.now()),
  1089. )
  1090. The function also returns a newly created
  1091. :class:`~sqlalchemy.schema.Table` object, corresponding to the table
  1092. specification given, which is suitable for
  1093. immediate SQL operations, in particular
  1094. :meth:`.Operations.bulk_insert`::
  1095. from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
  1096. from alembic import op
  1097. account_table = op.create_table(
  1098. "account",
  1099. Column("id", INTEGER, primary_key=True),
  1100. Column("name", VARCHAR(50), nullable=False),
  1101. Column("description", NVARCHAR(200)),
  1102. Column("timestamp", TIMESTAMP, server_default=func.now()),
  1103. )
  1104. op.bulk_insert(
  1105. account_table,
  1106. [
  1107. {"name": "A1", "description": "account 1"},
  1108. {"name": "A2", "description": "account 2"},
  1109. ],
  1110. )
  1111. :param table_name: Name of the table
  1112. :param \*columns: collection of :class:`~sqlalchemy.schema.Column`
  1113. objects within
  1114. the table, as well as optional :class:`~sqlalchemy.schema.Constraint`
  1115. objects
  1116. and :class:`~.sqlalchemy.schema.Index` objects.
  1117. :param schema: Optional schema name to operate within. To control
  1118. quoting of the schema outside of the default behavior, use
  1119. the SQLAlchemy construct
  1120. :class:`~sqlalchemy.sql.elements.quoted_name`.
  1121. :param if_not_exists: If True, adds IF NOT EXISTS operator when
  1122. creating the new table.
  1123. .. versionadded:: 1.13.3
  1124. :param \**kw: Other keyword arguments are passed to the underlying
  1125. :class:`sqlalchemy.schema.Table` object created for the command.
  1126. :return: the :class:`~sqlalchemy.schema.Table` object corresponding
  1127. to the parameters given.
  1128. """
  1129. op = cls(table_name, columns, if_not_exists=if_not_exists, **kw)
  1130. return operations.invoke(op)
  1131. @Operations.register_operation("drop_table")
  1132. class DropTableOp(MigrateOperation):
  1133. """Represent a drop table operation."""
  1134. def __init__(
  1135. self,
  1136. table_name: str,
  1137. *,
  1138. schema: Optional[str] = None,
  1139. if_exists: Optional[bool] = None,
  1140. table_kw: Optional[MutableMapping[Any, Any]] = None,
  1141. _reverse: Optional[CreateTableOp] = None,
  1142. ) -> None:
  1143. self.table_name = table_name
  1144. self.schema = schema
  1145. self.if_exists = if_exists
  1146. self.table_kw = table_kw or {}
  1147. self.comment = self.table_kw.pop("comment", None)
  1148. self.info = self.table_kw.pop("info", None)
  1149. self.prefixes = self.table_kw.pop("prefixes", None)
  1150. self._reverse = _reverse
  1151. def to_diff_tuple(self) -> Tuple[str, Table]:
  1152. return ("remove_table", self.to_table())
  1153. def reverse(self) -> CreateTableOp:
  1154. return CreateTableOp.from_table(self.to_table())
  1155. @classmethod
  1156. def from_table(
  1157. cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None
  1158. ) -> DropTableOp:
  1159. return cls(
  1160. table.name,
  1161. schema=table.schema,
  1162. table_kw={
  1163. "comment": table.comment,
  1164. "info": dict(table.info),
  1165. "prefixes": list(table._prefixes),
  1166. **table.kwargs,
  1167. },
  1168. _reverse=CreateTableOp.from_table(
  1169. table, _namespace_metadata=_namespace_metadata
  1170. ),
  1171. )
  1172. def to_table(
  1173. self, migration_context: Optional[MigrationContext] = None
  1174. ) -> Table:
  1175. if self._reverse:
  1176. cols_and_constraints = self._reverse.columns
  1177. else:
  1178. cols_and_constraints = []
  1179. schema_obj = schemaobj.SchemaObjects(migration_context)
  1180. t = schema_obj.table(
  1181. self.table_name,
  1182. *cols_and_constraints,
  1183. comment=self.comment,
  1184. info=self.info.copy() if self.info else {},
  1185. prefixes=list(self.prefixes) if self.prefixes else [],
  1186. schema=self.schema,
  1187. _constraints_included=(
  1188. self._reverse._constraints_included if self._reverse else False
  1189. ),
  1190. **self.table_kw,
  1191. )
  1192. return t
  1193. @classmethod
  1194. def drop_table(
  1195. cls,
  1196. operations: Operations,
  1197. table_name: str,
  1198. *,
  1199. schema: Optional[str] = None,
  1200. if_exists: Optional[bool] = None,
  1201. **kw: Any,
  1202. ) -> None:
  1203. r"""Issue a "drop table" instruction using the current
  1204. migration context.
  1205. e.g.::
  1206. drop_table("accounts")
  1207. :param table_name: Name of the table
  1208. :param schema: Optional schema name to operate within. To control
  1209. quoting of the schema outside of the default behavior, use
  1210. the SQLAlchemy construct
  1211. :class:`~sqlalchemy.sql.elements.quoted_name`.
  1212. :param if_exists: If True, adds IF EXISTS operator when
  1213. dropping the table.
  1214. .. versionadded:: 1.13.3
  1215. :param \**kw: Other keyword arguments are passed to the underlying
  1216. :class:`sqlalchemy.schema.Table` object created for the command.
  1217. """
  1218. op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw)
  1219. operations.invoke(op)
  1220. class AlterTableOp(MigrateOperation):
  1221. """Represent an alter table operation."""
  1222. def __init__(
  1223. self,
  1224. table_name: str,
  1225. *,
  1226. schema: Optional[str] = None,
  1227. ) -> None:
  1228. self.table_name = table_name
  1229. self.schema = schema
  1230. @Operations.register_operation("rename_table")
  1231. class RenameTableOp(AlterTableOp):
  1232. """Represent a rename table operation."""
  1233. def __init__(
  1234. self,
  1235. old_table_name: str,
  1236. new_table_name: str,
  1237. *,
  1238. schema: Optional[str] = None,
  1239. ) -> None:
  1240. super().__init__(old_table_name, schema=schema)
  1241. self.new_table_name = new_table_name
  1242. @classmethod
  1243. def rename_table(
  1244. cls,
  1245. operations: Operations,
  1246. old_table_name: str,
  1247. new_table_name: str,
  1248. *,
  1249. schema: Optional[str] = None,
  1250. ) -> None:
  1251. """Emit an ALTER TABLE to rename a table.
  1252. :param old_table_name: old name.
  1253. :param new_table_name: new name.
  1254. :param schema: Optional schema name to operate within. To control
  1255. quoting of the schema outside of the default behavior, use
  1256. the SQLAlchemy construct
  1257. :class:`~sqlalchemy.sql.elements.quoted_name`.
  1258. """
  1259. op = cls(old_table_name, new_table_name, schema=schema)
  1260. return operations.invoke(op)
  1261. @Operations.register_operation("create_table_comment")
  1262. @BatchOperations.register_operation(
  1263. "create_table_comment", "batch_create_table_comment"
  1264. )
  1265. class CreateTableCommentOp(AlterTableOp):
  1266. """Represent a COMMENT ON `table` operation."""
  1267. def __init__(
  1268. self,
  1269. table_name: str,
  1270. comment: Optional[str],
  1271. *,
  1272. schema: Optional[str] = None,
  1273. existing_comment: Optional[str] = None,
  1274. ) -> None:
  1275. self.table_name = table_name
  1276. self.comment = comment
  1277. self.existing_comment = existing_comment
  1278. self.schema = schema
  1279. @classmethod
  1280. def create_table_comment(
  1281. cls,
  1282. operations: Operations,
  1283. table_name: str,
  1284. comment: Optional[str],
  1285. *,
  1286. existing_comment: Optional[str] = None,
  1287. schema: Optional[str] = None,
  1288. ) -> None:
  1289. """Emit a COMMENT ON operation to set the comment for a table.
  1290. :param table_name: string name of the target table.
  1291. :param comment: string value of the comment being registered against
  1292. the specified table.
  1293. :param existing_comment: String value of a comment
  1294. already registered on the specified table, used within autogenerate
  1295. so that the operation is reversible, but not required for direct
  1296. use.
  1297. .. seealso::
  1298. :meth:`.Operations.drop_table_comment`
  1299. :paramref:`.Operations.alter_column.comment`
  1300. """
  1301. op = cls(
  1302. table_name,
  1303. comment,
  1304. existing_comment=existing_comment,
  1305. schema=schema,
  1306. )
  1307. return operations.invoke(op)
  1308. @classmethod
  1309. def batch_create_table_comment(
  1310. cls,
  1311. operations: BatchOperations,
  1312. comment: Optional[str],
  1313. *,
  1314. existing_comment: Optional[str] = None,
  1315. ) -> None:
  1316. """Emit a COMMENT ON operation to set the comment for a table
  1317. using the current batch migration context.
  1318. :param comment: string value of the comment being registered against
  1319. the specified table.
  1320. :param existing_comment: String value of a comment
  1321. already registered on the specified table, used within autogenerate
  1322. so that the operation is reversible, but not required for direct
  1323. use.
  1324. """
  1325. op = cls(
  1326. operations.impl.table_name,
  1327. comment,
  1328. existing_comment=existing_comment,
  1329. schema=operations.impl.schema,
  1330. )
  1331. return operations.invoke(op)
  1332. def reverse(self) -> Union[CreateTableCommentOp, DropTableCommentOp]:
  1333. """Reverses the COMMENT ON operation against a table."""
  1334. if self.existing_comment is None:
  1335. return DropTableCommentOp(
  1336. self.table_name,
  1337. existing_comment=self.comment,
  1338. schema=self.schema,
  1339. )
  1340. else:
  1341. return CreateTableCommentOp(
  1342. self.table_name,
  1343. self.existing_comment,
  1344. existing_comment=self.comment,
  1345. schema=self.schema,
  1346. )
  1347. def to_table(
  1348. self, migration_context: Optional[MigrationContext] = None
  1349. ) -> Table:
  1350. schema_obj = schemaobj.SchemaObjects(migration_context)
  1351. return schema_obj.table(
  1352. self.table_name, schema=self.schema, comment=self.comment
  1353. )
  1354. def to_diff_tuple(self) -> Tuple[Any, ...]:
  1355. return ("add_table_comment", self.to_table(), self.existing_comment)
  1356. @Operations.register_operation("drop_table_comment")
  1357. @BatchOperations.register_operation(
  1358. "drop_table_comment", "batch_drop_table_comment"
  1359. )
  1360. class DropTableCommentOp(AlterTableOp):
  1361. """Represent an operation to remove the comment from a table."""
  1362. def __init__(
  1363. self,
  1364. table_name: str,
  1365. *,
  1366. schema: Optional[str] = None,
  1367. existing_comment: Optional[str] = None,
  1368. ) -> None:
  1369. self.table_name = table_name
  1370. self.existing_comment = existing_comment
  1371. self.schema = schema
  1372. @classmethod
  1373. def drop_table_comment(
  1374. cls,
  1375. operations: Operations,
  1376. table_name: str,
  1377. *,
  1378. existing_comment: Optional[str] = None,
  1379. schema: Optional[str] = None,
  1380. ) -> None:
  1381. """Issue a "drop table comment" operation to
  1382. remove an existing comment set on a table.
  1383. :param table_name: string name of the target table.
  1384. :param existing_comment: An optional string value of a comment already
  1385. registered on the specified table.
  1386. .. seealso::
  1387. :meth:`.Operations.create_table_comment`
  1388. :paramref:`.Operations.alter_column.comment`
  1389. """
  1390. op = cls(table_name, existing_comment=existing_comment, schema=schema)
  1391. return operations.invoke(op)
  1392. @classmethod
  1393. def batch_drop_table_comment(
  1394. cls,
  1395. operations: BatchOperations,
  1396. *,
  1397. existing_comment: Optional[str] = None,
  1398. ) -> None:
  1399. """Issue a "drop table comment" operation to
  1400. remove an existing comment set on a table using the current
  1401. batch operations context.
  1402. :param existing_comment: An optional string value of a comment already
  1403. registered on the specified table.
  1404. """
  1405. op = cls(
  1406. operations.impl.table_name,
  1407. existing_comment=existing_comment,
  1408. schema=operations.impl.schema,
  1409. )
  1410. return operations.invoke(op)
  1411. def reverse(self) -> CreateTableCommentOp:
  1412. """Reverses the COMMENT ON operation against a table."""
  1413. return CreateTableCommentOp(
  1414. self.table_name, self.existing_comment, schema=self.schema
  1415. )
  1416. def to_table(
  1417. self, migration_context: Optional[MigrationContext] = None
  1418. ) -> Table:
  1419. schema_obj = schemaobj.SchemaObjects(migration_context)
  1420. return schema_obj.table(self.table_name, schema=self.schema)
  1421. def to_diff_tuple(self) -> Tuple[Any, ...]:
  1422. return ("remove_table_comment", self.to_table())
  1423. @Operations.register_operation("alter_column")
  1424. @BatchOperations.register_operation("alter_column", "batch_alter_column")
  1425. class AlterColumnOp(AlterTableOp):
  1426. """Represent an alter column operation."""
  1427. def __init__(
  1428. self,
  1429. table_name: str,
  1430. column_name: str,
  1431. *,
  1432. schema: Optional[str] = None,
  1433. existing_type: Optional[Any] = None,
  1434. existing_server_default: Any = False,
  1435. existing_nullable: Optional[bool] = None,
  1436. existing_comment: Optional[str] = None,
  1437. modify_nullable: Optional[bool] = None,
  1438. modify_comment: Optional[Union[str, Literal[False]]] = False,
  1439. modify_server_default: Any = False,
  1440. modify_name: Optional[str] = None,
  1441. modify_type: Optional[Any] = None,
  1442. **kw: Any,
  1443. ) -> None:
  1444. super().__init__(table_name, schema=schema)
  1445. self.column_name = column_name
  1446. self.existing_type = existing_type
  1447. self.existing_server_default = existing_server_default
  1448. self.existing_nullable = existing_nullable
  1449. self.existing_comment = existing_comment
  1450. self.modify_nullable = modify_nullable
  1451. self.modify_comment = modify_comment
  1452. self.modify_server_default = modify_server_default
  1453. self.modify_name = modify_name
  1454. self.modify_type = modify_type
  1455. self.kw = kw
  1456. def to_diff_tuple(self) -> Any:
  1457. col_diff = []
  1458. schema, tname, cname = self.schema, self.table_name, self.column_name
  1459. if self.modify_type is not None:
  1460. col_diff.append(
  1461. (
  1462. "modify_type",
  1463. schema,
  1464. tname,
  1465. cname,
  1466. {
  1467. "existing_nullable": self.existing_nullable,
  1468. "existing_server_default": (
  1469. self.existing_server_default
  1470. ),
  1471. "existing_comment": self.existing_comment,
  1472. },
  1473. self.existing_type,
  1474. self.modify_type,
  1475. )
  1476. )
  1477. if self.modify_nullable is not None:
  1478. col_diff.append(
  1479. (
  1480. "modify_nullable",
  1481. schema,
  1482. tname,
  1483. cname,
  1484. {
  1485. "existing_type": self.existing_type,
  1486. "existing_server_default": (
  1487. self.existing_server_default
  1488. ),
  1489. "existing_comment": self.existing_comment,
  1490. },
  1491. self.existing_nullable,
  1492. self.modify_nullable,
  1493. )
  1494. )
  1495. if self.modify_server_default is not False:
  1496. col_diff.append(
  1497. (
  1498. "modify_default",
  1499. schema,
  1500. tname,
  1501. cname,
  1502. {
  1503. "existing_nullable": self.existing_nullable,
  1504. "existing_type": self.existing_type,
  1505. "existing_comment": self.existing_comment,
  1506. },
  1507. self.existing_server_default,
  1508. self.modify_server_default,
  1509. )
  1510. )
  1511. if self.modify_comment is not False:
  1512. col_diff.append(
  1513. (
  1514. "modify_comment",
  1515. schema,
  1516. tname,
  1517. cname,
  1518. {
  1519. "existing_nullable": self.existing_nullable,
  1520. "existing_type": self.existing_type,
  1521. "existing_server_default": (
  1522. self.existing_server_default
  1523. ),
  1524. },
  1525. self.existing_comment,
  1526. self.modify_comment,
  1527. )
  1528. )
  1529. return col_diff
  1530. def has_changes(self) -> bool:
  1531. hc1 = (
  1532. self.modify_nullable is not None
  1533. or self.modify_server_default is not False
  1534. or self.modify_type is not None
  1535. or self.modify_comment is not False
  1536. )
  1537. if hc1:
  1538. return True
  1539. for kw in self.kw:
  1540. if kw.startswith("modify_"):
  1541. return True
  1542. else:
  1543. return False
  1544. def reverse(self) -> AlterColumnOp:
  1545. kw = self.kw.copy()
  1546. kw["existing_type"] = self.existing_type
  1547. kw["existing_nullable"] = self.existing_nullable
  1548. kw["existing_server_default"] = self.existing_server_default
  1549. kw["existing_comment"] = self.existing_comment
  1550. if self.modify_type is not None:
  1551. kw["modify_type"] = self.modify_type
  1552. if self.modify_nullable is not None:
  1553. kw["modify_nullable"] = self.modify_nullable
  1554. if self.modify_server_default is not False:
  1555. kw["modify_server_default"] = self.modify_server_default
  1556. if self.modify_comment is not False:
  1557. kw["modify_comment"] = self.modify_comment
  1558. # TODO: make this a little simpler
  1559. all_keys = {
  1560. m.group(1)
  1561. for m in [re.match(r"^(?:existing_|modify_)(.+)$", k) for k in kw]
  1562. if m
  1563. }
  1564. for k in all_keys:
  1565. if "modify_%s" % k in kw:
  1566. swap = kw["existing_%s" % k]
  1567. kw["existing_%s" % k] = kw["modify_%s" % k]
  1568. kw["modify_%s" % k] = swap
  1569. return self.__class__(
  1570. self.table_name, self.column_name, schema=self.schema, **kw
  1571. )
  1572. @classmethod
  1573. def alter_column(
  1574. cls,
  1575. operations: Operations,
  1576. table_name: str,
  1577. column_name: str,
  1578. *,
  1579. nullable: Optional[bool] = None,
  1580. comment: Optional[Union[str, Literal[False]]] = False,
  1581. server_default: Union[
  1582. str, bool, Identity, Computed, TextClause, None
  1583. ] = False,
  1584. new_column_name: Optional[str] = None,
  1585. type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
  1586. existing_type: Optional[
  1587. Union[TypeEngine[Any], Type[TypeEngine[Any]]]
  1588. ] = None,
  1589. existing_server_default: Union[
  1590. str, bool, Identity, Computed, TextClause, None
  1591. ] = False,
  1592. existing_nullable: Optional[bool] = None,
  1593. existing_comment: Optional[str] = None,
  1594. schema: Optional[str] = None,
  1595. **kw: Any,
  1596. ) -> None:
  1597. r"""Issue an "alter column" instruction using the
  1598. current migration context.
  1599. Generally, only that aspect of the column which
  1600. is being changed, i.e. name, type, nullability,
  1601. default, needs to be specified. Multiple changes
  1602. can also be specified at once and the backend should
  1603. "do the right thing", emitting each change either
  1604. separately or together as the backend allows.
  1605. MySQL has special requirements here, since MySQL
  1606. cannot ALTER a column without a full specification.
  1607. When producing MySQL-compatible migration files,
  1608. it is recommended that the ``existing_type``,
  1609. ``existing_server_default``, and ``existing_nullable``
  1610. parameters be present, if not being altered.
  1611. Type changes which are against the SQLAlchemy
  1612. "schema" types :class:`~sqlalchemy.types.Boolean`
  1613. and :class:`~sqlalchemy.types.Enum` may also
  1614. add or drop constraints which accompany those
  1615. types on backends that don't support them natively.
  1616. The ``existing_type`` argument is
  1617. used in this case to identify and remove a previous
  1618. constraint that was bound to the type object.
  1619. :param table_name: string name of the target table.
  1620. :param column_name: string name of the target column,
  1621. as it exists before the operation begins.
  1622. :param nullable: Optional; specify ``True`` or ``False``
  1623. to alter the column's nullability.
  1624. :param server_default: Optional; specify a string
  1625. SQL expression, :func:`~sqlalchemy.sql.expression.text`,
  1626. or :class:`~sqlalchemy.schema.DefaultClause` to indicate
  1627. an alteration to the column's default value.
  1628. Set to ``None`` to have the default removed.
  1629. :param comment: optional string text of a new comment to add to the
  1630. column.
  1631. :param new_column_name: Optional; specify a string name here to
  1632. indicate the new name within a column rename operation.
  1633. :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine`
  1634. type object to specify a change to the column's type.
  1635. For SQLAlchemy types that also indicate a constraint (i.e.
  1636. :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
  1637. the constraint is also generated.
  1638. :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column;
  1639. currently understood by the MySQL dialect.
  1640. :param existing_type: Optional; a
  1641. :class:`~sqlalchemy.types.TypeEngine`
  1642. type object to specify the previous type. This
  1643. is required for all MySQL column alter operations that
  1644. don't otherwise specify a new type, as well as for
  1645. when nullability is being changed on a SQL Server
  1646. column. It is also used if the type is a so-called
  1647. SQLAlchemy "schema" type which may define a constraint (i.e.
  1648. :class:`~sqlalchemy.types.Boolean`,
  1649. :class:`~sqlalchemy.types.Enum`),
  1650. so that the constraint can be dropped.
  1651. :param existing_server_default: Optional; The existing
  1652. default value of the column. Required on MySQL if
  1653. an existing default is not being changed; else MySQL
  1654. removes the default.
  1655. :param existing_nullable: Optional; the existing nullability
  1656. of the column. Required on MySQL if the existing nullability
  1657. is not being changed; else MySQL sets this to NULL.
  1658. :param existing_autoincrement: Optional; the existing autoincrement
  1659. of the column. Used for MySQL's system of altering a column
  1660. that specifies ``AUTO_INCREMENT``.
  1661. :param existing_comment: string text of the existing comment on the
  1662. column to be maintained. Required on MySQL if the existing comment
  1663. on the column is not being changed.
  1664. :param schema: Optional schema name to operate within. To control
  1665. quoting of the schema outside of the default behavior, use
  1666. the SQLAlchemy construct
  1667. :class:`~sqlalchemy.sql.elements.quoted_name`.
  1668. :param postgresql_using: String argument which will indicate a
  1669. SQL expression to render within the Postgresql-specific USING clause
  1670. within ALTER COLUMN. This string is taken directly as raw SQL which
  1671. must explicitly include any necessary quoting or escaping of tokens
  1672. within the expression.
  1673. """
  1674. alt = cls(
  1675. table_name,
  1676. column_name,
  1677. schema=schema,
  1678. existing_type=existing_type,
  1679. existing_server_default=existing_server_default,
  1680. existing_nullable=existing_nullable,
  1681. existing_comment=existing_comment,
  1682. modify_name=new_column_name,
  1683. modify_type=type_,
  1684. modify_server_default=server_default,
  1685. modify_nullable=nullable,
  1686. modify_comment=comment,
  1687. **kw,
  1688. )
  1689. return operations.invoke(alt)
  1690. @classmethod
  1691. def batch_alter_column(
  1692. cls,
  1693. operations: BatchOperations,
  1694. column_name: str,
  1695. *,
  1696. nullable: Optional[bool] = None,
  1697. comment: Optional[Union[str, Literal[False]]] = False,
  1698. server_default: Any = False,
  1699. new_column_name: Optional[str] = None,
  1700. type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
  1701. existing_type: Optional[
  1702. Union[TypeEngine[Any], Type[TypeEngine[Any]]]
  1703. ] = None,
  1704. existing_server_default: Optional[
  1705. Union[str, bool, Identity, Computed]
  1706. ] = False,
  1707. existing_nullable: Optional[bool] = None,
  1708. existing_comment: Optional[str] = None,
  1709. insert_before: Optional[str] = None,
  1710. insert_after: Optional[str] = None,
  1711. **kw: Any,
  1712. ) -> None:
  1713. """Issue an "alter column" instruction using the current
  1714. batch migration context.
  1715. Parameters are the same as that of :meth:`.Operations.alter_column`,
  1716. as well as the following option(s):
  1717. :param insert_before: String name of an existing column which this
  1718. column should be placed before, when creating the new table.
  1719. :param insert_after: String name of an existing column which this
  1720. column should be placed after, when creating the new table. If
  1721. both :paramref:`.BatchOperations.alter_column.insert_before`
  1722. and :paramref:`.BatchOperations.alter_column.insert_after` are
  1723. omitted, the column is inserted after the last existing column
  1724. in the table.
  1725. .. seealso::
  1726. :meth:`.Operations.alter_column`
  1727. """
  1728. alt = cls(
  1729. operations.impl.table_name,
  1730. column_name,
  1731. schema=operations.impl.schema,
  1732. existing_type=existing_type,
  1733. existing_server_default=existing_server_default,
  1734. existing_nullable=existing_nullable,
  1735. existing_comment=existing_comment,
  1736. modify_name=new_column_name,
  1737. modify_type=type_,
  1738. modify_server_default=server_default,
  1739. modify_nullable=nullable,
  1740. modify_comment=comment,
  1741. insert_before=insert_before,
  1742. insert_after=insert_after,
  1743. **kw,
  1744. )
  1745. return operations.invoke(alt)
  1746. @Operations.register_operation("add_column")
  1747. @BatchOperations.register_operation("add_column", "batch_add_column")
  1748. class AddColumnOp(AlterTableOp):
  1749. """Represent an add column operation."""
  1750. def __init__(
  1751. self,
  1752. table_name: str,
  1753. column: Column[Any],
  1754. *,
  1755. schema: Optional[str] = None,
  1756. if_not_exists: Optional[bool] = None,
  1757. **kw: Any,
  1758. ) -> None:
  1759. super().__init__(table_name, schema=schema)
  1760. self.column = column
  1761. self.if_not_exists = if_not_exists
  1762. self.kw = kw
  1763. def reverse(self) -> DropColumnOp:
  1764. op = DropColumnOp.from_column_and_tablename(
  1765. self.schema, self.table_name, self.column
  1766. )
  1767. op.if_exists = self.if_not_exists
  1768. return op
  1769. def to_diff_tuple(
  1770. self,
  1771. ) -> Tuple[str, Optional[str], str, Column[Any]]:
  1772. return ("add_column", self.schema, self.table_name, self.column)
  1773. def to_column(self) -> Column[Any]:
  1774. return self.column
  1775. @classmethod
  1776. def from_column(cls, col: Column[Any]) -> AddColumnOp:
  1777. return cls(col.table.name, col, schema=col.table.schema)
  1778. @classmethod
  1779. def from_column_and_tablename(
  1780. cls,
  1781. schema: Optional[str],
  1782. tname: str,
  1783. col: Column[Any],
  1784. ) -> AddColumnOp:
  1785. return cls(tname, col, schema=schema)
  1786. @classmethod
  1787. def add_column(
  1788. cls,
  1789. operations: Operations,
  1790. table_name: str,
  1791. column: Column[Any],
  1792. *,
  1793. schema: Optional[str] = None,
  1794. if_not_exists: Optional[bool] = None,
  1795. ) -> None:
  1796. """Issue an "add column" instruction using the current
  1797. migration context.
  1798. e.g.::
  1799. from alembic import op
  1800. from sqlalchemy import Column, String
  1801. op.add_column("organization", Column("name", String()))
  1802. The :meth:`.Operations.add_column` method typically corresponds
  1803. to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope
  1804. of this command, the column's name, datatype, nullability,
  1805. and optional server-generated defaults may be indicated.
  1806. .. note::
  1807. With the exception of NOT NULL constraints or single-column FOREIGN
  1808. KEY constraints, other kinds of constraints such as PRIMARY KEY,
  1809. UNIQUE or CHECK constraints **cannot** be generated using this
  1810. method; for these constraints, refer to operations such as
  1811. :meth:`.Operations.create_primary_key` and
  1812. :meth:`.Operations.create_check_constraint`. In particular, the
  1813. following :class:`~sqlalchemy.schema.Column` parameters are
  1814. **ignored**:
  1815. * :paramref:`~sqlalchemy.schema.Column.primary_key` - SQL databases
  1816. typically do not support an ALTER operation that can add
  1817. individual columns one at a time to an existing primary key
  1818. constraint, therefore it's less ambiguous to use the
  1819. :meth:`.Operations.create_primary_key` method, which assumes no
  1820. existing primary key constraint is present.
  1821. * :paramref:`~sqlalchemy.schema.Column.unique` - use the
  1822. :meth:`.Operations.create_unique_constraint` method
  1823. * :paramref:`~sqlalchemy.schema.Column.index` - use the
  1824. :meth:`.Operations.create_index` method
  1825. The provided :class:`~sqlalchemy.schema.Column` object may include a
  1826. :class:`~sqlalchemy.schema.ForeignKey` constraint directive,
  1827. referencing a remote table name. For this specific type of constraint,
  1828. Alembic will automatically emit a second ALTER statement in order to
  1829. add the single-column FOREIGN KEY constraint separately::
  1830. from alembic import op
  1831. from sqlalchemy import Column, INTEGER, ForeignKey
  1832. op.add_column(
  1833. "organization",
  1834. Column("account_id", INTEGER, ForeignKey("accounts.id")),
  1835. )
  1836. The column argument passed to :meth:`.Operations.add_column` is a
  1837. :class:`~sqlalchemy.schema.Column` construct, used in the same way it's
  1838. used in SQLAlchemy. In particular, values or functions to be indicated
  1839. as producing the column's default value on the database side are
  1840. specified using the ``server_default`` parameter, and not ``default``
  1841. which only specifies Python-side defaults::
  1842. from alembic import op
  1843. from sqlalchemy import Column, TIMESTAMP, func
  1844. # specify "DEFAULT NOW" along with the column add
  1845. op.add_column(
  1846. "account",
  1847. Column("timestamp", TIMESTAMP, server_default=func.now()),
  1848. )
  1849. :param table_name: String name of the parent table.
  1850. :param column: a :class:`sqlalchemy.schema.Column` object
  1851. representing the new column.
  1852. :param schema: Optional schema name to operate within. To control
  1853. quoting of the schema outside of the default behavior, use
  1854. the SQLAlchemy construct
  1855. :class:`~sqlalchemy.sql.elements.quoted_name`.
  1856. :param if_not_exists: If True, adds IF NOT EXISTS operator
  1857. when creating the new column for compatible dialects
  1858. .. versionadded:: 1.16.0
  1859. """
  1860. op = cls(
  1861. table_name,
  1862. column,
  1863. schema=schema,
  1864. if_not_exists=if_not_exists,
  1865. )
  1866. return operations.invoke(op)
  1867. @classmethod
  1868. def batch_add_column(
  1869. cls,
  1870. operations: BatchOperations,
  1871. column: Column[Any],
  1872. *,
  1873. insert_before: Optional[str] = None,
  1874. insert_after: Optional[str] = None,
  1875. if_not_exists: Optional[bool] = None,
  1876. ) -> None:
  1877. """Issue an "add column" instruction using the current
  1878. batch migration context.
  1879. .. seealso::
  1880. :meth:`.Operations.add_column`
  1881. """
  1882. kw = {}
  1883. if insert_before:
  1884. kw["insert_before"] = insert_before
  1885. if insert_after:
  1886. kw["insert_after"] = insert_after
  1887. op = cls(
  1888. operations.impl.table_name,
  1889. column,
  1890. schema=operations.impl.schema,
  1891. if_not_exists=if_not_exists,
  1892. **kw,
  1893. )
  1894. return operations.invoke(op)
  1895. @Operations.register_operation("drop_column")
  1896. @BatchOperations.register_operation("drop_column", "batch_drop_column")
  1897. class DropColumnOp(AlterTableOp):
  1898. """Represent a drop column operation."""
  1899. def __init__(
  1900. self,
  1901. table_name: str,
  1902. column_name: str,
  1903. *,
  1904. schema: Optional[str] = None,
  1905. if_exists: Optional[bool] = None,
  1906. _reverse: Optional[AddColumnOp] = None,
  1907. **kw: Any,
  1908. ) -> None:
  1909. super().__init__(table_name, schema=schema)
  1910. self.column_name = column_name
  1911. self.kw = kw
  1912. self.if_exists = if_exists
  1913. self._reverse = _reverse
  1914. def to_diff_tuple(
  1915. self,
  1916. ) -> Tuple[str, Optional[str], str, Column[Any]]:
  1917. return (
  1918. "remove_column",
  1919. self.schema,
  1920. self.table_name,
  1921. self.to_column(),
  1922. )
  1923. def reverse(self) -> AddColumnOp:
  1924. if self._reverse is None:
  1925. raise ValueError(
  1926. "operation is not reversible; "
  1927. "original column is not present"
  1928. )
  1929. op = AddColumnOp.from_column_and_tablename(
  1930. self.schema, self.table_name, self._reverse.column
  1931. )
  1932. op.if_not_exists = self.if_exists
  1933. return op
  1934. @classmethod
  1935. def from_column_and_tablename(
  1936. cls,
  1937. schema: Optional[str],
  1938. tname: str,
  1939. col: Column[Any],
  1940. ) -> DropColumnOp:
  1941. return cls(
  1942. tname,
  1943. col.name,
  1944. schema=schema,
  1945. _reverse=AddColumnOp.from_column_and_tablename(schema, tname, col),
  1946. )
  1947. def to_column(
  1948. self, migration_context: Optional[MigrationContext] = None
  1949. ) -> Column[Any]:
  1950. if self._reverse is not None:
  1951. return self._reverse.column
  1952. schema_obj = schemaobj.SchemaObjects(migration_context)
  1953. return schema_obj.column(self.column_name, NULLTYPE)
  1954. @classmethod
  1955. def drop_column(
  1956. cls,
  1957. operations: Operations,
  1958. table_name: str,
  1959. column_name: str,
  1960. *,
  1961. schema: Optional[str] = None,
  1962. **kw: Any,
  1963. ) -> None:
  1964. """Issue a "drop column" instruction using the current
  1965. migration context.
  1966. e.g.::
  1967. drop_column("organization", "account_id")
  1968. :param table_name: name of table
  1969. :param column_name: name of column
  1970. :param schema: Optional schema name to operate within. To control
  1971. quoting of the schema outside of the default behavior, use
  1972. the SQLAlchemy construct
  1973. :class:`~sqlalchemy.sql.elements.quoted_name`.
  1974. :param if_exists: If True, adds IF EXISTS operator when
  1975. dropping the new column for compatible dialects
  1976. .. versionadded:: 1.16.0
  1977. :param mssql_drop_check: Optional boolean. When ``True``, on
  1978. Microsoft SQL Server only, first
  1979. drop the CHECK constraint on the column using a
  1980. SQL-script-compatible
  1981. block that selects into a @variable from sys.check_constraints,
  1982. then exec's a separate DROP CONSTRAINT for that constraint.
  1983. :param mssql_drop_default: Optional boolean. When ``True``, on
  1984. Microsoft SQL Server only, first
  1985. drop the DEFAULT constraint on the column using a
  1986. SQL-script-compatible
  1987. block that selects into a @variable from sys.default_constraints,
  1988. then exec's a separate DROP CONSTRAINT for that default.
  1989. :param mssql_drop_foreign_key: Optional boolean. When ``True``, on
  1990. Microsoft SQL Server only, first
  1991. drop a single FOREIGN KEY constraint on the column using a
  1992. SQL-script-compatible
  1993. block that selects into a @variable from
  1994. sys.foreign_keys/sys.foreign_key_columns,
  1995. then exec's a separate DROP CONSTRAINT for that default. Only
  1996. works if the column has exactly one FK constraint which refers to
  1997. it, at the moment.
  1998. """
  1999. op = cls(table_name, column_name, schema=schema, **kw)
  2000. return operations.invoke(op)
  2001. @classmethod
  2002. def batch_drop_column(
  2003. cls, operations: BatchOperations, column_name: str, **kw: Any
  2004. ) -> None:
  2005. """Issue a "drop column" instruction using the current
  2006. batch migration context.
  2007. .. seealso::
  2008. :meth:`.Operations.drop_column`
  2009. """
  2010. op = cls(
  2011. operations.impl.table_name,
  2012. column_name,
  2013. schema=operations.impl.schema,
  2014. **kw,
  2015. )
  2016. return operations.invoke(op)
  2017. @Operations.register_operation("bulk_insert")
  2018. class BulkInsertOp(MigrateOperation):
  2019. """Represent a bulk insert operation."""
  2020. def __init__(
  2021. self,
  2022. table: Union[Table, TableClause],
  2023. rows: List[Dict[str, Any]],
  2024. *,
  2025. multiinsert: bool = True,
  2026. ) -> None:
  2027. self.table = table
  2028. self.rows = rows
  2029. self.multiinsert = multiinsert
  2030. @classmethod
  2031. def bulk_insert(
  2032. cls,
  2033. operations: Operations,
  2034. table: Union[Table, TableClause],
  2035. rows: List[Dict[str, Any]],
  2036. *,
  2037. multiinsert: bool = True,
  2038. ) -> None:
  2039. """Issue a "bulk insert" operation using the current
  2040. migration context.
  2041. This provides a means of representing an INSERT of multiple rows
  2042. which works equally well in the context of executing on a live
  2043. connection as well as that of generating a SQL script. In the
  2044. case of a SQL script, the values are rendered inline into the
  2045. statement.
  2046. e.g.::
  2047. from alembic import op
  2048. from datetime import date
  2049. from sqlalchemy.sql import table, column
  2050. from sqlalchemy import String, Integer, Date
  2051. # Create an ad-hoc table to use for the insert statement.
  2052. accounts_table = table(
  2053. "account",
  2054. column("id", Integer),
  2055. column("name", String),
  2056. column("create_date", Date),
  2057. )
  2058. op.bulk_insert(
  2059. accounts_table,
  2060. [
  2061. {
  2062. "id": 1,
  2063. "name": "John Smith",
  2064. "create_date": date(2010, 10, 5),
  2065. },
  2066. {
  2067. "id": 2,
  2068. "name": "Ed Williams",
  2069. "create_date": date(2007, 5, 27),
  2070. },
  2071. {
  2072. "id": 3,
  2073. "name": "Wendy Jones",
  2074. "create_date": date(2008, 8, 15),
  2075. },
  2076. ],
  2077. )
  2078. When using --sql mode, some datatypes may not render inline
  2079. automatically, such as dates and other special types. When this
  2080. issue is present, :meth:`.Operations.inline_literal` may be used::
  2081. op.bulk_insert(
  2082. accounts_table,
  2083. [
  2084. {
  2085. "id": 1,
  2086. "name": "John Smith",
  2087. "create_date": op.inline_literal("2010-10-05"),
  2088. },
  2089. {
  2090. "id": 2,
  2091. "name": "Ed Williams",
  2092. "create_date": op.inline_literal("2007-05-27"),
  2093. },
  2094. {
  2095. "id": 3,
  2096. "name": "Wendy Jones",
  2097. "create_date": op.inline_literal("2008-08-15"),
  2098. },
  2099. ],
  2100. multiinsert=False,
  2101. )
  2102. When using :meth:`.Operations.inline_literal` in conjunction with
  2103. :meth:`.Operations.bulk_insert`, in order for the statement to work
  2104. in "online" (e.g. non --sql) mode, the
  2105. :paramref:`~.Operations.bulk_insert.multiinsert`
  2106. flag should be set to ``False``, which will have the effect of
  2107. individual INSERT statements being emitted to the database, each
  2108. with a distinct VALUES clause, so that the "inline" values can
  2109. still be rendered, rather than attempting to pass the values
  2110. as bound parameters.
  2111. :param table: a table object which represents the target of the INSERT.
  2112. :param rows: a list of dictionaries indicating rows.
  2113. :param multiinsert: when at its default of True and --sql mode is not
  2114. enabled, the INSERT statement will be executed using
  2115. "executemany()" style, where all elements in the list of
  2116. dictionaries are passed as bound parameters in a single
  2117. list. Setting this to False results in individual INSERT
  2118. statements being emitted per parameter set, and is needed
  2119. in those cases where non-literal values are present in the
  2120. parameter sets.
  2121. """
  2122. op = cls(table, rows, multiinsert=multiinsert)
  2123. operations.invoke(op)
  2124. @Operations.register_operation("execute")
  2125. @BatchOperations.register_operation("execute", "batch_execute")
  2126. class ExecuteSQLOp(MigrateOperation):
  2127. """Represent an execute SQL operation."""
  2128. def __init__(
  2129. self,
  2130. sqltext: Union[Executable, str],
  2131. *,
  2132. execution_options: Optional[dict[str, Any]] = None,
  2133. ) -> None:
  2134. self.sqltext = sqltext
  2135. self.execution_options = execution_options
  2136. @classmethod
  2137. def execute(
  2138. cls,
  2139. operations: Operations,
  2140. sqltext: Union[Executable, str],
  2141. *,
  2142. execution_options: Optional[dict[str, Any]] = None,
  2143. ) -> None:
  2144. r"""Execute the given SQL using the current migration context.
  2145. The given SQL can be a plain string, e.g.::
  2146. op.execute("INSERT INTO table (foo) VALUES ('some value')")
  2147. Or it can be any kind of Core SQL Expression construct, such as
  2148. below where we use an update construct::
  2149. from sqlalchemy.sql import table, column
  2150. from sqlalchemy import String
  2151. from alembic import op
  2152. account = table("account", column("name", String))
  2153. op.execute(
  2154. account.update()
  2155. .where(account.c.name == op.inline_literal("account 1"))
  2156. .values({"name": op.inline_literal("account 2")})
  2157. )
  2158. Above, we made use of the SQLAlchemy
  2159. :func:`sqlalchemy.sql.expression.table` and
  2160. :func:`sqlalchemy.sql.expression.column` constructs to make a brief,
  2161. ad-hoc table construct just for our UPDATE statement. A full
  2162. :class:`~sqlalchemy.schema.Table` construct of course works perfectly
  2163. fine as well, though note it's a recommended practice to at least
  2164. ensure the definition of a table is self-contained within the migration
  2165. script, rather than imported from a module that may break compatibility
  2166. with older migrations.
  2167. In a SQL script context, the statement is emitted directly to the
  2168. output stream. There is *no* return result, however, as this
  2169. function is oriented towards generating a change script
  2170. that can run in "offline" mode. Additionally, parameterized
  2171. statements are discouraged here, as they *will not work* in offline
  2172. mode. Above, we use :meth:`.inline_literal` where parameters are
  2173. to be used.
  2174. For full interaction with a connected database where parameters can
  2175. also be used normally, use the "bind" available from the context::
  2176. from alembic import op
  2177. connection = op.get_bind()
  2178. connection.execute(
  2179. account.update()
  2180. .where(account.c.name == "account 1")
  2181. .values({"name": "account 2"})
  2182. )
  2183. Additionally, when passing the statement as a plain string, it is first
  2184. coerced into a :func:`sqlalchemy.sql.expression.text` construct
  2185. before being passed along. In the less likely case that the
  2186. literal SQL string contains a colon, it must be escaped with a
  2187. backslash, as::
  2188. op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')")
  2189. :param sqltext: Any legal SQLAlchemy expression, including:
  2190. * a string
  2191. * a :func:`sqlalchemy.sql.expression.text` construct.
  2192. * a :func:`sqlalchemy.sql.expression.insert` construct.
  2193. * a :func:`sqlalchemy.sql.expression.update` construct.
  2194. * a :func:`sqlalchemy.sql.expression.delete` construct.
  2195. * Any "executable" described in SQLAlchemy Core documentation,
  2196. noting that no result set is returned.
  2197. .. note:: when passing a plain string, the statement is coerced into
  2198. a :func:`sqlalchemy.sql.expression.text` construct. This construct
  2199. considers symbols with colons, e.g. ``:foo`` to be bound parameters.
  2200. To avoid this, ensure that colon symbols are escaped, e.g.
  2201. ``\:foo``.
  2202. :param execution_options: Optional dictionary of
  2203. execution options, will be passed to
  2204. :meth:`sqlalchemy.engine.Connection.execution_options`.
  2205. """
  2206. op = cls(sqltext, execution_options=execution_options)
  2207. return operations.invoke(op)
  2208. @classmethod
  2209. def batch_execute(
  2210. cls,
  2211. operations: Operations,
  2212. sqltext: Union[Executable, str],
  2213. *,
  2214. execution_options: Optional[dict[str, Any]] = None,
  2215. ) -> None:
  2216. """Execute the given SQL using the current migration context.
  2217. .. seealso::
  2218. :meth:`.Operations.execute`
  2219. """
  2220. return cls.execute(
  2221. operations, sqltext, execution_options=execution_options
  2222. )
  2223. def to_diff_tuple(self) -> Tuple[str, Union[Executable, str]]:
  2224. return ("execute", self.sqltext)
  2225. class OpContainer(MigrateOperation):
  2226. """Represent a sequence of operations operation."""
  2227. def __init__(self, ops: Sequence[MigrateOperation] = ()) -> None:
  2228. self.ops = list(ops)
  2229. def is_empty(self) -> bool:
  2230. return not self.ops
  2231. def as_diffs(self) -> Any:
  2232. return list(OpContainer._ops_as_diffs(self))
  2233. @classmethod
  2234. def _ops_as_diffs(
  2235. cls, migrations: OpContainer
  2236. ) -> Iterator[Tuple[Any, ...]]:
  2237. for op in migrations.ops:
  2238. if hasattr(op, "ops"):
  2239. yield from cls._ops_as_diffs(cast("OpContainer", op))
  2240. else:
  2241. yield op.to_diff_tuple()
  2242. class ModifyTableOps(OpContainer):
  2243. """Contains a sequence of operations that all apply to a single Table."""
  2244. def __init__(
  2245. self,
  2246. table_name: str,
  2247. ops: Sequence[MigrateOperation],
  2248. *,
  2249. schema: Optional[str] = None,
  2250. ) -> None:
  2251. super().__init__(ops)
  2252. self.table_name = table_name
  2253. self.schema = schema
  2254. def reverse(self) -> ModifyTableOps:
  2255. return ModifyTableOps(
  2256. self.table_name,
  2257. ops=list(reversed([op.reverse() for op in self.ops])),
  2258. schema=self.schema,
  2259. )
  2260. class UpgradeOps(OpContainer):
  2261. """contains a sequence of operations that would apply to the
  2262. 'upgrade' stream of a script.
  2263. .. seealso::
  2264. :ref:`customizing_revision`
  2265. """
  2266. def __init__(
  2267. self,
  2268. ops: Sequence[MigrateOperation] = (),
  2269. upgrade_token: str = "upgrades",
  2270. ) -> None:
  2271. super().__init__(ops=ops)
  2272. self.upgrade_token = upgrade_token
  2273. def reverse_into(self, downgrade_ops: DowngradeOps) -> DowngradeOps:
  2274. downgrade_ops.ops[:] = list(
  2275. reversed([op.reverse() for op in self.ops])
  2276. )
  2277. return downgrade_ops
  2278. def reverse(self) -> DowngradeOps:
  2279. return self.reverse_into(DowngradeOps(ops=[]))
  2280. class DowngradeOps(OpContainer):
  2281. """contains a sequence of operations that would apply to the
  2282. 'downgrade' stream of a script.
  2283. .. seealso::
  2284. :ref:`customizing_revision`
  2285. """
  2286. def __init__(
  2287. self,
  2288. ops: Sequence[MigrateOperation] = (),
  2289. downgrade_token: str = "downgrades",
  2290. ) -> None:
  2291. super().__init__(ops=ops)
  2292. self.downgrade_token = downgrade_token
  2293. def reverse(self) -> UpgradeOps:
  2294. return UpgradeOps(
  2295. ops=list(reversed([op.reverse() for op in self.ops]))
  2296. )
  2297. class MigrationScript(MigrateOperation):
  2298. """represents a migration script.
  2299. E.g. when autogenerate encounters this object, this corresponds to the
  2300. production of an actual script file.
  2301. A normal :class:`.MigrationScript` object would contain a single
  2302. :class:`.UpgradeOps` and a single :class:`.DowngradeOps` directive.
  2303. These are accessible via the ``.upgrade_ops`` and ``.downgrade_ops``
  2304. attributes.
  2305. In the case of an autogenerate operation that runs multiple times,
  2306. such as the multiple database example in the "multidb" template,
  2307. the ``.upgrade_ops`` and ``.downgrade_ops`` attributes are disabled,
  2308. and instead these objects should be accessed via the ``.upgrade_ops_list``
  2309. and ``.downgrade_ops_list`` list-based attributes. These latter
  2310. attributes are always available at the very least as single-element lists.
  2311. .. seealso::
  2312. :ref:`customizing_revision`
  2313. """
  2314. _needs_render: Optional[bool]
  2315. _upgrade_ops: List[UpgradeOps]
  2316. _downgrade_ops: List[DowngradeOps]
  2317. def __init__(
  2318. self,
  2319. rev_id: Optional[str],
  2320. upgrade_ops: UpgradeOps,
  2321. downgrade_ops: DowngradeOps,
  2322. *,
  2323. message: Optional[str] = None,
  2324. imports: Set[str] = set(),
  2325. head: Optional[str] = None,
  2326. splice: Optional[bool] = None,
  2327. branch_label: Optional[_RevIdType] = None,
  2328. version_path: Union[str, os.PathLike[str], None] = None,
  2329. depends_on: Optional[_RevIdType] = None,
  2330. ) -> None:
  2331. self.rev_id = rev_id
  2332. self.message = message
  2333. self.imports = imports
  2334. self.head = head
  2335. self.splice = splice
  2336. self.branch_label = branch_label
  2337. self.version_path = (
  2338. pathlib.Path(version_path).as_posix() if version_path else None
  2339. )
  2340. self.depends_on = depends_on
  2341. self.upgrade_ops = upgrade_ops
  2342. self.downgrade_ops = downgrade_ops
  2343. @property
  2344. def upgrade_ops(self) -> Optional[UpgradeOps]:
  2345. """An instance of :class:`.UpgradeOps`.
  2346. .. seealso::
  2347. :attr:`.MigrationScript.upgrade_ops_list`
  2348. """
  2349. if len(self._upgrade_ops) > 1:
  2350. raise ValueError(
  2351. "This MigrationScript instance has a multiple-entry "
  2352. "list for UpgradeOps; please use the "
  2353. "upgrade_ops_list attribute."
  2354. )
  2355. elif not self._upgrade_ops:
  2356. return None
  2357. else:
  2358. return self._upgrade_ops[0]
  2359. @upgrade_ops.setter
  2360. def upgrade_ops(
  2361. self, upgrade_ops: Union[UpgradeOps, List[UpgradeOps]]
  2362. ) -> None:
  2363. self._upgrade_ops = util.to_list(upgrade_ops)
  2364. for elem in self._upgrade_ops:
  2365. assert isinstance(elem, UpgradeOps)
  2366. @property
  2367. def downgrade_ops(self) -> Optional[DowngradeOps]:
  2368. """An instance of :class:`.DowngradeOps`.
  2369. .. seealso::
  2370. :attr:`.MigrationScript.downgrade_ops_list`
  2371. """
  2372. if len(self._downgrade_ops) > 1:
  2373. raise ValueError(
  2374. "This MigrationScript instance has a multiple-entry "
  2375. "list for DowngradeOps; please use the "
  2376. "downgrade_ops_list attribute."
  2377. )
  2378. elif not self._downgrade_ops:
  2379. return None
  2380. else:
  2381. return self._downgrade_ops[0]
  2382. @downgrade_ops.setter
  2383. def downgrade_ops(
  2384. self, downgrade_ops: Union[DowngradeOps, List[DowngradeOps]]
  2385. ) -> None:
  2386. self._downgrade_ops = util.to_list(downgrade_ops)
  2387. for elem in self._downgrade_ops:
  2388. assert isinstance(elem, DowngradeOps)
  2389. @property
  2390. def upgrade_ops_list(self) -> List[UpgradeOps]:
  2391. """A list of :class:`.UpgradeOps` instances.
  2392. This is used in place of the :attr:`.MigrationScript.upgrade_ops`
  2393. attribute when dealing with a revision operation that does
  2394. multiple autogenerate passes.
  2395. """
  2396. return self._upgrade_ops
  2397. @property
  2398. def downgrade_ops_list(self) -> List[DowngradeOps]:
  2399. """A list of :class:`.DowngradeOps` instances.
  2400. This is used in place of the :attr:`.MigrationScript.downgrade_ops`
  2401. attribute when dealing with a revision operation that does
  2402. multiple autogenerate passes.
  2403. """
  2404. return self._downgrade_ops