]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- The :class:`.ScriptDirectory` system that loads migration files
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 31 Dec 2013 20:01:50 +0000 (15:01 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 31 Dec 2013 20:01:50 +0000 (15:01 -0500)
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.  #163

.gitignore
alembic/__init__.py
alembic/compat.py
alembic/script.py
alembic/util.py
docs/build/changelog.rst
tests/__init__.py
tests/test_versioning.py

index f9e5e522820797c3d80f417d76734f760a246d4c..c495449fae63478184b68565c778d21e880dd375 100644 (file)
@@ -1,4 +1,5 @@
 *.pyc
+*.pyo
 build/
 dist/
 docs/build/output/
index d73b16b5dd10df8e94bb59e6da0e6cb5c65913a4..13031b0a0a3c4b0bba1a0ff25b2e1f9a01aeea3d 100644 (file)
@@ -1,6 +1,6 @@
 from os import path
 
-__version__ = '0.6.2'
+__version__ = '0.6.3'
 
 package_dir = path.abspath(path.dirname(__file__))
 
index 0e6162a118961328626527fe7d77c27f32a4051b..aac056013d2f141fe691ffc0c88f68016691bf8e 100644 (file)
@@ -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')
index 34fd1500c48944eddebcc9ebe83a1d72740b5243..e816d2b593b505c0a17850aa57ec5d0cf35e8d67 100644 (file)
@@ -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
index 6a5f8df51e00daff7c27617cf78f2705ecbaccd6..93b6b764d11db16c27fb0b94b946114e2cb91248 100644 (file)
@@ -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
index 937d6a027bf1b60ff25be6fcd7b364b2be94057d..a216737d79f7537848cbfd460481facbb32ee090 100644 (file)
@@ -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
index 904ee76957b9e63756fc857df963dde61b5dfd4b..ad5b03335be8c1e45ad496b0cd0080f51ded3052 100644 (file)
@@ -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()
index 91163c9d0f303cc23bb3ee5232e743052c6368df..a4be95f25c0de50ad9f74545b9cc2e446b8c1974 100644 (file)
@@ -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
+