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

1from argparse import ArgumentParser 

2import inspect 

3import os 

4import sys 

5 

6from . import command 

7from . import util 

8from .util import compat 

9from .util.compat import SafeConfigParser 

10 

11 

12class Config(object): 

13 

14 r"""Represent an Alembic configuration. 

15 

16 Within an ``env.py`` script, this is available 

17 via the :attr:`.EnvironmentContext.config` attribute, 

18 which in turn is available at ``alembic.context``:: 

19 

20 from alembic import context 

21 

22 some_param = context.config.get_main_option("my option") 

23 

24 When invoking Alembic programatically, a new 

25 :class:`.Config` can be created by passing 

26 the name of an .ini file to the constructor:: 

27 

28 from alembic.config import Config 

29 alembic_cfg = Config("/path/to/yourapp/alembic.ini") 

30 

31 With a :class:`.Config` object, you can then 

32 run Alembic commands programmatically using the directives 

33 in :mod:`alembic.command`. 

34 

35 The :class:`.Config` object can also be constructed without 

36 a filename. Values can be set programmatically, and 

37 new sections will be created as needed:: 

38 

39 from alembic.config import Config 

40 alembic_cfg = Config() 

41 alembic_cfg.set_main_option("script_location", "myapp:migrations") 

42 alembic_cfg.set_main_option("sqlalchemy.url", "postgresql://foo/bar") 

43 alembic_cfg.set_section_option("mysection", "foo", "bar") 

44 

45 .. warning:: 

46 

47 When using programmatic configuration, make sure the 

48 ``env.py`` file in use is compatible with the target configuration; 

49 including that the call to Python ``logging.fileConfig()`` is 

50 omitted if the programmatic configuration doesn't actually include 

51 logging directives. 

52 

53 For passing non-string values to environments, such as connections and 

54 engines, use the :attr:`.Config.attributes` dictionary:: 

55 

56 with engine.begin() as connection: 

57 alembic_cfg.attributes['connection'] = connection 

58 command.upgrade(alembic_cfg, "head") 

59 

60 :param file\_: name of the .ini file to open. 

61 :param ini_section: name of the main Alembic section within the 

62 .ini file 

63 :param output_buffer: optional file-like input buffer which 

64 will be passed to the :class:`.MigrationContext` - used to redirect 

65 the output of "offline generation" when using Alembic programmatically. 

66 :param stdout: buffer where the "print" output of commands will be sent. 

67 Defaults to ``sys.stdout``. 

68 

69 .. versionadded:: 0.4 

70 

71 :param config_args: A dictionary of keys and values that will be used 

72 for substitution in the alembic config file. The dictionary as given 

73 is **copied** to a new one, stored locally as the attribute 

74 ``.config_args``. When the :attr:`.Config.file_config` attribute is 

75 first invoked, the replacement variable ``here`` will be added to this 

76 dictionary before the dictionary is passed to ``SafeConfigParser()`` 

77 to parse the .ini file. 

78 

79 .. versionadded:: 0.7.0 

80 

81 :param attributes: optional dictionary of arbitrary Python keys/values, 

82 which will be populated into the :attr:`.Config.attributes` dictionary. 

83 

84 .. versionadded:: 0.7.5 

85 

86 .. seealso:: 

87 

88 :ref:`connection_sharing` 

89 

90 """ 

91 

92 def __init__( 

93 self, 

94 file_=None, 

95 ini_section="alembic", 

96 output_buffer=None, 

97 stdout=sys.stdout, 

98 cmd_opts=None, 

99 config_args=util.immutabledict(), 

100 attributes=None, 

101 ): 

102 """Construct a new :class:`.Config` 

103 

104 """ 

105 self.config_file_name = file_ 

106 self.config_ini_section = ini_section 

107 self.output_buffer = output_buffer 

108 self.stdout = stdout 

109 self.cmd_opts = cmd_opts 

110 self.config_args = dict(config_args) 

111 if attributes: 

112 self.attributes.update(attributes) 

113 

114 cmd_opts = None 

115 """The command-line options passed to the ``alembic`` script. 

116 

117 Within an ``env.py`` script this can be accessed via the 

118 :attr:`.EnvironmentContext.config` attribute. 

119 

120 .. versionadded:: 0.6.0 

121 

122 .. seealso:: 

123 

124 :meth:`.EnvironmentContext.get_x_argument` 

125 

126 """ 

127 

128 config_file_name = None 

129 """Filesystem path to the .ini file in use.""" 

130 

