Packages update
This commit is contained in:
@@ -110,19 +110,19 @@ def script(description, repository, **opts):
|
||||
|
||||
|
||||
@catch_known_errors
|
||||
def script_sql(database, repository, **opts):
|
||||
"""%prog script_sql DATABASE REPOSITORY_PATH
|
||||
def script_sql(database, description, repository, **opts):
|
||||
"""%prog script_sql DATABASE DESCRIPTION REPOSITORY_PATH
|
||||
|
||||
Create empty change SQL scripts for given DATABASE, where DATABASE
|
||||
is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.)
|
||||
is either specific ('postgresql', 'mysql', 'oracle', 'sqlite', etc.)
|
||||
or generic ('default').
|
||||
|
||||
For instance, manage.py script_sql postgres creates:
|
||||
repository/versions/001_postgres_upgrade.sql and
|
||||
repository/versions/001_postgres_postgres.sql
|
||||
For instance, manage.py script_sql postgresql description creates:
|
||||
repository/versions/001_description_postgresql_upgrade.sql and
|
||||
repository/versions/001_description_postgresql_downgrade.sql
|
||||
"""
|
||||
repo = Repository(repository)
|
||||
repo.create_script_sql(database, **opts)
|
||||
repo.create_script_sql(database, description, **opts)
|
||||
|
||||
|
||||
def version(repository, **opts):
|
||||
@@ -212,14 +212,15 @@ def test(url, repository, **opts):
|
||||
"""
|
||||
engine = opts.pop('engine')
|
||||
repos = Repository(repository)
|
||||
script = repos.version(None).script()
|
||||
|
||||
# Upgrade
|
||||
log.info("Upgrading...")
|
||||
script = repos.version(None).script(engine.name, 'upgrade')
|
||||
script.run(engine, 1)
|
||||
log.info("done")
|
||||
|
||||
log.info("Downgrading...")
|
||||
script = repos.version(None).script(engine.name, 'downgrade')
|
||||
script.run(engine, -1)
|
||||
log.info("done")
|
||||
log.info("Success")
|
||||
|
||||
@@ -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
|
||||
@@ -59,7 +66,7 @@ class ModelGenerator(object):
|
||||
pass
|
||||
else:
|
||||
kwarg.append('default')
|
||||
ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg)
|
||||
args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg]
|
||||
|
||||
# crs: not sure if this is good idea, but it gets rid of extra
|
||||
# u''
|
||||
@@ -73,43 +80,38 @@ class ModelGenerator(object):
|
||||
type_ = cls()
|
||||
break
|
||||
|
||||
type_repr = repr(type_)
|
||||
if type_repr.endswith('()'):
|
||||
type_repr = type_repr[:-2]
|
||||
|
||||
constraints = [repr(cn) for cn in col.constraints]
|
||||
|
||||
data = {
|
||||
'name': name,
|
||||
'type': type_,
|
||||
'constraints': ', '.join([repr(cn) for cn in col.constraints]),
|
||||
'args': ks and ks or ''}
|
||||
'commonStuff': ', '.join([type_repr] + constraints + args),
|
||||
}
|
||||
|
||||
if data['constraints']:
|
||||
if data['args']:
|
||||
data['args'] = ',' + data['args']
|
||||
|
||||
if data['constraints'] or data['args']:
|
||||
data['maybeComma'] = ','
|
||||
else:
|
||||
data['maybeComma'] = ''
|
||||
|
||||
commonStuff = """ %(maybeComma)s %(constraints)s %(args)s)""" % data
|
||||
commonStuff = commonStuff.strip()
|
||||
data['commonStuff'] = commonStuff
|
||||
if self.declarative:
|
||||
return """%(name)s = Column(%(type)r%(commonStuff)s""" % data
|
||||
return """%(name)s = Column(%(commonStuff)s)""" % data
|
||||
else:
|
||||
return """Column(%(name)r, %(type)r%(commonStuff)s""" % data
|
||||
return """Column(%(name)r, %(commonStuff)s)""" % data
|
||||
|
||||
def getTableDefn(self, table):
|
||||
def _getTableDefn(self, table, metaName='meta'):
|
||||
out = []
|
||||
tableName = table.name
|
||||
if self.declarative:
|
||||
out.append("class %(table)s(Base):" % {'table': tableName})
|
||||
out.append(" __tablename__ = '%(table)s'" % {'table': tableName})
|
||||
out.append(" __tablename__ = '%(table)s'\n" %
|
||||
{'table': tableName})
|
||||
for col in table.columns:
|
||||
out.append(" %s" % self.column_repr(col))
|
||||
out.append(" %s" % self.column_repr(col))
|
||||
out.append('\n')
|
||||
else:
|
||||
out.append("%(table)s = Table('%(table)s', meta," % \
|
||||
{'table': tableName})
|
||||
out.append("%(table)s = Table('%(table)s', %(meta)s," %
|
||||
{'table': tableName, 'meta': metaName})
|
||||
for col in table.columns:
|
||||
out.append(" %s," % self.column_repr(col))
|
||||
out.append(")")
|
||||
out.append(" %s," % self.column_repr(col))
|
||||
out.append(")\n")
|
||||
return out
|
||||
|
||||
def _get_tables(self,missingA=False,missingB=False,modified=False):
|
||||
@@ -122,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)
|
||||
@@ -132,67 +140,89 @@ class ModelGenerator(object):
|
||||
out.append(HEADER)
|
||||
out.append("")
|
||||
for table in self._get_tables(missingA=True):
|
||||
out.extend(self.getTableDefn(table))
|
||||
out.append("")
|
||||
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))
|
||||
'pre_meta = MetaData()',
|
||||
'post_meta = MetaData()',
|
||||
]
|
||||
upgradeCommands = ['pre_meta.bind = migrate_engine',
|
||||
'post_meta.bind = migrate_engine']
|
||||
downgradeCommands = list(upgradeCommands)
|
||||
|
||||
upgradeCommands, downgradeCommands = [], []
|
||||
for tableName in self.diff.tables_missing_from_A:
|
||||
upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
||||
downgradeCommands.append("%(table)s.create()" % \
|
||||
{'table': tableName})
|
||||
for tableName in self.diff.tables_missing_from_B:
|
||||
upgradeCommands.append("%(table)s.create()" % {'table': tableName})
|
||||
downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
||||
for tn in self.diff.tables_missing_from_A:
|
||||
pre_table = self.diff.metadataB.tables[tn]
|
||||
decls.extend(self._getTableDefn(pre_table, metaName='pre_meta'))
|
||||
upgradeCommands.append(
|
||||
"pre_meta.tables[%(table)r].drop()" % {'table': tn})
|
||||
downgradeCommands.append(
|
||||
"pre_meta.tables[%(table)r].create()" % {'table': tn})
|
||||
|
||||
for tableName in self.diff.tables_different:
|
||||
dbTable = self.diff.metadataB.tables[tableName]
|
||||
missingInDatabase, missingInModel, diffDecl = \
|
||||
self.diff.colDiffs[tableName]
|
||||
for col in missingInDatabase:
|
||||
upgradeCommands.append('%s.columns[%r].create()' % (
|
||||
modelTable, col.name))
|
||||
downgradeCommands.append('%s.columns[%r].drop()' % (
|
||||
modelTable, col.name))
|
||||
for col in missingInModel:
|
||||
upgradeCommands.append('%s.columns[%r].drop()' % (
|
||||
modelTable, col.name))
|
||||
downgradeCommands.append('%s.columns[%r].create()' % (
|
||||
modelTable, col.name))
|
||||
for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
|
||||
for tn in self.diff.tables_missing_from_B:
|
||||
post_table = self.diff.metadataA.tables[tn]
|
||||
decls.extend(self._getTableDefn(post_table, metaName='post_meta'))
|
||||
upgradeCommands.append(
|
||||
"post_meta.tables[%(table)r].create()" % {'table': tn})
|
||||
downgradeCommands.append(
|
||||
"post_meta.tables[%(table)r].drop()" % {'table': tn})
|
||||
|
||||
for (tn, td) in self.diff.tables_different.iteritems():
|
||||
if td.columns_missing_from_A or td.columns_different:
|
||||
pre_table = self.diff.metadataB.tables[tn]
|
||||
decls.extend(self._getTableDefn(
|
||||
pre_table, metaName='pre_meta'))
|
||||
if td.columns_missing_from_B or td.columns_different:
|
||||
post_table = self.diff.metadataA.tables[tn]
|
||||
decls.extend(self._getTableDefn(
|
||||
post_table, metaName='post_meta'))
|
||||
|
||||
for col in td.columns_missing_from_A:
|
||||
upgradeCommands.append(
|
||||
'pre_meta.tables[%r].columns[%r].drop()' % (tn, col))
|
||||
downgradeCommands.append(
|
||||
'pre_meta.tables[%r].columns[%r].create()' % (tn, col))
|
||||
for col in td.columns_missing_from_B:
|
||||
upgradeCommands.append(
|
||||
'post_meta.tables[%r].columns[%r].create()' % (tn, col))
|
||||
downgradeCommands.append(
|
||||
'post_meta.tables[%r].columns[%r].drop()' % (tn, col))
|
||||
for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different:
|
||||
upgradeCommands.append(
|
||||
'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
|
||||
modelTable, modelCol.name, databaseCol.name))
|
||||
tn, modelCol.name, databaseCol.name))
|
||||
downgradeCommands.append(
|
||||
'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
|
||||
modelTable, modelCol.name, databaseCol.name))
|
||||
pre_command = ' meta.bind = migrate_engine'
|
||||
tn, modelCol.name, databaseCol.name))
|
||||
|
||||
return (
|
||||
'\n'.join(decls),
|
||||
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]),
|
||||
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
|
||||
'\n'.join('%s%s' % (indent, line) for line in upgradeCommands),
|
||||
'\n'.join('%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)
|
||||
|
||||
@@ -208,9 +238,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:
|
||||
@@ -252,3 +282,4 @@ class ModelGenerator(object):
|
||||
except:
|
||||
trans.rollback()
|
||||
raise
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ class Repository(pathed.Pathed):
|
||||
options.setdefault('version_table', 'migrate_version')
|
||||
options.setdefault('repository_id', name)
|
||||
options.setdefault('required_dbs', [])
|
||||
options.setdefault('use_timestamp_numbering', False)
|
||||
|
||||
tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
|
||||
ret = TempitaTemplate(tmpl).substitute(options)
|
||||
@@ -152,11 +153,14 @@ class Repository(pathed.Pathed):
|
||||
|
||||
def create_script(self, description, **k):
|
||||
"""API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
|
||||
|
||||
k['use_timestamp_numbering'] = self.use_timestamp_numbering
|
||||
self.versions.create_new_python_version(description, **k)
|
||||
|
||||
def create_script_sql(self, database, **k):
|
||||
def create_script_sql(self, database, description, **k):
|
||||
"""API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`"""
|
||||
self.versions.create_new_sql_version(database, **k)
|
||||
k['use_timestamp_numbering'] = self.use_timestamp_numbering
|
||||
self.versions.create_new_sql_version(database, description, **k)
|
||||
|
||||
@property
|
||||
def latest(self):
|
||||
@@ -173,6 +177,13 @@ class Repository(pathed.Pathed):
|
||||
"""Returns repository id specified in config"""
|
||||
return self.config.get('db_settings', 'repository_id')
|
||||
|
||||
@property
|
||||
def use_timestamp_numbering(self):
|
||||
"""Returns use_timestamp_numbering specified in config"""
|
||||
if self.config.has_option('db_settings', 'use_timestamp_numbering'):
|
||||
return self.config.getboolean('db_settings', 'use_timestamp_numbering')
|
||||
return False
|
||||
|
||||
def version(self, *p, **k):
|
||||
"""API to :attr:`migrate.versioning.version.Collection.version`"""
|
||||
return self.versions.version(*p, **k)
|
||||
|
||||
@@ -11,6 +11,7 @@ from sqlalchemy import exceptions as sa_exceptions
|
||||
from sqlalchemy.sql import bindparam
|
||||
|
||||
from migrate import exceptions
|
||||
from migrate.changeset import SQLA_07
|
||||
from migrate.versioning import genmodel, schemadiff
|
||||
from migrate.versioning.repository import Repository
|
||||
from migrate.versioning.util import load_model
|
||||
@@ -57,14 +58,20 @@ class ControlledSchema(object):
|
||||
"""
|
||||
Remove version control from a database.
|
||||
"""
|
||||
try:
|
||||
self.table.drop()
|
||||
except (sa_exceptions.SQLError):
|
||||
raise exceptions.DatabaseNotControlledError(str(self.table))
|
||||
if SQLA_07:
|
||||
try:
|
||||
self.table.drop()
|
||||
except sa_exceptions.DatabaseError:
|
||||
raise exceptions.DatabaseNotControlledError(str(self.table))
|
||||
else:
|
||||
try:
|
||||
self.table.drop()
|
||||
except (sa_exceptions.SQLError):
|
||||
raise exceptions.DatabaseNotControlledError(str(self.table))
|
||||
|
||||
def changeset(self, version=None):
|
||||
"""API to Changeset creation.
|
||||
|
||||
|
||||
Uses self.version for start version and engine.name
|
||||
to get database name.
|
||||
"""
|
||||
@@ -110,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))
|
||||
|
||||
@@ -210,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()
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import logging
|
||||
import sqlalchemy
|
||||
|
||||
from migrate.changeset import SQLA_06
|
||||
from sqlalchemy.types import Float
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -17,8 +16,16 @@ def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None):
|
||||
:return: object which will evaluate to :keyword:`True` if there \
|
||||
are differences else :keyword:`False`.
|
||||
"""
|
||||
return SchemaDiff(metadata,
|
||||
sqlalchemy.MetaData(engine, reflect=True),
|
||||
db_metadata = sqlalchemy.MetaData(engine, reflect=True)
|
||||
|
||||
# sqlite will include a dynamically generated 'sqlite_sequence' table if
|
||||
# there are autoincrement sequences in the database; this should not be
|
||||
# compared.
|
||||
if engine.dialect.name == 'sqlite':
|
||||
if 'sqlite_sequence' in db_metadata.tables:
|
||||
db_metadata.remove(db_metadata.tables['sqlite_sequence'])
|
||||
|
||||
return SchemaDiff(metadata, db_metadata,
|
||||
labelA='model',
|
||||
labelB='database',
|
||||
excludeTables=excludeTables)
|
||||
@@ -39,11 +46,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 +58,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 +94,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 +108,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 +133,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 +146,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 +202,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 +233,7 @@ class SchemaDiff(object):
|
||||
|
||||
if cd:
|
||||
td.columns_different[col_name]=cd
|
||||
|
||||
|
||||
# XXX - index and constraint differences should
|
||||
# be checked for here
|
||||
|
||||
@@ -237,7 +244,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 +255,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 +274,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -38,7 +38,6 @@ class Template(pathed.Pathed):
|
||||
if `path` is not provided.
|
||||
"""
|
||||
pkg = 'migrate.versioning.templates'
|
||||
_manage = 'manage.py_tmpl'
|
||||
|
||||
def __new__(cls, path=None):
|
||||
if path is None:
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
from migrate.versioning.shell import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(%(defaults)s)
|
||||
@@ -7,4 +7,6 @@ del _vars['__template_name__']
|
||||
_vars.pop('repository_name', None)
|
||||
defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()])
|
||||
}}
|
||||
main({{ defaults }})
|
||||
|
||||
if __name__ == '__main__':
|
||||
main({{ defaults }})
|
||||
|
||||
@@ -26,4 +26,5 @@ conf_dict = ConfigLoader(conf_path).parser._sections['app:main']
|
||||
|
||||
# migrate supports passing url as an existing Engine instance (since 0.6.0)
|
||||
# usage: migrate -c path/to/config.ini COMMANDS
|
||||
main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
|
||||
if __name__ == '__main__':
|
||||
main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
|
||||
|
||||
@@ -18,3 +18,8 @@ version_table={{ locals().pop('version_table') }}
|
||||
# be using to ensure your updates to that database work properly.
|
||||
# This must be a list; example: ['postgres','sqlite']
|
||||
required_dbs={{ locals().pop('required_dbs') }}
|
||||
|
||||
# When creating new change scripts, Migrate will stamp the new script with
|
||||
# a version number. By default this is latest_version + 1. You can set this
|
||||
# to 'true' to tell Migrate to use the UTC timestamp instead.
|
||||
use_timestamp_numbering={{ locals().pop('use_timestamp_numbering') }}
|
||||
|
||||
@@ -18,3 +18,8 @@ version_table={{ locals().pop('version_table') }}
|
||||
# be using to ensure your updates to that database work properly.
|
||||
# This must be a list; example: ['postgres','sqlite']
|
||||
required_dbs={{ locals().pop('required_dbs') }}
|
||||
|
||||
# When creating new change scripts, Migrate will stamp the new script with
|
||||
# a version number. By default this is latest_version + 1. You can set this
|
||||
# to 'true' to tell Migrate to use the UTC timestamp instead.
|
||||
use_timestamp_numbering={{ locals().pop('use_timestamp_numbering') }}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from sqlalchemy import *
|
||||
from migrate import *
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
# Upgrade operations go here. Don't create your own engine; bind migrate_engine
|
||||
# to your metadata
|
||||
# Upgrade operations go here. Don't create your own engine; bind
|
||||
# migrate_engine to your metadata
|
||||
pass
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
# Operations to reverse the above upgrade go here.
|
||||
pass
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from sqlalchemy import *
|
||||
from migrate import *
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
# Upgrade operations go here. Don't create your own engine; bind migrate_engine
|
||||
# to your metadata
|
||||
# Upgrade operations go here. Don't create your own engine; bind
|
||||
# migrate_engine to your metadata
|
||||
pass
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
# Operations to reverse the above upgrade go here.
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
|
||||
from migrate import exceptions
|
||||
from migrate.versioning import pathed, script
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -88,9 +89,15 @@ class Collection(pathed.Pathed):
|
||||
""":returns: Latest version in Collection"""
|
||||
return max([VerNum(0)] + self.versions.keys())
|
||||
|
||||
def _next_ver_num(self, use_timestamp_numbering):
|
||||
if use_timestamp_numbering == True:
|
||||
return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
|
||||
else:
|
||||
return self.latest + 1
|
||||
|
||||
def create_new_python_version(self, description, **k):
|
||||
"""Create Python files for new version"""
|
||||
ver = self.latest + 1
|
||||
ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
|
||||
extra = str_to_filename(description)
|
||||
|
||||
if extra:
|
||||
@@ -105,14 +112,22 @@ class Collection(pathed.Pathed):
|
||||
script.PythonScript.create(filepath, **k)
|
||||
self.versions[ver] = Version(ver, self.path, [filename])
|
||||
|
||||
def create_new_sql_version(self, database, **k):
|
||||
def create_new_sql_version(self, database, description, **k):
|
||||
"""Create SQL files for new version"""
|
||||
ver = self.latest + 1
|
||||
ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
|
||||
self.versions[ver] = Version(ver, self.path, [])
|
||||
|
||||
extra = str_to_filename(description)
|
||||
|
||||
if extra:
|
||||
if extra == '_':
|
||||
extra = ''
|
||||
elif not extra.startswith('_'):
|
||||
extra = '_%s' % extra
|
||||
|
||||
# Create new files.
|
||||
for op in ('upgrade', 'downgrade'):
|
||||
filename = '%03d_%s_%s.sql' % (ver, database, op)
|
||||
filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op)
|
||||
filepath = self._version_path(filename)
|
||||
script.SqlScript.create(filepath, **k)
|
||||
self.versions[ver].add_script(filepath)
|
||||
@@ -176,18 +191,26 @@ class Version(object):
|
||||
elif path.endswith(Extensions.sql):
|
||||
self._add_script_sql(path)
|
||||
|
||||
SQL_FILENAME = re.compile(r'^(\d+)_([^_]+)_([^_]+).sql')
|
||||
SQL_FILENAME = re.compile(r'^.*\.sql')
|
||||
|
||||
def _add_script_sql(self, path):
|
||||
basename = os.path.basename(path)
|
||||
match = self.SQL_FILENAME.match(basename)
|
||||
|
||||
|
||||
if match:
|
||||
version, dbms, op = match.group(1), match.group(2), match.group(3)
|
||||
basename = basename.replace('.sql', '')
|
||||
parts = basename.split('_')
|
||||
if len(parts) < 3:
|
||||
raise exceptions.ScriptError(
|
||||
"Invalid SQL script name %s " % basename + \
|
||||
"(needs to be ###_description_database_operation.sql)")
|
||||
version = parts[0]
|
||||
op = parts[-1]
|
||||
dbms = parts[-2]
|
||||
else:
|
||||
raise exceptions.ScriptError(
|
||||
"Invalid SQL script name %s " % basename + \
|
||||
"(needs to be ###_database_operation.sql)")
|
||||
"(needs to be ###_description_database_operation.sql)")
|
||||
|
||||
# File the script into a dictionary
|
||||
self.sql.setdefault(dbms, {})[op] = script.SqlScript(path)
|
||||
|
||||
Reference in New Issue
Block a user