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+')
"""
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 "
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"):
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_:
**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].\
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))
# 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
# 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]
# 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
.. 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
# 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
``%%(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;
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>`_.
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
[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=""):
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 "
@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):
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])