Packages update

This commit is contained in:
Ruud
2012-02-11 16:28:06 +01:00
parent 3bbf1126c3
commit 02e01fb2d6
217 changed files with 26395 additions and 21194 deletions
+9 -8
View File
@@ -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")
+105 -74
View File
@@ -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
+13 -2
View File
@@ -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)
+14 -7
View File
@@ -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()
+42 -35
View File
@@ -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)
+8 -8
View File
@@ -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
-1
View File
@@ -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
+31 -8
View File
@@ -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)