Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make anyio.run_process() accept stdin argument #859

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ Version history

This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.

**UNRELEASED**

- Added ``stdin`` argument to ``anyio.run_process()`` akin to what
``anyio.open_process()``, ``asyncio.create_subprocess_…()``, ``trio.run_process()``,
and ``subprocess.run()`` already accept.
(`#859 <https://github.com/agronholm/anyio/pull/859>`_; PR by @jmehnle)

**4.8.0**

- Added **experimental** support for running functions in subinterpreters on Python
Expand Down
5 changes: 4 additions & 1 deletion src/anyio/_core/_subprocesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ async def run_process(
command: StrOrBytesPath | Sequence[StrOrBytesPath],
*,
input: bytes | None = None,
stdin: int | IO[Any] | None = None,
stdout: int | IO[Any] | None = PIPE,
stderr: int | IO[Any] | None = PIPE,
check: bool = True,
Expand All @@ -45,6 +46,8 @@ async def run_process(
:param command: either a string to pass to the shell, or an iterable of strings
containing the executable name or path and its arguments
:param input: bytes passed to the standard input of the subprocess
:param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
a file-like object, or `None`; ``input`` overrides this
:param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
a file-like object, or `None`
:param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
Expand Down Expand Up @@ -84,7 +87,7 @@ async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None:

async with await open_process(
command,
stdin=PIPE if input else DEVNULL,
stdin=(input and PIPE) or stdin or DEVNULL,
stdout=stdout,
stderr=stderr,
cwd=cwd,
Expand Down
34 changes: 33 additions & 1 deletion tests/test_subprocesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async def test_process_new_session_sid() -> None:
assert result.stdout.decode().strip() != str(sid)


async def test_run_process_connect_to_file(tmp_path: Path) -> None:
async def test_open_process_connect_to_file(tmp_path: Path) -> None:
stdinfile = tmp_path / "stdin"
stdinfile.write_text("Hello, process!\n")
stdoutfile = tmp_path / "stdout"
Expand Down Expand Up @@ -162,6 +162,38 @@ async def test_run_process_connect_to_file(tmp_path: Path) -> None:
)


async def test_run_process_connect_to_file(tmp_path: Path) -> None:
stdinfile = tmp_path / "stdin"
stdinfile.write_text("Hello, process!\n")
stdoutfile = tmp_path / "stdout"
stderrfile = tmp_path / "stderr"
with (
stdinfile.open("rb") as fin,
stdoutfile.open("wb") as fout,
stderrfile.open("wb") as ferr,
):
await run_process(
[
sys.executable,
"-c",
"import sys; txt = sys.stdin.read().strip(); "
'print("stdin says", repr(txt), "but stderr says NO!", '
"file=sys.stderr); "
'print("stdin says", repr(txt), "and stdout says YES!")',
],
stdin=fin,
stdout=fout,
stderr=ferr,
)

assert (
stdoutfile.read_text() == "stdin says 'Hello, process!' and stdout says YES!\n"
)
assert (
stderrfile.read_text() == "stdin says 'Hello, process!' but stderr says NO!\n"
)


async def test_run_process_inherit_stdout(capfd: pytest.CaptureFixture[str]) -> None:
await run_process(
[
Expand Down
Loading