From 4cf3cc4027f4f8fc3471915eb580a9e210d5f0cc Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 25 Apr 2010 11:26:02 -0400 Subject: [PATCH] commands --- alembic/__init__.py | 16 ++++++++- alembic/command.py | 69 ++++++++++++++++++++++++++---------- alembic/options.py | 76 ++++++++++++++++++++++++++++++---------- alembic/script.py | 8 ++--- scripts/alembic | 4 +-- templates/generic/README | 1 + templates/multidb/README | 1 + templates/pylons/README | 1 + 8 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 templates/generic/README create mode 100644 templates/multidb/README create mode 100644 templates/pylons/README diff --git a/alembic/__init__.py b/alembic/__init__.py index 304c26b2..649da28b 100644 --- a/alembic/__init__.py +++ b/alembic/__init__.py @@ -1,4 +1,18 @@ -from command import main +from alembic import options, command __version__ = '0.1alpha' + + +def main(argv): + + parser = options.get_option_parser() + + opt = options.Options(parser, argv) + cmd = opt.get_command().replace('-', '_') + if cmd not in dir(command): + parser.error("no such command %r" % cmd) + getattr(command, cmd)(opt) + + + diff --git a/alembic/command.py b/alembic/command.py index 8fdc9a45..7334dd76 100644 --- a/alembic/command.py +++ b/alembic/command.py @@ -1,36 +1,69 @@ from alembic.script import ScriptDirectory from alembic import options +import os +import sys -def main(argv=None): - - parser = options.get_option_parser() - - opts, args = parser.parse_args(argv[1:]) - if len(args) < 1: - parser.error("no command specified") # Will exit - - print opts.config - -def list_templates(options): +def _status(msg, fn, *arg, **kw): + sys.stdout.write(" " + msg + "...") + try: + ret = fn(*arg, **kw) + sys.stdout.write("done\n") + return ret + except: + sys.stdout.write("FAILED\n") + raise + +def list_templates(opts): """List available templates""" -def init(options): + print "Available templates:\n" + for tempname in os.listdir(opts.get_template_directory()): + readme = os.path.join( + opts.get_template_directory(), + tempname, + 'README') + synopsis = open(readme).next() + print options.format_opt(tempname, synopsis) + + print "\nTemplates are used via the 'init' command, e.g.:" + print "\n alembic init --template pylons ./scripts" + +def init(opts): """Initialize a new scripts directory.""" - script = ScriptDirectory(options) - script.init() + dir_, = opts.get_command_args(1, 'alembic init ') + if not _status("Checking for directory %s" % dir_, + os.access, dir_, os.F_OK): + _status("Creating directory %s" % dir_, + os.makedirs, dir_) + else: + opts.err("Directory %s already exists" % dir_) + # copy files... -def upgrade(options): +def upgrade(opts): """Upgrade to the latest version.""" - script = ScriptDirectory(options) + script = ScriptDirectory.from_options(opts) # ... -def revert(options, file_config): +def revert(opts): """Revert to a specific previous version.""" - script = ScriptDirectory(options) + script = ScriptDirectory.from_options(opts) # ... +def history(opts): + """List changeset scripts in chronological order.""" + + script = ScriptDirectory.from_options(opts) + +def splice(opts): + """'splice' two branches, creating a new revision file.""" + +def revision(opts): + """Create a new revision file.""" + +def branches(opts): + """Show current un-spliced branch points""" \ No newline at end of file diff --git a/alembic/options.py b/alembic/options.py index 1749400b..715f3139 100644 --- a/alembic/options.py +++ b/alembic/options.py @@ -1,49 +1,87 @@ from optparse import OptionParser import ConfigParser -import textwrap +import inspect +import os +import sys +def format_opt(opt, hlp, padding=22): + return " " + opt + \ + ((padding - len(opt)) * " ") + hlp + def get_option_parser(): + from alembic import command + # TODO: # OK, what's the super option parser library that # allows plus command-specfic sub-options ? + # we're inventing here a bit. + + commands = [ + (fn.__name__.replace('_', '-'), fn.__doc__) for fn in + [getattr(command, name) for name in sorted(dir(command))] + if inspect.isfunction(fn) and + fn.__name__[0] != '_' and + fn.__module__ == 'alembic.command' + ] - # TODO: pull the commands from command.py directly here parser = OptionParser( - "usage: %prog [options] \n\n" - "Available Commands:\n" - " list-templates\n" - " init\n" - " revision\n" - " upgrade\n" - " revert\n" - " history\n" - " splice\n" - " branches" + "usage: %prog [options] [command arguments]\n\n" + "Available Commands:\n" + + "\n".join([ + format_opt(cmd, hlp) + for cmd, hlp in commands + ]) ) parser.add_option("-c", "--config", type="string", default="alembic.ini", help="Alternate config file") parser.add_option("-t", "--template", + default='generic', type="string", help="Setup template for use with 'init'") - parser.add_option("-r", "--rev", + parser.add_option("-m", "--message", type="string", - help="Revsion identifier for usage with 'revert'" - ) + help="Message string to use with 'revision'") return parser class Options(object): - def __init__(self, cmd_line_options): - self.cmd_line_options = cmd_line_options + def __init__(self, parser, argv): + self.parser = parser + self.cmd_line_options, \ + self.cmd_line_args = parser.parse_args(argv[1:]) + if len(self.cmd_line_args) < 1: + self.err("no command specified") + self.config_file_name = self.cmd_line_options.config self.file_config = ConfigParser.ConfigParser() - # TODO: cfg file can come from options - self.file_config.read(['alembic.cfg']) + self.file_config.read([self.config_file_name]) + def get_command(self): + return self.cmd_line_args[0] + + def get_command_args(self, count, err): + if len(self.cmd_line_args[1:]) != count: + self.err( + "Command %r syntax: %r" % + (self.get_command(), err)) + return self.cmd_line_args[1:] + + def get_template_directory(self): + # TODO: what's the official way to get at + # setuptools-installed datafiles ? + return os.path.join(os.path.dirname(__file__), '..', 'templates') + def get_section(self, name): return dict(self.file_config.items(name)) + + def err(self, msg): + sys.stderr.write(msg + "\n") + sys.exit(-1) def get_main_option(self, name, default=None): + if not self.file_config.has_section('alembic'): + self.err("No config file %r found, or file has no " + "'[alembic]' section" % self.config_file_name) if self.file_config.get('alembic', name): return self.file_config.get('alembic', name) else: diff --git a/alembic/script.py b/alembic/script.py index 1bdd02f4..3085d450 100644 --- a/alembic/script.py +++ b/alembic/script.py @@ -5,10 +5,6 @@ class ScriptDirectory(object): self.dir = dir @classmethod - def from_options(cls, options, file_config): - return Script(file_config.get_main_option('script_location')) + def from_options(cls, options): + return ScriptDirectory(options.get_main_option('script_location')) - def init(self): - if not os.access(self.dir, os.F_OK): - os.makedirs(self.dir) - # copy files... diff --git a/scripts/alembic b/scripts/alembic index 65d7e02a..191cc29d 100755 --- a/scripts/alembic +++ b/scripts/alembic @@ -1,8 +1,8 @@ #!/usr/bin/env python -from alembic import command +from alembic import main import sys if __name__ == "__main__": - command.main(sys.argv) + main(sys.argv) diff --git a/templates/generic/README b/templates/generic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/templates/generic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/templates/multidb/README b/templates/multidb/README new file mode 100644 index 00000000..5db219f9 --- /dev/null +++ b/templates/multidb/README @@ -0,0 +1 @@ +Rudimentary multi-database configuration. \ No newline at end of file diff --git a/templates/pylons/README b/templates/pylons/README new file mode 100644 index 00000000..ed3c28ee --- /dev/null +++ b/templates/pylons/README @@ -0,0 +1 @@ +Configuration that reads from a Pylons project environment. \ No newline at end of file -- 2.47.2