summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-06-11 18:43:59 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-06-11 18:58:47 -0400
commit0c71f985e4885e4cd5c04eee46d73d7870bad6df (patch)
tree017d87272f4eec458f5fd3fcc9d8b191f741dab0
parent541cbd26b5b86ce445f2065b60d28fdcbbb299a9 (diff)
downloadalembic-0c71f985e4885e4cd5c04eee46d73d7870bad6df.tar.gz
- refactor most files into packages. provide a degree of
backwards compat for major import targets. command and config are too front-facing for a move like this so leave them as is. first part of #302
-rw-r--r--.gitignore1
-rw-r--r--alembic/__init__.py8
-rw-r--r--alembic/autogenerate/api.py2
-rw-r--r--alembic/autogenerate/compare.py2
-rw-r--r--alembic/autogenerate/render.py4
-rw-r--r--alembic/command.py5
-rw-r--r--alembic/config.py7
-rw-r--r--alembic/context.py2
-rw-r--r--alembic/ddl/impl.py2
-rw-r--r--alembic/ddl/mysql.py2
-rw-r--r--alembic/ddl/postgresql.py2
-rw-r--r--alembic/op.py2
-rw-r--r--alembic/operations/__init__.py3
-rw-r--r--alembic/operations/base.py (renamed from alembic/operations.py)9
-rw-r--r--alembic/operations/batch.py (renamed from alembic/batch.py)4
-rw-r--r--alembic/runtime/__init__.py0
-rw-r--r--alembic/runtime/environment.py (renamed from alembic/environment.py)8
-rw-r--r--alembic/runtime/migration.py (renamed from alembic/migration.py)4
-rw-r--r--alembic/script/__init__.py3
-rw-r--r--alembic/script/base.py (renamed from alembic/script.py)6
-rw-r--r--alembic/script/revision.py (renamed from alembic/revision.py)5
-rw-r--r--alembic/testing/assertions.py4
-rw-r--r--alembic/testing/env.py6
-rw-r--r--alembic/testing/exclusions.py3
-rw-r--r--alembic/testing/fixtures.py13
-rw-r--r--alembic/testing/mock.py2
-rw-r--r--alembic/testing/provision.py6
-rw-r--r--alembic/util/__init__.py20
-rw-r--r--alembic/util/compat.py (renamed from alembic/compat.py)0
-rw-r--r--alembic/util/langhelpers.py (renamed from alembic/util.py)201
-rw-r--r--alembic/util/messaging.py94
-rw-r--r--alembic/util/pyfiles.py80
-rw-r--r--alembic/util/sqla_compat.py24
-rw-r--r--docs/build/api.rst6
-rw-r--r--docs/build/ops.rst10
-rw-r--r--tests/test_autogen_render.py3
-rw-r--r--tests/test_batch.py8
-rw-r--r--tests/test_config.py5
-rw-r--r--tests/test_op.py3
-rw-r--r--tests/test_revision.py2
-rw-r--r--tests/test_script_consumption.py3
41 files changed, 305 insertions, 269 deletions
diff --git a/.gitignore b/.gitignore
index 0875618..5a97f5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ alembic.ini
.coverage
coverage.xml
.tox
+*.patch
diff --git a/alembic/__init__.py b/alembic/__init__.py
index f429441..345bf26 100644
--- a/alembic/__init__.py
+++ b/alembic/__init__.py
@@ -1,9 +1,15 @@
from os import path
-__version__ = '0.7.7'
+__version__ = '0.8.0'
package_dir = path.abspath(path.dirname(__file__))
from . import op # noqa
from . import context # noqa
+
+import sys
+from .runtime import environment
+from .runtime import migration
+sys.modules['alembic.migration'] = migration
+sys.modules['alembic.environment'] = environment
diff --git a/alembic/autogenerate/api.py b/alembic/autogenerate/api.py
index 6281a6c..e912a5d 100644
--- a/alembic/autogenerate/api.py
+++ b/alembic/autogenerate/api.py
@@ -5,7 +5,7 @@ import logging
import itertools
import re
-from ..compat import StringIO
+from ..util.compat import StringIO
from mako.pygen import PythonPrinter
from sqlalchemy.engine.reflection import Inspector
diff --git a/alembic/autogenerate/compare.py b/alembic/autogenerate/compare.py
index 2aae962..9c04f80 100644
--- a/alembic/autogenerate/compare.py
+++ b/alembic/autogenerate/compare.py
@@ -1,7 +1,7 @@
from sqlalchemy import schema as sa_schema, types as sqltypes
from sqlalchemy import event
import logging
-from .. import compat
+from ..util import compat
from sqlalchemy.util import OrderedSet
import re
from .render import _user_defined_render
diff --git a/alembic/autogenerate/render.py b/alembic/autogenerate/render.py
index 5007652..82f348b 100644
--- a/alembic/autogenerate/render.py
+++ b/alembic/autogenerate/render.py
@@ -1,9 +1,9 @@
from sqlalchemy import schema as sa_schema, types as sqltypes, sql
import logging
-from .. import compat
+from ..util import compat
from ..ddl.base import _table_for_constraint, _fk_spec
import re
-from ..compat import string_types
+from ..util.compat import string_types
log = logging.getLogger(__name__)
diff --git a/alembic/command.py b/alembic/command.py
index 5ba6d6a..fc80e38 100644
--- a/alembic/command.py
+++ b/alembic/command.py
@@ -1,8 +1,9 @@
import os
from .script import ScriptDirectory
-from .environment import EnvironmentContext
-from . import util, autogenerate as autogen
+from .runtime.environment import EnvironmentContext
+from . import util
+from . import autogenerate as autogen
def list_templates(config):
diff --git a/alembic/config.py b/alembic/config.py
index 7f813d2..74b4d17 100644
--- a/alembic/config.py
+++ b/alembic/config.py
@@ -1,10 +1,13 @@
from argparse import ArgumentParser
-from .compat import SafeConfigParser
+from .util.compat import SafeConfigParser
import inspect
import os
import sys
-from . import command, util, package_dir, compat
+from . import command
+from . import util
+from . import package_dir
+from .util import compat
class Config(object):
diff --git a/alembic/context.py b/alembic/context.py
index 9c0f676..1b1a3ab 100644
--- a/alembic/context.py
+++ b/alembic/context.py
@@ -1,4 +1,4 @@
-from .environment import EnvironmentContext
+from .runtime.environment import EnvironmentContext
from . import util
# create proxy functions for
diff --git a/alembic/ddl/impl.py b/alembic/ddl/impl.py
index 3cca1ef..8c4f1d6 100644
--- a/alembic/ddl/impl.py
+++ b/alembic/ddl/impl.py
@@ -3,7 +3,7 @@ from sqlalchemy.ext.compiler import compiles
from sqlalchemy import schema, text, sql
from sqlalchemy import types as sqltypes
-from ..compat import string_types, text_type, with_metaclass
+from ..util.compat import string_types, text_type, with_metaclass
from .. import util
from . import base
diff --git a/alembic/ddl/mysql.py b/alembic/ddl/mysql.py
index 7956185..fe42145 100644
--- a/alembic/ddl/mysql.py
+++ b/alembic/ddl/mysql.py
@@ -2,7 +2,7 @@ from sqlalchemy.ext.compiler import compiles
from sqlalchemy import types as sqltypes
from sqlalchemy import schema
-from ..compat import string_types
+from ..util.compat import string_types
from .. import util
from .impl import DefaultImpl
from .base import ColumnNullable, ColumnName, ColumnDefault, \
diff --git a/alembic/ddl/postgresql.py b/alembic/ddl/postgresql.py
index 9f97b34..ea423d7 100644
--- a/alembic/ddl/postgresql.py
+++ b/alembic/ddl/postgresql.py
@@ -1,6 +1,6 @@
import re
-from .. import compat
+from ..util import compat
from .. import util
from .base import compiles, alter_table, format_table_name, RenameTable
from .impl import DefaultImpl
diff --git a/alembic/op.py b/alembic/op.py
index 8e5f777..615fd58 100644
--- a/alembic/op.py
+++ b/alembic/op.py
@@ -1,4 +1,4 @@
-from .operations import Operations
+from .operations.base import Operations
from . import util
# create proxy functions for
diff --git a/alembic/operations/__init__.py b/alembic/operations/__init__.py
new file mode 100644
index 0000000..d7902ea
--- /dev/null
+++ b/alembic/operations/__init__.py
@@ -0,0 +1,3 @@
+from .base import Operations, BatchOperations # noqa
+
+__all__ = ['Operations', 'BatchOperations'] \ No newline at end of file
diff --git a/alembic/operations.py b/alembic/operations/base.py
index 2bf8060..cda5aa2 100644
--- a/alembic/operations.py
+++ b/alembic/operations/base.py
@@ -3,9 +3,10 @@ from contextlib import contextmanager
from sqlalchemy.types import NULLTYPE, Integer
from sqlalchemy import schema as sa_schema
-from . import util, batch
-from .compat import string_types
-from .ddl import impl
+from .. import util
+from . import batch
+from ..util.compat import string_types
+from ..ddl import impl
__all__ = ('Operations', 'BatchOperations')
@@ -58,7 +59,7 @@ class Operations(object):
@classmethod
@contextmanager
def context(cls, migration_context):
- from .op import _install_proxy, _remove_proxy
+ from ..op import _install_proxy, _remove_proxy
op = Operations(migration_context)
_install_proxy(op)
yield op
diff --git a/alembic/batch.py b/alembic/operations/batch.py
index 1006739..9644a33 100644
--- a/alembic/batch.py
+++ b/alembic/operations/batch.py
@@ -3,8 +3,8 @@ from sqlalchemy import Table, MetaData, Index, select, Column, \
from sqlalchemy import types as sqltypes
from sqlalchemy import schema as sql_schema
from sqlalchemy.util import OrderedDict
-from . import util
-from .ddl.base import _columns_for_constraint, _is_type_bound
+from .. import util
+from ..ddl.base import _columns_for_constraint, _is_type_bound
class BatchOperationsImpl(object):
diff --git a/alembic/runtime/__init__.py b/alembic/runtime/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/alembic/runtime/__init__.py
diff --git a/alembic/environment.py b/alembic/runtime/environment.py
index 860315b..287d70a 100644
--- a/alembic/environment.py
+++ b/alembic/runtime/environment.py
@@ -1,6 +1,6 @@
-from .operations import Operations
+from ..operations import Operations
from .migration import MigrationContext
-from . import util
+from .. import util
class EnvironmentContext(object):
@@ -96,12 +96,12 @@ class EnvironmentContext(object):
be made available as ``from alembic import context``.
"""
- from .context import _install_proxy
+ from ..context import _install_proxy
_install_proxy(self)
return self
def __exit__(self, *arg, **kw):
- from . import context, op
+ from .. import context, op
context._remove_proxy()
op._remove_proxy()
diff --git a/alembic/migration.py b/alembic/runtime/migration.py
index 9b46052..84a3c7f 100644
--- a/alembic/migration.py
+++ b/alembic/runtime/migration.py
@@ -6,8 +6,8 @@ from sqlalchemy import MetaData, Table, Column, String, literal_column
from sqlalchemy.engine.strategies import MockEngineStrategy
from sqlalchemy.engine import url as sqla_url
-from .compat import callable, EncodedIO
-from . import ddl, util
+from ..util.compat import callable, EncodedIO
+from .. import ddl, util
log = logging.getLogger(__name__)
diff --git a/alembic/script/__init__.py b/alembic/script/__init__.py
new file mode 100644
index 0000000..cae294f
--- /dev/null
+++ b/alembic/script/__init__.py
@@ -0,0 +1,3 @@
+from .base import ScriptDirectory, Script # noqa
+
+__all__ = ['ScriptDirectory', 'Script']
diff --git a/alembic/script.py b/alembic/script/base.py
index 095a04b..e30c8b2 100644
--- a/alembic/script.py
+++ b/alembic/script/base.py
@@ -2,10 +2,10 @@ import datetime
import os
import re
import shutil
-from . import util
-from . import compat
+from .. import util
+from ..util import compat
from . import revision
-from . import migration
+from ..runtime import migration
from contextlib import contextmanager
diff --git a/alembic/revision.py b/alembic/script/revision.py
index 4eea514..e9958b1 100644
--- a/alembic/revision.py
+++ b/alembic/script/revision.py
@@ -1,10 +1,9 @@
import re
import collections
-import itertools
-from . import util
+from .. import util
from sqlalchemy import util as sqlautil
-from . import compat
+from ..util import compat
_relative_destination = re.compile(r'(?:(.+?)@)?(\w+)?((?:\+|-)\d+)')
diff --git a/alembic/testing/assertions.py b/alembic/testing/assertions.py
index b3a5acd..6acca21 100644
--- a/alembic/testing/assertions.py
+++ b/alembic/testing/assertions.py
@@ -2,9 +2,9 @@ from __future__ import absolute_import
import re
-from alembic import util
+from .. import util
from sqlalchemy.engine import default
-from alembic.compat import text_type, py3k
+from ..util.compat import text_type, py3k
import contextlib
from sqlalchemy.util import decorator
from sqlalchemy import exc as sa_exc
diff --git a/alembic/testing/env.py b/alembic/testing/env.py
index 9c53d5d..f8ad447 100644
--- a/alembic/testing/env.py
+++ b/alembic/testing/env.py
@@ -4,9 +4,9 @@ import os
import shutil
import textwrap
-from alembic.compat import u
-from alembic.script import Script, ScriptDirectory
-from alembic import util
+from ..util.compat import u
+from ..script import Script, ScriptDirectory
+from .. import util
from . import engines
from . import provision
diff --git a/alembic/testing/exclusions.py b/alembic/testing/exclusions.py
index 88df9fc..90f8bc6 100644
--- a/alembic/testing/exclusions.py
+++ b/alembic/testing/exclusions.py
@@ -14,11 +14,12 @@ from .plugin.plugin_base import SkipTest
from sqlalchemy.util import decorator
from . import config
from sqlalchemy import util
-from alembic import compat
+from ..util import compat
import inspect
import contextlib
from .compat import get_url_driver_name, get_url_backend_name
+
def skip_if(predicate, reason=None):
rule = compound()
pred = _as_predicate(predicate, reason)
diff --git a/alembic/testing/fixtures.py b/alembic/testing/fixtures.py
index ae25fd2..7e05525 100644
--- a/alembic/testing/fixtures.py
+++ b/alembic/testing/fixtures.py
@@ -5,13 +5,12 @@ import re
from sqlalchemy import create_engine, text, MetaData
import alembic
-from alembic.compat import configparser
-from alembic import util
-from alembic.compat import string_types, text_type
-from alembic.migration import MigrationContext
-from alembic.environment import EnvironmentContext
-from alembic.operations import Operations
-from alembic.ddl.impl import _impls
+from ..util.compat import configparser
+from .. import util
+from ..util.compat import string_types, text_type
+from ..migration import MigrationContext
+from ..environment import EnvironmentContext
+from ..operations import Operations
from contextlib import contextmanager
from .plugin.plugin_base import SkipTest
from .assertions import _get_dialect, eq_
diff --git a/alembic/testing/mock.py b/alembic/testing/mock.py
index cdfcb88..b82a404 100644
--- a/alembic/testing/mock.py
+++ b/alembic/testing/mock.py
@@ -12,7 +12,7 @@
"""
from __future__ import absolute_import
-from alembic.compat import py33
+from ..util.compat import py33
if py33:
from unittest.mock import MagicMock, Mock, call, patch
diff --git a/alembic/testing/provision.py b/alembic/testing/provision.py
index 801d36b..37ae141 100644
--- a/alembic/testing/provision.py
+++ b/alembic/testing/provision.py
@@ -3,9 +3,9 @@
"""
from sqlalchemy.engine import url as sa_url
from sqlalchemy import text
-from alembic import compat
-from alembic.testing import config, engines
-from alembic.testing.compat import get_url_backend_name
+from ..util import compat
+from . import config, engines
+from .compat import get_url_backend_name
FOLLOWER_IDENT = None
diff --git a/alembic/util/__init__.py b/alembic/util/__init__.py
new file mode 100644
index 0000000..6cfc5d3
--- /dev/null
+++ b/alembic/util/__init__.py
@@ -0,0 +1,20 @@
+from .langhelpers import ( # noqa
+ create_module_class_proxy, asbool, rev_id, to_tuple, memoized_property,
+ immutabledict, _with_legacy_names)
+from .messaging import ( # noqa
+ write_outstream, status, err, obfuscate_url_pw, warn, msg, format_as_comma)
+from .pyfiles import ( # noqa
+ template_to_file, coerce_resource_to_filename, simple_pyc_file_from_path,
+ pyc_file_from_path, load_python_file)
+from .sqla_compat import ( # noqa
+ sqla_07, sqla_079, sqla_08, sqla_083, sqla_084, sqla_09, sqla_092,
+ sqla_094, sqla_094, sqla_099, sqla_100, sqla_105)
+
+
+class CommandError(Exception):
+ pass
+
+
+if not sqla_07:
+ raise CommandError(
+ "SQLAlchemy 0.7.3 or greater is required. ")
diff --git a/alembic/compat.py b/alembic/util/compat.py
index a9e35f0..a9e35f0 100644
--- a/alembic/compat.py
+++ b/alembic/util/compat.py
diff --git a/alembic/util.py b/alembic/util/langhelpers.py
index 2e0f731..04cb6e4 100644
--- a/alembic/util.py
+++ b/alembic/util/langhelpers.py
@@ -1,78 +1,14 @@
-import sys
-import os
import textwrap
import warnings
-import re
import inspect
import uuid
import collections
-from mako.template import Template
-from sqlalchemy.engine import url
-from sqlalchemy import __version__
-
-from .compat import callable, exec_, load_module_py, load_module_pyc, \
- binary_type, string_types, py27
-
-
-class CommandError(Exception):
- pass
-
-
-def _safe_int(value):
- try:
- return int(value)
- except:
- return value
-_vers = tuple(
- [_safe_int(x) for x in re.findall(r'(\d+|[abc]\d)', __version__)])
-sqla_07 = _vers > (0, 7, 2)
-sqla_079 = _vers >= (0, 7, 9)
-sqla_08 = _vers >= (0, 8, 0)
-sqla_083 = _vers >= (0, 8, 3)
-sqla_084 = _vers >= (0, 8, 4)
-sqla_09 = _vers >= (0, 9, 0)
-sqla_092 = _vers >= (0, 9, 2)
-sqla_094 = _vers >= (0, 9, 4)
-sqla_094 = _vers >= (0, 9, 4)
-sqla_099 = _vers >= (0, 9, 9)
-sqla_100 = _vers >= (1, 0, 0)
-sqla_105 = _vers >= (1, 0, 5)
-if not sqla_07:
- raise CommandError(
- "SQLAlchemy 0.7.3 or greater is required. ")
+from .compat import callable, exec_, string_types
from sqlalchemy.util import format_argspec_plus, update_wrapper
from sqlalchemy.util.compat import inspect_getfullargspec
-import logging
-log = logging.getLogger(__name__)
-
-if py27:
- # disable "no handler found" errors
- logging.getLogger('alembic').addHandler(logging.NullHandler())
-
-
-try:
- import fcntl
- import termios
- import struct
- ioctl = fcntl.ioctl(0, termios.TIOCGWINSZ,
- struct.pack('HHHH', 0, 0, 0, 0))
- _h, TERMWIDTH, _hp, _wp = struct.unpack('HHHH', ioctl)
- if TERMWIDTH <= 0: # can occur if running in emacs pseudo-tty
- TERMWIDTH = None
-except (ImportError, IOError):
- TERMWIDTH = None
-
-
-def template_to_file(template_file, dest, output_encoding, **kw):
- with open(dest, 'wb') as f:
- template = Template(filename=template_file)
- f.write(
- template.render_unicode(**kw).encode(output_encoding)
- )
-
def create_module_class_proxy(cls, globals_, locals_):
"""Create module level proxy functions for the
@@ -157,135 +93,11 @@ def create_module_class_proxy(cls, globals_, locals_):
attr_names.add(methname)
-def write_outstream(stream, *text):
- encoding = getattr(stream, 'encoding', 'ascii') or 'ascii'
- for t in text:
- if not isinstance(t, binary_type):
- t = t.encode(encoding, 'replace')
- t = t.decode(encoding)
- try:
- stream.write(t)
- except IOError:
- # suppress "broken pipe" errors.
- # no known way to handle this on Python 3 however
- # as the exception is "ignored" (noisily) in TextIOWrapper.
- break
-
-
-def coerce_resource_to_filename(fname):
- """Interpret a filename as either a filesystem location or as a package
- resource.
-
- Names that are non absolute paths and contain a colon
- are interpreted as resources and coerced to a file location.
-
- """
- if not os.path.isabs(fname) and ":" in fname:
- import pkg_resources
- fname = pkg_resources.resource_filename(*fname.split(':'))
- return fname
-
-
-def status(_statmsg, fn, *arg, **kw):
- msg(_statmsg + " ...", False)
- try:
- ret = fn(*arg, **kw)
- write_outstream(sys.stdout, " done\n")
- return ret
- except:
- write_outstream(sys.stdout, " FAILED\n")
- raise
-
-
-def err(message):
- log.error(message)
- msg("FAILED: %s" % message)
- sys.exit(-1)
-
-
-def obfuscate_url_pw(u):
- u = url.make_url(u)
- if u.password:
- u.password = 'XXXXX'
- return str(u)
-
-
def asbool(value):
return value is not None and \
value.lower() == 'true'
-def warn(msg):
- warnings.warn(msg)
-
-
-def msg(msg, newline=True):
- if TERMWIDTH is None:
- write_outstream(sys.stdout, msg)
- if newline:
- write_outstream(sys.stdout, "\n")
- else:
- # left indent output lines
- lines = textwrap.wrap(msg, TERMWIDTH)
- if len(lines) > 1:
- for line in lines[0:-1]:
- write_outstream(sys.stdout, " ", line, "\n")
- write_outstream(sys.stdout, " ", lines[-1], ("\n" if newline else ""))
-
-
-def load_python_file(dir_, filename):
- """Load a file from the given path as a Python module."""
-
- module_id = re.sub(r'\W', "_", filename)
- path = os.path.join(dir_, filename)
- _, ext = os.path.splitext(filename)
- if ext == ".py":
- if os.path.exists(path):
- module = load_module_py(module_id, path)
- elif os.path.exists(simple_pyc_file_from_path(path)):
- # look for sourceless load
- module = load_module_pyc(
- module_id, simple_pyc_file_from_path(path))
- else:
- raise ImportError("Can't find Python file %s" % path)
- elif ext in (".pyc", ".pyo"):
- module = load_module_pyc(module_id, path)
- del sys.modules[module_id]
- return module
-
-
-def simple_pyc_file_from_path(path):
- """Given a python source path, return the so-called
- "sourceless" .pyc or .pyo path.
-
- This just a .pyc or .pyo file where the .py file would be.
-
- Even with PEP-3147, which normally puts .pyc/.pyo files in __pycache__,
- this use case remains supported as a so-called "sourceless module import".
-
- """
- if sys.flags.optimize:
- return path + "o" # e.g. .pyo
- else:
- return path + "c" # e.g. .pyc
-
-
-def pyc_file_from_path(path):
- """Given a python source path, locate the .pyc.
-
- See http://www.python.org/dev/peps/pep-3147/
- #detecting-pep-3147-availability
- http://www.python.org/dev/peps/pep-3147/#file-extension-checks
-
- """
- import imp
- has3147 = hasattr(imp, 'get_tag')
- if has3147:
- return imp.cache_from_source(path)
- else:
- return simple_pyc_file_from_path(path)
-
-
def rev_id():
val = int(uuid.uuid4()) % 100000000000000
return hex(val)[2:-1]
@@ -302,17 +114,6 @@ def to_tuple(x, default=None):
raise ValueError("Don't know how to turn %r into a tuple" % x)
-def format_as_comma(value):
- if value is None:
- return ""
- elif isinstance(value, string_types):
- return value
- elif isinstance(value, collections.Iterable):
- return ", ".join(value)
- else:
- raise ValueError("Don't know how to comma-format %r" % value)
-
-
class memoized_property(object):
"""A read-only @property that is only evaluated once."""
diff --git a/alembic/util/messaging.py b/alembic/util/messaging.py
new file mode 100644
index 0000000..c202e96
--- /dev/null
+++ b/alembic/util/messaging.py
@@ -0,0 +1,94 @@
+from .compat import py27, binary_type, string_types
+import sys
+from sqlalchemy.engine import url
+import warnings
+import textwrap
+import collections
+import logging
+
+log = logging.getLogger(__name__)
+
+if py27:
+ # disable "no handler found" errors
+ logging.getLogger('alembic').addHandler(logging.NullHandler())
+
+
+try:
+ import fcntl
+ import termios
+ import struct
+ ioctl = fcntl.ioctl(0, termios.TIOCGWINSZ,
+ struct.pack('HHHH', 0, 0, 0, 0))
+ _h, TERMWIDTH, _hp, _wp = struct.unpack('HHHH', ioctl)
+ if TERMWIDTH <= 0: # can occur if running in emacs pseudo-tty
+ TERMWIDTH = None
+except (ImportError, IOError):
+ TERMWIDTH = None
+
+
+def write_outstream(stream, *text):
+ encoding = getattr(stream, 'encoding', 'ascii') or 'ascii'
+ for t in text:
+ if not isinstance(t, binary_type):
+ t = t.encode(encoding, 'replace')
+ t = t.decode(encoding)
+ try:
+ stream.write(t)
+ except IOError:
+ # suppress "broken pipe" errors.
+ # no known way to handle this on Python 3 however
+ # as the exception is "ignored" (noisily) in TextIOWrapper.
+ break
+
+
+def status(_statmsg, fn, *arg, **kw):
+ msg(_statmsg + " ...", False)
+ try:
+ ret = fn(*arg, **kw)
+ write_outstream(sys.stdout, " done\n")
+ return ret
+ except:
+ write_outstream(sys.stdout, " FAILED\n")
+ raise
+
+
+def err(message):
+ log.error(message)
+ msg("FAILED: %s" % message)
+ sys.exit(-1)
+
+
+def obfuscate_url_pw(u):
+ u = url.make_url(u)
+ if u.password:
+ u.password = 'XXXXX'
+ return str(u)
+
+
+def warn(msg):
+ warnings.warn(msg)
+
+
+def msg(msg, newline=True):
+ if TERMWIDTH is None:
+ write_outstream(sys.stdout, msg)
+ if newline:
+ write_outstream(sys.stdout, "\n")
+ else:
+ # left indent output lines
+ lines = textwrap.wrap(msg, TERMWIDTH)
+ if len(lines) > 1:
+ for line in lines[0:-1]:
+ write_outstream(sys.stdout, " ", line, "\n")
+ write_outstream(sys.stdout, " ", lines[-1], ("\n" if newline else ""))
+
+
+def format_as_comma(value):
+ if value is None:
+ return ""
+ elif isinstance(value, string_types):
+ return value
+ elif isinstance(value, collections.Iterable):
+ return ", ".join(value)
+ else:
+ raise ValueError("Don't know how to comma-format %r" % value)
diff --git a/alembic/util/pyfiles.py b/alembic/util/pyfiles.py
new file mode 100644
index 0000000..c51e187
--- /dev/null
+++ b/alembic/util/pyfiles.py
@@ -0,0 +1,80 @@
+import sys
+import os
+import re
+from .compat import load_module_py, load_module_pyc
+from mako.template import Template
+
+
+def template_to_file(template_file, dest, output_encoding, **kw):
+ with open(dest, 'wb') as f:
+ template = Template(filename=template_file)
+ f.write(
+ template.render_unicode(**kw).encode(output_encoding)
+ )
+
+
+def coerce_resource_to_filename(fname):
+ """Interpret a filename as either a filesystem location or as a package
+ resource.
+
+ Names that are non absolute paths and contain a colon
+ are interpreted as resources and coerced to a file location.
+
+ """
+ if not os.path.isabs(fname) and ":" in fname:
+ import pkg_resources
+ fname = pkg_resources.resource_filename(*fname.split(':'))
+ return fname
+
+
+def simple_pyc_file_from_path(path):
+ """Given a python source path, return the so-called
+ "sourceless" .pyc or .pyo path.
+
+ This just a .pyc or .pyo file where the .py file would be.
+
+ Even with PEP-3147, which normally puts .pyc/.pyo files in __pycache__,
+ this use case remains supported as a so-called "sourceless module import".
+
+ """
+ if sys.flags.optimize:
+ return path + "o" # e.g. .pyo
+ else:
+ return path + "c" # e.g. .pyc
+
+
+def pyc_file_from_path(path):
+ """Given a python source path, locate the .pyc.
+
+ See http://www.python.org/dev/peps/pep-3147/
+ #detecting-pep-3147-availability
+ http://www.python.org/dev/peps/pep-3147/#file-extension-checks
+
+ """
+ import imp
+ has3147 = hasattr(imp, 'get_tag')
+ if has3147:
+ return imp.cache_from_source(path)
+ else:
+ return simple_pyc_file_from_path(path)
+
+
+def load_python_file(dir_, filename):
+ """Load a file from the given path as a Python module."""
+
+ module_id = re.sub(r'\W', "_", filename)
+ path = os.path.join(dir_, filename)
+ _, ext = os.path.splitext(filename)
+ if ext == ".py":
+ if os.path.exists(path):
+ module = load_module_py(module_id, path)
+ elif os.path.exists(simple_pyc_file_from_path(path)):
+ # look for sourceless load
+ module = load_module_pyc(
+ module_id, simple_pyc_file_from_path(path))
+ else:
+ raise ImportError("Can't find Python file %s" % path)
+ elif ext in (".pyc", ".pyo"):
+ module = load_module_pyc(module_id, path)
+ del sys.modules[module_id]
+ return module
diff --git a/alembic/util/sqla_compat.py b/alembic/util/sqla_compat.py
new file mode 100644
index 0000000..41badd4
--- /dev/null
+++ b/alembic/util/sqla_compat.py
@@ -0,0 +1,24 @@
+import re
+from sqlalchemy import __version__
+
+
+def _safe_int(value):
+ try:
+ return int(value)
+ except:
+ return value
+_vers = tuple(
+ [_safe_int(x) for x in re.findall(r'(\d+|[abc]\d)', __version__)])
+sqla_07 = _vers > (0, 7, 2)
+sqla_079 = _vers >= (0, 7, 9)
+sqla_08 = _vers >= (0, 8, 0)
+sqla_083 = _vers >= (0, 8, 3)
+sqla_084 = _vers >= (0, 8, 4)
+sqla_09 = _vers >= (0, 9, 0)
+sqla_092 = _vers >= (0, 9, 2)
+sqla_094 = _vers >= (0, 9, 4)
+sqla_094 = _vers >= (0, 9, 4)
+sqla_099 = _vers >= (0, 9, 9)
+sqla_100 = _vers >= (1, 0, 0)
+sqla_105 = _vers >= (1, 0, 5)
+
diff --git a/docs/build/api.rst b/docs/build/api.rst
index fea4e14..a22d333 100644
--- a/docs/build/api.rst
+++ b/docs/build/api.rst
@@ -56,13 +56,13 @@ current :class:`.EnvironmentContext` in use.
In particular, the key method used within ``env.py`` is :meth:`.EnvironmentContext.configure`,
which establishes all the details about how the database will be accessed.
-.. automodule:: alembic.environment
+.. automodule:: alembic.runtime.environment
:members:
The Migration Context
=====================
-.. automodule:: alembic.migration
+.. automodule:: alembic.runtime.migration
:members:
The Operations Object
@@ -151,7 +151,7 @@ Revision
The :class:`.RevisionMap` object serves as the basis for revision
management, used exclusively by :class:`.ScriptDirectory`.
-.. automodule:: alembic.revision
+.. automodule:: alembic.script.revision
:members:
Autogeneration
diff --git a/docs/build/ops.rst b/docs/build/ops.rst
index 1df9d27..b4838aa 100644
--- a/docs/build/ops.rst
+++ b/docs/build/ops.rst
@@ -7,8 +7,8 @@ Operation Reference
This file provides documentation on Alembic migration directives.
The directives here are used within user-defined migration files,
-within the ``upgrade()`` and ``downgrade()`` functions, as well as
-any functions further invoked by those.
+within the ``upgrade()`` and ``downgrade()`` functions, as well as
+any functions further invoked by those.
All directives exist as methods on a class called :class:`.Operations`.
When migration scripts are run, this object is made available
@@ -19,11 +19,11 @@ with individual proxies for each method on :class:`.Operations`,
so symbols can be imported safely from the ``alembic.op`` namespace.
A key design philosophy to the :mod:`alembic.operations` methods is that
-to the greatest degree possible, they internally generate the
+to the greatest degree possible, they internally generate the
appropriate SQLAlchemy metadata, typically involving
:class:`~sqlalchemy.schema.Table` and :class:`~sqlalchemy.schema.Constraint`
-objects. This so that migration instructions can be
-given in terms of just the string names and/or flags involved.
+objects. This so that migration instructions can be
+given in terms of just the string names and/or flags involved.
The exceptions to this
rule include the :meth:`~.Operations.add_column` and :meth:`~.Operations.create_table`
directives, which require full :class:`~sqlalchemy.schema.Column`
diff --git a/tests/test_autogen_render.py b/tests/test_autogen_render.py
index 52f3601..32975f0 100644
--- a/tests/test_autogen_render.py
+++ b/tests/test_autogen_render.py
@@ -16,7 +16,8 @@ from sqlalchemy.sql import and_, column, literal_column, false
from alembic.testing.mock import patch
-from alembic import autogenerate, util, compat
+from alembic import autogenerate, util
+from alembic.util import compat
from alembic.testing import eq_, eq_ignore_whitespace, config
from alembic.testing.fixtures import op_fixture
diff --git a/tests/test_batch.py b/tests/test_batch.py
index a498c36..c827ac4 100644
--- a/tests/test_batch.py
+++ b/tests/test_batch.py
@@ -1,15 +1,13 @@
from contextlib import contextmanager
import re
-import io
-
from alembic.testing import exclusions
from alembic.testing import TestBase, eq_, config
from alembic.testing.fixtures import op_fixture
from alembic.testing import mock
from alembic.operations import Operations
-from alembic.batch import ApplyBatchImpl
-from alembic.migration import MigrationContext
+from alembic.operations.batch import ApplyBatchImpl
+from alembic.runtime.migration import MigrationContext
from sqlalchemy import Integer, Table, Column, String, MetaData, ForeignKey, \
@@ -503,7 +501,7 @@ class BatchAPITest(TestBase):
batch = op.batch_alter_table(
'tname', recreate='never', schema=schema).__enter__()
- with mock.patch("alembic.operations.sa_schema") as mock_schema:
+ with mock.patch("alembic.operations.base.sa_schema") as mock_schema:
yield batch
batch.impl.flush()
self.mock_schema = mock_schema
diff --git a/tests/test_config.py b/tests/test_config.py
index db37456..da0b413 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,9 +1,8 @@
#!coding: utf-8
-import os
-import tempfile
-from alembic import config, util, compat
+from alembic import config, util
+from alembic.util import compat
from alembic.migration import MigrationContext
from alembic.operations import Operations
from alembic.script import ScriptDirectory
diff --git a/tests/test_op.py b/tests/test_op.py
index 7d5f83e..5be9aa8 100644
--- a/tests/test_op.py
+++ b/tests/test_op.py
@@ -524,7 +524,8 @@ class OpTest(TestBase):
def test_add_foreign_key_dialect_kw(self):
op_fixture()
with mock.patch(
- "alembic.operations.sa_schema.ForeignKeyConstraint") as fkc:
+ "alembic.operations.base.sa_schema.ForeignKeyConstraint"
+ ) as fkc:
op.create_foreign_key('fk_test', 't1', 't2',
['foo', 'bar'], ['bat', 'hoho'],
foobar_arg='xyz')
diff --git a/tests/test_revision.py b/tests/test_revision.py
index d73316d..0a515de 100644
--- a/tests/test_revision.py
+++ b/tests/test_revision.py
@@ -1,6 +1,6 @@
from alembic.testing.fixtures import TestBase
from alembic.testing import eq_, assert_raises_message
-from alembic.revision import RevisionMap, Revision, MultipleHeads, \
+from alembic.script.revision import RevisionMap, Revision, MultipleHeads, \
RevisionError
diff --git a/tests/test_script_consumption.py b/tests/test_script_consumption.py
index 11b8080..c2eef0a 100644
--- a/tests/test_script_consumption.py
+++ b/tests/test_script_consumption.py
@@ -3,7 +3,8 @@
import os
import re
-from alembic import command, util, compat
+from alembic import command, util
+from alembic.util import compat
from alembic.script import ScriptDirectory, Script
from alembic.testing.env import clear_staging_env, staging_env, \
_sqlite_testing_config, write_script, _sqlite_file_db, \