from alembic import op
-
-class _ContextProxy(object):
- """A proxy object for the current :class:`.EnvironmentContext`."""
- def __getattr__(self, key):
- return getattr(_context, key)
-context = _ContextProxy()
+from alembic import context
--- /dev/null
+from alembic.environment import EnvironmentContext
+from alembic import util
+
+# create proxy functions for
+# each method on the EnvironmentContext class.
+util.create_module_class_proxy(EnvironmentContext, globals(), locals())
be made available as ``from alembic import context``.
"""
- alembic._context = self
+ alembic.context._install_proxy(self)
return self
def __exit__(self, *arg, **kw):
- alembic._context = None
- alembic.op._proxy = None
+ alembic.context._remove_proxy()
+ alembic.op._remove_proxy()
def is_offline_mode(self):
"""Return True if the current migrations environment
made available via :meth:`.configure`.
"""
- return self.migration_context.impl.transactional_ddl
+ return self.get_context().impl.transactional_ddl
def requires_connection(self):
return not self.is_offline_mode()
"""
if self._migration_context is not None:
- return self.script._as_rev_number(self.migration_context._start_from_rev)
+ return self.script._as_rev_number(self.get_context()._start_from_rev)
elif 'starting_rev' in self.context_opts:
return self.script._as_rev_number(self.context_opts['starting_rev'])
else:
"""
with Operations.context(self._migration_context):
- self.migration_context.run_migrations(**kw)
+ self.get_context().run_migrations(**kw)
def execute(self, sql):
"""Execute the given SQL using the current change context.
made available via :meth:`.configure`.
"""
- self.migration_context.execute(sql)
+ self.get_context().execute(sql)
def static_output(self, text):
"""Emit text directly to the "offline" SQL stream.
is added, etc.
"""
- self.migration_context.impl.static_output(text)
+ self.get_context().impl.static_output(text)
def begin_transaction(self):
"""Return a context manager that will
elif self.is_offline_mode():
@contextmanager
def begin_commit():
- self.migration_context.impl.emit_begin()
+ self.get_context().impl.emit_begin()
yield
- self.migration_context.impl.emit_commit()
+ self.get_context().impl.emit_commit()
return begin_commit()
else:
return self.get_bind().begin()
- @property
- def migration_context(self):
+ def get_context(self):
"""Return the current :class:`.MigrationContext` object.
If :meth:`.EnvironmentContext.configure` has not been called yet, raises
an exception.
"""
+
if self._migration_context is None:
raise Exception("No context has been configured yet.")
return self._migration_context
- def get_context(self):
- """A synonym for :attr:`.EnvironmentContext.migration_context`."""
-
- return self.migration_context
-
def get_bind(self):
"""Return the current 'bind'.
made available via :meth:`.configure`.
"""
- return self.migration_context.bind
+ return self.get_context().bind
def get_impl(self):
- return self.migration_context.impl
+ return self.get_context().impl
configure = EnvironmentContext
from alembic.operations import Operations
+from alembic import util
# create proxy functions for
# each method on the Operations class.
-
-# TODO: this is a quick and dirty version of this.
-# Ideally, we'd be duplicating method signatures
-# and such, using eval(), etc.
-
-_proxy = None
-def _create_op_proxy(name):
- def go(*arg, **kw):
- return getattr(_proxy, name)(*arg, **kw)
- go.__name__ = name
- return go
-
-for methname in dir(Operations):
- if not methname.startswith('_'):
- locals()[methname] = _create_op_proxy(methname)
\ No newline at end of file
+util.create_module_class_proxy(Operations, globals(), locals())
@contextmanager
def context(cls, migration_context):
op = Operations(migration_context)
- alembic.op._proxy = op
+ alembic.op._install_proxy(op)
yield op
- del alembic.op._proxy
+ alembic.op._remove_proxy()
def _foreign_key_constraint(self, name, source, referent, local_cols, remote_cols):
m = schema.MetaData()
import os
import textwrap
from sqlalchemy.engine import url
+from sqlalchemy import util as sqla_util
import imp
import warnings
import re
+import inspect
import time
import random
import uuid
Template(filename=template_file).render(**kw)
)
+def create_module_class_proxy(cls, globals_, locals_):
+ """Create module level proxy functions for the
+ methods on a given class.
+
+ The functions will have a compatible signature
+ as the methods. A proxy is established
+ using the ``_install_proxy(obj)`` function,
+ and removed using ``_remove_proxy()``, both
+ installed by calling this function.
+
+ """
+ attr_names = set()
+
+ def _install_proxy(obj):
+ globals_['_proxy'] = obj
+ for name in attr_names:
+ globals_[name] = getattr(obj, name)
+
+ def _remove_proxy():
+ globals_['_proxy'] = None
+ for name in attr_names:
+ del globals_[name]
+
+ globals_['_install_proxy'] = _install_proxy
+ globals_['_remove_proxy'] = _remove_proxy
+
+ def _create_op_proxy(name):
+ fn = getattr(cls, name)
+ spec = inspect.getargspec(fn)
+ if spec[0] and spec[0][0] == 'self':
+ spec[0].pop(0)
+ args = inspect.formatargspec(*spec)
+ num_defaults = 0
+ if spec[3]:
+ num_defaults += len(spec[3])
+ name_args = spec[0]
+ if num_defaults:
+ defaulted_vals = name_args[0-num_defaults:]
+ else:
+ defaulted_vals = ()
+
+ apply_kw = inspect.formatargspec(
+ name_args, spec[1], spec[2],
+ defaulted_vals,
+ formatvalue=lambda x: '=' + x)
+
+ func_text = textwrap.dedent("""\
+ def %(name)s(%(args)s):
+ %(doc)r
+ return _proxy.%(name)s(%(apply_kw)s)
+ """ % {
+ 'name':name,
+ 'args':args[1:-1],
+ 'apply_kw':apply_kw[1:-1],
+ 'doc':fn.__doc__,
+ })
+ lcl = {}
+ exec func_text in globals_, lcl
+ return lcl[name]
+
+ for methname in dir(cls):
+ if not methname.startswith('_'):
+ if callable(getattr(cls, methname)):
+ locals_[methname] = _create_op_proxy(methname)
+ else:
+ attr_names.add(methname)
def status(_statmsg, fn, *arg, **kw):
msg(_statmsg + "...", False)
.. image:: api_overview.png
An Alembic command begins by instantiating an :class:`.EnvironmentContext` object, then
-making it available via the ``alembic.context`` datamember. The ``env.py``
+making it available via the ``alembic.context`` proxy module. The ``env.py``
script, representing a user-configurable migration environment, is then
invoked. The ``env.py`` script is then responsible for calling upon the
:meth:`.EnvironmentContext.configure`, whose job it is to create
Finally, ``env.py`` calls upon the :meth:`.EnvironmentContext.run_migrations`
method. Within this method, a new :class:`.Operations` object, which
provides an API for individual database migration operations, is established
-within the ``alembic.op`` datamember. The :class:`.Operations` object
+within the ``alembic.op`` proxy module. The :class:`.Operations` object
uses the :class:`.MigrationContext` object ultimately as a source of
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
Alembic 0.2 has some reorganizations and features that might impact an existing 0.1
installation. These include:
-* The ``alembic.op`` module is now generated from a class called
- :class:`.Operations`, including standalone functions that each proxy
- to the current instance of :class:`.Operations`. The behavior here
- is tailored such that an existing migration script that imports
- symbols directly from ``alembic.op``, that is,
- ``from alembic.op import create_table``, should still work fine; though ideally
- it's better to use the style ``from alembic import op``, then call
- migration methods directly from the ``op`` member. The functions inside
- of ``alembic.op`` are at the moment minimally tailored proxies; a future
- release should refine these to more closely resemble the :class:`.Operations`
- methods they represent.
-* The ``alembic.context`` module no longer exists, instead ``alembic.context``
- is an object inside the ``alembic`` module which proxies to an underlying
- instance of :class:`.EnvironmentContext`. :class:`.EnvironmentContext`
- represents the current environment in an encapsulated way. Most ``env.py``
- scripts that don't import from the ``alembic.context`` name directly,
- instead importing ``context`` itself, should be fine here. A script that attempts to
- import from it, such as ``from alembic.context import configure``, will
- need to be changed to read ``from alembic import context; context.configure()``.
* The naming convention for migration files is now customizable, and defaults
to the scheme "%(rev)s_%(slug)s", where "slug" is based on the message
added to the script. When Alembic reads one of these files, it looks
unless you are renaming them. Alembic will fall back to pulling in the version
identifier from the filename if ``revision`` isn't present, as long as the
filename uses the old naming convention.
-
+* The ``alembic.op`` and ``alembic.context`` modules are now generated
+ as a collection of proxy functions, which when used refer to an
+ object instance installed when migrations run. ``alembic.op`` refers to
+ an instance of the :class:`.Operations` object, and ``alembic.context`` refers to
+ an instance of the :class:`.EnvironmentContext` object. Most existing
+ setups should be able to run with no changes, as the functions are
+ established at module load time and remain fully importable.
Community
=========
Boolean
from sqlalchemy.sql import table, column, func
+
def test_rename_table():
context = op_fixture()
op.rename_table('t1', 't2')