Skip to content

Switch to auto-updating spinners wholesale - #14238

Open
ichard26 wants to merge 4 commits into
pypa:mainfrom
ichard26:refactor/rich-spinners2
Open

Switch to auto-updating spinners wholesale #14238
ichard26 wants to merge 4 commits into
pypa:mainfrom
ichard26:refactor/rich-spinners2

Conversation

@ichard26

@ichard26 ichard26 commented Aug 8, 2026

Copy link
Copy Markdown
Member

These are easier to work with and are more flexible since they don't have a spin() method that needs to be called constantly. One such auto-updating spinner, _PipRichSpinner, was already added for the inprocess build dependency installer. This commit extends this concept for the rest of the codebase.

Please note the new spinner interface is a bit weird. For one, spinners can be marked as finished multiple times with only the first call taking effect. This is necessary to ensure robust error handling in case the spinner's caller doesn't actively manage the spinner itself (which is optional).

Furthermore, the final status message is printed even if the spinner was never started. This is carried over from the old spinners. The spinners are interesting since they do two jobs: showing a spinner while pip does work and displaying the final status of said work.

Finally, the spinners don't necessarily auto-start. The backend hook caller has some extra code to decide whether to spin the spinner, so providing for manual start control was needed.

The overall changes look like this:

  • Add a new non-interactive spinner that uses a background thread to schedule keep-live "still working" messages

  • Add a new no-op spinner for when no status/spinner output is desired. Previously this was achieved by relying on the non-interactive spinner's use of logger.info() which will be hidden automagically. This is clever, but an explicit no-op spinner is IMO cleaner.

  • Simplify the rich spinner a fair bit

  • Add more documentation, comments, and tests for future maintainability and test the (previously untested?) spinner selection logic.

  • ... and finally, delete the old spinners and switch to the new spinners everywhere

This migration has the extra benefit of bringing the inprocess build dependency installer's output handling closer to the subprocess installer. I'd never wired a non-interactive spinner for the inprocess installer originally, oops!

--- a	2026-08-08 18:20:53.977536914 -0400
+++ b	2026-08-08 18:21:00.906592918 -0400
@@ -1,5 +1,7 @@
 Obtaining file:///home/ichard26/dev/oss/pip
-  Installing build dependencies ... done  Checking if build backend supports build_editable: started
+  Installing build dependencies: started
+  Installing build dependencies: finished with status 'done'
+  Checking if build backend supports build_editable: started
   Checking if build backend supports build_editable: finished with status 'done'
   Getting requirements to build editable: started
   Getting requirements to build editable: finished with status 'done'
@@ -12,7 +14,7 @@
   Building editable for pip (pyproject.toml): started
   Building editable for pip (pyproject.toml): finished with status 'done'
   Created wheel for pip: filename=pip-26.3.dev0-py3-none-any.whl size=39980 sha256=9663b0ea135b304414b54d11e916f1cb0a65e144751e995308f888e5088289d0
-  Stored in directory: /tmp/pip-ephem-wheel-cache-ytpulo34/wheels/02/b2/7b/cd44cb77bc4424b9ddde1ee0abd178e67d530bdc69f13f7fde
+  Stored in directory: /tmp/pip-ephem-wheel-cache-8ozfu0o2/wheels/02/b2/7b/cd44cb77bc4424b9ddde1ee0abd178e67d530bdc69f13f7fde
 Successfully built pip
 Installing collected packages: six, pip

Towards #9081, and supersedes and closes #14026.

Tip

This is easier to review in the split diff viewer.

These are easier to work with and are more flexible since they don't
have a spin() method that needs to be called constantly. One such
auto-updating spinner, _PipRichSpinner, was already added for the
inprocess build dependency installer. This commit extends this concept
for the rest of the codebase.

Please note the new spinner interface is a bit weird. For one, spinners
can be marked as finished multiple times with only the first call taking
effect. This is necessary to ensure robust error handling in case the
spinner's caller doesn't actively manage the spinner itself (which is
optional).

Furthermore, the final status message is printed even if the spinner
was never started. This is carried over from the old spinners. The
spinners are interesting since they do two jobs: showing a spinner while
pip does work and displaying the final status of said work.

Finally, the spinners don't necessarily auto-start. The backend hook
caller has some extra code to decide whether to spin the spinner, so
providing for manual start control was needed.

The overall changes look like this:

- Add a new non-interactive spinner that uses a background thread to
  schedule keep-live "still working" messages

- Add a new no-op spinner for when no status/spinner output is desired.
  Previously this was achieved by relying on the non-interactive
  spinner's use of logger.info() which will be hidden automagically.
  This is clever, but an explicit no-op spinner is IMO cleaner.

- Simplify the rich spinner a fair bit

- Add more documentation, comments, and tests for future maintainability
  and test the (previously?) spinner selection logic.

- ... and finally, delete the old spinners and switch to the new
  spinners everywhere

This migration has the extra benefit of bringing the inprocess build
dependency installer's output handling closer to the subprocess
installer. I'd never wired a non-interactive spinner for the
inprocess installer originally, oops!
It's only used by the raw progress bars now.
Otherwise the build backend/build dep installer tests fail while
setting up their spinner.
@ichard26 ichard26 added the skip PR template check Silence the PR template check in CI label Aug 8, 2026
@ichard26

ichard26 commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

I was hoping this would result in an overall codesize reduction, but unfortunately the additional code documentation and tests made this PR much bigger. I still think this is worth it though as it simplifies the actual business logic. Exchanging logic for more tests and documentation is a good trade, IMO.

$ pip diff --stat main... -- src/
 src/pip/_internal/build_env/installer.py |   6 +--
 src/pip/_internal/cli/progress_bars.py   |  16 ++++++-
 src/pip/_internal/cli/spinners.py        | 292 +++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------
 src/pip/_internal/utils/subprocess.py    |   9 ++--
 4 files changed, 129 insertions(+), 194 deletions(-)

@sepehr-rs sepehr-rs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, seems like a nice improvement! I have left a couple of inline comments as someone less familiar with this part of the codebase:

Comment thread news/14238.bugfix.rst Outdated
assert start.called is autostart


def test_noninteractive_spinner_lifecycle(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be missing some context, but do we have a test that covers the RichSpinner lifecycle?

@ichard26 ichard26 Aug 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing the rich spinner is quite difficult since the logic is largely delegated to the rich library and its spinner implementation. The finish part of the rich spinner lifecycle is already tested separately, so I decided it wasn't worth adding more tests.

OTOH, the non-interactive spinner is entirely custom (before and after this PR) so we ought to test it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, thanks for the context! That makes sense.

@notatallshaw

Copy link
Copy Markdown
Member

Does this change the look and feel? And if so do you have screenshots or gifs?

@ichard26

Copy link
Copy Markdown
Member Author

Does this change the look and feel? And if so do you have screenshots or gifs?

No, it shouldn't. I've spent a fair bit of time ensuring the look and feel would be the same. The one behavioural change I know of is with the inprocess build dependency installer. It's a simple bugfix where it simply never had a non-interactive spinner (unlike the rest of the codebase) leading to inconsistent status reporting. This was mentioned in the PR description at the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided skip PR template check Silence the PR template check in CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants