]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- remove add_constraint, this is not the current philosophy of the op package
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Nov 2011 18:07:40 +0000 (13:07 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Nov 2011 18:07:40 +0000 (13:07 -0500)
- document most op methods
- add support for create_index, drop_index
- remove needless arguments from drop_table
- propagate arguemnts to UniqueConstraint

alembic/context.py
alembic/ddl/impl.py
alembic/op.py
docs/build/ops.rst
tests/test_op.py

index ed0c547388b29086a6847d5356aed9399d432b97..cf19344a354cf79212b8bae15bdea93a310a17a6 100644 (file)
@@ -344,37 +344,11 @@ def run_migrations(**kw):
 
 def execute(sql):
     """Execute the given SQL using the current change context.
-    
-    In a SQL script context, the statement is emitted directly to the 
-    output stream.   There is *no* return result, however, as this
-    function is oriented towards generating a change script
-    that can run in "offline" mode.  For full interaction
-    with a connected database, use the "bind" available 
-    from the context::
-    
-        connection = get_context().bind
-    
-    Also note that any parameterized statement here *will not work*
-    in offline mode - any kind of UPDATE or DELETE needs to render
-    inline expressions.   Due to these limitations, 
-    :func:`.execute` is overall not spectacularly useful for migration 
-    scripts that wish to run in offline mode.  Consider using the Alembic 
-    directives, or if the environment is only meant to run in 
-    "online" mode, use the ``get_context().bind``.
-    
-    :param sql: Any legal SQLAlchemy expression, including:
-    
-    * a string
-    * a :func:`sqlalchemy.sql.expression.text` construct, with the caveat that
-      bound parameters won't work correctly in offline mode.
-    * a :func:`sqlalchemy.sql.expression.insert` construct.  If working 
-      in offline mode, consider using :func:`alembic.op.bulk_insert`
-      instead to support parameterization.
-    * a :func:`sqlalchemy.sql.expression.update`, :func:`sqlalchemy.sql.expression.insert`, 
-      or :func:`sqlalchemy.sql.expression.delete`  construct, with the caveat
-      that bound parameters won't work correctly in offline mode.
-    * Pretty much anything that's "executable" as described
-      in :ref:`sqlexpression_toplevel`.
+
+    The behavior of :func:`.context.execute` is the same
+    as that of :func:`.op.execute`.  Please see that
+    function's documentation for full detail including
+    caveats and limitations.
 
     This function requires that a :class:`.Context` has first been 
     made available via :func:`.configure`.
index f1dd4a70a3c20cb0849cd43b9cff463a8464e72e..3a3ffe729c39fcf59db631ad2438aeeeefc195c7 100644 (file)
@@ -99,6 +99,12 @@ class DefaultImpl(object):
     def drop_table(self, table):
         self._exec(schema.DropTable(table))
 
+    def create_index(self, index):
+        self._exec(schema.CreateIndex(index))
+
+    def drop_index(self, index):
+        self._exec(schema.DropIndex(index))
+
     def bulk_insert(self, table, rows):
         if self.as_sql:
             for row in rows:
index 49631221b1d332fe61ccb5ef0a16d01ed019b4b1..cb016e88c9161eb82600867d856f1fbf6f4f5ec2 100644 (file)
@@ -5,19 +5,20 @@ from sqlalchemy import schema, sql
 
 util.importlater.resolve_all()
 
-__all__ = [
+__all__ = sorted([
             'alter_column', 
             'add_column',
             'drop_column',
-            'add_constraint',
             'create_foreign_key', 
             'create_table',
             'drop_table',
+            'drop_index',
+            'create_index',
             'bulk_insert',
             'create_unique_constraint', 
             'get_context',
             'get_bind',
-            'execute']
+            'execute'])
 
 def _foreign_key_constraint(name, source, referent, local_cols, remote_cols):
     m = schema.MetaData()
@@ -35,21 +36,10 @@ def _foreign_key_constraint(name, source, referent, local_cols, remote_cols):
 
     return f
 
-def _ensure_table_for_constraint(name, constraint):
-    if getattr(constraint, 'parent', None) is not None:
-        return
-    if isinstance(constraint, schema.UniqueConstraint):
-        # TODO: what if constraint has Column objects already
-        columns = [schema.Column(n, NULLTYPE) for n in 
-                        constraint._pending_colargs]
-    else:
-        columns = []
-    return schema.Table(name, schema.MetaData(), *(columns + [constraint]) )
-
-def _unique_constraint(name, source, local_cols):
+def _unique_constraint(name, source, local_cols, **kw):
     t = schema.Table(source, schema.MetaData(), 
                 *[schema.Column(n, NULLTYPE) for n in local_cols])
-    return schema.UniqueConstraint(*t.c, name=name)
+    return schema.UniqueConstraint(*t.c, name=name, **kw)
 
 def _table(name, *columns, **kw):
     m = schema.MetaData()
@@ -61,6 +51,12 @@ def _table(name, *columns, **kw):
 def _column(name, type_, **kw):
     return schema.Column(name, type_, **kw)
 
+def _index(name, tablename, columns, **kw):
+    t = schema.Table(tablename, schema.MetaData(),
+        *[schema.Column(n, NULLTYPE) for n in columns]
+    )
+    return schema.Index(name, *list(t.c), **kw)
+
 def _ensure_table_for_fk(metadata, fk):
     """create a placeholder Table object for the referent of a
     ForeignKey.
@@ -102,11 +98,31 @@ def add_column(table_name, column):
     """Issue an "add column" instruction using the current change context.
     
     e.g.::
+
+        from alembic.op import add_column
+        from sqlalchemy import Column, String
+
+        add_column('organization', 
+            Column('name', String())
+        )        
+
+    The provided :class:`~sqlalchemy.schema.Column` object can also
+    specify a :class:`~sqlalchemy.schema.ForeignKey`, referencing
+    a remote table name.  Alembic will automatically generate a stub
+    "referenced" table and emit a second ALTER statement in order
+    to add the constraint separately::
     
+        from alembic.op import add_column
+        from sqlalchemy import Column, INTEGER, ForeignKey
+
         add_column('organization', 
             Column('account_id', INTEGER, ForeignKey('accounts.id'))
         )        
     
+    :param table_name: String name of the parent table.
+    :param column: a :class:`sqlalchemy.schema.Column` object
+     representing the new column.
+     
     """
 
     t = _table(table_name, column)
@@ -131,27 +147,84 @@ def drop_column(table_name, column_name):
         _column(column_name, NULLTYPE)
     )
 
