command.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. import os
  2. from .script import ScriptDirectory
  3. from .runtime.environment import EnvironmentContext
  4. from . import util
  5. from . import autogenerate as autogen
  6. def list_templates(config):
  7. """List available templates
  8. :param config: a :class:`.Config` object.
  9. """
  10. config.print_stdout("Available templates:\n")
  11. for tempname in os.listdir(config.get_template_directory()):
  12. with open(os.path.join(
  13. config.get_template_directory(),
  14. tempname,
  15. 'README')) as readme:
  16. synopsis = next(readme)
  17. config.print_stdout("%s - %s", tempname, synopsis)
  18. config.print_stdout("\nTemplates are used via the 'init' command, e.g.:")
  19. config.print_stdout("\n alembic init --template generic ./scripts")
  20. def init(config, directory, template='generic'):
  21. """Initialize a new scripts directory.
  22. :param config: a :class:`.Config` object.
  23. :param directory: string path of the target directory
  24. :param template: string name of the migration environment template to
  25. use.
  26. """
  27. if os.access(directory, os.F_OK):
  28. raise util.CommandError("Directory %s already exists" % directory)
  29. template_dir = os.path.join(config.get_template_directory(),
  30. template)
  31. if not os.access(template_dir, os.F_OK):
  32. raise util.CommandError("No such template %r" % template)
  33. util.status("Creating directory %s" % os.path.abspath(directory),
  34. os.makedirs, directory)
  35. versions = os.path.join(directory, 'versions')
  36. util.status("Creating directory %s" % os.path.abspath(versions),
  37. os.makedirs, versions)
  38. script = ScriptDirectory(directory)
  39. for file_ in os.listdir(template_dir):
  40. file_path = os.path.join(template_dir, file_)
  41. if file_ == 'alembic.ini.mako':
  42. config_file = os.path.abspath(config.config_file_name)
  43. if os.access(config_file, os.F_OK):
  44. util.msg("File %s already exists, skipping" % config_file)
  45. else:
  46. script._generate_template(
  47. file_path,
  48. config_file,
  49. script_location=directory
  50. )
  51. elif os.path.isfile(file_path):
  52. output_file = os.path.join(directory, file_)
  53. script._copy_file(
  54. file_path,
  55. output_file
  56. )
  57. util.msg("Please edit configuration/connection/logging "
  58. "settings in %r before proceeding." % config_file)
  59. def revision(
  60. config, message=None, autogenerate=False, sql=False,
  61. head="head", splice=False, branch_label=None,
  62. version_path=None, rev_id=None, depends_on=None,
  63. process_revision_directives=None):
  64. """Create a new revision file.
  65. :param config: a :class:`.Config` object.
  66. :param message: string message to apply to the revision; this is the
  67. ``-m`` option to ``alembic revision``.
  68. :param autogenerate: whether or not to autogenerate the script from
  69. the database; this is the ``--autogenerate`` option to ``alembic revision``.
  70. :param sql: whether to dump the script out as a SQL string; when specified,
  71. the script is dumped to stdout. This is the ``--sql`` option to
  72. ``alembic revision``.
  73. :param head: head revision to build the new revision upon as a parent;
  74. this is the ``--head`` option to ``alembic revision``.
  75. :param splice: whether or not the new revision should be made into a
  76. new head of its own; is required when the given ``head`` is not itself
  77. a head. This is the ``--splice`` option to ``alembic revision``.
  78. :param branch_label: string label to apply to the branch; this is the
  79. ``--branch-label`` option to ``alembic revision``.
  80. :param version_path: string symbol identifying a specific version path
  81. from the configuration; this is the ``--version-path`` option to
  82. ``alembic revision``.
  83. :param rev_id: optional revision identifier to use instead of having
  84. one generated; this is the ``--rev-id`` option to ``alembic revision``.
  85. :param depends_on: optional list of "depends on" identifiers; this is the
  86. ``--depends-on`` option to ``alembic revision``.
  87. :param process_revision_directives: this is a callable that takes the
  88. same form as the callable described at
  89. :paramref:`.EnvironmentContext.configure.process_revision_directives`;
  90. will be applied to the structure generated by the revision process
  91. where it can be altered programmatically. Note that unlike all
  92. the other parameters, this option is only available via programmatic
  93. use of :func:`.command.revision`
  94. .. versionadded:: 0.9.0
  95. """
  96. script_directory = ScriptDirectory.from_config(config)
  97. command_args = dict(
  98. message=message,
  99. autogenerate=autogenerate,
  100. sql=sql, head=head, splice=splice, branch_label=branch_label,
  101. version_path=version_path, rev_id=rev_id, depends_on=depends_on
  102. )
  103. revision_context = autogen.RevisionContext(
  104. config, script_directory, command_args,
  105. process_revision_directives=process_revision_directives)
  106. environment = util.asbool(
  107. config.get_main_option("revision_environment")
  108. )
  109. if autogenerate:
  110. environment = True
  111. if sql:
  112. raise util.CommandError(
  113. "Using --sql with --autogenerate does not make any sense")
  114. def retrieve_migrations(rev, context):
  115. revision_context.run_autogenerate(rev, context)
  116. return []
  117. elif environment:
  118. def retrieve_migrations(rev, context):
  119. revision_context.run_no_autogenerate(rev, context)
  120. return []
  121. elif sql:
  122. raise util.CommandError(
  123. "Using --sql with the revision command when "
  124. "revision_environment is not configured does not make any sense")
  125. if environment:
  126. with EnvironmentContext(
  127. config,
  128. script_directory,
  129. fn=retrieve_migrations,
  130. as_sql=sql,
  131. template_args=revision_context.template_args,
  132. revision_context=revision_context
  133. ):
  134. script_directory.run_env()
  135. scripts = [
  136. script for script in
  137. revision_context.generate_scripts()
  138. ]
  139. if len(scripts) == 1:
  140. return scripts[0]
  141. else:
  142. return scripts
  143. def merge(config, revisions, message=None, branch_label=None, rev_id=None):
  144. """Merge two revisions together. Creates a new migration file.
  145. .. versionadded:: 0.7.0
  146. :param config: a :class:`.Config` instance
  147. :param message: string message to apply to the revision
  148. :param branch_label: string label name to apply to the new revision
  149. :param rev_id: hardcoded revision identifier instead of generating a new
  150. one.
  151. .. seealso::
  152. :ref:`branches`
  153. """
  154. script = ScriptDirectory.from_config(config)
  155. template_args = {
  156. 'config': config # Let templates use config for
  157. # e.g. multiple databases
  158. }
  159. return script.generate_revision(
  160. rev_id or util.rev_id(), message, refresh=True,
  161. head=revisions, branch_labels=branch_label,
  162. **template_args)
  163. def upgrade(config, revision, sql=False, tag=None):
  164. """Upgrade to a later version.
  165. :param config: a :class:`.Config` instance.
  166. :param revision: string revision target or range for --sql mode
  167. :param sql: if True, use ``--sql`` mode
  168. :param tag: an arbitrary "tag" that can be intercepted by custom
  169. ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument`
  170. method.
  171. """
  172. script = ScriptDirectory.from_config(config)
  173. starting_rev = None
  174. if ":" in revision:
  175. if not sql:
  176. raise util.CommandError("Range revision not allowed")
  177. starting_rev, revision = revision.split(':', 2)
  178. def upgrade(rev, context):
  179. return script._upgrade_revs(revision, rev)
  180. with EnvironmentContext(
  181. config,
  182. script,
  183. fn=upgrade,
  184. as_sql=sql,
  185. starting_rev=starting_rev,
  186. destination_rev=revision,
  187. tag=tag
  188. ):
  189. script.run_env()
  190. def downgrade(config, revision, sql=False, tag=None):
  191. """Revert to a previous version.
  192. :param config: a :class:`.Config` instance.
  193. :param revision: string revision target or range for --sql mode
  194. :param sql: if True, use ``--sql`` mode
  195. :param tag: an arbitrary "tag" that can be intercepted by custom
  196. ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument`
  197. method.
  198. """
  199. script = ScriptDirectory.from_config(config)
  200. starting_rev = None
  201. if ":" in revision:
  202. if not sql:
  203. raise util.CommandError("Range revision not allowed")
  204. starting_rev, revision = revision.split(':', 2)
  205. elif sql:
  206. raise util.CommandError(
  207. "downgrade with --sql requires <fromrev>:<torev>")
  208. def downgrade(rev, context):
  209. return script._downgrade_revs(revision, rev)
  210. with EnvironmentContext(
  211. config,
  212. script,
  213. fn=downgrade,
  214. as_sql=sql,
  215. starting_rev=starting_rev,
  216. destination_rev=revision,
  217. tag=tag
  218. ):
  219. script.run_env()
  220. def show(config, rev):
  221. """Show the revision(s) denoted by the given symbol.
  222. :param config: a :class:`.Config` instance.
  223. :param revision: string revision target
  224. """
  225. script = ScriptDirectory.from_config(config)
  226. if rev == "current":
  227. def show_current(rev, context):
  228. for sc in script.get_revisions(rev):
  229. config.print_stdout(sc.log_entry)
  230. return []
  231. with EnvironmentContext(
  232. config,
  233. script,
  234. fn=show_current
  235. ):
  236. script.run_env()
  237. else:
  238. for sc in script.get_revisions(rev):
  239. config.print_stdout(sc.log_entry)
  240. def history(config, rev_range=None, verbose=False):
  241. """List changeset scripts in chronological order.
  242. :param config: a :class:`.Config` instance.
  243. :param rev_range: string revision range
  244. :param verbose: output in verbose mode.
  245. """
  246. script = ScriptDirectory.from_config(config)
  247. if rev_range is not None:
  248. if ":" not in rev_range:
  249. raise util.CommandError(
  250. "History range requires [start]:[end], "
  251. "[start]:, or :[end]")
  252. base, head = rev_range.strip().split(":")
  253. else:
  254. base = head = None
  255. def _display_history(config, script, base, head):
  256. for sc in script.walk_revisions(
  257. base=base or "base",
  258. head=head or "heads"):
  259. config.print_stdout(
  260. sc.cmd_format(
  261. verbose=verbose, include_branches=True,
  262. include_doc=True, include_parents=True))
  263. def _display_history_w_current(config, script, base=None, head=None):
  264. def _display_current_history(rev, context):
  265. if head is None:
  266. _display_history(config, script, base, rev)
  267. elif base is None:
  268. _display_history(config, script, rev, head)
  269. return []
  270. with EnvironmentContext(
  271. config,
  272. script,
  273. fn=_display_current_history
  274. ):
  275. script.run_env()
  276. if base == "current":
  277. _display_history_w_current(config, script, head=head)
  278. elif head == "current":
  279. _display_history_w_current(config, script, base=base)
  280. else:
  281. _display_history(config, script, base, head)
  282. def heads(config, verbose=False, resolve_dependencies=False):
  283. """Show current available heads in the script directory
  284. :param config: a :class:`.Config` instance.
  285. :param verbose: output in verbose mode.
  286. :param resolve_dependencies: treat dependency version as down revisions.
  287. """
  288. script = ScriptDirectory.from_config(config)
  289. if resolve_dependencies:
  290. heads = script.get_revisions("heads")
  291. else:
  292. heads = script.get_revisions(script.get_heads())
  293. for rev in heads:
  294. config.print_stdout(
  295. rev.cmd_format(
  296. verbose, include_branches=True, tree_indicators=False))
  297. def branches(config, verbose=False):
  298. """Show current branch points.
  299. :param config: a :class:`.Config` instance.
  300. :param verbose: output in verbose mode.
  301. """
  302. script = ScriptDirectory.from_config(config)
  303. for sc in script.walk_revisions():
  304. if sc.is_branch_point:
  305. config.print_stdout(
  306. "%s\n%s\n",
  307. sc.cmd_format(verbose, include_branches=True),
  308. "\n".join(
  309. "%s -> %s" % (
  310. " " * len(str(sc.revision)),
  311. rev_obj.cmd_format(
  312. False, include_branches=True, include_doc=verbose)
  313. ) for rev_obj in
  314. (script.get_revision(rev) for rev in sc.nextrev)
  315. )
  316. )
  317. def current(config, verbose=False, head_only=False):
  318. """Display the current revision for a database.
  319. :param config: a :class:`.Config` instance.
  320. :param verbose: output in verbose mode.
  321. :param head_only: deprecated; use ``verbose`` for additional output.
  322. """
  323. script = ScriptDirectory.from_config(config)
  324. if head_only:
  325. util.warn("--head-only is deprecated")
  326. def display_version(rev, context):
  327. if verbose:
  328. config.print_stdout(
  329. "Current revision(s) for %s:",
  330. util.obfuscate_url_pw(context.connection.engine.url)
  331. )
  332. for rev in script.get_all_current(rev):
  333. config.print_stdout(rev.cmd_format(verbose))
  334. return []
  335. with EnvironmentContext(
  336. config,
  337. script,
  338. fn=display_version
  339. ):
  340. script.run_env()
  341. def stamp(config, revision, sql=False, tag=None):
  342. """'stamp' the revision table with the given revision; don't
  343. run any migrations.
  344. :param config: a :class:`.Config` instance.
  345. :param revision: target revision.
  346. :param sql: use ``--sql`` mode
  347. :param tag: an arbitrary "tag" that can be intercepted by custom
  348. ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument`
  349. method.
  350. """
  351. script = ScriptDirectory.from_config(config)
  352. starting_rev = None
  353. if ":" in revision:
  354. if not sql:
  355. raise util.CommandError("Range revision not allowed")
  356. starting_rev, revision = revision.split(':', 2)
  357. def do_stamp(rev, context):
  358. return script._stamp_revs(revision, rev)
  359. with EnvironmentContext(
  360. config,
  361. script,
  362. fn=do_stamp,
  363. as_sql=sql,
  364. destination_rev=revision,
  365. starting_rev=starting_rev,
  366. tag=tag
  367. ):
  368. script.run_env()
  369. def edit(config, rev):
  370. """Edit revision script(s) using $EDITOR.
  371. :param config: a :class:`.Config` instance.
  372. :param rev: target revision.
  373. """
  374. script = ScriptDirectory.from_config(config)
  375. if rev == "current":
  376. def edit_current(rev, context):
  377. if not rev:
  378. raise util.CommandError("No current revisions")
  379. for sc in script.get_revisions(rev):
  380. util.edit(sc.path)
  381. return []
  382. with EnvironmentContext(
  383. config,
  384. script,
  385. fn=edit_current
  386. ):
  387. script.run_env()
  388. else:
  389. revs = script.get_revisions(rev)
  390. if not revs:
  391. raise util.CommandError(
  392. "No revision files indicated by symbol '%s'" % rev)
  393. for sc in revs:
  394. util.edit(sc.path)