Repository: pypa/pipx (python)

Issue:
Upgrading shared libraries messes up JSON output
**Describe the bug**
I have a script that parses the output of `pipx list --json` to list all installed packages. Today, it triggered the upgrading shared libraries logic. The upgrader wrote "upgrading shared libraries..." to stdout, which caused an error when attempting to parse the JSON output.

**How to reproduce**
Manually set `SHARED_LIBS_MAX_AGE_SEC = 0`. Then run `pipx list --json`.

**Expected behavior**
Stdout should contain valid JSON. Instead it contains `upgrading shared libraries...\n{ JSON output... }`


Pull request description:
Write animation fallback message to stderr
Attempt at fixing #769, although I couldn't get the test `test_delay_suppresses_output` to pass. Any suggestions would be appreciated!

Fixes #769.

<!-- add an 'x' in the brackets below -->
* [x] I have added an entry to `docs/changelog.md`

## Summary of changes

## Test plan
<!-- provide evidence of testing, preferably with command(s) that can be copy+pasted by others -->
Tested by running
```
# command(s) to exercise these changes
```


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 7450e74..b608b04 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@ dev
 
 - Include machinery to build a manpage for pipx with [argparse-manpage](https://pypi.org/project/argparse-manpage/).
 - Add better handling for 'app not found' when a single app is present in the project, and an improved error message (#733)
+- Fixed animations sending output to stdout, which can break JSON output. (#769)
 
 0.17.0
 
diff --git a/src/pipx/animate.py b/src/pipx/animate.py
index 5814ec7..e9d8bb3 100644
--- a/src/pipx/animate.py
+++ b/src/pipx/animate.py
@@ -36,7 +36,7 @@ def animate(
 
     if not do_animation or not _env_supports_animation():
         # No animation, just a single print of message
-        print(f"{message}...")
+        sys.stderr.write(f"{message}...\n")
         yield
         return
 
diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py
index 0bf1ccb..477e6da 100644
--- a/src/pipx/commands/run.py
+++ b/src/pipx/commands/run.py
@@ -163,7 +163,7 @@ def _download_and_run(
                 app_filename = app
         else:
             all_apps = (
-                f"{a} - usage: 'pipx run --spec {package_name} {app} [arguments?]'"
+                f"{a} - usage: 'pipx run --spec {package_name} {a} [arguments?]'"
                 for a in apps
             )
             raise PipxError(


Test patch:
diff --git a/tests/test_animate.py b/tests/test_animate.py
index 4c612a2..b57816e 100644
--- a/tests/test_animate.py
+++ b/tests/test_animate.py
@@ -55,6 +55,7 @@ def check_animate_output(
 
 def test_delay_suppresses_output(capsys, monkeypatch):
     monkeypatch.setattr(pipx.animate, "stderr_is_tty", True)
+    monkeypatch.setenv("COLUMNS", "80")
 
     test_string = "asdf"
 
@@ -138,5 +139,5 @@ def test_env_no_animate(capsys, monkeypatch, env_columns, stderr_is_tty):
     time.sleep(extra_after_thread_time)
     captured = capsys.readouterr()
 
-    assert captured.out == expected_string
-    assert captured.err == ""
+    assert captured.out == ""
+    assert captured.err == expected_string