update docs, delete obsolete code in constraints

This commit is contained in:
iElectric 2009-06-12 22:43:02 +00:00
parent 431d22be61
commit cc82a1ad12
8 changed files with 127 additions and 212 deletions

View File

@ -116,12 +116,14 @@ Module :mod:`repository <migrate.versioning.repository>`
.. automodule:: migrate.versioning.repository
:members:
:synopsis: SQLAlchemy migrate repository management
:member-order: groupwise
Module :mod:`schema <migrate.versioning.schema>`
------------------------------------------------
.. automodule:: migrate.versioning.schema
:members:
:member-order: groupwise
:synopsis: Database schema management
Module :mod:`schemadiff <migrate.versioning.schemadiff>`
@ -136,15 +138,18 @@ Module :mod:`script <migrate.versioning.script>`
.. automodule:: migrate.versioning.script.base
:synopsis: Script utilities
:member-order: groupwise
:members:
.. automodule:: migrate.versioning.script.py
:members:
:member-order: groupwise
:inherited-members:
:show-inheritance:
.. automodule:: migrate.versioning.script.sql
:members:
:member-order: groupwise
:show-inheritance:
:inherited-members:
@ -167,4 +172,5 @@ Module :mod:`version <migrate.versioning.version>`
.. automodule:: migrate.versioning.version
:members:
:member-order: groupwise
:synopsis: Version management

View File

