]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- rename autogenerate_metadata to target_metadata, autogenerate_sqlalchemy_prefix
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 28 Nov 2011 20:50:14 +0000 (15:50 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 28 Nov 2011 20:50:14 +0000 (15:50 -0500)
to sqlalchemy_module_prefix
- add create_check_constraint() directive

alembic/autogenerate.py
alembic/context.py
alembic/op.py
alembic/templates/generic/env.py
alembic/templates/multidb/env.py
alembic/templates/pylons/env.py
docs/build/tutorial.rst
tests/test_autogenerate.py
tests/test_op.py

index b6811fabb3f9bde0c438657726e347e932e5d47d..bae14af17a44f2a3166e932c138f613e52ef1413 100644 (file)
@@ -14,7 +14,7 @@ log = logging.getLogger(__name__)
 # top level
 
 def produce_migration_diffs(template_args, imports):
-    metadata = _context_opts['autogenerate_metadata']
+    metadata = _context_opts['target_metadata']
     if metadata is None:
         raise util.CommandError(
                 "Can't proceed with --autogenerate option; environment "
@@ -347,7 +347,7 @@ def _modify_col(tname, cname,
     return text
 
 def _autogenerate_prefix():
-    return _context_opts['autogenerate_sqlalchemy_prefix'] or ''
+    return _context_opts['sqlalchemy_module_prefix'] or ''
 
 def _render_column(column, autogen_context):
     opts = []
index 0ac9cb9ab1266cb1c5d8db890a246e705ad37fd7..51a060e97412309718cb6be03ab7b72fc7bcc10d 100644 (file)
@@ -309,12 +309,12 @@ def configure(
         output_buffer=None,
         starting_rev=None,
         tag=None,
-        autogenerate_metadata=None,
+        target_metadata=None,
         compare_type=False,
         compare_server_default=False,
         upgrade_token="upgrades",
         downgrade_token="downgrades",
-        autogenerate_sqlalchemy_prefix="sa.",
+        sqlalchemy_module_prefix="sa.",
     ):
     """Configure the migration environment.
 
@@ -351,11 +351,12 @@ def configure(
      ``--sql`` mode.
     :param tag: a string tag for usage by custom ``env.py`` scripts.  Set via
      the ``--tag`` option, can be overridden here.
-    :param autogenerate_metadata: a :class:`sqlalchemy.schema.MetaData` object that
+    :param target_metadata: a :class:`sqlalchemy.schema.MetaData` object that
      will be consulted if the ``--autogenerate`` option is passed to the 
      "alembic revision" command.  The tables present will be compared against
      what is locally available on the target :class:`~sqlalchemy.engine.base.Connection`
      to produce candidate upgrade/downgrade operations.
+     
     :param compare_type: Indicates type comparison behavior during an autogenerate
      operation.  Defaults to ``False`` which disables type comparison.  Set to 
      ``True`` to turn on default type comparison, which has varied accuracy depending
@@ -371,7 +372,12 @@ def configure(
             # False if not, or None to allow the default implementation
             # to compare these types
             pass
-
+    
+     ``inspected_column`` is a dictionary structure as returned by
+     :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas
+     ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from
+     the local model environment.
+     
      A return value of ``None`` indicates to allow default type comparison to
      proceed.
 
@@ -392,19 +398,29 @@ def configure(
             # to compare these defaults
             pass
 
+     ``inspected_column`` is a dictionary structure as returned by
+     :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas
+     ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from
+     the local model environment.
+
      A return value of ``None`` indicates to allow default server default comparison 
      to proceed.  Note that some backends such as Postgresql actually execute
      the two defaults on the database side to compare for equivalence.
 
     :param upgrade_token: when running "alembic revision" with the ``--autogenerate``
      option, the text of the candidate upgrade operations will be present in this
-     template variable when script.py.mako is rendered.
+     template variable when ``script.py.mako`` is rendered.  Defaults to ``upgrades``.
     :param downgrade_token: when running "alembic revision" with the ``--autogenerate``
      option, the text of the candidate downgrade operations will be present in this
-     template variable when script.py.mako is rendered.
-    :param autogenerate_sqlalchemy_prefix: When autogenerate refers to SQLAlchemy 
+     template variable when ``script.py.mako`` is rendered.  Defaults to ``downgrades``.
+     
+    :param sqlalchemy_module_prefix: When autogenerate refers to SQLAlchemy 
      :class:`~sqlalchemy.schema.Column` or type classes, this prefix will be used
-     (i.e. ``sa.Column("somename", sa.Integer)``)
+     (i.e. ``sa.Column("somename", sa.Integer)``)  Defaults to "``sa.``".
+     Can be ``None`` to indicate no prefix.  
+     Note that when dialect-specific types are rendered, autogenerate
+     will render them using the dialect module name, i.e. ``mssql.BIT()``, 
+     ``postgresql.UUID()``.
 
     """
 
@@ -431,10 +447,10 @@ def configure(
         opts['starting_rev'] = starting_rev
     if tag:
         opts['tag'] = tag
-    opts['autogenerate_metadata'] = autogenerate_metadata
+    opts['target_metadata'] = target_metadata
     opts['upgrade_token'] = upgrade_token
     opts['downgrade_token'] = downgrade_token
-    opts['autogenerate_sqlalchemy_prefix'] = autogenerate_sqlalchemy_prefix
+    opts['sqlalchemy_module_prefix'] = sqlalchemy_module_prefix
     _context = Context(
                         dialect, _script, connection, 
                         opts['fn'],
index 227dc9c25d805a28b6f2afdbd314057c8f8703c7..848e40baee08b9bb12776a30f65f4254d398363a 100644 (file)
@@ -1,7 +1,7 @@
 from alembic import util
 from alembic.ddl import impl
 from alembic.context import get_impl, get_context
-from sqlalchemy.types import NULLTYPE
+from sqlalchemy.types import NULLTYPE, Integer
 from sqlalchemy import schema, sql
 
 __all__ = sorted([
@@ -18,6 +18,7 @@ __all__ = sorted([
             'bulk_insert',
             'rename_table',
             'create_unique_constraint', 
+            'create_check_constraint',
             'get_context',
             'get_bind',
             'execute'])
@@ -47,6 +48,13 @@ def _unique_constraint(name, source, local_cols, **kw):
     t.append_constraint(uq)
     return uq
 
+def _check_constraint(name, source, condition, **kw):
+    t = schema.Table(source, schema.MetaData(), 
+                schema.Column('x', Integer))
+    ck = schema.CheckConstraint(condition, name=name, **kw)
+    t.append_constraint(ck)
+    return ck
+
 def _table(name, *columns, **kw):
     m = schema.MetaData()
     t = schema.Table(name, m, *columns, **kw)
@@ -336,6 +344,46 @@ def create_unique_constraint(name, source, local_cols, **kw):
                     **kw)
             )
 
+def create_check_constraint(name, source, condition, **kw):
+    """Issue a "create check constraint" instruction using the current change context.
+    
+    e.g.::
+    
+        from alembic.op import create_check_constraint
+        from sqlalchemy.sql import column, func
+        
+        create_check_constraint(
+            "ck_user_name_len",
+            "user", 
+            func.len(column('name')) > 5
+        )
+    
+    CHECK constraints are usually against a SQL expression, so ad-hoc
+    table metadata is usually needed.   The function will convert the given 
+    arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound 
+    to an anonymous table in order to emit the CREATE statement.
+
+    :param name: Name of the check 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 condition: SQL expression that's the condition of the constraint.  
+     Can be a string or SQLAlchemy expression language structure.
+    :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(
+        _check_constraint(name, source, condition, **kw)
+    )
+
 def create_table(name, *columns, **kw):
     """Issue a "create table" instruction using the current change context.
     
index 5095f0be13c1ba4932d38f314badada62c98df33..4bc6065a1d2bc27439d3f8753b3bd06e68b71011 100644 (file)
@@ -13,8 +13,8 @@ fileConfig(config.config_file_name)
 # add your model's MetaData object here
 # for 'autogenerate' support
 # from myapp import mymodel
-# autogenerate_metadata = mymodel.Base.metadata
-autogenerate_metadata = None
+# target_metadata = mymodel.Base.metadata
+target_metadata = None
 
 # other values from the config, defined by the needs of env.py,
 # can be acquired:
@@ -55,7 +55,7 @@ def run_migrations_online():
     connection = engine.connect()
     context.configure(
                 connection=connection, 
-                autogenerate_metadata=autogenerate_metadata
+                target_metadata=target_metadata
                 )
 
     trans = connection.begin()
index 1ace59cd9744d3ebcfe600d08d7fe56be055afa7..0db2a671b7e39fd4c79a4f6cfa4d09ca6fa4d374 100644 (file)
@@ -20,11 +20,11 @@ db_names = options.get_main_option('databases')
 # helpful here in case a "copy" of
 # a MetaData is needed.
 # from myapp import mymodel
-# autogenerate_metadata = {
+# target_metadata = {
 #       'engine1':mymodel.metadata1,
 #       'engine2':mymodel.metadata2
 #}
-autogenerate_metadata = {}
+target_metadata = {}
 
 def run_migrations_offline():
     """Run migrations in 'offline' mode.
@@ -87,7 +87,7 @@ def run_migrations_online():
                         connection=rec['connection'],
                         upgrade_token="%s_upgrades",
                         downgrade_token="%s_downgrades",
-                        autogenerate_metadata=autogenerate_metadata.get(name)
+                        target_metadata=target_metadata.get(name)
                     )
             context.execute("--running migrations for engine %s" % name)
             context.run_migrations(engine=name)
index da193f467d72c89a8dc50b0d5ee16860412c2029..e9ea402efbf66269439680d191dd5e04fe7f78b1 100644 (file)
@@ -26,8 +26,8 @@ meta = __import__("%s.model.meta" % config['pylons.package']).model.meta
 # add your model's MetaData object here
 # for 'autogenerate' support
 # from myapp import mymodel
-# autogenerate_metadata = mymodel.Base.metadata
-autogenerate_metadata = None
+# target_metadata = mymodel.Base.metadata
+target_metadata = None
 
 def run_migrations_offline():
     """Run migrations in 'offline' mode.
@@ -55,7 +55,7 @@ def run_migrations_online():
     connection = meta.engine.connect()
     context.configure(
                 connection=connection,
-                autogenerate_metadata=autogenerate_metadata
+                target_metadata=target_metadata
                 )
     trans = connection.begin()
     try:
index ce24423799f83fd4653778ce1d49e379010e4869..52c5305c067d9aad4629cfca33426f83449e4ac2 100644 (file)
@@ -375,20 +375,20 @@ has a `declarative base <http://www.sqlalchemy.org/docs/orm/extensions/declarati
 in ``myapp.mymodel``.  This base contains a :class:`~sqlalchemy.schema.MetaData` object which
 contains :class:`~sqlalchemy.schema.Table` objects defining our database.  We make sure this
 is loaded in ``env.py`` and then passed to :func:`.context.configure` via the
-``autogenerate_metadata`` argument.   The ``env.py`` sample script already has a 
+``target_metadata`` argument.   The ``env.py`` sample script already has a 
 variable declaration near the top for our convenience, where we replace ``None``
 with our :class:`~sqlalchemy.schema.MetaData`.  Starting with::
 
     # add your model's MetaData object here
     # for 'autogenerate' support
     # from myapp import mymodel
-    # autogenerate_metadata = mymodel.Base.metadata
-    autogenerate_metadata = None
+    # target_metadata = mymodel.Base.metadata
+    target_metadata = None
 
 we change to::
 
     from myapp.mymodel import Base
-    autogenerate_metadata = Base.metadata
+    target_metadata = Base.metadata
 
 If we look later in the script, down in ``run_migrations_online()``, 
 we can see the directive passed to :func:`.context.configure`::
@@ -400,7 +400,7 @@ we can see the directive passed to :func:`.context.configure`::
         connection = engine.connect()
         context.configure(
                     connection=connection, 
-                    autogenerate_metadata=autogenerate_metadata
+                    target_metadata=target_metadata
                     )
 
         trans = connection.begin()
index 694bec6443ac49241e352f36ec4897ebc1d20d6e..1d0e0f72f2f422bd46ab2f6e46ba778c30cec4a9 100644 (file)
@@ -72,7 +72,7 @@ class AutogenerateDiffTest(TestCase):
             connection = cls.bind.connect(),
             compare_type = True,
             compare_server_default = True,
-            autogenerate_metadata=cls.m2
+            target_metadata=cls.m2
         )
 
     @classmethod
@@ -196,7 +196,7 @@ class AutogenRenderTest(TestCase):
 
     @classmethod
     def setup_class(cls):
-        context._context_opts['autogenerate_sqlalchemy_prefix'] = 'sa.'
+        context._context_opts['sqlalchemy_module_prefix'] = 'sa.'
 
     def test_render_table_upgrade(self):
         m = MetaData()
index f67033c0702950d7ad6daea568d5fef5935dc851..1065fb0acbcbfe8cafa9016767ddb68d6712c096 100644 (file)
@@ -5,7 +5,7 @@ from alembic import op
 from sqlalchemy import Integer, Column, ForeignKey, \
             UniqueConstraint, Table, MetaData, String,\
             Boolean
-from sqlalchemy.sql import table
+from sqlalchemy.sql import table, column, func
 
 def test_rename_table():
     context = _op_fixture()
@@ -131,6 +131,18 @@ def test_add_foreign_key():
             "REFERENCES t2 (bat, hoho)"
     )
 
+def test_add_check_constraint():
+    context = _op_fixture()
+    op.create_check_constraint(
+        "ck_user_name_len",
+        "user_table",
+        func.len(column('name')) > 5
+    )
+    context.assert_(
+        "ALTER TABLE user_table ADD CONSTRAINT ck_user_name_len "
+        "CHECK (len(name) > 5)"
+    )
+
 def test_add_unique_constraint():
     context = _op_fixture()
     op.create_unique_constraint('uk_test', 't1', ['foo', 'bar'])