-def add_constraint(table_name, constraint):
-    """Issue an "add constraint" instruction using the current change context."""
-
-    _ensure_table_for_constraint(table_name, constraint)
-    get_impl().add_constraint(
-        constraint
-    )
 
 def create_foreign_key(name, source, referent, local_cols, remote_cols):
-    """Issue a "create foreign key" instruction using the current change context."""
+    """Issue a "create foreign key" instruction using the 
+    current change context.
+
+    e.g.::
+    
+        from alembic.op import create_foreign_key
+        create_foreign_key("fk_user_address", "address", "user", ["user_id"], ["id"])
+
+    This internally generates a :class:`~sqlalchemy.schema.Table` object
+    containing the necessary columns, then generates a new 
+    :class:`~sqlalchemy.schema.ForeignKeyConstraint`
+    object which it then associates with the :class:`~sqlalchemy.schema.Table`.
+    Any event listeners associated with this action will be fired 
+    off normally.   The :class:`~sqlalchemy.schema.AddConstraint`
+    construct is ultimately used to generate the ALTER statement.
+    
+    :param name: Name of the foreign key constraint.  The name is necessary
+     so that an ALTER statement can be emitted.  For setups that
+     use an automated naming scheme such as that described at
+     `NamingConventions <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/NamingConventions>`_, 
+     ``name`` here can be ``None``, as the event listener will
+     apply the name to the constraint object when it is associated
+     with the table.
+    :param source: String name of the source table.  Currently
+     there is no support for dotted schema names.
+    :param referent: String name of the destination table. Currently
+     there is no support for dotted schema names.
+    :param local_cols: a list of string column names in the 
+     source table.
+    :param remote_cols: a list of string column names in the
+     remote table.
+    
+    """
 
     get_impl().add_constraint(
                 _foreign_key_constraint(name, source, referent, 
                         local_cols, remote_cols)
             )
 
