On 6/10/14, 6:09 PM, Jon Rosebaugh wrote:
At my workplace, we use Alembic to handle migrations, but we
frequently have minor issues like forgetting to downgrade to the most
recent common migration before switching git branches, or not noticing
that a branch has migrations to be run when checking it out. This
causes a bit of aggravation.
I've just written a post-checkout git hook which helps with this by
inspecting the Alembic revision history and determining if the current
revision is present, and if so does it have any child revisions.
Unfortunately, I'm not very proud of the code I had to write to do
this.
Here's the relevant bits; the full git hook is at
https://gist.github.com/inklesspen/3289015398d14b740074
class CaptureCurrentContext(EnvironmentContext):
# This is a sham EnvironmentContext which only captures the current
revision
def __init__(self, cfg, script, **kw):
super(CaptureCurrentContext, self).__init__(cfg, script, **kw)
# we use a set because we have multiple DBs configured in env.py,
# so run_migrations will be called once for each DB.
self.current_revisions = set()
def run_migrations(self, **kw):
self.current_revisions.add(self.get_context().get_current_revision())
with py.path.local(alembic_root).as_cwd():
# as_cwd is a context manager for the current directory
# since the alembic script_location seems to be interpreted relative to
cwd,
# rather than the alembic.ini location
fake_cmd_opts = type('args', (object,), {'x': []})()
# Would be nice if I could pass None for cmd_opts, but that causes
a traceback.
cfg = Config(file_="alembic.ini", cmd_opts=fake_cmd_opts)
script = ScriptDirectory.from_config(cfg)
sham = CaptureCurrentContext(cfg, script)
with sham:
script.run_env()
# now we check that both DBs are on the same revision using
sham.current_revisions
# and use script's .get_heads() and .walk_revisions() methods to
get info about the tree
I don't see why "fake_cmd_opts" is needed, it seems like maybe you hit
https://bitbucket.org/zzzeek/alembic/issue/195/ but that's been fixed,
just upgrade.
Working directory, the first argument to ScriptDirectory is "dir", which
is the directory where everything resides, if you are using
ScriptDirectory.from_config() then you'd put "script_location" into the
config just like in
http://alembic.readthedocs.org/en/latest/api.html#alembic.script.ScriptDirectory
to point to wherever that is.
"sham environmentconfig", well if you want to get the version number
that is present inside the alembic_version table, and you want to use
Alembic API to get at that, you need to have a database connection, but
"env.py" isn't necessarily "connect to a single database", an env.py
might be iterating through many databases. The use case of "get the
current version in a particular database" is documented using
MigrationContext directly:
http://alembic.readthedocs.org/en/latest/api.html#alembic.migration.MigrationContext
get_revision() raises a CommandError - It is extremely unusual that you
have a use case where you are getting a revision identifier from that
does not exist in the established versions/. You're exec'ing git and
pulling down source files and stuff like that that are intentionally not
necessarily lined up with the database that's present, I guess. If it
were me I'd just do the walk_revisions() and load it all into some other
data structure that suits what I'm doing, a has_revision() method
certainly not a big deal to add (pull request).
cfg.revision_tree() - script.walk_revisions() ?
cfg.current_revision() - MigrationContext.get_current_revisions() ?
Annoyances:
* have to make a fake cmd_opts
* have to change working directory instead of being able to infer the
script directory from the ini file or an argument
* Have to make a sham EnvironmentContext and actually run the env.py
script because Alembic's env setup relies on module-level code to run
the migration instead of calling a main() function in env.py
* ScriptDirectory.get_revision() raises a util.CommandError -- the
same exception raised by nearly every error case -- instead of
something appropriate to a missing key; also doesn't have a
.has_revision(), so my code has to implement that check with a
try/catch
* In general, I have to get my hands dirty with alembic implementation
details instead of calling cfg.revision_tree() or
cfg.current_revision(). Alembic has commands to get this information,
but they print to stdout instead of returning useful python objects.
IMO it would be better for Alembic to have one layer which produces
the Python objects and then a wrapper layer to print those to stdout.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy-alembic" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.