]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- add version check for at least 06, tests for 07 in selected
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 29 Nov 2011 03:29:43 +0000 (22:29 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 29 Nov 2011 03:29:43 +0000 (22:29 -0500)
areas
- add "requires 07" decorators to test suite
- add tests for PG ENUM in offline mode.  works in conjunction
with the latest 0.7.4 tip of SQLAlchemy, fixes #9.  Docs will
be needed to illustrate how ENUM should be used.
- add support for table before_create and after_create events
within op.create_table().   Currently this will do the ENUM
thing for PG but will also invoke any other kinds of events
that might get configured on the table.

alembic/command.py
alembic/context.py
alembic/ddl/impl.py
alembic/op.py
alembic/util.py
tests/__init__.py
tests/test_autogenerate.py
tests/test_postgresql.py
tests/test_sql_script.py

index a3598ac7f2a8bc8708a86a6b383a4bb263191bbf..f7075b184c1933a180552e455375a5bb56bc3f19 100644 (file)
@@ -66,6 +66,7 @@ def revision(config, message=None, autogenerate=False):
     template_args = {}
     imports = set()
     if autogenerate:
+        util.requires_07("autogenerate")
         def retrieve_migrations(rev):
             if script._get_rev(rev) is not script._get_rev("head"):
                 raise util.CommandError("Target database is not up to date.")
index 51a060e97412309718cb6be03ab7b72fc7bcc10d..d9500e7bf895ddd105c04f69ba9ee9a172b9b55c 100644 (file)
@@ -48,7 +48,7 @@ class Context(object):
 
         self._start_from_rev = starting_rev
         self.impl = ddl.DefaultImpl.get_by_dialect(dialect)(
-                            dialect, connection, self.as_sql,
+                            dialect, self.connection, self.as_sql,
                             transactional_ddl,
                             self.output_buffer
                             )
index 2c6a666b15f3f6d448f83011ae1f3fec5e4f30a8..4159d526363aa456bfc9537c8951f35a385adbb7 100644 (file)
@@ -3,6 +3,7 @@ from sqlalchemy.sql.expression import _BindParamClause
 from sqlalchemy.ext.compiler import compiles
 from sqlalchemy import schema
 from alembic.ddl import base
+from alembic import util
 from sqlalchemy import types as sqltypes
 
 class ImplMeta(type):
@@ -31,11 +32,13 @@ class DefaultImpl(object):
 
     transactional_ddl = False
 
-    def __init__(self, dialect, connection, as_sql, transactional_ddl, output_buffer):
+    def __init__(self, dialect, connection, as_sql, 
+                    transactional_ddl, output_buffer):
         self.dialect = dialect
         self.connection = connection
         self.as_sql = as_sql
         self.output_buffer = output_buffer
+        self.memo = {}
         if transactional_ddl is not None:
             self.transactional_ddl = transactional_ddl
 
@@ -46,6 +49,10 @@ class DefaultImpl(object):
     def static_output(self, text):
         self.output_buffer.write(text + "\n\n")
 
+    @property
+    def bind(self):
+        return self.connection
+
     def _exec(self, construct, *args, **kw):
         if isinstance(construct, basestring):
             construct = text(construct)
@@ -123,7 +130,15 @@ class DefaultImpl(object):
                     new_table_name, schema=schema))
 
     def create_table(self, table):
+        if util.sqla_07:
+            table.dispatch.before_create(table, self.connection,
+                                        checkfirst=False,
+                                            _ddl_runner=self)
         self._exec(schema.CreateTable(table))
+        if util.sqla_07:
+            table.dispatch.after_create(table, self.connection,
+                                        checkfirst=False,
+                                            _ddl_runner=self)
         for index in table.indexes:
             self._exec(schema.CreateIndex(index))
 
index 848e40baee08b9bb12776a30f65f4254d398363a..224003e72526f49731b8c6c17e372d4e034d4f17 100644 (file)
@@ -401,8 +401,17 @@ def create_table(name, *columns, **kw):
             Column('description', NVARCHAR(200))
         )
 