-def create_unique_constraint(name, source, local_cols):
-    """Issue a "create unique constraint" instruction using the current change context."""
+def create_unique_constraint(name, source, local_cols, **kw):
+    """Issue a "create unique constraint" instruction using the current change context.
+
+    e.g.::
+    
+        from alembic.op import create_unique_constraint
+        create_unique_constraint("uq_user_name", "user", ["name"])
+
+    This internally generates a :class:`~sqlalchemy.schema.Table` object
+    containing the necessary columns, then generates a new 
+    :class:`~sqlalchemy.schema.UniqueConstraint`
+    object which it then associates with the :class:`~sqlalchemy.schema.Table`.
+    Any event listeners associated with this action will be fired 
+    off normally.   The :class:`~sqlalchemy.schema.AddConstraint`
+    construct is ultimately used to generate the ALTER statement.
+    
+    :param name: Name of the unique constraint.  The name is necessary
+     so that an ALTER statement can be emitted.  For setups that
+     use an automated naming scheme such as that described at
+     `NamingConventions <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/NamingConventions>`_, 
+     ``name`` here can be ``None``, as the event listener will
+     apply the name to the constraint object when it is associated
+     with the table.
+    :param source: String name of the source table.  Currently
+     there is no support for dotted schema names.
+    :param local_cols: a list of string column names in the 
+     source table.
+    :param deferrable: optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when
+     issuing DDL for this constraint.
+    :param initially: optional string. If set, emit INITIALLY <value> when issuing DDL
+     for this constraint.
+    
+    """
 
     get_impl().add_constraint(
-                _unique_constraint(name, source, local_cols)
+                _unique_constraint(name, source, local_cols, 
+                    **kw)
             )
 
 def create_table(name, *columns, **kw):
@@ -177,7 +250,7 @@ def create_table(name, *columns, **kw):
         _table(name, *columns, **kw)
     )
 
-def drop_table(name, *columns, **kw):
+def drop_table(name):
     """Issue a "drop table" instruction using the current change context.
     
     
@@ -187,9 +260,34 @@ def drop_table(name, *columns, **kw):
         
     """
     get_impl().drop_table(
-        _table(name, *columns, **kw)
+        _table(name)
     )
 
+def create_index(name, tablename, *columns, **kw):
+    """Issue a "create index" instruction using the current change context.
+    
+    e.g.::
+        
+        from alembic.op import create_index
+        create_index('ik_test', 't1', ['foo', 'bar'])
+
+    """
+
+    get_impl().create_index(
+        _index(name, tablename, *columns, **kw)
+    )
+
+def drop_index(name):
+    """Issue a "drop index" instruction using the current change context.
+    
+    
+    e.g.::
+    
+        drop_index("accounts")
+        
+    """
+    get_impl().drop_index(_index(name, 'foo', []))
+
 def bulk_insert(table, rows):
     """Issue a "bulk insert" operation using the current change context.
     
@@ -218,7 +316,37 @@ def execute(sql):
     """Execute the given SQL using the current change context.
     
     In a SQL script context, the statement is emitted directly to the 
