Merge pull request #1619 from mrclary/subprocess-env-vars

Provide option to pass explicit environment variables to Environment and CompiledSubprocess
This commit is contained in:
Dave Halter
2020-06-22 00:11:18 +02:00
committed by GitHub
2 changed files with 31 additions and 21 deletions

View File

@@ -61,8 +61,9 @@ class Environment(_BaseEnvironment):
""" """
_subprocess = None _subprocess = None
def __init__(self, executable): def __init__(self, executable, env_vars={}):
self._start_executable = executable self._start_executable = executable
self._env_vars = env_vars
# Initialize the environment # Initialize the environment
self._get_subprocess() self._get_subprocess()
@@ -71,7 +72,8 @@ class Environment(_BaseEnvironment):
return self._subprocess return self._subprocess
try: try:
self._subprocess = CompiledSubprocess(self._start_executable) self._subprocess = CompiledSubprocess(self._start_executable,
env_vars=self._env_vars)
info = self._subprocess._send(None, _get_info) info = self._subprocess._send(None, _get_info)
except Exception as exc: except Exception as exc:
raise InvalidPythonEnvironment( raise InvalidPythonEnvironment(
@@ -134,6 +136,7 @@ class _SameEnvironmentMixin(object):
self._start_executable = self.executable = sys.executable self._start_executable = self.executable = sys.executable
self.path = sys.prefix self.path = sys.prefix
self.version_info = _VersionInfo(*sys.version_info[:3]) self.version_info = _VersionInfo(*sys.version_info[:3])
self._env_vars = {}
class SameEnvironment(_SameEnvironmentMixin, Environment): class SameEnvironment(_SameEnvironmentMixin, Environment):
@@ -321,7 +324,7 @@ def find_virtualenvs(paths=None, **kwargs):
return py27_comp(paths, **kwargs) return py27_comp(paths, **kwargs)
def find_system_environments(): def find_system_environments(**kwargs):
""" """
Ignores virtualenvs and returns the Python versions that were installed on Ignores virtualenvs and returns the Python versions that were installed on
your system. This might return nothing, if you're running Python e.g. from your system. This might return nothing, if you're running Python e.g. from
@@ -333,14 +336,14 @@ def find_system_environments():
""" """
for version_string in _SUPPORTED_PYTHONS: for version_string in _SUPPORTED_PYTHONS:
try: try:
yield get_system_environment(version_string) yield get_system_environment(version_string, **kwargs)
except InvalidPythonEnvironment: except InvalidPythonEnvironment:
pass pass
# TODO: this function should probably return a list of environments since # TODO: this function should probably return a list of environments since
# multiple Python installations can be found on a system for the same version. # multiple Python installations can be found on a system for the same version.
def get_system_environment(version): def get_system_environment(version, **kwargs):
""" """
Return the first Python environment found for a string of the form 'X.Y' Return the first Python environment found for a string of the form 'X.Y'
where X and Y are the major and minor versions of Python. where X and Y are the major and minor versions of Python.
@@ -357,24 +360,30 @@ def get_system_environment(version):
if os.name == 'nt': if os.name == 'nt':
for exe in _get_executables_from_windows_registry(version): for exe in _get_executables_from_windows_registry(version):
try: try:
return Environment(exe) return Environment(exe, **kwargs)
except InvalidPythonEnvironment: except InvalidPythonEnvironment:
pass pass
raise InvalidPythonEnvironment("Cannot find executable python%s." % version) raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
def create_environment(path, safe=True): def create_environment(path, safe=True, **kwargs):
""" """
Make it possible to manually create an Environment object by specifying a Make it possible to manually create an Environment object by specifying a
Virtualenv path or an executable path. Virtualenv path or an executable path and optional environment variables.
:raises: :exc:`.InvalidPythonEnvironment` :raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`.Environment` :returns: :class:`.Environment`
TODO: make env_vars a kwarg when Python 2 is dropped. For now, preserve API
""" """
return _create_environment(path, safe, **kwargs)
def _create_environment(path, safe=True, env_vars={}):
if os.path.isfile(path): if os.path.isfile(path):
_assert_safe(path, safe) _assert_safe(path, safe)
return Environment(path) return Environment(path, env_vars=env_vars)
return Environment(_get_executable_path(path, safe=safe)) return Environment(_get_executable_path(path, safe=safe), env_vars=env_vars)
def _get_executable_path(path, safe=True): def _get_executable_path(path, safe=True):

View File

@@ -156,11 +156,21 @@ class CompiledSubprocess(object):
# Start with 2, gets set after _get_info. # Start with 2, gets set after _get_info.
_pickle_protocol = 2 _pickle_protocol = 2
def __init__(self, executable): def __init__(self, executable, env_vars={}):
self._executable = executable self._executable = executable
self._env_vars = dict(env_vars)
self._inference_state_deletion_queue = queue.deque() self._inference_state_deletion_queue = queue.deque()
self._cleanup_callable = lambda: None self._cleanup_callable = lambda: None
# Use explicit envionment to ensure reliable results (#1540)
if os.name == 'nt':
# if SYSTEMROOT (or case variant) exists in environment,
# ensure it goes to subprocess
for k, v in os.environ.items():
if 'SYSTEMROOT' == k.upper():
self._env_vars.update({k: os.environ[k]})
break # don't risk multiple entries
def __repr__(self): def __repr__(self):
pid = os.getpid() pid = os.getpid()
return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % ( return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % (
@@ -181,15 +191,6 @@ class CompiledSubprocess(object):
os.path.dirname(os.path.dirname(parso_path)), os.path.dirname(os.path.dirname(parso_path)),
'.'.join(str(x) for x in sys.version_info[:3]), '.'.join(str(x) for x in sys.version_info[:3]),
) )
# Use explicit envionment to ensure reliable results (#1540)
env = {}
if os.name == 'nt':
# if SYSTEMROOT (or case variant) exists in environment,
# ensure it goes to subprocess
for k, v in os.environ.items():
if 'SYSTEMROOT' == k.upper():
env.update({k: os.environ[k]})
break # don't risk multiple entries
process = GeneralizedPopen( process = GeneralizedPopen(
args, args,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
@@ -198,7 +199,7 @@ class CompiledSubprocess(object):
# Use system default buffering on Python 2 to improve performance # Use system default buffering on Python 2 to improve performance
# (this is already the case on Python 3). # (this is already the case on Python 3).
bufsize=-1, bufsize=-1,
env=env env=self._env_vars
) )
self._stderr_queue = Queue() self._stderr_queue = Queue()
self._stderr_thread = t = Thread( self._stderr_thread = t = Thread(