+    :param name: Name of the table
+    :param \*columns: collection of :class:`~sqlalchemy.schema.Column` objects within
+     the table, as well as optional :class:`~sqlalchemy.schema.Constraint` objects
+     and :class:`~.sqlalchemy.schema.Index` objects.
+    :param emit_events: if ``True``, emit ``before_create`` and ``after_create``
+     events when the table is being created.  In particular, the Postgresql ENUM
+     type will emit a CREATE TYPE within these events.
+    :param \**kw: Other keyword arguments are passed to the underlying
+     :class:`.Table` object created for the command.
+     
     """
-
     get_impl().create_table(
         _table(name, *columns, **kw)
     )
index 82572fd1ac65dc614bb8b36221c4539919bd9f8d..aa5d0e252fa1b352b0a6673f82cf6138f4d3bf76 100644 (file)
@@ -10,10 +10,25 @@ import time
 import random
 import uuid
 
-
 class CommandError(Exception):
     pass
 
+from sqlalchemy import __version__
+_vers = tuple([int(x) for x in __version__.split(".")])
+sqla_06 = _vers > (0, 6)
+sqla_07 = _vers > (0, 7)
+if not sqla_06:
+    raise CommandError(
+            "SQLAlchemy 0.6 or greater is required. "
+            "Version 0.7 or above required for full featureset.")
+
+def requires_07(feature):
+    if not sqla_07:
+        raise CommandError(
+            "The %s feature requires "
+            "SQLAlchemy 0.7 or greater."
+            % feature
+        )
 try:
     width = int(os.environ['COLUMNS'])
 except (KeyError, ValueError):
index be4c48bb775bbf571f40374743425b0eee4a87c4..4f81b58065e4f16d75f7272fa3ae42e4f8c7197a 100644 (file)
@@ -13,6 +13,7 @@ from alembic.ddl.impl import _impls
 import ConfigParser
 from nose import SkipTest
 from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.util import decorator
 
 staging_directory = os.path.join(os.path.dirname(__file__), 'scratch')
 files_directory = os.path.join(os.path.dirname(__file__), 'files')
@@ -47,6 +48,12 @@ def db_for_dialect(name):
         _engs[name] = eng
         return eng
 
+@decorator
+def requires_07(fn, *arg, **kw):
+    if not util.sqla_07:
+        raise SkipTest("SQLAlchemy 0.7 required")
+    return fn(*arg, **kw)
+
 _dialects = {}
 def _get_dialect(name):
     if name is None or name == 'default':
@@ -117,7 +124,10 @@ def op_fixture(dialect='default', as_sql=False):
             self.assertion = []
             self.dialect = dialect
             self.as_sql = as_sql
-
+            # TODO: this might need to 
+            # be more like a real connection
+            # as tests get more involved
+            self.connection = None
         def _exec(self, construct, *args, **kw):
             if isinstance(construct, basestring):
                 construct = text(construct)
index 5260b04fc7583cb988d61cd769f24fba50ffa3c5..7e4336657af747e418564e2493fc9cbf6771665e 100644 (file)
@@ -3,7 +3,8 @@ from sqlalchemy import MetaData, Column, Table, Integer, String, Text, \
 from sqlalchemy.types import NULLTYPE
 from alembic import autogenerate, context
 from unittest import TestCase
-from tests import staging_env, sqlite_db, clear_staging_env, eq_, eq_ignore_whitespace
+from tests import staging_env, sqlite_db, clear_staging_env, eq_, \
+        eq_ignore_whitespace, requires_07
 
 def _model_one():
     m = MetaData()
@@ -63,6 +64,7 @@ def _model_two():
 
 class AutogenerateDiffTest(TestCase):
     @classmethod
+    @requires_07
     def setup_class(cls):
         staging_env()
         cls.bind = sqlite_db()
@@ -220,6 +222,7 @@ class AutogenRenderTest(TestCase):
     """test individual directives"""
 
     @classmethod
+    @requires_07
     def setup_class(cls):
         context._context_opts['sqlalchemy_module_prefix'] = 'sa.'
 
index 0f88f57eb9fa619a58475d4f041caad46685c72a..325cbb6179d08d4ee6e402e250e3c887153c13a4 100644 (file)
@@ -1,9 +1,95 @@
 
-from tests import op_fixture, db_for_dialect, eq_, staging_env, clear_staging_env
+from tests import op_fixture, db_for_dialect, eq_, staging_env, \
+            clear_staging_env, no_sql_testing_config,\
+            capture_context_buffer, requires_07
 from unittest import TestCase
 from sqlalchemy import DateTime, MetaData, Table, Column, text, Integer, String
 from sqlalchemy.engine.reflection import Inspector
-from alembic import context
+from alembic import context, command, util
+from alembic.script import ScriptDirectory
+
+class PGOfflineEnumTest(TestCase):
+    @requires_07
+    def setUp(self):
+        env = staging_env()
+        self.cfg = cfg = no_sql_testing_config()
+
+        self.rid = rid = util.rev_id()
+
+        self.script = script = ScriptDirectory.from_config(cfg)
+        script.generate_rev(rid, None, refresh=True)
+
+    def _inline_enum_script(self):
+        self.script.write(self.rid, """
+down_revision = None
+
+from alembic.op import *
+from sqlalchemy.dialects.postgresql import ENUM
+from sqlalchemy import Column
+
+def upgrade():
+    create_table("sometable", 
+        Column("data", ENUM("one", "two", "three", name="pgenum"))
+    )
+
+def downgrade():
+    drop_table("sometable")
+""")
+
+    def _distinct_enum_script(self):
+        self.script.write(self.rid, """
+down_revision = None
+
+from alembic.op import *
+from sqlalchemy.dialects.postgresql import ENUM
+from sqlalchemy import Column
+
+def upgrade():
+    enum = ENUM("one", "two", "three", name="pgenum", create_type=False)
+    enum.create(get_bind(), checkfirst=False)
+    create_table("sometable", 
+        Column("data", enum)
+    )
+
+def downgrade():
+    drop_table("sometable")
+    ENUM(name="pgenum").drop(get_bind(), checkfirst=False)
+    
+""")
+
+    def tearDown(self):
+        clear_staging_env()
+
+    def test_offline_inline_enum_create(self):
+        self._inline_enum_script()
+        with capture_context_buffer() as buf:
+            command.upgrade(self.cfg, self.rid, sql=True)
+        assert "CREATE TYPE pgenum AS ENUM ('one','two','three')" in buf.getvalue()
+        assert "CREATE TABLE sometable (\n    data pgenum\n)" in buf.getvalue()
+
+    def test_offline_inline_enum_drop(self):
+        self._inline_enum_script()
+        with capture_context_buffer() as buf:
+            command.downgrade(self.cfg, "%s:base" % self.rid, sql=True)
+        assert "DROP TABLE sometable" in buf.getvalue()
+        # no drop since we didn't emit events
+        assert "DROP TYPE pgenum" not in buf.getvalue()
+
+    def test_offline_distinct_enum_create(self):
+        self._distinct_enum_script()
+        with capture_context_buffer() as buf:
+            command.upgrade(self.cfg, self.rid, sql=True)
+        assert "CREATE TYPE pgenum AS ENUM ('one','two','three')" in buf.getvalue()
+        assert "CREATE TABLE sometable (\n    data pgenum\n)" in buf.getvalue()
+
+    def test_offline_distinct_enum_drop(self):
+        self._distinct_enum_script()
+        with capture_context_buffer() as buf:
+            command.downgrade(self.cfg, "%s:base" % self.rid, sql=True)
+        assert "DROP TABLE sometable" in buf.getvalue()
+        assert "DROP TYPE pgenum" in buf.getvalue()
+
+
 
 class PostgresqlDefaultCompareTest(TestCase):
     @classmethod
@@ -49,14 +135,6 @@ class PostgresqlDefaultCompareTest(TestCase):
         assert self._compare_default(
             t, t2, t2.c.somecol, alternate
         ) is expected
-#        t.create(self.bind)
-#        insp = Inspector.from_engine(self.bind)
-#        cols = insp.get_columns("test")
-#        ctx = context.get_context()
-#        assert ctx.impl.compare_server_default(
-#           cols[0],
-#            t2.c.somecol, 
-#           alternate) is expected
 
     def _compare_default(
         self,
index 1c3df484f92857806fa7623d254568e169b25560..1df94cdcb1be99d44b795fd61e3df95f7755cc9a 100644 (file)
@@ -1,4 +1,6 @@
-from tests import clear_staging_env, staging_env, no_sql_testing_config, sqlite_db, eq_, ne_, capture_context_buffer, three_rev_fixture
+from tests import clear_staging_env, staging_env, \
+    no_sql_testing_config, sqlite_db, eq_, ne_, capture_context_buffer, \
+    three_rev_fixture
 from alembic import command, util
 
 def setup():