-    output stream.
+    output stream.   There is *no* return result, however, as this
+    function is oriented towards generating a change script
+    that can run in "offline" mode.  For full interaction
+    with a connected database, use the "bind" available 
+    from the context::
+    
+        from alembic.op import get_bind
+        connection = get_bind()
+    
+    Also note that any parameterized statement here *will not work*
+    in offline mode - any kind of UPDATE or DELETE needs to render
+    inline expressions.   Due to these limitations, 
+    :func:`.execute` is overall not spectacularly useful for migration 
+    scripts that wish to run in offline mode.  Consider using the Alembic 
+    directives, or if the environment is only meant to run in 
+    "online" mode, use the ``get_context().bind``.
+    
+    :param sql: Any legal SQLAlchemy expression, including:
+    
+    * a string
+    * a :func:`sqlalchemy.sql.expression.text` construct, with the caveat that
+      bound parameters won't work correctly in offline mode.
+    * a :func:`sqlalchemy.sql.expression.insert` construct.  If working 
+      in offline mode, consider using :func:`alembic.op.bulk_insert`
+      instead to support parameterization.
+    * a :func:`sqlalchemy.sql.expression.update`, :func:`sqlalchemy.sql.expression.insert`, 
+      or :func:`sqlalchemy.sql.expression.delete`  construct, with the caveat
+      that bound parameters won't work correctly in offline mode.
+    * Pretty much anything that's "executable" as described
+      in :ref:`sqlexpression_toplevel`.
+
     
     """
     get_impl().execute(sql)
index 92338a5b3e0e7b62b16e5640f851a5d05427089b..d4ed87d9da6130c912b0174535baf3add26ee652 100644 (file)
@@ -10,6 +10,17 @@ The directives here are used within user-defined migration files,
 within the ``upgrade()`` and ``downgrade()`` functions, as well as 
 any functions further invoked by those.  
 
+A key design philosophy to the :mod:`alembic.op` functions is that
+to the greatest degree possible, they internally generate the 
+appropriate SQLAlchemy metadata, typically involving
+:class:`~sqlalchemy.schema.Table` and :class:`~sqlalchemy.schema.Constraint`
+objects.  This so that migration instructions can be 
+given in terms of just the string names and/or flags involved.   
+The exceptions to this
+rule include the :func:`.op.add_column` and :func:`.op.create_table`
+directives, which require full :class:`~sqlalchemy.schema.Column`
+objects, though the table metadata is still generated here.
+
 The functions here all require that a :class:`.Context` has been
 configured within the ``env.py`` script.  Under normal circumstances
 this is always the case, as the migration scripts are invoked via
index 987b25b331be7af531e4d4fa9bb7a1b8f20d596f..d46f001d0609caf8ad0928d047353b5f72b62434 100644 (file)
@@ -24,6 +24,14 @@ def test_add_column_fk():
         "ALTER TABLE t1 ADD FOREIGN KEY(c1) REFERENCES c2 (id)"
     )
 
+def test_add_column_fk_self_referential():
+    context = _op_fixture()
+    op.add_column('t1', Column('c1', Integer, ForeignKey('t1.c2'), nullable=False))
+    context.assert_(
+        "ALTER TABLE t1 ADD COLUMN c1 INTEGER NOT NULL",
+        "ALTER TABLE t1 ADD FOREIGN KEY(c1) REFERENCES t1 (c2)"
+    )
+
 def test_drop_column():
     context = _op_fixture()
     op.drop_column('t1', 'c1')
@@ -62,26 +70,27 @@ def test_add_unique_constraint():
         "ALTER TABLE t1 ADD CONSTRAINT uk_test UNIQUE (foo, bar)"
     )
 
-def test_add_unique_constraint_table_detached():
+def test_create_index():
     context = _op_fixture()
-    op.add_constraint('t1', UniqueConstraint('foo', 'bar', name="uk_test"))
+    op.create_index('ik_test', 't1', ['foo', 'bar'])
     context.assert_(
-        "ALTER TABLE t1 ADD CONSTRAINT uk_test UNIQUE (foo, bar)"
+        "CREATE INDEX ik_test ON t1 (foo, bar)"
     )
 
-def test_add_unique_constraint_table_attached():
+
+def test_drop_index():
     context = _op_fixture()
-    uq = UniqueConstraint('foo', 'bar', name="uk_test")
-    t1 = Table('t1', MetaData(),
-        Column('foo', Integer),
-        Column('bar', Integer),
-        uq
-    )
-    op.add_constraint('t1', uq)
+    op.drop_index('ik_test')
     context.assert_(
-        "ALTER TABLE t1 ADD CONSTRAINT uk_test UNIQUE (foo, bar)"
+        "DROP INDEX ik_test"
     )
 
+def test_drop_table():
+    context = _op_fixture()
+    op.drop_table('tb_test')
+    context.assert_(
+        "DROP TABLE tb_test"
+    )
 
 def test_create_table_fk_and_schema():
     context = _op_fixture()