]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- move to 0.3 as we are changing API rel_0_3_0
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 5 Apr 2012 17:33:37 +0000 (13:33 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 5 Apr 2012 17:33:37 +0000 (13:33 -0400)
- [general] The focus of 0.3 is to clean up
  and more fully document the public API of Alembic,
  including better accessors on the MigrationContext
  and ScriptDirectory objects.  Methods that are
  not considered to be public on these objects have
  been underscored, and methods which should be public
  have been cleaned up and documented, including:

    MigrationContext.get_current_revision()
    ScriptDirectory.iterate_revisions()
    ScriptDirectory.get_current_head()
    ScriptDirectory.get_heads()
    ScriptDirectory.get_base()
    ScriptDirectory.generate_revision()

- [feature] Added a bit of autogenerate to the
  public API in the form of the function
  alembic.autogenerate.compare_metadata.

17 files changed:
CHANGES
alembic/__init__.py
alembic/autogenerate.py
alembic/command.py
alembic/config.py
alembic/environment.py
alembic/migration.py
alembic/script.py
docs/build/api.rst
docs/build/conf.py
docs/build/front.rst
tests/__init__.py
tests/test_autogenerate.py
tests/test_postgresql.py
tests/test_revision_create.py
tests/test_revision_paths.py
tests/test_versioning.py

diff --git a/CHANGES b/CHANGES
index 7950a139d2e8f2daab6588ba58a31617dadcfda9..001bd6ad69d2ce55c7455a1e1ff248b644ed84e8 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,24 @@
+0.3.0
+=====
+- [general] The focus of 0.3 is to clean up 
+  and more fully document the public API of Alembic,
+  including better accessors on the MigrationContext
+  and ScriptDirectory objects.  Methods that are
+  not considered to be public on these objects have
+  been underscored, and methods which should be public
+  have been cleaned up and documented, including:
+
+    MigrationContext.get_current_revision()
+    ScriptDirectory.iterate_revisions()
+    ScriptDirectory.get_current_head()
+    ScriptDirectory.get_heads()
+    ScriptDirectory.get_base()
+    ScriptDirectory.generate_revision()
+
+- [feature] Added a bit of autogenerate to the 
+  public API in the form of the function
+  alembic.autogenerate.compare_metadata.
+
 0.2.2
 =====
 - [feature] Informative error message when op.XYZ
   into a new schema.  For dev environments, the 
   dev installer should be building the whole DB from 
   scratch.  Or just use Postgresql, which is a much
-  better database for non-trivial schemas.  
+  better database for non-trivial schemas.
   Requests for full ALTER support on SQLite should be 
   reported to SQLite's bug tracker at 
   http://www.sqlite.org/src/wiki?name=Bug+Reports,
   by key, etc. for full support here.
 
 - Support for tables in remote schemas, 
-  i.e. "schemaname.tablename", is very poor.   
+  i.e. "schemaname.tablename", is very poor.
   Missing "schema" behaviors should be 
   reported as tickets, though in the author's
   experience, migrations typically proceed only
index 7c50518945b827f974b1d209b6128a9996f5bc50..c1c253c88e5cea06941763bf905c4ab72a4d30ae 100644 (file)
@@ -1,6 +1,6 @@
 from os import path
 
-__version__ = '0.2.2'
+__version__ = '0.3.0'
 
 package_dir = path.abspath(path.dirname(__file__))
 
index ba0ecd961de0820769ed800aa869bff0e29e7009..5fa856c5170630c8a010083daedf6e6ef821dd3d 100644 (file)
@@ -11,10 +11,95 @@ import logging
 log = logging.getLogger(__name__)
 
 ###################################################
-# top level
+# public
+def compare_metadata(context, metadata):
+    """Compare a database schema to that given in a :class:`~sqlalchemy.schema.MetaData`
+    instance.
+    
+    The database connection is presented in the context
+    of a :class:`.MigrationContext` object, which 
+    provides database connectivity as well as optional
+    comparison functions to use for datatypes and
+    server defaults - see the "autogenerate" arguments
+    at :meth:`.EnvironmentContext.configure`
+    for details on these.
+    
+    The return format is a list of "diff" directives,
+    each representing individual differences::
+
+        from alembic.migration import MigrationContext
+        from alembic.autogenerate import compare_metadata
+        from sqlalchemy.schema import SchemaItem
+        from sqlalchemy.types import TypeEngine
+        from sqlalchemy import (create_engine, MetaData, Column, 
+                Integer, String, Table)
+        import pprint
+
+        engine = create_engine("sqlite://")
+
+        engine.execute('''
+            create table foo (
+                id integer not null primary key,
+                old_data varchar,
+                x integer
+            )''')
+
+        engine.execute('''
+            create table bar (
+                data varchar
+            )''')
+
+        metadata = MetaData()
+        Table('foo', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('data', Integer),
+            Column('x', Integer, nullable=False)
+        )
+        Table('bat', metadata, 
+            Column('info', String)
+        )
+
+        mc = MigrationContext.configure(engine.connect())
+
+        diff = compare_metadata(mc, metadata)
+        pprint.pprint(diff, indent=2, width=20)
+    
+    Output::
+    
+        [ ( 'add_table',
+            Table('bat', MetaData(bind=None), Column('info', String(), table=<bat>), schema=None)),
+          ( 'remove_table',
+            Table(u'bar', MetaData(bind=None), Column(u'data', VARCHAR(), table=<bar>), schema=None)),
+          ( 'add_column',
+            'foo',
+            Column('data', Integer(), table=<foo>)),
+          ( 'remove_column',
+            'foo',
+            Column(u'old_data', VARCHAR(), table=None)),
+          [ ( 'modify_nullable',
+              'foo',
+              u'x',
+              { 'existing_server_default': None,
+                'existing_type': INTEGER()},
+              True,
+              False)]]
+    
+    
+    :param context: a :class:`.MigrationContext`
+     instance.
+    :param metadata: a :class:`~sqlalchemy.schema.MetaData` 
+     instance.
+    
+    """
+    autogen_context, connection = _autogen_context(context, None)
+    diffs = []
+    _produce_net_changes(connection, metadata, diffs, autogen_context)
+    return diffs
 
+###################################################
+# top level
 
-def produce_migration_diffs(context, template_args, imports):
+def _produce_migration_diffs(context, template_args, imports):
     opts = context.opts
     metadata = opts['target_metadata']
     if metadata is None:
@@ -24,15 +109,9 @@ def produce_migration_diffs(context, template_args, imports):
                 "a MetaData object to the context." % (
                     context.script.env_py_location
                 ))
