Skip to content

Commit

Permalink
issue #291: permit supplying a full Python argv.
Browse files Browse the repository at this point in the history
  • Loading branch information
dw committed Jul 17, 2018
1 parent 09d077e commit d513ef7
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 5 deletions.
10 changes: 7 additions & 3 deletions docs/api.rst
Expand Up @@ -483,9 +483,13 @@ Router Class
determine its installation prefix. This is required to support
virtualenv.

:param str python_path:
Path to the Python interpreter to use for bootstrap. Defaults to
:data:`sys.executable`. For SSH, defaults to ``python``.
:param str|list python_path:
String or list path to the Python interpreter to use for bootstrap.
Defaults to :data:`sys.executable` for local connections, and
``python`` for remote connections.

It is possible to pass a list to invoke Python wrapped using
another tool, such as ``["/usr/bin/env", "python"]``.

:param bool debug:
If :data:`True`, arrange for debug logging (:py:meth:`enable_debug`) to
Expand Down
17 changes: 15 additions & 2 deletions mitogen/parent.py
Expand Up @@ -871,6 +871,19 @@ def _first_stage():
fp.close()
os.write(1,'MITO001\n'.encode())

def get_python_argv(self):
"""
Return the initial argument vector elements necessary to invoke Python,
by returning a 1-element list containing :attr:`python_path` if it is a
string, or simply returning it if it is already a list.
This allows emulation of existing tools where the Python invocation may
be set to e.g. `['/usr/bin/env', 'python']`.
"""
if isinstance(self.python_path, list):
return self.python_path
return [self.python_path]

def get_boot_command(self):
source = inspect.getsource(self._first_stage)
source = textwrap.dedent('\n'.join(source.strip().split('\n')[2:]))
Expand All @@ -886,8 +899,8 @@ def get_boot_command(self):
# codecs.decode() requires a bytes object. Since we must be compatible
# with 2.4 (no bytes literal), an extra .encode() either returns the
# same str (2.x) or an equivalent bytes (3.x).
return [
self.python_path, '-c',
return self.get_python_argv() + [
'-c',
'import codecs,os,sys;_=codecs.decode;'
'exec(_(_("%s".encode(),"base64"),"zip"))' % (encoded.decode(),)
]
Expand Down
39 changes: 39 additions & 0 deletions tests/local_test.py
@@ -1,5 +1,6 @@

import os
import sys

import unittest2

Expand All @@ -11,6 +12,14 @@
import plain_old_module


def get_sys_executable():
return sys.executable


def get_os_environ():
return dict(os.environ)


class LocalTest(testlib.RouterMixin, unittest2.TestCase):
stream_class = mitogen.ssh.Stream

Expand All @@ -20,5 +29,35 @@ def test_stream_name(self):
self.assertEquals('local.%d' % (pid,), context.name)


class PythonPathTest(testlib.RouterMixin, unittest2.TestCase):
stream_class = mitogen.ssh.Stream

def test_inherited(self):
context = self.router.local()
self.assertEquals(sys.executable, context.call(get_sys_executable))

def test_string(self):
os.environ['PYTHON'] = sys.executable
context = self.router.local(
python_path=testlib.data_path('env_wrapper.sh'),
)
self.assertEquals(sys.executable, context.call(get_sys_executable))
env = context.call(get_os_environ)
self.assertEquals('1', env['EXECUTED_VIA_ENV_WRAPPER'])

def test_list(self):
context = self.router.local(
python_path=[
testlib.data_path('env_wrapper.sh'),
"magic_first_arg",
sys.executable
]
)
self.assertEquals(sys.executable, context.call(get_sys_executable))
env = context.call(get_os_environ)
self.assertEquals('magic_first_arg', env['ENV_WRAPPER_FIRST_ARG'])
self.assertEquals('1', env['EXECUTED_VIA_ENV_WRAPPER'])


if __name__ == '__main__':
unittest2.main()

0 comments on commit d513ef7

Please sign in to comment.