summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alembic/__init__.py7
-rw-r--r--alembic/context.py6
-rw-r--r--alembic/environment.py33
-rw-r--r--alembic/op.py17
-rw-r--r--alembic/operations.py4
-rw-r--r--alembic/util.py68
-rw-r--r--docs/build/api.rst4
-rw-r--r--docs/build/front.rst27
-rw-r--r--tests/test_op.py1
9 files changed, 103 insertions, 64 deletions
diff --git a/alembic/__init__.py b/alembic/__init__.py
index 09d91df..dd0591e 100644
--- a/alembic/__init__.py
+++ b/alembic/__init__.py
@@ -6,10 +6,5 @@ package_dir = path.abspath(path.dirname(__file__))
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
diff --git a/alembic/context.py b/alembic/context.py
new file mode 100644
index 0000000..6c33d85
--- /dev/null
+++ b/alembic/context.py
@@ -0,0 +1,6 @@
+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())
diff --git a/alembic/environment.py b/alembic/environment.py
index f61c9c7..5e86fcd 100644
--- a/alembic/environment.py
+++ b/alembic/environment.py
@@ -46,12 +46,12 @@ class EnvironmentContext(object):
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
@@ -78,7 +78,7 @@ class EnvironmentContext(object):
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()
@@ -105,7 +105,7 @@ class EnvironmentContext(object):
"""
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:
@@ -345,7 +345,7 @@ class EnvironmentContext(object):
"""
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.
@@ -359,7 +359,7 @@ class EnvironmentContext(object):
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.
@@ -370,7 +370,7 @@ class EnvironmentContext(object):
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
@@ -423,30 +423,25 @@ class EnvironmentContext(object):
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'.
@@ -458,9 +453,9 @@ class EnvironmentContext(object):
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
diff --git a/alembic/op.py b/alembic/op.py
index 8a5e0fa..9f2a26b 100644
--- a/alembic/op.py
+++ b/alembic/op.py
@@ -1,19 +1,6 @@
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())
diff --git a/alembic/operations.py b/alembic/operations.py
index f3e6708..9efa830 100644
--- a/alembic/operations.py
+++ b/alembic/operations.py
@@ -37,9 +37,9 @@ class Operations(object):
@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()
diff --git a/alembic/util.py b/alembic/util.py
index f58992a..3ae15f9 100644
--- a/alembic/util.py
+++ b/alembic/util.py
@@ -5,9 +5,11 @@ import sys
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
@@ -42,6 +44,72 @@ def template_to_file(template_file, dest, **kw):
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)
diff --git a/docs/build/api.rst b/docs/build/api.rst
index 3abc955..73e0948 100644
--- a/docs/build/api.rst
+++ b/docs/build/api.rst
@@ -14,7 +14,7 @@ and :class:`.Operations` classes, pictured below.
.. 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
@@ -34,7 +34,7 @@ via the :attr:`.EnvironmentContext.migration_context` datamember.
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
diff --git a/docs/build/front.rst b/docs/build/front.rst
index 3399d72..bbc6437 100644
--- a/docs/build/front.rst
+++ b/docs/build/front.rst
@@ -56,25 +56,6 @@ Upgrading from Alembic 0.1 to 0.2
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
@@ -92,7 +73,13 @@ installation. These include:
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
=========
diff --git a/tests/test_op.py b/tests/test_op.py
index c85869a..8af4711 100644
--- a/tests/test_op.py
+++ b/tests/test_op.py
@@ -7,6 +7,7 @@ from sqlalchemy import Integer, Column, ForeignKey, \
Boolean
from sqlalchemy.sql import table, column, func
+
def test_rename_table():
context = op_fixture()
op.rename_table('t1', 't2')