131 config_ini_section = None 

132 """Name of the config file section to read basic configuration 

133 from. Defaults to ``alembic``, that is the ``[alembic]`` section 

134 of the .ini file. This value is modified using the ``-n/--name`` 

135 option to the Alembic runnier. 

136 

137 """ 

138 

139 @util.memoized_property 

140 def attributes(self): 

141 """A Python dictionary for storage of additional state. 

142 

143 

144 This is a utility dictionary which can include not just strings but 

145 engines, connections, schema objects, or anything else. 

146 Use this to pass objects into an env.py script, such as passing 

147 a :class:`sqlalchemy.engine.base.Connection` when calling 

148 commands from :mod:`alembic.command` programmatically. 

149 

150 .. versionadded:: 0.7.5 

151 

152 .. seealso:: 

153 

154 :ref:`connection_sharing` 

155 

156 :paramref:`.Config.attributes` 

157 

158 """ 

159 return {} 

160 

161 def print_stdout(self, text, *arg): 

162 """Render a message to standard out. 

163 

164 When :meth:`.Config.print_stdout` is called with additional args 

165 those arguments will formatted against the provided text, 

166 otherwise we simply output the provided text verbatim. 

167 

168 e.g.:: 

169 

170 >>> config.print_stdout('Some text %s', 'arg') 

171 Some Text arg 

172 

173 """ 

174 

175 if arg: 

176 output = compat.text_type(text) % arg 

177 else: 

178 output = compat.text_type(text) 

179 

180 util.write_outstream(self.stdout, output, "\n") 

181 

182 @util.memoized_property 

183 def file_config(self): 

184 """Return the underlying ``ConfigParser`` object. 

185 

186 Direct access to the .ini file is available here, 

187 though the :meth:`.Config.get_section` and 

188 :meth:`.Config.get_main_option` 

189 methods provide a possibly simpler interface. 

190 

191 """ 

192 

193 if self.config_file_name: 

194 here = os.path.abspath(os.path.dirname(self.config_file_name)) 

195 else: 

196 here = "" 

197 self.config_args["here"] = here 

198 file_config = SafeConfigParser(self.config_args) 

199 if self.config_file_name: 

200 file_config.read([self.config_file_name]) 

201 else: 

202 file_config.add_section(self.config_ini_section) 

203 return file_config 

204 

205 def get_template_directory(self): 

206 """Return the directory where Alembic setup templates are found. 

207 

208 This method is used by the alembic ``init`` and ``list_templates`` 

209 commands. 

210 

211 """ 

212 import alembic 

213 

214 package_dir = os.path.abspath(os.path.dirname(alembic.__file__)) 

215 return os.path.join(package_dir, "templates") 

216 

217 def get_section(self, name, default=None): 

218 """Return all the configuration options from a given .ini file section 

219 as a dictionary. 

220 

221 """ 

222 if not self.file_config.has_section(name): 

223 return default 

224 

225 return dict(self.file_config.items(name)) 

226 

227 def set_main_option(self, name, value): 

228 """Set an option programmatically within the 'main' section. 

229 

230 This overrides whatever was in the .ini file. 

231 

232 :param name: name of the value 

233 

234 :param value: the value. Note that this value is passed to 

235 ``ConfigParser.set``, which supports variable interpolation using 

236 pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of 

237 an interpolation symbol must therefore be escaped, e.g. ``%%``. 

238 The given value may refer to another value already in the file 

239 using the interpolation format. 

240 

241 """ 

242 self.set_section_option(self.config_ini_section, name, value) 

243 

244 def remove_main_option(self, name): 

245 self.file_config.remove_option(self.config_ini_section, name) 

246 

247 def set_section_option(self, section, name, value): 

248 """Set an option programmatically within the given section. 

249 

250 The section is created if it doesn't exist already. 

251 The value here will override whatever was in the .ini 

252 file. 

253 

254 :param section: name of the section 

255 

256 :param name: name of the value 

257 

258 :param value: the value. Note that this value is passed to 

259 ``ConfigParser.set``, which supports variable interpolation using 

260 pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of 

261 an interpolation symbol must therefore be escaped, e.g. ``%%``. 

262 The given value may refer to another value already in the file 

263 using the interpolation format. 

264 

265 """ 

266 

267 if not self.file_config.has_section(section): 

268 self.file_config.add_section(section) 

269 self.file_config.set(section, name, value) 

270 

271 def get_section_option(self, section, name, default=None): 

272 """Return an option from the given section of the .ini file. 

273 

274 """ 

