From 2e60b9032e12a105edaefba58059990f5d9e18df Mon Sep 17 00:00:00 2001 From: Istvan Szekeres Date: Mon, 20 Nov 2023 16:51:05 +0000 Subject: [PATCH 1/6] Fix glob Fixes #708 glob.glob has many possible arguments but the current wrapping solution doesn't cover all them. This change directly passes all args (positional and kw) as-is to the wrapped function. --- sh.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sh.py b/sh.py index 28654e5a..08903d56 100644 --- a/sh.py +++ b/sh.py @@ -457,12 +457,12 @@ def __init__(self, path, results): list.__init__(self, results) -def glob(path, recursive=False): - expanded = GlobResults(path, _old_glob(path, recursive=recursive)) +def glob(path, *args, **kwargs): + expanded = GlobResults(path, _old_glob(path, *args, **kwargs)) return expanded -glob_module.glob = glob +glob_module.glob = glob # type: ignore def canonicalize(path): From 4b980af34e446f322b464262dfe26d35c56936e0 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Thu, 4 Jan 2024 15:08:21 +0100 Subject: [PATCH 2/6] Fix example code in docs Add _return_cmd attr since normal commands now return strings. Closes #717 --- docs/source/sections/exit_codes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/sections/exit_codes.rst b/docs/source/sections/exit_codes.rst index f19dac6d..17119025 100644 --- a/docs/source/sections/exit_codes.rst +++ b/docs/source/sections/exit_codes.rst @@ -3,12 +3,12 @@ Exit Codes & Exceptions ======================= -Normal processes exit with exit code 0. This can be seen through a +Normal processes exit with exit code 0. This can be seen from :attr:`RunningCommand.exit_code`: .. code-block:: python - output = ls("/") + output = ls("/", _return_cmd=True) print(output.exit_code) # should be 0 If a process terminates, and the exit code is not 0, an exception is generated From 28a9762b09b07e8d1f038c1b97b039cae1f8efe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20K=C5=82oczko?= Date: Wed, 13 Mar 2024 13:57:54 +0000 Subject: [PATCH 3/6] drop use python<=3.7 syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Despite what is mentioned in pyproject.toml that `sh` supports only python 3.8.x `sh` code still uses older syntax. Pass all code over `pyupgrade --py38`. Signed-off-by: Tomasz Kłoczko --- sh.py | 42 +++++++++++++++++++++--------------------- tests/sh_test.py | 11 +++++------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/sh.py b/sh.py index 08903d56..d3db6462 100644 --- a/sh.py +++ b/sh.py @@ -28,7 +28,7 @@ try: from collections.abc import Mapping except ImportError: # pragma: no cover - from collections import Mapping + from collections.abc import Mapping import errno import fcntl @@ -106,7 +106,7 @@ def get_num_args(fn): return len(inspect.getfullargspec(fn).args) -_unicode_methods = set(dir(str())) +_unicode_methods = set(dir('')) HAS_POLL = hasattr(select, "poll") POLLER_EVENT_READ = 1 @@ -115,7 +115,7 @@ def get_num_args(fn): POLLER_EVENT_ERROR = 8 -class PollPoller(object): +class PollPoller: def __init__(self): self._poll = select.poll() # file descriptor <-> file object bidirectional maps @@ -191,7 +191,7 @@ def poll(self, timeout): return results -class SelectPoller(object): +class SelectPoller: def __init__(self): self.rlist = [] self.wlist = [] @@ -271,7 +271,7 @@ class ErrorReturnCodeMeta(type): """ def __subclasscheck__(self, o): - other_bases = set([b.__name__ for b in o.__bases__]) + other_bases = {b.__name__ for b in o.__bases__} return self.__name__ in other_bases or o.__name__ == self.__name__ @@ -330,7 +330,7 @@ def __init__(self, full_cmd, stdout, stderr, truncate=True): f"\n\n STDERR:\n{exc_stderr.decode(DEFAULT_ENCODING, 'replace')}" ) - super(ErrorReturnCode, self).__init__(msg) + super().__init__(msg) class SignalException(ErrorReturnCode): @@ -372,9 +372,9 @@ class CommandNotFound(AttributeError): rc_exc_regex = re.compile(r"(ErrorReturnCode|SignalException)_((\d+)|SIG[a-zA-Z]+)") rc_exc_cache: Dict[str, Type[ErrorReturnCode]] = {} -SIGNAL_MAPPING = dict( - [(v, k) for k, v in signal.__dict__.items() if re.match(r"SIG[a-zA-Z]+", k)] -) +SIGNAL_MAPPING = { + v: k for k, v in signal.__dict__.items() if re.match(r"SIG[a-zA-Z]+", k) +} def get_exc_from_name(name): @@ -536,7 +536,7 @@ def resolve_command(name, command_cls, baked_args=None): return cmd -class Logger(object): +class Logger: """provides a memory-inexpensive logger. a gotcha about python's builtin logger is that logger objects are never garbage collected. if you create a thousand loggers with unique names, they'll sit there in memory until your @@ -596,7 +596,7 @@ def default_logger_str(cmd, call_args, pid=None): return s -class RunningCommand(object): +class RunningCommand: """this represents an executing Command object. it is returned as the result of __call__() being executed on a Command instance. this creates a reference to a OProc instance, which is a low-level wrapper around the @@ -1158,7 +1158,7 @@ def env_validator(passed_kwargs, merged_kwargs): return invalid -class Command(object): +class Command: """represents an un-run system program, like "ls" or "cd". because it represents the program itself (and not a running instance of it), it should hold very little state. in fact, the only state it does hold is baked @@ -1773,7 +1773,7 @@ def no_interrupt(syscall, *args, **kwargs): return ret -class OProc(object): +class OProc: """this class is instantiated by RunningCommand for a command to be exec'd. it handles all the nasty business involved with correctly setting up the input/output to the child process. it gets its name for subprocess.Popen @@ -2088,12 +2088,12 @@ def __init__( # don't inherit file descriptors try: inherited_fds = os.listdir("/dev/fd") - except (IOError, OSError): + except OSError: # Some systems don't have /dev/fd. Raises OSError in # Python2, FileNotFoundError on Python3. The latter doesn't # exist on Python2, but inherits from IOError, which does. inherited_fds = os.listdir("/proc/self/fd") - inherited_fds = set(int(fd) for fd in inherited_fds) - pass_fds + inherited_fds = {int(fd) for fd in inherited_fds} - pass_fds for fd in inherited_fds: try: os.close(fd) @@ -2870,7 +2870,7 @@ def bufsize_type_to_bufsize(bf_type): return bufsize -class StreamWriter(object): +class StreamWriter: """StreamWriter reads from some input (the stdin param) and writes to a fd (the stream param). the stdin may be a Queue, a callable, something with the "read" method, a string, or an iterable""" @@ -3073,7 +3073,7 @@ def finish(): return process, finish -class StreamReader(object): +class StreamReader: """reads from some output (the stream) and sends what it just read to the handler.""" @@ -3160,7 +3160,7 @@ def read(self): self.write_chunk(chunk) -class StreamBufferer(object): +class StreamBufferer: """this is used for feeding in chunks of stdout/stderr, and breaking it up into chunks that will actually be put into the internal buffers. for example, if you have two processes, one being piped to the other, and you @@ -3506,7 +3506,7 @@ def process(a, kwargs): def ssh(orig): # pragma: no cover """An ssh command for automatic password login""" - class SessionContent(object): + class SessionContent: def __init__(self): self.chars = deque(maxlen=50000) self.lines = deque(maxlen=5000) @@ -3531,7 +3531,7 @@ def cur_line(self): line = "".join(self.line_chars) return line - class SSHInteract(object): + class SSHInteract: def __init__(self, prompt_match, pass_getter, out_handler, login_success): self.prompt_match = prompt_match self.pass_getter = pass_getter @@ -3626,7 +3626,7 @@ def __init__(self, self_module, baked_args=None): # but it seems to be the only way to make reload() behave # nicely. if i make these attributes dynamic lookups in # __getattr__, reload sometimes chokes in weird ways... - super(SelfWrapper, self).__init__( + super().__init__( name=getattr(self_module, "__name__", None), doc=getattr(self_module, "__doc__", None), ) diff --git a/tests/sh_test.py b/tests/sh_test.py index 196749d2..d12c3dcf 100644 --- a/tests/sh_test.py +++ b/tests/sh_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf8 -*- import asyncio import errno import fcntl @@ -446,7 +445,7 @@ def test_multiple_pipes(self): def inc(*args, **kwargs): return python("-u", inc_py.name, *args, **kwargs) - class Derp(object): + class Derp: def __init__(self): self.times = [] self.stdout = [] @@ -795,7 +794,7 @@ def test_doesnt_execute_directories(self): self.assertEqual(gcc._path, gcc_file2) self.assertEqual( gcc("no-error", _return_cmd=True).stdout.strip(), - "no-error".encode("ascii"), + b"no-error", ) finally: @@ -2308,7 +2307,7 @@ def test_callable_interact(self): """ ) - class Callable(object): + class Callable: def __init__(self): self.line = None @@ -2647,7 +2646,7 @@ def test_baked_command_can_be_printed(self): def test_done_callback(self): import time - class Callback(object): + class Callback: def __init__(self): self.called = False self.exit_code = None @@ -2792,7 +2791,7 @@ def session_false_group_true(pid, pgid, sid, test_pid): def test_done_cb_exc(self): from sh import ErrorReturnCode - class Callback(object): + class Callback: def __init__(self): self.called = False self.success = None From d667176b06d290c167ca0edd18c57e50ad7fdbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20K=C5=82oczko?= Date: Wed, 13 Mar 2024 15:58:20 +0000 Subject: [PATCH 4/6] make blak happy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomasz Kłoczko --- sh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sh.py b/sh.py index d3db6462..590dd9b3 100644 --- a/sh.py +++ b/sh.py @@ -106,7 +106,7 @@ def get_num_args(fn): return len(inspect.getfullargspec(fn).args) -_unicode_methods = set(dir('')) +_unicode_methods = set(dir("")) HAS_POLL = hasattr(select, "poll") POLLER_EVENT_READ = 1 From 39157b536fdf55a471326489b2c8b43bb5767c84 Mon Sep 17 00:00:00 2001 From: Andrew Moffat Date: Fri, 31 May 2024 16:25:48 -0700 Subject: [PATCH 5/6] changelog for 2.0.7 --- CHANGELOG.md | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 003216ce..a188e3e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.0.7 - 5/31/24 + +- Fix `sh.glob` arguments [#708](https://github.com/amoffat/sh/issues/708) +- Misc modernizations + ## 2.0.6 - 8/9/23 - Add back appropriate sdist files [comment](https://github.com/amoffat/sh/commit/89333ae48069a5b445b3535232195b2de6f4648f) diff --git a/pyproject.toml b/pyproject.toml index 127134d2..a6da69b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sh" -version = "2.0.6" +version = "2.0.7" description = "Python subprocess replacement" authors = ["Andrew Moffat "] readme = "README.rst" From a451ef83252100bc29835f77e05d17b967cd3380 Mon Sep 17 00:00:00 2001 From: Andrew Moffat Date: Fri, 31 May 2024 16:56:19 -0700 Subject: [PATCH 6/6] use pypi trusted publishing --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 103b14fd..c8778236 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -138,6 +138,10 @@ jobs: needs: test runs-on: ubuntu-latest if: github.ref_name == 'master' + + permissions: + id-token: write + steps: - uses: actions/checkout@v2 @@ -163,5 +167,3 @@ jobs: - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }}