CLI tool
pavdmyt/yaspin avatar
pavdmyt/yaspin

yaspin: a Python terminal spinner that behaves in a pipeline

A lightweight terminal spinner for Python 🎁

915 stars45 forksPythonMIT

At a glance

What is it?
yaspin is a terminal spinner for Python with one runtime dependency, termcolor, and an API that works as a context manager, a decorator or a manually driven object. Its 70 plus spinner frames come from cli-spinners, and it is careful about the case where output is piped rather than shown on a terminal.
Who is it for?
yaspin suits a Python developer who wants a progress indicator in a CLI tool without pulling in a framework, and who cares that the tool still works when its output is redirected to a file or piped to grep. It does not suit a project that needs progress reporting for a long-running service or a GUI, since it is a terminal library with one dependency and a narrow scope.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The case that breaks most spinners

yaspin provides a full-featured terminal spinner to show progress during long-running operations, and it is easy to integrate into an existing codebase as a context manager or as a function decorator.

The feature list calls out safe pipes and redirects as a first-class concern, with two examples:

bash
$ python script_that_uses_yaspin.py > script.log
$ python script_that_uses_yaspin.py | grep ERROR

That is the detail that separates a careful spinner library from a naive one. A spinner animates by rewriting the same line, using carriage returns and ANSI cursor movement. Redirect the output to a file and you get a log full of thousands of overwritten lines. Pipe it into grep and you get a file descriptor that is not a terminal, so the control sequences do nothing useful and the frames pile up. A library that checks whether its output stream is a terminal, and simply stops animating when it is not, produces a log file with a single line.

The second feature line is about POSIX signals. The README describes a user-friendly API for handling POSIX signals, and there is an examples/signals.py file in the repository, which is the usual shape of the problem: a long operation is interrupted with Ctrl+C, and the spinner has to restore the terminal so the shell prompt is not left mid-line with escape codes in it.

The audience is a developer writing a command line tool in Python who wants a progress indicator that behaves.

Three ways to use the same object

The integration surface is small and deliberately conventional.

The context manager is the primary form:

python
import time
from yaspin import yaspin

with yaspin():
    time.sleep(3)  # time consuming code

The decorator form takes a text argument and wraps a function:

python
import time
from yaspin import yaspin

@yaspin(text="Loading...")
def some_operations():
    time.sleep(3)  # time consuming code

some_operations()

And for the cases where you need to start and stop it yourself, the object is a plain handle:

python
import time
from yaspin import yaspin

spinner = yaspin()
spinner.start()

time.sleep(3)  # time consuming tasks

spinner.stop()

The context manager also carries the outcome. The basic example assigns a random success value inside the with block and then calls spinner.ok or spinner.fail with a short prefix, so the final state of the line tells the user what happened rather than leaving an unexplained spinner that simply vanished. In a script that logs to a terminal, that final marker is often the only part of the output anybody reads.

The examples directory shows the full surface, with files for advanced colours, basic usage, cli spinners, colours, custom streams, a demo, dynamic text, ellipsis, finalizers, hide and show, a prompt-toolkit integration, injecting a spinner into a function, a reversed spinner, a right side spinner, signals, spinner properties, a timer spinner and a write method. That file list is a better index of the API than the README prose is.

Seventy spinner frames and termcolor styling

The spinner catalogue is not the project's own. yaspin supports all 70 or more spinners from cli-spinners, the collection maintained by sindresorhus, and the Spinners enum exposes them by name:

python
import time
from yaspin import yaspin
from yaspin.spinners import Spinners

with yaspin(Spinners.earth, text="Earth") as sp:
    time.sleep(2)                # time consuming code

    # change spinner
    sp.spinner = Spinners.moon
    sp.text = "Moon"

    time.sleep(2)                # time consuming code

Styling comes from termcolor, and the README states that all colours, highlights, attributes and their mixes are supported. The style can be applied with keyword arguments or through a chain of attribute access, and both produce the same result:

python
import time
from yaspin import yaspin
from yaspin.spinners import Spinners

text = "Bold blink magenta spinner on cyan color"
with yaspin().bold.blink.magenta.bouncingBall.on_cyan as sp:
    sp.text = text
    time.sleep(3)

The chained form is the one that makes the library pleasant to use. A spinner configured as .bold.blink.magenta.bouncingBall.on_cyan reads as a description of the result rather than as a configuration call, and it is applied by the same object model that carries the runtime properties.

On dependency weight, the project is as lean as it is possible to be. pyproject.toml declares a single dependency, termcolor at 3.3 or later and below 4.0, and requires-python of at least 3.11 and below 4.0. The spinner catalogue is data rather than a dependency, which is why seventy frames cost nothing at install time.

The README also notes that the library is easy to combine with other command line libraries, naming prompt-toolkit as an example, and there is a matching example file for the hide and show behaviour with prompt-toolkit.

Everything is a mutable property at run time

The same property model that carries the styling also carries the state, and it is writable while the spinner is running. The spinner properties example shows the set of changes you can make on the fly:

python
import time
from yaspin import yaspin
from yaspin.spinners import Spinners

with yaspin(Spinners.noise, text="Noise spinner") as sp:
    time.sleep(2)

    sp.spinner = Spinners.arc  # spinner type
    sp.text = "Arc spinner"    # text along with spinner
    sp.color = "green"         # spinner color
    sp.side = "right"          # put spinner to the right

