Repository: alfredodeza/remoto

Issue:
Allow remote python interpreter path
Currently, `get_python_executable` is used to determine the path for python interpreter on remote nodes. This method runs `which` command to get `python3`, `python2` or `python` command path on the remote system. 

Different systems can have different python environment setups, especially for projects using virtualenvs. `JsonModuleExecute` can allow passing an option to run python modules using provided python interpreter path on remote nodes. 

Something like: 
```python
    def __init__(self, conn, module, logger=None, remote_python=None):
        ...
        self.python_executable = remote_python
        ...
```

Discussion:

Patch:
diff --git a/remoto/backends/__init__.py b/remoto/backends/__init__.py
index 6de9d0e..ff20b79 100644
--- a/remoto/backends/__init__.py
+++ b/remoto/backends/__init__.py
@@ -114,7 +114,7 @@ class BaseConnection(object):
     def exit(self):
         self.group.terminate(timeout=1.0)
 
-    def import_module(self, module):
+    def import_module(self, module, python_executable=None):
         """
         Allows remote execution of a local module. Depending on the
         ``remote_import_system`` attribute it may use execnet's implementation
@@ -125,7 +125,8 @@ class BaseConnection(object):
         """
         if self.remote_import_system is not None:
             if self.remote_import_system == 'json':
-                self.remote_module = JsonModuleExecute(self, module, self.logger)
+                self.remote_module = JsonModuleExecute(self, module, self.logger,
+                                                       python_executable=python_executable)
             else:
                 self.remote_module = LegacyModuleExecute(self.gateway, module, self.logger)
         else:
@@ -217,12 +218,12 @@ class JsonModuleExecute(object):
     as well and raised accordingly.
     """
 
-    def __init__(self, conn, module, logger=None):
+    def __init__(self, conn, module, logger=None, python_executable=None):
         self.conn = conn
         self.module = module
         self._module_source = inspect.getsource(module)
         self.logger = logger
-        self.python_executable = None
+        self.python_executable = python_executable
 
     def __getattr__(self, name):
         if not hasattr(self.module, name):


Test patch:
diff --git a/remoto/tests/backends/test_backends.py b/remoto/tests/backends/test_backends.py
index 7a8d138..0e83a07 100644
--- a/remoto/tests/backends/test_backends.py
+++ b/remoto/tests/backends/test_backends.py
@@ -71,6 +71,24 @@ class TestJsonModuleExecute(object):
             pass
         assert remote_fake_module.python_executable is not None
 
+    def test_python_executable(self):
+        python_executable = sys.executable
+        conn = local.LocalConnection()
+        conn.remote_import_system = 'json'
+        remote_fake_module = conn.import_module(fake_module,
+                                                python_executable=python_executable)
+        assert remote_fake_module.remote_interpreter() == python_executable
+
+    def test_wrong_python_executable(self):
+        python_executable = '/path/to/python'
+        conn = local.LocalConnection()
+        conn.remote_import_system = 'json'
+        remote_fake_module = conn.import_module(fake_module,
+                                                python_executable=python_executable)
+        with pytest.raises(Exception) as error:
+            remote_fake_module.remote_interpreter()
+        assert 'Failed to execute command: {}'.format(python_executable) in str(error.value)
+
 
 class TestNeedsSsh(object):
 
diff --git a/remoto/tests/fake_module.py b/remoto/tests/fake_module.py
index b4194fe..1803fe3 100644
--- a/remoto/tests/fake_module.py
+++ b/remoto/tests/fake_module.py
@@ -23,3 +23,7 @@ def noop():
 
 def passes():
     pass
+
+
+def remote_interpreter():
+    return sys.executable