ops.py 92 KB

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