-    connection = context.bind
+    autogen_context, connection = _autogen_context(context, imports)
+
     diffs = []
-    autogen_context = {
-        'imports':imports,
-        'connection':connection,
-        'dialect':connection.dialect,
-        'context':context,
-        'opts':opts
-    }
     _produce_net_changes(connection, metadata, diffs, autogen_context)
     template_args[opts['upgrade_token']] = \
             _indent(_produce_upgrade_commands(diffs, autogen_context))
@@ -40,6 +119,16 @@ def produce_migration_diffs(context, template_args, imports):
             _indent(_produce_downgrade_commands(diffs, autogen_context))
     template_args['imports'] = "\n".join(sorted(imports))
 
+def _autogen_context(context, imports):
+    opts = context.opts
+    connection = context.bind
+    return {
+        'imports':imports,
+        'connection':connection,
+        'dialect':connection.dialect,
+        'context':context,
+        'opts':opts
+    }, connection
 
 def _indent(text):
     text = "### commands auto generated by Alembic - please adjust! ###\n" + text
@@ -178,7 +267,7 @@ def _compare_type(tname, cname, conn_col,
         log.info("Column '%s.%s' has no type within the model; can't compare" % (tname, cname))
         return
 
-    isdiff = autogen_context['context'].compare_type(conn_col, metadata_col)
+    isdiff = autogen_context['context']._compare_type(conn_col, metadata_col)
 
     if isdiff:
 
@@ -203,7 +292,7 @@ def _compare_server_default(tname, cname, conn_col, metadata_col,
     if conn_col_default is None and metadata_default is None:
         return False
     rendered_metadata_default = _render_server_default(metadata_default, autogen_context)
-    isdiff = autogen_context['context'].compare_server_default(
+    isdiff = autogen_context['context']._compare_server_default(
                         conn_col, metadata_col,
                         rendered_metadata_default
                     )
index 18b421c73184b732cdb09d92be1feaa32235f647..ed7b8302cb68c46789641a61860dbbf3bb7ee1e8 100644 (file)
@@ -1,7 +1,7 @@
 from alembic.script import ScriptDirectory
-from alembic import util, ddl, autogenerate as autogen, environment
+from alembic.environment import EnvironmentContext
+from alembic import util, ddl, autogenerate as autogen
 import os
-import functools
 
 def list_templates(config):
     """List available templates"""
@@ -44,14 +44,14 @@ def init(config, directory, template='generic'):
             if os.access(config_file, os.F_OK):
                 util.msg("File %s already exists, skipping" % config_file)
             else:
-                script.generate_template(
+                script._generate_template(
                     os.path.join(template_dir, file_),
                     config_file,
                     script_location=directory
                 )
         else:
             output_file = os.path.join(directory, file_)
-            script.copy_file(
+            script._copy_file(
                 os.path.join(template_dir, file_), 
                 output_file
             )
@@ -68,18 +68,18 @@ def revision(config, message=None, autogenerate=False):
     if autogenerate:
         util.requires_07("autogenerate")
         def retrieve_migrations(rev, context):
-            if script._get_rev(rev) is not script._get_rev("head"):
+            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)
+            autogen._produce_migration_diffs(context, template_args, imports)
             return []
 
-        with environment.configure(
+        with EnvironmentContext(
             config,
             script,
             fn = retrieve_migrations
         ):
             script.run_env()
-    script.generate_rev(util.rev_id(), message, **template_args)
+    script.generate_revision(util.rev_id(), message, **template_args)
 
 
 def upgrade(config, revision, sql=False, tag=None):
@@ -92,10 +92,14 @@ def upgrade(config, revision, sql=False, tag=None):
         if not sql:
             raise util.CommandError("Range revision not allowed")
         starting_rev, revision = revision.split(':', 2)
-    with environment.configure(
+
+    def upgrade(rev, context):
+        return script._upgrade_revs(revision, rev)
+
+    with EnvironmentContext(
         config,
         script,
-        fn = functools.partial(script.upgrade_from, revision),
+        fn = upgrade,
         as_sql = sql,
         starting_rev = starting_rev,
         destination_rev = revision,
@@ -114,10 +118,13 @@ def downgrade(config, revision, sql=False, tag=None):
             raise util.CommandError("Range revision not allowed")
         starting_rev, revision = revision.split(':', 2)
 
-    with environment.configure(
+    def downgrade(rev, context):
+        return script._downgrade_revs(revision, rev)
+
+    with EnvironmentContext(
         config,
         script,
-        fn = functools.partial(script.downgrade_to, revision),
+        fn = downgrade,
         as_sql = sql,
         starting_rev = starting_rev,
         destination_rev = revision,
@@ -143,7 +150,7 @@ def branches(config):
             for rev in sc.nextrev:
                 print "%s -> %s" % (
                     " " * len(str(sc.down_revision)),
-                    script._get_rev(rev)
+                    script.get_revision(rev)
                 )
 
 def current(config):
@@ -154,10 +161,10 @@ def current(config):
         print "Current revision for %s: %s" % (
                             util.obfuscate_url_pw(
                                 context.connection.engine.url),
-                            script._get_rev(rev))
+                            script.get_revision(rev))
         return []
 
-    with environment.configure(
+    with EnvironmentContext(
         config,
         script,
         fn = display_version
@@ -174,12 +181,12 @@ def stamp(config, revision, sql=False, tag=None):
             current = False
         else:
             current = context._current_rev()
-        dest = script._get_rev(revision)
+        dest = script.get_revision(revision)
         if dest is not None:
             dest = dest.revision
         context._update_current_rev(current, dest)
         return []
-    with environment.configure(
+    with EnvironmentContext(
         config, 
         script,
         fn = do_stamp,
index 998bb85475c19355b78ec4f7e1d4529102631bd1..92e09211dcb67ee29103ca92d4e5fd52f3d0356b 100644 (file)
@@ -32,6 +32,7 @@ class Config(object):
     
         from alembic.config import Config
         alembic_cfg = Config()
+        alembic_cfg.set_main_option("script_location", "myapp:migrations")
         alembic_cfg.set_main_option("url", "postgresql://foo/bar")
         alembic_cfg.set_section_option("mysection", "foo", "bar")
 
index 231acebe1e39241e6c4fcbf3ebe403963d9cdac6..52c353a86a360a53f0ebb21fb87130ec5d8fa618 100644 (file)
@@ -13,6 +13,50 @@ class EnvironmentContext(object):
     :class:`.EnvironmentContext` is available via the 
     ``alembic.context`` datamember.
     
+    :class:`.EnvironmentContext` is also a Python context
+    manager, that is, is intended to be used using the
+    ``with:`` statement.  A typical use of :class:`.EnvironmentContext`::
+
+        from alembic.config import Config
+        from alembic.script import ScriptDirectory
+        
+        config = Config()
+        config.set_main_option("script_location", "myapp:migrations")
+        script = ScriptDirectory.from_config(config)
+        
+        def my_function(rev, context):
+            '''do something with revision "rev", which
+            will be the current database revision, 
+            and "context", which is the MigrationContext
+            that the env.py will create'''
+            
+        with EnvironmentContext(
+            config,
+            script,
+            fn = my_function,
+            as_sql = False,
+            starting_rev = 'base',
+            destination_rev = 'head',
+            tag = "sometag"
+        ):
+            script.run_env()
+    
+    The above script will invoke the ``env.py`` script
+    within the migration environment.  If and when ``env.py``
+    calls :meth:`.MigrationContext.run_migrations`, the
+    ``my_function()`` function above will be called 
+    by the :class:`.MigrationContext`, given the context
+    itself as well as the current revision in the database.
+    
+    .. note::
+
+        For most API usages other than full blown
+        invocation of migration scripts, the :class:`.MigrationContext`
+        and :class:`.ScriptDirectory` objects can be created and
+        used directly.  The :class:`.EnvironmentContext` object 
+        is *only* needed when you need to actually invoke the
+        ``env.py`` module present in the migration environment.
+    
     """
 
     _migration_context = None
@@ -31,6 +75,15 @@ class EnvironmentContext(object):
     """
 
     def __init__(self, config, script, **kw):
+        """Construct a new :class:`.EnvironmentContext`.
+        
+        :param config: a :class:`.Config` instance.
+        :param script: a :class:`.ScriptDirectory` instance.
+        :param \**kw: keyword options that will be ultimately
+         passed along to the :class:`.MigrationContext` when
+         :meth:`.EnvironmentContext.configure` is called.
+         
+        """
         self.config = config
         self.script = script
         self.context_opts = kw
@@ -495,4 +548,3 @@ class EnvironmentContext(object):
     def get_impl(self):
         return self.get_context().impl
 
-configure = EnvironmentContext
index d610b3bcbba849542c7bb0d177af12ab35401fbe..80e53b2564c2e42153c0c52e3b7e3a35f6ea3a23 100644 (file)
@@ -15,31 +15,48 @@ _version = Table('alembic_version', _meta,
             )
 
 class MigrationContext(object):
-    """Represent the state made available to a migration script,
-    or otherwise a series of migration operations.
+    """Represent the database state made available to a migration 
+    script.
 
-    Mediates the relationship between an ``env.py`` environment script, 
-    a :class:`.ScriptDirectory` instance, and a :class:`.DefaultImpl` instance.
-
-    The :class:`.MigrationContext` that's established for a 
-    duration of a migration command is available via the 
+    :class:`.MigrationContext` is the front end to an actual
+    database connection, or alternatively a string output
+    stream given a particular database dialect,
+    from an Alembic perspective.
+    
+    When inside the ``env.py`` script, the :class:`.MigrationContext` 
+    is available via the 
     :meth:`.EnvironmentContext.get_context` method,
     which is available at ``alembic.context``::
     
+        # from within env.py script
         from alembic import context
         migration_context = context.get_context()
     
-    A :class:`.MigrationContext` can be created programmatically
-    for usage outside of the usual Alembic migrations flow,
-    using the :meth:`.MigrationContext.configure` method::
+    For usage outside of an ``env.py`` script, such as for
+    utility routines that want to check the current version
+    in the database, the :meth:`.MigrationContext.configure` 
+    method to create new :class:`.MigrationContext` objects.
+    For example, to get at the current revision in the 
+    database using :meth:`.MigrationContext.get_current_revision`::
     
-        conn = myengine.connect()
-        ctx = MigrationContext.configure(conn)
+        # in any application, outside of an env.py script
+        from alembic.migration import MigrationContext
+        from sqlalchemy import create_engine
+        
+        engine = create_engine("postgresql://mydatabase")
+        conn = engine.connect()
+        
+        context = MigrationContext.configure(conn)
+        current_rev = context.get_current_revision()
     
-    The above context can then be used to produce
+    The above context can also be used to produce
     Alembic migration operations with an :class:`.Operations`
-    instance.
-    
+    instance::
+
+        # in any application, outside of the normal Alembic environment
+        from alembic.operations import Operations
+        op = Operations(context)
+        op.alter_column("mytable", "somecolumn", nullable=True)
 
     """
     def __init__(self, dialect, connection, opts):
@@ -119,7 +136,15 @@ class MigrationContext(object):
         return MigrationContext(dialect, connection, opts)
 
 
-    def _current_rev(self):
+    def get_current_revision(self):
+        """Return the current revision, usually that which is present
+        in the ``alembic_version`` table in the database.
+        
+        If this :class:`.MigrationContext` was configured in "offline"
+        mode, that is with ``as_sql=True``, the ``starting_rev`` 
+        parameter is returned instead, if any.
+        
+        """
         if self.as_sql:
             return self._start_from_rev
         else:
@@ -130,6 +155,9 @@ class MigrationContext(object):
             _version.create(self.connection, checkfirst=True)
         return self.connection.scalar(_version.select())
 
+    _current_rev = get_current_revision
+    """The 0.2 method name, for backwards compat."""
+
     def _update_current_rev(self, old, new):
         if old == new:
             return
@@ -145,11 +173,30 @@ class MigrationContext(object):
                     )
 
     def run_migrations(self, **kw):
-
+        """Run the migration scripts established for this :class:`.MigrationContext`, 
+        if any.
+        
+        The commands in :mod:`alembic.command` will set up a function
+        that is ultimately passed to the :class:`.MigrationContext`
+        as the ``fn`` argument.  This function represents the "work" 
+        that will be done when :meth:`.MigrationContext.run_migrations`
+        is called, typically from within the ``env.py`` script of the
+        migration environment.  The "work function" then provides an iterable
+        of version callables and other version information which 
+        in the case of the ``upgrade`` or ``downgrade`` commands are the
+        list of version scripts to invoke.  Other commands yield nothing,
+        in the case that a command wants to run some other operation
+        against the database such as the ``current`` or ``stamp`` commands.
+        
+        :param \**kw: keyword arguments here will be passed to each 
+         migration callable, that is the ``upgrade()`` or ``downgrade()``
+         method within revision scripts.
+         
+        """
         current_rev = rev = False
         self.impl.start_migrations()
         for change, prev_rev, rev in self._migrations_fn(
-                                        self._current_rev(),
+                                        self.get_current_revision(),
                                         self):
             if current_rev is False:
                 current_rev = prev_rev
@@ -174,6 +221,14 @@ class MigrationContext(object):
                 _version.drop(self.connection)
 
     def execute(self, sql):
+        """Execute a SQL construct or string statement.
+        
+        The underlying execution mechanics are used, that is
+        if this is "offline mode" the SQL is written to the 
+        output buffer, otherwise the SQL is emitted on
+        the current SQLAlchemy connection.
+
+        """
         self.impl._exec(sql)
 
     def _stdout_connection(self, connection):
@@ -203,7 +258,7 @@ class MigrationContext(object):
         """
         return self.connection
 
-    def compare_type(self, inspector_column, metadata_column):
+    def _compare_type(self, inspector_column, metadata_column):
         if self._user_compare_type is False:
             return False
 
@@ -222,7 +277,7 @@ class MigrationContext(object):
                                     inspector_column, 
                                     metadata_column)
 
-    def compare_server_default(self, inspector_column, 
+    def _compare_server_default(self, inspector_column, 
                             metadata_column, 
                             rendered_metadata_default):
 
index 952b572fa4e851ffb06370e81e33371015b38ac0..bef7dfb56b1dd0c915e4a3a4a4ef138fe93435eb 100644 (file)
@@ -15,7 +15,22 @@ _default_file_template = "%(rev)s_%(slug)s"
 
 class ScriptDirectory(object):
     """Provides operations upon an Alembic script directory.
-    
+
+    This object is useful to get information as to current revisions,
+    most notably being able to get at the "head" revision, for schemes
+    that want to test if the current revision in the database is the most
+    recent::
+
+        from alembic.script import ScriptDirectory
+        from alembic.config import Config
+        config = Config()
+        config.set_main_option("script_location", "myapp:migrations")
+        script = ScriptDirectory.from_config(config)
+
+        head_revision = script.get_current_head()
+
+
+
     """
     def __init__(self, dir, file_template=_default_file_template):
         self.dir = dir
@@ -29,6 +44,13 @@ class ScriptDirectory(object):
 
     @classmethod
     def from_config(cls, config):
+        """Produce a new :class:`.ScriptDirectory` given a :class:`.Config` 
+        instance.
+
+        The :class:`.Config` need only have the ``script_location`` key
+        present.
+
+        """
         return ScriptDirectory(
                     util.coerce_resource_to_filename(
                         config.get_main_option('script_location')
@@ -45,23 +67,25 @@ class ScriptDirectory(object):
         with leaf nodes being heads.
 
         """
-        heads = set(self._get_heads())
-        base = self._get_rev("base")
+        heads = set(self.get_heads())
+        base = self.get_revision("base")
         while heads:
             todo = set(heads)
             heads = set()
             for head in todo:
                 if head in heads:
                     break
-                for sc in self._revs(head, base):
+                for sc in self.iterate_revisions(head, base):
                     if sc.is_branch_point and sc.revision not in todo:
                         heads.add(sc.revision)
                         break
                     else:
                         yield sc
 
-    def _get_rev(self, id_):
-        id_ = self._as_rev_number(id_)
+    def get_revision(self, id_):
+        """Return the :class:`.Script` instance with the given rev id."""
+
+        id_ = self.as_revision_number(id_)
         try:
             return self._revision_map[id_]
         except KeyError:
@@ -80,16 +104,34 @@ class ScriptDirectory(object):
             else:
                 return self._revision_map[revs[0]]
 
-    def _as_rev_number(self, id_):
+    _get_rev = get_revision
+
+    def as_revision_number(self, id_):
+        """Convert a symbolic revision, i.e. 'head' or 'base', into
+        an actual revision number."""
+
         if id_ == 'head':
-            id_ = self._current_head()
+            id_ = self.get_current_head()
         elif id_ == 'base':
             id_ = None
         return id_
 
-    def _revs(self, upper, lower):
-        lower = self._get_rev(lower)
-        upper = self._get_rev(upper)
+    _as_rev_number = as_revision_number
+
+    def iterate_revisions(self, upper, lower):
+        """Iterate through script revisions, starting at the given 
+        upper revision identifier and ending at the lower.
+
+        The traversal uses strictly the `down_revision`
+        marker inside each migration script, so
+        it is a requirement that upper >= lower,
+        else you'll get nothing back.
+
+        The iterator yields :class:`.Script` objects.
+
+        """
+        lower = self.get_revision(lower)
+        upper = self.get_revision(upper)
         script = upper
         while script != lower:
             yield script
@@ -99,15 +141,15 @@ class ScriptDirectory(object):
                 raise util.CommandError(
                         "Couldn't find revision %s" % downrev)
 
-    def upgrade_from(self, destination, current_rev, context):
-        revs = self._revs(destination, current_rev)
+    def _upgrade_revs(self, destination, current_rev):
+        revs = self.iterate_revisions(destination, current_rev)
         return [
             (script.module.upgrade, script.down_revision, script.revision)
             for script in reversed(list(revs))
             ]
 
-    def downgrade_to(self, destination, current_rev, context):
-        revs = self._revs(current_rev, destination)
+    def _downgrade_revs(self, destination, current_rev):
+        revs = self.iterate_revisions(current_rev, destination)
         return [
             (script.module.downgrade, script.revision, script.down_revision)
             for script in revs
@@ -115,12 +157,12 @@ class ScriptDirectory(object):
 
     def run_env(self):
         """Run the script environment.
-        
+
         This basically runs the ``env.py`` script present
         in the migration environment.   It is called exclusively
         by the command functions in :mod:`alembic.command`.
-        
-        
+
+
         """
         util.load_python_file(self.dir, 'env.py')
 
@@ -132,7 +174,7 @@ class ScriptDirectory(object):
     def _revision_map(self):
         map_ = {}
         for file_ in os.listdir(self.versions):
-            script = Script.from_filename(self.versions, file_)
+            script = Script._from_filename(self.versions, file_)
             if script is None:
                 continue
             if script.revision in map_:
@@ -158,8 +200,16 @@ class ScriptDirectory(object):
         )
         return os.path.join(self.versions, filename)
 
-    def _current_head(self):
-        current_heads = self._get_heads()
+    def get_current_head(self):
+        """Return the current head revision.
+
+        If the script directory has multiple heads
+        due to branching, an error is raised.
+
+        Returns a string revision number.
+
+        """
+        current_heads = self.get_heads()
         if len(current_heads) > 1:
             raise util.CommandError("Only a single head supported so far...")
         if current_heads:
@@ -167,22 +217,45 @@ class ScriptDirectory(object):
         else:
             return None
 
-    def _get_heads(self):
+    _current_head = get_current_head
+    """the 0.2 name, for backwards compat."""
+
+    def get_heads(self):
+        """Return all "head" revisions as strings.
+
+        Returns a list of string revision numbers.
+
+        This is normally a list of length one,
+        unless branches are present.  The
+        :meth:`.ScriptDirectory.get_current_head()` method
+        can be used normally when a script directory
+        has only one head.
+
+        """
         heads = []
         for script in self._revision_map.values():
             if script and script.is_head:
                 heads.append(script.revision)
         return heads
 
-    def _get_origin(self):
+    def get_base(self):
+        """Return the "base" revision as a string.
+
+        This is the revision number of the script that
+        has a ``down_revision`` of None.
+
+        Behavior is not defined if more than one script
+        has a ``down_revision`` of None.
+
+        """
         for script in self._revision_map.values():
             if script.down_revision is None \
                 and script.revision in self._revision_map:
-                return script
+                return script.revision
         else:
             return None
 
-    def generate_template(self, src, dest, **kw):
+    def _generate_template(self, src, dest, **kw):
         util.status("Generating %s" % os.path.abspath(dest),
             util.template_to_file,
             src, 
@@ -190,15 +263,33 @@ class ScriptDirectory(object):
             **kw
         )
 
-    def copy_file(self, src, dest):
+    def _copy_file(self, src, dest):
         util.status("Generating %s" % os.path.abspath(dest), 
                     shutil.copy, 
                     src, dest)
 
-    def generate_rev(self, revid, message, refresh=False, **kw):
-        current_head = self._current_head()
+    def generate_revision(self, revid, message, refresh=False, **kw):
+        """Generate a new revision file.
+
+        This runs the ``script.py.mako`` template, given
+        template arguments, and creates a new file.
+
+        :param revid: String revision id.  Typically this
+         comes from ``alembic.util.rev_id()``.
+        :param message: the revision message, the one passed
+         by the -m argument to the ``revision`` command.
+        :param refresh: when True, the in-memory state of this
+         :class:`.ScriptDirectory` will be updated with a new
+         :class:`.Script` instance representing the new revision;
+         the :class:`.Script` instance is returned.
+         If False, the file is created but the state of the
+         :class:`.ScriptDirectory` is unmodified; ``None``
+         is returned.
+
+        """
+        current_head = self.get_current_head()
         path = self._rev_path(revid, message)
-        self.generate_template(
+        self._generate_template(
             os.path.join(self.dir, "script.py.mako"),
             path,
             up_revision=str(revid),
@@ -208,18 +299,24 @@ class ScriptDirectory(object):
             **kw
         )
         if refresh:
-            script = Script.from_path(path)
+            script = Script._from_path(path)
             self._revision_map[script.revision] = script
             if script.down_revision:
                 self._revision_map[script.down_revision].\
                         add_nextrev(script.revision)
             return script
         else:
-            return revid
+            return None
 
 
 class Script(object):
-    """Represent a single revision file in a ``versions/`` directory."""
+    """Represent a single revision file in a ``versions/`` directory.
+
+    The :class:`.Script` instance is returned by methods
+    such as :meth:`.ScriptDirectory.iterate_revisions`.
+
+    """
+
     nextrev = frozenset()
 
     def __init__(self, module, rev_id, path):
@@ -228,8 +325,21 @@ class Script(object):
         self.path = path
         self.down_revision = getattr(module, 'down_revision', None)
 
+    revision = None
+    """The string revision number for this :class:`.Script` instance."""
+
+    module = None
+    """The Python module representing the actual script itself."""
+
+    path = None
+    """Filesystem path of the script."""
+
+    down_revision = None
+    """The ``down_revision`` identifier within the migration script."""
+
     @property
     def doc(self):
+        """Return the docstring given in the script."""
         return re.split(r"\n\n", self.module.__doc__)[0]
 
     def add_nextrev(self, rev):
@@ -237,10 +347,25 @@ class Script(object):
 
     @property
     def is_head(self):
+        """Return True if this :class:`.Script` is a 'head' revision.
+
+        This is determined based on whether any other :class:`.Script`
+        within the :class:`.ScriptDirectory` refers to this
+        :class:`.Script`.   Multiple heads can be present.
+
+        """
         return not bool(self.nextrev)
 
     @property
     def is_branch_point(self):
+        """Return True if this :class:`.Script` is a branch point.
+
+        A branchpoint is defined as a :class:`.Script` which is referred
+        to by more than one succeeding :class:`.Script`, that is more
+        than one :class:`.Script` has a `down_revision` identifier pointing
+        here.
+
+        """
         return len(self.nextrev) > 1
 
     def __str__(self):
@@ -252,12 +377,12 @@ class Script(object):
                         self.doc)
 
     @classmethod
-    def from_path(cls, path):
+    def _from_path(cls, path):
         dir_, filename = os.path.split(path)
-        return cls.from_filename(dir_, filename)
+        return cls._from_filename(dir_, filename)
 
     @classmethod
-    def from_filename(cls, dir_, filename):
+    def _from_filename(cls, dir_, filename):
         m = _rev_file.match(filename)
         if not m:
             return None
index def71a3780f6a66a74f82378262afac42dd25f14..de8f37e64370348582fbca074e267c77dd8fd03b 100644 (file)
@@ -1,3 +1,5 @@
+.. _api:
+
 ===========
 API Details
 ===========
@@ -40,26 +42,36 @@ database connectivity, though in such a way that it does not care if the
 :class:`.MigrationContext` is talking to a real database or just writing
 out SQL to a file.
 
-env.py Directives
-=================
+The Environment Context
+=======================
 
-This section covers the objects that are generally used within an 
-``env.py`` environmental configuration script.   Alembic normally generates
-this script for you; it is however made available locally within the migrations
-environment so that it can be customized.
+The :class:`.EnvironmentContext` class provides most of the
+API used within an ``env.py`` script.  Within ``env.py``, 
+the instantated :class:`.EnvironmentContext` is made available
+via a special *proxy module* called ``alembic.context``.   That is,
+you can import ``alembic.context`` like a regular Python module,
+and each name you call upon it is ultimately routed towards the
+current :class:`.EnvironmentContext` in use.
 
 In particular, the key method used within ``env.py`` is :meth:`.EnvironmentContext.configure`,
 which establishes all the details about how the database will be accessed.
 
-
-.. autofunction:: sqlalchemy.engine.engine_from_config
-
 .. automodule:: alembic.environment
     :members:
 
+The Migration Context
+=====================
+
 .. automodule:: alembic.migration
     :members:
 
+The Operations Object
+=====================
+
+Within migration scripts, actual database migration operations are handled
+via an instance of :class:`.Operations`.    See :ref:`ops` for an overview
+of this object.
+
 Commands
 =========
 
@@ -75,31 +87,68 @@ object, as in::
     alembic_cfg = Config("/path/to/yourapp/alembic.ini")
     command.upgrade(alembic_cfg, "head")
 
+To write small API functions that make direct use of database and script directory
+information, rather than just running one of the built-in commands,
+use the :class:`.ScriptDirectory` and :class:`.MigrationContext`
+classes directly.
+
 .. currentmodule:: alembic.command
 
 .. automodule:: alembic.command
     :members:
-    :undoc-members:
 
 Configuration
 ==============
 
+The :class:`.Config` object represents the configuration 
+passed to the Alembic environment.  From an API usage perspective,
+it is needed for the following use cases:
+
+* to create a :class:`.ScriptDirectory`, which allows you to work
+  with the actual script files in a migration environment
+* to create an :class:`.EnvironmentContext`, which allows you to 
+  actually run the ``env.py`` module within the migration environment
+* to programatically run any of the commands in the :mod:`alembic.command`
+  module.
+
+The :class:`.Config` is *not* needed for these cases:
+
+* to instantiate a :class:`.MigrationContext` directly - this object
+  only needs a SQLAlchemy connection or dialect name.
+* to instantiate a :class:`.Operations` object - this object only
+  needs a :class:`.MigrationContext`.
+
 .. currentmodule:: alembic.config
 
 .. automodule:: alembic.config
     :members:
-    :undoc-members:
 
+Script Directory
+================
 
-Internals
-=========
+The :class:`.ScriptDirectory` object provides programmatic access
+to the Alembic version files present in the filesystem.
 
 .. automodule:: alembic.script
     :members:
-    :undoc-members:
+
+Autogeneration
+==============
+
+Alembic 0.3 introduces a small portion of the autogeneration system
+as a public API.
+
+.. autofunction:: alembic.autogenerate.compare_metadata
 
 DDL Internals
--------------
+=============
+
+These are some of the constructs used to generate migration
+instructions.  The APIs here build off of the :class:`sqlalchemy.schema.DDLElement`
+and :mod:`sqlalchemy.ext.compiler` systems.
+
+For programmatic usage of Alembic's migration directives, the easiest
+route is to use the higher level functions given by :mod:`alembic.operations`.
 
 .. automodule:: alembic.ddl
     :members:
@@ -114,7 +163,7 @@ DDL Internals
     :undoc-members:
 
 MySQL
-^^^^^
+-----
 
 .. automodule:: alembic.ddl.mysql
     :members:
@@ -122,7 +171,7 @@ MySQL
     :show-inheritance:
 
 MS-SQL
-^^^^^^
+------
 
 .. automodule:: alembic.ddl.mssql
     :members:
@@ -130,7 +179,7 @@ MS-SQL
     :show-inheritance:
 
 Postgresql
-^^^^^^^^^^
+----------
 
 .. automodule:: alembic.ddl.postgresql
     :members:
@@ -138,7 +187,7 @@ Postgresql
     :show-inheritance:
 
 SQLite
-^^^^^^
+------
 
 .. automodule:: alembic.ddl.sqlite
     :members:
index 44dde4b64f0c6efb543c70d7a3eb1d891a67d66b..2bb0961564b3a632e19ab76490bf2406c1fc40ec 100644 (file)
@@ -206,6 +206,8 @@ latex_documents = [
 
 #{'python': ('http://docs.python.org/3.2', None)}
 
+autoclass_content = "both"
+
 intersphinx_mapping = {
     'sqla':('http://www.sqlalchemy.org/docs/', None), 
 }
index bbc6437348f47a4497d09bf9ec27cb98754c3662..cee535c975d963b6b55d509b4c3acfbf2e70569e 100644 (file)
@@ -50,6 +50,36 @@ is installed, in addition to other dependencies.  Alembic will work with
 SQLAlchemy as of version **0.6**, though with a limited featureset.  
 The latest version of SQLAlchemy within the **0.7** series is strongly recommended.
 
+Upgrading from Alembic 0.2 to 0.3
+=================================
+
+Alembic 0.3 is mostly identical to version 0.2 except for some API
+changes, allowing better programmatic access and less ambiguity 
+between public and private methods.   In particular:
+
+* :class:`.ScriptDirectory` now features these methods - the old
+  versions have been removed unless noted:
+
+  * :meth:`.ScriptDirectory.iterate_revisions()`
+  * :meth:`.ScriptDirectory.get_current_head()` (old name ``_current_head`` is available)
+  * :meth:`.ScriptDirectory.get_heads()`
+  * :meth:`.ScriptDirectory.get_base()`
+  * :meth:`.ScriptDirectory.generate_revision()`
+  * :meth:`.ScriptDirectory.get_revision()` (old name ``_get_rev`` is available)
+  * :meth:`.ScriptDirectory.as_revision_number()` (old name ``_as_rev_number`` is available)
+
+* :meth:`.MigrationContext.get_current_revision()` (old name ``_current_rev`` remains available)
+
+* Methods which have been made private include ``ScriptDirectory._copy_file()``,
+  ``ScriptDirectory._generate_template()``, ``ScriptDirectory._upgrade_revs()``,
+  ``ScriptDirectory._downgrade_revs()``.   ``autogenerate._produce_migration_diffs``.
+  It's pretty unlikely that end-user applications
+  were using these directly.
+
+See the newly cleaned up :ref:`api` documentation for what are hopefully clearly
+laid out use cases for API usage, particularly being able to get at the revision
+information in a database as well as a script directory.
+
 Upgrading from Alembic 0.1 to 0.2
 =================================
 
index e8baba875c7e6ed2116592e968640cd449856743..d3193ae2e901f8d40c598307910aca385b37d4f0 100644 (file)
@@ -121,6 +121,10 @@ def ne_(a, b, msg=None):
     """Assert a != b, with repr messaging on failure."""
     assert a != b, msg or "%r == %r" % (a, b)
 
+def is_(a, b, msg=None):
+    """Assert a is b, with repr messaging on failure."""
+    assert a is b, msg or "%r is not %r" % (a, b)
+
 def assert_raises_message(except_cls, msg, callable_, *args, **kwargs):
     try:
         callable_(*args, **kwargs)
@@ -294,7 +298,7 @@ def write_script(scriptdir, rev_id, content):
     pyc_path = util.pyc_file_from_path(path)
     if os.access(pyc_path, os.F_OK):
         os.unlink(pyc_path)
-    script = Script.from_path(path)
+    script = Script._from_path(path)
     old = scriptdir._revision_map[script.revision]
     if old.down_revision != script.down_revision:
         raise Exception("Can't change down_revision "
@@ -309,7 +313,7 @@ def three_rev_fixture(cfg):
     c = util.rev_id()
 
     script = ScriptDirectory.from_config(cfg)
-    script.generate_rev(a, "revision a", refresh=True)
+    script.generate_revision(a, "revision a", refresh=True)
     write_script(script, a, """
 revision = '%s'
 down_revision = None
@@ -324,7 +328,7 @@ def downgrade():
 
 """ % a)
 
-    script.generate_rev(b, "revision b", refresh=True)
+    script.generate_revision(b, "revision b", refresh=True)
     write_script(script, b, """
 revision = '%s'
 down_revision = '%s'
@@ -339,7 +343,7 @@ def downgrade():
 
 """ % (b, a))
 
-    script.generate_rev(c, "revision c", refresh=True)
+    script.generate_revision(c, "revision c", refresh=True)
     write_script(script, c, """
 revision = '%s'
 down_revision = '%s'
index e03ceead1488667206d8cfd84617164d01225d50..9a1f6e7bb7c84cc5ae76835cd35d9e79db820822 100644 (file)
@@ -129,7 +129,7 @@ class AutogenerateDiffTest(TestCase):
         diffs = []
         autogenerate._produce_net_changes(connection, metadata, diffs, 
                                           self.autogen_context)
-        
+
         eq_(
             diffs[0],
             ('add_table', metadata.tables['item'])
@@ -177,7 +177,7 @@ class AutogenerateDiffTest(TestCase):
             }
         )
         template_args = {}
-        autogenerate.produce_migration_diffs(context, template_args, set())
+        autogenerate._produce_migration_diffs(context, template_args, set())
         eq_(re.sub(r"u'", "'", template_args['upgrades']),
 """### commands auto generated by Alembic - please adjust! ###
     pass
@@ -192,7 +192,7 @@ class AutogenerateDiffTest(TestCase):
 
         metadata = self.m2
         template_args = {}
-        autogenerate.produce_migration_diffs(self.context, template_args, set())
+        autogenerate._produce_migration_diffs(self.context, template_args, set())
         eq_(re.sub(r"u'", "'", template_args['upgrades']),
 """### commands auto generated by Alembic - please adjust! ###
     op.create_table('item',
@@ -320,7 +320,7 @@ class AutogenerateDiffOrderTest(TestCase):
                 'sqlalchemy_module_prefix':'sa.'
             }
         )
-        
+
         connection = empty_context.bind
         cls.autogen_empty_context = {
             'imports':set(),
@@ -332,7 +332,7 @@ class AutogenerateDiffOrderTest(TestCase):
     @classmethod
     def teardown_class(cls):
         clear_staging_env()
-    
+
     def test_diffs_order(self):
         """
         Added in order to test that child tables(tables with FKs) are generated
@@ -342,10 +342,10 @@ class AutogenerateDiffOrderTest(TestCase):
         metadata = self.m4
         connection = self.empty_context.bind
         diffs = []
-        
+
         autogenerate._produce_net_changes(connection, metadata, diffs, 
                                           self.autogen_empty_context)
-        
+
         eq_(diffs[0][0], 'add_table')
         eq_(diffs[0][1].name, "parent")
         eq_(diffs[1][0], 'add_table')
index 3eea778d303378e5d0a9c7a181bfa6aa07c21482..38560efda6613bbd1ba3127e55c1854bbc48ae16 100644 (file)
@@ -18,7 +18,7 @@ class PGOfflineEnumTest(TestCase):
         self.rid = rid = util.rev_id()
 
         self.script = script = ScriptDirectory.from_config(cfg)
-        script.generate_rev(rid, None, refresh=True)
+        script.generate_revision(rid, None, refresh=True)
 
     def tearDown(self):
         clear_staging_env()
index 2bdca938ab4fe26afae19097279ad91fd1b5be98..eaf1859d8df8b5afedec9f213a0fa980458dafec 100644 (file)
@@ -1,4 +1,4 @@
-from tests import clear_staging_env, staging_env, eq_, ne_
+from tests import clear_staging_env, staging_env, eq_, ne_, is_
 from alembic import util
 import os
 
@@ -16,20 +16,20 @@ def test_002_rev_ids():
     ne_(abc, def_)
 
 def test_003_heads():
-    eq_(env._get_heads(), [])
+    eq_(env.get_heads(), [])
 
 def test_004_rev():
-    script = env.generate_rev(abc, "this is a message", refresh=True)
+    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])
+    eq_(env.get_heads(), [abc])
 
 def test_005_nextrev():
-    script = env.generate_rev(def_, "this is the next rev", refresh=True)
+    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_)
@@ -38,7 +38,7 @@ def test_005_nextrev():
     assert script.module.down_revision == abc
     assert callable(script.module.upgrade)
     assert callable(script.module.downgrade)
-    eq_(env._get_heads(), [def_])
+    eq_(env.get_heads(), [def_])
 
 def test_006_from_clean_env():
     # test the environment so far with a 
@@ -50,17 +50,18 @@ def test_006_from_clean_env():
     eq_(abc_rev.nextrev, set([def_]))
     eq_(abc_rev.revision, abc)
     eq_(def_rev.down_revision, abc)
-    eq_(env._get_heads(), [def_])
+    eq_(env.get_heads(), [def_])
 
 def test_007_no_refresh():
-    script = env.generate_rev(util.rev_id(), "dont' refresh")
-    ne_(script, env._as_rev_number("head"))
+    rid = util.rev_id()
+    script = env.generate_revision(rid, "dont' refresh")
+    is_(script, None)
     env2 = staging_env(create=False)
-    eq_(script, env2._as_rev_number("head"))
+    eq_(env2._as_rev_number("head"), rid)
 
 def test_008_long_name():
     rid = util.rev_id()
-    script = env.generate_rev(rid, 
+    script = env.generate_revision(rid, 
             "this is a really long name with "
             "lots of characters and also "
             "I'd like it to\nhave\nnewlines")
index fd09a85b3031dffafc0273f54c2f87149ef148ab..127fda8adabf2cee3d2b14b2d41f7c2f808e2664 100644 (file)
@@ -6,11 +6,11 @@ def setup():
     global env
     env = staging_env()
     global a, b, c, d, e
-    a = env.generate_rev(util.rev_id(), None, refresh=True)
-    b = env.generate_rev(util.rev_id(), None, refresh=True)
-    c = env.generate_rev(util.rev_id(), None, refresh=True)
-    d = env.generate_rev(util.rev_id(), None, refresh=True)
-    e = env.generate_rev(util.rev_id(), None, refresh=True)
+    a = env.generate_revision(util.rev_id(), None, refresh=True)
+    b = env.generate_revision(util.rev_id(), None, refresh=True)
+    c = env.generate_revision(util.rev_id(), None, refresh=True)
+    d = env.generate_revision(util.rev_id(), None, refresh=True)
+    e = env.generate_revision(util.rev_id(), None, refresh=True)
 
 def teardown():
     clear_staging_env()
@@ -19,7 +19,7 @@ def teardown():
 def test_upgrade_path():
 
     eq_(
-        env.upgrade_from(e.revision, c.revision, None),
+        env._upgrade_revs(e.revision, c.revision),
         [
             (d.module.upgrade, c.revision, d.revision),
             (e.module.upgrade, d.revision, e.revision),
@@ -27,7 +27,7 @@ def test_upgrade_path():
     )
 
     eq_(
-        env.upgrade_from(c.revision, None, None),
+        env._upgrade_revs(c.revision, None),
         [
             (a.module.upgrade, None, a.revision),
             (b.module.upgrade, a.revision, b.revision),
@@ -38,7 +38,7 @@ def test_upgrade_path():
 def test_downgrade_path():
 
     eq_(
-        env.downgrade_to(c.revision, e.revision, None),
+        env._downgrade_revs(c.revision, e.revision),
         [
             (e.module.downgrade, e.revision, e.down_revision),
             (d.module.downgrade, d.revision, d.down_revision),
@@ -46,7 +46,7 @@ def test_downgrade_path():
     )
 
     eq_(
-        env.downgrade_to(None, c.revision, None),
+        env._downgrade_revs(None, c.revision),
         [
             (c.module.downgrade, c.revision, c.down_revision),
             (b.module.downgrade, b.revision, b.down_revision),
index 2f19157455a9f510d85a54d8ea9e20f5dbf51a15..989d612b34382590cc17b7030366511785e3ed18 100644 (file)
@@ -16,7 +16,7 @@ class VersioningTest(unittest.TestCase):
         c = util.rev_id()
 
         script = ScriptDirectory.from_config(self.cfg)
-        script.generate_rev(a, None, refresh=True)
+        script.generate_revision(a, None, refresh=True)
         write_script(script, a, """
     revision = '%s'
     down_revision = None
@@ -31,7 +31,7 @@ class VersioningTest(unittest.TestCase):
 
     """ % a)
 
-        script.generate_rev(b, None, refresh=True)
+        script.generate_revision(b, None, refresh=True)
         write_script(script, b, """
     revision = '%s'
     down_revision = '%s'
@@ -46,7 +46,7 @@ class VersioningTest(unittest.TestCase):
 
     """ % (b, a))
 
-        script.generate_rev(c, None, refresh=True)
+        script.generate_revision(c, None, refresh=True)
         write_script(script, c, """
     revision = '%s'
     down_revision = '%s'
@@ -117,7 +117,7 @@ class VersionNameTemplateTest(unittest.TestCase):
         self.cfg.set_main_option("file_template", "myfile_%%(slug)s")
         script = ScriptDirectory.from_config(self.cfg)
         a = util.rev_id()
-        script.generate_rev(a, "some message", refresh=True)
+        script.generate_revision(a, "some message", refresh=True)
         write_script(script, a, """
     revision = '%s'
     down_revision = None
@@ -141,7 +141,7 @@ class VersionNameTemplateTest(unittest.TestCase):
         self.cfg.set_main_option("file_template", "%%(rev)s")
         script = ScriptDirectory.from_config(self.cfg)
         a = util.rev_id()
-        script.generate_rev(a, None, refresh=True)
+        script.generate_revision(a, None, refresh=True)
         write_script(script, a, """
     down_revision = None
 
@@ -164,7 +164,7 @@ class VersionNameTemplateTest(unittest.TestCase):
         self.cfg.set_main_option("file_template", "%%(slug)s_%%(rev)s")
         script = ScriptDirectory.from_config(self.cfg)
         a = util.rev_id()
-        script.generate_rev(a, "foobar", refresh=True)
+        script.generate_revision(a, "foobar", refresh=True)
         assert_raises_message(
             util.CommandError,
             "Could not determine revision id from filename foobar_%s.py. "