summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichel Albert <michel@albert.lu>2015-07-15 07:39:24 +0200
committerMichel Albert <michel@albert.lu>2015-07-15 17:55:40 +0200
commit7b91b325ff43a0e9235e0f15b57391fa92352626 (patch)
tree551f8b38a2cfddcebb99303c2798128b627f19b2
parentc1947910621ad283219c2730d9224644e51a816c (diff)
downloadalembic-7b91b325ff43a0e9235e0f15b57391fa92352626.tar.gz
New CLI command: "edit" - Edits the latest rev.
Running ``alembic edit`` will open the latest revision in a text-editor.
-rw-r--r--alembic/command.py13
-rw-r--r--alembic/config.py7
-rw-r--r--alembic/util/__init__.py2
-rw-r--r--alembic/util/os_helpers.py49
-rw-r--r--tests/test_command.py48
-rw-r--r--tests/test_os_helpers.py44
6 files changed, 163 insertions, 0 deletions
diff --git a/alembic/command.py b/alembic/command.py
index 3ce5131..644a169 100644
--- a/alembic/command.py
+++ b/alembic/command.py
@@ -353,3 +353,16 @@ def stamp(config, revision, sql=False, tag=None):
tag=tag
):
script.run_env()
+
+
+def edit(config):
+ """Edit the latest ervision"""
+
+ script = ScriptDirectory.from_config(config)
+ revisions = script.walk_revisions()
+ head = next(revisions)
+
+ try:
+ util.open_in_editor(head.path)
+ except OSError as exc:
+ raise util.CommandError('Error executing editor (%s)' % (exc,))
diff --git a/alembic/config.py b/alembic/config.py
index b3fc36f..3d11916 100644
--- a/alembic/config.py
+++ b/alembic/config.py
@@ -351,6 +351,13 @@ class CommandLine(object):
action="store",
help="Specify a revision range; "
"format is [start]:[end]")
+ ),
+ 'edit': (
+ "--edit",
+ dict(
+ action="store_true",
+ help="Edit the latest revision"
+ )
)
}
positional_help = {
diff --git a/alembic/util/__init__.py b/alembic/util/__init__.py
index bd7196c..0fe9f3c 100644
--- a/alembic/util/__init__.py
+++ b/alembic/util/__init__.py
@@ -9,6 +9,8 @@ from .pyfiles import ( # noqa
from .sqla_compat import ( # noqa
sqla_07, sqla_079, sqla_08, sqla_083, sqla_084, sqla_09, sqla_092,
sqla_094, sqla_094, sqla_099, sqla_100, sqla_105)
+from .os_helpers import ( # noqa
+ open_in_editor)
class CommandError(Exception):
diff --git a/alembic/util/os_helpers.py b/alembic/util/os_helpers.py
new file mode 100644
index 0000000..ccbfa11
--- /dev/null
+++ b/alembic/util/os_helpers.py
@@ -0,0 +1,49 @@
+from os.path import join, exists
+from subprocess import check_call
+import os
+
+
+def open_in_editor(filename, environ=None):
+ '''
+ Opens the given file in a text editor. If the environment vaiable ``EDITOR``
+ is set, this is taken as preference.
+
+ Otherwise, a list of commonly installed editors is tried.
+
+ If no editor matches, an :py:exc:`OSError` is raised.
+
+ :param filename: The filename to open. Will be passed verbatim to the
+ editor command.
+ :param environ: An optional drop-in replacement for ``os.environ``. Used
+ mainly for testing.
+ '''
+
+ environ = environ or os.environ
+
+ # Look for an editor. Prefer the user's choice by env-var, fall back to most
+ # commonly installed editor (nano/vim)
+ candidates = [
+ '/usr/bin/sensible-editor',
+ '/usr/bin/nano',
+ '/usr/bin/vim',
+ ]
+
+ if 'EDITOR' in environ:
+ user_choice = environ['EDITOR']
+ if '/' not in user_choice:
+ # Assuming this is on the PATH, we need to determine it's absolute
+ # location. Otherwise, ``check_call`` will fail
+ for path in environ.get('PATH', '').split(os.pathsep):
+ if exists(join(path, user_choice)):
+ user_choice = join(path, user_choice)
+ break
+ candidates.insert(0, user_choice)
+
+ for path in candidates:
+ if exists(path):
+ editor = path
+ break
+ else:
+ raise OSError('No suitable editor found. Please set the '
+ '"EDITOR" environment variable')
+ check_call([editor, filename])
diff --git a/tests/test_command.py b/tests/test_command.py
index 0061023..ffb659b 100644
--- a/tests/test_command.py
+++ b/tests/test_command.py
@@ -1,4 +1,5 @@
from alembic import command
+from mock import patch
from io import TextIOWrapper, BytesIO
from alembic.script import ScriptDirectory
from alembic.testing.fixtures import TestBase, capture_context_buffer
@@ -371,3 +372,50 @@ down_revision = '%s'
self.bind.scalar("select version_num from alembic_version"),
self.a
)
+
+
+class EditTest(TestBase):
+
+ @classmethod
+ def setup_class(cls):
+ cls.env = staging_env()
+ cls.cfg = _sqlite_testing_config()
+ cls.a, cls.b, cls.c = three_rev_fixture(cls.cfg)
+
+ @classmethod
+ def teardown_class(cls):
+ clear_staging_env()
+
+ def test_edit_with_user_editor(self):
+ expected_call_arg = '%s/scripts/versions/%s_revision_c.py' % (
+ EditTest.cfg.config_args['here'],
+ EditTest.c
+ )
+
+ with patch('alembic.util.os_helpers.check_call') as check_call, \
+ patch('alembic.util.os_helpers.exists') as exists:
+ exists.side_effect = lambda fname: fname == '/usr/bin/vim'
+ command.edit(self.cfg)
+ check_call.assert_called_with(['/usr/bin/vim', expected_call_arg])
+
+ def test_edit_with_default_editor(self):
+ expected_call_arg = '%s/scripts/versions/%s_revision_c.py' % (
+ EditTest.cfg.config_args['here'],
+ EditTest.c
+ )
+
+ with patch('alembic.util.os_helpers.check_call') as check_call, \
+ patch('alembic.util.os_helpers.exists') as exists:
+ exists.side_effect = lambda fname: fname == '/usr/bin/vim'
+ command.edit(self.cfg)
+ check_call.assert_called_with(['/usr/bin/vim', expected_call_arg])
+
+ def test_edit_with_missing_editor(self):
+ with patch('alembic.util.os_helpers.check_call'), \
+ patch('alembic.util.os_helpers.exists') as exists:
+ exists.return_value = False
+ assert_raises_message(
+ util.CommandError,
+ 'EDITOR',
+ command.edit,
+ self.cfg)
diff --git a/tests/test_os_helpers.py b/tests/test_os_helpers.py
new file mode 100644
index 0000000..220e114
--- /dev/null
+++ b/tests/test_os_helpers.py
@@ -0,0 +1,44 @@
+from alembic import util
+from alembic.testing import assert_raises_message
+from alembic.testing.fixtures import TestBase
+
+try:
+ from unittest.mock import patch
+except ImportError:
+ from mock import patch # noqa
+
+
+class TestHelpers(TestBase):
+
+ def test_edit_with_user_editor(self):
+ test_environ = {
+ 'EDITOR': 'myvim',
+ 'PATH': '/usr/bin'
+ }
+
+ with patch('alembic.util.os_helpers.check_call') as check_call, \
+ patch('alembic.util.os_helpers.exists') as exists:
+ exists.side_effect = lambda fname: fname == '/usr/bin/myvim'
+ util.open_in_editor('myfile', test_environ)
+ check_call.assert_called_with(['/usr/bin/myvim', 'myfile'])
+
+ def test_edit_with_default_editor(self):
+ test_environ = {}
+
+ with patch('alembic.util.os_helpers.check_call') as check_call, \
+ patch('alembic.util.os_helpers.exists') as exists:
+ exists.side_effect = lambda fname: fname == '/usr/bin/vim'
+ util.open_in_editor('myfile', test_environ)
+ check_call.assert_called_with(['/usr/bin/vim', 'myfile'])
+
+ def test_edit_with_missing_editor(self):
+ test_environ = {}
+ with patch('alembic.util.os_helpers.check_call'), \
+ patch('alembic.util.os_helpers.exists') as exists:
+ exists.return_value = False
+ assert_raises_message(
+ OSError,
+ 'EDITOR',
+ util.open_in_editor,
+ 'myfile',
+ test_environ)