summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alembic/script.py33
-rw-r--r--alembic/templates/generic/alembic.ini.mako5
-rw-r--r--alembic/templates/multidb/alembic.ini.mako5
-rw-r--r--alembic/templates/pylons/alembic.ini.mako5
-rw-r--r--docs/build/changelog.rst8
-rw-r--r--docs/build/tutorial.rst21
-rw-r--r--tests/__init__.py7
-rw-r--r--tests/test_versioning.py35
8 files changed, 101 insertions, 18 deletions
diff --git a/alembic/script.py b/alembic/script.py
index 32adf1c..3294366 100644
--- a/alembic/script.py
+++ b/alembic/script.py
@@ -4,7 +4,8 @@ import re
import shutil
from . import util
-_rev_file = re.compile(r'(.*\.py)(c|o)?$')
+_sourceless_rev_file = re.compile(r'(.*\.py)(c|o)?$')
+_only_source_rev_file = re.compile(r'(.*\.py)$')
_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+')
@@ -31,11 +32,13 @@ class ScriptDirectory(object):
"""
def __init__(self, dir, file_template=_default_file_template,
- truncate_slug_length=40):
+ truncate_slug_length=40,
+ sourceless=False):
self.dir = dir
self.versions = os.path.join(self.dir, 'versions')
self.file_template = file_template
self.truncate_slug_length = truncate_slug_length or 40
+ self.sourceless = sourceless
if not os.access(dir, os.F_OK):
raise util.CommandError("Path doesn't exist: %r. Please use "
@@ -63,7 +66,8 @@ class ScriptDirectory(object):
file_template=config.get_main_option(
'file_template',
_default_file_template),
- truncate_slug_length=truncate_slug_length
+ truncate_slug_length=truncate_slug_length,
+ sourceless=config.get_main_option("sourceless") == "true"
)
def walk_revisions(self, base="base", head="head"):
@@ -206,7 +210,7 @@ class ScriptDirectory(object):
def _revision_map(self):
map_ = {}
for file_ in os.listdir(self.versions):
- script = Script._from_filename(self.versions, file_)
+ script = Script._from_filename(self, self.versions, file_)
if script is None:
continue
if script.revision in map_:
@@ -348,7 +352,7 @@ class ScriptDirectory(object):
**kw
)
if refresh:
- script = Script._from_path(path)
+ script = Script._from_path(self, path)
self._revision_map[script.revision] = script
if script.down_revision:
self._revision_map[script.down_revision].\
@@ -457,20 +461,27 @@ class Script(object):
self.doc)
@classmethod
- def _from_path(cls, path):
+ def _from_path(cls, scriptdir, path):
dir_, filename = os.path.split(path)
- return cls._from_filename(dir_, filename)
+ return cls._from_filename(scriptdir, dir_, filename)
@classmethod
- def _from_filename(cls, dir_, filename):
- py_match = _rev_file.match(filename)
+ def _from_filename(cls, scriptdir, dir_, filename):
+ if scriptdir.sourceless:
+ py_match = _sourceless_rev_file.match(filename)
+ else:
+ py_match = _only_source_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 scriptdir.sourceless:
+ is_c = py_match.group(2) == 'c'
+ is_o = py_match.group(2) == 'o'
+ else:
+ is_c = is_o = False
if is_o or is_c:
py_exists = os.path.exists(os.path.join(dir_, py_filename))
diff --git a/alembic/templates/generic/alembic.ini.mako b/alembic/templates/generic/alembic.ini.mako
index f7a5301..a738a24 100644
--- a/alembic/templates/generic/alembic.ini.mako
+++ b/alembic/templates/generic/alembic.ini.mako
@@ -15,6 +15,11 @@ script_location = ${script_location}
# the 'revision' command, regardless of autogenerate
# revision_environment = false
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
sqlalchemy.url = driver://user:pass@localhost/dbname
diff --git a/alembic/templates/multidb/alembic.ini.mako b/alembic/templates/multidb/alembic.ini.mako
index bd29898..132b246 100644
--- a/alembic/templates/multidb/alembic.ini.mako
+++ b/alembic/templates/multidb/alembic.ini.mako
@@ -15,6 +15,11 @@ script_location = ${script_location}
# the 'revision' command, regardless of autogenerate
# revision_environment = false
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
databases = engine1, engine2
[engine1]
diff --git a/alembic/templates/pylons/alembic.ini.mako b/alembic/templates/pylons/alembic.ini.mako
index 009b675..771c027 100644
--- a/alembic/templates/pylons/alembic.ini.mako
+++ b/alembic/templates/pylons/alembic.ini.mako
@@ -15,6 +15,11 @@ script_location = ${script_location}
# the 'revision' command, regardless of autogenerate
# revision_environment = false
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
pylons_config_file = ./development.ini
# that's it ! \ No newline at end of file
diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst
index caaa5c9..0b142df 100644
--- a/docs/build/changelog.rst
+++ b/docs/build/changelog.rst
@@ -6,6 +6,14 @@ Changelog
:version: 0.6.4
.. change::
+ :tags: feature
+ :tickets: 163
+
+ Altered the support for "sourceless" migration files (e.g. only
+ .pyc or .pyo present) so that the flag "sourceless=true" needs to
+ be in alembic.ini for this behavior to take effect.
+
+ .. change::
:tags: bug, mssql
:tickets: 185
diff --git a/docs/build/tutorial.rst b/docs/build/tutorial.rst
index 1819f2f..aade686 100644
--- a/docs/build/tutorial.rst
+++ b/docs/build/tutorial.rst
@@ -127,6 +127,11 @@ The file generated with the "generic" configuration looks like::
# the 'revision' command, regardless of autogenerate
# revision_environment = false
+ # set to 'true' to allow .pyc and .pyo files without
+ # a source .py file to be detected as revisions in the
+ # versions/ directory
+ # sourceless = false
+
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
@@ -198,12 +203,12 @@ This file contains the following features:
``%%(minute).2d``, ``%%(second).2d`` - components of the create date
as returned by ``datetime.datetime.now()``
- .. versionadded:: 0.3.6 - added date parameters to ``file_template``.
+ .. versionadded:: 0.3.6 - added date parameters to ``file_template``.
* ``truncate_slug_length`` - defaults to 40, the max number of characters
to include in the "slug" field.
- .. versionadded:: 0.6.1 - added ``truncate_slug_length`` configuration
+ .. versionadded:: 0.6.1 - added ``truncate_slug_length`` configuration
* ``sqlalchemy.url`` - A URL to connect to the database via SQLAlchemy. This key is in fact
only referenced within the ``env.py`` file that is specific to the "generic" configuration;
@@ -212,7 +217,17 @@ This file contains the following features:
of the file.
* ``revision_environment`` - this is a flag which when set to the value 'true', will indicate
that the migration environment script ``env.py`` should be run unconditionally when
- generating new revision files (new in 0.3.3).
+ generating new revision files
+
+ .. versionadded:: 0.3.3
+
+* ``sourceless`` - when set to 'true', revision files that only exist as .pyc
+ or .pyo files in the versions directory will be used as versions, allowing
+ "sourceless" versioning folders. When left at the default of 'false',
+ only .py files are consumed as version files.
+
+ .. versionadded:: 0.6.4
+
* ``[loggers]``, ``[handlers]``, ``[formatters]``, ``[logger_*]``, ``[handler_*]``,
``[formatter_*]`` - these sections are all part of Python's standard logging configuration,
the mechanics of which are documented at `Configuration File Format <http://docs.python.org/library/logging.config.html#configuration-file-format>`_.
diff --git a/tests/__init__.py b/tests/__init__.py
index 3c4633e..9bb5ef6 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -241,12 +241,13 @@ config = context.config
with open(path, 'w') as f:
f.write(txt)
-def _sqlite_testing_config():
+def _sqlite_testing_config(sourceless=False):
dir_ = os.path.join(staging_directory, 'scripts')
return _write_config_file("""
[alembic]
script_location = %s
sqlalchemy.url = sqlite:///%s/foo.db
+sourceless = %s
[loggers]
keys = root
@@ -271,7 +272,7 @@ keys = generic
[formatter_generic]
format = %%(levelname)-5.5s [%%(name)s] %%(message)s
datefmt = %%H:%%M:%%S
- """ % (dir_, dir_))
+ """ % (dir_, dir_, "true" if sourceless else "false"))
def _no_sql_testing_config(dialect="postgresql", directives=""):
@@ -362,7 +363,7 @@ def write_script(scriptdir, rev_id, content, encoding='ascii', sourceless=False)
pyc_path = util.pyc_file_from_path(path)
if os.access(pyc_path, os.F_OK):
os.unlink(pyc_path)
- script = Script._from_path(path)
+ script = Script._from_path(scriptdir, path)
old = scriptdir._revision_map[script.revision]
if old.down_revision != script.down_revision:
raise Exception("Can't change down_revision "
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index 41e4be1..68440fc 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -100,7 +100,7 @@ class VersioningTest(unittest.TestCase):
@classmethod
def setup_class(cls):
cls.env = staging_env(sourceless=cls.sourceless)
- cls.cfg = _sqlite_testing_config()
+ cls.cfg = _sqlite_testing_config(sourceless=cls.sourceless)
@classmethod
def teardown_class(cls):
@@ -188,3 +188,36 @@ class VersionNameTemplateTest(unittest.TestCase):
class SourcelessVersioningTest(VersioningTest):
sourceless = True
+class SourcelessNeedsFlagTest(unittest.TestCase):
+ def setUp(self):
+ self.env = staging_env(sourceless=False)
+ self.cfg = _sqlite_testing_config()
+
+ def tearDown(self):
+ clear_staging_env()
+
+ def test_needs_flag(self):
+ a = util.rev_id()
+
+ script = ScriptDirectory.from_config(self.cfg)
+ script.generate_revision(a, None, refresh=True)
+ write_script(script, a, """
+ revision = '%s'
+ down_revision = None
+
+ from alembic import op
+
+ def upgrade():
+ op.execute("CREATE TABLE foo(id integer)")
+
+ def downgrade():
+ op.execute("DROP TABLE foo")
+
+ """ % a, sourceless=True)
+
+ script = ScriptDirectory.from_config(self.cfg)
+ eq_(script.get_heads(), [])
+
+ self.cfg.set_main_option("sourceless", "true")
+ script = ScriptDirectory.from_config(self.cfg)
+ eq_(script.get_heads(), [a])