Spinnerยถ
An indeterminate progress spinner for blocking work whose duration is unknown: a subprocess, a network call, a long query. Where click.progressbar needs a known length or an iterable to advance through, Spinner simply signals that something is happening.
It animates on a background thread, so the calling thread stays free to block on the work itself:
from time import sleep
from click_extra import Spinner
with Spinner("Brewing tea"):
sleep(5)
The spinner draws to stderr and is a no-op whenever that stream is not a terminal (a pipe, a file, a CI log), so redirected output and machine-readable formats stay clean. Reassign its label while it runs to reflect the current step, and set a delay so it only appears once an operation is genuinely slow.
Use as a decoratorยถ
A Spinner doubles as a decorator, with or without parentheses. @Spinner wraps a function directly; @Spinner("โฆ") configures the spinner first. Either way the function spins for the duration of every call and returns its result untouched.
@Spinner # Bare form: a default spinner.
def roast(batch):
sleep(5)
return batch
@Spinner("Roasting coffee", timer=True) # Configured form.
def roast_slowly(batch):
sleep(5)
return batch
The one instance is shared across calls, which is right for sequential use; give concurrent callers their own spinner.
Spin directionยถ
Pass reverse=True to rotate the other way. It works with the default frames or any custom sequence:
with Spinner("Chilling lemonade", reverse=True):
sleep(5)
A clock is the clearest thing to run backwards, since a reader already knows which way its hands are supposed to go:
This is why the catalog carries no timeTravel. cli-spinners ships one, because its renderers only play frames forwards and a backwards clock has to be a second preset to exist at all. Here it is SPINNERS["clock"] with reverse=True, and a duplicate entry would only be a second name for the same animation.
The animation source is just a sequence of strings. click_extra.spinner ships the default Braille SPINNER_FRAMES and a plain ASCII_SPINNER_FRAMES for terminals without Unicode glyphs; pass your own to frames for anything else.
Picturing a spinnerยถ
frame_lines() hands back one turn of the animation held still, one line per frame. The glyph, the label, the style and the timer land where the running spinner puts them, reverse included, so the picture cannot drift from what the terminal shows. No thread starts and no terminal is needed:
from click_extra import SPINNERS, Spinner, Style
spinner = Spinner("Brewing tea", spinner=SPINNERS["moon"], style=Style(fg="green"))
lines = spinner.frame_lines()
Colors are applied by default, whatever stream the spinner itself would have drawn on: a picture carries its own answer to whether ANSI survives. Pass color=False for the bare text. These lines are what an animated capture stacks into an SVG.
A documentation page asks for that picture with :screenshot-animate:, naming the spinner to draw. The option takes the frames and the interval straight off it, so the image below is built from the same object the paragraph above describes:
Because the frames come from a declared spinner rather than from a timed recording, the same expression composes the same lines on every build. The committed asset keeps the same bytes, and the working tree stays clean, until a new Click Extra version rewrites its @generated stamp.
Spinner catalogยถ
SPINNERS is a catalog of around 90 ready-made animations, each a SpinnerPreset bundling the frames and the interval they were tuned for. They are ported from cli-spinners, the de-facto reference collection. Pick one with spinner=:
from click_extra import Spinner, SPINNERS
with Spinner("Brewing tea", spinner=SPINNERS["moon"]):
sleep(5)
The preset sets both the frames and the interval; an explicit frames= or interval= still overrides it. Because the spinner redraws the whole line instead of backspacing, the multi-character animations (bouncing-bar, pong, shark, โฆ) render correctly here, unlike in the upstream renderers that had to drop them.
Full inventoryยถ
Every style is browsable from the CLI. On an interactive terminal click-extra spinner animates a live tour of the selection (--all for the whole catalog, --random N for a sample, or --select name1,name2 for specific ones); --table prints the reference table below instead of animating. The Frames column previews each animation, and the Tour column is the dwell time the live tour spends on each: three full cycles, clamped to two-to-three seconds:
$ click-extra --color spinner --all --table
โญโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโฎ
โ Name โ Frames โ Interval โ Tour โ
โโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโค
โ dots โ โ โ โ น โ ธ โ ผ โ ด โ ฆ โ ง โ โ โ 0.08s โ 2.4s โ
โ dots2 โ โฃพ โฃฝ โฃป โขฟ โกฟ โฃ โฃฏ โฃท โ 0.08s โ 2.0s โ
โ dots3 โ โ โ โ โ โ โ ฆ โ ด โ ฒ โ ณ โ โ 0.08s โ 2.4s โ
โ dots4 โ โ โ โ โ โ โ ธ โ ฐ โ โ ฐ โ ธ โ โ โ โ โ 0.08s โ 3.0s โ
โ dots5 โ โ โ โ โ โ โ โ โ ฒ โ ด โ ฆ โ โ โ โ โ โ โ โ 0.08s โ 3.0s โ
โ dots6 โ โ โ โ โ โ โ โ โ โ ฒ โ ด โ ค โ โ โ ค โ ด โ ฒ โ โ โ โ โ โ โ โ โ 0.08s โ 3.0s โ
โ dots7 โ โ โ โ โ โ โ โ โ โ โ ฆ โ ค โ โ โ ค โ ฆ โ โ โ โ โ โ โ โ โ โ 0.08s โ 3.0s โ
โ dots8 โ โ โ โ โ โ โ โ โ โ โ ฒ โ ด โ ค โ โ โ ค โ โ โ ค โ ฆ โ โ โ โ โ โ โ โ โ โฆ (+1) โ 0.08s โ 3.0s โ
โ dots9 โ โขน โขบ โขผ โฃธ โฃ โกง โก โก โ 0.08s โ 2.0s โ
โ dots10 โ โข โข โข โก โก โก โก โ 0.08s โ 2.0s โ
โ dots11 โ โ โ โ โก โข โ โ โ โ 0.1s โ 2.4s โ
โ dots12 โ โขโ โกโ โ โ โขโ โกโ โ
โ โขโ โกโ โ โ โขโ โกโ โ โ โขโ โกโ โ โ โ โ โ โ โ โ โ โ โฆ (+37) โ 0.08s โ 4.5s โ
โ dots13 โ โฃผ โฃน โขป โ ฟ โก โฃ โฃง โฃถ โ 0.08s โ 2.0s โ
โ dots14 โ โ โ โ โ โ โ น โ โขธ โ โฃฐ โขโฃ โฃโฃ โฃโก โฃโ โกโ โ โ โ โ โ 0.08s โ 2.9s โ
โ dots-8bit โ โ โ โ โ โ โ
โ โ โก โก โก โก โก โก
โก โก โ โ โ โ โ โ โ โ โก โก โก โก โฆ (+228) โ 0.08s โ 20.5s โ
โ dots-circle โ โข โ โ โ โ โ โ ฑ โกฑ โขโกฐ โขโก โขโก โ 0.08s โ 2.0s โ
โ sand โ โ โ โ โก โก โก โก โฃ โฃ โฃ โฃ โฃ โฃ โฃค โฃฅ โฃฆ โฃฎ โฃถ โฃท โฃฟ โกฟ โ ฟ โข โ โก โ โ ซ โข โฆ (+7) โ 0.08s โ 3.0s โ
โ line โ - \ | / โ 0.13s โ 2.0s โ
โ line2 โ โ - โ โ โ - โ 0.1s โ 2.0s โ
โ rolling-line โ / - \ | | \ - / โ 0.08s โ 2.0s โ
โ pipe โ โค โ โด โ โ โ โฌ โ โ 0.1s โ 2.4s โ
โ simple-dots โ . .. ... โ 0.4s โ 3.0s โ
โ simple-dots-scrolling โ . .. ... .. . โ 0.2s โ 3.0s โ
โ star โ โถ โธ โน โบ โน โท โ 0.07s โ 2.0s โ
โ star2 โ + x * โ 0.08s โ 2.0s โ
โ flip โ _ _ _ - ` ` ' ยด - _ _ _ โ 0.07s โ 2.5s โ
โ hamburger โ โฑ โฒ โด โ 0.1s โ 2.0s โ
โ grow-vertical โ โ โ โ โ
โ โ โ โ
โ โ โ 0.12s โ 3.0s โ
โ grow-horizontal โ โ โ โ โ โ โ โ โ โ โ โ โ โ 0.12s โ 3.0s โ
โ balloon โ . o O @ * โ 0.14s โ 2.9s โ
โ balloon2 โ . o O ยฐ O o . โ 0.12s โ 2.5s โ
โ noise โ โ โ โ โ 0.1s โ 2.0s โ
โ bounce โ โ โ โ โ โ 0.12s โ 2.0s โ
โ box-bounce โ โ โ โ โ โ 0.12s โ 2.0s โ
โ box-bounce2 โ โ โ โ โ โ 0.1s โ 2.0s โ
โ triangle โ โข โฃ โค โฅ โ 0.05s โ 2.0s โ
โ binary โ 010010 001100 100101 111010 111101 010111 101011 111000 โฆ (+2) โ 0.08s โ 2.4s โ
โ arc โ โ โ โ โ โก โ โ 0.1s โ 2.0s โ
โ circle โ โก โ โ โ 0.12s โ 2.0s โ
โ square-corners โ โฐ โณ โฒ โฑ โ 0.18s โ 2.2s โ
โ circle-quarters โ โด โท โถ โต โ 0.12s โ 2.0s โ
โ circle-halves โ โ โ โ โ โ 0.05s โ 2.0s โ
โ squish โ โซ โช โ 0.1s โ 2.0s โ
โ toggle โ โถ โท โ 0.25s โ 2.0s โ
โ toggle2 โ โซ โช โ 0.08s โ 2.0s โ
โ toggle3 โ โก โ โ 0.12s โ 2.0s โ
โ toggle4 โ โ โก โช โซ โ 0.1s โ 2.0s โ
โ toggle5 โ โฎ โฏ โ 0.1s โ 2.0s โ
โ toggle6 โ แ แ โ 0.3s โ 2.0s โ
โ toggle7 โ โฆพ โฆฟ โ 0.08s โ 2.0s โ
โ toggle8 โ โ โ โ 0.1s โ 2.0s โ
โ toggle9 โ โ โ โ 0.1s โ 2.0s โ
โ toggle10 โ ใ ใ ใ โ 0.1s โ 2.0s โ
โ toggle11 โ โง โง โ 0.05s โ 2.0s โ
โ toggle12 โ โ โ โ 0.12s โ 2.0s โ
โ toggle13 โ = * - โ 0.08s โ 2.0s โ
โ arrow โ โ โ โ โ โ โ โ โ โ 0.1s โ 2.4s โ
โ arrow2 โ โฌ โ โก โ โฌ โ โฌ
โ โ 0.08s โ 2.0s โ
โ arrow3 โ โนโนโนโนโน โธโนโนโนโน โนโธโนโนโน โนโนโธโนโน โนโนโนโธโน โนโนโนโนโธ โ 0.12s โ 2.2s โ
โ bouncing-bar โ [ ] [= ] [== ] [=== ] [====] [ ===] [ ==] [ =] โฆ (+8) โ 0.08s โ 3.0s โ
โ bouncing-ball โ ( โ ) ( โ ) ( โ ) ( โ ) ( โ) ( โ ) โฆ (+4) โ 0.08s โ 2.4s โ
โ smiley โ ๐ ๐ โ 0.2s โ 2.0s โ
โ monkey โ ๐ ๐ ๐ ๐ โ 0.3s โ 3.0s โ
โ hearts โ ๐ ๐ ๐ ๐ ๐ โ 0.1s โ 2.0s โ
โ clock โ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ โ 0.1s โ 3.0s โ
โ earth โ ๐ ๐ ๐ โ 0.18s โ 2.0s โ
โ material โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โฆ (+90) โ 0.017s โ 3.0s โ
โ moon โ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ โ 0.08s โ 2.0s โ
โ runner โ ๐ถ ๐ โ 0.14s โ 2.0s โ
โ pong โ โโ โ โโ โ โ โ โ โ โ โ โ โก โ โฆ (+25) โ 0.08s โ 3.0s โ
โ shark โ โ|\____________โ โ_|\___________โ โ__|\__________โ โฆ (+23) โ 0.12s โ 3.1s โ
โ dqpb โ d q p b โ 0.1s โ 2.0s โ
โ weather โ โ โ โ ๐ค โ
๐ฅ โ ๐ง ๐จ ๐ง ๐จ ๐ง ๐จ โ ๐จ ๐ง ๐จ โ ๐ฅ โ
โฆ (+3) โ 0.1s โ 3.0s โ
โ christmas โ ๐ฒ ๐ โ 0.4s โ 2.4s โ
โ grenade โ ุ โฒ ยด โพ โธ โธ | โ โ เทด โ โ 0.08s โ 3.0s โ
โ point โ โโโ โโโ โโโ โโโ โโโ โ 0.125s โ 2.0s โ
โ layer โ - = โก โ 0.15s โ 2.0s โ
โ beta-wave โ ฯฮฒฮฒฮฒฮฒฮฒฮฒ ฮฒฯฮฒฮฒฮฒฮฒฮฒ ฮฒฮฒฯฮฒฮฒฮฒฮฒ ฮฒฮฒฮฒฯฮฒฮฒฮฒ ฮฒฮฒฮฒฮฒฯฮฒฮฒ ฮฒฮฒฮฒฮฒฮฒฯฮฒ ฮฒฮฒฮฒฮฒฮฒฮฒฯ โ 0.08s โ 2.0s โ
โ finger-dance โ ๐ค ๐ค ๐ โ ๐ค ๐ โ 0.16s โ 2.9s โ
โ fist-bump โ ๐คใใใใ๐ค ๐คใใใใ๐ค ๐คใใใใ๐ค ใ๐คใใ๐คใ โฆ (+3) โ 0.08s โ 2.0s โ
โ soccer-header โ ๐งโฝ ๐ง ๐ง โฝ ๐ง ๐ง โฝ ๐ง โฆ (+9) โ 0.08s โ 2.9s โ
โ mindblown โ ๐ ๐ ๐ฎ ๐ฎ ๐ฆ ๐ฆ ๐ง ๐ง ๐คฏ ๐ฅ โจ โ 0.16s โ 3.0s โ
โ speaker โ ๐ ๐ ๐ ๐ โ 0.16s โ 2.0s โ
โ orange-pulse โ ๐ธ ๐ถ ๐ ๐ ๐ถ โ 0.1s โ 2.0s โ
โ blue-pulse โ ๐น ๐ท ๐ต ๐ต ๐ท โ 0.1s โ 2.0s โ
โ orange-blue-pulse โ ๐ธ ๐ถ ๐ ๐ ๐ถ ๐น ๐ท ๐ต ๐ต ๐ท โ 0.1s โ 3.0s โ
โ aesthetic โ โฐโฑโฑโฑโฑโฑโฑ โฐโฐโฑโฑโฑโฑโฑ โฐโฐโฐโฑโฑโฑโฑ โฐโฐโฐโฐโฑโฑโฑ โฐโฐโฐโฐโฐโฑโฑ โฐโฐโฐโฐโฐโฐโฑ โฐโฐโฐโฐโฐโฐโฐ โฆ (+1) โ 0.08s โ 2.0s โ
โ dwarf-fortress โ โโโโโโยฃยฃยฃ โบโโโโโโยฃยฃยฃ โบโโโโโโยฃยฃยฃ โบโโโโโโยฃยฃยฃ โฆ (+129) โ 0.08s โ 10.6s โ
โ fish โ ~~~~~~~~~~~~~~~~~~~~ > ~~~~~~~~~~~~~~~~~~ โฆ (+25) โ 0.08s โ 3.0s โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโฏ
Every one of them, animating:
Name |
Animation |
Name |
Animation |
Name |
Animation |
|---|---|---|---|---|---|
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
ย |
ย |
Bell on completionยถ
Set beep=True to ring the terminal bell once when the spinner stops, handy for a long task you walk away from. It rings only when the spinner was actually shown, so redirected or non-interactive runs stay quiet:
with Spinner("Baking bread", beep=True):
sleep(5)
Printing while spinningยถ
Because the spinner draws to stderr, results written to stdout never collide with the animation. To emit a line on the same stream as the spinner, use echo(): it erases the current frame, prints the message above the spinner, and lets the animation carry on underneath. A bare print would instead leave a frame glyph stranded mid-line.
from time import sleep
from click_extra import Spinner
def pick(stream=None):
"""Fill three baskets, tracing each one as it lands."""
with Spinner("Picking apples", stream=stream) as spinner:
for basket in range(3):
sleep(1.5)
spinner.echo(f"Filled basket {basket}")
The stream argument is threaded through only so this page can record the animation below. A real call leaves it out, and the spinner finds stderr on its own.
Each echoed line is kept where it landed and the animation carries on below it, which is the whole difference from a bare print.
Parallel workยถ
A Spinner drives a single line, and one spinner can report on a whole pool of concurrent tasks. The simplest way is to let the main thread update it as the tasks finish, through concurrent.futures.as_completed.
Update the label for a running count:
from concurrent.futures import ThreadPoolExecutor, as_completed
cities = ["Cairo", "Lima", "Oslo", "Paris", "Tokyo"]
# How long each forecast takes to come back, standing in for a real fetch.
latency = {"Cairo": 1.7, "Lima": 0.5, "Oslo": 2.1, "Paris": 0.9, "Tokyo": 1.3}
def fetch(city):
sleep(latency[city]) # The blocking call: a download, a query, a subprocess.
return city
def count_forecasts(stream=None):
"""Fetch every forecast at once, counting them off as they land."""
with Spinner(f"Fetching forecasts (0/{len(cities)})", stream=stream) as spinner:
with ThreadPoolExecutor() as pool:
futures = [pool.submit(fetch, city) for city in cities]
for done, _ in enumerate(as_completed(futures), 1):
spinner.label = f"Fetching forecasts ({done}/{len(cities)})"
# A spinner erases itself on the way out, so the final tally only
# reaches the screen if the animation gets one more beat to draw it.
sleep(0.3)
Or echo() a line as each task lands, leaving a trail of finished work that scrolls up while the spinner keeps turning below it:
def trail_forecasts(stream=None):
"""Fetch every forecast at once, leaving a line behind for each."""
with Spinner("Fetching forecasts", stream=stream) as spinner:
with ThreadPoolExecutor() as pool:
futures = {pool.submit(fetch, city): city for city in cities}
for future in as_completed(futures):
spinner.echo(f"โ {futures[future]}")
The cities land in whatever order the pool finishes them, which is why the trail above is not alphabetical.
Both label and echo() are safe to touch while the animation runs, so a worker thread can stream its own progress mid-task rather than only reporting on completion. A genuine spinner per task, several rotating at once on their own lines, is a separate capability: it needs a coordinated multi-line region, which Spinner does not attempt.
The operation trailยถ
The two idioms above (a running done/total label, plus one echoed line per finished task) are packaged together as OperationTrail, the batch-reporting companion of the concurrency primitives run_jobs and run_lanes. Each completed operation leaves a persistent โ/โ trail_line() on screen, and finish() closes the batch with a kept summary line:
from click_extra.execution import run_jobs
from click_extra.spinner import OperationTrail
jobs = 4
with OperationTrail(
label="Fetching", unit="feeds", total=len(feeds), jobs=jobs
) as trail:
def fetch(feed):
trail.mark(*pull(feed)) # pull() returns (ok, message).
list(run_jobs(fetch, feeds, jobs=jobs))
trail.finish(
trail.ok_count == len(feeds),
f"Fetched {trail.ok_count}/{len(feeds)} feeds",
)
Run concurrently, that is one aggregate spinner carrying the tally while the finished operations stack up above it:
The rendering adapts to the batchโs concurrency, and you pick neither mode by hand. Run concurrently (jobs > 1), one aggregate spinner carries the Fetching 3/5 feeds tally while the trail lines stream above it, its animation picked from the catalog with spinner=SPINNERS["moon"]. Run sequentially (jobs <= 1), each outcome echoes as a plain line and every operation stays free to keep its own per-call Spinner. Either way the finisher carries the elapsed time. OperationTrail details what each mode drives.
Only the aggregate spinner or bar needs an interactive terminal, since it redraws in place. The โ/โ lines and the finisher print on any stream, so a pipe or a CI log keeps the batchโs record, and mark() is safe to call from worker threads. A sequential batch whose real product is another output (a result table on stdout) can silence its trail with echo_sequential=False while keeping the ok_count tally.
Two arguments decide what a trail shows. live says where its spinner or bar may draw: "auto" (the default) on an interactive terminal, unless --no-progress or --accessible turned progress off; "always" on any stream; and "never" nowhere. A trail that does not draw keeps its lines. visible=False silences the whole trail.
This page captures its commands off a terminal, and the trail still prints there. That is how it shows a live sequential run:
from click_extra import command
from click_extra.spinner import OperationTrail
@command
def roast():
"""Roast a tray of vegetables, tracing each outcome as it lands."""
vegetables = ["carrots", "fennel", "leeks", "peppers"]
with OperationTrail(jobs=1) as trail:
for vegetable in vegetables:
roasted = vegetable != "leeks" # The leeks caught the heat.
trail.mark(
roasted,
f"{vegetable} roasted" if roasted else f"{vegetable} scorched",
)
trail.finish(
trail.ok_count == len(vegetables),
f"Roasted {trail.ok_count}/{len(vegetables)} vegetables",
)
$ roast
โ carrots roasted
โ fennel roasted
โ leeks scorched
โ peppers roasted
โ Roasted 3/4 vegetables
The lines echo in order as each outcome lands, and the run closes on the โ finisher because one vegetable scorched: the same trail a sequential batch leaves in a real terminal, without the live redraw.
The trailโs timer follows the --time / --no-time flag by default (timer=None), so this untimed run shows no clocks. Under --time the โ finisher gains the batchโs total, and each operation can report its own elapsed time: wrap the work in an operation() handle, whose mark() times itself, so the lines read โ carrots roasted (2.4s). Force it either way with timer=True or timer=False, or hand timer a lambda seconds: โฆ callable to format the clock.
def roast(vegetable):
op = trail.operation() # Start this operation's clock.
roasted = vegetable != "leeks"
op.mark(
roasted,
f"{vegetable} roasted" if roasted else f"{vegetable} scorched",
)
While the batch runs, the aggregate indicator counts the elapsed time up from zero by default; pass clock="eta" to count down an estimate of the time remaining instead. A determinate bar reads that estimate from Click natively, and the concurrent spinner borrows the same click.progressbar estimator since the trail knows its total. The per-operation and finisher times stay elapsed either way.
The bundled CLI wraps all three renderings in one command, to watch in a terminal what this page can only echo: click-extra trail roasts a batch behind a concurrent aggregate spinner, --jobs 1 drops to the sequential plain-line trail above, and --progress-bar swaps the spinner for a determinate bar.
$ click-extra trail --help
Usage: click-extra trail [OPTIONS]
Trace a simulated batch of operations behind an operation trail.
Roasts a handful of make-believe vegetables (each a short pause, the leeks
scorching) and reports them as they land. The display follows the batch: with
--jobs 1 each outcome echoes as a plain line; with two or more jobs a spinner
carries the running tally while outcomes stream above it; --progress-bar swaps
that spinner for a determinate progress bar. Add --time to append each
vegetable's roast time and the batch total; --elapsed and --eta turn that on
too, counting up from zero or down as an estimate. Under --no-progress, or off
an interactive terminal, no spinner or bar draws, and each outcome and the
summary print as plain lines.
Options:
--progress-bar Drive the batch with a determinate progress bar
instead of a spinner.
--eta / --elapsed For --progress-bar, show the time remaining (--eta)
or elapsed (--elapsed); either one turns timing on,
like --time. A spinner always shows elapsed time.
--spinner NAME Aggregate spinner animation for concurrent runs
(see the spinner command for names). Defaults to
the built-in spinner; ignored with --progress-bar.
--jobs [auto|max|INTEGER] Number of parallel jobs. Accepts an integer, auto
(the host's logical CPUs, minus one when there are
three or more) or max (all logical CPUs). --jobs 0
runs sequentially. [default: auto]
-h, --help Show this message and exit.
A Ctrl+C inside the trail still ends it on a finisher, like โ Interrupted after 2/5 roasts, so the record of the batch says where it stopped. A task already running when the Ctrl+C landed prints its own outcome below that line once it finishes, while the CLI waits for it.
A progress bar instead of a spinnerยถ
Because the trail knows its total, it can carry a determinate progress bar rather than an indeterminate spinner. Give the roast command above a bar by adding progress_bar=True (mutually exclusive with spinner=, and requiring a positive total), plus the label and unit its tally reads:
with OperationTrail(
label="Roasting",
unit="vegetables",
total=len(vegetables),
progress_bar=True,
) as trail:
... # mark() each outcome, then finish(), exactly as above.
The aggregate indicator becomes a bar holding the done/total count, the same โ/โ outcomes streaming above it, and a kept summary replaces the bar on finish(). It serves sequential and concurrent batches alike.
Unlike the sequential trail above, the bar is driven by cursor-control codes, so it draws only on an interactive terminal, unless live="always" forces it. Recorded off one, the landed outcomes sit above a bar tracking the tally:
When the last vegetable lands, finish() replaces the bar with the kept โ Roasted 3/4 vegetables (0.0s) summary, the same trail a sequential run leaves behind. A log record emitted mid-batch still lands on its own line above the bar, through the same cooperation the spinner uses. To watch a bar drive a live batch, run click-extra trail --progress-bar in a terminal.
Styling and colorยถ
The spinnerโs glyph, label and timer are painted with a Style instance: the very type Click Extraโs theme system is built on. The simplest customization is a foreground color:
from click_extra import Spinner, Style
with Spinner("Counting sheep", style=Style(fg="cyan")):
sleep(5)
A Style carries far more than a foreground color. Add a background with bg, and text attributes like bold, dim, italic, underline, blink or reverse, and combine them freely:
with Spinner("Counting sheep", style=Style(fg="bright_white", bg="blue", bold=True)):
sleep(5)
Colors accept any form click.style understands: ANSI names ("red", "bright_magenta"), 256-color indexes, #rrggbb hex strings, or (r, g, b) tuples. A Style carrying an unrenderable color or attribute is rejected with a ValueError at construction, so a typo fails fast instead of silently dying on the animation thread.
Color follows the terminal, not the spinnerยถ
Color is decoupled from the animation: under --no-color or NO_COLOR the spinner keeps spinning, just in plain text (the --progress section below explains the rationale). Inside a Click Extra CLI the color follows the reconciled --color/--no-color flag; standalone it reads the same environment variables as --color, then falls back to whether the terminal is interactive.
The same Style type colors the ok() / fail() finishers: they default to the themeโs success/error style and take a style= override, covered in the Success and failure section below.
Success and failureยถ
Stopping the spinner (or leaving its context) erases it and shows the cursor it hid, even when SIGTERM kills the process, which runs no clean-up of its own. To leave a result on screen instead, finish with ok() or fail(): each replaces the final frame with a kept line. The marker defaults to the themeโs success/error glyph (โ / โ), painted with the active themeโs success/error Style, so a finished spinner matches the rest of a themed CLI.
with Spinner("Baking bread") as spinner:
sleep(5)
spinner.ok() # โ Baking bread
Pass your own marker (spinner.ok("done")) or override the paint with a Style (spinner.fail(style=Style(fg="bright_red"))). Color is stripped under --no-color/NO_COLOR; off a terminal the line is still written, so the outcome is recorded in logs and pipes.
Because the finisher is written even when the spinner never appeared (a call shorter than the delay, a pipe, a non-terminal), gate it on the shown property when you only want it after a spinner the reader actually saw:
with Spinner("Baking bread") as spinner:
bake()
if spinner.shown:
spinner.ok()
Elapsed timeยถ
Set timer=True to append the running wall-clock time to the spinner, and to any ok()/fail() line:
with Spinner("Simmering stock", timer=True) as spinner:
sleep(5)
spinner.ok() # โ Simmering stock (5.0s)
The default format is compact: 2.3s, then 1:05, then 1:02:03. For anything else, pass a callable instead of True: it receives the elapsed seconds and returns the string to show:
with Spinner("Simmering stock", timer=lambda s: f"{s / 60:.0f} min") as spinner:
sleep(5)
spinner.ok() # โ Simmering stock (0 min)
Read the elapsed time any moment from the elapsed_time property, which freezes once the spinner stops.
The --progress optionยถ
click_extra.command and click_extra.group add a --progress/--no-progress flag to every CLI by default. It resolves to a single boolean at ctx.meta["click_extra.progress"]. A Spinner or an OperationTrail left at live="auto" reads it on its own, so --no-progress keeps it from drawing with no wiring in the command:
from click_extra import Spinner, command
@command
def harvest():
"""Pick apples behind a spinner, unless --no-progress is passed."""
with Spinner("Picking apples"):
sleep(5)
Pass live="always" or live="never" to decide regardless of the flag.
A command that is not a Click Extra one gets the same flag from the @progress_option decorator:
import click
from click_extra import Spinner, progress_option
@click.command
@progress_option
def press():
"""Press apples into cider."""
with Spinner("Pressing apples"):
click.echo("Cider is ready.")
$ press --help
Usage: press [OPTIONS]
Press apples into cider.
Options:
--progress / --no-progress Show progress indicators during long operations.
Disabled for non-interactive output (pipes, dumb
terminals, CI) and by --accessible.
--help Show this message and exit.
Spinner display is decoupled from color. A spinner is an interactivity concern, not a color one: it is driven by cursor-control codes, which the NO_COLOR standard explicitly does not govern. So --no-color and NO_COLOR strip the spinnerโs color but keep it spinning, the same way cargo, npm, pip, Rich, indicatif and ora gate progress on the terminal rather than on color.
The resolved value is False only for non-interactive output (a pipe, a TERM=dumb terminal, or CI: handled by the widgetโs own check when you pass live="auto") and for explicit intent (--no-progress or --accessible, the latter so a screen reader is never handed a spinning glyph).
Progress barsยถ
The same --progress/--no-progress flag also gates Clickโs determinate progress bar. click_extra.progressbar is a drop-in for click.progressbar: it reads the resolved flag and hides the bar when progress is off, so a single --no-progress (or --accessible) silences both the indeterminate spinner and the determinate bar.
from click_extra import command, progressbar
@command
def harvest():
"""Pick apples behind a determinate progress bar."""
with progressbar((1, 2, 3), label="Picking apples") as bar:
for _ in bar:
pass
$ harvest
Picking apples
$ harvest --no-progress
The hidden argument stays authoritative: pass an explicit hidden=True or hidden=False to force the bar regardless of the flag, mirroring how an explicit color= overrides ctx.color on click.echo. Color is handled upstream too, since Click renders the bar through click.echo: --no-color and NO_COLOR strip its ANSI without any extra wiring.
The barโs estimated-time display is gated the same way, but on --time rather than --progress: show_eta defaults to None, shown under --time and hidden otherwise, so a bare bar and an operation trail agree on when to surface timing. An explicit show_eta=True or show_eta=False overrides it (Clickโs own default is True).
click_extra.spinner APIยถ
classDiagram
ExtraOption <|-- ProgressOption
Protocol <|-- _AggregateIndicator
Protocol <|-- _LiveLine
tuple <|-- SpinnerPreset
Where a spinner or a progress bar may draw, see LIVE_MODES.
- click_extra.spinner.TLive
Where a spinner or a progress bar may draw, see
LIVE_MODES.alias of
Literal[โautoโ, โalwaysโ, โneverโ]
- click_extra.spinner.active_spinner(stream=None)[source]
Return the innermost
Spinnercurrently animating, orNone.A spinner-typed view of
_active_line(), skipping any progress-bar indicator that may own the line instead. Withstreamgiven, only a spinner drawing on that very stream matches.
- click_extra.spinner.LIVE_MODES: tuple[str, ...] = ('auto', 'always', 'never')
The values of the
liveargument ofSpinnerandOperationTrail."auto": draw only on an interactive terminal that can move the cursor, and only when the--progressflag of the active command allows it."always": draw on any stream, a pipe or a captured buffer included."never": never draw.
- class click_extra.spinner.Spinner(label='', *, frames=None, spinner=None, reverse=False, interval=None, delay=0.0, style=None, label_style=None, timer=None, timer_style=None, stream=None, live='auto', hide_cursor=True, beep=False, enabled=Sentinel.UNSET)[source]
Bases:
objectA thread-animated, indeterminate progress spinner usable as a context manager.
The animation runs on a background daemon thread, leaving the calling thread free to block on the actual work. Entering the context (or calling
start()) begins the animation; leaving it (or callingstop()) halts the thread and erases the spinner line so it never lingers above the next output.Note
A single
Spinnerinstance drives one animation at a time. mpm and similar tools run their subprocesses sequentially, so one shared instance whoselabelis reassigned between steps is enough; for concurrent work, use one instance per thread.Configure (but do not start) the spinner.
- Parameters:
label (
str|Callable[...,Any]) โ text shown after the spinner glyph. As a special case, a bare@Spinnerdecorator passes the wrapped function here instead; it is detected and the label defaults to empty.frames (
Sequence[str] |None) โ the animation frames, cycled in order. Defaults toSPINNER_FRAMES, or thespinnerpresetโs frames when given.spinner (
SpinnerPreset|None) โ aSpinnerPresetfrom theSPINNERScatalog (spinner=SPINNERS["moon"]), supplying both frames and a tuned interval. An explicitframesorintervalstill overrides it.reverse (
bool) โ cycle the frames backwards, spinning the animation the other way. Set it when the rotation runs counter to what you expect; it composes with any customframes.interval (
float|None) โ seconds between two frames. Defaults to0.1, or thespinnerpresetโs interval when given.delay (
float) โ seconds to wait before drawing the first frame. A non-zero delay keeps the spinner silent for calls that finish quickly, so it only surfaces once an operation is genuinely slow.style (
Style|None) โ aStyleapplied to the spinner glyph, label and timer (Style(fg="cyan", bold=True)). Color is decoupled from animation:--no-color/NO_COLORstrip it while the spinner keeps spinning (seeProgressOption). The strip reaches escapes embedded inlabelitself too, so a label styled by hand is as safe as one styled through these arguments.label_style (
Style|None) โ aStylefor the label alone, in place ofstylethere. It also paints the label of the keptok()/fail()line, whichstyleleaves plain. It outlives a reassignedlabel, where styling embedded in the text has to be applied again at every step.timer (
bool|Callable[[float],str] |None) โ append the elapsed wall-clock time to the spinner, and to any finalok()/fail()line.None(the default) follows the CLIโs--time/--no-timeflag, as anOperationTraildoes.Trueforces it on withformat_duration()โs compact format (2.3s,1:05, then1:02:03),Falseforces it off, and a callable(seconds: float) -> strformats the duration yourself, liketimer=lambda s: f"{s / 60:.0f}m"for whole minutes.timer_style (
Style|None) โ aStylefor the timer, parentheses included, in place ofstylethere (Style(dim=True)to set the clock back from the label). Atimercallable formats only the duration, so it cannot reach the parentheses: this can. Also paints the timer of the keptok()/fail()line.stream (
IO[str] |None) โ where to draw; defaults tosys.stderrso the spinner never mixes intostdoutdata.live (
Literal['auto','always','never']) โ where the animation may draw:"auto"(the default) on an interactive terminal only, unless--no-progressturns it off,"always"on any stream,"never"nowhere. A spinner that does not draw still writes itsok()/fail()line.hide_cursor (
bool) โ hide the text cursor while spinning and restore it on stop.beep (
bool) โ ring the terminal bell once when the spinner stops. It fires only when the spinner was active, so a disabled or redirected spinner stays silent.enabled (
bool|Literal[UNSET] |None) โ deprecated, useliveinstead:Nonestands for"auto",Truefor"always"andFalsefor"never".
- Raises:
ValueError โ if
style,label_styleortimer_stylecarries a color or attribute that cannot be rendered, or ifliveis not one ofLIVE_MODES.
- label: str
Text drawn after the spinner glyph.
Reassign it at any time while the spinner runs to reflect the current step; the animation thread reads it afresh on every frame.
- property elapsed_time: float
Seconds elapsed since
start(), frozen oncestop()is called.Returns
0.0before the spinner has started.
- property shown: bool
Whether the spinner has drawn at least one frame to its stream.
Trueonly once an animation frame was actually rendered. It staysFalsefor a disabled spinner (off a TTY, on aTERM=dumbterminal, or withlive="never") and for a call that finishes withindelay, before the first frame. Reset bystart().Use it to gate output that should mirror the spinnerโs visibility.
ok()andfail()write their line unconditionally, so an outcome is still recorded in a pipe or log; guard them withshownwhen you only want the finisher on screen after a spinner the user actually saw:with Spinner("Baking bread") as spinner: bake() if spinner.shown: spinner.ok()
- frame_lines(*, color=True)[source]
Every line this spinnerโs animation draws, one per frame.
One turn of the animation, held still.
reverse, the label, the style and the timer all land the waystart()would draw them, which is what an animated capture stacks into a picture of this spinner.Note
The timer is read once, here, so every line carries the same elapsed time rather than a counting one: a still cannot show a clock running. A spinner that never started reads zero.
- start()[source]
Begin animating on a background thread, unless the spinner is disabled.
A disabled spinner (non-TTY stream, or
live="never") returns at once without spawning a thread or emitting anything (but still records the start time, so a laterok()/fail()can report a duration).- Return type:
- stop()[source]
Halt the animation and erase the spinner line.
Idempotent and safe to call when the spinner never started. Restores the cursor and clears the line only if the animation actually drew to the terminal.
- Return type:
- echo(message='')[source]
Print
messageon its own line above the running spinner.Clickโs
click.progressbar()and a bareprintboth fight the animation: a frame drawn between the cursor returns and the text mangles the line.echo()takes the same draw lock as the animation thread, erases the in-progress frame, writesmessagefollowed by a newline, and lets the next tick redraw the spinner underneath. It is safe to call from another thread while the spinner runs.Output goes to the spinnerโs own
stream(stderrby default), so results written tostdoutnever need it. When the spinner is not animating (disabled, or a non-TTY stream), it degrades to a plain write ofmessagewith no control codes.- Return type:
- ok(symbol=None, *, style=None)[source]
Stop the spinner and leave a persistent success line on screen.
Where
stop()erases the spinner,ok()replaces the final frame withsymbolfollowed by the current label (and the elapsed time whentimeris set), then keeps that line.symboldefaults to the themed success glyphOK_GLYPH(โ), painted with the active themeโssuccessslot unlessstyleoverrides it. Color is stripped under--no-color/NO_COLOR; the glyph stays.- Return type:
- click_extra.spinner.trail_glyph(ok)[source]
Return the themed
โorโglyph for a trail line or finisher.The success glyph
OK_GLYPHpainted with the active themeโssuccessslot, or the failure glyphKO_GLYPHpainted with itserrorslot.- Return type:
- click_extra.spinner.trail_line(ok, message)[source]
Format one
โ/โtrail line: a status glyph followed bymessage.- Return type:
- class click_extra.spinner.OperationTrail(*, label='', unit='', total=0, jobs=1, spinner=None, progress_bar=False, timer=None, clock='elapsed', visible=True, live='auto', echo_sequential=True, delay=0.0, stream=None, enabled=Sentinel.UNSET)[source]
Bases:
objectA
โ/โprogress trail and finisher for a batch of operations.Where
Spinnernarrates one long-running call,OperationTrailreports a batch of them: each completed operation leaves a persistenttrail_line()on screen, a runningdone/totaltally keeps the batchโs pulse visible, andfinish()closes with a persistent summary line. The natural reporting companion of the concurrency primitivesrun_jobs()andrun_lanes(), rendered one of three ways:sequential (
jobs <= 1): echo each outcome as it lands, with no aggregate indicator (each operation is free to keep its own per-callSpinner).finish()appends the elapsed time.concurrent (
jobs > 1): drive one aggregateSpinner(per-call spinners would collide on the shared stream), buffering outcomes until it first draws, then streaming the rest live above it. Pick the animation from theSPINNERScatalog withspinner=.progress bar (
progress_bar=True): drive one aggregate determinate bar carrying the{done}/:total:` tally, with outcomes streaming above it. Serves sequential and concurrent batches alike, and needs a known `total.
The aggregate indicators redraw in place, which a pipe or a CI log cannot do, so by default they draw only on an interactive terminal, and
livechanges where they draw. Theโ/โlines and the finisher only append, so they print on any stream, in plain text where color is off. Where no indicator draws, every rendering echoes each outcome as it lands, as the sequential one does.visible=Falsesilences all of it. The runningโtally is kept as outcomes land (ok_count), so a caller computes no counts of its own.Thread-safe:
mark()may be called from worker threads. Use it as a context manager whenever it may run concurrently, to bound the aggregate spinnerโs life; a purely sequential caller may construct it bare.from click_extra.execution import run_jobs from click_extra.spinner import OperationTrail with OperationTrail(label="Fetching", unit="feeds", total=len(feeds), jobs=jobs) as trail: def fetch(feed): trail.mark(*pull(feed)) # pull() returns (ok, message). list(run_jobs(fetch, feeds, jobs=jobs)) trail.finish( trail.ok_count == len(feeds), f"Fetched {trail.ok_count}/{len(feeds)} feeds", )
Configure (but do not start) the trail.
- Parameters:
label (
str) โ present-tense verb for the running aggregate indicator ("Fetching"), composed into its{label} {done}/{total} {unit}tally.unit (
str) โ the noun counted in the tally ("files","feeds").total (
int) โ how many outcomes are expected, for thedone/totalcount.jobs (
int) โ the batchโs worker count;> 1selects the concurrent rendering (one aggregate spinner),<= 1the sequential one (plain echoed lines).spinner (
SpinnerPreset|None) โ aSpinnerPresetfrom theSPINNERScatalog (spinner=SPINNERS["moon"]) for the concurrent aggregate spinner. Ignored by the sequential and progress-bar renderings, and mutually exclusive withprogress_bar.progress_bar (
bool) โ render the aggregate indicator as a determinateclick.progressbar()instead of a spinner, for a sequential or concurrent batch alike. Requires a positivetotal(a bar needs a length) and is mutually exclusive withspinner.timer (
bool|Callable[[float],str] |None) โ append each operationโs and the batchโs elapsed time to the trail lines and the finisher.None(the default) follows the CLIโs--time/--no-timeflag;Trueforces timing on withformat_duration()โs compact clock, a callable(seconds: float) -> strforces it on with a custom format, andFalseforces it off. Per-operation times come from asecondsargument tomark(), filled in automatically by anoperation()handle.clock (
Literal['elapsed','eta']) โ whether a running aggregate indicator shows elapsed time ("elapsed", the default: a stopwatch counting up, visible from the start) or remaining time ("eta": an estimate from the batchโs rate, appearing only once an outcome lets it be computed). Both the progress bar and the concurrent spinner honor"eta"(the spinner reuses Clickโs progress-bar estimate, since the trail knows itstotal). Per-operation and finisher times are always elapsed.visible (
bool) โ whether the trail shows anything.Falsesilences the lines, the finisher and the aggregate indicator, whileok_countkeeps counting.live (
Literal['auto','always','never']) โ where the aggregate indicator may draw:"auto"(the default) on an interactive terminal only, unless--no-progressturns it off,"always"on any stream,"never"nowhere. Where it does not draw, each outcome line prints as it lands.echo_sequential (
bool) โ whether the batch echoes its outcome lines and finisher as plain lines at all: in a sequential batch, in a batch whose aggregate indicator cannot draw on the stream, and in one that finishes before its indicator first draws. Turn it off when the batch has another output that is the real product (a result table) and the trail would be noise. An indicator that did draw is unaffected.delay (
float) โ seconds before the aggregate indicator first draws: a fast batch then completes without ever flashing one, its lines echoed plainly atfinish()instead (seeecho_sequential).stream (
IO[str] |None) โ where to render; defaults tosys.stderrso the trail never mixes intostdoutdata.enabled (
bool|Literal[UNSET] |None) โ deprecated, usevisibleandliveinstead:Falsestands forvisible=False, andTrueforlive="always".
- Raises:
ValueError โ if
progress_baris set without a positivetotal, or together withspinner, ifclockis neither"elapsed"nor"eta", or ifliveis not one ofLIVE_MODES.
- property ok_count: int
How many marked outcomes have succeeded so far.
- mark(ok, message, seconds=None)[source]
Record one
โ/โoutcome: tally it and render its trail line.- Parameters:
seconds (
float|None) โ the operationโs own elapsed time. Whentimeris on it is formatted and appended tomessageas(2.3s). Anoperation()handle fills this in from when it was created; pass it yourself when you already hold a duration.- Return type:
- finish(ok, summary)[source]
Render the persistent
โ/โ{summary}finisher.With an aggregate indicator that drew, it becomes the indicatorโs kept line (a spinnerโs
Spinner.ok()/Spinner.fail()line, or the barโs replacement line); otherwise, a plain echoed line. The batchโs elapsed time since construction is appended whentimeris on.A batch finishing inside
delaynever draws its indicator, so none of its buffered lines reached the stream. When the trail is the batchโs output (echo_sequentialandvisible), they are echoed plainly along with the finisher, the way a sequential batch prints them: how fast a batch ran must not decide whether its record exists.- Return type:
- operation()[source]
Start a timed operation, returning a handle to record its outcome.
The handle captures the current time; call
_Operation.mark()when the work finishes to record itsโ/โoutcome with the elapsed time appended (whentimeris on). This is how a batch reports per-operation timings under concurrency, where the trail itself never sees when an operation began:def fetch(feed): op = trail.operation() ok, message = pull(feed) op.mark(ok, message)
- Return type:
_Operation
- class click_extra.spinner.ProgressOption(param_decls=None, *, is_flag=True, default=True, is_eager=True, expose_value=False, help='Show progress indicators during long operations. Disabled for non-interactive output (pipes, dumb terminals, CI) and by --accessible.', **kwargs)[source]
Bases:
ExtraOptionA pre-configured
--progress/--no-progressflag gating spinner display.Resolves to a single boolean published at
ctx.meta[click_extra.context.PROGRESS], whichSpinner,OperationTrailandprogressbar()read on their own when left at their automatic default. The default isTrue;--accessiblelowers it toFalse(viadefault_map) so a screen reader is never handed a spinning glyph.Note
Spinner display is intentionally decoupled from color, even though both emit ANSI. A spinner is an interactivity concern, not a color one: it is built from cursor-control codes (hide-cursor, carriage return, clear-line), which the NO_COLOR standard explicitly does not govern โ it โonly signals the userโs intention regarding adding ANSI color to text outputโ. So
--no-color/NO_COLORstrip the spinnerโs colors but never hide it.This matches how the wider ecosystem treats the two axes as orthogonal: cargo, npm, pip, Rich, indicatif and ora all gate progress on the terminal (and a dedicated
--progress/--quietknob), whileNO_COLORonly affects color. Rich usesTERM=dumbโ notNO_COLORโ as the signal to drop cursor-moving features like progress bars.The spinner is therefore silenced by two things only, neither of them color:
non-interactive output โ a pipe, file, CI log, or
TERM=dumbterminal that cannot move the cursor (seeSpinner._resolve_live);explicit intent โ
--no-progressor--accessible.
This option is eager. It no longer reads
ctx.color, so its position relative toColorOptionis not load-bearing.- set_progress(ctx, param, value)[source]
Publish whether progress spinners may be shown.
Stores the resolved
--progressflag atPROGRESS. Deliberately independent of color: see theProgressOptionnote for why a spinner is gated on interactivity (TTY /TERM=dumb) and--accessible, never on--no-color/NO_COLOR.- Return type:
- click_extra.spinner.progressbar(iterable=None, length=None, label=None, hidden=None, show_eta=None, **kwargs)[source]
Drop-in for
click.progressbar()honoring--progressand--time.Clickโs own progress bar is determinate, the counterpart to the indeterminate
Spinner. This thin wrapper gates its visibility on the samePROGRESSflag the spinner uses, so a single--no-progress(or--accessible, which lowers theprogressdefault) silences both, and gates its estimated-time display on--time.- Parameters:
hidden (
bool|None) โ tri-state. Left at its defaultNone, the bar follows the resolved--progressflag: hidden when the user (or--accessible) turned progress off, shown otherwise. An explicitTrueorFalseforces the bar regardless, mirroring how an explicitcolor=argument overridesctx.coloronclick.echo(). With no active context (the bar used outside a Click command) it defaults to shown.show_eta (
bool|None) โ tri-state, likehidden. Left at its defaultNone, the estimated-time-remaining display follows the--time/--no-timeflag: shown under--time, hidden otherwise (its default, or outside a command). An explicitTrueorFalseforces it, keeping a bare barโs timing in step with anOperationTrailโstimer. Clickโs own default isTrue.
- Return type:
ProgressBar[TypeVar(V)]
Note
The
--progressflag gates visibility and--timethe ETA. Color is already handled upstream: Click renders the bar throughclick.echo(), whosecolor=Noneresolves againstctx.color, so--no-color/NO_COLORstrip the barโs ANSI without any work from this wrapper.
classDiagram
tuple <|-- SpinnerPreset
The bundled catalog of terminal spinner presets.
Ported from cli-spinners, with frame intervals converted from milliseconds to seconds.
Note
One upstream entry is deliberately absent. timeTravel is clock running
backwards, which upstream has to ship as a second preset because its renderers
only play frames forwards. Spinner takes a
reverse argument, so the same animation is SPINNERS["clock"] with
reverse=True and a duplicate would only be a second name for it.
Note
The emoji frames are one cell narrower than upstream writes them. Upstream pads
each with a trailing space, so a terminal drawing the emoji one cell wide still
leaves a gap before whatever follows. Here that space is one cell too many:
Spinner already writes one before its label, and
the pair reads as a double gap. A capture laid out on the frames alone also
reserves a column nothing is ever drawn in, which leaves the glyph off center.
Every frame of a preset is the same width, so a label never moves as the
animation turns. weather is the one upstream entry that was ragged, mixing
two- and three-cell frames.
- click_extra.spinner_presets.ASCII_SPINNER_FRAMES: Final = ('-', '\\', '|', '/')
Plain ASCII animation frames, for terminals or fonts lacking Unicode glyphs.
- click_extra.spinner_presets.SPINNER_FRAMES: Final = ('โ ', 'โ ', 'โ น', 'โ ธ', 'โ ผ', 'โ ด', 'โ ฆ', 'โ ง', 'โ ', 'โ ')
Default animation frames: the ubiquitous Braille-dots spinner.
Ten frames give a smooth rotation in any UTF-8 terminal. Fall back to
ASCII_SPINNER_FRAMESwhere Braille glyphs are unavailable.
- class click_extra.spinner_presets.SpinnerPreset(frames: tuple[str, ...], interval: float)[source]
Bases:
NamedTupleA named spinner animation: its frames and the interval they look best at.
The
SPINNERScatalog is ported from cli-spinners, with intervals converted from milliseconds to seconds. Pass one toSpinnervia itsspinnerargument.Create new instance of SpinnerPreset(frames, interval)
- interval: float
Seconds between two frames, tuned per spinner upstream.
- click_extra.spinner_presets.SPINNERS: Final = {'aesthetic': (('โฐโฑโฑโฑโฑโฑโฑ', 'โฐโฐโฑโฑโฑโฑโฑ', 'โฐโฐโฐโฑโฑโฑโฑ', 'โฐโฐโฐโฐโฑโฑโฑ', 'โฐโฐโฐโฐโฐโฑโฑ', 'โฐโฐโฐโฐโฐโฐโฑ', 'โฐโฐโฐโฐโฐโฐโฐ', 'โฐโฑโฑโฑโฑโฑโฑ'), 0.08), 'arc': (('โ', 'โ ', 'โ', 'โ', 'โก', 'โ'), 0.1), 'arrow': (('โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ'), 0.1), 'arrow2': (('โฌ๏ธ', 'โ๏ธ', 'โก๏ธ', 'โ๏ธ', 'โฌ๏ธ', 'โ๏ธ', 'โฌ ๏ธ', 'โ๏ธ'), 0.08), 'arrow3': (('โนโนโนโนโน', 'โธโนโนโนโน', 'โนโธโนโนโน', 'โนโนโธโนโน', 'โนโนโนโธโน', 'โนโนโนโนโธ'), 0.12), 'balloon': ((' ', '.', 'o', 'O', '@', '*', ' '), 0.14), 'balloon2': (('.', 'o', 'O', 'ยฐ', 'O', 'o', '.'), 0.12), 'beta-wave': (('ฯฮฒฮฒฮฒฮฒฮฒฮฒ', 'ฮฒฯฮฒฮฒฮฒฮฒฮฒ', 'ฮฒฮฒฯฮฒฮฒฮฒฮฒ', 'ฮฒฮฒฮฒฯฮฒฮฒฮฒ', 'ฮฒฮฒฮฒฮฒฯฮฒฮฒ', 'ฮฒฮฒฮฒฮฒฮฒฯฮฒ', 'ฮฒฮฒฮฒฮฒฮฒฮฒฯ'), 0.08), 'binary': (('010010', '001100', '100101', '111010', '111101', '010111', '101011', '111000', '110011', '110101'), 0.08), 'blue-pulse': (('๐น', '๐ท', '๐ต', '๐ต', '๐ท'), 0.1), 'bounce': (('โ ', 'โ ', 'โ ', 'โ '), 0.12), 'bouncing-ball': (('( โ )', '( โ )', '( โ )', '( โ )', '( โ)', '( โ )', '( โ )', '( โ )', '( โ )', '(โ )'), 0.08), 'bouncing-bar': (('[ ]', '[= ]', '[== ]', '[=== ]', '[====]', '[ ===]', '[ ==]', '[ =]', '[ ]', '[ =]', '[ ==]', '[ ===]', '[====]', '[=== ]', '[== ]', '[= ]'), 0.08), 'box-bounce': (('โ', 'โ', 'โ', 'โ'), 0.12), 'box-bounce2': (('โ', 'โ', 'โ', 'โ'), 0.1), 'christmas': (('๐ฒ', '๐'), 0.4), 'circle': (('โก', 'โ', 'โ '), 0.12), 'circle-halves': (('โ', 'โ', 'โ', 'โ'), 0.05), 'circle-quarters': (('โด', 'โท', 'โถ', 'โต'), 0.12), 'clock': (('๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐'), 0.1), 'dots': (('โ ', 'โ ', 'โ น', 'โ ธ', 'โ ผ', 'โ ด', 'โ ฆ', 'โ ง', 'โ ', 'โ '), 0.08), 'dots-8bit': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก ', 'โก', 'โก', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โก', 'โ ', 'โ ก', 'โ ข', 'โ ฃ', 'โ ค', 'โ ฅ', 'โ ฆ', 'โ ง', 'โก ', 'โกก', 'โกข', 'โกฃ', 'โกค', 'โกฅ', 'โกฆ', 'โกง', 'โ จ', 'โ ฉ', 'โ ช', 'โ ซ', 'โ ฌ', 'โ ญ', 'โ ฎ', 'โ ฏ', 'โกจ', 'โกฉ', 'โกช', 'โกซ', 'โกฌ', 'โกญ', 'โกฎ', 'โกฏ', 'โ ฐ', 'โ ฑ', 'โ ฒ', 'โ ณ', 'โ ด', 'โ ต', 'โ ถ', 'โ ท', 'โกฐ', 'โกฑ', 'โกฒ', 'โกณ', 'โกด', 'โกต', 'โกถ', 'โกท', 'โ ธ', 'โ น', 'โ บ', 'โ ป', 'โ ผ', 'โ ฝ', 'โ พ', 'โ ฟ', 'โกธ', 'โกน', 'โกบ', 'โกป', 'โกผ', 'โกฝ', 'โกพ', 'โกฟ', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข ', 'โข', 'โข', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ ', 'โฃ', 'โฃ', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โข', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โข ', 'โขก', 'โขข', 'โขฃ', 'โขค', 'โขฅ', 'โขฆ', 'โขง', 'โฃ ', 'โฃก', 'โฃข', 'โฃฃ', 'โฃค', 'โฃฅ', 'โฃฆ', 'โฃง', 'โขจ', 'โขฉ', 'โขช', 'โขซ', 'โขฌ', 'โขญ', 'โขฎ', 'โขฏ', 'โฃจ', 'โฃฉ', 'โฃช', 'โฃซ', 'โฃฌ', 'โฃญ', 'โฃฎ', 'โฃฏ', 'โขฐ', 'โขฑ', 'โขฒ', 'โขณ', 'โขด', 'โขต', 'โขถ', 'โขท', 'โฃฐ', 'โฃฑ', 'โฃฒ', 'โฃณ', 'โฃด', 'โฃต', 'โฃถ', 'โฃท', 'โขธ', 'โขน', 'โขบ', 'โขป', 'โขผ', 'โขฝ', 'โขพ', 'โขฟ', 'โฃธ', 'โฃน', 'โฃบ', 'โฃป', 'โฃผ', 'โฃฝ', 'โฃพ', 'โฃฟ'), 0.08), 'dots-circle': (('โข ', 'โ โ ', 'โ โ ', 'โ โ ฑ', ' โกฑ', 'โขโกฐ', 'โขโก ', 'โขโก'), 0.08), 'dots10': (('โข', 'โข', 'โข', 'โก', 'โก', 'โก', 'โก '), 0.08), 'dots11': (('โ ', 'โ ', 'โ ', 'โก', 'โข', 'โ ', 'โ ', 'โ '), 0.1), 'dots12': (('โขโ ', 'โกโ ', 'โ โ ', 'โขโ ', 'โกโ ', 'โ โ ', 'โขโ ', 'โกโ ', 'โ โ ', 'โขโ ', 'โกโ ', 'โ โ ', 'โขโ ', 'โกโ ', 'โ โ ', 'โ โ ', 'โ โ ', 'โ โ ', 'โ โ ', 'โ โ ฉ', 'โ โข', 'โ โก', 'โขโ ฉ', 'โกโข', 'โ โก', 'โขโ ฉ', 'โกโข', 'โ โก', 'โขโ จ', 'โกโข', 'โ โก', 'โขโ ', 'โกโข', 'โ โก', 'โขโ ', 'โกโ ', 'โ โ ', 'โ โ ', 'โ โ ', 'โ โ ', 'โ โ ', 'โ โ ฉ', 'โ โข', 'โ โก', 'โ โ ฉ', 'โ โข', 'โ โก', 'โ โ ฉ', 'โ โข', 'โ โก', 'โ โ จ', 'โ โข', 'โ โก', 'โ โ ', 'โ โข', 'โ โก'), 0.08), 'dots13': (('โฃผ', 'โฃน', 'โขป', 'โ ฟ', 'โก', 'โฃ', 'โฃง', 'โฃถ'), 0.08), 'dots14': (('โ โ ', 'โ โ ', 'โ โ น', 'โ โขธ', 'โ โฃฐ', 'โขโฃ ', 'โฃโฃ', 'โฃโก', 'โฃโ ', 'โกโ ', 'โ โ ', 'โ โ '), 0.08), 'dots2': (('โฃพ', 'โฃฝ', 'โฃป', 'โขฟ', 'โกฟ', 'โฃ', 'โฃฏ', 'โฃท'), 0.08), 'dots3': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ฆ', 'โ ด', 'โ ฒ', 'โ ณ', 'โ '), 0.08), 'dots4': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ธ', 'โ ฐ', 'โ ', 'โ ฐ', 'โ ธ', 'โ ', 'โ ', 'โ ', 'โ '), 0.08), 'dots5': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ฒ', 'โ ด', 'โ ฆ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ '), 0.08), 'dots6': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ฒ', 'โ ด', 'โ ค', 'โ ', 'โ ', 'โ ค', 'โ ด', 'โ ฒ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ '), 0.08), 'dots7': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ฆ', 'โ ค', 'โ ', 'โ ', 'โ ค', 'โ ฆ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ '), 0.08), 'dots8': (('โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ฒ', 'โ ด', 'โ ค', 'โ ', 'โ ', 'โ ค', 'โ ', 'โ ', 'โ ค', 'โ ฆ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ ', 'โ '), 0.08), 'dots9': (('โขน', 'โขบ', 'โขผ', 'โฃธ', 'โฃ', 'โกง', 'โก', 'โก'), 0.08), 'dqpb': (('d', 'q', 'p', 'b'), 0.1), 'dwarf-fortress': ((' โโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบโโโโโโยฃยฃยฃ ', 'โบ โโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบโโโโโยฃยฃยฃ ', ' โบ โโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบโโโโยฃยฃยฃ ', ' โบ โโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบโโโยฃยฃยฃ ', ' โบ โโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบโโยฃยฃยฃ ', ' โบ โยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบโยฃยฃยฃ ', ' โบ ยฃยฃยฃ ', ' โบยฃยฃยฃ ', ' โบยฃยฃยฃ ', ' โบโยฃยฃ ', ' โบโยฃยฃ ', ' โบโยฃยฃ ', ' โบโยฃยฃ ', ' โบโยฃยฃ ', ' โบโยฃยฃ ', ' โบ ยฃยฃ ', ' โบยฃยฃ ', ' โบยฃยฃ ', ' โบโยฃ ', ' โบโยฃ ', ' โบโยฃ ', ' โบโยฃ ', ' โบโยฃ ', ' โบโยฃ ', ' โบ ยฃ ', ' โบยฃ ', ' โบยฃ ', ' โบโ ', ' โบโ ', ' โบโ ', ' โบโ ', ' โบโ ', ' โบโ ', ' โบ ', ' โบ &', ' โบ โผ&', ' โบ โผ &', ' โบโผ &', ' โบโผ & ', ' โผ & ', ' โบ & ', ' โผ & ', ' โบ & ', ' โผ & ', ' โบ & ', 'โผ & ', ' & ', ' & ', ' & โ ', ' & โ ', ' & โ ', ' & ยฃ ', ' & โยฃ ', ' & โยฃ ', ' & โยฃ ', ' & ยฃยฃ ', ' & โยฃยฃ ', ' & โยฃยฃ ', '& โยฃยฃ ', '& ยฃยฃยฃ ', ' โยฃยฃยฃ ', ' โยฃยฃยฃ ', ' โยฃยฃยฃ ', ' โยฃยฃยฃ ', ' โโยฃยฃยฃ ', ' โโยฃยฃยฃ ', ' โโยฃยฃยฃ ', ' โโยฃยฃยฃ ', ' โโโยฃยฃยฃ ', ' โโโยฃยฃยฃ ', ' โโโยฃยฃยฃ ', ' โโโยฃยฃยฃ ', ' โโโโยฃยฃยฃ ', ' โโโโยฃยฃยฃ ', ' โโโโยฃยฃยฃ ', ' โโโโยฃยฃยฃ ', ' โโโโโยฃยฃยฃ ', ' โโโโโยฃยฃยฃ ', ' โโโโโยฃยฃยฃ ', ' โโโโโยฃยฃยฃ ', ' โโโโโโยฃยฃยฃ ', ' โโโโโโยฃยฃยฃ ', ' โโโโโโยฃยฃยฃ ', ' โโโโโโยฃยฃยฃ ', ' โโโโโโยฃยฃยฃ '), 0.08), 'earth': (('๐', '๐', '๐'), 0.18), 'finger-dance': (('๐ค', '๐ค', '๐', 'โ', '๐ค', '๐'), 0.16), 'fish': (('~~~~~~~~~~~~~~~~~~~~', '> ~~~~~~~~~~~~~~~~~~', 'ยบ> ~~~~~~~~~~~~~~~~~', '(ยบ> ~~~~~~~~~~~~~~~~', '((ยบ> ~~~~~~~~~~~~~~~', '<((ยบ> ~~~~~~~~~~~~~~', '><((ยบ> ~~~~~~~~~~~~~', ' ><((ยบ> ~~~~~~~~~~~~', '~ ><((ยบ> ~~~~~~~~~~~', '~~ <>((ยบ> ~~~~~~~~~~', '~~~ ><((ยบ> ~~~~~~~~~', '~~~~ <>((ยบ> ~~~~~~~~', '~~~~~ ><((ยบ> ~~~~~~~', '~~~~~~ <>((ยบ> ~~~~~~', '~~~~~~~ ><((ยบ> ~~~~~', '~~~~~~~~ <>((ยบ> ~~~~', '~~~~~~~~~ ><((ยบ> ~~~', '~~~~~~~~~~ <>((ยบ> ~~', '~~~~~~~~~~~ ><((ยบ> ~', '~~~~~~~~~~~~ <>((ยบ> ', '~~~~~~~~~~~~~ ><((ยบ>', '~~~~~~~~~~~~~~ <>((ยบ', '~~~~~~~~~~~~~~~ ><((', '~~~~~~~~~~~~~~~~ <>(', '~~~~~~~~~~~~~~~~~ ><', '~~~~~~~~~~~~~~~~~~ <', '~~~~~~~~~~~~~~~~~~~~'), 0.08), 'fist-bump': (('๐ค\u3000\u3000\u3000\u3000๐ค', '๐ค\u3000\u3000\u3000\u3000๐ค', '๐ค\u3000\u3000\u3000\u3000๐ค', '\u3000๐ค\u3000\u3000๐ค\u3000', '\u3000\u3000๐ค๐ค\u3000\u3000', '\u3000๐คโจ๐ค\u3000\u3000', '๐ค\u3000โจ\u3000๐ค\u3000'), 0.08), 'flip': (('_', '_', '_', '-', '`', '`', "'", 'ยด', '-', '_', '_', '_'), 0.07), 'grenade': (('ุ ', 'โฒ ', ' ยด ', ' โพ ', ' โธ', ' โธ', ' |', ' โ', ' โ', ' เทด ', ' โ', ' ', ' ', ' '), 0.08), 'grow-horizontal': (('โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ'), 0.12), 'grow-vertical': (('โ', 'โ', 'โ', 'โ ', 'โ', 'โ', 'โ', 'โ ', 'โ', 'โ'), 0.12), 'hamburger': (('โฑ', 'โฒ', 'โด'), 0.1), 'hearts': (('๐', '๐', '๐', '๐', '๐'), 0.1), 'layer': (('-', '=', 'โก'), 0.15), 'line': (('-', '\\', '|', '/'), 0.13), 'line2': (('โ ', '-', 'โ', 'โ', 'โ', '-'), 0.1), 'material': (('โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ', 'โโโโโโโโโโโโโโโโโโโโ'), 0.017), 'mindblown': (('๐', '๐', '๐ฎ', '๐ฎ', '๐ฆ', '๐ฆ', '๐ง', '๐ง', '๐คฏ', '๐ฅ', 'โจ', '\u3000', '\u3000', '\u3000'), 0.16), 'monkey': (('๐', '๐', '๐', '๐'), 0.3), 'moon': (('๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐'), 0.08), 'noise': (('โ', 'โ', 'โ'), 0.1), 'orange-blue-pulse': (('๐ธ', '๐ถ', '๐ ', '๐ ', '๐ถ', '๐น', '๐ท', '๐ต', '๐ต', '๐ท'), 0.1), 'orange-pulse': (('๐ธ', '๐ถ', '๐ ', '๐ ', '๐ถ'), 0.1), 'pipe': (('โค', 'โ', 'โด', 'โ', 'โ', 'โ', 'โฌ', 'โ'), 0.1), 'point': (('โโโ', 'โโโ', 'โโโ', 'โโโ', 'โโโ'), 0.125), 'pong': (('โโ โ', 'โโ โ', 'โ โ โ', 'โ โ โ', 'โ โก โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โก โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โกโ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โก โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โ โ', 'โ โก โ', 'โโ โ'), 0.08), 'rolling-line': (('/ ', ' - ', ' \\ ', ' |', ' |', ' \\ ', ' - ', '/ '), 0.08), 'runner': (('๐ถ', '๐'), 0.14), 'sand': (('โ ', 'โ ', 'โ ', 'โก', 'โก', 'โก', 'โก ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃ', 'โฃค', 'โฃฅ', 'โฃฆ', 'โฃฎ', 'โฃถ', 'โฃท', 'โฃฟ', 'โกฟ', 'โ ฟ', 'โข', 'โ ', 'โก', 'โ ', 'โ ซ', 'โข', 'โ ', 'โ ', 'โก', 'โ ', 'โ ', 'โ ก', 'โข'), 0.08), 'shark': (('โ|\\____________โ', 'โ_|\\___________โ', 'โ__|\\__________โ', 'โ___|\\_________โ', 'โ____|\\________โ', 'โ_____|\\_______โ', 'โ______|\\______โ', 'โ_______|\\_____โ', 'โ________|\\____โ', 'โ_________|\\___โ', 'โ__________|\\__โ', 'โ___________|\\_โ', 'โ____________|\\โ', 'โ____________/|โ', 'โ___________/|_โ', 'โ__________/|__โ', 'โ_________/|___โ', 'โ________/|____โ', 'โ_______/|_____โ', 'โ______/|______โ', 'โ_____/|_______โ', 'โ____/|________โ', 'โ___/|_________โ', 'โ__/|__________โ', 'โ_/|___________โ', 'โ/|____________โ'), 0.12), 'simple-dots': (('. ', '.. ', '...', ' '), 0.4), 'simple-dots-scrolling': (('. ', '.. ', '...', ' ..', ' .', ' '), 0.2), 'smiley': (('๐', '๐'), 0.2), 'soccer-header': ((' ๐งโฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ๐ง ', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง', '๐ง โฝ๏ธ ๐ง'), 0.08), 'speaker': (('๐', '๐', '๐', '๐'), 0.16), 'square-corners': (('โฐ', 'โณ', 'โฒ', 'โฑ'), 0.18), 'squish': (('โซ', 'โช'), 0.1), 'star': (('โถ', 'โธ', 'โน', 'โบ', 'โน', 'โท'), 0.07), 'star2': (('+', 'x', '*'), 0.08), 'toggle': (('โถ', 'โท'), 0.25), 'toggle10': (('ใ', 'ใ', 'ใ'), 0.1), 'toggle11': (('โง', 'โง'), 0.05), 'toggle12': (('โ', 'โ'), 0.12), 'toggle13': (('=', '*', '-'), 0.08), 'toggle2': (('โซ', 'โช'), 0.08), 'toggle3': (('โก', 'โ '), 0.12), 'toggle4': (('โ ', 'โก', 'โช', 'โซ'), 0.1), 'toggle5': (('โฎ', 'โฏ'), 0.1), 'toggle6': (('แ', 'แ'), 0.3), 'toggle7': (('โฆพ', 'โฆฟ'), 0.08), 'toggle8': (('โ', 'โ'), 0.1), 'toggle9': (('โ', 'โ'), 0.1), 'triangle': (('โข', 'โฃ', 'โค', 'โฅ'), 0.05), 'weather': (('โ๏ธ', 'โ๏ธ', 'โ๏ธ', '๐ค ', 'โ ๏ธ', '๐ฅ ', 'โ๏ธ', '๐ง ', '๐จ ', '๐ง ', '๐จ ', '๐ง ', '๐จ ', 'โ ', '๐จ ', '๐ง ', '๐จ ', 'โ๏ธ', '๐ฅ ', 'โ ๏ธ', '๐ค ', 'โ๏ธ', 'โ๏ธ'), 0.1)}
Named spinner animations ported from cli-spinners, keyed by name.
Each value is a
SpinnerPresetbundling frames and a tuned interval. Select one withSpinnerโsspinnerargument:from click_extra import Spinner, SPINNERS with Spinner("Brewing tea", spinner=SPINNERS["moon"]): ...
Unlike the upstream
\b-based renderers,Spinnerredraws the whole line, so the multi-character animations (bouncingBar,pong,shark, โฆ) render correctly here.