Commits

Mike Bayer committed ac7b69a

- [feature] New config argument
"revision_environment=true", causes env.py to
be run unconditionally when the "revision" command
is run, to support script.py.mako templates with
dependencies on custom "template_args".

- [feature] Added "template_args" option to configure()
so that an env.py can add additional arguments
to the template context when running the
"revision" command. This requires either --autogenerate
or the configuration directive "revision_environment=true".

  • Participants
  • Parent commits d2b4193
  • Tags rel_0_3_3

Comments (0)

Files changed (10)

 0.3.3
 =====
+- [feature] New config argument 
+  "revision_environment=true", causes env.py to
+  be run unconditionally when the "revision" command
+  is run, to support script.py.mako templates with 
+  dependencies on custom "template_args".
+
+- [feature] Added "template_args" option to configure()
+  so that an env.py can add additional arguments
+  to the template context when running the 
+  "revision" command.  This requires either --autogenerate
+  or the configuration directive "revision_environment=true".
+
 - [bug] Added "type" argument to op.drop_constraint(),
   and implemented full constraint drop support for
   MySQL.  CHECK and undefined raise an error.  

alembic/command.py

     util.msg("Please edit configuration/connection/logging "\
             "settings in %r before proceeding." % config_file)
 
-def revision(config, message=None, autogenerate=False):
+def revision(config, message=None, autogenerate=False, environment=False):
     """Create a new revision file."""
 
     script = ScriptDirectory.from_config(config)
     template_args = {}
     imports = set()
+
+    if util.asbool(config.get_main_option("revision_environment")):
+        environment = True
+
     if autogenerate:
+        environment = True
         util.requires_07("autogenerate")
         def retrieve_migrations(rev, context):
             if script.get_revision(rev) is not script.get_revision("head"):
                 raise util.CommandError("Target database is not up to date.")
             autogen._produce_migration_diffs(context, template_args, imports)
             return []
+    elif environment:
+        def retrieve_migrations(rev, context):
+            pass
 
+    if environment:
         with EnvironmentContext(
             config,
             script,
-            fn = retrieve_migrations
+            fn = retrieve_migrations,
+            template_args = template_args,
         ):
             script.run_env()
     script.generate_revision(util.rev_id(), message, **template_args)

alembic/environment.py

             output_buffer=None,
             starting_rev=None,
             tag=None,
+            template_args=None,
             target_metadata=None,
             compare_type=False,
             compare_server_default=False,
          when using ``--sql`` mode.
         :param tag: a string tag for usage by custom ``env.py`` scripts.  
          Set via the ``--tag`` option, can be overridden here.
+        :param template_args: dictionary of template arguments which
+         will be added to the template argument environment when
+         running the "revision" command.   Note that the script environment
+         is only run within the "revision" command if the --autogenerate
+         option is used, or if the option "revision_environment=true"
+         is present in the alembic.ini file.  New in 0.3.3.
         :param version_table: The name of the Alembic version table.
          The default is ``'alembic_version'``.
 
             opts['starting_rev'] = starting_rev
         if tag:
             opts['tag'] = tag
+        if template_args and 'template_args' in opts:
+            opts['template_args'].update(template_args)
         opts['target_metadata'] = target_metadata
         opts['upgrade_token'] = upgrade_token
         opts['downgrade_token'] = downgrade_token

alembic/templates/generic/alembic.ini.mako

 # template used to generate migration files
 # file_template = %%(rev)s_%%(slug)s
 
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
 sqlalchemy.url = driver://user:pass@localhost/dbname
 
 

alembic/templates/multidb/alembic.ini.mako

 # template used to generate migration files
 # file_template = %%(rev)s_%%(slug)s
 
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
 databases = engine1, engine2
 
 [engine1]

alembic/templates/pylons/alembic.ini.mako

 # template used to generate migration files
 # file_template = %%(rev)s_%%(slug)s
 
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
 pylons_config_file = ./development.ini
 
 # that's it !
         u.password = 'XXXXX'
     return str(u)
 
+def asbool(value):
+    return value is not None and \
+        value.lower() == 'true'
+
 def warn(msg):
     warnings.warn(msg)
 

docs/build/tutorial.rst

     # template used to generate migration files
     # file_template = %%(rev)s_%%(slug)s
 
+    # set to 'true' to run the environment during
+    # the 'revision' command, regardless of autogenerate
+    # revision_environment = false
+
     sqlalchemy.url = driver://user:pass@localhost/dbname
 
     # Logging configuration
   a file that can be customized by the developer. A multiple
   database configuration may respond to multiple keys here, or may reference other sections
   of the file.
