Skip to content

Commit be9f8ea

Browse files
authored
Merge pull request #745 from amoffat/develop
Release 2.2.0
2 parents 4c34340 + ca44695 commit be9f8ea

File tree

6 files changed

+55
-29
lines changed

6 files changed

+55
-29
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
strategy:
5656
matrix:
5757
os: [ubuntu-latest]
58-
python-version: ["3.8", "3.9", "3.10", "3.11"]
58+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
5959
use-select: [0, 1]
6060
lang: [C, en_US.UTF-8]
6161

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 2.2.0 - 1/9/25
4+
5+
- `return_cmd` with `await` now works correctly [#743](https://github.com/amoffat/sh/issues/743)
6+
- Formal support for Python 3.12
7+
38
## 2.1.0 - 10/8/24
49

510
- Add contrib command `sh.contrib.bash` [#736](https://github.com/amoffat/sh/pull/736)

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
|
2323
24-
sh is a full-fledged subprocess replacement for Python 3.8 - 3.11, and PyPy
24+
sh is a full-fledged subprocess replacement for Python 3.8 - 3.12, and PyPy
2525
that allows you to call *any* program as if it were a function:
2626

2727
.. code:: python

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[tool.poetry]
22
name = "sh"
3-
version = "2.1.0"
3+
version = "2.2.0"
44
description = "Python subprocess replacement"
55
authors = ["Andrew Moffat <arwmoffat@gmail.com>"]
66
readme = "README.rst"
77
maintainers = [
88
"Andrew Moffat <arwmoffat@gmail.com>",
9-
"Erik Cederstrand <erik@cederstrand.dk>"
9+
"Erik Cederstrand <erik@cederstrand.dk>",
1010
]
1111
homepage = "https://sh.readthedocs.io/"
1212
repository = "https://github.com/amoffat/sh"

sh.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@
2727
from collections import deque
2828
from collections.abc import Mapping
2929

30+
import platform
31+
from importlib import metadata
32+
33+
try:
34+
__version__ = metadata.version("sh")
35+
except metadata.PackageNotFoundError: # pragma: no cover
36+
__version__ = "unknown"
37+
38+
if "windows" in platform.system().lower(): # pragma: no cover
39+
raise ImportError(
40+
f"sh {__version__} is currently only supported on Linux and macOS."
41+
)
42+
3043
import errno
3144
import fcntl
3245
import gc
@@ -35,7 +48,6 @@
3548
import inspect
3649
import logging
3750
import os
38-
import platform
3951
import pty
4052
import pwd
4153
import re
@@ -55,7 +67,6 @@
5567
from asyncio import Queue as AQueue
5668
from contextlib import contextmanager
5769
from functools import partial
58-
from importlib import metadata
5970
from io import BytesIO, StringIO, UnsupportedOperation
6071
from io import open as fdopen
6172
from locale import getpreferredencoding
@@ -64,17 +75,8 @@
6475
from types import GeneratorType, ModuleType
6576
from typing import Any, Dict, Type, Union
6677

67-
try:
68-
__version__ = metadata.version("sh")
69-
except metadata.PackageNotFoundError: # pragma: no cover
70-
__version__ = "unknown"
7178
__project_url__ = "https://github.com/amoffat/sh"
7279

73-
if "windows" in platform.system().lower(): # pragma: no cover
74-
raise ImportError(
75-
f"sh {__version__} is currently only supported on Linux and macOS."
76-
)
77-
7880
TEE_STDOUT = {True, "out", 1}
7981
TEE_STDERR = {"err", 2}
8082

@@ -887,7 +889,10 @@ def __next__(self):
887889
def __await__(self):
888890
async def wait_for_completion():
889891
await self.aio_output_complete.wait()
890-
return str(self)
892+
if self.call_args["return_cmd"]:
893+
return self
894+
else:
895+
return str(self)
891896

892897
return wait_for_completion().__await__()
893898

tests/sh_test.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,7 +1707,6 @@ def test_async(self):
17071707
)
17081708

17091709
alternating = []
1710-
q = AQueue()
17111710

17121711
async def producer(q):
17131712
alternating.append(1)
@@ -1722,19 +1721,20 @@ async def consumer(q):
17221721
self.assertEqual(msg, "hello")
17231722
alternating.append(2)
17241723

1725-
loop = asyncio.get_event_loop()
1726-
fut = asyncio.gather(producer(q), consumer(q))
1727-
loop.run_until_complete(fut)
1724+
async def main():
1725+
q = AQueue()
1726+
await asyncio.gather(producer(q), consumer(q))
1727+
1728+
asyncio.run(main())
17281729
self.assertListEqual(alternating, [1, 2, 1, 2])
17291730

17301731
def test_async_exc(self):
17311732
py = create_tmp_test("""exit(34)""")
17321733

17331734
async def producer():
1734-
await python(py.name, _async=True)
1735+
await python(py.name, _async=True, _return_cmd=False)
17351736

1736-
loop = asyncio.get_event_loop()
1737-
self.assertRaises(sh.ErrorReturnCode_34, loop.run_until_complete, producer())
1737+
self.assertRaises(sh.ErrorReturnCode_34, asyncio.run, producer())
17381738

17391739
def test_async_iter(self):
17401740
py = create_tmp_test(
@@ -1743,7 +1743,6 @@ def test_async_iter(self):
17431743
print(i)
17441744
"""
17451745
)
1746-
q = AQueue()
17471746

17481747
# this list will prove that our coroutines are yielding to eachother as each
17491748
# line is produced
@@ -1763,9 +1762,11 @@ async def consumer(q):
17631762
return
17641763
alternating.append(2)
17651764

1766-
loop = asyncio.get_event_loop()
1767-
res = asyncio.gather(producer(q), consumer(q))
1768-
loop.run_until_complete(res)
1765+
async def main():
1766+
q = AQueue()
1767+
await asyncio.gather(producer(q), consumer(q))
1768+
1769+
asyncio.run(main())
17691770
self.assertListEqual(alternating, [1, 2, 1, 2, 1, 2, 1, 2, 1, 2])
17701771

17711772
def test_async_iter_exc(self):
@@ -1783,8 +1784,23 @@ async def producer():
17831784
async for line in python(py.name, _async=True):
17841785
lines.append(int(line.strip()))
17851786

1786-
loop = asyncio.get_event_loop()
1787-
self.assertRaises(sh.ErrorReturnCode_34, loop.run_until_complete, producer())
1787+
self.assertRaises(sh.ErrorReturnCode_34, asyncio.run, producer())
1788+
1789+
def test_async_return_cmd(self):
1790+
py = create_tmp_test(
1791+
"""
1792+
import sys
1793+
sys.exit(0)
1794+
"""
1795+
)
1796+
1797+
async def main():
1798+
result = await python(py.name, _async=True, _return_cmd=True)
1799+
self.assertIsInstance(result, sh.RunningCommand)
1800+
result_str = await python(py.name, _async=True, _return_cmd=False)
1801+
self.assertIsInstance(result_str, str)
1802+
1803+
asyncio.run(main())
17881804

17891805
def test_handle_both_out_and_err(self):
17901806
py = create_tmp_test(

0 commit comments

Comments
 (0)