275 if not self.file_config.has_section(section): 

276 raise util.CommandError( 

277 "No config file %r found, or file has no " 

278 "'[%s]' section" % (self.config_file_name, section) 

279 ) 

280 if self.file_config.has_option(section, name): 

281 return self.file_config.get(section, name) 

282 else: 

283 return default 

284 

285 def get_main_option(self, name, default=None): 

286 """Return an option from the 'main' section of the .ini file. 

287 

288 This defaults to being a key from the ``[alembic]`` 

289 section, unless the ``-n/--name`` flag were used to 

290 indicate a different section. 

291 

292 """ 

293 return self.get_section_option(self.config_ini_section, name, default) 

294 

295 

296class CommandLine(object): 

297 def __init__(self, prog=None): 

298 self._generate_args(prog) 

299 

300 def _generate_args(self, prog): 

301 def add_options(fn, parser, positional, kwargs): 

302 kwargs_opts = { 

303 "template": ( 

304 "-t", 

305 "--template", 

306 dict( 

307 default="generic", 

308 type=str, 

309 help="Setup template for use with 'init'", 

310 ), 

311 ), 

312 "message": ( 

313 "-m", 

314 "--message", 

315 dict( 

316 type=str, help="Message string to use with 'revision'" 

317 ), 

318 ), 

319 "sql": ( 

320 "--sql", 

321 dict( 

322 action="store_true", 

323 help="Don't emit SQL to database - dump to " 

324 "standard output/file instead. See docs on " 

325 "offline mode.", 

326 ), 

327 ), 

328 "tag": ( 

329 "--tag", 

330 dict( 

331 type=str, 

332 help="Arbitrary 'tag' name - can be used by " 

333 "custom env.py scripts.", 

334 ), 

335 ), 

336 "head": ( 

337 "--head", 

338 dict( 

339 type=str, 

340 help="Specify head revision or <branchname>@head " 

341 "to base new revision on.", 

342 ), 

343 ), 

344 "splice": ( 

345 "--splice", 

346 dict( 

347 action="store_true", 

348 help="Allow a non-head revision as the " 

349 "'head' to splice onto", 

350 ), 

351 ), 

352 "depends_on": ( 

353 "--depends-on", 

354 dict( 

355 action="append", 

356 help="Specify one or more revision identifiers " 

357 "which this revision should depend on.", 

358 ), 

359 ), 

360 "rev_id": ( 

361 "--rev-id", 

362 dict( 

363 type=str, 

364 help="Specify a hardcoded revision id instead of " 

365 "generating one", 

366 ), 

367 ), 

368 "version_path": ( 

369 "--version-path", 

370 dict( 

371 type=str, 

372 help="Specify specific path from config for " 

373 "version file", 

374 ), 

375 ), 

376 "branch_label": ( 

377 "--branch-label", 

378 dict( 

379 type=str, 

380 help="Specify a branch label to apply to the " 

381 "new revision", 

382 ), 

383 ), 

384 "verbose": ( 

385 "-v", 

386 "--verbose", 

387 dict(action="store_true", help="Use more verbose output"), 

388 ), 

389 "resolve_dependencies": ( 

390 "--resolve-dependencies", 

391 dict( 

392 action="store_true", 

393 help="Treat dependency versions as down revisions", 

394 ), 

395 ), 

396 "autogenerate": ( 

397 "--autogenerate", 

398 dict( 

399 action="store_true", 

400 help="Populate revision script with candidate " 

401 "migration operations, based on comparison " 

402 "of database to model.", 

403 ), 

404 ), 

405 "head_only": ( 

406 "--head-only", 

407 dict( 

408 action="store_true", 

409 help="Deprecated. Use --verbose for " 

410 "additional output", 

411 ), 

412 ), 

413 "rev_range": ( 

414 "-r", 

415 "--rev-range", 

416 dict( 

417 action="store", 

418 help="Specify a revision range; " 

419 "format is [start]:[end]", 

420 ), 

421 ), 

422 "indicate_current": ( 

423 "-i", 

424 "--indicate-current", 

425 dict( 

426 action="store_true", 

427 help="Indicate the current revision", 

428 ), 

429 ), 

430 "purge": ( 

431 "--purge", 

432 dict( 

433 action="store_true", 

434 help="Unconditionally erase the version table " 

435 "before stamping", 

436 ), 

437 ), 

438 "package": ( 

439 "--package", 

440 dict( 

441 action="store_true", 

442 help="Write empty __init__.py files to the " 

443 "environment and version locations", 

444 ), 

445 ), 

446 } 

