Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import os 

2 

3from . import autogenerate as autogen 

4from . import util 

5from .runtime.environment import EnvironmentContext 

6from .script import ScriptDirectory 

7 

8 

9def list_templates(config): 

10 """List available templates. 

11 

12 :param config: a :class:`.Config` object. 

13 

14 """ 

15 

16 config.print_stdout("Available templates:\n") 

17 for tempname in os.listdir(config.get_template_directory()): 

18 with open( 

19 os.path.join(config.get_template_directory(), tempname, "README") 

20 ) as readme: 

21 synopsis = next(readme) 

22 config.print_stdout("%s - %s", tempname, synopsis) 

23 

24 config.print_stdout("\nTemplates are used via the 'init' command, e.g.:") 

25 config.print_stdout("\n alembic init --template generic ./scripts") 

26 

27 

28def init(config, directory, template="generic", package=False): 

29 """Initialize a new scripts directory. 

30 

31 :param config: a :class:`.Config` object. 

32 

33 :param directory: string path of the target directory 

34 

35 :param template: string name of the migration environment template to 

36 use. 

37 

38 :param package: when True, write ``__init__.py`` files into the 

39 environment location as well as the versions/ location. 

40 

41 .. versionadded:: 1.2 

42 

43 

44 """ 

45 

46 if os.access(directory, os.F_OK) and os.listdir(directory): 

47 raise util.CommandError( 

48 "Directory %s already exists and is not empty" % directory 

49 ) 

50 

51 template_dir = os.path.join(config.get_template_directory(), template) 

52 if not os.access(template_dir, os.F_OK): 

53 raise util.CommandError("No such template %r" % template) 

54 

55 if not os.access(directory, os.F_OK): 

56 util.status( 

57 "Creating directory %s" % os.path.abspath(directory), 

58 os.makedirs, 

59 directory, 

60 ) 

61 

62 versions = os.path.join(directory, "versions") 

63 util.status( 

64 "Creating directory %s" % os.path.abspath(versions), 

65 os.makedirs, 

66 versions, 

67 ) 

68 

69 script = ScriptDirectory(directory) 

70 

71 for file_ in os.listdir(template_dir): 

72 file_path = os.path.join(template_dir, file_) 

73 if file_ == "alembic.ini.mako": 

74 config_file = os.path.abspath(config.config_file_name) 

75 if os.access(config_file, os.F_OK): 

76 util.msg("File %s already exists, skipping" % config_file) 

77 else: 

78 script._generate_template( 

79 file_path, config_file, script_location=directory 

80 ) 

81 elif os.path.isfile(file_path): 

82 output_file = os.path.join(directory, file_) 

83 script._copy_file(file_path, output_file) 

84 

85 if package: 

86 for path in [ 

87 os.path.join(os.path.abspath(directory), "__init__.py"), 

88 os.path.join(os.path.abspath(versions), "__init__.py"), 

89 ]: 

90 file_ = util.status("Adding %s" % path, open, path, "w") 

91 file_.close() 

92 

93 util.msg( 

94 "Please edit configuration/connection/logging " 

95 "settings in %r before proceeding." % config_file 

96 ) 

97 

98 

99def revision( 

100 config, 

101 message=None, 

102 autogenerate=False, 

103 sql=False, 

104 head="head", 

105 splice=False, 

106 branch_label=None, 

107 version_path=None, 

108 rev_id=None, 

109 depends_on=None, 

110 process_revision_directives=None, 

111): 

112 """Create a new revision file. 

113 

114 :param config: a :class:`.Config` object. 

115 

116 :param message: string message to apply to the revision; this is the 

117 ``-m`` option to ``alembic revision``. 

118 

119 :param autogenerate: whether or not to autogenerate the script from 

120 the database; this is the ``--autogenerate`` option to 

121 ``alembic revision``. 

122 

123 :param sql: whether to dump the script out as a SQL string; when specified, 

124 the script is dumped to stdout. This is the ``--sql`` option to 

125 ``alembic revision``. 

126 

127 :param head: head revision to build the new revision upon as a parent; 

128 this is the ``--head`` option to ``alembic revision``. 

129 

130 :param splice: whether or not the new revision should be made into a 

131 new head of its own; is required when the given ``head`` is not itself 

132 a head. This is the ``--splice`` option to ``alembic revision``. 

133 

134 :param branch_label: string label to apply to the branch; this is the 

135 ``--branch-label`` option to ``alembic revision``. 

136 

137 :param version_path: string symbol identifying a specific version path 

138 from the configuration; this is the ``--version-path`` option to 

139 ``alembic revision``. 

140 

141 :param rev_id: optional revision identifier to use instead of having 

142 one generated; this is the ``--rev-id`` option to ``alembic revision``. 

143 

144 :param depends_on: optional list of "depends on" identifiers; this is the 

145 ``--depends-on`` option to ``alembic revision``. 

146 

147 :param process_revision_directives: this is a callable that takes the 

148 same form as the callable described at 

149 :paramref:`.EnvironmentContext.configure.process_revision_directives`; 

150 will be applied to the structure generated by the revision process 

151 where it can be altered programmatically. Note that unlike all 

152 the other parameters, this option is only available via programmatic 

153 use of :func:`.command.revision` 

154 

155 .. versionadded:: 0.9.0 

156 

157 """ 

