diff --git a/.hgtags b/.hgtags index f0a7f11..a6e8fed 100644 --- a/.hgtags +++ b/.hgtags @@ -1 +1,2 @@ cb01bf174b05b1590258d6c996b89f60ebd88e5a v0.6 +c2526dce0768f11e6bf88afb641a6a9058fa685c v0.6.1 diff --git a/docs/changelog.rst b/docs/changelog.rst index 5ee078f..399f1b8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,4 +1,7 @@ -0.6.1 (xxxxxxx) +0.6.2 (XXXX-XX-XX) +--------------------------- + +0.6.1 (2011-02-11) --------------------------- Features @@ -7,26 +10,31 @@ Features - implemented columns adding with unique constraints for sqlite - implemented adding unique and foreign key constraints to columns for sqlite - +- remove experimental `alter_metadata` parameter Fixed bugs ****************** - updated tests for Python 2.7 +- repository keyword in :func:`api.version_control` can also be unicode - added if main condition for manage.py script +- make :func:`migrate.changeset.constraint.ForeignKeyConstraint.autoname` + work with SQLAlchemy 0.5 and 0.6 - fixed case sensitivity in setup.py dependencies - moved :mod:`migrate.changeset.exceptions` and :mod:`migrate.versioning.exceptions` to :mod:`migrate.exceptions` -- cleared up test output and improved testing of deprecation warnings. +- cleared up test output and improved testing of deprecation warnings. - some documentation fixes -- fixed bug with column dropping in sqlite (issue 96) +- #107: fixed syntax error in genmodel.py +- #96: fixed bug with column dropping in sqlite +- #94: fixed bug that prevented non-unique indexes being created - fixed bug with column dropping involving foreign keys -- fixed bug that prevented non-unique indexes being created (issue 94) - fixed bug when dropping columns with unique constraints in sqlite - rewrite of the schema diff internals, now supporting column differences in additon to missing columns and tables. - fixed bug when passing empty list in :func:`migrate.versioning.shell.main` failed +- #108: Fixed issues with firebird support. 0.6 (11.07.2010) --------------------------- diff --git a/docs/conf.py b/docs/conf.py index e49fcce..b8346bb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -47,16 +47,16 @@ master_doc = 'index' # General information about the project. project = u'SQLAlchemy Migrate' -copyright = u'2010, Evan Rosson, Jan Dittberner, Domen Kožar' +copyright = u'2011, Evan Rosson, Jan Dittberner, Domen Kožar, Chris Withers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.6' +version = '0.6.2' # The full version, including alpha/beta/rc tags. -release = '0.6' +release = '0.6.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/migrate/changeset/databases/firebird.py b/migrate/changeset/databases/firebird.py index f880035..675666c 100644 --- a/migrate/changeset/databases/firebird.py +++ b/migrate/changeset/databases/firebird.py @@ -2,7 +2,7 @@ Firebird database specific implementations of changeset classes. """ from sqlalchemy.databases import firebird as sa_base - +from sqlalchemy.schema import PrimaryKeyConstraint from migrate import exceptions from migrate.changeset import ansisql, SQLA_06 @@ -27,13 +27,32 @@ class FBColumnDropper(ansisql.ANSIColumnDropper): if column.table.primary_key.columns.contains_column(column): column.table.primary_key.drop() # TODO: recreate primary key if it references more than this column - if column.unique or getattr(column, 'unique_name', None): - for cons in column.table.constraints: - if cons.contains_column(column): - cons.drop() - # TODO: recreate unique constraint if it refenrences more than this column - table = self.start_alter_table(column) + for index in column.table.indexes: + # "column in index.columns" causes problems as all + # column objects compare equal and return a SQL expression + if column.name in [col.name for col in index.columns]: + index.drop() + # TODO: recreate index if it references more than this column + + for cons in column.table.constraints: + if isinstance(cons,PrimaryKeyConstraint): + # will be deleted only when the column its on + # is deleted! + continue + + if SQLA_06: + should_drop = column.name in cons.columns + else: + should_drop = cons.contains_column(column) and cons.name + if should_drop: + self.start_alter_table(column) + self.append("DROP CONSTRAINT ") + self.append(self.preparer.format_constraint(cons)) + self.execute() + # TODO: recreate unique constraint if it refenrences more than this column + + self.start_alter_table(column) self.append('DROP %s' % self.preparer.format_column(column)) self.execute() diff --git a/migrate/changeset/databases/sqlite.py b/migrate/changeset/databases/sqlite.py index 66a8f5a..447412d 100644 --- a/migrate/changeset/databases/sqlite.py +++ b/migrate/changeset/databases/sqlite.py @@ -80,10 +80,17 @@ class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper): """SQLite ColumnDropper""" def _modify_table(self, table, column, delta): + columns = ' ,'.join(map(self.preparer.format_column, table.columns)) return 'INSERT INTO %(table_name)s SELECT ' + columns + \ ' from migration_tmp' + def visit_column(self,column): + # For SQLite, we *have* to remove the column here so the table + # is re-created properly. + column.remove_from_table(column.table,unset_table=False) + super(SQLiteColumnDropper,self).visit_column(column) + class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger): """SQLite SchemaChanger""" diff --git a/migrate/changeset/schema.py b/migrate/changeset/schema.py index ef8dd85..75cd3fd 100644 --- a/migrate/changeset/schema.py +++ b/migrate/changeset/schema.py @@ -29,9 +29,6 @@ __all__ = [ 'ColumnDelta', ] -DEFAULT_ALTER_METADATA = True - - def create_column(column, table=None, *p, **kw): """Create a column, given the table. @@ -109,19 +106,11 @@ def alter_column(*p, **k): The :class:`~sqlalchemy.engine.base.Engine` to use for table reflection and schema alterations. - :param alter_metadata: - If `True`, which is the default, the - :class:`~sqlalchemy.schema.Column` will also modified. - If `False`, the :class:`~sqlalchemy.schema.Column` will be left - as it was. - :returns: A :class:`ColumnDelta` instance representing the change. """ - k.setdefault('alter_metadata', DEFAULT_ALTER_METADATA) - if 'table' not in k and isinstance(p[0], sqlalchemy.Column): k['table'] = p[0].table if 'engine' not in k: @@ -135,6 +124,12 @@ def alter_column(*p, **k): MigrateDeprecationWarning ) engine = k['engine'] + + # enough tests seem to break when metadata is always altered + # that this crutch has to be left in until they can be sorted + # out + k['alter_metadata']=True + delta = ColumnDelta(*p, **k) visitorcallable = get_engine_visitor(engine, 'schemachanger') @@ -188,11 +183,10 @@ class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem): :param table: Table at which current Column should be bound to.\ If table name is given, reflection will be used. :type table: string or Table instance - :param alter_metadata: If True, it will apply changes to metadata. - :type alter_metadata: bool - :param metadata: If `alter_metadata` is true, \ - metadata is used to reflect table names into - :type metadata: :class:`MetaData` instance + + :param metadata: A :class:`MetaData` instance to store + reflected table names + :param engine: When reflecting tables, either engine or metadata must \ be specified to acquire engine object. :type engine: :class:`Engine` instance @@ -213,7 +207,11 @@ class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem): __visit_name__ = 'column' def __init__(self, *p, **kw): + # 'alter_metadata' is not a public api. It exists purely + # as a crutch until the tests that fail when 'alter_metadata' + # behaviour always happens can be sorted out self.alter_metadata = kw.pop("alter_metadata", False) + self.meta = kw.pop("metadata", None) self.engine = kw.pop("engine", None) @@ -237,9 +235,11 @@ class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem): self.apply_diffs(diffs) def __repr__(self): - return '' % (self.alter_metadata, - super(ColumnDelta, self).__repr__()) - + return '' % ( + self.alter_metadata, + super(ColumnDelta, self).__repr__() + ) + def __getitem__(self, key): if key not in self.keys(): raise KeyError("No such diff key, available: %s" % self.diffs ) @@ -395,7 +395,6 @@ class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem): self._table = table if not self.alter_metadata: self._table.meta = sqlalchemy.MetaData(bind=self._table.bind) - def _get_result_column(self): return getattr(self, '_result_column', None) @@ -456,22 +455,18 @@ class ChangesetTable(object): :param name: New name of the table. :type name: string - :param alter_metadata: If True, table will be removed from metadata - :type alter_metadata: bool :param connection: reuse connection istead of creating new one. :type connection: :class:`sqlalchemy.engine.base.Connection` instance """ - self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) engine = self.bind self.new_name = name visitorcallable = get_engine_visitor(engine, 'schemachanger') run_single_visitor(engine, visitorcallable, self, connection, **kwargs) # Fix metadata registration - if self.alter_metadata: - self.name = name - self.deregister() - self._set_parent(self.metadata) + self.name = name + self.deregister() + self._set_parent(self.metadata) def _meta_key(self): return sqlalchemy.schema._get_table_key(self.name, self.schema) @@ -510,7 +505,6 @@ class ChangesetColumn(object): `~migrate.changeset.constraint.UniqueConstraint` on this column. :param primary_key_name: Creates :class:\ `~migrate.changeset.constraint.PrimaryKeyConstraint` on this column. - :param alter_metadata: If True, column will be added to table object. :param populate_default: If True, created column will be \ populated with defaults :param connection: reuse connection istead of creating new one. @@ -518,22 +512,19 @@ populated with defaults :type index_name: string :type unique_name: string :type primary_key_name: string - :type alter_metadata: bool :type populate_default: bool :type connection: :class:`sqlalchemy.engine.base.Connection` instance :returns: self """ self.populate_default = populate_default - self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) self.index_name = index_name self.unique_name = unique_name self.primary_key_name = primary_key_name for cons in ('index_name', 'unique_name', 'primary_key_name'): self._check_sanity_constraints(cons) - if self.alter_metadata: - self.add_to_table(table) + self.add_to_table(table) engine = self.table.bind visitorcallable = get_engine_visitor(engine, 'columngenerator') engine._run_visitor(visitorcallable, self, connection, **kwargs) @@ -550,21 +541,16 @@ populated with defaults ``ALTER TABLE DROP COLUMN``, for most databases. - :param alter_metadata: If True, column will be removed from table object. - :type alter_metadata: bool :param connection: reuse connection istead of creating new one. :type connection: :class:`sqlalchemy.engine.base.Connection` instance """ - self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) if table is not None: self.table = table engine = self.table.bind - if self.alter_metadata: - self.remove_from_table(self.table, unset_table=False) visitorcallable = get_engine_visitor(engine, 'columndropper') engine._run_visitor(visitorcallable, self, connection, **kwargs) - if self.alter_metadata: - self.table = None + self.remove_from_table(self.table, unset_table=False) + self.table = None return self def add_to_table(self, table): @@ -643,18 +629,14 @@ class ChangesetIndex(object): :param name: New name of the Index. :type name: string - :param alter_metadata: If True, Index object will be altered. - :type alter_metadata: bool :param connection: reuse connection istead of creating new one. :type connection: :class:`sqlalchemy.engine.base.Connection` instance """ - self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) engine = self.table.bind self.new_name = name visitorcallable = get_engine_visitor(engine, 'schemachanger') engine._run_visitor(visitorcallable, self, connection, **kwargs) - if self.alter_metadata: - self.name = name + self.name = name class ChangesetDefaultClause(object): diff --git a/migrate/tests/changeset/test_changeset.py b/migrate/tests/changeset/test_changeset.py index 5c06ef2..2b50922 100644 --- a/migrate/tests/changeset/test_changeset.py +++ b/migrate/tests/changeset/test_changeset.py @@ -262,7 +262,6 @@ class TestAddDropColumn(fixture.DB): self._check_index(False) - Index('ix_data', col).drop(bind=self.engine) col.drop() @fixture.usedb() @@ -284,7 +283,6 @@ class TestAddDropColumn(fixture.DB): self._check_index(True) - Index('ix_data', col).drop(bind=self.engine) col.drop() @fixture.usedb() @@ -424,7 +422,8 @@ class TestAddDropColumn(fixture.DB): Column('r1', Integer), Column('r2', Integer), ForeignKeyConstraint(['r1','r2'], - [reftable.c.id,reftable.c.jd]) + [reftable.c.id,reftable.c.jd], + name='test_fk') ) self.table.create() @@ -705,20 +704,6 @@ class TestColumnChange(fixture.DB): finally: cw.__exit__() - @fixture.usedb() - def test_alter_metadata(self): - """Test if alter_metadata is respected""" - - self.table.c.data.alter(type=String(100)) - - self.assert_(isinstance(self.table.c.data.type, String)) - self.assertEqual(self.table.c.data.type.length, 100) - - # nothing should change - self.table.c.data.alter(type=String(200),alter_metadata=False) - self.assert_(isinstance(self.table.c.data.type, String)) - self.assertEqual(self.table.c.data.type.length, 100) - @fixture.usedb() def test_alter_returns_delta(self): """Test if alter constructs return delta""" @@ -742,8 +727,7 @@ class TestColumnChange(fixture.DB): kw = dict(nullable=False, server_default='foobar', name='data_new', - type=String(50), - alter_metadata=True) + type=String(50)) if self.engine.name == 'firebird': del kw['nullable'] self.table.c.data.alter(**kw) @@ -840,23 +824,17 @@ class TestColumnDelta(fixture.DB): self.verify([], self.mkcol(server_default='foobar'), self.mkcol('id', String, DefaultClause('foobar'))) self.verify(['type'], self.mkcol(server_default='foobar'), self.mkcol('id', Text, DefaultClause('foobar'))) - # test alter_metadata col = self.mkcol(server_default='foobar') self.verify(['type'], col, self.mkcol('id', Text, DefaultClause('foobar')), alter_metadata=True) self.assert_(isinstance(col.type, Text)) col = self.mkcol() - self.verify(['name', 'server_default', 'type'], col, self.mkcol('beep', Text, DefaultClause('foobar')), alter_metadata=True) + self.verify(['name', 'server_default', 'type'], col, self.mkcol('beep', Text, DefaultClause('foobar')), + alter_metadata=True) self.assert_(isinstance(col.type, Text)) self.assertEqual(col.name, 'beep') self.assertEqual(col.server_default.arg, 'foobar') - col = self.mkcol() - self.verify(['name', 'server_default', 'type'], col, self.mkcol('beep', Text, DefaultClause('foobar')), alter_metadata=False) - self.assertFalse(isinstance(col.type, Text)) - self.assertNotEqual(col.name, 'beep') - self.assertFalse(col.server_default) - @fixture.usedb() def test_deltas_zero_columns(self): """Testing ColumnDelta with zero columns""" @@ -867,24 +845,18 @@ class TestColumnDelta(fixture.DB): self.verify(['type'], 'ids', table=self.table.name, type=String(80), engine=self.engine) self.verify(['type'], 'ids', table=self.table.name, type=String(80), metadata=self.meta) - # check if alter_metadata is respected self.meta.clear() - delta = self.verify(['type'], 'ids', table=self.table.name, type=String(80), alter_metadata=True, metadata=self.meta) + delta = self.verify(['type'], 'ids', table=self.table.name, type=String(80), metadata=self.meta, + alter_metadata=True) self.assert_(self.table.name in self.meta) self.assertEqual(delta.result_column.type.length, 80) self.assertEqual(self.meta.tables.get(self.table.name).c.ids.type.length, 80) - self.meta.clear() - self.verify(['type'], 'ids', table=self.table.name, type=String(80), alter_metadata=False, engine=self.engine) - self.assert_(self.table.name not in self.meta) - - self.meta.clear() - self.verify(['type'], 'ids', table=self.table.name, type=String(80), alter_metadata=False, metadata=self.meta) - self.assert_(self.table.name not in self.meta) - # test defaults self.meta.clear() - self.verify(['server_default'], 'ids', table=self.table.name, server_default='foobar', alter_metadata=True, metadata=self.meta) + self.verify(['server_default'], 'ids', table=self.table.name, server_default='foobar', + metadata=self.meta, + alter_metadata=True) self.meta.tables.get(self.table.name).c.ids.server_default.arg == 'foobar' # test missing parameters @@ -908,17 +880,11 @@ class TestColumnDelta(fixture.DB): self.assertEquals(delta.get('name'), 'blah') self.assertEquals(delta.current_name, 'id') - # check if alter_metadata is respected col_orig = self.mkcol(primary_key=True) self.verify(['name', 'type'], col_orig, name='id12', type=Text, alter_metadata=True) self.assert_(isinstance(col_orig.type, Text)) self.assertEqual(col_orig.name, 'id12') - col_orig = self.mkcol(primary_key=True) - self.verify(['name', 'type'], col_orig, name='id12', type=Text, alter_metadata=False) - self.assert_(isinstance(col_orig.type, String)) - self.assertEqual(col_orig.name, 'id') - # test server default col_orig = self.mkcol(primary_key=True) delta = self.verify(['server_default'], col_orig, DefaultClause('foobar')) diff --git a/migrate/versioning/genmodel.py b/migrate/versioning/genmodel.py index 5a43437..b3449b6 100644 --- a/migrate/versioning/genmodel.py +++ b/migrate/versioning/genmodel.py @@ -170,11 +170,11 @@ class ModelGenerator(object): modelTable, col.name)) for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl: upgradeCommands.append( - 'assert False, "Can\'t alter columns: %s:%s=>%s"', - modelTable, modelCol.name, databaseCol.name) + 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( + modelTable, modelCol.name, databaseCol.name)) downgradeCommands.append( - 'assert False, "Can\'t alter columns: %s:%s=>%s"', - modelTable, modelCol.name, databaseCol.name) + 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( + modelTable, modelCol.name, databaseCol.name)) pre_command = ' meta.bind = migrate_engine' return ( diff --git a/migrate/versioning/script/py.py b/migrate/versioning/script/py.py index ed5b87e..35fe4aa 100644 --- a/migrate/versioning/script/py.py +++ b/migrate/versioning/script/py.py @@ -4,6 +4,7 @@ import shutil import warnings import logging +import inspect from StringIO import StringIO import migrate @@ -136,12 +137,12 @@ class PythonScript(base.BaseScript): funcname = base.operations[op] script_func = self._func(funcname) - try: - script_func(engine) - except TypeError: - warnings.warn("upgrade/downgrade functions must accept engine" - " parameter (since version > 0.5.4)", MigrateDeprecationWarning) - raise + # check for old way of using engine + if not inspect.getargspec(script_func)[0]: + raise TypeError("upgrade/downgrade functions must accept engine" + " parameter (since version 0.5.4)") + + script_func(engine) @property def module(self): diff --git a/setup.py b/setup.py index c0b6f6b..a2daa11 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ readme_file = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), setup( name = "sqlalchemy-migrate", - version = "0.6.1", + version = "0.6.2", packages = find_packages(exclude=["migrate.tests*"]), include_package_data = True, description = "Database schema migration for SQLAlchemy", diff --git a/test-req.pip b/test-req.pip index 58011eb..46c0196 100644 --- a/test-req.pip +++ b/test-req.pip @@ -9,6 +9,6 @@ pytz http://initd.org/psycopg/tarballs/psycopg2-2.2.2.tar.gz pysqlite mysql-python -http://downloads.sourceforge.net/firebird/kinterbasdb-3.3.0.tar.bz2 +http://jenkins.gnuviech-server.de/userContent/kinterbasdb-3.3.0.tar.bz2 virtualenv unittest2