diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea3571c9..0202c21e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,14 +11,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - id: check-yaml -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 - hooks: - - id: pyupgrade - args: [--py38-plus] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.2.2 hooks: diff --git a/pyproject.toml b/pyproject.toml index cd6e5abb..c05e314a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,35 @@ testing = [ Homepage = "https://execnet.readthedocs.io/en/latest/" [tool.ruff.lint] -ignore = ["E741"] -extend-select = ["I001"] +extend-select = [ + "B", # bugbear + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "PYI", # flake8-pyi + "UP", # pyupgrade + "RUF", # ruff + "W", # pycodestyle + "PIE", # flake8-pie + "PGH", # pygrep-hooks + "PLE", # pylint error + "PLW", # pylint warning +] +ignore = [ + # bugbear ignore + "B007", # Loop control variable `i` not used within loop body + "B011", # Do not `assert False` (`python -O` removes these calls) + # pycodestyle ignore + "E501", # Line too long + "E741", # Ambiguous variable name + # ruff ignore + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + # pylint ignore + "PLW0603", # Using the global statement + "PLW0120", # remove the else and dedent its contents + "PLW2901", # for loop variable overwritten by assignment target + "PLR5501", # Use `elif` instead of `else` then `if` +] [tool.ruff.lint.isort] force-single-line = true diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 55ce16fa..870897ee 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -143,7 +143,7 @@ def __init__(self, kwargs): self.__dict__.update(kwargs) def __repr__(self): - info = ", ".join("%s=%s" % item for item in sorted(self.__dict__.items())) + info = ", ".join(f"{k}={v}" for k, v in sorted(self.__dict__.items())) return "" % info @@ -201,8 +201,8 @@ def _source_of_function(function): try: source = inspect.getsource(function) - except OSError: - raise ValueError("can't find source file for %s" % function) + except OSError as e: + raise ValueError("can't find source file for %s" % function) from e source = textwrap.dedent(source) # just for inner functions diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index be2bc13b..9141f7e6 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -289,7 +289,7 @@ def get(self, timeout=None): try: return self._result except AttributeError: - raise self._exc + raise self._exc from None def waitfinish(self, timeout=None): if not self._result_ready.wait(timeout): @@ -528,7 +528,7 @@ def from_io(io): if not header: raise EOFError("empty read") except EOFError as e: - raise EOFError("couldn't load message header, " + e.args[0]) + raise EOFError("couldn't load message header, " + e.args[0]) from None msgtype, channel, payload = struct.unpack("!bii", header) return Message(msgtype, channel, io.read(payload)) @@ -542,9 +542,7 @@ def received(self, gateway): def __repr__(self): name = self._types[self.msgcode][0] - return "".format( - name, self.channelid, len(self.data) - ) + return f"" def _status(message, gateway): # we use the channelid to send back information @@ -851,7 +849,7 @@ def receive(self, timeout=None): try: x = itemqueue.get(timeout=timeout) except self.gateway.execmodel.queue.Empty: - raise self.TimeoutError("no item after %r seconds" % timeout) + raise self.TimeoutError("no item after %r seconds" % timeout) from None if x is ENDMARKER: itemqueue.put(x) # for other receivers raise self._getremoteerror() or EOFError() @@ -865,7 +863,7 @@ def next(self): try: return self.receive() except EOFError: - raise StopIteration + raise StopIteration from None __next__ = next @@ -1108,7 +1106,7 @@ def _send(self, msgcode, channelid=0, data=b""): except (OSError, ValueError) as e: self._trace("failed to send", message, e) # ValueError might be because the IO is already closed - raise OSError("cannot send (already closed?)") + raise OSError("cannot send (already closed?)") from e def _local_schedulexec(self, channel, sourcetask): channel.close("execution disallowed") @@ -1316,12 +1314,12 @@ def load(self, versioned=False): loader = self.num2func[opcode] except KeyError: raise LoadError( - "unknown opcode %r - " "wire protocol corruption?" % (opcode,) - ) + f"unknown opcode {opcode!r} - wire protocol corruption?" + ) from None loader(self) except _Stop: if len(self.stack) != 1: - raise LoadError("internal unserialization error") + raise LoadError("internal unserialization error") from None return self.stack.pop(0) else: raise LoadError("didn't get STOP") @@ -1550,7 +1548,7 @@ def _save(self, obj): methodname = "save_" + tp.__name__ meth = getattr(self.__class__, methodname, None) if meth is None: - raise DumpError(f"can't serialize {tp}") + raise DumpError(f"can't serialize {tp}") from None dispatch = self._dispatch[tp] = meth dispatch(self, obj) @@ -1574,8 +1572,8 @@ def save_str(self, s): def _write_unicode_string(self, s): try: as_bytes = s.encode("utf-8") - except UnicodeEncodeError: - raise DumpError("strings must be utf-8 encodable") + except UnicodeEncodeError as e: + raise DumpError("strings must be utf-8 encodable") from e self._write_byte_sequence(as_bytes) def _write_byte_sequence(self, bytes_): @@ -1593,7 +1591,7 @@ def _save_integral(self, i, short_op, long_op): def save_int(self, i): self._save_integral(i, opcode.INT, opcode.LONGINT) - def save_long(self, l): # noqa:E741 + def save_long(self, l): self._save_integral(l, opcode.LONG, opcode.LONGLONG) def save_float(self, flt): diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index a924090e..dfa632ea 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -50,7 +50,7 @@ def bootstrap_exec(io, spec): except EOFError: ret = io.wait() if ret == 255: - raise HostNotFound(io.remoteaddress) + raise HostNotFound(io.remoteaddress) from None def bootstrap_socket(io, id): diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 1484d49d..8afa4dac 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -24,7 +24,7 @@ class RSync: def __init__(self, sourcedir, callback=None, verbose=True): self._sourcedir = str(sourcedir) self._verbose = verbose - assert callback is None or hasattr(callback, "__call__") + assert callback is None or callable(callback) self._callback = callback self._channels = {} self._receivequeue = Queue() @@ -168,7 +168,7 @@ def _send_directory(self, path): names.append(name) subpaths.append(p) mode = os.lstat(path).st_mode - self._broadcast([mode] + names) + self._broadcast([mode, *names]) for p in subpaths: self._send_directory_structure(p) diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index 3dbe2d84..758c4ca4 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -42,7 +42,7 @@ def receive_directory_structure(path, relcomponents): entrynames = {} for entryname in msg: destpath = os.path.join(path, entryname) - receive_directory_structure(destpath, relcomponents + [entryname]) + receive_directory_structure(destpath, [*relcomponents, entryname]) entrynames[entryname] = True if options.get("delete"): for othername in os.listdir(path): diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index a80b80ea..c69a57ef 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -55,7 +55,7 @@ def run(self): while 1: try: - clientfile.write("%s %s >>> " % loc) + clientfile.write("{} {} >>> ".format(*loc)) clientfile.flush() line = filein.readline() if not line: diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index 95f93289..f56f5e76 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -48,7 +48,7 @@ def print_(*args): def exec_from_one_connection(serversock): print_(progname, "Entering Accept loop", serversock.getsockname()) clientsock, address = serversock.accept() - print_(progname, "got new connection from %s %s" % address) + print_(progname, "got new connection from {} {}".format(*address)) clientfile = clientsock.makefile("rb") print_("reading line") # rstrip so that we can use \r\n for telnet testing @@ -60,7 +60,7 @@ def exec_from_one_connection(serversock): co = compile(source + "\n", "", "exec") print_(progname, "compiled source, executing") try: - exec_(co, g) # noqa + exec_(co, g) # noqa: F821 finally: print_(progname, "finished executing code") # background thread might hold a reference to this (!?) diff --git a/testing/conftest.py b/testing/conftest.py index 2ee21202..133c1db4 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -152,8 +152,8 @@ def gw(request, execmodel, group): proxygw = group.makegateway("popen//id=%s" % pname) # assert group['proxygw'].remote_status().receiving gw = group.makegateway( - "socket//id=socket//installvia=%s" - "//execmodel=%s" % (pname, execmodel.backend) + f"socket//id=socket//installvia={pname}" + f"//execmodel={execmodel.backend}" ) gw.proxygw = proxygw assert pname in group diff --git a/testing/test_basics.py b/testing/test_basics.py index b9433291..e52007b8 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -1,3 +1,4 @@ +# ruff: noqa: B018 from __future__ import annotations import inspect diff --git a/testing/test_gateway.py b/testing/test_gateway.py index ee4ce375..a2567485 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -494,7 +494,7 @@ def test_no_tracing_by_default(self): ], ) def test_popen_args(spec, expected_args): - expected_args = expected_args + ["-u", "-c", gateway_io.popen_bootstrapline] + expected_args = [*expected_args, "-u", "-c", gateway_io.popen_bootstrapline] args = gateway_io.popen_args(execnet.XSpec(spec)) assert args == expected_args diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 9b11d1b7..ca2fbf9d 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -50,9 +50,8 @@ def load(self, data: bytes): res = subprocess.run( [str(self.executable), str(script_file)], capture_output=True, + check=True, ) - if res.returncode: - raise ValueError(res.stderr) return res.stdout.decode("ascii").splitlines() diff --git a/testing/test_termination.py b/testing/test_termination.py index 6abd444e..39108717 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -66,7 +66,7 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): gw.remote_exec("channel.receive()") gw._group.terminate() command = ["ps", "-p", str(pid)] - output = subprocess.run(command, capture_output=True, text=True) + output = subprocess.run(command, capture_output=True, text=True, check=False) assert str(pid) not in output.stdout, output diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 0162e2ea..98187349 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -60,7 +60,7 @@ def first(): def test_waitfinish_on_reply(pool): - l = [] # noqa:E741 + l = [] reply = pool.spawn(lambda: l.append(1)) reply.waitfinish() assert l == [1] diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 9fccc5b8..30552dca 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -200,6 +200,7 @@ def test_vagrant(self, makegateway): capture_output=True, encoding="utf-8", errors="replace", + check=True, ).stdout print(res) if ",default,state,shutoff\n" in res: @@ -211,8 +212,8 @@ def test_vagrant(self, makegateway): gw = makegateway("vagrant_ssh=default//python=python3") rinfo = gw._rinfo() - rinfo.cwd == "/home/vagrant" - rinfo.executable == "/usr/bin/python" + assert rinfo.cwd == "/home/vagrant" + assert rinfo.executable == "/usr/bin/python" def test_socket(self, specsocket, makegateway): gw = makegateway("socket=%s//id=sock1" % specsocket.socket)