migration.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396
  1. # mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
  2. # mypy: no-warn-return-any, allow-any-generics
  3. from __future__ import annotations
  4. from contextlib import contextmanager
  5. from contextlib import nullcontext
  6. import logging
  7. import sys
  8. from typing import Any
  9. from typing import Callable
  10. from typing import cast
  11. from typing import Collection
  12. from typing import ContextManager
  13. from typing import Dict
  14. from typing import Iterable
  15. from typing import Iterator
  16. from typing import List
  17. from typing import Optional
  18. from typing import Set
  19. from typing import Tuple
  20. from typing import TYPE_CHECKING
  21. from typing import Union
  22. from sqlalchemy import Column
  23. from sqlalchemy import literal_column
  24. from sqlalchemy import MetaData
  25. from sqlalchemy import PrimaryKeyConstraint
  26. from sqlalchemy import String
  27. from sqlalchemy import Table
  28. from sqlalchemy.engine import Engine
  29. from sqlalchemy.engine import url as sqla_url
  30. from sqlalchemy.engine.strategies import MockEngineStrategy
  31. from .. import ddl
  32. from .. import util
  33. from ..util import sqla_compat
  34. from ..util.compat import EncodedIO
  35. if TYPE_CHECKING:
  36. from sqlalchemy.engine import Dialect
  37. from sqlalchemy.engine import URL
  38. from sqlalchemy.engine.base import Connection
  39. from sqlalchemy.engine.base import Transaction
  40. from sqlalchemy.engine.mock import MockConnection
  41. from sqlalchemy.sql import Executable
  42. from .environment import EnvironmentContext
  43. from ..config import Config
  44. from ..script.base import Script
  45. from ..script.base import ScriptDirectory
  46. from ..script.revision import _RevisionOrBase
  47. from ..script.revision import Revision
  48. from ..script.revision import RevisionMap
  49. log = logging.getLogger(__name__)
  50. class _ProxyTransaction:
  51. def __init__(self, migration_context: MigrationContext) -> None:
  52. self.migration_context = migration_context
  53. @property
  54. def _proxied_transaction(self) -> Optional[Transaction]:
  55. return self.migration_context._transaction
  56. def rollback(self) -> None:
  57. t = self._proxied_transaction
  58. assert t is not None
  59. t.rollback()
  60. self.migration_context._transaction = None
  61. def commit(self) -> None:
  62. t = self._proxied_transaction
  63. assert t is not None
  64. t.commit()
  65. self.migration_context._transaction = None
  66. def __enter__(self) -> _ProxyTransaction:
  67. return self
  68. def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
  69. if self._proxied_transaction is not None:
  70. self._proxied_transaction.__exit__(type_, value, traceback)
  71. self.migration_context._transaction = None
  72. class MigrationContext:
  73. """Represent the database state made available to a migration
  74. script.
  75. :class:`.MigrationContext` is the front end to an actual
  76. database connection, or alternatively a string output
  77. stream given a particular database dialect,
  78. from an Alembic perspective.
  79. When inside the ``env.py`` script, the :class:`.MigrationContext`
  80. is available via the
  81. :meth:`.EnvironmentContext.get_context` method,
  82. which is available at ``alembic.context``::
  83. # from within env.py script
  84. from alembic import context
  85. migration_context = context.get_context()
  86. For usage outside of an ``env.py`` script, such as for
  87. utility routines that want to check the current version
  88. in the database, the :meth:`.MigrationContext.configure`
  89. method to create new :class:`.MigrationContext` objects.
  90. For example, to get at the current revision in the
  91. database using :meth:`.MigrationContext.get_current_revision`::
  92. # in any application, outside of an env.py script
  93. from alembic.migration import MigrationContext
  94. from sqlalchemy import create_engine
  95. engine = create_engine("postgresql://mydatabase")
  96. conn = engine.connect()
  97. context = MigrationContext.configure(conn)
  98. current_rev = context.get_current_revision()
  99. The above context can also be used to produce
  100. Alembic migration operations with an :class:`.Operations`
  101. instance::
  102. # in any application, outside of the normal Alembic environment
  103. from alembic.operations import Operations
  104. op = Operations(context)
  105. op.alter_column("mytable", "somecolumn", nullable=True)
  106. """
  107. def __init__(
  108. self,
  109. dialect: Dialect,
  110. connection: Optional[Connection],
  111. opts: Dict[str, Any],
  112. environment_context: Optional[EnvironmentContext] = None,
  113. ) -> None:
  114. self.environment_context = environment_context
  115. self.opts = opts
  116. self.dialect = dialect
  117. self.script: Optional[ScriptDirectory] = opts.get("script")
  118. as_sql: bool = opts.get("as_sql", False)
  119. transactional_ddl = opts.get("transactional_ddl")
  120. self._transaction_per_migration = opts.get(
  121. "transaction_per_migration", False
  122. )
  123. self.on_version_apply_callbacks = opts.get("on_version_apply", ())
  124. self._transaction: Optional[Transaction] = None
  125. if as_sql:
  126. self.connection = cast(
  127. Optional["Connection"], self._stdout_connection(connection)
  128. )
  129. assert self.connection is not None
  130. self._in_external_transaction = False
  131. else:
  132. self.connection = connection
  133. self._in_external_transaction = (
  134. sqla_compat._get_connection_in_transaction(connection)
  135. )
  136. self._migrations_fn: Optional[
  137. Callable[..., Iterable[RevisionStep]]
  138. ] = opts.get("fn")
  139. self.as_sql = as_sql
  140. self.purge = opts.get("purge", False)
  141. if "output_encoding" in opts:
  142. self.output_buffer = EncodedIO(
  143. opts.get("output_buffer")
  144. or sys.stdout, # type:ignore[arg-type]
  145. opts["output_encoding"],
  146. )
  147. else:
  148. self.output_buffer = opts.get("output_buffer", sys.stdout)
  149. self._user_compare_type = opts.get("compare_type", True)
  150. self._user_compare_server_default = opts.get(
  151. "compare_server_default", False
  152. )
  153. self.version_table = version_table = opts.get(
  154. "version_table", "alembic_version"
  155. )
  156. self.version_table_schema = version_table_schema = opts.get(
  157. "version_table_schema", None
  158. )
  159. self._version = Table(
  160. version_table,
  161. MetaData(),
  162. Column("version_num", String(32), nullable=False),
  163. schema=version_table_schema,
  164. )
  165. if opts.get("version_table_pk", True):
  166. self._version.append_constraint(
  167. PrimaryKeyConstraint(
  168. "version_num", name="%s_pkc" % version_table
  169. )
  170. )
  171. self._start_from_rev: Optional[str] = opts.get("starting_rev")
  172. self.impl = ddl.DefaultImpl.get_by_dialect(dialect)(
  173. dialect,
  174. self.connection,
  175. self.as_sql,
  176. transactional_ddl,
  177. self.output_buffer,
  178. opts,
  179. )
  180. log.info("Context impl %s.", self.impl.__class__.__name__)
  181. if self.as_sql:
  182. log.info("Generating static SQL")
  183. log.info(
  184. "Will assume %s DDL.",
  185. "transactional"
  186. if self.impl.transactional_ddl
  187. else "non-transactional",
  188. )
  189. @classmethod
  190. def configure(
  191. cls,
  192. connection: Optional[Connection] = None,
  193. url: Optional[Union[str, URL]] = None,
  194. dialect_name: Optional[str] = None,
  195. dialect: Optional[Dialect] = None,
  196. environment_context: Optional[EnvironmentContext] = None,
  197. dialect_opts: Optional[Dict[str, str]] = None,
  198. opts: Optional[Any] = None,
  199. ) -> MigrationContext:
  200. """Create a new :class:`.MigrationContext`.
  201. This is a factory method usually called
  202. by :meth:`.EnvironmentContext.configure`.
  203. :param connection: a :class:`~sqlalchemy.engine.Connection`
  204. to use for SQL execution in "online" mode. When present,
  205. is also used to determine the type of dialect in use.
  206. :param url: a string database url, or a
  207. :class:`sqlalchemy.engine.url.URL` object.
  208. The type of dialect to be used will be derived from this if
  209. ``connection`` is not passed.
  210. :param dialect_name: string name of a dialect, such as
  211. "postgresql", "mssql", etc. The type of dialect to be used will be
  212. derived from this if ``connection`` and ``url`` are not passed.
  213. :param opts: dictionary of options. Most other options
  214. accepted by :meth:`.EnvironmentContext.configure` are passed via
  215. this dictionary.
  216. """
  217. if opts is None:
  218. opts = {}
  219. if dialect_opts is None:
  220. dialect_opts = {}
  221. if connection:
  222. if isinstance(connection, Engine):
  223. raise util.CommandError(
  224. "'connection' argument to configure() is expected "
  225. "to be a sqlalchemy.engine.Connection instance, "
  226. "got %r" % connection,
  227. )
  228. dialect = connection.dialect
  229. elif url:
  230. url_obj = sqla_url.make_url(url)
  231. dialect = url_obj.get_dialect()(**dialect_opts)
  232. elif dialect_name:
  233. url_obj = sqla_url.make_url("%s://" % dialect_name)
  234. dialect = url_obj.get_dialect()(**dialect_opts)
  235. elif not dialect:
  236. raise Exception("Connection, url, or dialect_name is required.")
  237. assert dialect is not None
  238. return MigrationContext(dialect, connection, opts, environment_context)
  239. @contextmanager
  240. def autocommit_block(self) -> Iterator[None]:
  241. """Enter an "autocommit" block, for databases that support AUTOCOMMIT
  242. isolation levels.
  243. This special directive is intended to support the occasional database
  244. DDL or system operation that specifically has to be run outside of
  245. any kind of transaction block. The PostgreSQL database platform
  246. is the most common target for this style of operation, as many
  247. of its DDL operations must be run outside of transaction blocks, even
  248. though the database overall supports transactional DDL.
  249. The method is used as a context manager within a migration script, by
  250. calling on :meth:`.Operations.get_context` to retrieve the
  251. :class:`.MigrationContext`, then invoking
  252. :meth:`.MigrationContext.autocommit_block` using the ``with:``
  253. statement::
  254. def upgrade():
  255. with op.get_context().autocommit_block():
  256. op.execute("ALTER TYPE mood ADD VALUE 'soso'")
  257. Above, a PostgreSQL "ALTER TYPE..ADD VALUE" directive is emitted,
  258. which must be run outside of a transaction block at the database level.
  259. The :meth:`.MigrationContext.autocommit_block` method makes use of the
  260. SQLAlchemy ``AUTOCOMMIT`` isolation level setting, which against the
  261. psycogp2 DBAPI corresponds to the ``connection.autocommit`` setting,
  262. to ensure that the database driver is not inside of a DBAPI level
  263. transaction block.
  264. .. warning::
  265. As is necessary, **the database transaction preceding the block is
  266. unconditionally committed**. This means that the run of migrations
  267. preceding the operation will be committed, before the overall
  268. migration operation is complete.
  269. It is recommended that when an application includes migrations with
  270. "autocommit" blocks, that
  271. :paramref:`.EnvironmentContext.transaction_per_migration` be used
  272. so that the calling environment is tuned to expect short per-file
  273. migrations whether or not one of them has an autocommit block.
  274. """
  275. _in_connection_transaction = self._in_connection_transaction()
  276. if self.impl.transactional_ddl and self.as_sql:
  277. self.impl.emit_commit()
  278. elif _in_connection_transaction:
  279. assert self._transaction is not None
  280. self._transaction.commit()
  281. self._transaction = None
  282. if not self.as_sql:
  283. assert self.connection is not None
  284. current_level = self.connection.get_isolation_level()
  285. base_connection = self.connection
  286. # in 1.3 and 1.4 non-future mode, the connection gets switched
  287. # out. we can use the base connection with the new mode
  288. # except that it will not know it's in "autocommit" and will
  289. # emit deprecation warnings when an autocommit action takes
  290. # place.
  291. self.connection = (
  292. self.impl.connection
  293. ) = base_connection.execution_options(isolation_level="AUTOCOMMIT")
  294. # sqlalchemy future mode will "autobegin" in any case, so take
  295. # control of that "transaction" here
  296. fake_trans: Optional[Transaction] = self.connection.begin()
  297. else:
  298. fake_trans = None
  299. try:
  300. yield
  301. finally:
  302. if not self.as_sql:
  303. assert self.connection is not None
  304. if fake_trans is not None:
  305. fake_trans.commit()
  306. self.connection.execution_options(
  307. isolation_level=current_level
  308. )
  309. self.connection = self.impl.connection = base_connection
  310. if self.impl.transactional_ddl and self.as_sql:
  311. self.impl.emit_begin()
  312. elif _in_connection_transaction:
  313. assert self.connection is not None
  314. self._transaction = self.connection.begin()
  315. def begin_transaction(
  316. self, _per_migration: bool = False
  317. ) -> Union[_ProxyTransaction, ContextManager[None]]:
  318. """Begin a logical transaction for migration operations.
  319. This method is used within an ``env.py`` script to demarcate where
  320. the outer "transaction" for a series of migrations begins. Example::
  321. def run_migrations_online():
  322. connectable = create_engine(...)
  323. with connectable.connect() as connection:
  324. context.configure(
  325. connection=connection, target_metadata=target_metadata
  326. )
  327. with context.begin_transaction():
  328. context.run_migrations()
  329. Above, :meth:`.MigrationContext.begin_transaction` is used to demarcate
  330. where the outer logical transaction occurs around the
  331. :meth:`.MigrationContext.run_migrations` operation.
  332. A "Logical" transaction means that the operation may or may not
  333. correspond to a real database transaction. If the target database
  334. supports transactional DDL (or
  335. :paramref:`.EnvironmentContext.configure.transactional_ddl` is true),
  336. the :paramref:`.EnvironmentContext.configure.transaction_per_migration`
  337. flag is not set, and the migration is against a real database
  338. connection (as opposed to using "offline" ``--sql`` mode), a real
  339. transaction will be started. If ``--sql`` mode is in effect, the
  340. operation would instead correspond to a string such as "BEGIN" being
  341. emitted to the string output.
  342. The returned object is a Python context manager that should only be
  343. used in the context of a ``with:`` statement as indicated above.
  344. The object has no other guaranteed API features present.
  345. .. seealso::
  346. :meth:`.MigrationContext.autocommit_block`
  347. """
  348. if self._in_external_transaction:
  349. return nullcontext()
  350. if self.impl.transactional_ddl:
  351. transaction_now = _per_migration == self._transaction_per_migration
  352. else:
  353. transaction_now = _per_migration is True
  354. if not transaction_now:
  355. return nullcontext()
  356. elif not self.impl.transactional_ddl:
  357. assert _per_migration
  358. if self.as_sql:
  359. return nullcontext()
  360. else:
  361. # track our own notion of a "transaction block", which must be
  362. # committed when complete. Don't rely upon whether or not the
  363. # SQLAlchemy connection reports as "in transaction"; this
  364. # because SQLAlchemy future connection features autobegin
  365. # behavior, so it may already be in a transaction from our
  366. # emitting of queries like "has_version_table", etc. While we
  367. # could track these operations as well, that leaves open the
  368. # possibility of new operations or other things happening in
  369. # the user environment that still may be triggering
  370. # "autobegin".
  371. in_transaction = self._transaction is not None
  372. if in_transaction:
  373. return nullcontext()
  374. else:
  375. assert self.connection is not None
  376. self._transaction = (
  377. sqla_compat._safe_begin_connection_transaction(
  378. self.connection
  379. )
  380. )
  381. return _ProxyTransaction(self)
  382. elif self.as_sql:
  383. @contextmanager
  384. def begin_commit():
  385. self.impl.emit_begin()
  386. yield
  387. self.impl.emit_commit()
  388. return begin_commit()
  389. else:
  390. assert self.connection is not None
  391. self._transaction = sqla_compat._safe_begin_connection_transaction(
  392. self.connection
  393. )
  394. return _ProxyTransaction(self)
  395. def get_current_revision(self) -> Optional[str]:
  396. """Return the current revision, usually that which is present
  397. in the ``alembic_version`` table in the database.
  398. This method intends to be used only for a migration stream that
  399. does not contain unmerged branches in the target database;
  400. if there are multiple branches present, an exception is raised.
  401. The :meth:`.MigrationContext.get_current_heads` should be preferred
  402. over this method going forward in order to be compatible with
  403. branch migration support.
  404. If this :class:`.MigrationContext` was configured in "offline"
  405. mode, that is with ``as_sql=True``, the ``starting_rev``
  406. parameter is returned instead, if any.
  407. """
  408. heads = self.get_current_heads()
  409. if len(heads) == 0:
  410. return None
  411. elif len(heads) > 1:
  412. raise util.CommandError(
  413. "Version table '%s' has more than one head present; "
  414. "please use get_current_heads()" % self.version_table
  415. )
  416. else:
  417. return heads[0]
  418. def get_current_heads(self) -> Tuple[str, ...]:
  419. """Return a tuple of the current 'head versions' that are represented
  420. in the target database.
  421. For a migration stream without branches, this will be a single
  422. value, synonymous with that of
  423. :meth:`.MigrationContext.get_current_revision`. However when multiple
  424. unmerged branches exist within the target database, the returned tuple
  425. will contain a value for each head.
  426. If this :class:`.MigrationContext` was configured in "offline"
  427. mode, that is with ``as_sql=True``, the ``starting_rev``
  428. parameter is returned in a one-length tuple.
  429. If no version table is present, or if there are no revisions
  430. present, an empty tuple is returned.
  431. """
  432. if self.as_sql:
  433. start_from_rev: Any = self._start_from_rev
  434. if start_from_rev == "base":
  435. start_from_rev = None
  436. elif start_from_rev is not None and self.script:
  437. start_from_rev = [
  438. self.script.get_revision(sfr).revision
  439. for sfr in util.to_list(start_from_rev)
  440. if sfr not in (None, "base")
  441. ]
  442. return util.to_tuple(start_from_rev, default=())
  443. else:
  444. if self._start_from_rev:
  445. raise util.CommandError(
  446. "Can't specify current_rev to context "
  447. "when using a database connection"
  448. )
  449. if not self._has_version_table():
  450. return ()
  451. assert self.connection is not None
  452. return tuple(
  453. row[0] for row in self.connection.execute(self._version.select())
  454. )
  455. def _ensure_version_table(self, purge: bool = False) -> None:
  456. with sqla_compat._ensure_scope_for_ddl(self.connection):
  457. assert self.connection is not None
  458. self._version.create(self.connection, checkfirst=True)
  459. if purge:
  460. assert self.connection is not None
  461. self.connection.execute(self._version.delete())
  462. def _has_version_table(self) -> bool:
  463. assert self.connection is not None
  464. return sqla_compat._connectable_has_table(
  465. self.connection, self.version_table, self.version_table_schema
  466. )
  467. def stamp(self, script_directory: ScriptDirectory, revision: str) -> None:
  468. """Stamp the version table with a specific revision.
  469. This method calculates those branches to which the given revision
  470. can apply, and updates those branches as though they were migrated
  471. towards that revision (either up or down). If no current branches
  472. include the revision, it is added as a new branch head.
  473. """
  474. heads = self.get_current_heads()
  475. if not self.as_sql and not heads:
  476. self._ensure_version_table()
  477. head_maintainer = HeadMaintainer(self, heads)
  478. for step in script_directory._stamp_revs(revision, heads):
  479. head_maintainer.update_to_step(step)
  480. def run_migrations(self, **kw: Any) -> None:
  481. r"""Run the migration scripts established for this
  482. :class:`.MigrationContext`, if any.
  483. The commands in :mod:`alembic.command` will set up a function
  484. that is ultimately passed to the :class:`.MigrationContext`
  485. as the ``fn`` argument. This function represents the "work"
  486. that will be done when :meth:`.MigrationContext.run_migrations`
  487. is called, typically from within the ``env.py`` script of the
  488. migration environment. The "work function" then provides an iterable
  489. of version callables and other version information which
  490. in the case of the ``upgrade`` or ``downgrade`` commands are the
  491. list of version scripts to invoke. Other commands yield nothing,
  492. in the case that a command wants to run some other operation
  493. against the database such as the ``current`` or ``stamp`` commands.
  494. :param \**kw: keyword arguments here will be passed to each
  495. migration callable, that is the ``upgrade()`` or ``downgrade()``
  496. method within revision scripts.
  497. """
  498. self.impl.start_migrations()
  499. heads: Tuple[str, ...]
  500. if self.purge:
  501. if self.as_sql:
  502. raise util.CommandError("Can't use --purge with --sql mode")
  503. self._ensure_version_table(purge=True)
  504. heads = ()
  505. else:
  506. heads = self.get_current_heads()
  507. dont_mutate = self.opts.get("dont_mutate", False)
  508. if not self.as_sql and not heads and not dont_mutate:
  509. self._ensure_version_table()
  510. head_maintainer = HeadMaintainer(self, heads)
  511. assert self._migrations_fn is not None
  512. for step in self._migrations_fn(heads, self):
  513. with self.begin_transaction(_per_migration=True):
  514. if self.as_sql and not head_maintainer.heads:
  515. # for offline mode, include a CREATE TABLE from
  516. # the base
  517. assert self.connection is not None
  518. self._version.create(self.connection)
  519. log.info("Running %s", step)
  520. if self.as_sql:
  521. self.impl.static_output(
  522. "-- Running %s" % (step.short_log,)
  523. )
  524. step.migration_fn(**kw)
  525. # previously, we wouldn't stamp per migration
  526. # if we were in a transaction, however given the more
  527. # complex model that involves any number of inserts
  528. # and row-targeted updates and deletes, it's simpler for now
  529. # just to run the operations on every version
  530. head_maintainer.update_to_step(step)
  531. for callback in self.on_version_apply_callbacks:
  532. callback(
  533. ctx=self,
  534. step=step.info,
  535. heads=set(head_maintainer.heads),
  536. run_args=kw,
  537. )
  538. if self.as_sql and not head_maintainer.heads:
  539. assert self.connection is not None
  540. self._version.drop(self.connection)
  541. def _in_connection_transaction(self) -> bool:
  542. try:
  543. meth = self.connection.in_transaction # type:ignore[union-attr]
  544. except AttributeError:
  545. return False
  546. else:
  547. return meth()
  548. def execute(
  549. self,
  550. sql: Union[Executable, str],
  551. execution_options: Optional[Dict[str, Any]] = None,
  552. ) -> None:
  553. """Execute a SQL construct or string statement.
  554. The underlying execution mechanics are used, that is
  555. if this is "offline mode" the SQL is written to the
  556. output buffer, otherwise the SQL is emitted on
  557. the current SQLAlchemy connection.
  558. """
  559. self.impl._exec(sql, execution_options)
  560. def _stdout_connection(
  561. self, connection: Optional[Connection]
  562. ) -> MockConnection:
  563. def dump(construct, *multiparams, **params):
  564. self.impl._exec(construct)
  565. return MockEngineStrategy.MockConnection(self.dialect, dump)
  566. @property
  567. def bind(self) -> Optional[Connection]:
  568. """Return the current "bind".
  569. In online mode, this is an instance of
  570. :class:`sqlalchemy.engine.Connection`, and is suitable
  571. for ad-hoc execution of any kind of usage described
  572. in SQLAlchemy Core documentation as well as
  573. for usage with the :meth:`sqlalchemy.schema.Table.create`
  574. and :meth:`sqlalchemy.schema.MetaData.create_all` methods
  575. of :class:`~sqlalchemy.schema.Table`,
  576. :class:`~sqlalchemy.schema.MetaData`.
  577. Note that when "standard output" mode is enabled,
  578. this bind will be a "mock" connection handler that cannot
  579. return results and is only appropriate for a very limited
  580. subset of commands.
  581. """
  582. return self.connection
  583. @property
  584. def config(self) -> Optional[Config]:
  585. """Return the :class:`.Config` used by the current environment,
  586. if any."""
  587. if self.environment_context:
  588. return self.environment_context.config
  589. else:
  590. return None
  591. def _compare_type(
  592. self, inspector_column: Column[Any], metadata_column: Column
  593. ) -> bool:
  594. if self._user_compare_type is False:
  595. return False
  596. if callable(self._user_compare_type):
  597. user_value = self._user_compare_type(
  598. self,
  599. inspector_column,
  600. metadata_column,
  601. inspector_column.type,
  602. metadata_column.type,
  603. )
  604. if user_value is not None:
  605. return user_value
  606. return self.impl.compare_type(inspector_column, metadata_column)
  607. def _compare_server_default(
  608. self,
  609. inspector_column: Column[Any],
  610. metadata_column: Column[Any],
  611. rendered_metadata_default: Optional[str],
  612. rendered_column_default: Optional[str],
  613. ) -> bool:
  614. if self._user_compare_server_default is False:
  615. return False
  616. if callable(self._user_compare_server_default):
  617. user_value = self._user_compare_server_default(
  618. self,
  619. inspector_column,
  620. metadata_column,
  621. rendered_column_default,
  622. metadata_column.server_default,
  623. rendered_metadata_default,
  624. )
  625. if user_value is not None:
  626. return user_value
  627. return self.impl.compare_server_default(
  628. inspector_column,
  629. metadata_column,
  630. rendered_metadata_default,
  631. rendered_column_default,
  632. )
  633. class HeadMaintainer:
  634. def __init__(self, context: MigrationContext, heads: Any) -> None:
  635. self.context = context
  636. self.heads = set(heads)
  637. def _insert_version(self, version: str) -> None:
  638. assert version not in self.heads
  639. self.heads.add(version)
  640. self.context.impl._exec(
  641. self.context._version.insert().values(
  642. version_num=literal_column("'%s'" % version)
  643. )
  644. )
  645. def _delete_version(self, version: str) -> None:
  646. self.heads.remove(version)
  647. ret = self.context.impl._exec(
  648. self.context._version.delete().where(
  649. self.context._version.c.version_num
  650. == literal_column("'%s'" % version)
  651. )
  652. )
  653. if (
  654. not self.context.as_sql
  655. and self.context.dialect.supports_sane_rowcount
  656. and ret is not None
  657. and ret.rowcount != 1
  658. ):
  659. raise util.CommandError(
  660. "Online migration expected to match one "
  661. "row when deleting '%s' in '%s'; "
  662. "%d found"
  663. % (version, self.context.version_table, ret.rowcount)
  664. )
  665. def _update_version(self, from_: str, to_: str) -> None:
  666. assert to_ not in self.heads
  667. self.heads.remove(from_)
  668. self.heads.add(to_)
  669. ret = self.context.impl._exec(
  670. self.context._version.update()
  671. .values(version_num=literal_column("'%s'" % to_))
  672. .where(
  673. self.context._version.c.version_num
  674. == literal_column("'%s'" % from_)
  675. )
  676. )
  677. if (
  678. not self.context.as_sql
  679. and self.context.dialect.supports_sane_rowcount
  680. and ret is not None
  681. and ret.rowcount != 1
  682. ):
  683. raise util.CommandError(
  684. "Online migration expected to match one "
  685. "row when updating '%s' to '%s' in '%s'; "
  686. "%d found"
  687. % (from_, to_, self.context.version_table, ret.rowcount)
  688. )
  689. def update_to_step(self, step: Union[RevisionStep, StampStep]) -> None:
  690. if step.should_delete_branch(self.heads):
  691. vers = step.delete_version_num
  692. log.debug("branch delete %s", vers)
  693. self._delete_version(vers)
  694. elif step.should_create_branch(self.heads):
  695. vers = step.insert_version_num
  696. log.debug("new branch insert %s", vers)
  697. self._insert_version(vers)
  698. elif step.should_merge_branches(self.heads):
  699. # delete revs, update from rev, update to rev
  700. (
  701. delete_revs,
  702. update_from_rev,
  703. update_to_rev,
  704. ) = step.merge_branch_idents(self.heads)
  705. log.debug(
  706. "merge, delete %s, update %s to %s",
  707. delete_revs,
  708. update_from_rev,
  709. update_to_rev,
  710. )
  711. for delrev in delete_revs:
  712. self._delete_version(delrev)
  713. self._update_version(update_from_rev, update_to_rev)
  714. elif step.should_unmerge_branches(self.heads):
  715. (
  716. update_from_rev,
  717. update_to_rev,
  718. insert_revs,
  719. ) = step.unmerge_branch_idents(self.heads)
  720. log.debug(
  721. "unmerge, insert %s, update %s to %s",
  722. insert_revs,
  723. update_from_rev,
  724. update_to_rev,
  725. )
  726. for insrev in insert_revs:
  727. self._insert_version(insrev)
  728. self._update_version(update_from_rev, update_to_rev)
  729. else:
  730. from_, to_ = step.update_version_num(self.heads)
  731. log.debug("update %s to %s", from_, to_)
  732. self._update_version(from_, to_)
  733. class MigrationInfo:
  734. """Exposes information about a migration step to a callback listener.
  735. The :class:`.MigrationInfo` object is available exclusively for the
  736. benefit of the :paramref:`.EnvironmentContext.on_version_apply`
  737. callback hook.
  738. """
  739. is_upgrade: bool
  740. """True/False: indicates whether this operation ascends or descends the
  741. version tree."""
  742. is_stamp: bool
  743. """True/False: indicates whether this operation is a stamp (i.e. whether
  744. it results in any actual database operations)."""
  745. up_revision_id: Optional[str]
  746. """Version string corresponding to :attr:`.Revision.revision`.
  747. In the case of a stamp operation, it is advised to use the
  748. :attr:`.MigrationInfo.up_revision_ids` tuple as a stamp operation can
  749. make a single movement from one or more branches down to a single
  750. branchpoint, in which case there will be multiple "up" revisions.
  751. .. seealso::
  752. :attr:`.MigrationInfo.up_revision_ids`
  753. """
  754. up_revision_ids: Tuple[str, ...]
  755. """Tuple of version strings corresponding to :attr:`.Revision.revision`.
  756. In the majority of cases, this tuple will be a single value, synonymous
  757. with the scalar value of :attr:`.MigrationInfo.up_revision_id`.
  758. It can be multiple revision identifiers only in the case of an
  759. ``alembic stamp`` operation which is moving downwards from multiple
  760. branches down to their common branch point.
  761. """
  762. down_revision_ids: Tuple[str, ...]
  763. """Tuple of strings representing the base revisions of this migration step.
  764. If empty, this represents a root revision; otherwise, the first item
  765. corresponds to :attr:`.Revision.down_revision`, and the rest are inferred
  766. from dependencies.
  767. """
  768. revision_map: RevisionMap
  769. """The revision map inside of which this operation occurs."""
  770. def __init__(
  771. self,
  772. revision_map: RevisionMap,
  773. is_upgrade: bool,
  774. is_stamp: bool,
  775. up_revisions: Union[str, Tuple[str, ...]],
  776. down_revisions: Union[str, Tuple[str, ...]],
  777. ) -> None:
  778. self.revision_map = revision_map
  779. self.is_upgrade = is_upgrade
  780. self.is_stamp = is_stamp
  781. self.up_revision_ids = util.to_tuple(up_revisions, default=())
  782. if self.up_revision_ids:
  783. self.up_revision_id = self.up_revision_ids[0]
  784. else:
  785. # this should never be the case with
  786. # "upgrade", "downgrade", or "stamp" as we are always
  787. # measuring movement in terms of at least one upgrade version
  788. self.up_revision_id = None
  789. self.down_revision_ids = util.to_tuple(down_revisions, default=())
  790. @property
  791. def is_migration(self) -> bool:
  792. """True/False: indicates whether this operation is a migration.
  793. At present this is true if and only the migration is not a stamp.
  794. If other operation types are added in the future, both this attribute
  795. and :attr:`~.MigrationInfo.is_stamp` will be false.
  796. """
  797. return not self.is_stamp
  798. @property
  799. def source_revision_ids(self) -> Tuple[str, ...]:
  800. """Active revisions before this migration step is applied."""
  801. return (
  802. self.down_revision_ids if self.is_upgrade else self.up_revision_ids
  803. )
  804. @property
  805. def destination_revision_ids(self) -> Tuple[str, ...]:
  806. """Active revisions after this migration step is applied."""
  807. return (
  808. self.up_revision_ids if self.is_upgrade else self.down_revision_ids
  809. )
  810. @property
  811. def up_revision(self) -> Optional[Revision]:
  812. """Get :attr:`~.MigrationInfo.up_revision_id` as
  813. a :class:`.Revision`.
  814. """
  815. return self.revision_map.get_revision(self.up_revision_id)
  816. @property
  817. def up_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
  818. """Get :attr:`~.MigrationInfo.up_revision_ids` as a
  819. :class:`.Revision`."""
  820. return self.revision_map.get_revisions(self.up_revision_ids)
  821. @property
  822. def down_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
  823. """Get :attr:`~.MigrationInfo.down_revision_ids` as a tuple of
  824. :class:`Revisions <.Revision>`."""
  825. return self.revision_map.get_revisions(self.down_revision_ids)
  826. @property
  827. def source_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
  828. """Get :attr:`~MigrationInfo.source_revision_ids` as a tuple of
  829. :class:`Revisions <.Revision>`."""
  830. return self.revision_map.get_revisions(self.source_revision_ids)
  831. @property
  832. def destination_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
  833. """Get :attr:`~MigrationInfo.destination_revision_ids` as a tuple of
  834. :class:`Revisions <.Revision>`."""
  835. return self.revision_map.get_revisions(self.destination_revision_ids)
  836. class MigrationStep:
  837. from_revisions_no_deps: Tuple[str, ...]
  838. to_revisions_no_deps: Tuple[str, ...]
  839. is_upgrade: bool
  840. migration_fn: Any
  841. if TYPE_CHECKING:
  842. @property
  843. def doc(self) -> Optional[str]:
  844. ...
  845. @property
  846. def name(self) -> str:
  847. return self.migration_fn.__name__
  848. @classmethod
  849. def upgrade_from_script(
  850. cls, revision_map: RevisionMap, script: Script
  851. ) -> RevisionStep:
  852. return RevisionStep(revision_map, script, True)
  853. @classmethod
  854. def downgrade_from_script(
  855. cls, revision_map: RevisionMap, script: Script
  856. ) -> RevisionStep:
  857. return RevisionStep(revision_map, script, False)
  858. @property
  859. def is_downgrade(self) -> bool:
  860. return not self.is_upgrade
  861. @property
  862. def short_log(self) -> str:
  863. return "%s %s -> %s" % (
  864. self.name,
  865. util.format_as_comma(self.from_revisions_no_deps),
  866. util.format_as_comma(self.to_revisions_no_deps),
  867. )
  868. def __str__(self):
  869. if self.doc:
  870. return "%s %s -> %s, %s" % (
  871. self.name,
  872. util.format_as_comma(self.from_revisions_no_deps),
  873. util.format_as_comma(self.to_revisions_no_deps),
  874. self.doc,
  875. )
  876. else:
  877. return self.short_log
  878. class RevisionStep(MigrationStep):
  879. def __init__(
  880. self, revision_map: RevisionMap, revision: Script, is_upgrade: bool
  881. ) -> None:
  882. self.revision_map = revision_map
  883. self.revision = revision
  884. self.is_upgrade = is_upgrade
  885. if is_upgrade:
  886. self.migration_fn = revision.module.upgrade
  887. else:
  888. self.migration_fn = revision.module.downgrade
  889. def __repr__(self):
  890. return "RevisionStep(%r, is_upgrade=%r)" % (
  891. self.revision.revision,
  892. self.is_upgrade,
  893. )
  894. def __eq__(self, other: object) -> bool:
  895. return (
  896. isinstance(other, RevisionStep)
  897. and other.revision == self.revision
  898. and self.is_upgrade == other.is_upgrade
  899. )
  900. @property
  901. def doc(self) -> Optional[str]:
  902. return self.revision.doc
  903. @property
  904. def from_revisions(self) -> Tuple[str, ...]:
  905. if self.is_upgrade:
  906. return self.revision._normalized_down_revisions
  907. else:
  908. return (self.revision.revision,)
  909. @property
  910. def from_revisions_no_deps( # type:ignore[override]
  911. self,
  912. ) -> Tuple[str, ...]:
  913. if self.is_upgrade:
  914. return self.revision._versioned_down_revisions
  915. else:
  916. return (self.revision.revision,)
  917. @property
  918. def to_revisions(self) -> Tuple[str, ...]:
  919. if self.is_upgrade:
  920. return (self.revision.revision,)
  921. else:
  922. return self.revision._normalized_down_revisions
  923. @property
  924. def to_revisions_no_deps( # type:ignore[override]
  925. self,
  926. ) -> Tuple[str, ...]:
  927. if self.is_upgrade:
  928. return (self.revision.revision,)
  929. else:
  930. return self.revision._versioned_down_revisions
  931. @property
  932. def _has_scalar_down_revision(self) -> bool:
  933. return len(self.revision._normalized_down_revisions) == 1
  934. def should_delete_branch(self, heads: Set[str]) -> bool:
  935. """A delete is when we are a. in a downgrade and b.
  936. we are going to the "base" or we are going to a version that
  937. is implied as a dependency on another version that is remaining.
  938. """
  939. if not self.is_downgrade:
  940. return False
  941. if self.revision.revision not in heads:
  942. return False
  943. downrevs = self.revision._normalized_down_revisions
  944. if not downrevs:
  945. # is a base
  946. return True
  947. else:
  948. # determine what the ultimate "to_revisions" for an
  949. # unmerge would be. If there are none, then we're a delete.
  950. to_revisions = self._unmerge_to_revisions(heads)
  951. return not to_revisions
  952. def merge_branch_idents(
  953. self, heads: Set[str]
  954. ) -> Tuple[List[str], str, str]:
  955. other_heads = set(heads).difference(self.from_revisions)
  956. if other_heads:
  957. ancestors = {
  958. r.revision
  959. for r in self.revision_map._get_ancestor_nodes(
  960. self.revision_map.get_revisions(other_heads), check=False
  961. )
  962. }
  963. from_revisions = list(
  964. set(self.from_revisions).difference(ancestors)
  965. )
  966. else:
  967. from_revisions = list(self.from_revisions)
  968. return (
  969. # delete revs, update from rev, update to rev
  970. list(from_revisions[0:-1]),
  971. from_revisions[-1],
  972. self.to_revisions[0],
  973. )
  974. def _unmerge_to_revisions(self, heads: Set[str]) -> Tuple[str, ...]:
  975. other_heads = set(heads).difference([self.revision.revision])
  976. if other_heads:
  977. ancestors = {
  978. r.revision
  979. for r in self.revision_map._get_ancestor_nodes(
  980. self.revision_map.get_revisions(other_heads), check=False
  981. )
  982. }
  983. return tuple(set(self.to_revisions).difference(ancestors))
  984. else:
  985. # for each revision we plan to return, compute its ancestors
  986. # (excluding self), and remove those from the final output since
  987. # they are already accounted for.
  988. ancestors = {
  989. r.revision
  990. for to_revision in self.to_revisions
  991. for r in self.revision_map._get_ancestor_nodes(
  992. self.revision_map.get_revisions(to_revision), check=False
  993. )
  994. if r.revision != to_revision
  995. }
  996. return tuple(set(self.to_revisions).difference(ancestors))
  997. def unmerge_branch_idents(
  998. self, heads: Set[str]
  999. ) -> Tuple[str, str, Tuple[str, ...]]:
  1000. to_revisions = self._unmerge_to_revisions(heads)
  1001. return (
  1002. # update from rev, update to rev, insert revs
  1003. self.from_revisions[0],
  1004. to_revisions[-1],
  1005. to_revisions[0:-1],
  1006. )
  1007. def should_create_branch(self, heads: Set[str]) -> bool:
  1008. if not self.is_upgrade:
  1009. return False
  1010. downrevs = self.revision._normalized_down_revisions
  1011. if not downrevs:
  1012. # is a base
  1013. return True
  1014. else:
  1015. # none of our downrevs are present, so...
  1016. # we have to insert our version. This is true whether
  1017. # or not there is only one downrev, or multiple (in the latter
  1018. # case, we're a merge point.)
  1019. if not heads.intersection(downrevs):
  1020. return True
  1021. else:
  1022. return False
  1023. def should_merge_branches(self, heads: Set[str]) -> bool:
  1024. if not self.is_upgrade:
  1025. return False
  1026. downrevs = self.revision._normalized_down_revisions
  1027. if len(downrevs) > 1 and len(heads.intersection(downrevs)) > 1:
  1028. return True
  1029. return False
  1030. def should_unmerge_branches(self, heads: Set[str]) -> bool:
  1031. if not self.is_downgrade:
  1032. return False
  1033. downrevs = self.revision._normalized_down_revisions
  1034. if self.revision.revision in heads and len(downrevs) > 1:
  1035. return True
  1036. return False
  1037. def update_version_num(self, heads: Set[str]) -> Tuple[str, str]:
  1038. if not self._has_scalar_down_revision:
  1039. downrev = heads.intersection(
  1040. self.revision._normalized_down_revisions
  1041. )
  1042. assert (
  1043. len(downrev) == 1
  1044. ), "Can't do an UPDATE because downrevision is ambiguous"
  1045. down_revision = list(downrev)[0]
  1046. else:
  1047. down_revision = self.revision._normalized_down_revisions[0]
  1048. if self.is_upgrade:
  1049. return down_revision, self.revision.revision
  1050. else:
  1051. return self.revision.revision, down_revision
  1052. @property
  1053. def delete_version_num(self) -> str:
  1054. return self.revision.revision
  1055. @property
  1056. def insert_version_num(self) -> str:
  1057. return self.revision.revision
  1058. @property
  1059. def info(self) -> MigrationInfo:
  1060. return MigrationInfo(
  1061. revision_map=self.revision_map,
  1062. up_revisions=self.revision.revision,
  1063. down_revisions=self.revision._normalized_down_revisions,
  1064. is_upgrade=self.is_upgrade,
  1065. is_stamp=False,
  1066. )
  1067. class StampStep(MigrationStep):
  1068. def __init__(
  1069. self,
  1070. from_: Optional[Union[str, Collection[str]]],
  1071. to_: Optional[Union[str, Collection[str]]],
  1072. is_upgrade: bool,
  1073. branch_move: bool,
  1074. revision_map: Optional[RevisionMap] = None,
  1075. ) -> None:
  1076. self.from_: Tuple[str, ...] = util.to_tuple(from_, default=())
  1077. self.to_: Tuple[str, ...] = util.to_tuple(to_, default=())
  1078. self.is_upgrade = is_upgrade
  1079. self.branch_move = branch_move
  1080. self.migration_fn = self.stamp_revision
  1081. self.revision_map = revision_map
  1082. doc: Optional[str] = None
  1083. def stamp_revision(self, **kw: Any) -> None:
  1084. return None
  1085. def __eq__(self, other):
  1086. return (
  1087. isinstance(other, StampStep)
  1088. and other.from_revisions == self.from_revisions
  1089. and other.to_revisions == self.to_revisions
  1090. and other.branch_move == self.branch_move
  1091. and self.is_upgrade == other.is_upgrade
  1092. )
  1093. @property
  1094. def from_revisions(self):
  1095. return self.from_
  1096. @property
  1097. def to_revisions(self) -> Tuple[str, ...]:
  1098. return self.to_
  1099. @property
  1100. def from_revisions_no_deps( # type:ignore[override]
  1101. self,
  1102. ) -> Tuple[str, ...]:
  1103. return self.from_
  1104. @property
  1105. def to_revisions_no_deps( # type:ignore[override]
  1106. self,
  1107. ) -> Tuple[str, ...]:
  1108. return self.to_
  1109. @property
  1110. def delete_version_num(self) -> str:
  1111. assert len(self.from_) == 1
  1112. return self.from_[0]
  1113. @property
  1114. def insert_version_num(self) -> str:
  1115. assert len(self.to_) == 1
  1116. return self.to_[0]
  1117. def update_version_num(self, heads: Set[str]) -> Tuple[str, str]:
  1118. assert len(self.from_) == 1
  1119. assert len(self.to_) == 1
  1120. return self.from_[0], self.to_[0]
  1121. def merge_branch_idents(
  1122. self, heads: Union[Set[str], List[str]]
  1123. ) -> Union[Tuple[List[Any], str, str], Tuple[List[str], str, str]]:
  1124. return (
  1125. # delete revs, update from rev, update to rev
  1126. list(self.from_[0:-1]),
  1127. self.from_[-1],
  1128. self.to_[0],
  1129. )
  1130. def unmerge_branch_idents(
  1131. self, heads: Set[str]
  1132. ) -> Tuple[str, str, List[str]]:
  1133. return (
  1134. # update from rev, update to rev, insert revs
  1135. self.from_[0],
  1136. self.to_[-1],
  1137. list(self.to_[0:-1]),
  1138. )
  1139. def should_delete_branch(self, heads: Set[str]) -> bool:
  1140. # TODO: we probably need to look for self.to_ inside of heads,
  1141. # in a similar manner as should_create_branch, however we have
  1142. # no tests for this yet (stamp downgrades w/ branches)
  1143. return self.is_downgrade and self.branch_move
  1144. def should_create_branch(self, heads: Set[str]) -> Union[Set[str], bool]:
  1145. return (
  1146. self.is_upgrade
  1147. and (self.branch_move or set(self.from_).difference(heads))
  1148. and set(self.to_).difference(heads)
  1149. )
  1150. def should_merge_branches(self, heads: Set[str]) -> bool:
  1151. return len(self.from_) > 1
  1152. def should_unmerge_branches(self, heads: Set[str]) -> bool:
  1153. return len(self.to_) > 1
  1154. @property
  1155. def info(self) -> MigrationInfo:
  1156. up, down = (
  1157. (self.to_, self.from_)
  1158. if self.is_upgrade
  1159. else (self.from_, self.to_)
  1160. )
  1161. assert self.revision_map is not None
  1162. return MigrationInfo(
  1163. revision_map=self.revision_map,
  1164. up_revisions=up,
  1165. down_revisions=down,
  1166. is_upgrade=self.is_upgrade,
  1167. is_stamp=True,
  1168. )