158 

159 script_directory = ScriptDirectory.from_config(config) 

160 

161 command_args = dict( 

162 message=message, 

163 autogenerate=autogenerate, 

164 sql=sql, 

165 head=head, 

166 splice=splice, 

167 branch_label=branch_label, 

168 version_path=version_path, 

169 rev_id=rev_id, 

170 depends_on=depends_on, 

171 ) 

172 revision_context = autogen.RevisionContext( 

173 config, 

174 script_directory, 

175 command_args, 

176 process_revision_directives=process_revision_directives, 

177 ) 

178 

179 environment = util.asbool(config.get_main_option("revision_environment")) 

180 

181 if autogenerate: 

182 environment = True 

183 

184 if sql: 

185 raise util.CommandError( 

186 "Using --sql with --autogenerate does not make any sense" 

187 ) 

188 

189 def retrieve_migrations(rev, context): 

190 revision_context.run_autogenerate(rev, context) 

191 return [] 

192 

193 elif environment: 

194 

195 def retrieve_migrations(rev, context): 

196 revision_context.run_no_autogenerate(rev, context) 

197 return [] 

198 

199 elif sql: 

200 raise util.CommandError( 

201 "Using --sql with the revision command when " 

202 "revision_environment is not configured does not make any sense" 

203 ) 

204 

205 if environment: 

206 with EnvironmentContext( 

207 config, 

208 script_directory, 

209 fn=retrieve_migrations, 

210 as_sql=sql, 

211 template_args=revision_context.template_args, 

212 revision_context=revision_context, 

213 ): 

214 script_directory.run_env() 

215 

216 # the revision_context now has MigrationScript structure(s) present. 

217 # these could theoretically be further processed / rewritten *here*, 

218 # in addition to the hooks present within each run_migrations() call, 

219 # or at the end of env.py run_migrations_online(). 

220 

221 scripts = [script for script in revision_context.generate_scripts()] 

222 if len(scripts) == 1: 

223 return scripts[0] 

224 else: 

225 return scripts 

226 

227 

228def merge(config, revisions, message=None, branch_label=None, rev_id=None): 

229 """Merge two revisions together. Creates a new migration file. 

230 

231 .. versionadded:: 0.7.0 

232 

233 :param config: a :class:`.Config` instance 

234 

235 :param message: string message to apply to the revision 

236 

237 :param branch_label: string label name to apply to the new revision 

238 

239 :param rev_id: hardcoded revision identifier instead of generating a new 

240 one. 

241 

242 .. seealso:: 

243 

244 :ref:`branches` 

245 

246 """ 

247 

248 script = ScriptDirectory.from_config(config) 

249 template_args = { 

250 "config": config # Let templates use config for 

251 # e.g. multiple databases 

252 } 

253 return script.generate_revision( 

254 rev_id or util.rev_id(), 

255 message, 

256 refresh=True, 

257 head=revisions, 

258 branch_labels=branch_label, 

259 **template_args 

260 ) 

261 

262 

263def upgrade(config, revision, sql=False, tag=None): 

264 """Upgrade to a later version. 

265 

266 :param config: a :class:`.Config` instance. 

267 

268 :param revision: string revision target or range for --sql mode 

269 

270 :param sql: if True, use ``--sql`` mode 

271 

272 :param tag: an arbitrary "tag" that can be intercepted by custom 

273 ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` 

274 method. 

275 

276 """ 

277 

278 script = ScriptDirectory.from_config(config) 

279 

280 starting_rev = None 

281 if ":" in revision: 

282 if not sql: 

283 raise util.CommandError("Range revision not allowed") 

284 starting_rev, revision = revision.split(":", 2) 

285 

286 def upgrade(rev, context): 

287 return script._upgrade_revs(revision, rev) 

288 

289 with EnvironmentContext( 

290 config, 

291 script, 

292 fn=upgrade, 

293 as_sql=sql, 

294 starting_rev=starting_rev, 

295 destination_rev=revision, 

296 tag=tag, 

297 ): 

