Skip to content

Commit

Permalink
- move to 0.3 as we are changing API
Browse files Browse the repository at this point in the history
- [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.
  • Loading branch information
zzzeek committed Apr 5, 2012
1 parent 67fda40 commit 8332e56
Show file tree
Hide file tree
Showing 17 changed files with 581 additions and 145 deletions.
25 changes: 23 additions & 2 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -196,7 +217,7 @@
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,
Expand Down Expand Up @@ -258,7 +279,7 @@
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
Expand Down
2 changes: 1 addition & 1 deletion alembic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from os import path

__version__ = '0.2.2'
__version__ = '0.3.0'

package_dir = path.abspath(path.dirname(__file__))

Expand Down
113 changes: 101 additions & 12 deletions alembic/autogenerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,95 @@
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:
Expand All @@ -24,22 +109,26 @@ 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))
template_args[opts['downgrade_token']] = \
_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
Expand Down Expand Up @@ -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:

Expand All @@ -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
)
Expand Down
41 changes: 24 additions & 17 deletions alembic/command.py
Original file line number Diff line number Diff line change
@@ -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"""
Expand Down Expand Up @@ -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
)
Expand All @@ -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):
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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):
Expand All @@ -154,10 +161,10 @@ def display_version(rev, context):
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
Expand All @@ -174,12 +181,12 @@ def do_stamp(rev, context):
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,
Expand Down
1 change: 1 addition & 0 deletions alembic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading

0 comments on commit 8332e56

Please sign in to comment.