diff --git a/docs/changeset.rst b/docs/changeset.rst index d0d8a8a..d778879 100644 --- a/docs/changeset.rst +++ b/docs/changeset.rst @@ -25,14 +25,14 @@ Column Given a standard SQLAlchemy table:: - table = Table('mytable',meta, - Column('id',Integer,primary_key=True), + table = Table('mytable', meta, + Column('id', Integer, primary_key=True), ) table.create() Create a column:: - col = Column('col1',String) + col = Column('col1', String) col.create(table) # Column is added to table based on its name @@ -92,7 +92,7 @@ SQLAlchemy supports creating/dropping constraints at the same time a table is cr Primary key constraints:: - cons = PrimaryKeyConstraint(col1,col2) + cons = PrimaryKeyConstraint(col1, col2) # Create the constraint cons.create() # Drop the constraint @@ -100,7 +100,7 @@ Primary key constraints:: Note that Oracle requires that you state the name of the primary key constraint to be created/dropped. SQLAlchemy Migrate will try to guess the name of the PK constraint for other databases, but if it's something other than the default, you'll need to give its name:: - PrimaryKeyConstraint(col1,col2,name='my_pk_constraint') + PrimaryKeyConstraint(col1, col2, name='my_pk_constraint') Foreign key constraints:: @@ -112,4 +112,4 @@ Foreign key constraints:: Names are specified just as with primary key constraints:: - ForeignKeyConstraint([table.c.fkey], [othertable.c.id],name='my_fk_constraint') + ForeignKeyConstraint([table.c.fkey], [othertable.c.id], name='my_fk_constraint') diff --git a/migrate/changeset/ansisql.py b/migrate/changeset/ansisql.py index fea4037..1c8d3f8 100644 --- a/migrate/changeset/ansisql.py +++ b/migrate/changeset/ansisql.py @@ -33,7 +33,7 @@ class RawAlterTableVisitor(object): def _do_quote_table_identifier(self, identifier): """Returns a quoted version of the given table identifier.""" - return '"%s"'%identifier + return '"%s"' % identifier def start_alter_table(self, param): """Returns the start of an ``ALTER TABLE`` SQL-Statement. diff --git a/migrate/changeset/constraint.py b/migrate/changeset/constraint.py index 56a630f..8599877 100644 --- a/migrate/changeset/constraint.py +++ b/migrate/changeset/constraint.py @@ -106,9 +106,7 @@ class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint): def autoname(self): """Mimic the database's automatic constraint names""" - ret = "%(table)s_pkey"%dict( - table=self.table.name, - ) + ret = "%(table)s_pkey" % dict(table=self.table.name) return ret def drop(self, *args, **kwargs): diff --git a/migrate/changeset/schema.py b/migrate/changeset/schema.py index 6c1cdf7..1f165e8 100644 --- a/migrate/changeset/schema.py +++ b/migrate/changeset/schema.py @@ -2,7 +2,9 @@ Schema module providing common schema operations. """ import re + import sqlalchemy + from migrate.changeset.databases.visitor import get_engine_visitor __all__ = [ @@ -15,37 +17,19 @@ __all__ = [ def create_column(column, table=None, *p, **k): + """Create a column, given the table""" if table is not None: return table.create_column(column, *p, **k) return column.create(*p, **k) def drop_column(column, table=None, *p, **k): + """Drop a column, given the table""" if table is not None: return table.drop_column(column, *p, **k) return column.drop(*p, **k) -def _to_table(table, engine=None): - if isinstance(table, sqlalchemy.Table): - return table - # Given: table name, maybe an engine - meta = sqlalchemy.MetaData() - if engine is not None: - meta.bind = engine - return sqlalchemy.Table(table, meta) - - -def _to_index(index, table=None, engine=None): - if isinstance(index, sqlalchemy.Index): - return index - # Given: index name; table name required - table = _to_table(table, engine) - ret = sqlalchemy.Index(index) - ret.table = table - return ret - - def rename_table(table, name, engine=None): """Rename a table, given the table's current name and the new name.""" @@ -63,16 +47,6 @@ def rename_index(index, name, table=None, engine=None): index = _to_index(index, table, engine) index.rename(name) - -def _engine_run_visitor(engine, visitorcallable, element, **kwargs): - conn = engine.connect() - try: - element.accept_schema_visitor(visitorcallable(engine.dialect, - connection=conn)) - finally: - conn.close() - - def alter_column(*p, **k): """Alter a column. @@ -114,6 +88,35 @@ def alter_column(*p, **k): setattr(col, key, val) +def _to_table(table, engine=None): + if isinstance(table, sqlalchemy.Table): + return table + # Given: table name, maybe an engine + meta = sqlalchemy.MetaData() + if engine is not None: + meta.bind = engine + return sqlalchemy.Table(table, meta) + + +def _to_index(index, table=None, engine=None): + if isinstance(index, sqlalchemy.Index): + return index + # Given: index name; table name required + table = _to_table(table, engine) + ret = sqlalchemy.Index(index) + ret.table = table + return ret + + +def _engine_run_visitor(engine, visitorcallable, element, **kwargs): + conn = engine.connect() + try: + element.accept_schema_visitor(visitorcallable(engine.dialect, + connection=conn)) + finally: + conn.close() + + def _normalize_table(column, table): if table is not None: if table is not column.table: diff --git a/migrate/versioning/api.py b/migrate/versioning/api.py index 48a9dd3..d72d14e 100644 --- a/migrate/versioning/api.py +++ b/migrate/versioning/api.py @@ -87,7 +87,7 @@ def create(repository, name, **opts): @catch_known_errors def script(description, repository, **opts): - """%prog script [--repository=REPOSITORY_PATH] DESCRIPTION + """%prog script DESCRIPTION REPOSITORY_PATH Create an empty change script using the next unused version number appended with the given description. @@ -101,7 +101,7 @@ def script(description, repository, **opts): @catch_known_errors def script_sql(database, repository, **opts): - """%prog script_sql [--repository=REPOSITORY_PATH] DATABASE + """%prog script_sql DATABASE REPOSITORY_PATH Create empty change SQL scripts for given DATABASE, where DATABASE is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.) @@ -115,29 +115,6 @@ def script_sql(database, repository, **opts): repo.create_script_sql(database, **opts) -def test(repository, url=None, **opts): - """%prog test REPOSITORY_PATH URL [VERSION] - - Performs the upgrade and downgrade option on the given - database. This is not a real test and may leave the database in a - bad state. You should therefore better run the test on a copy of - your database. - """ - engine = construct_engine(url, **opts) - repos = Repository(repository) - script = repos.version(None).script() - - # Upgrade - print "Upgrading...", - script.run(engine, 1) - print "done" - - print "Downgrading...", - script.run(engine, -1) - print "done" - print "Success" - - def version(repository, **opts): """%prog version REPOSITORY_PATH @@ -147,6 +124,20 @@ def version(repository, **opts): return repo.latest +def db_version(url, repository, **opts): + """%prog db_version URL REPOSITORY_PATH + + Show the current version of the repository with the given + connection string, under version control of the specified + repository. + + The url should be any valid SQLAlchemy connection string. + """ + engine = construct_engine(url, **opts) + schema = ControlledSchema(engine, repository) + return schema.version + + def source(version, dest=None, repository=None, **opts): """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH @@ -166,43 +157,6 @@ def source(version, dest=None, repository=None, **opts): return ret -def version_control(url, repository, version=None, **opts): - """%prog version_control URL REPOSITORY_PATH [VERSION] - - Mark a database as under this repository's version control. - - Once a database is under version control, schema changes should - only be done via change scripts in this repository. - - This creates the table version_table in the database. - - The url should be any valid SQLAlchemy connection string. - - By default, the database begins at version 0 and is assumed to be - empty. If the database is not empty, you may specify a version at - which to begin instead. No attempt is made to verify this - version's correctness - the database schema is expected to be - identical to what it would be if the database were created from - scratch. - """ - engine = construct_engine(url, **opts) - ControlledSchema.create(engine, repository, version) - - -def db_version(url, repository, **opts): - """%prog db_version URL REPOSITORY_PATH - - Show the current version of the repository with the given - connection string, under version control of the specified - repository. - - The url should be any valid SQLAlchemy connection string. - """ - engine = construct_engine(url, **opts) - schema = ControlledSchema(engine, repository) - return schema.version - - def upgrade(url, repository, version=None, **opts): """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql] @@ -236,6 +190,51 @@ def downgrade(url, repository, version, **opts): "Try 'upgrade' instead." return _migrate(url, repository, version, upgrade=False, err=err, **opts) +def test(repository, url, **opts): + """%prog test REPOSITORY_PATH URL [VERSION] + + Performs the upgrade and downgrade option on the given + database. This is not a real test and may leave the database in a + bad state. You should therefore better run the test on a copy of + your database. + """ + engine = construct_engine(url, **opts) + repos = Repository(repository) + script = repos.version(None).script() + + # Upgrade + print "Upgrading...", + script.run(engine, 1) + print "done" + + print "Downgrading...", + script.run(engine, -1) + print "done" + print "Success" + + +def version_control(url, repository, version=None, **opts): + """%prog version_control URL REPOSITORY_PATH [VERSION] + + Mark a database as under this repository's version control. + + Once a database is under version control, schema changes should + only be done via change scripts in this repository. + + This creates the table version_table in the database. + + The url should be any valid SQLAlchemy connection string. + + By default, the database begins at version 0 and is assumed to be + empty. If the database is not empty, you may specify a version at + which to begin instead. No attempt is made to verify this + version's correctness - the database schema is expected to be + identical to what it would be if the database were created from + scratch. + """ + engine = construct_engine(url, **opts) + ControlledSchema.create(engine, repository, version) + def drop_version_control(url, repository, **opts): """%prog drop_version_control URL REPOSITORY_PATH @@ -279,7 +278,7 @@ def compare_model_to_db(url, model, repository, **opts): def create_model(url, repository, **opts): - """%prog create_model URL REPOSITORY_PATH + """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True] Dump the current database as a Python model to stdout. diff --git a/migrate/versioning/script/py.py b/migrate/versioning/script/py.py index e942f26..5846c04 100644 --- a/migrate/versioning/script/py.py +++ b/migrate/versioning/script/py.py @@ -11,6 +11,7 @@ from migrate.versioning.template import template from migrate.versioning.script import base from migrate.versioning.util import import_path, load_model, construct_engine + class PythonScript(base.BaseScript): """Base for Python scripts""" diff --git a/test/versioning/test_schema.py b/test/versioning/test_schema.py index 81fbc69..58e1632 100644 --- a/test/versioning/test_schema.py +++ b/test/versioning/test_schema.py @@ -151,6 +151,9 @@ class TestControlledSchema(fixture.Pathed, fixture.DB): dbschema.upgrade(10) + self.assertRaises(ValueError, dbschema.upgrade, 'a') + self.assertRaises(exceptions.InvalidVersionError, dbschema.runchange, 20, '', 1) + # TODO: test for table version in db # cleanup