@ -13,7 +13,7 @@ from migrate.changeset import constraint, exceptions
SchemaIterator = sa.engine.SchemaIterator
class RawAlterTableVisitor(object):
class AlterTableVisitor(SchemaIterator):
"""Common operations for ``ALTER TABLE`` statements."""
def _to_table(self, param):
@ -24,13 +24,6 @@ class RawAlterTableVisitor(object):
ret = param
return ret
def _to_table_name(self, param):
"""Returns the table name for the given param object."""
ret = self._to_table(param)
if isinstance(ret, sa.Table):
ret = ret.fullname
return ret
def start_alter_table(self, param):
"""Returns the start of an ``ALTER TABLE`` SQL-Statement.
@ -70,11 +63,6 @@ class RawAlterTableVisitor(object):
return ret
class AlterTableVisitor(SchemaIterator, RawAlterTableVisitor):
"""Common operations for ``ALTER TABLE`` statements"""
pass
class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
"""Extends ansisql generator for column creation (alter table add col)"""
@ -82,7 +70,7 @@ class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
"""Create a column (table already exists).
:param column: column object
:type column: :class:`sqlalchemy.Column`
:type column: :class:`sqlalchemy.Column` instance
"""
table = self.start_alter_table(column)
self.append("ADD ")
@ -94,7 +82,7 @@ class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
"""Default table visitor, does nothing.
:param table: table object
:type table: :class:`sqlalchemy.Table`
:type table: :class:`sqlalchemy.Table` instance
"""
pass
@ -124,7 +112,7 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
All items may be renamed. Columns can also have many of their properties -
type, for example - changed.
Each function is passed a tuple, containing (object,name); where
Each function is passed a tuple, containing (object, name); where
object is a type of object you'd expect for that function
(ie. table for visit_table) and name is the object's new
name. NONE means the name is unchanged.
@ -137,6 +125,14 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
self.append("RENAME TO %s" % self.preparer.quote(newname, table.quote))
self.execute()
def visit_index(self, param):
"""Rename an index"""
index, newname = param
self.append("ALTER INDEX %s RENAME TO %s" %
(self.preparer.quote(self._validate_identifier(index.name, True), index.quote),
self.preparer.quote(self._validate_identifier(newname, True) , index.quote)))
self.execute()
def visit_column(self, delta):
"""Rename/change a column."""
# ALTER COLUMN is implemented as several ALTER statements
@ -152,14 +148,12 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
if 'name' in keys:
self._run_subvisit(delta, self._visit_column_name)
def _run_subvisit(self, delta, func, col_name=None, table_name=None):
if table_name is None:
table_name = self._to_table(delta.table)
if col_name is None:
col_name = delta.current_name
ret = func(table_name, col_name, delta)
def _run_subvisit(self, delta, func):
"""Runs visit method based on what needs to be changed on column"""
table = self._to_table(delta.table)
col_name = delta.current_name
ret = func(table, col_name, delta)
self.execute()
return ret
def _visit_column_foreign_key(self, delta):
table = delta.table
@ -183,10 +177,10 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
cons.drop()
cons.create()
def _visit_column_nullable(self, table_name, col_name, delta):
def _visit_column_nullable(self, table, col_name, delta):
nullable = delta['nullable']
table = self._to_table(delta)
self.start_alter_table(table_name)
table = self._to_table(table)
self.start_alter_table(table)
# TODO: use preparer.format_column
self.append("ALTER COLUMN %s " % self.preparer.quote_identifier(col_name))
if nullable:
@ -194,50 +188,41 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
else:
self.append("SET NOT NULL")
def _visit_column_default(self, table_name, col_name, delta):
def _visit_column_default(self, table, col_name, delta):
server_default = delta['server_default']
# Dummy column: get_col_default_string needs a column for some
# reason
dummy = sa.Column(None, None, server_default=server_default)
default_text = self.get_column_default_string(dummy)
self.start_alter_table(table_name)
self.start_alter_table(table)
# TODO: use preparer.format_column
self.append("ALTER COLUMN %s " % self.preparer.quote_identifier(col_name))
if default_text is not None:
# TODO: format needed?
self.append("SET DEFAULT %s" % default_text)
else:
self.append("DROP DEFAULT")
def _visit_column_type(self, table_name, col_name, delta):
type = delta['type']
if not isinstance(type, sa.types.AbstractType):
def _visit_column_type(self, table, col_name, delta):
type_ = delta['type']
if not isinstance(type_, sa.types.AbstractType):
# It's the class itself, not an instance... make an
# instance
type = type()
type_text = type.dialect_impl(self.dialect).get_col_spec()
self.start_alter_table(table_name)
type_ = type_()
type_text = type_.dialect_impl(self.dialect).get_col_spec()
self.start_alter_table(table)
# TODO: does type need formating?
# TODO: use preparer.format_column
self.append("ALTER COLUMN %s TYPE %s" %
(self.preparer.quote_identifier(col_name), type_text))
def _visit_column_name(self, table_name, col_name, delta):
def _visit_column_name(self, table, col_name, delta):
new_name = delta['name']
self.start_alter_table(table_name)
self.start_alter_table(table)
# TODO: use preparer.format_column
self.append('RENAME COLUMN %s TO %s' % \
(self.preparer.quote_identifier(col_name),
self.preparer.quote_identifier(new_name)))
def visit_index(self, param):
"""Rename an index; #36"""
index, newname = param
self.append("ALTER INDEX %s RENAME TO %s" %
(self.preparer.quote(self._validate_identifier(index.name, True), index.quote),
self.preparer.quote(self._validate_identifier(newname, True) , index.quote)))
self.execute()
class ANSIConstraintCommon(AlterTableVisitor):
"""
@ -250,12 +235,10 @@ class ANSIConstraintCommon(AlterTableVisitor):
"""Gets a name for the given constraint.
If the name is already set it will be used otherwise the
constraint's :meth:`autoname
<migrate.changeset.constraint.ConstraintChangeset.autoname>`
constraint's :meth:`autoname <migrate.changeset.constraint.ConstraintChangeset.autoname>`
method is used.
:param cons: constraint object
:type cons: :class:`migrate.changeset.constraint.ConstraintChangeset`
"""
if cons.name is not None:
ret = cons.name
@ -331,9 +314,7 @@ class ANSIFKGenerator(AlterTableVisitor, SchemaGenerator):
"""Extends ansisql generator for column creation (alter table add col)"""
def __init__(self, *args, **kwargs):
self.fk = kwargs.get('fk', None)
if self.fk:
del kwargs['fk']
self.fk = kwargs.pop('fk', None)
super(ANSIFKGenerator, self).__init__(*args, **kwargs)
def visit_column(self, column):

View File

@ -6,10 +6,9 @@ from sqlalchemy import schema
class ConstraintChangeset(object):
"""Base class for Constraint classes.
"""
"""Base class for Constraint classes."""
def _normalize_columns(self, cols, fullname=False):
def _normalize_columns(self, cols, table_name=False):
"""Given: column objects or names; return col names and
(maybe) a table"""
colnames = []
@ -18,62 +17,48 @@ class ConstraintChangeset(object):
if isinstance(col, schema.Column):
if col.table is not None and table is None:
table = col.table
if fullname:
if table_name:
col = '.'.join((col.table.name, col.name))
else:
col = col.name
colnames.append(col)
return colnames, table
def create(self, engine=None):
def create(self, *args, **kwargs):
"""Create the constraint in the database.
:param engine: the database engine to use. If this is
:keyword:`None` the instance's engine will be used
:param engine: the database engine to use. If this is \
:keyword:`None` the instance's engine will be used
:type engine: :class:`sqlalchemy.engine.base.Engine`
"""
if engine is None:
engine = self.engine
engine.create(self)
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintgenerator')
_engine_run_visitor(self.table.bind, visitorcallable, self)
def drop(self, engine=None):
def drop(self, *args, **kwargs):
"""Drop the constraint from the database.
:param engine: the database engine to use. If this is
:keyword:`None` the instance's engine will be used
:type engine: :class:`sqlalchemy.engine.base.Engine`
"""
if engine is None:
engine = self.engine
engine.drop(self)
def _derived_metadata(self):
return self.table._derived_metadata()
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintdropper')
_engine_run_visitor(self.table.bind, visitorcallable, self)
self.columns.clear()
return self
def accept_schema_visitor(self, visitor, *p, **k):
"""
:raises: :exc:`NotImplementedError` if this method is not \
overridden by a subclass
"""
raise NotImplementedError()
def _accept_schema_visitor(self, visitor, func, *p, **k):
"""Call the visitor only if it defines the given function"""
try:
func = getattr(visitor, func)
except AttributeError:
return
return func(self)
return getattr(visitor, self._func)(self)
def autoname(self):
"""Automatically generate a name for the constraint instance.
Subclasses must implement this method.
:raises: :exc:`NotImplementedError` if this method is not \
overridden by a subclass
"""
raise NotImplementedError()
def _engine_run_visitor(engine, visitorcallable, element, **kwargs):
@ -87,6 +72,8 @@ def _engine_run_visitor(engine, visitorcallable, element, **kwargs):
class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint):
"""Primary key constraint class."""
_func = 'visit_migrate_primary_key_constraint'
def __init__(self, *cols, **kwargs):
colnames, table = self._normalize_columns(cols)
table = kwargs.pop('table', table)
@ -94,86 +81,47 @@ class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint):
if table is not None:
self._set_parent(table)
def _set_parent(self, table):
self.table = table
return super(ConstraintChangeset, self)._set_parent(table)
def create(self, *args, **kwargs):
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintgenerator')
_engine_run_visitor(self.table.bind, visitorcallable, self)
def autoname(self):
"""Mimic the database's automatic constraint names"""
ret = "%(table)s_pkey" % dict(table=self.table.name)
return ret
def drop(self, *args, **kwargs):
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintdropper')
_engine_run_visitor(self.table.bind, visitorcallable, self)
self.columns.clear()
return self
def accept_schema_visitor(self, visitor, *p, **k):
func = 'visit_migrate_primary_key_constraint'
return self._accept_schema_visitor(visitor, func, *p, **k)
return "%s_pkey" % self.table.name
class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint):
"""Foreign key constraint class."""
_func = 'visit_migrate_foreign_key_constraint'
def __init__(self, columns, refcolumns, *p, **k):
colnames, table = self._normalize_columns(columns)
table = k.pop('table', table)
refcolnames, reftable = self._normalize_columns(refcolumns,
fullname=True)
table_name=True)
super(ForeignKeyConstraint, self).__init__(colnames, refcolnames, *p,
**k)
if table is not None:
self._set_parent(table)
def _get_referenced(self):
@property
def referenced(self):
return [e.column for e in self.elements]
referenced = property(_get_referenced)
def _get_reftable(self):
@property
def reftable(self):
return self.referenced[0].table
reftable = property(_get_reftable)
def autoname(self):
"""Mimic the database's automatic constraint names"""
ret = "%(table)s_%(reftable)s_fkey"%dict(
ret = "%(table)s_%(reftable)s_fkey" % dict(
table=self.table.name,
reftable=self.reftable.name,
)
reftable=self.reftable.name,)
return ret
def create(self, *args, **kwargs):
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintgenerator')
_engine_run_visitor(self.table.bind, visitorcallable, self)
return self
def drop(self, *args, **kwargs):
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintdropper')
_engine_run_visitor(self.table.bind, visitorcallable, self)
self.columns.clear()
return self
def accept_schema_visitor(self, visitor, *p, **k):
func = 'visit_migrate_foreign_key_constraint'
return self._accept_schema_visitor(visitor, func, *p, **k)
class CheckConstraint(ConstraintChangeset, schema.CheckConstraint):
"""Check constraint class."""
_func = 'visit_migrate_check_constraint'
def __init__(self, sqltext, *args, **kwargs):
cols = kwargs.pop('columns')
colnames, table = self._normalize_columns(cols)
@ -184,28 +132,6 @@ class CheckConstraint(ConstraintChangeset, schema.CheckConstraint):
self._set_parent(table)
self.colnames = colnames
def _set_parent(self, table):
self.table = table
return super(ConstraintChangeset, self)._set_parent(table)
def create(self):
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintgenerator')
_engine_run_visitor(self.table.bind, visitorcallable, self)
def drop(self):
from migrate.changeset.databases.visitor import get_engine_visitor
visitorcallable = get_engine_visitor(self.table.bind,
'constraintdropper')
_engine_run_visitor(self.table.bind, visitorcallable, self)
self.columns.clear()
return self
def autoname(self):
return "%(table)s_%(cols)s_check" % \
{"table": self.table.name, "cols": "_".join(self.colnames)}
def accept_schema_visitor(self, visitor, *args, **kwargs):
func = 'visit_migrate_check_constraint'
return self._accept_schema_visitor(visitor, func, *args, **kwargs)
dict(table=self.table.name, cols="_".join(self.colnames))

View File

@ -3,8 +3,8 @@
implementations.
"""
__all__=[
'postgres',
'sqlite',
'mysql',
'oracle',
'postgres',
'sqlite',
'mysql',
'oracle',
]

View File

@ -2,9 +2,10 @@
MySQL database specific implementations of changeset classes.
"""
from migrate.changeset import ansisql, exceptions
from sqlalchemy.databases import mysql as sa_base
#import sqlalchemy as sa
from migrate.changeset import ansisql, exceptions
MySQLSchemaGenerator = sa_base.MySQLSchemaGenerator

View File

@ -9,8 +9,7 @@ import sqlalchemy as sa
OracleSchemaGenerator = sa_base.OracleSchemaGenerator
class OracleColumnGenerator(OracleSchemaGenerator,
ansisql.ANSIColumnGenerator):
class OracleColumnGenerator(OracleSchemaGenerator, ansisql.ANSIColumnGenerator):
pass
@ -40,7 +39,7 @@ class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
if 'name' in keys:
self._run_subvisit(delta, self._visit_column_name)
def _visit_column_change(self, table_name, col_name, delta):
def _visit_column_change(self, table, col_name, delta):
if not hasattr(delta, 'result_column'):
# Oracle needs the whole column definition, not just a
# lone name/type
@ -76,8 +75,7 @@ class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
if dropdefault_hack:
column.server_default = None
# TODO: format from table
self.start_alter_table(self.preparer.quote(table_name, True))
self.start_alter_table(self.preparer.format_table(table))
self.append("MODIFY ")
self.append(colspec)

View File

@ -7,6 +7,7 @@ import sqlalchemy
from migrate.changeset.databases.visitor import get_engine_visitor
__all__ = [
'create_column',
'drop_column',
@ -37,10 +38,18 @@ def drop_column(column, table=None, *p, **k):
def rename_table(table, name, engine=None):
"""Rename a table, given the table's current name and the new
name.
"""Rename a table.
If Table instance is given, engine is not used.
API to :meth:`table.rename`
:param table: Table to be renamed
:param name: new name
:param engine: Engine instance
:type table: string or Table instance
:type name: string
:type engine: obj
"""
table = _to_table(table, engine)
table.rename(name)
@ -49,15 +58,24 @@ def rename_table(table, name, engine=None):
def rename_index(index, name, table=None, engine=None):
"""Rename an index.
Takes an index name/object, a table name/object, and an
engine. Engine and table aren't required if an index object is
given.
If Index and Table object instances are given,
table and engine are not used.
API to :meth:`index.rename`
:param index: Index to be renamed
:param name: new name
:param table: Table to which Index is reffered
:param engine: Engine instance
:type index: string or Index instance
:type name: string
:type table: string or Table instance
:type engine: obj
"""
index = _to_index(index, table, engine)
index.rename(name)
def alter_column(*p, **k):
"""Alter a column.
@ -70,10 +88,12 @@ def alter_column(*p, **k):
col = p[0]
else:
col = None
if 'table' not in k:
k['table'] = col.table
if 'engine' not in k:
k['engine'] = k['table'].bind
engine = k['engine']
delta = _ColumnDelta(*p, **k)
visitorcallable = get_engine_visitor(engine, 'schemachanger')
@ -102,8 +122,10 @@ def alter_column(*p, **k):
def _to_table(table, engine=None):
"""Return if instance of Table, else construct new with metadata"""
if isinstance(table, sqlalchemy.Table):
return table
# Given: table name, maybe an engine
meta = sqlalchemy.MetaData()
if engine is not None:
@ -112,8 +134,10 @@ def _to_table(table, engine=None):
def _to_index(index, table=None, engine=None):
"""Return if instance of Index, else construct new with metadata"""
if isinstance(index, sqlalchemy.Index):
return index
# Given: index name; table name required
table = _to_table(table, engine)
ret = sqlalchemy.Index(index)
@ -149,13 +173,10 @@ class _WrapRename(object):
self.name = name
def accept_schema_visitor(self, visitor):
if isinstance(self.item, sqlalchemy.Table):
suffix = 'table'
elif isinstance(self.item, sqlalchemy.Column):
suffix = 'column'
elif isinstance(self.item, sqlalchemy.Index):
suffix = 'index'
"""Map Class (Table, Index, Column) to visitor function"""
suffix = self.item.__class__.__name__.lower()
funcname = 'visit_%s' % suffix
func = getattr(visitor, funcname)
param = self.item, self.name
return func(param)
@ -207,14 +228,6 @@ class _ColumnDelta(dict):
'primary_key',
'foreign_key')
@property
def table_name(self):
if isinstance(self._table, basestring):
ret = self._table
else:
ret = self._table.name
return ret
@property
def table(self):
if isinstance(self._table, sqlalchemy.Table):
@ -310,16 +323,6 @@ class ChangesetTable(object):
column = sqlalchemy.Column(str(column))
column.drop(table=self)
def _meta_key(self):
return sqlalchemy.schema._get_table_key(self.name, self.schema)
def deregister(self):
"""Remove this table from its metadata"""
key = self._meta_key()
meta = self.metadata
if key in meta.tables:
del meta.tables[key]
def rename(self, name, *args, **kwargs):
"""Rename this table.
@ -337,16 +340,15 @@ class ChangesetTable(object):
self.name = name
self._set_parent(meta)
def _get_fullname(self):
"""Fullname should always be up to date"""
# Copied from Table constructor
if self.schema is not None:
ret = "%s.%s" % (self.schema, self.name)
else:
ret = self.name
return ret
def _meta_key(self):
return sqlalchemy.schema._get_table_key(self.name, self.schema)
fullname = property(_get_fullname, (lambda self, val: None))
def deregister(self):
"""Remove this table from its metadata"""
key = self._meta_key()
meta = self.metadata
if key in meta.tables:
del meta.tables[key]
class ChangesetColumn(object):
@ -384,7 +386,7 @@ class ChangesetColumn(object):
visitorcallable = get_engine_visitor(engine, 'columngenerator')
engine._run_visitor(visitorcallable, self, *args, **kwargs)
#add in foreign keys
# add in foreign keys
if self.foreign_keys:
for fk in self.foreign_keys:
visitorcallable = get_engine_visitor(engine,

View File

@ -8,6 +8,7 @@ from migrate.changeset import *
from test import fixture
class TestConstraint(fixture.DB):
level = fixture.DB.CONNECT
@ -37,7 +38,7 @@ class TestConstraint(fixture.DB):
def _define_pk(self, *cols):
# Add a pk by creating a PK constraint
pk = PrimaryKeyConstraint(table=self.table, *cols)
self.assertEquals(list(pk.columns),list(cols))
self.assertEquals(list(pk.columns), list(cols))
if self.url.startswith('oracle'):
# Can't drop Oracle PKs without an explicit name
pk.name = 'fgsfds'
@ -54,7 +55,7 @@ class TestConstraint(fixture.DB):
pk.drop()
self.refresh_table()
#self.assertEquals(list(self.table.primary_key),list())
self.assertEquals(len(self.table.primary_key),0)
self.assertEquals(len(self.table.primary_key), 0)
self.assert_(isinstance(self.table.primary_key,
schema.PrimaryKeyConstraint),self.table.primary_key.__class__)
return pk
@ -75,7 +76,7 @@ class TestConstraint(fixture.DB):
if self.url.startswith('mysql'):
# MySQL FKs need an index
index = Index('index_name',self.table.c.fkey)
index = Index('index_name', self.table.c.fkey)
index.create()
if self.url.startswith('oracle'):
# Oracle constraints need a name