Claude Code for yaspin: Python Terminal Spinner — Claude Skills 360 Blog
Blog / AI / Claude Code for yaspin: Python Terminal Spinner
AI

Claude Code for yaspin: Python Terminal Spinner

Published: March 22, 2028
Read time: 5 min read
By: Claude Skills 360

yaspin renders animated terminal spinners with 100+ built-in styles. pip install yaspin. Basic: from yaspin import yaspin; with yaspin(text="Loading") as sp: time.sleep(2). Custom spinner: from yaspin.spinners import Spinners; with yaspin(Spinners.earth) as sp: .... Text: sp.text = "Step 2". Color: with yaspin(color="green") as sp:. On-color bg: yaspin(on_color="on_white"). Attrs: yaspin(attrs=["bold"]). Side: yaspin(text="Loading", side="right") — spinner on right. Outcome: sp.ok("✔"). sp.fail("✗"). sp.write("log line") — print without breaking spinner. Timer: yaspin(timer=True) — shows elapsed time. Hidden: with sp.hidden(): print("clean output"). Signal map: from yaspin.core import SIGMAP; yaspin(sigmap={signal.SIGTERM: sp.fail}). Spinner object: from yaspin.spinners import Spinners; Spinners.dots | Spinners.arc | Spinners.clock | Spinners.earth | Spinners.moon | Spinners.pong | Spinners.bouncingBar | Spinners.shark. Custom: sp.spinner = {"interval":100,"frames":["⠋","⠙","⠹","⠸","⠼"]}. Color choices: blue, green, red, yellow, cyan, magenta, white. on_color: on_blue, on_green, on_red, on_yellow. Attrs: bold, dark, underline, blink, reverse, concealed. Nested: combine with indent via custom prefix. TTY: sp = yaspin(); sp.enabled = sys.stdout.isatty(). Claude Code generates yaspin spinners for deploy scripts, import jobs, and long-running CLI tools.

CLAUDE.md for yaspin

## yaspin Stack
- Version: yaspin >= 3.0 | pip install yaspin
- Basic: with yaspin(text="Loading", color="cyan") as sp: do_work(); sp.ok("✔")
- Spinners: Spinners.dots | .earth | .moon | .arc | .pong | .shark — 100+ in Spinners enum
- Outcomes: sp.ok(text) | sp.fail(text) — mark done; writes final line
- Live print: sp.write("message") — prints line without breaking animation
- Timer: yaspin(timer=True) — appends elapsed time to spinner text
- Hidden: with sp.hidden(): print(...) — suppress spinner for clean output

yaspin Terminal Spinner Pipeline

# app/spinner.py — yaspin spinner utilities for CLI tools and long-running tasks
from __future__ import annotations

import signal
import subprocess
import sys
import time
from contextlib import contextmanager
from typing import Any, Callable, Iterator, TypeVar

from yaspin import yaspin
from yaspin.spinners import Spinners

T = TypeVar("T")


# ─────────────────────────────────────────────────────────────────────────────
# 1. Spinner factory
# ─────────────────────────────────────────────────────────────────────────────

def make_spinner(
    text: str = "Working",
    spinner=Spinners.dots,
    color: str = "cyan",
    timer: bool = False,
    side: str = "left",
    enabled: bool | None = None,
):
    """
    Create a yaspin spinner.
    enabled: None → auto-detect TTY; True/False → force on/off.
    """
    sp = yaspin(spinner=spinner, text=text, color=color, timer=timer, side=side)
    if enabled is not None:
        sp.enabled = enabled
    elif not sys.stderr.isatty():
        sp.enabled = False
    return sp


@contextmanager
def spin(
    text: str = "Working",
    spinner=Spinners.dots,
    color: str = "cyan",
    ok_text: str = "✔",
    fail_text: str = "✗",
    timer: bool = False,
) -> Iterator[Any]:
    """
    Context manager: auto-calls sp.ok() on success, sp.fail() on exception.

    Usage:
        with spin("Fetching data") as sp:
            data = requests.get(url).json()
            sp.write(f"  Got {len(data)} records")
        # → prints "✔ Fetching data"
    """
    sp = make_spinner(text, spinner=spinner, color=color, timer=timer)
    sp.start()
    try:
        yield sp
        sp.ok(ok_text)
    except Exception as exc:
        sp.fail(f"{fail_text} {exc}")
        raise


# ─────────────────────────────────────────────────────────────────────────────
# 2. Step runner
# ─────────────────────────────────────────────────────────────────────────────

