migration.py 49 KB

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