298 script.run_env() 

299 

300 

301def downgrade(config, revision, sql=False, tag=None): 

302 """Revert to a previous version. 

303 

304 :param config: a :class:`.Config` instance. 

305 

306 :param revision: string revision target or range for --sql mode 

307 

308 :param sql: if True, use ``--sql`` mode 

309 

310 :param tag: an arbitrary "tag" that can be intercepted by custom 

311 ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` 

312 method. 

313 

314 """ 

315 

316 script = ScriptDirectory.from_config(config) 

317 starting_rev = None 

318 if ":" in revision: 

319 if not sql: 

320 raise util.CommandError("Range revision not allowed") 

321 starting_rev, revision = revision.split(":", 2) 

322 elif sql: 

323 raise util.CommandError( 

324 "downgrade with --sql requires <fromrev>:<torev>" 

325 ) 

326 

327 def downgrade(rev, context): 

328 return script._downgrade_revs(revision, rev) 

329 

330 with EnvironmentContext( 

331 config, 

332 script, 

333 fn=downgrade, 

334 as_sql=sql, 

335 starting_rev=starting_rev, 

336 destination_rev=revision, 

337 tag=tag, 

338 ): 

339 script.run_env() 

340 

341 

342def show(config, rev): 

343 """Show the revision(s) denoted by the given symbol. 

344 

345 :param config: a :class:`.Config` instance. 

346 

347 :param revision: string revision target 

348 

349 """ 

350 

351 script = ScriptDirectory.from_config(config) 

352 

353 if rev == "current": 

354 

355 def show_current(rev, context): 

356 for sc in script.get_revisions(rev): 

357 config.print_stdout(sc.log_entry) 

358 return [] 

359 

360 with EnvironmentContext(config, script, fn=show_current): 

361 script.run_env() 

362 else: 

363 for sc in script.get_revisions(rev): 

364 config.print_stdout(sc.log_entry) 

365 

366 

367def history(config, rev_range=None, verbose=False, indicate_current=False): 

368 """List changeset scripts in chronological order. 

369 

370 :param config: a :class:`.Config` instance. 

371 

372 :param rev_range: string revision range 

373 

374 :param verbose: output in verbose mode. 

375 

376 :param indicate_current: indicate current revision. 

377 

378 ..versionadded:: 0.9.9 

379 

380 """ 

381 

382 script = ScriptDirectory.from_config(config) 

383 if rev_range is not None: 

384 if ":" not in rev_range: 

385 raise util.CommandError( 

386 "History range requires [start]:[end], " "[start]:, or :[end]" 

387 ) 

388 base, head = rev_range.strip().split(":") 

389 else: 

390 base = head = None 

391 

392 environment = ( 

393 util.asbool(config.get_main_option("revision_environment")) 

394 or indicate_current 

395 ) 

396 

397 def _display_history(config, script, base, head, currents=()): 

398 for sc in script.walk_revisions( 

399 base=base or "base", head=head or "heads" 

400 ): 

401 

402 if indicate_current: 

403 sc._db_current_indicator = sc.revision in currents 

404 

405 config.print_stdout( 

406 sc.cmd_format( 

407 verbose=verbose, 

408 include_branches=True, 

409 include_doc=True, 

410 include_parents=True, 

411 ) 

412 ) 

413 

414 def _display_history_w_current(config, script, base, head): 

415 def _display_current_history(rev, context): 

416 if head == "current": 

417 _display_history(config, script, base, rev, rev) 

418 elif base == "current": 

419 _display_history(config, script, rev, head, rev) 

420 else: 

421 _display_history(config, script, base, head, rev) 

422 return [] 

423 

424 with EnvironmentContext(config, script, fn=_display_current_history): 

425 script.run_env() 

426 

427 if base == "current" or head == "current" or environment: 

428 _display_history_w_current(config, script, base, head) 

429 else: 

430 _display_history(config, script, base, head) 

431 

432 

433def heads(config, verbose=False, resolve_dependencies=False): 

434 """Show current available heads in the script directory. 

435 

436 :param config: a :class:`.Config` instance. 

437 

438 :param verbose: output in verbose mode. 

439 

440 :param resolve_dependencies: treat dependency version as down revisions. 

441 

442 """ 

443 

444 script = ScriptDirectory.from_config(config) 

445 if resolve_dependencies: 

446 heads = script.get_revisions("heads") 

447 else: 

448 heads = script.get_revisions(script.get_heads()) 

449 

450 for rev in heads: 

451 config.print_stdout( 

452 rev.cmd_format( 

453 verbose, include_branches=True, tree_indicators=False 

454 ) 

455 ) 

