diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-08-03 19:18:30 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-08-03 19:18:30 -0400 |
commit | e155fa69a628a89215f2ef843393d4f9f4dde758 (patch) | |
tree | b2d35bf4918b845b8877b3c2a1522707f8860c3c | |
parent | 0d38962a5e29a591e8e7c5788be05e13eef6f452 (diff) | |
download | alembic-e155fa69a628a89215f2ef843393d4f9f4dde758.tar.gz |
- Fixed bug where in the erroneous case that alembic_version contains
duplicate revisions, some commands would fail to process the
version history correctly and end up with a KeyError. The fix
allows the versioning logic to proceed, however a clear error is
emitted later when attempting to update the alembic_version table.
fixes #314
-rw-r--r-- | alembic/script/revision.py | 3 | ||||
-rw-r--r-- | alembic/util/__init__.py | 2 | ||||
-rw-r--r-- | alembic/util/langhelpers.py | 18 | ||||
-rw-r--r-- | docs/build/changelog.rst | 9 | ||||
-rw-r--r-- | tests/test_command.py | 14 | ||||
-rw-r--r-- | tests/test_revision.py | 17 |
6 files changed, 61 insertions, 2 deletions
diff --git a/alembic/script/revision.py b/alembic/script/revision.py index e618c4d..c1750a0 100644 --- a/alembic/script/revision.py +++ b/alembic/script/revision.py @@ -618,7 +618,8 @@ class RevisionMap(object): limit_to_lower_branch = \ isinstance(lower, compat.string_types) and lower.endswith('@base') - uppers = self.get_revisions(upper) + uppers = util.dedupe_tuple(self.get_revisions(upper)) + if not uppers and not requested_lowers: raise StopIteration() diff --git a/alembic/util/__init__.py b/alembic/util/__init__.py index ff08ad9..a111000 100644 --- a/alembic/util/__init__.py +++ b/alembic/util/__init__.py @@ -1,5 +1,5 @@ from .langhelpers import ( # noqa - asbool, rev_id, to_tuple, to_list, memoized_property, + asbool, rev_id, to_tuple, to_list, memoized_property, dedupe_tuple, immutabledict, _with_legacy_names, Dispatcher, ModuleClsProxy) from .messaging import ( # noqa write_outstream, status, err, obfuscate_url_pw, warn, msg, format_as_comma) diff --git a/alembic/util/langhelpers.py b/alembic/util/langhelpers.py index 6c92e3c..9445949 100644 --- a/alembic/util/langhelpers.py +++ b/alembic/util/langhelpers.py @@ -208,6 +208,24 @@ def to_tuple(x, default=None): raise ValueError("Don't know how to turn %r into a tuple" % x) +def unique_list(seq, hashfunc=None): + seen = set() + seen_add = seen.add + if not hashfunc: + return [x for x in seq + if x not in seen + and not seen_add(x)] + else: + return [x for x in seq + if hashfunc(x) not in seen + and not seen_add(hashfunc(x))] + + +def dedupe_tuple(tup): + return tuple(unique_list(tup)) + + + class memoized_property(object): """A read-only @property that is only evaluated once.""" diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index e69732e..e1a5a76 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -124,6 +124,15 @@ Changelog :ref:`alembic.autogenerate.toplevel` + .. change:: + :tags: bug, versioning + :tickets: 314 + + Fixed bug where in the erroneous case that alembic_version contains + duplicate revisions, some commands would fail to process the + version history correctly and end up with a KeyError. The fix + allows the versioning logic to proceed, however a clear error is + emitted later when attempting to update the alembic_version table. .. changelog:: :version: 0.7.7 diff --git a/tests/test_command.py b/tests/test_command.py index aa8efa4..b57e3b3 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -184,6 +184,20 @@ finally: command.revision, self.cfg, autogenerate=True ) + def test_err_correctly_raised_on_dupe_rows(self): + self._env_fixture() + command.revision(self.cfg) + r2 = command.revision(self.cfg) + db = _sqlite_file_db() + command.upgrade(self.cfg, "head") + db.execute("insert into alembic_version values ('%s')" % r2.revision) + assert_raises_message( + util.CommandError, + "Online migration expected to match one row when " + "updating .* in 'alembic_version'; 2 found", + command.downgrade, self.cfg, "-1" + ) + def test_create_rev_plain_need_to_select_head(self): self._env_fixture() command.revision(self.cfg) diff --git a/tests/test_revision.py b/tests/test_revision.py index a96aa5b..45687ea 100644 --- a/tests/test_revision.py +++ b/tests/test_revision.py @@ -94,6 +94,23 @@ class APITest(TestBase): ) eq_(map_.get_revision('base'), None) + def test_iterate_tolerates_dupe_targets(self): + map_ = RevisionMap( + lambda: [ + Revision('a', ()), + Revision('b', ('a',)), + Revision('c', ('b',)), + ] + ) + + eq_( + [ + r.revision for r in + map_._iterate_revisions(('c', 'c'), 'a') + ], + ['c', 'b', 'a'] + ) + class DownIterateTest(TestBase): def _assert_iteration( |