from argparse import ArgumentParser from .util.compat import SafeConfigParser import inspect import os import sys from . import command from . import util from . import package_dir from .util import compat class Config(object): """Represent an Alembic configuration. Within an ``env.py`` script, this is available via the :attr:`.EnvironmentContext.config` attribute, which in turn is available at ``alembic.context``:: from alembic import context some_param = context.config.get_main_option("my option") When invoking Alembic programatically, a new :class:`.Config` can be created by passing the name of an .ini file to the constructor:: from alembic.config import Config alembic_cfg = Config("/path/to/yourapp/alembic.ini") With a :class:`.Config` object, you can then run Alembic commands programmatically using the directives in :mod:`alembic.command`. The :class:`.Config` object can also be constructed without a filename. Values can be set programmatically, and new sections will be created as needed:: from alembic.config import Config alembic_cfg = Config() alembic_cfg.set_main_option("script_location", "myapp:migrations") alembic_cfg.set_main_option("url", "postgresql://foo/bar") alembic_cfg.set_section_option("mysection", "foo", "bar") .. warning:: When using programmatic configuration, make sure the ``env.py`` file in use is compatible with the target configuration; including that the call to Python ``logging.fileConfig()`` is omitted if the programmatic configuration doesn't actually include logging directives. For passing non-string values to environments, such as connections and engines, use the :attr:`.Config.attributes` dictionary:: with engine.begin() as connection: alembic_cfg.attributes['connection'] = connection command.upgrade(alembic_cfg, "head") :param file_: name of the .ini file to open. :param ini_section: name of the main Alembic section within the .ini file :param output_buffer: optional file-like input buffer which will be passed to the :class:`.MigrationContext` - used to redirect the output of "offline generation" when using Alembic programmatically. :param stdout: buffer where the "print" output of commands will be sent. Defaults to ``sys.stdout``. .. versionadded:: 0.4 :param config_args: A dictionary of keys and values that will be used for substitution in the alembic config file. The dictionary as given is **copied** to a new one, stored locally as the attribute ``.config_args``. When the :attr:`.Config.file_config` attribute is first invoked, the replacement variable ``here`` will be added to this dictionary before the dictionary is passed to ``SafeConfigParser()`` to parse the .ini file. .. versionadded:: 0.7.0 :param attributes: optional dictionary of arbitrary Python keys/values, which will be populated into the :attr:`.Config.attributes` dictionary. .. versionadded:: 0.7.5 .. seealso:: :ref:`connection_sharing` """ def __init__(self, file_=None, ini_section='alembic', output_buffer=None, stdout=sys.stdout, cmd_opts=None, config_args=util.immutabledict(), attributes=None): """Construct a new :class:`.Config` """ self.config_file_name = file_ self.config_ini_section = ini_section self.output_buffer = output_buffer self.stdout = stdout self.cmd_opts = cmd_opts self.config_args = dict(config_args) if attributes: self.attributes.update(attributes) cmd_opts = None """The command-line options passed to the ``alembic`` script. Within an ``env.py`` script this can be accessed via the :attr:`.EnvironmentContext.config` attribute. .. versionadded:: 0.6.0 .. seealso:: :meth:`.EnvironmentContext.get_x_argument` """ config_file_name = None """Filesystem path to the .ini file in use.""" config_ini_section = None """Name of the config file section to read basic configuration from. Defaults to ``alembic``, that is the ``[alembic]`` section of the .ini file. This value is modified using the ``-n/--name`` option to the Alembic runnier. """ @util.memoized_property def attributes(self): """A Python dictionary for storage of additional state. This is a utility dictionary which can include not just strings but engines, connections, schema objects, or anything else. Use this to pass objects into an env.py script, such as passing a :class:`sqlalchemy.engine.base.Connection` when calling commands from :mod:`alembic.command` programmatically. .. versionadded:: 0.7.5 .. seealso:: :ref:`connection_sharing` :paramref:`.Config.attributes` """ return {} def print_stdout(self, text, *arg): """Render a message to standard out.""" util.write_outstream( self.stdout, (compat.text_type(text) % arg), "\n" ) @util.memoized_property def file_config(self): """Return the underlying ``ConfigParser`` object. Direct access to the .ini file is available here, though the :meth:`.Config.get_section` and :meth:`.Config.get_main_option` methods provide a possibly simpler interface. """ if self.config_file_name: here = os.path.abspath(os.path.dirname(self.config_file_name)) else: here = "" self.config_args['here'] = here file_config = SafeConfigParser(self.config_args) if self.config_file_name: file_config.read([self.config_file_name]) else: file_config.add_section(self.config_ini_section) return file_config def get_template_directory(self): """Return the directory where Alembic setup templates are found. This method is used by the alembic ``init`` and ``list_templates`` commands. """ return os.path.join(package_dir, 'templates') def get_section(self, name): """Return all the configuration options from a given .ini file section as a dictionary. """ return dict(self.file_config.items(name)) def set_main_option(self, name, value): """Set an option programmatically within the 'main' section. This overrides whatever was in the .ini file. :param name: name of the value :param value: the value. Note that this value is passed to ``ConfigParser.set``, which supports variable interpolation using pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of an interpolation symbol must therefore be escaped, e.g. ``%%``. The given value may refer to another value already in the file using the interpolation format. """ self.set_section_option(self.config_ini_section, name, value) def remove_main_option(self, name): self.file_config.remove_option(self.config_ini_section, name) def set_section_option(self, section, name, value): """Set an option programmatically within the given section. The section is created if it doesn't exist already. The value here will override whatever was in the .ini file. :param section: name of the section :param name: name of the value :param value: the value. Note that this value is passed to ``ConfigParser.set``, which supports variable interpolation using pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of an interpolation symbol must therefore be escaped, e.g. ``%%``. The given value may refer to another value already in the file using the interpolation format. """ if not self.file_config.has_section(section): self.file_config.add_section(section) self.file_config.set(section, name, value) def get_section_option(self, section, name, default=None): """Return an option from the given section of the .ini file. """ if not self.file_config.has_section(section): raise util.CommandError("No config file %r found, or file has no " "'[%s]' section" % (self.config_file_name, section)) if self.file_config.has_option(section, name): return self.file_config.get(section, name) else: return default def get_main_option(self, name, default=None): """Return an option from the 'main' section of the .ini file. This defaults to being a key from the ``[alembic]`` section, unless the ``-n/--name`` flag were used to indicate a different section. """ return self.get_section_option(self.config_ini_section, name, default) class CommandLine(object): def __init__(self, prog=None): self._generate_args(prog) def _generate_args(self, prog): def add_options(parser, positional, kwargs): kwargs_opts = { 'template': ( "-t", "--template", dict( default='generic', type=str, help="Setup template for use with 'init'" ) ), 'message': ( "-m", "--message", dict( type=str, help="Message string to use with 'revision'") ), 'sql': ( "--sql", dict( action="store_true", help="Don't emit SQL to database - dump to " "standard output/file instead" ) ), 'tag': ( "--tag", dict( type=str, help="Arbitrary 'tag' name - can be used by " "custom env.py scripts.") ), 'head': ( "--head", dict( type=str, help="Specify head revision or @head " "to base new revision on." ) ), 'splice': ( "--splice", dict( action="store_true", help="Allow a non-head revision as the " "'head' to splice onto" ) ), 'depends_on': ( "--depends-on", dict( action="append", help="Specify one or more revision identifiers " "which this revision should depend on." ) ), 'rev_id': ( "--rev-id", dict( type=str, help="Specify a hardcoded revision id instead of " "generating one" ) ), 'version_path': ( "--version-path", dict( type=str, help="Specify specific path from config for " "version file" ) ), 'branch_label': ( "--branch-label", dict( type=str, help="Specify a branch label to apply to the " "new revision" ) ), 'verbose': ( "-v", "--verbose", dict( action="store_true", help="Use more verbose output" ) ), 'resolve_dependencies': ( '--resolve-dependencies', dict( action="store_true", help="Treat dependency versions as down revisions" ) ), 'autogenerate': ( "--autogenerate", dict( action="store_true", help="Populate revision script with candidate " "migration operations, based on comparison " "of database to model.") ), 'head_only': ( "--head-only", dict( action="store_true", help="Deprecated. Use --verbose for " "additional output") ), 'rev_range': ( "-r", "--rev-range", dict( action="store", help="Specify a revision range; " "format is [start]:[end]") ) } positional_help = { 'directory': "location of scripts directory", 'revision': "revision identifier", 'revisions': "one or more revisions, or 'heads' for all heads" } for arg in kwargs: if arg in kwargs_opts: args = kwargs_opts[arg] args, kw = args[0:-1], args[-1] parser.add_argument(*args, **kw) for arg in positional: if arg == "revisions": subparser.add_argument( arg, nargs='+', help=positional_help.get(arg)) else: subparser.add_argument(arg, help=positional_help.get(arg)) parser = ArgumentParser(prog=prog) parser.add_argument("-c", "--config", type=str, default="alembic.ini", help="Alternate config file") parser.add_argument("-n", "--name", type=str, default="alembic", help="Name of section in .ini file to " "use for Alembic config") parser.add_argument("-x", action="append", help="Additional arguments consumed by " "custom env.py scripts, e.g. -x " "setting1=somesetting -x setting2=somesetting") parser.add_argument("--raiseerr", action="store_true", help="Raise a full stack trace on error") subparsers = parser.add_subparsers() for fn in [getattr(command, n) for n in dir(command)]: if inspect.isfunction(fn) and \ fn.__name__[0] != '_' and \ fn.__module__ == 'alembic.command': spec = compat.inspect_getargspec(fn) if spec[3]: positional = spec[0][1:-len(spec[3])] kwarg = spec[0][-len(spec[3]):] else: positional = spec[0][1:] kwarg = [] subparser = subparsers.add_parser( fn.__name__, help=fn.__doc__) add_options(subparser, positional, kwarg) subparser.set_defaults(cmd=(fn, positional, kwarg)) self.parser = parser def run_cmd(self, config, options): fn, positional, kwarg = options.cmd try: fn(config, *[getattr(options, k, None) for k in positional], **dict((k, getattr(options, k, None)) for k in kwarg) ) except util.CommandError as e: if options.raiseerr: raise else: util.err(str(e)) def main(self, argv=None): options = self.parser.parse_args(argv) if not hasattr(options, "cmd"): # see http://bugs.python.org/issue9253, argparse # behavior changed incompatibly in py3.3 self.parser.error("too few arguments") else: cfg = Config(file_=options.config, ini_section=options.name, cmd_opts=options) self.run_cmd(cfg, options) def main(argv=None, prog=None, **kwargs): """The console runner function for Alembic.""" CommandLine(prog=prog).main(argv=argv) if __name__ == '__main__': main()