Skip to content

Commit

Permalink
Make it possible to run commands in Python virtual environments
Browse files Browse the repository at this point in the history
  • Loading branch information
xolox committed Oct 18, 2015
1 parent 214e34f commit 84b584c
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 9 deletions.
34 changes: 28 additions & 6 deletions executor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Programmer friendly subprocess wrapper.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: October 6, 2015
# Last Change: October 18, 2015
# URL: https://executor.readthedocs.org

"""
Expand Down Expand Up @@ -61,7 +61,7 @@
unicode = str

# Semi-standard module versioning.
__version__ = '7.0.1'
__version__ = '7.1'

# Initialize a logger.
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -192,9 +192,9 @@ class ExternalCommand(PropertyManager):
The :attr:`async`, :attr:`capture`, :attr:`capture_stderr`, :attr:`check`,
:attr:`directory`, :attr:`encoding`, :attr:`environment`,
:attr:`fakeroot`, :attr:`input`, :attr:`logger`, :attr:`merge_streams`,
:attr:`silent`, :attr:`stdout_file`, :attr:`stderr_file` and :attr:`sudo`
properties allow you to configure how the external command will be run
(before it is started).
:attr:`silent`, :attr:`stdout_file`, :attr:`stderr_file`, :attr:`sudo` and
:attr:`virtual_environment` properties allow you to configure how the
external command will be run (before it is started).
**Computed properties**
The :attr:`command`, :attr:`command_line`, :attr:`decoded_stderr`,
Expand Down Expand Up @@ -352,12 +352,22 @@ def command_line(self):
semicolons, ampersands and pipes can be used (and all the usual
caveats apply :-).
- If :attr:`virtual_environment` is set the command is converted to a
shell command line and prefixed by the applicable ``source ...``
command.
- If :attr:`fakeroot` or :attr:`sudo` is set the respective command
name may be prefixed to the command line generated here.
"""
command_line = list(self.command)
if self.virtual_environment:
# Prepare to execute the command inside a Python virtual environment.
activate_script = os.path.join(self.virtual_environment, 'bin', 'activate')
shell_command = command_line[0] if len(command_line) == 1 else quote(command_line)
command_line = ['source %s && %s' % (quote(activate_script), shell_command)]
if len(command_line) == 1:
command_line = [DEFAULT_SHELL, '-c'] + command_line
# Prepare to execute a shell command.
command_line = [DEFAULT_SHELL, '-c', command_line[0]]
if (self.fakeroot or self.sudo) and not self.have_superuser_privileges:
if self.sudo:
# Superuser privileges requested by caller.
Expand Down Expand Up @@ -714,6 +724,18 @@ def sudo(self):
"""
return False

@mutable_property
def virtual_environment(self):
"""
The Python virtual environment to activate before running the command.
If this option is set to the directory of a Python virtual environment
(a string) then the external command will be prefixed by a ``source``
command that evaluates the ``bin/activate`` script in the Python
virtual environment before executing the user defined external
command.
"""

@property
def was_started(self):
"""
Expand Down
25 changes: 24 additions & 1 deletion executor/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Automated tests for the `executor' module.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: October 6, 2015
# Last Change: October 18, 2015
# URL: https://executor.readthedocs.org

"""Automated tests for the `executor` package."""
Expand Down Expand Up @@ -198,6 +198,29 @@ def test_working_directory(self):
finally:
os.rmdir(directory)

def test_virtual_environment_option(self):
"""Make sure Python virtual environments can be used."""
directory = tempfile.mkdtemp()
virtual_environment = os.path.join(directory, 'environment')
try:
# Create a virtual environment to run the command in.
execute('virtualenv', virtual_environment)
# This is the expected value of `sys.executable'.
expected_executable = os.path.join(virtual_environment, 'bin', 'python')
# Get the actual value of `sys.executable' by running a Python
# interpreter inside the virtual environment.
actual_executable = execute('python', '-c', 'import sys; print(sys.executable)',
capture=True, virtual_environment=virtual_environment)
# Make sure the values match.
assert os.path.samefile(expected_executable, actual_executable)
# Make sure that shell commands are also supported (command line
# munging inside executor is a bit tricky and I specifically got
# this wrong on the first attempt :-).
output = execute('echo $VIRTUAL_ENV', capture=True, virtual_environment=virtual_environment)
assert os.path.samefile(virtual_environment, output)
finally:
shutil.rmtree(directory)

def test_fakeroot_option(self):
"""Make sure ``fakeroot`` can be used."""
filename = os.path.join(tempfile.gettempdir(), 'executor-%s-fakeroot-test' % os.getpid())
Expand Down
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""Setup script for the `executor` package."""

# Author: Peter Odding <peter@peterodding.com>
# Last Change: October 6, 2015
# Last Change: October 18, 2015
# URL: https://executor.readthedocs.org

# Standard library modules.
Expand Down Expand Up @@ -45,4 +45,7 @@
'humanfriendly >= 1.19',
'property-manager >= 1.2',
],
test_suite='executor.tests')
test_suite='executor.tests',
tests_require=[
'virtualenv',
])
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ envlist = py26, py27, py34, pypy
deps =
coloredlogs
pytest
virtualenv
commands = py.test {posargs}

[flake8]
Expand Down

0 comments on commit 84b584c

Please sign in to comment.