diff options
| author | Gabriel <g2p.code+sqla@gmail.com> | 2011-07-05 00:44:57 +0200 |
|---|---|---|
| committer | Gabriel <g2p.code+sqla@gmail.com> | 2011-07-05 00:44:57 +0200 |
| commit | 3fe1b4a3be05fe5184feb8bc237ceded5a98e8dc (patch) | |
| tree | 812139ad9bf4c9404d82cd5c7e6c76920a533ba2 /migrate/versioning | |
| parent | 5cfc74959f6f7cf0ff58693613ca410a256c3b4f (diff) | |
| download | sqlalchemy-migrate-3fe1b4a3be05fe5184feb8bc237ceded5a98e8dc.tar.gz | |
Fix and test issue 118. Clarify genmodel transformations.
Diffstat (limited to 'migrate/versioning')
| -rw-r--r-- | migrate/versioning/genmodel.py | 57 | ||||
| -rw-r--r-- | migrate/versioning/schema.py | 6 | ||||
| -rw-r--r-- | migrate/versioning/schemadiff.py | 64 | ||||
| -rw-r--r-- | migrate/versioning/script/py.py | 16 |
4 files changed, 83 insertions, 60 deletions
diff --git a/migrate/versioning/genmodel.py b/migrate/versioning/genmodel.py index 6cb8e09..cfe9996 100644 --- a/migrate/versioning/genmodel.py +++ b/migrate/versioning/genmodel.py @@ -1,9 +1,9 @@ """ - Code to generate a Python model from a database or differences - between a model and database. +Code to generate a Python model from a database or differences +between a model and database. - Some of this is borrowed heavily from the AutoCode project at: - http://code.google.com/p/sqlautocode/ +Some of this is borrowed heavily from the AutoCode project at: +http://code.google.com/p/sqlautocode/ """ import sys @@ -34,6 +34,13 @@ Base = declarative.declarative_base() 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): self.diff = diff @@ -89,7 +96,7 @@ class ModelGenerator(object): else: return """Column(%(name)r, %(commonStuff)s)""" % data - def getTableDefn(self, table): + def _getTableDefn(self, table): out = [] tableName = table.name if self.declarative: @@ -117,9 +124,15 @@ class ModelGenerator(object): if bool_: for name in names: yield metadata.tables.get(name) - - def toPython(self): - """Assume database is current and model is empty.""" + + def genBDefinition(self): + """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 = [] if self.declarative: out.append(DECLARATIVE_HEADER) @@ -127,17 +140,22 @@ class ModelGenerator(object): out.append(HEADER) out.append("") for table in self._get_tables(missingA=True): - out.extend(self.getTableDefn(table)) + out.extend(self._getTableDefn(table)) return '\n'.join(out) - def toUpgradeDowngradePython(self, indent=' '): - ''' Assume model is most current and database is out-of-date. ''' + def genB2AMigration(self, indent=' '): + '''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', 'meta = MetaData()'] for table in self._get_tables( missingA=True,missingB=True,modified=True ): - decls.extend(self.getTableDefn(table)) + decls.extend(self._getTableDefn(table)) upgradeCommands, downgradeCommands = [], [] 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])) 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 and not td.columns_missing_from_A and not td.columns_different): - # Even sqlite can handle this. + # Even sqlite can handle column additions. return True else: return not self.engine.url.drivername.startswith('sqlite') - def applyModel(self): - """Apply model to current database.""" + def runB2A(self): + """Goes from B to A. + + Was: applyModel. Apply model (A) to current database (B). + """ meta = sqlalchemy.MetaData(self.engine) @@ -200,9 +223,9 @@ class ModelGenerator(object): dbTable = self.diff.metadataB.tables[tableName] td = self.diff.tables_different[tableName] - + if self._db_can_handle_this_change(td): - + for col in td.columns_missing_from_B: modelTable.columns[col].create() for col in td.columns_missing_from_A: diff --git a/migrate/versioning/schema.py b/migrate/versioning/schema.py index 1085538..e4d9365 100644 --- a/migrate/versioning/schema.py +++ b/migrate/versioning/schema.py @@ -71,7 +71,7 @@ class ControlledSchema(object): def changeset(self, version=None): """API to Changeset creation. - + Uses self.version for start version and engine.name to get database name. """ @@ -117,7 +117,7 @@ class ControlledSchema(object): diff = schemadiff.getDiffOfModelAgainstDatabase( 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)) @@ -217,4 +217,4 @@ class ControlledSchema(object): diff = schemadiff.getDiffOfModelAgainstDatabase( MetaData(), engine, excludeTables=[repository.version_table] ) - return genmodel.ModelGenerator(diff, engine, declarative).toPython() + return genmodel.ModelGenerator(diff, engine, declarative).genBDefinition() diff --git a/migrate/versioning/schemadiff.py b/migrate/versioning/schemadiff.py index 17c2d8e..77661d3 100644 --- a/migrate/versioning/schemadiff.py +++ b/migrate/versioning/schemadiff.py @@ -39,11 +39,11 @@ class ColDiff(object): Container for differences in one :class:`~sqlalchemy.schema.Column` between two :class:`~sqlalchemy.schema.Table` instances, ``A`` and ``B``. - + .. attribute:: col_A The :class:`~sqlalchemy.schema.Column` object for A. - + .. attribute:: col_B The :class:`~sqlalchemy.schema.Column` object for B. @@ -51,15 +51,15 @@ class ColDiff(object): .. attribute:: type_A The most generic type of the :class:`~sqlalchemy.schema.Column` - object in A. - + object in A. + .. attribute:: type_B The most generic type of the :class:`~sqlalchemy.schema.Column` - object in A. - + object in A. + """ - + diff = False 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: self.diff=True return - + def __nonzero__(self): return self.diff - + class TableDiff(object): """ 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. - + .. attribute:: columns_missing_from_B A sequence of column names that were found in A but weren't in B. - + .. attribute:: columns_different A dictionary containing information about columns that were @@ -126,7 +126,7 @@ class TableDiff(object): self.columns_missing_from_B or self.columns_different ) - + class SchemaDiff(object): """ 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 changes found, enabling it to be used much like a boolean in expressions. - + :param metadataA: First :class:`~sqlalchemy.schema.MetaData` to compare. - + :param metadataB: Second :class:`~sqlalchemy.schema.MetaData` to compare. - + :param labelA: The label to use in messages about the first - :class:`~sqlalchemy.schema.MetaData`. - - :param labelB: + :class:`~sqlalchemy.schema.MetaData`. + + :param labelB: The label to use in messages about the second - :class:`~sqlalchemy.schema.MetaData`. - + :class:`~sqlalchemy.schema.MetaData`. + :param excludeTables: A sequence of table names to exclude. - + .. attribute:: tables_missing_from_A A sequence of table names that were found in B but weren't in A. - + .. attribute:: tables_missing_from_B A sequence of table names that were found in A but weren't in B. - + .. attribute:: tables_different A dictionary containing information about tables that were found @@ -195,26 +195,26 @@ class SchemaDiff(object): self.tables_missing_from_B = sorted( A_table_names - B_table_names - excludeTables ) - + self.tables_different = {} for table_name in A_table_names.intersection(B_table_names): td = TableDiff() - + A_table = metadataA.tables[table_name] B_table = metadataB.tables[table_name] - + 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 ) - + td.columns_missing_from_B = sorted( A_column_names - B_column_names ) - + td.columns_different = {} for col_name in A_column_names.intersection(B_column_names): @@ -226,7 +226,7 @@ class SchemaDiff(object): if cd: td.columns_different[col_name]=cd - + # XXX - index and constraint differences should # be checked for here @@ -237,7 +237,7 @@ class SchemaDiff(object): ''' Summarize differences. ''' out = [] column_template =' %%%is: %%r' % self.label_width - + for names,label in ( (self.tables_missing_from_A,self.labelA), (self.tables_missing_from_B,self.labelB), @@ -248,7 +248,7 @@ class SchemaDiff(object): label,', '.join(sorted(names)) ) ) - + for name,td in sorted(self.tables_different.items()): out.append( ' table with differences: %s' % name @@ -267,7 +267,7 @@ class SchemaDiff(object): 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)) - + if out: out.insert(0, 'Schema diffs:') return '\n'.join(out) diff --git a/migrate/versioning/script/py.py b/migrate/versioning/script/py.py index 35fe4aa..3a090d4 100644 --- a/migrate/versioning/script/py.py +++ b/migrate/versioning/script/py.py @@ -25,7 +25,7 @@ class PythonScript(base.BaseScript): @classmethod def create(cls, path, **opts): """Create an empty migration script at specified path - + :returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`""" cls.require_notfound(path) @@ -38,7 +38,7 @@ class PythonScript(base.BaseScript): def make_update_script_for_model(cls, engine, oldmodel, model, repository, **opts): """Create a migration script based on difference between two SA models. - + :param repository: path to migrate repository :param oldmodel: 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 :rtype: string """ - + if isinstance(repository, basestring): # oh dear, an import cycle! from migrate.versioning.repository import Repository @@ -61,12 +61,12 @@ class PythonScript(base.BaseScript): # Compute differences. diff = schemadiff.getDiffOfModelAgainstModel( - oldmodel, model, + oldmodel, excludeTables=[repository.version_table]) # TODO: diff can be False (there is no difference?) decls, upgradeCommands, downgradeCommands = \ - genmodel.ModelGenerator(diff,engine).toUpgradeDowngradePython() + genmodel.ModelGenerator(diff,engine).genB2AMigration() # Store differences into file. src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None)) @@ -86,7 +86,7 @@ class PythonScript(base.BaseScript): @classmethod def verify_module(cls, path): """Ensure path is a valid script - + :param path: Script location :type path: string :raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>` @@ -101,7 +101,7 @@ class PythonScript(base.BaseScript): return module 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>` :returns: SQL file @@ -119,7 +119,7 @@ class PythonScript(base.BaseScript): return go(url, step, **args) def run(self, engine, step): - """Core method of Script file. + """Core method of Script file. Exectues :func:`update` or :func:`downgrade` functions :param engine: SQLAlchemy Engine |
