Skip to content

Commit

Permalink
command: fix handling of env-vars passed to plumbum Commands; support…
Browse files Browse the repository at this point in the history
… new with_cwd

- don't use the non-threadsafe and session-dependent .env() context manager
- sync popen support for 'env' param in all machine impls: local/ssh/paramiko
- add .with_cwd() to complement .with_env()
  • Loading branch information
koreno committed Jun 16, 2020
1 parent 1c68c69 commit 50f295f
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 18 deletions.
30 changes: 20 additions & 10 deletions plumbum/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,17 @@ def __call__(self, *args, **kwargs):
def _get_encoding(self):
raise NotImplementedError()

def with_env(self, **envvars):
def with_env(self, **env):
"""Returns a BoundEnvCommand with the given environment variables"""
if not envvars:
if not env:
return self
return BoundEnvCommand(self, envvars)
return BoundEnvCommand(self, env=env)

def with_cwd(self, path):
"""Returns a BoundEnvCommand with the specified working directory"""
if not path:
return self
return BoundEnvCommand(self, cwd=path)

setenv = with_env

Expand Down Expand Up @@ -313,14 +319,15 @@ def popen(self, args=(), **kwargs):


class BoundEnvCommand(BaseCommand):
__slots__ = ("cmd", "envvars")
__slots__ = ("cmd", "env", "cwd")

def __init__(self, cmd, envvars):
def __init__(self, cmd, env={}, cwd=None):
self.cmd = cmd
self.envvars = envvars
self.env = env
self.cwd = cmd.cwd if cwd is None else cwd

def __repr__(self):
return "BoundEnvCommand(%r, %r)" % (self.cmd, self.envvars)
return "BoundEnvCommand(%r, %r)" % (self.cmd, self.env)

def _get_encoding(self):
return self.cmd._get_encoding()
Expand All @@ -332,9 +339,12 @@ def formulate(self, level=0, args=()):
def machine(self):
return self.cmd.machine

def popen(self, args=(), **kwargs):
with self.machine.env(**self.envvars):
return self.cmd.popen(args, **kwargs)
def popen(self, args=(), cwd=None, env={}, **kwargs):
return self.cmd.popen(
args,
cwd=self.cwd if cwd is None else cwd,
env=dict(self.env, **env),
**kwargs)


class Pipeline(BaseCommand):
Expand Down
13 changes: 9 additions & 4 deletions plumbum/machines/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,15 @@ def preexec_fn(prev_fn=kwargs.get("preexec_fn", lambda: None)):

if cwd is None:
cwd = self.cwd
if env is None:
env = self.env
if isinstance(env, BaseEnv):
env = env.getdict()

envs = [self.env, env]
env = {}
for _env in envs:
if not _env:
continue
if isinstance(_env, BaseEnv):
_env = _env.getdict()
env.update(_env)

if self._as_user_stack:
argv, executable = self._as_user_stack[-1](argv)
Expand Down
3 changes: 3 additions & 0 deletions plumbum/machines/paramiko_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,13 @@ def popen(self,
stdout=None,
stderr=None,
new_session=False,
env=None,
cwd=None):
# new_session is ignored for ParamikoMachine
argv = []
envdelta = self.env.getdelta()
if env:
envdelta.update(env)
argv.extend(["cd", str(cwd or self.cwd), "&&"])
if envdelta:
argv.append("env")
Expand Down
15 changes: 11 additions & 4 deletions plumbum/machines/ssh_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,20 @@ def __str__(self):
return "ssh://%s" % (self._fqhost, )

@_setdoc(BaseRemoteMachine)
def popen(self, args, ssh_opts=(), **kwargs):
def popen(self, args, ssh_opts=(), env=None, cwd=None, **kwargs):
cmdline = []
cmdline.extend(ssh_opts)
cmdline.append(self._fqhost)
if args and hasattr(self, "env"):
envdelta = self.env.getdelta()
cmdline.extend(["cd", str(self.cwd), "&&"])
if args:
envdelta = {}
if hasattr(self, "env"):
envdelta.update(self.env.getdelta())
if env:
envdelta.update(env)
if cwd is None:
cwd = getattr(self, "cwd", None)
if cwd:
cmdline.extend(["cd", str(cwd), "&&"])
if envdelta:
cmdline.append("env")
cmdline.extend("%s=%s" % (k, shquote(v)) for k, v in envdelta.items())
Expand Down
2 changes: 2 additions & 0 deletions tests/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,8 @@ def test_bound_env(self):
assert printenv.with_env(FOO = "sea", BAR = "world")("FOO") == "sea\n"
assert printenv("FOO") == "hello\n"

assert local.cmd.pwd.with_cwd("/")() == "/\n"

def test_nesting_lists_as_argv(self):
from plumbum.cmd import ls
c = ls["-l", ["-a", "*.py"]]
Expand Down
2 changes: 2 additions & 0 deletions tests/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ def test_bound_env(self):
assert printenv.with_env(FOO = "sea", BAR = "world")("FOO") == "sea\n"
assert printenv.with_env(FOO = "sea", BAR = "world")("BAR") == "world\n"

assert rem.cmd.pwd.with_cwd("/")() == "/\n"

@pytest.mark.skipif('useradd' not in local,
reason = "System does not have useradd (Mac?)")
def test_sshpass(self):
Expand Down

0 comments on commit 50f295f

Please sign in to comment.