456 

457 

458def branches(config, verbose=False): 

459 """Show current branch points. 

460 

461 :param config: a :class:`.Config` instance. 

462 

463 :param verbose: output in verbose mode. 

464 

465 """ 

466 script = ScriptDirectory.from_config(config) 

467 for sc in script.walk_revisions(): 

468 if sc.is_branch_point: 

469 config.print_stdout( 

470 "%s\n%s\n", 

471 sc.cmd_format(verbose, include_branches=True), 

472 "\n".join( 

473 "%s -> %s" 

474 % ( 

475 " " * len(str(sc.revision)), 

476 rev_obj.cmd_format( 

477 False, include_branches=True, include_doc=verbose 

478 ), 

479 ) 

480 for rev_obj in ( 

481 script.get_revision(rev) for rev in sc.nextrev 

482 ) 

483 ), 

484 ) 

485 

486 

487def current(config, verbose=False, head_only=False): 

488 """Display the current revision for a database. 

489 

490 :param config: a :class:`.Config` instance. 

491 

492 :param verbose: output in verbose mode. 

493 

494 :param head_only: deprecated; use ``verbose`` for additional output. 

495 

496 """ 

497 

498 script = ScriptDirectory.from_config(config) 

499 

500 if head_only: 

501 util.warn("--head-only is deprecated", stacklevel=3) 

502 

503 def display_version(rev, context): 

504 if verbose: 

505 config.print_stdout( 

506 "Current revision(s) for %s:", 

507 util.obfuscate_url_pw(context.connection.engine.url), 

508 ) 

509 for rev in script.get_all_current(rev): 

510 config.print_stdout(rev.cmd_format(verbose)) 

511 

512 return [] 

513 

514 with EnvironmentContext(config, script, fn=display_version): 

515 script.run_env() 

516 

517 

518def stamp(config, revision, sql=False, tag=None, purge=False): 

519 """'stamp' the revision table with the given revision; don't 

520 run any migrations. 

521 

522 :param config: a :class:`.Config` instance. 

523 

524 :param revision: target revision or list of revisions. May be a list 

525 to indicate stamping of multiple branch heads. 

526 

527 .. note:: this parameter is called "revisions" in the command line 

528 interface. 

529 

530 .. versionchanged:: 1.2 The revision may be a single revision or 

531 list of revisions when stamping multiple branch heads. 

532 

533 :param sql: use ``--sql`` mode 

534 

535 :param tag: an arbitrary "tag" that can be intercepted by custom 

536 ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument` 

537 method. 

538 

539 :param purge: delete all entries in the version table before stamping. 

540 

541 .. versionadded:: 1.2 

542 

543 """ 

544 

545 script = ScriptDirectory.from_config(config) 

546 

547 if sql: 

548 destination_revs = [] 

549 starting_rev = None 

550 for _revision in util.to_list(revision): 

551 if ":" in _revision: 

552 srev, _revision = _revision.split(":", 2) 

553 

554 if starting_rev != srev: 

555 if starting_rev is None: 

556 starting_rev = srev 

557 else: 

558 raise util.CommandError( 

559 "Stamp operation with --sql only supports a " 

560 "single starting revision at a time" 

561 ) 

562 destination_revs.append(_revision) 

563 else: 

564 destination_revs = util.to_list(revision) 

565 

566 def do_stamp(rev, context): 

567 return script._stamp_revs(util.to_tuple(destination_revs), rev) 

568 

569 with EnvironmentContext( 

570 config, 

571 script, 

572 fn=do_stamp, 

573 as_sql=sql, 

574 starting_rev=starting_rev if sql else None, 

575 destination_rev=util.to_tuple(destination_revs), 

576 tag=tag, 

577 purge=purge, 

578 ): 

579 script.run_env() 

580 

581 

582def edit(config, rev): 

583 """Edit revision script(s) using $EDITOR. 

584 

585 :param config: a :class:`.Config` instance. 

586 

587 :param rev: target revision. 

588 

589 """ 

590 

591 script = ScriptDirectory.from_config(config) 

592 

593 if rev == "current": 

594 

595 def edit_current(rev, context): 

596 if not rev: 

597 raise util.CommandError("No current revisions") 

598 for sc in script.get_revisions(rev): 

599 util.edit(sc.path) 

600 return [] 

601 

602 with EnvironmentContext(config, script, fn=edit_current): 

603 script.run_env() 

604 else: 

605 revs = script.get_revisions(rev) 

606 if not revs: 

607 raise util.CommandError( 

608 "No revision files indicated by symbol '%s'" % rev 

609 ) 

610 for sc in revs: 

611 util.edit(sc.path)