From: Mike Bayer Date: Fri, 25 Feb 2011 20:53:29 +0000 (-0500) Subject: - migrate to ArgParse X-Git-Tag: rel_0_1_0~86 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7622c2bfc0e4a4f6f2c29add7b729d14638258b3;p=thirdparty%2Fsqlalchemy%2Falembic.git - migrate to ArgParse - long lines/whitespace - fix "list_templates" - will support py3k immediately --- diff --git a/alembic/command.py b/alembic/command.py index c1147593..b2352d6c 100644 --- a/alembic/command.py +++ b/alembic/command.py @@ -13,7 +13,7 @@ def list_templates(config): tempname, 'README') synopsis = open(readme).next() - print util.format_opt(tempname, synopsis) + print "%s - %s" % (tempname, synopsis) print "\nTemplates are used via the 'init' command, e.g.:" print "\n alembic init --template pylons ./scripts" diff --git a/alembic/config.py b/alembic/config.py index 1cbe1f0a..e713b047 100644 --- a/alembic/config.py +++ b/alembic/config.py @@ -1,9 +1,8 @@ from alembic import command, util -from optparse import OptionParser +from argparse import ArgumentParser import ConfigParser import inspect import os -import sys class Config(object): def __init__(self, file_): @@ -34,13 +33,36 @@ class Config(object): def main(argv): - # TODO: - # OK, what's the super option parser library that - # allows plus command-specfic sub-options, - # and derives everything from callables ? - # we're inventing here a bit. + def add_options(parser, positional, kwargs): + parser.add_argument("-c", "--config", + type=str, + default="alembic.ini", + help="Alternate config file") + if 'template' in kwargs: + parser.add_argument("-t", "--template", + default='generic', + type=str, + help="Setup template for use with 'init'") + if 'message' in kwargs: + parser.add_argument("-m", "--message", + type=str, + help="Message string to use with 'revision'") + if 'sql' in kwargs: + parser.add_argument("--sql", + action="store_true", + help="Don't emit SQL to database - dump to " + "standard output instead") + + positional_help = { + 'directory':"location of scripts directory", + 'revision':"revision identifier" + } + for arg in positional: + subparser.add_argument(arg, help=positional_help.get(arg)) + + parser = ArgumentParser() + subparsers = parser.add_subparsers() - commands = {} for fn in [getattr(command, n) for n in dir(command)]: if inspect.isfunction(fn) and \ fn.__name__[0] != '_' and \ @@ -54,74 +76,21 @@ def main(argv): positional = spec[0][1:] kwarg = [] - commands[fn.__name__] = { - 'name':fn.__name__, - 'fn':fn, - 'positional':positional, - 'kwargs':kwarg - } + subparser = subparsers.add_parser( + fn.__name__, + help=fn.__doc__) + add_options(subparser, positional, kwarg) + subparser.set_defaults(cmd=(fn, positional, kwarg)) - def format_cmd(cmd): - return "%s %s" % ( - cmd['name'], - " ".join(["<%s>" % p for p in cmd['positional']]) - ) + options = parser.parse_args() - def format_opt(cmd, padding=32): - opt = format_cmd(cmd) - return " " + opt + \ - ((padding - len(opt)) * " ") + cmd['fn'].__doc__ - - parser = OptionParser( - "usage: %prog [options] [command arguments]\n\n" - "Available Commands:\n" + - "\n".join(sorted([ - format_opt(cmd) - for cmd in commands.values() - ])) + - "\n\n is a hex revision id, 'head' or 'base'." - ) - - 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("-m", "--message", - type="string", - help="Message string to use with 'revision'") - parser.add_option("--sql", - action="store_true", - help="Dump output to a SQL file") - - cmd_line_options, cmd_line_args = parser.parse_args(argv[1:]) - - if len(cmd_line_args) < 1: - util.err("no command specified") - - cmd = cmd_line_args.pop(0).replace('-', '_') + fn, positional, kwarg = options.cmd + cfg = Config(options.config) try: - cmd_fn = commands[cmd] - except KeyError: - util.err("no such command %r" % cmd) - - kw = dict( - (k, getattr(cmd_line_options, k)) - for k in cmd_fn['kwargs'] - ) - - if len(cmd_line_args) != len(cmd_fn['positional']): - util.err("Usage: %s %s [options]" % ( - os.path.basename(argv[0]), - format_cmd(cmd_fn) - )) - - cfg = Config(cmd_line_options.config) - try: - cmd_fn['fn'](cfg, *cmd_line_args, **kw) + fn(cfg, + *[getattr(options, k) for k in positional], + **dict((k, getattr(options, k)) for k in kwarg) + ) except util.CommandError, e: util.err(str(e)) diff --git a/alembic/context.py b/alembic/context.py index 711d1cd4..ae1b3f09 100644 --- a/alembic/context.py +++ b/alembic/context.py @@ -1,5 +1,6 @@ from alembic import util -from sqlalchemy import MetaData, Table, Column, String, literal_column, text +from sqlalchemy import MetaData, Table, Column, String, literal_column, \ + text from sqlalchemy.schema import CreateTable import logging @@ -33,7 +34,8 @@ class DefaultContext(object): def _current_rev(self): if self.as_sql: - if not self.connection.dialect.has_table(self.connection, 'alembic_version'): + if not self.connection.dialect.has_table(self.connection, + 'alembic_version'): self._exec(CreateTable(_version)) return None else: @@ -47,9 +49,13 @@ class DefaultContext(object): if new is None: self._exec(_version.delete()) elif old is None: - self._exec(_version.insert().values(version_num=literal_column("'%s'" % new))) + self._exec(_version.insert(). + values(version_num=literal_column("'%s'" % new)) + ) else: - self._exec(_version.update().values(version_num=literal_column("'%s'" % new))) + self._exec(_version.update(). + values(version_num=literal_column("'%s'" % new)) + ) def run_migrations(self, **kw): log.info("Context class %s.", self.__class__.__name__) @@ -78,7 +84,9 @@ class DefaultContext(object): if isinstance(construct, basestring): construct = text(construct) if self.as_sql: - print unicode(construct.compile(dialect=self.connection.dialect)).replace("\t", " ") + ";" + print unicode( + construct.compile(dialect=self.connection.dialect) + ).replace("\t", " ") + ";" else: self.connection.execute(construct) @@ -95,7 +103,9 @@ class DefaultContext(object): if nullable is not util.NO_VALUE: self._exec(base.ColumnNullable(table_name, column_name, nullable)) if server_default is not util.NO_VALUE: - self._exec(base.ColumnDefault(table_name, column_name, server_default)) + self._exec(base.ColumnDefault( + table_name, column_name, server_default + )) # ... etc @@ -110,7 +120,9 @@ def opts(cfg, **kw): def configure_connection(connection): global _context from alembic.ddl import base - _context = _context_impls.get(connection.dialect.name, DefaultContext)(connection, **_context_opts) + _context = _context_impls.get( + connection.dialect.name, + DefaultContext)(connection, **_context_opts) def run_migrations(**kw): _context.run_migrations(**kw) diff --git a/alembic/op.py b/alembic/op.py index c4682b62..e402f119 100644 --- a/alembic/op.py +++ b/alembic/op.py @@ -76,7 +76,8 @@ def _ensure_table_for_fk(metadata, fk): def create_foreign_key(name, source, referent, local_cols, remote_cols): get_context().add_constraint( - _foreign_key_constraint(source, referent, local_cols, remote_cols) + _foreign_key_constraint(source, referent, + local_cols, remote_cols) ) def create_unique_constraint(name, source, local_cols): diff --git a/alembic/script.py b/alembic/script.py index 888d4cae..b91a36b2 100644 --- a/alembic/script.py +++ b/alembic/script.py @@ -86,7 +86,8 @@ class ScriptDirectory(object): if script is None: continue if script.revision in map_: - util.warn("Revision %s is present more than once" % script.revision) + util.warn("Revision %s is present more than once" % + script.revision) map_[script.revision] = script for rev in map_.values(): if rev.down_revision is None: @@ -112,7 +113,8 @@ class ScriptDirectory(object): script = Script.from_path(path) old = self._revision_map[script.revision] if old.down_revision != script.down_revision: - raise Exception("Can't change down_revision on a refresh operation.") + raise Exception("Can't change down_revision " + "on a refresh operation.") self._revision_map[script.revision] = script script.nextrev = old.nextrev @@ -167,7 +169,8 @@ class ScriptDirectory(object): script = Script.from_path(path) self._revision_map[script.revision] = script if script.down_revision: - self._revision_map[script.down_revision].add_nextrev(script.revision) + self._revision_map[script.down_revision].\ + add_nextrev(script.revision) return script class Script(object): diff --git a/alembic/util.py b/alembic/util.py index f333bf16..59dee9c5 100644 --- a/alembic/util.py +++ b/alembic/util.py @@ -74,6 +74,7 @@ def rev_id(): class memoized_property(object): """A read-only @property that is only evaluated once.""" + def __init__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ diff --git a/setup.py b/setup.py index 2462ba39..13afc53a 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,12 @@ from setuptools import setup, find_packages import os import re +extra = {} +if sys.version_info >= (3, 0): + extra.update( + use_2to3=True, + ) + v = open(os.path.join(os.path.dirname(__file__), 'alembic', '__init__.py')) VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v.read()).group(1) v.close() @@ -32,23 +38,23 @@ Key goals of Alembic are: names plus flags. * Transparent and explicit declaration of all configuration as well as the engine/transactional environment in which - migrations run, based on templates which generate the + migrations run, starting with templates which generate the migration environment. The environment can be modified to suit the specifics of the use case. * Support for multiple-database configurations, including migrations that are run for all / some connections. * Support for running migrations transactionally for "transactional DDL" backends, which include Postgresql and - SQLite. + Microsoft SQL Server. * Allowing any series of migrations to be generated as SQL - scripts. + scripts to standard out, instead of emitting to the database. * Support for branched series of migrations, including the ability to view branches and "splice" them together. * The ability to "prune" old migration scripts, setting the "root" of the system to a newer file. * The ability to integrate configuration with other frameworks. - A Pylons template is included which pulls all configuration - from the Pylons project environment. + A sample Pylons template is included which pulls all + configuration from the Pylons project environment. """, classifiers=[ @@ -56,6 +62,7 @@ Key goals of Alembic are: 'Environment :: Console', 'Intended Audience :: Developers', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Topic :: Database :: Front-Ends', ], keywords='SQLAlchemy migrations', @@ -75,4 +82,5 @@ Key goals of Alembic are: ], entry_points=""" """, + **extra ) diff --git a/templates/generic/env.py b/templates/generic/env.py index 60073764..347356e7 100644 --- a/templates/generic/env.py +++ b/templates/generic/env.py @@ -5,7 +5,8 @@ config = context.config fileConfig(config.config_file_name) -engine = engine_from_config(config.get_section('alembic'), prefix='sqlalchemy.') +engine = engine_from_config( + config.get_section('alembic'), prefix='sqlalchemy.') connection = engine.connect() context.configure_connection(connection) diff --git a/templates/multidb/env.py b/templates/multidb/env.py index bde257d5..892b5806 100644 --- a/templates/multidb/env.py +++ b/templates/multidb/env.py @@ -13,14 +13,15 @@ engines = {} for name in re.split(r',\s*', db_names): engines[name] = rec = {} rec['engine'] = engine = \ - engine_from_config(options.get_section(name), prefix='sqlalchemy.') + engine_from_config(options.get_section(name), + prefix='sqlalchemy.') rec['connection'] = conn = engine.connect() - + if USE_TWOPHASE: rec['transaction'] = conn.begin_twophase() else: rec['transaction'] = conn.begin() - + try: for name, rec in engines.items(): context.configure_connection(rec['connection']) @@ -29,7 +30,7 @@ try: if USE_TWOPHASE: for rec in engines.values(): rec['transaction'].prepare() - + for rec in engines.values(): rec['transaction'].commit() except: diff --git a/tests/__init__.py b/tests/__init__.py index 60869318..7c97f2a6 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -18,7 +18,8 @@ def _get_dialect(name): def assert_compiled(element, assert_string, dialect=None): dialect = _get_dialect(dialect) eq_( - unicode(element.compile(dialect=dialect)).replace("\n", "").replace("\t", ""), + unicode(element.compile(dialect=dialect)).\ + replace("\n", "").replace("\t", ""), assert_string.replace("\n", "").replace("\t", "") ) diff --git a/tests/test_ddl.py b/tests/test_ddl.py index 105dc6a3..4e0d19fa 100644 --- a/tests/test_ddl.py +++ b/tests/test_ddl.py @@ -10,7 +10,8 @@ def test_add_column(): "ALTER TABLE footable ADD COLUMN foocol VARCHAR(50) NOT NULL" ) assert_compiled( - AddColumn("footable", Column("foocol", String(50), server_default="12")), + AddColumn("footable", Column("foocol", String(50), + server_default="12")), "ALTER TABLE footable ADD COLUMN foocol VARCHAR(50) DEFAULT '12'" ) @@ -25,4 +26,3 @@ def test_column_nullable(): ColumnNullable("footable", "foocol", False), "ALTER TABLE footable ALTER COLUMN foocol NOT NULL" ) - \ No newline at end of file diff --git a/tests/test_revision_create.py b/tests/test_revision_create.py index 6c8163b3..be1e7819 100644 --- a/tests/test_revision_create.py +++ b/tests/test_revision_create.py @@ -23,7 +23,8 @@ def test_004_rev(): eq_(script.doc, "this is a message") eq_(script.revision, abc) eq_(script.down_revision, None) - assert os.access(os.path.join(env.dir, 'versions', '%s.py' % abc), os.F_OK) + assert os.access( + os.path.join(env.dir, 'versions', '%s.py' % abc), os.F_OK) assert callable(script.module.upgrade) eq_(env._get_heads(), [abc]) diff --git a/tests/test_schema.py b/tests/test_schema.py index f5bcb8e3..53fc6a9f 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -6,10 +6,12 @@ from sqlalchemy.schema import AddConstraint, ForeignKeyConstraint, \ from sqlalchemy import Integer def test_foreign_key(): - fk = op._foreign_key_constraint('fk_test', 't1', 't2', ['foo', 'bar'], ['bat', 'hoho']) + fk = op._foreign_key_constraint('fk_test', 't1', 't2', + ['foo', 'bar'], ['bat', 'hoho']) assert_compiled( AddConstraint(fk), - "ALTER TABLE t1 ADD CONSTRAINT hoho FOREIGN KEY(foo, bar) REFERENCES t2 (bat, hoho)" + "ALTER TABLE t1 ADD CONSTRAINT hoho FOREIGN KEY(foo, bar) " + "REFERENCES t2 (bat, hoho)" ) def test_unique_constraint():