summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--alembic/__init__.py2
-rw-r--r--alembic/compat.py21
-rw-r--r--alembic/script.py22
-rw-r--r--alembic/util.py16
-rw-r--r--docs/build/changelog.rst19
-rw-r--r--tests/__init__.py13
-rw-r--r--tests/test_versioning.py12
8 files changed, 89 insertions, 17 deletions
diff --git a/.gitignore b/.gitignore
index f9e5e52..c495449 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.pyc
+*.pyo
build/
dist/
docs/build/output/
diff --git a/alembic/__init__.py b/alembic/__init__.py
index d73b16b..13031b0 100644
--- a/alembic/__init__.py
+++ b/alembic/__init__.py
@@ -1,6 +1,6 @@
from os import path
-__version__ = '0.6.2'
+__version__ = '0.6.3'
package_dir = path.abspath(path.dirname(__file__))
diff --git a/alembic/compat.py b/alembic/compat.py
index 0e6162a..aac0560 100644
--- a/alembic/compat.py
+++ b/alembic/compat.py
@@ -45,21 +45,28 @@ if py2k:
if py33:
from importlib import machinery
- def load_module(module_id, path):
- return machinery.SourceFileLoader(module_id, path).load_module()
+ def load_module_py(module_id, path):
+ return machinery.SourceFileLoader(module_id, path).load_module(module_id)
+
+ def load_module_pyc(module_id, path):
+ return machinery.SourcelessFileLoader(module_id, path).load_module(module_id)
+
else:
import imp
- def load_module(module_id, path):
- fp = open(path, 'rb')
- try:
+ def load_module_py(module_id, path):
+ with open(path, 'rb') as fp:
mod = imp.load_source(module_id, path, fp)
if py2k:
source_encoding = parse_encoding(fp)
if source_encoding:
mod._alembic_source_encoding = source_encoding
return mod
- finally:
- fp.close()
+
+ def load_module_pyc(module_id, path):
+ with open(path, 'rb') as fp:
+ mod = imp.load_compiled(module_id, path, fp)
+ # no source encoding here
+ return mod
try:
exec_ = getattr(compat_builtins, 'exec')
diff --git a/alembic/script.py b/alembic/script.py
index 34fd150..e816d2b 100644
--- a/alembic/script.py
+++ b/alembic/script.py
@@ -4,7 +4,7 @@ import re
import shutil
from . import util
-_rev_file = re.compile(r'.*\.py$')
+_rev_file = re.compile(r'(.*\.py)(c|o)?$')
_legacy_rev = re.compile(r'([a-f0-9]+)\.py$')
_mod_def_re = re.compile(r'(upgrade|downgrade)_([a-z0-9]+)')
_slug_re = re.compile(r'\w+')
@@ -463,9 +463,27 @@ class Script(object):
@classmethod
def _from_filename(cls, dir_, filename):
- if not _rev_file.match(filename):
+ py_match = _rev_file.match(filename)
+
+ if not py_match:
return None
+
+ py_filename = py_match.group(1)
+ is_c = py_match.group(2) == 'c'
+ is_o = py_match.group(2) == 'o'
+
+ if is_o or is_c:
+ py_exists = os.path.exists(os.path.join(dir_, py_filename))
+ pyc_exists = os.path.exists(os.path.join(dir_, py_filename + "c"))
+
+ # prefer .py over .pyc because we'd like to get the
+ # source encoding; prefer .pyc over .pyo because we'd like to
+ # have the docstrings which a -OO file would not have
+ if py_exists or is_o and pyc_exists:
+ return None
+
module = util.load_python_file(dir_, filename)
+
if not hasattr(module, "revision"):
# attempt to get the revision id from the script name,
# this for legacy only
diff --git a/alembic/util.py b/alembic/util.py
index 6a5f8df..93b6b76 100644
--- a/alembic/util.py
+++ b/alembic/util.py
@@ -10,7 +10,7 @@ from mako.template import Template
from sqlalchemy.engine import url
from sqlalchemy import __version__
-from .compat import callable, exec_, load_module, binary_type
+from .compat import callable, exec_, load_module_py, load_module_pyc, binary_type
class CommandError(Exception):
pass
@@ -196,10 +196,20 @@ def load_python_file(dir_, filename):
module_id = re.sub(r'\W', "_", filename)
path = os.path.join(dir_, filename)
- module = load_module(module_id, path)
+ _, ext = os.path.splitext(filename)
+ if ext == ".py":
+ module = load_module_py(module_id, 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):
+ 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.
@@ -213,7 +223,7 @@ def pyc_file_from_path(path):
if has3147:
return imp.cache_from_source(path)
else:
- return path + "c"
+ return simple_pyc_file_from_path(path)
def rev_id():
val = int(uuid.uuid4()) % 100000000000000
diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst
index 937d6a0..a216737 100644
--- a/docs/build/changelog.rst
+++ b/docs/build/changelog.rst
@@ -2,6 +2,25 @@
==========
Changelog
==========
+.. changelog::
+ :version: 0.6.3
+
+ .. change::
+ :tags: feature
+ :tickets: 163
+
+ The :class:`.ScriptDirectory` system that loads migration files
+ from a ``versions/`` directory now supports so-called
+ "sourceless" operation, where the ``.py`` files are not present
+ and instead ``.pyc`` or ``.pyo`` files are directly present where
+ the ``.py`` files should be. Note that while Python 3.3 has a
+ new system of locating ``.pyc``/``.pyo`` files within a directory
+ called ``__pycache__`` (e.g. PEP-3147), PEP-3147 maintains
+ support for the "source-less imports" use case, where the
+ ``.pyc``/``.pyo`` are in present in the "old" location, e.g. next
+ to the ``.py`` file; this is the usage that's supported even when
+ running Python3.3.
+
.. changelog::
:version: 0.6.2
diff --git a/tests/__init__.py b/tests/__init__.py
index 904ee76..ad5b033 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -318,7 +318,7 @@ def clear_staging_env():
shutil.rmtree(staging_directory, True)
-def write_script(scriptdir, rev_id, content, encoding='ascii'):
+def write_script(scriptdir, rev_id, content, encoding='ascii', sourceless=False):
old = scriptdir._revision_map[rev_id]
path = old.path
@@ -338,6 +338,17 @@ def write_script(scriptdir, rev_id, content, encoding='ascii'):
scriptdir._revision_map[script.revision] = script
script.nextrev = old.nextrev
+ if sourceless:
+ # note that if -O is set, you'd see pyo files here,
+ # the pyc util function looks at sys.flags.optimize to handle this
+ assert os.access(pyc_path, os.F_OK)
+ # look for a non-pep3147 path here.
+ # if not present, need to copy from __pycache__
+ simple_pyc_path = util.simple_pyc_file_from_path(path)
+ if not os.access(simple_pyc_path, os.F_OK):
+ shutil.copyfile(pyc_path, simple_pyc_path)
+ os.unlink(path)
+
def three_rev_fixture(cfg):
a = util.rev_id()
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index 91163c9..a4be95f 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -8,6 +8,8 @@ from . import clear_staging_env, staging_env, \
assert_raises_message
class VersioningTest(unittest.TestCase):
+ sourceless = False
+
def test_001_revisions(self):
global a, b, c
a = util.rev_id()
@@ -28,7 +30,7 @@ class VersioningTest(unittest.TestCase):
def downgrade():
op.execute("DROP TABLE foo")
- """ % a)
+ """ % a, sourceless=self.sourceless)
script.generate_revision(b, None, refresh=True)
write_script(script, b, """
@@ -43,7 +45,7 @@ class VersioningTest(unittest.TestCase):
def downgrade():
op.execute("DROP TABLE bar")
- """ % (b, a))
+ """ % (b, a), sourceless=self.sourceless)
script.generate_revision(c, None, refresh=True)
write_script(script, c, """
@@ -58,7 +60,7 @@ class VersioningTest(unittest.TestCase):
def downgrade():
op.execute("DROP TABLE bat")
- """ % (c, b))
+ """ % (c, b), sourceless=self.sourceless)
def test_002_upgrade(self):
@@ -182,3 +184,7 @@ class VersionNameTemplateTest(unittest.TestCase):
""")
+
+class SourcelessVersioningTest(VersioningTest):
+ sourceless = True
+