ops.py 93 KB

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