On 8/1/15 6:59 PM, Ian McCullough wrote:
I've been getting up to speed
with SQLAlchemy and alembic and having a great
time of it. This is some great stuff!
One thing that's been confounding me is
this: My Alembic schema revisions are
'authoritative' for my metadata (i.e. I've
started from scratch using alembic to build
the schema from nothing), yet it doesn't
appear that the metadata that exists in my
alembic scripts can be leveraged by my
models in my main app. So far, I've been
maintaining effectively two versions of the
metadata, one in the form of the "flattened
projection" of my alembic schema rev
scripts, and another in my application
models scripts. I understand that there are
some facilities to auto-re-generate the
metadata from the RDBMS on the application
side, but that seems potentially "lossy", or
at least subject to the whims of whatever
DBAPI provider I'm using.
Is there a way to pull this flattened
projection of metadata out of alembic and
into my app's models at runtime? (i.e.
import alembic, read the version from the
live DB, then build the metadata by playing
the upgrade scripts forward, not against the
database, but against a metadata instance?)
It seems like a fool's errand to try to keep
my app models in sync with the flattened
projection of the schema revisions by hand.
My assumption is that I'm missing something
super-obvious here.
There's a lot to say on this issue. The idea of
the migrations themselves driving the metadata
would be nice, and I think that the recent rewrite
of django south does something somewhat analogous
to this.
Also, the reorganization of Alembic operations
into objects that you can hang any number of
operations upon, this is due for Alembic 0.8, is
also something that we'd leverage to make this
kind of thing happen.
However, where it gets thorny is that neither
Alembic migrations nor SQLAlchemy metadata are
supersets of each other. That is, there's many
things in SQLAlchemy metadata that currently has
no formal representation in Alembic operations,
the primary example is that of Python-side default
operations on columns, which have no relevance to
emitting ALTER statements. On the Alembic side,
a set of migrations that takes care to only use
the official Alembic op.* operations, and also
does not use "execute()" for any of them, is the
only way to guarantee that each change is
potentially representable in SQLAlchemy
metadata. A migration that emits
op.execute("ALTER TABLE foo ADD COLUMN xyz")
wouldn't work here, and a migration that has lots
of conditionals and runtime logic might also not
be useful in this way.
SQLAlchemy Table and Column objects also do not
support removal from their parents. This would
be necessary in order to represent "drop"
mutations as targeted at a SQLAlchemy metadata
structure. This is something that could be
implemented but SQLA has always made a point to
not get into this because it's very complicated to
handle "cascades" of dependent objects, whether
that means raising an error or mimicking other
functionality of a real "drop" operation.
Finally, the whole workflow of Alembic up til now
has been organized for the opposite workflow; the
MetaData is the authoritative model, and
migrations are generated using tools like
autogenerate to minimize how much they need to be
coded by hand (and there is of course no issue of
maintaining the same code in two places because
migration scripts are a fixed point in time once
created). This model is practical for many
reasons; all of the above reasons, plus that it is
compatible with applications that weren't using
migrations up to point or were using some other
system, plus that it allows easy pruning of old
migrations.