Fix and test issue 118. Clarify genmodel transformations.
This commit is contained in:
parent
5cfc74959f
commit
3fe1b4a3be
@ -35,7 +35,7 @@ class TestSchemaDiff(fixture.DB):
|
|||||||
|
|
||||||
def _applyLatestModel(self):
|
def _applyLatestModel(self):
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
||||||
genmodel.ModelGenerator(diff,self.engine).applyModel()
|
genmodel.ModelGenerator(diff,self.engine).runB2A()
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_functional(self):
|
def test_functional(self):
|
||||||
@ -57,30 +57,44 @@ class TestSchemaDiff(fixture.DB):
|
|||||||
|
|
||||||
# Check Python upgrade and downgrade of database from updated model.
|
# Check Python upgrade and downgrade of database from updated model.
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
||||||
decls, upgradeCommands, downgradeCommands = genmodel.ModelGenerator(diff,self.engine).toUpgradeDowngradePython()
|
decls, upgradeCommands, downgradeCommands = genmodel.ModelGenerator(diff,self.engine).genB2AMigration()
|
||||||
self.assertEqualsIgnoreWhitespace(decls, '''
|
|
||||||
from migrate.changeset import schema
|
# Feature test for a recent SQLa feature;
|
||||||
meta = MetaData()
|
# expect different output in that case.
|
||||||
tmp_schemadiff = Table('tmp_schemadiff', meta,
|
if repr(String()) == 'String()':
|
||||||
Column('id', Integer(), primary_key=True, nullable=False),
|
self.assertEqualsIgnoreWhitespace(decls, '''
|
||||||
Column('name', UnicodeText(length=None)),
|
from migrate.changeset import schema
|
||||||
Column('data', UnicodeText(length=None)),
|
meta = MetaData()
|
||||||
)
|
tmp_schemadiff = Table('tmp_schemadiff', meta,
|
||||||
''')
|
Column('id', Integer, primary_key=True, nullable=False),
|
||||||
|
Column('name', UnicodeText),
|
||||||
|
Column('data', UnicodeText),
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
else:
|
||||||
|
self.assertEqualsIgnoreWhitespace(decls, '''
|
||||||
|
from migrate.changeset import schema
|
||||||
|
meta = MetaData()
|
||||||
|
tmp_schemadiff = Table('tmp_schemadiff', meta,
|
||||||
|
Column('id', Integer(), primary_key=True, nullable=False),
|
||||||
|
Column('name', UnicodeText(length=None)),
|
||||||
|
Column('data', UnicodeText(length=None)),
|
||||||
|
)
|
||||||
|
''')
|
||||||
self.assertEqualsIgnoreWhitespace(upgradeCommands,
|
self.assertEqualsIgnoreWhitespace(upgradeCommands,
|
||||||
'''meta.bind = migrate_engine
|
'''meta.bind = migrate_engine
|
||||||
tmp_schemadiff.create()''')
|
tmp_schemadiff.create()''')
|
||||||
self.assertEqualsIgnoreWhitespace(downgradeCommands,
|
self.assertEqualsIgnoreWhitespace(downgradeCommands,
|
||||||
'''meta.bind = migrate_engine
|
'''meta.bind = migrate_engine
|
||||||
tmp_schemadiff.drop()''')
|
tmp_schemadiff.drop()''')
|
||||||
|
|
||||||
# Create table in database, now model should match database.
|
# Create table in database, now model should match database.
|
||||||
self._applyLatestModel()
|
self._applyLatestModel()
|
||||||
assertDiff(False, [], [], [])
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
# Check Python code gen from database.
|
# Check Python code gen from database.
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(MetaData(), self.engine, excludeTables=['migrate_version'])
|
diff = schemadiff.getDiffOfModelAgainstDatabase(MetaData(), self.engine, excludeTables=['migrate_version'])
|
||||||
src = genmodel.ModelGenerator(diff,self.engine).toPython()
|
src = genmodel.ModelGenerator(diff,self.engine).genBDefinition()
|
||||||
|
|
||||||
exec src in locals()
|
exec src in locals()
|
||||||
|
|
||||||
@ -105,25 +119,25 @@ class TestSchemaDiff(fixture.DB):
|
|||||||
Column('data2',Integer(),nullable=True),
|
Column('data2',Integer(),nullable=True),
|
||||||
)
|
)
|
||||||
assertDiff(True, [], [], [self.table_name])
|
assertDiff(True, [], [], [self.table_name])
|
||||||
|
|
||||||
# Apply latest model changes and find no more diffs.
|
# Apply latest model changes and find no more diffs.
|
||||||
self._applyLatestModel()
|
self._applyLatestModel()
|
||||||
assertDiff(False, [], [], [])
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
if not self.engine.name == 'oracle':
|
if not self.engine.name == 'oracle':
|
||||||
# Make sure data is still present.
|
# Make sure data is still present.
|
||||||
result = self.engine.execute(self.table.select(self.table.c.id==dataId))
|
result = self.engine.execute(self.table.select(self.table.c.id==dataId))
|
||||||
rows = result.fetchall()
|
rows = result.fetchall()
|
||||||
eq_(len(rows), 1)
|
eq_(len(rows), 1)
|
||||||
eq_(rows[0].name, 'mydata')
|
eq_(rows[0].name, 'mydata')
|
||||||
|
|
||||||
# Add data, later we'll make sure it's still present.
|
# Add data, later we'll make sure it's still present.
|
||||||
result = self.engine.execute(self.table.insert(), id=2, name=u'mydata2', data2=123)
|
result = self.engine.execute(self.table.insert(), id=2, name=u'mydata2', data2=123)
|
||||||
if SQLA_06:
|
if SQLA_06:
|
||||||
dataId2 = result.inserted_primary_key[0]
|
dataId2 = result.inserted_primary_key[0]
|
||||||
else:
|
else:
|
||||||
dataId2 = result.last_inserted_ids()[0]
|
dataId2 = result.last_inserted_ids()[0]
|
||||||
|
|
||||||
# Change column type in model.
|
# Change column type in model.
|
||||||
self.meta.remove(self.table)
|
self.meta.remove(self.table)
|
||||||
self.table = Table(self.table_name,self.meta,
|
self.table = Table(self.table_name,self.meta,
|
||||||
@ -134,13 +148,13 @@ class TestSchemaDiff(fixture.DB):
|
|||||||
|
|
||||||
# XXX test type diff
|
# XXX test type diff
|
||||||
return
|
return
|
||||||
|
|
||||||
assertDiff(True, [], [], [self.table_name])
|
assertDiff(True, [], [], [self.table_name])
|
||||||
|
|
||||||
# Apply latest model changes and find no more diffs.
|
# Apply latest model changes and find no more diffs.
|
||||||
self._applyLatestModel()
|
self._applyLatestModel()
|
||||||
assertDiff(False, [], [], [])
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
if not self.engine.name == 'oracle':
|
if not self.engine.name == 'oracle':
|
||||||
# Make sure data is still present.
|
# Make sure data is still present.
|
||||||
result = self.engine.execute(self.table.select(self.table.c.id==dataId2))
|
result = self.engine.execute(self.table.select(self.table.c.id==dataId2))
|
||||||
@ -148,11 +162,11 @@ class TestSchemaDiff(fixture.DB):
|
|||||||
self.assertEquals(len(rows), 1)
|
self.assertEquals(len(rows), 1)
|
||||||
self.assertEquals(rows[0].name, 'mydata2')
|
self.assertEquals(rows[0].name, 'mydata2')
|
||||||
self.assertEquals(rows[0].data2, '123')
|
self.assertEquals(rows[0].data2, '123')
|
||||||
|
|
||||||
# Delete data, since we're about to make a required column.
|
# Delete data, since we're about to make a required column.
|
||||||
# Not even using sqlalchemy.PassiveDefault helps because we're doing explicit column select.
|
# Not even using sqlalchemy.PassiveDefault helps because we're doing explicit column select.
|
||||||
self.engine.execute(self.table.delete(), id=dataId)
|
self.engine.execute(self.table.delete(), id=dataId)
|
||||||
|
|
||||||
if not self.engine.name == 'firebird':
|
if not self.engine.name == 'firebird':
|
||||||
# Change column nullable in model.
|
# Change column nullable in model.
|
||||||
self.meta.remove(self.table)
|
self.meta.remove(self.table)
|
||||||
@ -162,11 +176,11 @@ class TestSchemaDiff(fixture.DB):
|
|||||||
Column('data2',String(255),nullable=False),
|
Column('data2',String(255),nullable=False),
|
||||||
)
|
)
|
||||||
assertDiff(True, [], [], [self.table_name]) # TODO test nullable diff
|
assertDiff(True, [], [], [self.table_name]) # TODO test nullable diff
|
||||||
|
|
||||||
# Apply latest model changes and find no more diffs.
|
# Apply latest model changes and find no more diffs.
|
||||||
self._applyLatestModel()
|
self._applyLatestModel()
|
||||||
assertDiff(False, [], [], [])
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
# Remove table from model.
|
# Remove table from model.
|
||||||
self.meta.remove(self.table)
|
self.meta.remove(self.table)
|
||||||
assertDiff(True, [], [self.table_name], [])
|
assertDiff(True, [], [self.table_name], [])
|
||||||
|
@ -161,23 +161,44 @@ def upgrade(migrate_engine):
|
|||||||
self.assertTrue('User.create()' in source_script)
|
self.assertTrue('User.create()' in source_script)
|
||||||
self.assertTrue('User.drop()' in source_script)
|
self.assertTrue('User.drop()' in source_script)
|
||||||
|
|
||||||
#@fixture.usedb()
|
@fixture.usedb()
|
||||||
#def test_make_update_script_for_model_equals(self):
|
def test_make_update_script_for_equal_models(self):
|
||||||
# """Try to make update script from two identical models"""
|
"""Try to make update script from two identical models"""
|
||||||
|
|
||||||
# self.setup_model_params()
|
self.setup_model_params()
|
||||||
# self.write_file(self.first_model_path, self.base_source + self.model_source)
|
self.write_file(self.first_model_path, self.base_source + self.model_source)
|
||||||
# self.write_file(self.second_model_path, self.base_source + self.model_source)
|
self.write_file(self.second_model_path, self.base_source + self.model_source)
|
||||||
|
|
||||||
# source_script = self.pyscript.make_update_script_for_model(
|
source_script = self.pyscript.make_update_script_for_model(
|
||||||
# engine=self.engine,
|
engine=self.engine,
|
||||||
# oldmodel=load_model('testmodel_first:meta'),
|
oldmodel=load_model('testmodel_first:meta'),
|
||||||
# model=load_model('testmodel_second:meta'),
|
model=load_model('testmodel_second:meta'),
|
||||||
# repository=self.repo_path,
|
repository=self.repo_path,
|
||||||
# )
|
)
|
||||||
|
|
||||||
# self.assertFalse('User.create()' in source_script)
|
self.assertFalse('User.create()' in source_script)
|
||||||
# self.assertFalse('User.drop()' in source_script)
|
self.assertFalse('User.drop()' in source_script)
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_make_update_script_direction(self):
|
||||||
|
"""Check update scripts go in the right direction"""
|
||||||
|
|
||||||
|
self.setup_model_params()
|
||||||
|
self.write_file(self.first_model_path, self.base_source)
|
||||||
|
self.write_file(self.second_model_path, self.base_source + self.model_source)
|
||||||
|
|
||||||
|
source_script = self.pyscript.make_update_script_for_model(
|
||||||
|
engine=self.engine,
|
||||||
|
oldmodel=load_model('testmodel_first:meta'),
|
||||||
|
model=load_model('testmodel_second:meta'),
|
||||||
|
repository=self.repo_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(0
|
||||||
|
< source_script.find('upgrade')
|
||||||
|
< source_script.find('User.create()')
|
||||||
|
< source_script.find('downgrade')
|
||||||
|
< source_script.find('User.drop()'))
|
||||||
|
|
||||||
def setup_model_params(self):
|
def setup_model_params(self):
|
||||||
self.script_path = self.tmp_py()
|
self.script_path = self.tmp_py()
|
||||||
@ -195,6 +216,8 @@ User = Table('User', meta,
|
|||||||
|
|
||||||
self.repo = repository.Repository.create(self.repo_path, 'repo')
|
self.repo = repository.Repository.create(self.repo_path, 'repo')
|
||||||
self.pyscript = PythonScript.create(self.script_path)
|
self.pyscript = PythonScript.create(self.script_path)
|
||||||
|
sys.modules.pop('testmodel_first', None)
|
||||||
|
sys.modules.pop('testmodel_second', None)
|
||||||
|
|
||||||
def write_file(self, path, contents):
|
def write_file(self, path, contents):
|
||||||
f = open(path, 'w')
|
f = open(path, 'w')
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Code to generate a Python model from a database or differences
|
Code to generate a Python model from a database or differences
|
||||||
between a model and database.
|
between a model and database.
|
||||||
|
|
||||||
Some of this is borrowed heavily from the AutoCode project at:
|
Some of this is borrowed heavily from the AutoCode project at:
|
||||||
http://code.google.com/p/sqlautocode/
|
http://code.google.com/p/sqlautocode/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -34,6 +34,13 @@ Base = declarative.declarative_base()
|
|||||||
|
|
||||||
|
|
||||||
class ModelGenerator(object):
|
class ModelGenerator(object):
|
||||||
|
"""Various transformations from an A, B diff.
|
||||||
|
|
||||||
|
In the implementation, A tends to be called the model and B
|
||||||
|
the database (although this is not true of all diffs).
|
||||||
|
The diff is directionless, but transformations apply the diff
|
||||||
|
in a particular direction, described in the method name.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, diff, engine, declarative=False):
|
def __init__(self, diff, engine, declarative=False):
|
||||||
self.diff = diff
|
self.diff = diff
|
||||||
@ -89,7 +96,7 @@ class ModelGenerator(object):
|
|||||||
else:
|
else:
|
||||||
return """Column(%(name)r, %(commonStuff)s)""" % data
|
return """Column(%(name)r, %(commonStuff)s)""" % data
|
||||||
|
|
||||||
def getTableDefn(self, table):
|
def _getTableDefn(self, table):
|
||||||
out = []
|
out = []
|
||||||
tableName = table.name
|
tableName = table.name
|
||||||
if self.declarative:
|
if self.declarative:
|
||||||
@ -117,9 +124,15 @@ class ModelGenerator(object):
|
|||||||
if bool_:
|
if bool_:
|
||||||
for name in names:
|
for name in names:
|
||||||
yield metadata.tables.get(name)
|
yield metadata.tables.get(name)
|
||||||
|
|
||||||
def toPython(self):
|
def genBDefinition(self):
|
||||||
"""Assume database is current and model is empty."""
|
"""Generates the source code for a definition of B.
|
||||||
|
|
||||||
|
Assumes a diff where A is empty.
|
||||||
|
|
||||||
|
Was: toPython. Assume database (B) is current and model (A) is empty.
|
||||||
|
"""
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
if self.declarative:
|
if self.declarative:
|
||||||
out.append(DECLARATIVE_HEADER)
|
out.append(DECLARATIVE_HEADER)
|
||||||
@ -127,17 +140,22 @@ class ModelGenerator(object):
|
|||||||
out.append(HEADER)
|
out.append(HEADER)
|
||||||
out.append("")
|
out.append("")
|
||||||
for table in self._get_tables(missingA=True):
|
for table in self._get_tables(missingA=True):
|
||||||
out.extend(self.getTableDefn(table))
|
out.extend(self._getTableDefn(table))
|
||||||
return '\n'.join(out)
|
return '\n'.join(out)
|
||||||
|
|
||||||
def toUpgradeDowngradePython(self, indent=' '):
|
def genB2AMigration(self, indent=' '):
|
||||||
''' Assume model is most current and database is out-of-date. '''
|
'''Generate a migration from B to A.
|
||||||
|
|
||||||
|
Was: toUpgradeDowngradePython
|
||||||
|
Assume model (A) is most current and database (B) is out-of-date.
|
||||||
|
'''
|
||||||
|
|
||||||
decls = ['from migrate.changeset import schema',
|
decls = ['from migrate.changeset import schema',
|
||||||
'meta = MetaData()']
|
'meta = MetaData()']
|
||||||
for table in self._get_tables(
|
for table in self._get_tables(
|
||||||
missingA=True,missingB=True,modified=True
|
missingA=True,missingB=True,modified=True
|
||||||
):
|
):
|
||||||
decls.extend(self.getTableDefn(table))
|
decls.extend(self._getTableDefn(table))
|
||||||
|
|
||||||
upgradeCommands, downgradeCommands = [], []
|
upgradeCommands, downgradeCommands = [], []
|
||||||
for tableName in self.diff.tables_missing_from_A:
|
for tableName in self.diff.tables_missing_from_A:
|
||||||
@ -175,16 +193,21 @@ class ModelGenerator(object):
|
|||||||
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
|
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
|
||||||
|
|
||||||
def _db_can_handle_this_change(self,td):
|
def _db_can_handle_this_change(self,td):
|
||||||
|
"""Check if the database can handle going from B to A."""
|
||||||
|
|
||||||
if (td.columns_missing_from_B
|
if (td.columns_missing_from_B
|
||||||
and not td.columns_missing_from_A
|
and not td.columns_missing_from_A
|
||||||
and not td.columns_different):
|
and not td.columns_different):
|
||||||
# Even sqlite can handle this.
|
# Even sqlite can handle column additions.
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return not self.engine.url.drivername.startswith('sqlite')
|
return not self.engine.url.drivername.startswith('sqlite')
|
||||||
|
|
||||||
def applyModel(self):
|
def runB2A(self):
|
||||||
"""Apply model to current database."""
|
"""Goes from B to A.
|
||||||
|
|
||||||
|
Was: applyModel. Apply model (A) to current database (B).
|
||||||
|
"""
|
||||||
|
|
||||||
meta = sqlalchemy.MetaData(self.engine)
|
meta = sqlalchemy.MetaData(self.engine)
|
||||||
|
|
||||||
@ -200,9 +223,9 @@ class ModelGenerator(object):
|
|||||||
dbTable = self.diff.metadataB.tables[tableName]
|
dbTable = self.diff.metadataB.tables[tableName]
|
||||||
|
|
||||||
td = self.diff.tables_different[tableName]
|
td = self.diff.tables_different[tableName]
|
||||||
|
|
||||||
if self._db_can_handle_this_change(td):
|
if self._db_can_handle_this_change(td):
|
||||||
|
|
||||||
for col in td.columns_missing_from_B:
|
for col in td.columns_missing_from_B:
|
||||||
modelTable.columns[col].create()
|
modelTable.columns[col].create()
|
||||||
for col in td.columns_missing_from_A:
|
for col in td.columns_missing_from_A:
|
||||||
|
@ -71,7 +71,7 @@ class ControlledSchema(object):
|
|||||||
|
|
||||||
def changeset(self, version=None):
|
def changeset(self, version=None):
|
||||||
"""API to Changeset creation.
|
"""API to Changeset creation.
|
||||||
|
|
||||||
Uses self.version for start version and engine.name
|
Uses self.version for start version and engine.name
|
||||||
to get database name.
|
to get database name.
|
||||||
"""
|
"""
|
||||||
@ -117,7 +117,7 @@ class ControlledSchema(object):
|
|||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
||||||
model, self.engine, excludeTables=[self.repository.version_table]
|
model, self.engine, excludeTables=[self.repository.version_table]
|
||||||
)
|
)
|
||||||
genmodel.ModelGenerator(diff,self.engine).applyModel()
|
genmodel.ModelGenerator(diff,self.engine).runB2A()
|
||||||
|
|
||||||
self.update_repository_table(self.version, int(self.repository.latest))
|
self.update_repository_table(self.version, int(self.repository.latest))
|
||||||
|
|
||||||
@ -217,4 +217,4 @@ class ControlledSchema(object):
|
|||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
||||||
MetaData(), engine, excludeTables=[repository.version_table]
|
MetaData(), engine, excludeTables=[repository.version_table]
|
||||||
)
|
)
|
||||||
return genmodel.ModelGenerator(diff, engine, declarative).toPython()
|
return genmodel.ModelGenerator(diff, engine, declarative).genBDefinition()
|
||||||
|
@ -39,11 +39,11 @@ class ColDiff(object):
|
|||||||
Container for differences in one :class:`~sqlalchemy.schema.Column`
|
Container for differences in one :class:`~sqlalchemy.schema.Column`
|
||||||
between two :class:`~sqlalchemy.schema.Table` instances, ``A``
|
between two :class:`~sqlalchemy.schema.Table` instances, ``A``
|
||||||
and ``B``.
|
and ``B``.
|
||||||
|
|
||||||
.. attribute:: col_A
|
.. attribute:: col_A
|
||||||
|
|
||||||
The :class:`~sqlalchemy.schema.Column` object for A.
|
The :class:`~sqlalchemy.schema.Column` object for A.
|
||||||
|
|
||||||
.. attribute:: col_B
|
.. attribute:: col_B
|
||||||
|
|
||||||
The :class:`~sqlalchemy.schema.Column` object for B.
|
The :class:`~sqlalchemy.schema.Column` object for B.
|
||||||
@ -51,15 +51,15 @@ class ColDiff(object):
|
|||||||
.. attribute:: type_A
|
.. attribute:: type_A
|
||||||
|
|
||||||
The most generic type of the :class:`~sqlalchemy.schema.Column`
|
The most generic type of the :class:`~sqlalchemy.schema.Column`
|
||||||
object in A.
|
object in A.
|
||||||
|
|
||||||
.. attribute:: type_B
|
.. attribute:: type_B
|
||||||
|
|
||||||
The most generic type of the :class:`~sqlalchemy.schema.Column`
|
The most generic type of the :class:`~sqlalchemy.schema.Column`
|
||||||
object in A.
|
object in A.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
diff = False
|
diff = False
|
||||||
|
|
||||||
def __init__(self,col_A,col_B):
|
def __init__(self,col_A,col_B):
|
||||||
@ -87,10 +87,10 @@ class ColDiff(object):
|
|||||||
if not (A is None or B is None) and A!=B:
|
if not (A is None or B is None) and A!=B:
|
||||||
self.diff=True
|
self.diff=True
|
||||||
return
|
return
|
||||||
|
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return self.diff
|
return self.diff
|
||||||
|
|
||||||
class TableDiff(object):
|
class TableDiff(object):
|
||||||
"""
|
"""
|
||||||
Container for differences in one :class:`~sqlalchemy.schema.Table`
|
Container for differences in one :class:`~sqlalchemy.schema.Table`
|
||||||
@ -101,12 +101,12 @@ class TableDiff(object):
|
|||||||
|
|
||||||
A sequence of column names that were found in B but weren't in
|
A sequence of column names that were found in B but weren't in
|
||||||
A.
|
A.
|
||||||
|
|
||||||
.. attribute:: columns_missing_from_B
|
.. attribute:: columns_missing_from_B
|
||||||
|
|
||||||
A sequence of column names that were found in A but weren't in
|
A sequence of column names that were found in A but weren't in
|
||||||
B.
|
B.
|
||||||
|
|
||||||
.. attribute:: columns_different
|
.. attribute:: columns_different
|
||||||
|
|
||||||
A dictionary containing information about columns that were
|
A dictionary containing information about columns that were
|
||||||
@ -126,7 +126,7 @@ class TableDiff(object):
|
|||||||
self.columns_missing_from_B or
|
self.columns_missing_from_B or
|
||||||
self.columns_different
|
self.columns_different
|
||||||
)
|
)
|
||||||
|
|
||||||
class SchemaDiff(object):
|
class SchemaDiff(object):
|
||||||
"""
|
"""
|
||||||
Compute the difference between two :class:`~sqlalchemy.schema.MetaData`
|
Compute the difference between two :class:`~sqlalchemy.schema.MetaData`
|
||||||
@ -139,34 +139,34 @@ class SchemaDiff(object):
|
|||||||
The length of a :class:`SchemaDiff` will give the number of
|
The length of a :class:`SchemaDiff` will give the number of
|
||||||
changes found, enabling it to be used much like a boolean in
|
changes found, enabling it to be used much like a boolean in
|
||||||
expressions.
|
expressions.
|
||||||
|
|
||||||
:param metadataA:
|
:param metadataA:
|
||||||
First :class:`~sqlalchemy.schema.MetaData` to compare.
|
First :class:`~sqlalchemy.schema.MetaData` to compare.
|
||||||
|
|
||||||
:param metadataB:
|
:param metadataB:
|
||||||
Second :class:`~sqlalchemy.schema.MetaData` to compare.
|
Second :class:`~sqlalchemy.schema.MetaData` to compare.
|
||||||
|
|
||||||
:param labelA:
|
:param labelA:
|
||||||
The label to use in messages about the first
|
The label to use in messages about the first
|
||||||
:class:`~sqlalchemy.schema.MetaData`.
|
:class:`~sqlalchemy.schema.MetaData`.
|
||||||
|
|
||||||
:param labelB:
|
:param labelB:
|
||||||
The label to use in messages about the second
|
The label to use in messages about the second
|
||||||
:class:`~sqlalchemy.schema.MetaData`.
|
:class:`~sqlalchemy.schema.MetaData`.
|
||||||
|
|
||||||
:param excludeTables:
|
:param excludeTables:
|
||||||
A sequence of table names to exclude.
|
A sequence of table names to exclude.
|
||||||
|
|
||||||
.. attribute:: tables_missing_from_A
|
.. attribute:: tables_missing_from_A
|
||||||
|
|
||||||
A sequence of table names that were found in B but weren't in
|
A sequence of table names that were found in B but weren't in
|
||||||
A.
|
A.
|
||||||
|
|
||||||
.. attribute:: tables_missing_from_B
|
.. attribute:: tables_missing_from_B
|
||||||
|
|
||||||
A sequence of table names that were found in A but weren't in
|
A sequence of table names that were found in A but weren't in
|
||||||
B.
|
B.
|
||||||
|
|
||||||
.. attribute:: tables_different
|
.. attribute:: tables_different
|
||||||
|
|
||||||
A dictionary containing information about tables that were found
|
A dictionary containing information about tables that were found
|
||||||
@ -195,26 +195,26 @@ class SchemaDiff(object):
|
|||||||
self.tables_missing_from_B = sorted(
|
self.tables_missing_from_B = sorted(
|
||||||
A_table_names - B_table_names - excludeTables
|
A_table_names - B_table_names - excludeTables
|
||||||
)
|
)
|
||||||
|
|
||||||
self.tables_different = {}
|
self.tables_different = {}
|
||||||
for table_name in A_table_names.intersection(B_table_names):
|
for table_name in A_table_names.intersection(B_table_names):
|
||||||
|
|
||||||
td = TableDiff()
|
td = TableDiff()
|
||||||
|
|
||||||
A_table = metadataA.tables[table_name]
|
A_table = metadataA.tables[table_name]
|
||||||
B_table = metadataB.tables[table_name]
|
B_table = metadataB.tables[table_name]
|
||||||
|
|
||||||
A_column_names = set(A_table.columns.keys())
|
A_column_names = set(A_table.columns.keys())
|
||||||
B_column_names = set(B_table.columns.keys())
|
B_column_names = set(B_table.columns.keys())
|
||||||
|
|
||||||
td.columns_missing_from_A = sorted(
|
td.columns_missing_from_A = sorted(
|
||||||
B_column_names - A_column_names
|
B_column_names - A_column_names
|
||||||
)
|
)
|
||||||
|
|
||||||
td.columns_missing_from_B = sorted(
|
td.columns_missing_from_B = sorted(
|
||||||
A_column_names - B_column_names
|
A_column_names - B_column_names
|
||||||
)
|
)
|
||||||
|
|
||||||
td.columns_different = {}
|
td.columns_different = {}
|
||||||
|
|
||||||
for col_name in A_column_names.intersection(B_column_names):
|
for col_name in A_column_names.intersection(B_column_names):
|
||||||
@ -226,7 +226,7 @@ class SchemaDiff(object):
|
|||||||
|
|
||||||
if cd:
|
if cd:
|
||||||
td.columns_different[col_name]=cd
|
td.columns_different[col_name]=cd
|
||||||
|
|
||||||
# XXX - index and constraint differences should
|
# XXX - index and constraint differences should
|
||||||
# be checked for here
|
# be checked for here
|
||||||
|
|
||||||
@ -237,7 +237,7 @@ class SchemaDiff(object):
|
|||||||
''' Summarize differences. '''
|
''' Summarize differences. '''
|
||||||
out = []
|
out = []
|
||||||
column_template =' %%%is: %%r' % self.label_width
|
column_template =' %%%is: %%r' % self.label_width
|
||||||
|
|
||||||
for names,label in (
|
for names,label in (
|
||||||
(self.tables_missing_from_A,self.labelA),
|
(self.tables_missing_from_A,self.labelA),
|
||||||
(self.tables_missing_from_B,self.labelB),
|
(self.tables_missing_from_B,self.labelB),
|
||||||
@ -248,7 +248,7 @@ class SchemaDiff(object):
|
|||||||
label,', '.join(sorted(names))
|
label,', '.join(sorted(names))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for name,td in sorted(self.tables_different.items()):
|
for name,td in sorted(self.tables_different.items()):
|
||||||
out.append(
|
out.append(
|
||||||
' table with differences: %s' % name
|
' table with differences: %s' % name
|
||||||
@ -267,7 +267,7 @@ class SchemaDiff(object):
|
|||||||
out.append(' column with differences: %s' % name)
|
out.append(' column with differences: %s' % name)
|
||||||
out.append(column_template % (self.labelA,cd.col_A))
|
out.append(column_template % (self.labelA,cd.col_A))
|
||||||
out.append(column_template % (self.labelB,cd.col_B))
|
out.append(column_template % (self.labelB,cd.col_B))
|
||||||
|
|
||||||
if out:
|
if out:
|
||||||
out.insert(0, 'Schema diffs:')
|
out.insert(0, 'Schema diffs:')
|
||||||
return '\n'.join(out)
|
return '\n'.join(out)
|
||||||
|
@ -25,7 +25,7 @@ class PythonScript(base.BaseScript):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, path, **opts):
|
def create(cls, path, **opts):
|
||||||
"""Create an empty migration script at specified path
|
"""Create an empty migration script at specified path
|
||||||
|
|
||||||
:returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
|
:returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
|
||||||
cls.require_notfound(path)
|
cls.require_notfound(path)
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ class PythonScript(base.BaseScript):
|
|||||||
def make_update_script_for_model(cls, engine, oldmodel,
|
def make_update_script_for_model(cls, engine, oldmodel,
|
||||||
model, repository, **opts):
|
model, repository, **opts):
|
||||||
"""Create a migration script based on difference between two SA models.
|
"""Create a migration script based on difference between two SA models.
|
||||||
|
|
||||||
:param repository: path to migrate repository
|
:param repository: path to migrate repository
|
||||||
:param oldmodel: dotted.module.name:SAClass or SAClass object
|
:param oldmodel: dotted.module.name:SAClass or SAClass object
|
||||||
:param model: dotted.module.name:SAClass or SAClass object
|
:param model: dotted.module.name:SAClass or SAClass object
|
||||||
@ -50,7 +50,7 @@ class PythonScript(base.BaseScript):
|
|||||||
:returns: Upgrade / Downgrade script
|
:returns: Upgrade / Downgrade script
|
||||||
:rtype: string
|
:rtype: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(repository, basestring):
|
if isinstance(repository, basestring):
|
||||||
# oh dear, an import cycle!
|
# oh dear, an import cycle!
|
||||||
from migrate.versioning.repository import Repository
|
from migrate.versioning.repository import Repository
|
||||||
@ -61,12 +61,12 @@ class PythonScript(base.BaseScript):
|
|||||||
|
|
||||||
# Compute differences.
|
# Compute differences.
|
||||||
diff = schemadiff.getDiffOfModelAgainstModel(
|
diff = schemadiff.getDiffOfModelAgainstModel(
|
||||||
oldmodel,
|
|
||||||
model,
|
model,
|
||||||
|
oldmodel,
|
||||||
excludeTables=[repository.version_table])
|
excludeTables=[repository.version_table])
|
||||||
# TODO: diff can be False (there is no difference?)
|
# TODO: diff can be False (there is no difference?)
|
||||||
decls, upgradeCommands, downgradeCommands = \
|
decls, upgradeCommands, downgradeCommands = \
|
||||||
genmodel.ModelGenerator(diff,engine).toUpgradeDowngradePython()
|
genmodel.ModelGenerator(diff,engine).genB2AMigration()
|
||||||
|
|
||||||
# Store differences into file.
|
# Store differences into file.
|
||||||
src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
|
src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
|
||||||
@ -86,7 +86,7 @@ class PythonScript(base.BaseScript):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def verify_module(cls, path):
|
def verify_module(cls, path):
|
||||||
"""Ensure path is a valid script
|
"""Ensure path is a valid script
|
||||||
|
|
||||||
:param path: Script location
|
:param path: Script location
|
||||||
:type path: string
|
:type path: string
|
||||||
:raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
|
:raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
|
||||||
@ -101,7 +101,7 @@ class PythonScript(base.BaseScript):
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
def preview_sql(self, url, step, **args):
|
def preview_sql(self, url, step, **args):
|
||||||
"""Mocks SQLAlchemy Engine to store all executed calls in a string
|
"""Mocks SQLAlchemy Engine to store all executed calls in a string
|
||||||
and runs :meth:`PythonScript.run <migrate.versioning.script.py.PythonScript.run>`
|
and runs :meth:`PythonScript.run <migrate.versioning.script.py.PythonScript.run>`
|
||||||
|
|
||||||
:returns: SQL file
|
:returns: SQL file
|
||||||
@ -119,7 +119,7 @@ class PythonScript(base.BaseScript):
|
|||||||
return go(url, step, **args)
|
return go(url, step, **args)
|
||||||
|
|
||||||
def run(self, engine, step):
|
def run(self, engine, step):
|
||||||
"""Core method of Script file.
|
"""Core method of Script file.
|
||||||
Exectues :func:`update` or :func:`downgrade` functions
|
Exectues :func:`update` or :func:`downgrade` functions
|
||||||
|
|
||||||
:param engine: SQLAlchemy Engine
|
:param engine: SQLAlchemy Engine
|
||||||
|
Loading…
x
Reference in New Issue
Block a user