This commit is contained in:
iElectric 2011-04-29 19:27:52 +02:00
commit 86bb3e7e36
11 changed files with 99 additions and 115 deletions

View File

@ -1 +1,2 @@
cb01bf174b05b1590258d6c996b89f60ebd88e5a v0.6
c2526dce0768f11e6bf88afb641a6a9058fa685c v0.6.1

View File

@ -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)
---------------------------

View File

@ -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.

View File

@ -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()

View File

@ -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"""

View File

@ -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 '<ColumnDelta altermetadata=%r, %s>' % (self.alter_metadata,
super(ColumnDelta, self).__repr__())
return '<ColumnDelta altermetadata=%r, %s>' % (
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):

View File

@ -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'))

View File

@ -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 (

View File

@ -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):

View File

@ -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",

View File

@ -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