def run_steps(
    steps: list[tuple[str, Callable[[], Any]]],
    spinner=Spinners.dots,
    color: str = "cyan",
    stop_on_error: bool = True,
    ok_symbol: str = "✔",
    fail_symbol: str = "✗",
) -> list[dict[str, Any]]:
    """
    Run labeled steps with a spinner each.
    Returns list of {"step", "ok", "result"|"error"} dicts.
    """
    results = []
    for label, fn in steps:
        sp = make_spinner(label, spinner=spinner, color=color)
        sp.start()
        try:
            result = fn()
            sp.ok(ok_symbol)
            results.append({"step": label, "ok": True, "result": result})
        except Exception as exc:
            sp.fail(fail_symbol)
            results.append({"step": label, "ok": False, "error": str(exc)})
            if stop_on_error:
                break
    return results


def step(label: str, fn: Callable[[], T], spinner=Spinners.dots, color: str = "cyan") -> T:
    """Run a single labeled function with a spinner. Returns fn's return value."""
    with spin(label, spinner=spinner, color=color) as sp:
        result = fn()
    return result


# ─────────────────────────────────────────────────────────────────────────────
# 3. Live-logging spinner
# ─────────────────────────────────────────────────────────────────────────────

class LoggingSpinner:
    """
    Spinner that supports sp.log(msg) — prints lines while animation runs.

    Usage:
        with LoggingSpinner("Processing files") as sp:
            for path in paths:
                process(path)
                sp.log(f"  Processed: {path}")
    """

    def __init__(
        self,
        text: str,
        spinner=Spinners.dots,
        color: str = "cyan",
        timer: bool = True,
    ):
        self._sp = make_spinner(text, spinner=spinner, color=color, timer=timer)
        self._text = text

    def __enter__(self) -> "LoggingSpinner":
        self._sp.start()
        return self

    def log(self, message: str) -> None:
        """Print a line without interrupting the spinner."""
        self._sp.write(message)

    def update(self, text: str) -> None:
        """Update the spinner label."""
        self._sp.text = text

    def ok(self, text: str | None = None) -> None:
        self._sp.ok(f"✔ {text or self._text}")

    def fail(self, text: str | None = None) -> None:
        self._sp.fail(f"✗ {text or self._text}")

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            self._sp.fail(f"✗ {self._text}")
        else:
            self._sp.ok(f"✔ {self._text}")
        return False


# ─────────────────────────────────────────────────────────────────────────────
# 4. Subprocess helpers
# ─────────────────────────────────────────────────────────────────────────────

def run_command(
    cmd: list[str],
    text: str | None = None,
    check: bool = True,
    capture_output: bool = True,
    spinner=Spinners.dots,
) -> subprocess.CompletedProcess:
    """Run a command with a yaspin spinner."""
    label = text or " ".join(cmd)
    sp = make_spinner(label, spinner=spinner)
    sp.start()
    try:
        result = subprocess.run(cmd, check=check, capture_output=capture_output, text=True)
        sp.ok("✔")
        return result
    except subprocess.CalledProcessError as exc:
        sp.fail("✗")
        raise


def run_pipeline(
    commands: list[tuple[str, list[str]]],
    stop_on_error: bool = True,
) -> bool:
    """Run a list of (label, command) pairs with spinners. Returns True if all pass."""
    for label, cmd in commands:
        sp = make_spinner(label)
        sp.start()
        try:
            subprocess.run(cmd, check=True, capture_output=True, text=True)
            sp.ok("✔")
        except subprocess.CalledProcessError:
            sp.fail("✗")
            if stop_on_error:
                return False
    return True


# ─────────────────────────────────────────────────────────────────────────────
# 5. Timer spinner (shows elapsed)
# ─────────────────────────────────────────────────────────────────────────────

@contextmanager
def timed_spin(text: str, spinner=Spinners.arc, color: str = "yellow") -> Iterator[Any]:
    """
    Spinner with elapsed time display.
    Uses yaspin(timer=True) — appends elapsed seconds automatically.
    """
    sp = make_spinner(text, spinner=spinner, color=color, timer=True)
    sp.start()
    try:
        yield sp
        sp.ok("✔")
    except Exception as exc:
        sp.fail(f"✗ {exc}")
        raise


# ─────────────────────────────────────────────────────────────────────────────
# 6. Signal-safe spinner
# ─────────────────────────────────────────────────────────────────────────────

@contextmanager
def interruptible_spin(text: str) -> Iterator[Any]:
    """
    Spinner that handles SIGTERM/SIGINT gracefully.
    Calls sp.fail() with a message when interrupted.
    """
    sp = make_spinner(text)

    def _handle_sigterm(signum, frame):
        sp.fail("✗ Terminated")
        sys.exit(1)

    old_sigterm = signal.getsignal(signal.SIGTERM)
    signal.signal(signal.SIGTERM, _handle_sigterm)

    sp.start()
    try:
        yield sp
        sp.ok("✔")
    except KeyboardInterrupt:
        sp.fail("✗ Interrupted")
        raise
    finally:
        signal.signal(signal.SIGTERM, old_sigterm)