447 positional_help = { 

448 "directory": "location of scripts directory", 

449 "revision": "revision identifier", 

450 "revisions": "one or more revisions, or 'heads' for all heads", 

451 } 

452 for arg in kwargs: 

453 if arg in kwargs_opts: 

454 args = kwargs_opts[arg] 

455 args, kw = args[0:-1], args[-1] 

456 parser.add_argument(*args, **kw) 

457 

458 for arg in positional: 

459 if ( 

460 arg == "revisions" 

461 or fn in positional_translations 

462 and positional_translations[fn][arg] == "revisions" 

463 ): 

464 subparser.add_argument( 

465 "revisions", 

466 nargs="+", 

467 help=positional_help.get("revisions"), 

468 ) 

469 else: 

470 subparser.add_argument(arg, help=positional_help.get(arg)) 

471 

472 parser = ArgumentParser(prog=prog) 

473 

474 parser.add_argument( 

475 "-c", 

476 "--config", 

477 type=str, 

478 default=os.environ.get("ALEMBIC_CONFIG", "alembic.ini"), 

479 help="Alternate config file; defaults to value of " 

480 'ALEMBIC_CONFIG environment variable, or "alembic.ini"', 

481 ) 

482 parser.add_argument( 

483 "-n", 

484 "--name", 

485 type=str, 

486 default="alembic", 

487 help="Name of section in .ini file to " "use for Alembic config", 

488 ) 

489 parser.add_argument( 

490 "-x", 

491 action="append", 

492 help="Additional arguments consumed by " 

493 "custom env.py scripts, e.g. -x " 

494 "setting1=somesetting -x setting2=somesetting", 

495 ) 

496 parser.add_argument( 

497 "--raiseerr", 

498 action="store_true", 

499 help="Raise a full stack trace on error", 

500 ) 

501 subparsers = parser.add_subparsers() 

502 

503 positional_translations = {command.stamp: {"revision": "revisions"}} 

504 

505 for fn in [getattr(command, n) for n in dir(command)]: 

506 if ( 

507 inspect.isfunction(fn) 

508 and fn.__name__[0] != "_" 

509 and fn.__module__ == "alembic.command" 

510 ): 

511 

512 spec = compat.inspect_getargspec(fn) 

513 if spec[3]: 

514 positional = spec[0][1 : -len(spec[3])] 

515 kwarg = spec[0][-len(spec[3]) :] 

516 else: 

517 positional = spec[0][1:] 

518 kwarg = [] 

519 

520 if fn in positional_translations: 

521 positional = [ 

522 positional_translations[fn].get(name, name) 

523 for name in positional 

524 ] 

525 

526 # parse first line(s) of helptext without a line break 

527 help_ = fn.__doc__ 

528 if help_: 

529 help_text = [] 

530 for line in help_.split("\n"): 

531 if not line.strip(): 

532 break 

533 else: 

534 help_text.append(line.strip()) 

535 else: 

536 help_text = "" 

537 subparser = subparsers.add_parser( 

538 fn.__name__, help=" ".join(help_text) 

539 ) 

540 add_options(fn, subparser, positional, kwarg) 

541 subparser.set_defaults(cmd=(fn, positional, kwarg)) 

542 self.parser = parser 

543 

544 def run_cmd(self, config, options): 

545 fn, positional, kwarg = options.cmd 

546 

547 try: 

548 fn( 

549 config, 

550 *[getattr(options, k, None) for k in positional], 

551 **dict((k, getattr(options, k, None)) for k in kwarg) 

552 ) 

553 except util.CommandError as e: 

554 if options.raiseerr: 

555 raise 

556 else: 

557 util.err(str(e)) 

558 

559 def main(self, argv=None): 

560 options = self.parser.parse_args(argv) 

561 if not hasattr(options, "cmd"): 

562 # see http://bugs.python.org/issue9253, argparse 

563 # behavior changed incompatibly in py3.3 

564 self.parser.error("too few arguments") 

565 else: 

566 cfg = Config( 

567 file_=options.config, 

568 ini_section=options.name, 

569 cmd_opts=options, 

570 ) 

571 self.run_cmd(cfg, options) 

572 

573 

574def main(argv=None, prog=None, **kwargs): 

575 """The console runner function for Alembic.""" 

576 

577 CommandLine(prog=prog).main(argv=argv) 

578 

579 

580if __name__ == "__main__": 

581 main()