From 95935fb0b3318214c186bf71a28fc42be5a4d687 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:09:48 +0000 Subject: [PATCH 1/7] Fix using run with file descriptors --- src/pipx/commands/run.py | 14 ++++++++------ tests/test_run.py | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index 1e036de6e6..ecc9aa589a 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -42,17 +42,19 @@ {app_lines}""" -def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str, Path]]: +def maybe_script_content(app: str, is_path: bool) -> Optional[str]: # If the app is a script, return its content. # Return None if it should be treated as a package name. + # Look for a local file first + try: + return Path(app).read_text(encoding="utf-8") + except (FileNotFoundError, IsADirectoryError): + pass - # Look for a local file first. - app_path = Path(app) - if app_path.is_file(): - return app_path - elif is_path: + if is_path: raise PipxError(f"The specified path {app} does not exist") + # Check for a URL if urllib.parse.urlparse(app).scheme: if not app.endswith(".py"): diff --git a/tests/test_run.py b/tests/test_run.py index df7abde707..53a6751957 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -354,6 +354,32 @@ def test_run_script_by_relative_name(caplog, pipx_temp_env, monkeypatch, tmp_pat run_pipx_cli_exit(["run", "test.py"]) assert out.read_text() == test_str +@mock.patch("os.execvpe", new=execvpe_mock) +@pytest.mark.skipif(sys.platform.startswith("win"), reason="uses file descriptor") +def test_run_script_by_file_descriptor(caplog, pipx_temp_env, monkeypatch, tmp_path): + read_fd, write_fd = os.pipe() + out = tmp_path / "output.txt" + test_str = "Hello, world!" + + os.write( + write_fd, + textwrap.dedent( + f""" + from pathlib import Path + Path({repr(str(out))}).write_text({repr(test_str)}) + """ + ).strip().encode("utf-8") + ) + os.close(write_fd) + + with monkeypatch.context() as m: + m.chdir(tmp_path) + try: + run_pipx_cli_exit(["run", f'/dev/fd/{read_fd}']) + finally: + os.close(read_fd) + assert out.read_text() == test_str + @pytest.mark.skipif(not sys.platform.startswith("win"), reason="uses windows version format") @mock.patch("os.execvpe", new=execvpe_mock) From 672c605510ed33de03624a1b290f2b25f0ffe77b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:18:01 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pipx/commands/run.py | 1 - tests/test_run.py | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index ecc9aa589a..84b928a71a 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -54,7 +54,6 @@ def maybe_script_content(app: str, is_path: bool) -> Optional[str]: if is_path: raise PipxError(f"The specified path {app} does not exist") - # Check for a URL if urllib.parse.urlparse(app).scheme: if not app.endswith(".py"): diff --git a/tests/test_run.py b/tests/test_run.py index 53a6751957..14b09f2e42 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -354,6 +354,7 @@ def test_run_script_by_relative_name(caplog, pipx_temp_env, monkeypatch, tmp_pat run_pipx_cli_exit(["run", "test.py"]) assert out.read_text() == test_str + @mock.patch("os.execvpe", new=execvpe_mock) @pytest.mark.skipif(sys.platform.startswith("win"), reason="uses file descriptor") def test_run_script_by_file_descriptor(caplog, pipx_temp_env, monkeypatch, tmp_path): @@ -368,14 +369,16 @@ def test_run_script_by_file_descriptor(caplog, pipx_temp_env, monkeypatch, tmp_p from pathlib import Path Path({repr(str(out))}).write_text({repr(test_str)}) """ - ).strip().encode("utf-8") + ) + .strip() + .encode("utf-8"), ) os.close(write_fd) - + with monkeypatch.context() as m: m.chdir(tmp_path) try: - run_pipx_cli_exit(["run", f'/dev/fd/{read_fd}']) + run_pipx_cli_exit(["run", f"/dev/fd/{read_fd}"]) finally: os.close(read_fd) assert out.read_text() == test_str From 3b9fd970a8e198a0df1921c9560a1253f80d2073 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:20:46 +0000 Subject: [PATCH 3/7] Add changelog entry --- changelog.d/1293.bugfix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1293.bugfix.md diff --git a/changelog.d/1293.bugfix.md b/changelog.d/1293.bugfix.md new file mode 100644 index 0000000000..f628fdf01f --- /dev/null +++ b/changelog.d/1293.bugfix.md @@ -0,0 +1 @@ +Fix run command with bash substitution (e.g. `pipx run <(pbpaste)`) From a96f233ce9fedf7897b0f9a0a90304c27346e211 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:56:03 +0000 Subject: [PATCH 4/7] Read content only in case it's a file descriptor --- src/pipx/commands/run.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index 84b928a71a..fd15638e41 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -42,18 +42,21 @@ {app_lines}""" -def maybe_script_content(app: str, is_path: bool) -> Optional[str]: +def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str, Path]]: # If the app is a script, return its content. # Return None if it should be treated as a package name. # Look for a local file first - try: - return Path(app).read_text(encoding="utf-8") - except (FileNotFoundError, IsADirectoryError): - pass + app_path = Path(app) + if app_path.is_file(): + return app_path + # In case it's a named pipe, read it out to pass to the interpreter + if app_path.is_fifo(): + return app_path.read_text(encoding="utf-8") if is_path: raise PipxError(f"The specified path {app} does not exist") + # Check for a URL if urllib.parse.urlparse(app).scheme: if not app.endswith(".py"): From d5149695c4628da5b606021ad629ccff95b46da1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:56:25 +0000 Subject: [PATCH 5/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pipx/commands/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index fd15638e41..b91407faf9 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -56,7 +56,6 @@ def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str, Path]]: if is_path: raise PipxError(f"The specified path {app} does not exist") - # Check for a URL if urllib.parse.urlparse(app).scheme: if not app.endswith(".py"): From 430d51d258273aaa85f7f5ba0401362d634ac659 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Mon, 18 Mar 2024 01:59:54 +0300 Subject: [PATCH 6/7] Remove unrelated change --- src/pipx/commands/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index b91407faf9..86126b85f2 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -45,6 +45,7 @@ def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str, Path]]: # If the app is a script, return its content. # Return None if it should be treated as a package name. + # Look for a local file first app_path = Path(app) if app_path.is_file(): From 60304d634f671f07a1d19b42b5b8aa135adc14f2 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:01:25 +0300 Subject: [PATCH 7/7] Oops... --- src/pipx/commands/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index 86126b85f2..64d568dfc8 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -46,7 +46,7 @@ def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str, Path]]: # If the app is a script, return its content. # Return None if it should be treated as a package name. - # Look for a local file first + # Look for a local file first. app_path = Path(app) if app_path.is_file(): return app_path