# ─────────────────────────────────────────────────────────────────────────────
# 7. Spinner catalog
# ─────────────────────────────────────────────────────────────────────────────

SPINNER_CATALOG: dict[str, Any] = {
    "dots":    Spinners.dots,
    "arc":     Spinners.arc,
    "clock":   Spinners.clock,
    "earth":   Spinners.earth,
    "moon":    Spinners.moon,
    "pong":    Spinners.pong,
    "bounce":  Spinners.bouncingBall,
    "line":    Spinners.line,
    "shark":   Spinners.shark,
    "hearts":  Spinners.hearts,
}


def demo_styles(duration: float = 0.6) -> None:
    """Demo each spinner style briefly."""
    for name, style in SPINNER_CATALOG.items():
        sp = make_spinner(f"Style: {name}", spinner=style)
        sp.start()
        time.sleep(duration)
        sp.ok("✔")


# ─────────────────────────────────────────────────────────────────────────────
# 8. Loguru integration
# ─────────────────────────────────────────────────────────────────────────────

def with_loguru_spinner(text: str, spinner=Spinners.dots):
    """
    Decorator: wraps a function with a yaspin spinner, routing logger output
    through sp.write() so log lines don't break the animation.

    Usage:
        @with_loguru_spinner("Importing data")
        def import_data(path):
            logger.info("Reading {}", path)
            ...
    """
    try:
        from loguru import logger as _logger
    except ImportError:
        _logger = None

    import functools

    def decorator(fn: Callable) -> Callable:
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            sp = make_spinner(text, spinner=spinner)
            sp.start()
            try:
                if _logger:
                    handler_id = _logger.add(lambda msg: sp.write(msg.rstrip()))
                result = fn(*args, **kwargs)
                sp.ok("✔")
                return result
            except Exception as exc:
                sp.fail(f"✗ {exc}")
                raise
            finally:
                if _logger and handler_id is not None:
                    _logger.remove(handler_id)
        return wrapper
    return decorator


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== Basic spin context manager ===")
    with spin("Loading configuration", ok_text="✔") as sp:
        time.sleep(0.4)
        sp.write("  Config: env=production")

    print("\n=== Timed spinner ===")
    with timed_spin("Running database migration") as sp:
        time.sleep(0.5)

    print("\n=== Logging spinner ===")
    with LoggingSpinner("Processing records", timer=True) as sp:
        for i in range(3):
            time.sleep(0.2)
            sp.log(f"  Record {i+1} processed")
        sp.update("Finalising...")
        time.sleep(0.2)

    print("\n=== Step runner ===")
    results = run_steps([
        ("Validating input",  lambda: time.sleep(0.2)),
        ("Fetching upstream", lambda: time.sleep(0.3)),
        ("Writing output",    lambda: time.sleep(0.2)),
    ])
    for r in results:
        print(f"  {r['step']}: {'OK' if r['ok'] else 'FAIL'}")

    print("\n=== Spinner styles ===")
    for name, style in list(SPINNER_CATALOG.items())[:4]:
        sp = make_spinner(f"Style: {name}", spinner=style)
        sp.start()
        time.sleep(0.4)
        sp.ok("✔")

    print("\n=== Side=right spinner ===")
    sp = make_spinner("Loading", spinner=Spinners.arc, side="right")
    sp.start()
    time.sleep(0.4)
    sp.ok("✔")

For the halo alternative — Halo outputs to stderr by default whereas yaspin defaults to stdout and targets stderr only when explicitly configured; yaspin exposes yaspin(timer=True) for elapsed time display that Halo lacks, and yaspin’s spinners.Spinners enum gives IDE auto-completion across 100+ styles; both are solid choices with very similar APIs, choose Halo when you prefer its slightly simpler sp.succeed()/sp.fail()/sp.warn()/sp.info() outcome methods (yaspin only has ok()/fail()). For the rich.status alternative — rich.Status renders with rich markup, supports live-updating layouts with rich.live.Live, and integrates with Tables and Panels in the same render cycle; yaspin is a standalone 30 KB library with zero heavy dependencies, which is preferable when you don’t need the full rich ecosystem and want a spinner that works reliably in any POSIX terminal. The Claude Skills 360 bundle includes yaspin skill sets covering yaspin() constructor with spinner/color/timer/side, ok()/fail() outcomes, sp.write() non-breaking log lines, Spinners enum catalog, make_spinner() factory, spin() context manager, timed_spin() elapsed timer, LoggingSpinner with live logging, run_steps() pipeline, run_command()/run_pipeline() subprocess wrappers, interruptible_spin() SIGTERM handler, and with_loguru_spinner() decorator. Start with the free tier to try terminal spinner animation code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free