+* ``revision_environment`` - this is a flag which when set to the value 'true', will indicate
+  that the migration environment script ``env.py`` should be run unconditionally when
+  generating new revision files (new in 0.3.3).
 * ``[loggers]``, ``[handlers]``, ``[formatters]``, ``[logger_*]``, ``[handler_*]``, 
   ``[formatter_*]`` - these sections are all part of Python's standard logging configuration,
   the mechanics of which are documented at `Configuration File Format <http://docs.python.org/library/logging.config.html#configuration-file-format>`_.

tests/__init__.py

     alembic.op._proxy = Operations(context)
     return context
 
+def script_file_fixture(txt):
+    dir_ = os.path.join(staging_directory, 'scripts')
+    path = os.path.join(dir_, "script.py.mako")
+    with open(path, 'w') as f:
+        f.write(txt)
+
 def env_file_fixture(txt):
     dir_ = os.path.join(staging_directory, 'scripts')
     txt = """
     """ % (dir_, dir_))
 
 
-def _no_sql_testing_config(dialect="postgresql"):
+def _no_sql_testing_config(dialect="postgresql", directives=""):
     """use a postgresql url with no host so that connections guaranteed to fail"""
     dir_ = os.path.join(staging_directory, 'scripts')
     return _write_config_file("""
 [alembic]
 script_location = %s
 sqlalchemy.url = %s://
+%s
 
 [loggers]
 keys = root
 format = %%(levelname)-5.5s [%%(name)s] %%(message)s
 datefmt = %%H:%%M:%%S
 
-""" % (dir_, dialect))
+""" % (dir_, dialect, directives))
 
 def _write_config_file(text):
     cfg = _testing_config()

tests/test_revision_create.py

 from tests import clear_staging_env, staging_env, eq_, ne_, is_
+from tests import _no_sql_testing_config, env_file_fixture, script_file_fixture
+from alembic import command
+from alembic.script import ScriptDirectory
+from alembic.environment import EnvironmentContext
 from alembic import util
 import os
+import unittest
 
-def test_001_environment():
-    assert_set = set(['env.py', 'script.py.mako', 'README'])
-    eq_(
-        assert_set.intersection(os.listdir(env.dir)),
-        assert_set
-    )
+class GeneralOrderedTests(unittest.TestCase):
+    def test_001_environment(self):
+        assert_set = set(['env.py', 'script.py.mako', 'README'])
+        eq_(
+            assert_set.intersection(os.listdir(env.dir)),
+            assert_set
+        )
 
-def test_002_rev_ids():
-    global abc, def_
-    abc = util.rev_id()
-    def_ = util.rev_id()
-    ne_(abc, def_)
+    def test_002_rev_ids(self):
+        global abc, def_
+        abc = util.rev_id()
+        def_ = util.rev_id()
+        ne_(abc, def_)
 
-def test_003_heads():
-    eq_(env.get_heads(), [])
+    def test_003_heads(self):
+        eq_(env.get_heads(), [])
 
-def test_004_rev():
-    script = env.generate_revision(abc, "this is a message", refresh=True)
-    eq_(script.doc, "this is a message")
-    eq_(script.revision, abc)
-    eq_(script.down_revision, None)
-    assert os.access(
-        os.path.join(env.dir, 'versions', '%s_this_is_a_message.py' % abc), os.F_OK)
-    assert callable(script.module.upgrade)
-    eq_(env.get_heads(), [abc])
+    def test_004_rev(self):
+        script = env.generate_revision(abc, "this is a message", refresh=True)
+        eq_(script.doc, "this is a message")
+        eq_(script.revision, abc)
+        eq_(script.down_revision, None)
+        assert os.access(
+            os.path.join(env.dir, 'versions', '%s_this_is_a_message.py' % abc), os.F_OK)
+        assert callable(script.module.upgrade)
+        eq_(env.get_heads(), [abc])
 
-def test_005_nextrev():
-    script = env.generate_revision(def_, "this is the next rev", refresh=True)
-    assert os.access(
-        os.path.join(env.dir, 'versions', '%s_this_is_the_next_rev.py' % def_), os.F_OK)
-    eq_(script.revision, def_)
-    eq_(script.down_revision, abc)
-    eq_(env._revision_map[abc].nextrev, set([def_]))
-    assert script.module.down_revision == abc
-    assert callable(script.module.upgrade)
-    assert callable(script.module.downgrade)
-    eq_(env.get_heads(), [def_])
+    def test_005_nextrev(self):
+        script = env.generate_revision(def_, "this is the next rev", refresh=True)
+        assert os.access(
+            os.path.join(env.dir, 'versions', '%s_this_is_the_next_rev.py' % def_), os.F_OK)
+        eq_(script.revision, def_)
+        eq_(script.down_revision, abc)
+        eq_(env._revision_map[abc].nextrev, set([def_]))
+        assert script.module.down_revision == abc
+        assert callable(script.module.upgrade)
+        assert callable(script.module.downgrade)
+        eq_(env.get_heads(), [def_])
 
-def test_006_from_clean_env():
-    # test the environment so far with a 
-    # new ScriptDirectory instance.
+    def test_006_from_clean_env(self):
+        # test the environment so far with a 
+        # new ScriptDirectory instance.
 
-    env = staging_env(create=False)
-    abc_rev = env._revision_map[abc]
-    def_rev = env._revision_map[def_]
-    eq_(abc_rev.nextrev, set([def_]))
-    eq_(abc_rev.revision, abc)
-    eq_(def_rev.down_revision, abc)
-    eq_(env.get_heads(), [def_])
+        env = staging_env(create=False)
+        abc_rev = env._revision_map[abc]
+        def_rev = env._revision_map[def_]
+        eq_(abc_rev.nextrev, set([def_]))
+        eq_(abc_rev.revision, abc)
+        eq_(def_rev.down_revision, abc)
+        eq_(env.get_heads(), [def_])
 
-def test_007_no_refresh():
-    rid = util.rev_id()
-    script = env.generate_revision(rid, "dont' refresh")
-    is_(script, None)
-    env2 = staging_env(create=False)
-    eq_(env2._as_rev_number("head"), rid)
+    def test_007_no_refresh(self):
+        rid = util.rev_id()
+        script = env.generate_revision(rid, "dont' refresh")
+        is_(script, None)
+        env2 = staging_env(create=False)
+        eq_(env2._as_rev_number("head"), rid)
 
-def test_008_long_name():
-    rid = util.rev_id()
-    script = env.generate_revision(rid, 
-            "this is a really long name with "
-            "lots of characters and also "
-            "I'd like it to\nhave\nnewlines")
-    assert os.access(
-        os.path.join(env.dir, 'versions', '%s_this_is_a_really_lon.py' % rid), os.F_OK)
+    def test_008_long_name(self):
+        rid = util.rev_id()
+        script = env.generate_revision(rid, 
+                "this is a really long name with "
+                "lots of characters and also "
+                "I'd like it to\nhave\nnewlines")
+        assert os.access(
+            os.path.join(env.dir, 'versions', '%s_this_is_a_really_lon.py' % rid), os.F_OK)
 
+    @classmethod
+    def setup_class(cls):
+        global env
+        env = staging_env()
 
-def setup():
-    global env
-    env = staging_env()
+    @classmethod
+    def teardown_class(cls):
+        clear_staging_env()
 
-def teardown():
-    clear_staging_env()
+class TemplateArgsTest(unittest.TestCase):
+    def setUp(self):
+        env = staging_env()
+        self.cfg = _no_sql_testing_config(
+            directives="\nrevision_environment=true\n"
+        )
+
+    def tearDown(self):
+        clear_staging_env()
+
+    def test_args_propagate(self):
+        config = _no_sql_testing_config()
+        script = ScriptDirectory.from_config(config)
+        template_args = {"x":"x1", "y":"y1", "z":"z1"}
+        env = EnvironmentContext(
+            config,
+            script,
+            template_args = template_args
+        )
+        mig_env = env.configure(dialect_name="sqlite", 
+                        template_args={"y":"y2", "q":"q1"})
+        eq_(
+            template_args,
+            {"x":"x1", "y":"y2", "z":"z1", "q":"q1"}
+        )
+
+    def test_tmpl_args_revision(self):
+        env_file_fixture("""
+context.configure(dialect_name='sqlite', template_args={"somearg":"somevalue"})
+""")
+        script_file_fixture("""
+# somearg: ${somearg}
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+""")
+        command.revision(self.cfg, message="some rev")
+        script = ScriptDirectory.from_config(self.cfg)
+        rev = script.get_revision('head')
+        text = open(rev.path).read()
+        assert "somearg: somevalue" in text
+