From 1c68c69c533756d42999f1e3a1a028b51cf2ee8f Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Tue, 16 Jun 2020 12:27:23 +0300 Subject: [PATCH 01/11] tests: shorten test_timeout to 3s instead of 10s --- tests/test_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_local.py b/tests/test_local.py index fa2891e8d..897831004 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -478,7 +478,7 @@ def test_run(self): def test_timeout(self): from plumbum.cmd import sleep with pytest.raises(ProcessTimedOut): - sleep(10, timeout = 5) + sleep(3, timeout=1) @skip_on_windows def test_pipe_stderr(self, capfd): From 6b3470d42874d5a7da637728fcd1912421322a4b Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Tue, 16 Jun 2020 01:08:59 +0300 Subject: [PATCH 02/11] command: fix handling of env-vars passed to plumbum Commands; support 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() --- plumbum/commands/base.py | 30 ++++++++++++++++++---------- plumbum/machines/local.py | 13 ++++++++---- plumbum/machines/paramiko_machine.py | 3 +++ plumbum/machines/ssh_machine.py | 15 ++++++++++---- tests/test_local.py | 3 +++ tests/test_remote.py | 3 +++ 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/plumbum/commands/base.py b/plumbum/commands/base.py index 528c8ccc3..6e8eba87c 100644 --- a/plumbum/commands/base.py +++ b/plumbum/commands/base.py @@ -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 @@ -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 = 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() @@ -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): diff --git a/plumbum/machines/local.py b/plumbum/machines/local.py index 9b6e3f999..73c666ab8 100644 --- a/plumbum/machines/local.py +++ b/plumbum/machines/local.py @@ -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) diff --git a/plumbum/machines/paramiko_machine.py b/plumbum/machines/paramiko_machine.py index 2d8555efa..d9870d180 100644 --- a/plumbum/machines/paramiko_machine.py +++ b/plumbum/machines/paramiko_machine.py @@ -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") diff --git a/plumbum/machines/ssh_machine.py b/plumbum/machines/ssh_machine.py index de524dae9..c0165c7da 100644 --- a/plumbum/machines/ssh_machine.py +++ b/plumbum/machines/ssh_machine.py @@ -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()) diff --git a/tests/test_local.py b/tests/test_local.py index 897831004..35145708a 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -858,6 +858,9 @@ 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" + assert local.cmd.pwd['-L'].with_env(A='X').with_cwd("/")() == "/\n" + def test_nesting_lists_as_argv(self): from plumbum.cmd import ls c = ls["-l", ["-a", "*.py"]] diff --git a/tests/test_remote.py b/tests/test_remote.py index 9335cb83c..5f1e321cd 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -375,6 +375,9 @@ 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" + assert rem.cmd.pwd['-L'].with_env(A='X').with_cwd("/")() == "/\n" + @pytest.mark.skipif('useradd' not in local, reason = "System does not have useradd (Mac?)") def test_sshpass(self): From b600191982e60bd44bc67daead6d7d607b222610 Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Thu, 10 Sep 2020 14:18:57 +0300 Subject: [PATCH 03/11] ssh: better error reporting on SshSession errors --- plumbum/commands/processes.py | 12 ++++++++++-- plumbum/machines/session.py | 29 ++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 98d861726..18f7c55b6 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -127,8 +127,9 @@ class ProcessExecutionError(EnvironmentError): well as the command line used to create the process (``argv``) """ - def __init__(self, argv, retcode, stdout, stderr): + def __init__(self, argv, retcode, stdout, stderr, message=None): Exception.__init__(self, argv, retcode, stdout, stderr) + self.message = message self.argv = argv self.retcode = retcode if six.PY3 and isinstance(stdout, six.bytes): @@ -139,11 +140,18 @@ def __init__(self, argv, retcode, stdout, stderr): self.stderr = stderr def __str__(self): + # avoid an import cycle from plumbum.commands.base import shquote_list stdout = "\n | ".join(str(self.stdout).splitlines()) stderr = "\n | ".join(str(self.stderr).splitlines()) cmd = " ".join(shquote_list(self.argv)) - lines = ["Unexpected exit code: ", str(self.retcode)] + lines = [] + if self.message: + lines = [ + self.message, + "\nReturn code: | ", str(self.retcode)] + else: + lines = ["Unexpected exit code: ", str(self.retcode)] cmd = "\n | ".join(cmd.splitlines()) lines += ["\nCommand line: | ", cmd] if stdout: diff --git a/plumbum/machines/session.py b/plumbum/machines/session.py index f892fe0a8..9d7328e3b 100644 --- a/plumbum/machines/session.py +++ b/plumbum/machines/session.py @@ -3,6 +3,7 @@ import logging import threading from plumbum.commands import BaseCommand, run_proc +from plumbum.commands.processes import ProcessExecutionError from plumbum.lib import six from plumbum.machines.base import PopenAddons @@ -13,7 +14,7 @@ class ShellSessionError(Exception): pass -class SSHCommsError(EOFError): +class SSHCommsError(ProcessExecutionError, EOFError): """Raises when the communication channel can't be created on the remote host or it times out.""" @@ -124,16 +125,30 @@ def communicate(self, input=None): self.proc.poll() returncode = self.proc.returncode + stdout = six.b("").join(stdout).decode(self.custom_encoding, "ignore") + stderr = six.b("").join(stderr).decode(self.custom_encoding, "ignore") + argv = self.argv.decode(self.custom_encoding, "ignore").split(";")[:1] + if returncode == 5: raise IncorrectLogin( - "Incorrect username or password provided") + argv, returncode, stdout, stderr, + message="Incorrect username or password provided") elif returncode == 6: raise HostPublicKeyUnknown( - "The authenticity of the host can't be established") - msg = "No communication channel detected. Does the remote exist?" - msgerr = "No stderr result detected. Does the remote have Bash as the default shell?" - raise SSHCommsChannel2Error( - msgerr) if name == "2" else SSHCommsError(msg) + argv, returncode, stdout, stderr, + message="The authenticity of the host can't be established") + elif returncode != 0: + raise SSHCommsError( + argv, returncode, stdout, stderr, + message="SSH communication failed") + elif name == "2": + raise SSHCommsChannel2Error( + argv, returncode, stdout, stderr, + message="No stderr result detected. Does the remote have Bash as the default shell?") + else: + raise SSHCommsError( + argv, returncode, stdout, stderr, + message="No communication channel detected. Does the remote exist?") if not line: del sources[i] else: From 0399033baddacbd10ae78580b53bc3185ae17a56 Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Thu, 10 Sep 2020 12:02:47 +0300 Subject: [PATCH 04/11] machines: use a cache to speed-up lookups commands/programs this is particularly important for remote machines, where this lookup is very costly --- plumbum/machines/base.py | 9 +++++++++ plumbum/machines/local.py | 10 ++++++++++ plumbum/machines/remote.py | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/plumbum/machines/base.py b/plumbum/machines/base.py index 51673ecbf..214b0349f 100644 --- a/plumbum/machines/base.py +++ b/plumbum/machines/base.py @@ -96,3 +96,12 @@ def __getattr__(self, name): @property def cmd(self): return self.Cmd(self) + + def clear_program_cache(self): + """ + Clear the program cache, which is populated via ``machine.which(progname)`` calls. + + This cache speeds up the lookup of a program in the machines PATH, and is particularly + effective for RemoteMachines. + """ + self._program_cache.clear() diff --git a/plumbum/machines/local.py b/plumbum/machines/local.py index 9b6e3f999..082256b1c 100644 --- a/plumbum/machines/local.py +++ b/plumbum/machines/local.py @@ -153,6 +153,7 @@ class LocalMachine(BaseMachine): custom_encoding = sys.getfilesystemencoding() uname = platform.uname()[0] + _program_cache = {} def __init__(self): self._as_user_stack = [] @@ -192,6 +193,14 @@ def which(cls, progname): :returns: A :class:`LocalPath ` """ + + key = (progname, cls.env.get("PATH", "")) + + try: + return cls._program_cache[key] + except KeyError: + pass + alternatives = [progname] if "_" in progname: alternatives.append(progname.replace("_", "-")) @@ -199,6 +208,7 @@ def which(cls, progname): for pn in alternatives: path = cls._which(pn) if path: + cls._program_cache[key] = path return path raise CommandNotFound(progname, list(cls.env.path)) diff --git a/plumbum/machines/remote.py b/plumbum/machines/remote.py index c237c66da..191d3fdb6 100644 --- a/plumbum/machines/remote.py +++ b/plumbum/machines/remote.py @@ -173,6 +173,7 @@ def __init__(self, encoding="utf8", connect_timeout=10, new_session=False): self.uname = self._get_uname() self.env = RemoteEnv(self) self._python = None + self._program_cache = {} def _get_uname(self): rc, out, _ = self._session.run("uname", retcode=None) @@ -225,6 +226,13 @@ def which(self, progname): :returns: A :class:`RemotePath ` """ + key = (progname, self.env.get("PATH", "")) + + try: + return self._program_cache[key] + except KeyError: + pass + alternatives = [progname] if "_" in progname: alternatives.append(progname.replace("_", "-")) @@ -233,6 +241,7 @@ def which(self, progname): for p in self.env.path: fn = p / name if fn.access("x") and not fn.is_dir(): + self._program_cache[key] = fn return fn raise CommandNotFound(progname, self.env.path) From 6c143a17369dde97f7c3fb5e34b0898f6489663e Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Thu, 10 Dec 2020 10:41:25 +0200 Subject: [PATCH 05/11] make iter_lines deal with decoding errors during iteration; Also... remove broken support for no-decoding --- plumbum/commands/processes.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 18f7c55b6..85072ea1a 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -354,11 +354,8 @@ def iter_lines(proc, assert mode in (BY_POSITION, BY_TYPE) - encoding = getattr(proc, "custom_encoding", None) - if encoding: - decode = lambda s: s.decode(encoding).rstrip() - else: - decode = lambda s: s + encoding = getattr(proc, "custom_encoding", None) or 'utf-8' + decode = lambda s: s.decode(encoding, errors='replace').rstrip() _register_proc_timeout(proc, timeout) From ab61cec331d1e3fb155c04353bc7f763b3c92b33 Mon Sep 17 00:00:00 2001 From: ErezH Date: Sun, 10 Jan 2021 21:40:17 +0200 Subject: [PATCH 06/11] Add 'host' to ssh exceptions --- plumbum/commands/processes.py | 5 ++++- plumbum/machines/session.py | 23 +++++++++++++++-------- plumbum/machines/ssh_machine.py | 4 +++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 85072ea1a..233c344ba 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -127,9 +127,10 @@ class ProcessExecutionError(EnvironmentError): well as the command line used to create the process (``argv``) """ - def __init__(self, argv, retcode, stdout, stderr, message=None): + def __init__(self, argv, retcode, stdout, stderr, message=None, host=None): Exception.__init__(self, argv, retcode, stdout, stderr) self.message = message + self.host = host self.argv = argv self.retcode = retcode if six.PY3 and isinstance(stdout, six.bytes): @@ -154,6 +155,8 @@ def __str__(self): lines = ["Unexpected exit code: ", str(self.retcode)] cmd = "\n | ".join(cmd.splitlines()) lines += ["\nCommand line: | ", cmd] + if self.host: + lines += ["\nHost: | ", self.host] if stdout: lines += ["\nStdout: | ", stdout] if stderr: diff --git a/plumbum/machines/session.py b/plumbum/machines/session.py index 4fe8191fd..3514dd9b3 100644 --- a/plumbum/machines/session.py +++ b/plumbum/machines/session.py @@ -73,7 +73,8 @@ class SessionPopen(PopenAddons): """A shell-session-based ``Popen``-like object (has the following attributes: ``stdin``, ``stdout``, ``stderr``, ``returncode``)""" - def __init__(self, proc, argv, isatty, stdin, stdout, stderr, encoding): + def __init__(self, host, proc, argv, isatty, stdin, stdout, stderr, encoding): + self.host = host self.proc = proc self.argv = argv self.isatty = isatty @@ -132,23 +133,28 @@ def communicate(self, input=None): if returncode == 5: raise IncorrectLogin( argv, returncode, stdout, stderr, - message="Incorrect username or password provided") + message="Incorrect username or password provided", + host=self.host) elif returncode == 6: raise HostPublicKeyUnknown( argv, returncode, stdout, stderr, - message="The authenticity of the host can't be established") + message="The authenticity of the host can't be established", + host=self.host) elif returncode != 0: raise SSHCommsError( argv, returncode, stdout, stderr, - message="SSH communication failed") + message="SSH communication failed", + host=self.host) elif name == "2": raise SSHCommsChannel2Error( argv, returncode, stdout, stderr, - message="No stderr result detected. Does the remote have Bash as the default shell?") + message="No stderr result detected. Does the remote have Bash as the default shell?", + host=self.host) else: raise SSHCommsError( argv, returncode, stdout, stderr, - message="No communication channel detected. Does the remote exist?") + message="No communication channel detected. Does the remote exist?", + host=self.host) if not line: del sources[i] else: @@ -186,7 +192,8 @@ class ShellSession(object): is seen, the shell process is killed """ - def __init__(self, proc, encoding="auto", isatty=False, connect_timeout=5): + def __init__(self, proc, encoding="auto", isatty=False, connect_timeout=5, host=None): + self.host = host self.proc = proc self.custom_encoding = proc.custom_encoding if encoding == "auto" else encoding self.isatty = isatty @@ -276,7 +283,7 @@ def popen(self, cmd): self.proc.stdin.write(full_cmd + six.b("\n")) self.proc.stdin.flush() self._current = SessionPopen( - self.proc, full_cmd, self.isatty, self.proc.stdin, + self.host, self.proc, full_cmd, self.isatty, self.proc.stdin, MarkedPipe(self.proc.stdout, marker), MarkedPipe(self.proc.stderr, marker), self.custom_encoding) return self._current diff --git a/plumbum/machines/ssh_machine.py b/plumbum/machines/ssh_machine.py index c0165c7da..d7c2bf427 100644 --- a/plumbum/machines/ssh_machine.py +++ b/plumbum/machines/ssh_machine.py @@ -102,6 +102,7 @@ def __init__(self, scp_args = [] ssh_args = [] + self.host = host if user: self._fqhost = "%s@%s" % (user, host) else: @@ -208,7 +209,8 @@ def session(self, isatty=False, new_session=False): self.popen( ["/bin/sh"], (["-tt"] if isatty else ["-T"]), new_session=new_session), self.custom_encoding, isatty, - self.connect_timeout) + self.connect_timeout, + host=self.host) def tunnel(self, lport, From 55750edd8fc4b2f80544b9fd59c6dfaffb7244d8 Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Fri, 28 Jan 2022 23:33:14 +0200 Subject: [PATCH 07/11] .gitignore: add .eggs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 56ade7f24..7335ba0d2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ tests/.cache/* *.pot /*venv* *.mypy_cache +.eggs From 6f0be6c0825dba5b5b51d2e6d033f438a523ee42 Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Fri, 28 Jan 2022 23:54:26 +0200 Subject: [PATCH 08/11] iter_lines: added new 'buffer_size' parameter, and updated docstrings --- plumbum/commands/processes.py | 24 ++++++++++++++++++++---- tests/test_local.py | 10 ++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 068b7a7d2..c7348db7b 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -1,7 +1,6 @@ import atexit import heapq import time -from io import StringIO from queue import Empty as QueueEmpty from queue import Queue from threading import Thread @@ -304,6 +303,7 @@ def run_proc(proc, retcode, timeout=None): BY_POSITION = object() BY_TYPE = object() DEFAULT_ITER_LINES_MODE = BY_POSITION +DEFAULT_BUFFER_SIZE = _INFINITE = float("inf") def iter_lines( @@ -312,6 +312,7 @@ def iter_lines( timeout=None, linesize=-1, line_timeout=None, + buffer_size=None, mode=None, _iter_lines=_iter_lines, ): @@ -335,11 +336,22 @@ def iter_lines( Raise an :class:`ProcessLineTimedOut ` if the timeout has been reached. ``None`` means no timeout is imposed. + :param buffer_size: Maximum number of lines to keep in the stdout/stderr buffers, in case of a ProcessExecutionError. + Default is ``None``, which defaults to DEFAULT_BUFFER_SIZE (which is infinite by default). + ``0`` will disable bufferring completely. + + :param mode: Controls what the generator yields. Defaults to DEFAULT_ITER_LINES_MODE (which is BY_POSITION by default) + - BY_POSITION (default): yields ``(out, err)`` line tuples, where either item may be ``None`` + - BY_TYPE: yields ``(fd, line)`` tuples, where ``fd`` is 1 (stdout) or 2 (stderr) + :returns: An iterator of (out, err) line tuples. """ if mode is None: mode = DEFAULT_ITER_LINES_MODE + if buffer_size is None: + buffer_size = DEFAULT_BUFFER_SIZE + assert mode in (BY_POSITION, BY_TYPE) encoding = getattr(proc, "custom_encoding", None) or "utf-8" @@ -347,13 +359,17 @@ def iter_lines( _register_proc_timeout(proc, timeout) - buffers = [StringIO(), StringIO()] + buffers = [[], []] for t, line in _iter_lines(proc, decode, linesize, line_timeout): # verify that the proc hasn't timed out yet proc.verify(timeout=timeout, retcode=None, stdout=None, stderr=None) - buffers[t].write(line + "\n") + buffer = buffers[t] + if buffer_size > 0: + buffer.append(line) + if buffer_size < _INFINITE: + del buffer[:-buffer_size] if mode is BY_POSITION: ret = [None, None] @@ -363,4 +379,4 @@ def iter_lines( yield (t + 1), line # 1=stdout, 2=stderr # this will take care of checking return code and timeouts - _check_process(proc, retcode, timeout, *(s.getvalue() for s in buffers)) + _check_process(proc, retcode, timeout, *("\n".join(s) + "\n" for s in buffers)) diff --git a/tests/test_local.py b/tests/test_local.py index 43962eadf..27c9439aa 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -551,6 +551,16 @@ def test_iter_lines_timeout(self): print(i, "out:", out) assert i in (2, 3) # Mac is a bit flakey + @skip_on_windows + def test_iter_lines_buffer_size(self): + from plumbum.cmd import bash + + cmd = bash["-ce", "for ((i=0;i<100;i++)); do echo $i; done; false"] + with pytest.raises(ProcessExecutionError) as e: + for _ in cmd.popen().iter_lines(timeout=1, buffer_size=5): + pass + assert e.value.stdout == "\n".join(map(str, range(95, 100))) + "\n" + @skip_on_windows def test_iter_lines_timeout_by_type(self): from plumbum.cmd import bash From a811a436d5ced6d3077e41d7b55a953c4cb3e02b Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Sat, 29 Jan 2022 12:24:15 +0200 Subject: [PATCH 09/11] iter_lines: pylint formatting fix --- plumbum/commands/processes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 36033a160..ed35b16ce 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -354,6 +354,7 @@ def iter_lines( if buffer_size is None: buffer_size = DEFAULT_BUFFER_SIZE + buffer_size: int assert mode in (BY_POSITION, BY_TYPE) From e5e4f131e447840e703c63895acbb5f7550143f1 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 3 Feb 2022 11:50:09 -0500 Subject: [PATCH 10/11] Update plumbum/commands/processes.py --- plumbum/commands/processes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 372aa902b..b60d69a92 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -113,7 +113,7 @@ class ProcessExecutionError(EnvironmentError): well as the command line used to create the process (``argv``) """ - def __init__(self, argv, retcode, stdout, stderr, message=None, host=None): + def __init__(self, argv, retcode, stdout, stderr, message=None, *, host=None): super().__init__(argv, retcode, stdout, stderr) self.message = message self.host = host From 685a89b7c85cc2c2ef1601f5e843645b75b2422c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 3 Feb 2022 11:50:52 -0500 Subject: [PATCH 11/11] Update base.py --- plumbum/commands/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plumbum/commands/base.py b/plumbum/commands/base.py index d7293f746..c15c3ba7f 100644 --- a/plumbum/commands/base.py +++ b/plumbum/commands/base.py @@ -335,6 +335,8 @@ def popen(self, args=(), **kwargs): class BoundEnvCommand(BaseCommand): + __slots__ = ("cmd",) + def __init__(self, cmd, env=None, cwd=None): self.cmd = cmd self.env = env or {}