1from argparse import ArgumentParser 2import inspect 3import os 4import sys 5 6from . import __version__ 7from . import command 8from . import util 9from .util import compat 10from .util.compat import SafeConfigParser 11 12 13class Config(object): 14 15 r"""Represent an Alembic configuration. 16 17 Within an ``env.py`` script, this is available 18 via the :attr:`.EnvironmentContext.config` attribute, 19 which in turn is available at ``alembic.context``:: 20 21 from alembic import context 22 23 some_param = context.config.get_main_option("my option") 24 25 When invoking Alembic programatically, a new 26 :class:`.Config` can be created by passing 27 the name of an .ini file to the constructor:: 28 29 from alembic.config import Config 30 alembic_cfg = Config("/path/to/yourapp/alembic.ini") 31 32 With a :class:`.Config` object, you can then 33 run Alembic commands programmatically using the directives 34 in :mod:`alembic.command`. 35 36 The :class:`.Config` object can also be constructed without 37 a filename. Values can be set programmatically, and 38 new sections will be created as needed:: 39 40 from alembic.config import Config 41 alembic_cfg = Config() 42 alembic_cfg.set_main_option("script_location", "myapp:migrations") 43 alembic_cfg.set_main_option("sqlalchemy.url", "postgresql://foo/bar") 44 alembic_cfg.set_section_option("mysection", "foo", "bar") 45 46 .. warning:: 47 48 When using programmatic configuration, make sure the 49 ``env.py`` file in use is compatible with the target configuration; 50 including that the call to Python ``logging.fileConfig()`` is 51 omitted if the programmatic configuration doesn't actually include 52 logging directives. 53 54 For passing non-string values to environments, such as connections and 55 engines, use the :attr:`.Config.attributes` dictionary:: 56 57 with engine.begin() as connection: 58 alembic_cfg.attributes['connection'] = connection 59 command.upgrade(alembic_cfg, "head") 60 61 :param file\_: name of the .ini file to open. 62 :param ini_section: name of the main Alembic section within the 63 .ini file 64 :param output_buffer: optional file-like input buffer which 65 will be passed to the :class:`.MigrationContext` - used to redirect 66 the output of "offline generation" when using Alembic programmatically. 67 :param stdout: buffer where the "print" output of commands will be sent. 68 Defaults to ``sys.stdout``. 69 70 .. versionadded:: 0.4 71 72 :param config_args: A dictionary of keys and values that will be used 73 for substitution in the alembic config file. The dictionary as given 74 is **copied** to a new one, stored locally as the attribute 75 ``.config_args``. When the :attr:`.Config.file_config` attribute is 76 first invoked, the replacement variable ``here`` will be added to this 77 dictionary before the dictionary is passed to ``SafeConfigParser()`` 78 to parse the .ini file. 79 80 .. versionadded:: 0.7.0 81 82 :param attributes: optional dictionary of arbitrary Python keys/values, 83 which will be populated into the :attr:`.Config.attributes` dictionary. 84 85 .. versionadded:: 0.7.5 86 87 .. seealso:: 88 89 :ref:`connection_sharing` 90 91 """ 92 93 def __init__( 94 self, 95 file_=None, 96 ini_section="alembic", 97 output_buffer=None, 98 stdout=sys.stdout, 99 cmd_opts=None, 100 config_args=util.immutabledict(), 101 attributes=None, 102 ): 103 """Construct a new :class:`.Config` 104 105 """ 106 self.config_file_name = file_ 107 self.config_ini_section = ini_section 108 self.output_buffer = output_buffer 109 self.stdout = stdout 110 self.cmd_opts = cmd_opts 111 self.config_args = dict(config_args) 112 if attributes: 113 self.attributes.update(attributes) 114 115 cmd_opts = None 116 """The command-line options passed to the ``alembic`` script. 117 118 Within an ``env.py`` script this can be accessed via the 119 :attr:`.EnvironmentContext.config` attribute. 120 121 .. versionadded:: 0.6.0 122 123 .. seealso:: 124 125 :meth:`.EnvironmentContext.get_x_argument` 126 127 """ 128 129 config_file_name = None 130 """Filesystem path to the .ini file in use.""" 131 132 config_ini_section = None 133 """Name of the config file section to read basic configuration 134 from. Defaults to ``alembic``, that is the ``[alembic]`` section 135 of the .ini file. This value is modified using the ``-n/--name`` 136 option to the Alembic runnier. 137 138 """ 139 140 @util.memoized_property 141 def attributes(self): 142 """A Python dictionary for storage of additional state. 143 144 145 This is a utility dictionary which can include not just strings but 146 engines, connections, schema objects, or anything else. 147 Use this to pass objects into an env.py script, such as passing 148 a :class:`sqlalchemy.engine.base.Connection` when calling 149 commands from :mod:`alembic.command` programmatically. 150 151 .. versionadded:: 0.7.5 152 153 .. seealso:: 154 155 :ref:`connection_sharing` 156 157 :paramref:`.Config.attributes` 158 159 """ 160 return {} 161 162 def print_stdout(self, text, *arg): 163 """Render a message to standard out. 164 165 When :meth:`.Config.print_stdout` is called with additional args 166 those arguments will formatted against the provided text, 167 otherwise we simply output the provided text verbatim. 168 169 e.g.:: 170 171 >>> config.print_stdout('Some text %s', 'arg') 172 Some Text arg 173 174 """ 175 176 if arg: 177 output = compat.text_type(text) % arg 178 else: 179 output = compat.text_type(text) 180 181 util.write_outstream(self.stdout, output, "\n") 182 183 @util.memoized_property 184 def file_config(self): 185 """Return the underlying ``ConfigParser`` object. 186 187 Direct access to the .ini file is available here, 188 though the :meth:`.Config.get_section` and 189 :meth:`.Config.get_main_option` 190 methods provide a possibly simpler interface. 191 192 """ 193 194 if self.config_file_name: 195 here = os.path.abspath(os.path.dirname(self.config_file_name)) 196 else: 197 here = "" 198 self.config_args["here"] = here 199 file_config = SafeConfigParser(self.config_args) 200 if self.config_file_name: 201 file_config.read([self.config_file_name]) 202 else: 203 file_config.add_section(self.config_ini_section) 204 return file_config 205 206 def get_template_directory(self): 207 """Return the directory where Alembic setup templates are found. 208 209 This method is used by the alembic ``init`` and ``list_templates`` 210 commands. 211 212 """ 213 import alembic 214 215 package_dir = os.path.abspath(os.path.dirname(alembic.__file__)) 216 return os.path.join(package_dir, "templates") 217 218 def get_section(self, name, default=None): 219 """Return all the configuration options from a given .ini file section 220 as a dictionary. 221 222 """ 223 if not self.file_config.has_section(name): 224 return default 225 226 return dict(self.file_config.items(name)) 227 228 def set_main_option(self, name, value): 229 """Set an option programmatically within the 'main' section. 230 231 This overrides whatever was in the .ini file. 232 233 :param name: name of the value 234 235 :param value: the value. Note that this value is passed to 236 ``ConfigParser.set``, which supports variable interpolation using 237 pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of 238 an interpolation symbol must therefore be escaped, e.g. ``%%``. 239 The given value may refer to another value already in the file 240 using the interpolation format. 241 242 """ 243 self.set_section_option(self.config_ini_section, name, value) 244 245 def remove_main_option(self, name): 246 self.file_config.remove_option(self.config_ini_section, name) 247 248 def set_section_option(self, section, name, value): 249 """Set an option programmatically within the given section. 250 251 The section is created if it doesn't exist already. 252 The value here will override whatever was in the .ini 253 file. 254 255 :param section: name of the section 256 257 :param name: name of the value 258 259 :param value: the value. Note that this value is passed to 260 ``ConfigParser.set``, which supports variable interpolation using 261 pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of 262 an interpolation symbol must therefore be escaped, e.g. ``%%``. 263 The given value may refer to another value already in the file 264 using the interpolation format. 265 266 """ 267 268 if not self.file_config.has_section(section): 269 self.file_config.add_section(section) 270 self.file_config.set(section, name, value) 271 272 def get_section_option(self, section, name, default=None): 273 """Return an option from the given section of the .ini file. 274 275 """ 276 if not self.file_config.has_section(section): 277 raise util.CommandError( 278 "No config file %r found, or file has no " 279 "'[%s]' section" % (self.config_file_name, section) 280 ) 281 if self.file_config.has_option(section, name): 282 return self.file_config.get(section, name) 283 else: 284 return default 285 286 def get_main_option(self, name, default=None): 287 """Return an option from the 'main' section of the .ini file. 288 289 This defaults to being a key from the ``[alembic]`` 290 section, unless the ``-n/--name`` flag were used to 291 indicate a different section. 292 293 """ 294 return self.get_section_option(self.config_ini_section, name, default) 295 296 297class CommandLine(object): 298 def __init__(self, prog=None): 299 self._generate_args(prog) 300 301 def _generate_args(self, prog): 302 def add_options(fn, parser, positional, kwargs): 303 kwargs_opts = { 304 "template": ( 305 "-t", 306 "--template", 307 dict( 308 default="generic", 309 type=str, 310 help="Setup template for use with 'init'", 311 ), 312 ), 313 "message": ( 314 "-m", 315 "--message", 316 dict( 317 type=str, help="Message string to use with 'revision'" 318 ), 319 ), 320 "sql": ( 321 "--sql", 322 dict( 323 action="store_true", 324 help="Don't emit SQL to database - dump to " 325 "standard output/file instead. See docs on " 326 "offline mode.", 327 ), 328 ), 329 "tag": ( 330 "--tag", 331 dict( 332 type=str, 333 help="Arbitrary 'tag' name - can be used by " 334 "custom env.py scripts.", 335 ), 336 ), 337 "head": ( 338 "--head", 339 dict( 340 type=str, 341 help="Specify head revision or <branchname>@head " 342 "to base new revision on.", 343 ), 344 ), 345 "splice": ( 346 "--splice", 347 dict( 348 action="store_true", 349 help="Allow a non-head revision as the " 350 "'head' to splice onto", 351 ), 352 ), 353 "depends_on": ( 354 "--depends-on", 355 dict( 356 action="append", 357 help="Specify one or more revision identifiers " 358 "which this revision should depend on.", 359 ), 360 ), 361 "rev_id": ( 362 "--rev-id", 363 dict( 364 type=str, 365 help="Specify a hardcoded revision id instead of " 366 "generating one", 367 ), 368 ), 369 "version_path": ( 370 "--version-path", 371 dict( 372 type=str, 373 help="Specify specific path from config for " 374 "version file", 375 ), 376 ), 377 "branch_label": ( 378 "--branch-label", 379 dict( 380 type=str, 381 help="Specify a branch label to apply to the " 382 "new revision", 383 ), 384 ), 385 "verbose": ( 386 "-v", 387 "--verbose", 388 dict(action="store_true", help="Use more verbose output"), 389 ), 390 "resolve_dependencies": ( 391 "--resolve-dependencies", 392 dict( 393 action="store_true", 394 help="Treat dependency versions as down revisions", 395 ), 396 ), 397 "autogenerate": ( 398 "--autogenerate", 399 dict( 400 action="store_true", 401 help="Populate revision script with candidate " 402 "migration operations, based on comparison " 403 "of database to model.", 404 ), 405 ), 406 "head_only": ( 407 "--head-only", 408 dict( 409 action="store_true", 410 help="Deprecated. Use --verbose for " 411 "additional output", 412 ), 413 ), 414 "rev_range": ( 415 "-r", 416 "--rev-range", 417 dict( 418 action="store", 419 help="Specify a revision range; " 420 "format is [start]:[end]", 421 ), 422 ), 423 "indicate_current": ( 424 "-i", 425 "--indicate-current", 426 dict( 427 action="store_true", 428 help="Indicate the current revision", 429 ), 430 ), 431 "purge": ( 432 "--purge", 433 dict( 434 action="store_true", 435 help="Unconditionally erase the version table " 436 "before stamping", 437 ), 438 ), 439 "package": ( 440 "--package", 441 dict( 442 action="store_true", 443 help="Write empty __init__.py files to the " 444 "environment and version locations", 445 ), 446 ), 447 } 448 positional_help = { 449 "directory": "location of scripts directory", 450 "revision": "revision identifier", 451 "revisions": "one or more revisions, or 'heads' for all heads", 452 } 453 for arg in kwargs: 454 if arg in kwargs_opts: 455 args = kwargs_opts[arg] 456 args, kw = args[0:-1], args[-1] 457 parser.add_argument(*args, **kw) 458 459 for arg in positional: 460 if ( 461 arg == "revisions" 462 or fn in positional_translations 463 and positional_translations[fn][arg] == "revisions" 464 ): 465 subparser.add_argument( 466 "revisions", 467 nargs="+", 468 help=positional_help.get("revisions"), 469 ) 470 else: 471 subparser.add_argument(arg, help=positional_help.get(arg)) 472 473 parser = ArgumentParser(prog=prog) 474 475 parser.add_argument( 476 "--version", action="version", version="%%(prog)s %s" % __version__ 477 ) 478 parser.add_argument( 479 "-c", 480 "--config", 481 type=str, 482 default=os.environ.get("ALEMBIC_CONFIG", "alembic.ini"), 483 help="Alternate config file; defaults to value of " 484 'ALEMBIC_CONFIG environment variable, or "alembic.ini"', 485 ) 486 parser.add_argument( 487 "-n", 488 "--name", 489 type=str, 490 default="alembic", 491 help="Name of section in .ini file to " "use for Alembic config", 492 ) 493 parser.add_argument( 494 "-x", 495 action="append", 496 help="Additional arguments consumed by " 497 "custom env.py scripts, e.g. -x " 498 "setting1=somesetting -x setting2=somesetting", 499 ) 500 parser.add_argument( 501 "--raiseerr", 502 action="store_true", 503 help="Raise a full stack trace on error", 504 ) 505 subparsers = parser.add_subparsers() 506 507 positional_translations = {command.stamp: {"revision": "revisions"}} 508 509 for fn in [getattr(command, n) for n in dir(command)]: 510 if ( 511 inspect.isfunction(fn) 512 and fn.__name__[0] != "_" 513 and fn.__module__ == "alembic.command" 514 ): 515 516 spec = compat.inspect_getargspec(fn) 517 if spec[3]: 518 positional = spec[0][1 : -len(spec[3])] 519 kwarg = spec[0][-len(spec[3]) :] 520 else: 521 positional = spec[0][1:] 522 kwarg = [] 523 524 if fn in positional_translations: 525 positional = [ 526 positional_translations[fn].get(name, name) 527 for name in positional 528 ] 529 530 # parse first line(s) of helptext without a line break 531 help_ = fn.__doc__ 532 if help_: 533 help_text = [] 534 for line in help_.split("\n"): 535 if not line.strip(): 536 break 537 else: 538 help_text.append(line.strip()) 539 else: 540 help_text = "" 541 subparser = subparsers.add_parser( 542 fn.__name__, help=" ".join(help_text) 543 ) 544 add_options(fn, subparser, positional, kwarg) 545 subparser.set_defaults(cmd=(fn, positional, kwarg)) 546 self.parser = parser 547 548 def run_cmd(self, config, options): 549 fn, positional, kwarg = options.cmd 550 551 try: 552 fn( 553 config, 554 *[getattr(options, k, None) for k in positional], 555 **dict((k, getattr(options, k, None)) for k in kwarg) 556 ) 557 except util.CommandError as e: 558 if options.raiseerr: 559 raise 560 else: 561 util.err(str(e)) 562 563 def main(self, argv=None): 564 options = self.parser.parse_args(argv) 565 if not hasattr(options, "cmd"): 566 # see http://bugs.python.org/issue9253, argparse 567 # behavior changed incompatibly in py3.3 568 self.parser.error("too few arguments") 569 else: 570 cfg = Config( 571 file_=options.config, 572 ini_section=options.name, 573 cmd_opts=options, 574 ) 575 self.run_cmd(cfg, options) 576 577 578def main(argv=None, prog=None, **kwargs): 579 """The console runner function for Alembic.""" 580 581 CommandLine(prog=prog).main(argv=argv) 582 583 584if __name__ == "__main__": 585 main() 586