The side property is worth noting, because a spinner on the right of the line and a spinner on the left of the line are different visual languages. The examples directory also has right_spinner.py and reverse_spinner.py for the same reason.

When no catalogue frame fits, you can compose your own from a frame sequence and an interval value. The Spinner class takes a list of frames and an interval in milliseconds, and yaspin accepts it in place of a catalogue spinner:

python
import time
from yaspin import yaspin, Spinner

# Compose new spinners with custom frame sequence and interval value
sp = Spinner(["*", "+", "#"], 200)

with yaspin(sp, text="Cat!"):
    time.sleep(3)

That is the escape hatch for a project with its own visual identity, and it needs nothing more than a list of strings and a number.

Beyond the frame, the README's table of contents lists further capabilities that are not in the quick start: a timer, custom streams, a custom ellipsis, dynamic text, a write method for emitting messages while the spinner is active, and a hide and show mode for the case where the spinner would collide with other output.

The toolchain behind a small library

The repository is a Poetry project with a Makefile that drives everything through poetry run, and the quality gates are worth looking at because they are more than a library this size usually carries.

Lint and format are handled by ruff, with the check-lint target running ruff check with a diff so it reports rather than rewrites, and the fmt target running ruff format. Spell checking uses cspell with a .cspell.json configuration and a project dictionary, yaspin-spellcheck-dict.txt, which is the sign of a project where the documentation and the code comments are held to the same standard as the code.

There is also a .semgrepignore file and a .pylintrc, and an AGENTS.md at the top level for automated coding agents. The presence of all four in one repository suggests a deliberate policy about static analysis and about how much tooling a contributor has to satisfy.

Tests run through pytest with the -n auto flag for parallel execution, and coverage is a separate target that produces terminal, HTML and XML reports. The build target calls poetry build, and there are version bump targets using poetry version.

HISTORY.rst is the changelog, the pyproject.toml points the Changelog URL at the GitHub releases page, and the project metadata classifies it as Development Status 5, Production/Stable, with classifiers for CPython 3.11 through 3.15 and for PyPy.

On cadence, the release list shows v3.5.1 on 2026-09-18, v3.5.0 on 2026-09-06 and v3.4.0 on 2025-12-06, and the repository is not archived with the last push on 2026-10-12. The licence field on the repository says MIT, with the LICENSE file at the top level, and the pyproject.toml declares the same.

Where a spinner is the wrong tool

It is worth being clear about the limits of the category, because a spinner is the kind of dependency that gets added by reflex.

A spinner is a terminal affordance. It assumes a human is watching, that the operation has a rough duration, and that progress can be described as an indeterminate activity. That excludes a lot of work. A long batch job that processes ten thousand items has a real numerator and a real denominator, and a progress bar communicates that in a way an animated loop cannot. A service that runs for hours does not want to redraw a line every 100 milliseconds on a log aggregator that will store every frame.

The pipe behaviour, which is the feature this library is careful about, is also the limit. When the output is not a terminal, the animation has to stop. That is the right default, but it means the same code path produces a spinner for a developer and a single line for a log file, so any information you wanted the spinner to convey is lost in the second case.

There is also the dependency question. The library is small and has one dependency, so this is not a serious objection. But termcolor is pinned below 4.0, and a project with its own colour handling, or one that wants to avoid ANSI codes on Windows, may find it easier to write twenty lines of their own.

The honest framing is that yaspin is a well-built answer to a narrow question: how do I show that my CLI is still working. If that is your question, it answers it well, including the parts that other implementations get wrong.

Editorial conclusion

yaspin suits a Python developer who wants a progress indicator in a CLI tool without pulling in a framework, and who cares that the tool still works when its output is redirected to a file or piped to grep. It does not suit a project that needs progress reporting for a long-running service or a GUI, since it is a terminal library with one dependency and a narrow scope. Before adopting, install it with pip install --upgrade yaspin, check that your target Python is 3.11 or later because that is the floor in pyproject.toml, and test the case that breaks most spinners, which is running your script with the output redirected to a file.

Frequently asked questions

How do I install yaspin?

Install the released version from PyPI with pip install --upgrade yaspin, or install the latest sources from GitHub with pip install https://github.com/pavdmyt/yaspin/archive/master.zip. The project requires Python 3.11 or later.

How do I use yaspin as a context manager or decorator?

Use it as a context manager with with yaspin(): and the long running code in the block, or as a decorator with @yaspin(text="Loading...") above a function. It can also be driven manually by calling start() and stop() on the object.

Does yaspin work when the output is piped or redirected?

That is an explicit feature. The README lists safe pipes and redirects, with examples redirecting the output to a log file and piping it into grep, and the project handles the case where the output stream is not an interactive terminal.

How many spinner styles does yaspin include and where do they come from?

It supports all 70 or more spinners from the cli-spinners project, and you can also compose your own from a frame sequence and an interval value using the Spinner class. Colours, highlights and attributes come from termcolor.

Can I change a yaspin spinner while it is running?

Yes. The spinner type, text, colour and side are all writable properties on the running object, so a long operation can change its frame or its message between phases.

Official sources

  1. License: MIT
  2. pavdmyt/yaspin on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pavdmyt-yaspin.svg)](https://hysenlabs.com/projects/pavdmyt-yaspin)

Was this useful?