##// END OF EJS Templates
Merged in liads/rhodecode (pull request #14)
Merged in liads/rhodecode (pull request #14)

File last commit:

r1203:6832ef66 beta
r1623:b7094c69 merge beta
Show More
schemadiff.py
285 lines | 8.3 KiB | text/x-python | PythonLexer
added dbmigrate package, added model changes...
r833 """
Schema differencing support.
"""
import logging
import sqlalchemy
fixed imports on migrate, added getting current version from database
r835 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06
added dbmigrate package, added model changes...
r833 from sqlalchemy.types import Float
log = logging.getLogger(__name__)
def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None):
"""
Return differences of model against database.
:return: object which will evaluate to :keyword:`True` if there \
are differences else :keyword:`False`.
"""
return SchemaDiff(metadata,
sqlalchemy.MetaData(engine, reflect=True),
labelA='model',
labelB='database',
excludeTables=excludeTables)
def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None):
"""
Return differences of model against another model.
:return: object which will evaluate to :keyword:`True` if there \
are differences else :keyword:`False`.
"""
return SchemaDiff(metadataA, metadataB, excludeTables)
class ColDiff(object):
"""
Container for differences in one :class:`~sqlalchemy.schema.Column`
between two :class:`~sqlalchemy.schema.Table` instances, ``A``
and ``B``.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: col_A
The :class:`~sqlalchemy.schema.Column` object for A.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: col_B
The :class:`~sqlalchemy.schema.Column` object for B.
.. attribute:: type_A
The most generic type of the :class:`~sqlalchemy.schema.Column`
source code cleanup: remove trailing white space, normalize file endings
r1203 object in A.
added dbmigrate package, added model changes...
r833 .. attribute:: type_B
The most generic type of the :class:`~sqlalchemy.schema.Column`
source code cleanup: remove trailing white space, normalize file endings
r1203 object in A.
added dbmigrate package, added model changes...
r833 """
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 diff = False
def __init__(self,col_A,col_B):
self.col_A = col_A
self.col_B = col_B
self.type_A = col_A.type
self.type_B = col_B.type
self.affinity_A = self.type_A._type_affinity
self.affinity_B = self.type_B._type_affinity
if self.affinity_A is not self.affinity_B:
self.diff = True
return
if isinstance(self.type_A,Float) or isinstance(self.type_B,Float):
if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)):
self.diff=True
return
for attr in ('precision','scale','length'):
A = getattr(self.type_A,attr,None)
B = getattr(self.type_B,attr,None)
if not (A is None or B is None) and A!=B:
self.diff=True
return
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 def __nonzero__(self):
return self.diff
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 class TableDiff(object):
"""
Container for differences in one :class:`~sqlalchemy.schema.Table`
between two :class:`~sqlalchemy.schema.MetaData` instances, ``A``
and ``B``.
.. attribute:: columns_missing_from_A
A sequence of column names that were found in B but weren't in
A.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: columns_missing_from_B
A sequence of column names that were found in A but weren't in
B.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: columns_different
A dictionary containing information about columns that were
found to be different.
It maps column names to a :class:`ColDiff` objects describing the
differences found.
"""
__slots__ = (
'columns_missing_from_A',
'columns_missing_from_B',
'columns_different',
)
def __nonzero__(self):
return bool(
self.columns_missing_from_A or
self.columns_missing_from_B or
self.columns_different
)
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 class SchemaDiff(object):
"""
Compute the difference between two :class:`~sqlalchemy.schema.MetaData`
objects.
The string representation of a :class:`SchemaDiff` will summarise
the changes found between the two
:class:`~sqlalchemy.schema.MetaData` objects.
The length of a :class:`SchemaDiff` will give the number of
changes found, enabling it to be used much like a boolean in
expressions.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 :param metadataA:
First :class:`~sqlalchemy.schema.MetaData` to compare.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 :param metadataB:
Second :class:`~sqlalchemy.schema.MetaData` to compare.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 :param labelA:
The label to use in messages about the first
source code cleanup: remove trailing white space, normalize file endings
r1203 :class:`~sqlalchemy.schema.MetaData`.
:param labelB:
added dbmigrate package, added model changes...
r833 The label to use in messages about the second
source code cleanup: remove trailing white space, normalize file endings
r1203 :class:`~sqlalchemy.schema.MetaData`.
added dbmigrate package, added model changes...
r833 :param excludeTables:
A sequence of table names to exclude.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: tables_missing_from_A
A sequence of table names that were found in B but weren't in
A.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: tables_missing_from_B
A sequence of table names that were found in A but weren't in
B.
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 .. attribute:: tables_different
A dictionary containing information about tables that were found
to be different.
It maps table names to a :class:`TableDiff` objects describing the
differences found.
"""
def __init__(self,
metadataA, metadataB,
labelA='metadataA',
labelB='metadataB',
excludeTables=None):
self.metadataA, self.metadataB = metadataA, metadataB
self.labelA, self.labelB = labelA, labelB
self.label_width = max(len(labelA),len(labelB))
excludeTables = set(excludeTables or [])
A_table_names = set(metadataA.tables.keys())
B_table_names = set(metadataB.tables.keys())
self.tables_missing_from_A = sorted(
B_table_names - A_table_names - excludeTables
)
self.tables_missing_from_B = sorted(
A_table_names - B_table_names - excludeTables
)
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 self.tables_different = {}
for table_name in A_table_names.intersection(B_table_names):
td = TableDiff()
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 A_table = metadataA.tables[table_name]
B_table = metadataB.tables[table_name]
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 A_column_names = set(A_table.columns.keys())
B_column_names = set(B_table.columns.keys())
td.columns_missing_from_A = sorted(
B_column_names - A_column_names
)
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 td.columns_missing_from_B = sorted(
A_column_names - B_column_names
)
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 td.columns_different = {}
for col_name in A_column_names.intersection(B_column_names):
cd = ColDiff(
A_table.columns.get(col_name),
B_table.columns.get(col_name)
)
if cd:
td.columns_different[col_name]=cd
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 # XXX - index and constraint differences should
# be checked for here
if td:
self.tables_different[table_name]=td
def __str__(self):
''' Summarize differences. '''
out = []
column_template =' %%%is: %%r' % self.label_width
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 for names,label in (
(self.tables_missing_from_A,self.labelA),
(self.tables_missing_from_B,self.labelB),
):
if names:
out.append(
' tables missing from %s: %s' % (
label,', '.join(sorted(names))
)
)
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 for name,td in sorted(self.tables_different.items()):
out.append(
' table with differences: %s' % name
)
for names,label in (
(td.columns_missing_from_A,self.labelA),
(td.columns_missing_from_B,self.labelB),
):
if names:
out.append(
' %s missing these columns: %s' % (
label,', '.join(sorted(names))
)
)
for name,cd in td.columns_different.items():
out.append(' column with differences: %s' % name)
out.append(column_template % (self.labelA,cd.col_A))
out.append(column_template % (self.labelB,cd.col_B))
source code cleanup: remove trailing white space, normalize file endings
r1203
added dbmigrate package, added model changes...
r833 if out:
out.insert(0, 'Schema diffs:')
return '\n'.join(out)
else:
return 'No schema diffs'
def __len__(self):
"""
Used in bool evaluation, return of 0 means no diffs.
"""
return (
len(self.tables_missing_from_A) +
len(self.tables_missing_from_B) +
len(self.tables_different)
)