123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- 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 <branchname>@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 = 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()
|