Claude Code for IceCream: Better Print Debugging in Python — Claude Skills 360 Blog
Blog / AI / Claude Code for IceCream: Better Print Debugging in Python
AI

Claude Code for IceCream: Better Print Debugging in Python

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

IceCream is a print debugging library that prints expression names alongside values. pip install icecream. Basic: from icecream import ic; ic(variable) → prints ic| variable: value. No return: ic() → prints filename and line. Pass-through: y = ic(expensive_fn(x)) — prints result AND assigns. Multi-arg: ic(a, b, c). Inline: result = func(ic(arg)). Context: from icecream import ic; ic.includeContext = True — shows file:line. Prefix: ic.prefix = "debug| ". Format: ic.configureOutput(prefix=">> ", includeContext=True). Disable: ic.disable(). Re-enable: ic.enable(). Install globally: ic.install() — makes ic a builtin. Uninstall: ic.uninstall(). stderr: default output to stderr. Redirect: ic.configureOutput(outputFunction=print). To string: from icecream import ic; ic.configureOutput(outputFunction=lambda s: None); result = ic.format(value). Custom serializer: ic.argToStringFunction = lambda x: f"{type(x).__name__}({x!r})". Logging: import logging; ic.configureOutput(outputFunction=logging.debug). Suppress in production: if not DEBUG: ic.disable(). Class/method context: works inside classes and closures. List comprehension: [ic(x) for x in items] — prints each. Generator: (ic(x) for x in items). Deep repr: works with dataclasses, Pydantic models, numpy arrays. Claude Code generates IceCream debug setups, logging integrations, and development workflow helpers.

CLAUDE.md for IceCream

## IceCream Stack
- Version: icecream >= 2.1 | pip install icecream
- Basic: ic(var) → "ic| var: value" to stderr | ic() → file + line number
- Passthrough: y = ic(expr) — prints AND returns expr for inline use
- Config: ic.configureOutput(prefix="app| ", includeContext=True)
- Disable: ic.disable() for production | ic.enable() to restore
- Custom: ic.argToStringFunction = lambda x: repr(x) | outputFunction=logging.debug

IceCream Debug Pipeline

# app/debug.py — icecream ic setup, context, logging integration, conditional debug, helpers
from __future__ import annotations

import logging
import os
import sys
from contextlib import contextmanager
from typing import Any, Callable, Generator, TypeVar

from icecream import IceCreamDebugger, ic


T = TypeVar("T")

log = logging.getLogger(__name__)


# ─────────────────────────────────────────────────────────────────────────────
# 1. Configuration helpers
# ─────────────────────────────────────────────────────────────────────────────

def configure_ic(
    prefix: str = "ic| ",
    include_context: bool = False,
    output_fn: Callable | None = None,
    arg_to_string: Callable | None = None,
) -> None:
    """
    Configure the global ic() instance.

    Example:
        configure_ic(prefix="debug: ", include_context=True)
        configure_ic(output_fn=logging.debug)  # send to logging
    """
    ic.configureOutput(
        prefix=prefix,
        includeContext=include_context,
        outputFunction=output_fn or (lambda s: print(s, file=sys.stderr)),
        argToStringFunction=arg_to_string or repr,
    )


def configure_for_logging(
    logger: logging.Logger | None = None,
    level: int = logging.DEBUG,
    include_context: bool = True,
    prefix: str = "",
) -> None:
    """
    Route ic() output to Python logging.

    Example:
        configure_for_logging()
        ic(some_var)  # goes to DEBUG log instead of stderr
    """
    target = logger or log

    def log_output(s: str) -> None:
        target.log(level, s.rstrip())

    ic.configureOutput(
        prefix=prefix,
        includeContext=include_context,
        outputFunction=log_output,
    )


def configure_for_production() -> None:
    """Disable ic() — call at startup in non-dev environments."""
    ic.disable()


def configure_for_development(include_context: bool = True) -> None:
    """Enable ic() with full context — call at startup in dev."""
    ic.enable()
    ic.configureOutput(
        prefix="ic| ",
        includeContext=include_context,
        outputFunction=lambda s: print(s, file=sys.stderr),
    )


def auto_configure(debug_env_var: str = "DEBUG") -> None:
    """
    Enable ic() if DEBUG env var is set, disable otherwise.

    Example:
        auto_configure()        # reads os.environ["DEBUG"]
        auto_configure("APP_DEBUG")
    """
    if os.getenv(debug_env_var, "").lower() in ("1", "true", "yes"):
        configure_for_development()
    else:
        configure_for_production()


# ─────────────────────────────────────────────────────────────────────────────
# 2. Scoped / contextual ic instances
# ─────────────────────────────────────────────────────────────────────────────

def make_ic(
    prefix: str,
    include_context: bool = True,
    enabled: bool = True,
) -> IceCreamDebugger:
    """
    Create a named ic() instance with a custom prefix.
    Useful for distinguishing output from different modules.

    Example:
        db_ic  = make_ic("db| ")
        api_ic = make_ic("api| ")
        db_ic(query)   # "db| query: SELECT ..."
        api_ic(status) # "api| status: 200"
    """
    debugger = IceCreamDebugger(prefix=prefix)
    debugger.configureOutput(prefix=prefix, includeContext=include_context)
    if not enabled:
        debugger.disable()
    return debugger


@contextmanager
def ic_context(
    prefix: str | None = None,
    include_context: bool = True,
    enabled: bool = True,
) -> Generator[IceCreamDebugger, None, None]:
    """
    Temporary ic() with different settings inside a with block.
    Restores previous settings on exit.

    Example:
        with ic_context(prefix="loop| ") as d:
            for i in range(5):
                d(i)
    """
    debugger = IceCreamDebugger()
    debugger.configureOutput(
        prefix=prefix or "ic| ",
        includeContext=include_context,
    )
    if not enabled:
        debugger.disable()
    yield debugger


@contextmanager
def ic_capture() -> Generator[list[str], None, None]:
    """
    Capture ic() output as a list of strings instead of printing.

    Example:
        with ic_capture() as lines:
            ic(x, y)
        for line in lines:
            do_something(line)
    """
    captured: list[str] = []

    original_fn = None
    try:
        # Save current output function via reconfiguring
        captured_fn = lambda s: captured.append(s.rstrip())
        ic.configureOutput(outputFunction=captured_fn)
        yield captured
    finally:
        # Restore defaults
        ic.configureOutput(outputFunction=lambda s: print(s, file=sys.stderr))


# ─────────────────────────────────────────────────────────────────────────────
# 3. Passthrough helpers
# ─────────────────────────────────────────────────────────────────────────────

def ic_return(value: T, label: str | None = None) -> T:
    """
    Debug-print a value with optional label and return it unchanged.
    Drop-in for inline ic() with an explicit name.

    Example:
        result = fetch_user(uid) |> ic_return("user") |> enrich_user
        result = ic_return(expensive_compute(x))
    """
    if label:
        print(f"ic| {label}: {repr(value)}", file=sys.stderr)
    else:
        ic(value)
    return value


def trace(fn: Callable) -> Callable:
    """
    Decorator: ic() the function's args and return value.

    Example:
        @trace
        def add(a, b):
            return a + b

        add(3, 4)
        # ic| add args: (3, 4), {} → 7
    """
    from functools import wraps

    @wraps(fn)
    def wrapper(*args, **kwargs):
        result = fn(*args, **kwargs)
        label = fn.__qualname__
        print(
            f"ic| {label}({', '.join(repr(a) for a in args)}"
            + (f", {kwargs!r}" if kwargs else "") + f") → {result!r}",
            file=sys.stderr,
        )
        return result
    return wrapper


def ic_list(items: list[T], label: str = "item") -> list[T]:
    """
    ic() each item in a list and return the list unchanged.

    Example:
        processed = ic_list(user_ids, label="user_id")
    """
    for i, item in enumerate(items):
        print(f"ic| {label}[{i}]: {item!r}", file=sys.stderr)
    return items


def ic_dict(d: dict, label: str = "dict") -> dict:
    """
    ic() each key-value pair and return the dict unchanged.

    Example:
        config = ic_dict(load_config(), label="config")
    """
    for k, v in d.items():
        print(f"ic| {label}[{k!r}]: {v!r}", file=sys.stderr)
    return d


# ─────────────────────────────────────────────────────────────────────────────
# 4. Custom serializers
# ─────────────────────────────────────────────────────────────────────────────

def rich_arg_to_string(obj: Any) -> str:
    """
    Custom argToStringFunction that shows type + truncated repr.
    Useful for large objects (DataFrames, tensors).

    Example:
        ic.argToStringFunction = rich_arg_to_string
        ic(my_dataframe)  # "DataFrame(shape=(100, 5), dtype=...)"
    """
    type_name = type(obj).__name__

    # NumPy arrays
    try:
        import numpy as np
        if isinstance(obj, np.ndarray):
            return f"ndarray(shape={obj.shape}, dtype={obj.dtype})"
    except ImportError:
        pass

    # DataFrames
    try:
        import pandas as pd
        if isinstance(obj, pd.DataFrame):
            return f"DataFrame(shape={obj.shape}, cols={list(obj.columns)[:5]})"
        if isinstance(obj, pd.Series):
            return f"Series(len={len(obj)}, dtype={obj.dtype})"
    except ImportError:
        pass

    # Long strings
    r = repr(obj)
    if len(r) > 200:
        return r[:200] + f"... <{len(r)} chars, {type_name}>"

    return r


def use_rich_serializer() -> None:
    """Switch ic() to use rich_arg_to_string for large-object printing."""
    ic.argToStringFunction = rich_arg_to_string


# ─────────────────────────────────────────────────────────────────────────────
# 5. Production guard pattern
# ─────────────────────────────────────────────────────────────────────────────

class DebugSession:
    """
    Context manager for a temporary debug session.
    Enables ic(), runs code, then disables ic() again.

    Example:
        with DebugSession(prefix="bug: "):
            result = complex_pipeline(data)
    """

    def __init__(
        self,
        prefix: str = "ic| ",
        include_context: bool = True,
    ) -> None:
        self.prefix  = prefix
        self.context = include_context

    def __enter__(self) -> "DebugSession":
        ic.enable()
        ic.configureOutput(
            prefix=self.prefix,
            includeContext=self.context,
        )
        return self

    def __exit__(self, *_) -> None:
        ic.disable()

    def __call__(self, *args) -> Any:
        return ic(*args)


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

if __name__ == "__main__":
    # Basic usage
    configure_for_development()

    x = 42
    name = "Claude"
    data = {"key": "value", "nums": [1, 2, 3]}

    print("=== Basic ic() ===")
    ic(x)
    ic(name, x)
    ic(1 + 1)               # expression, not variable name
    ic()                    # print file + line

    print("\n=== Passthrough ===")
    result = ic(x * 2)      # prints AND assigns
    print(f"  result = {result}")

    print("\n=== Named instance ===")
    api = make_ic("api| ")
    api({"status": 200, "body": "ok"})

    print("\n=== Trace decorator ===")

    @trace
    def multiply(a: int, b: int) -> int:
        return a * b

    multiply(6, 7)

    print("\n=== ic_capture ===")
    with ic_capture() as lines:
        ic(x)
        ic(name)
    print(f"  Captured {len(lines)} line(s):")
    for line in lines:
        print(f"    {line}")

    print("\n=== DebugSession ===")
    ic.disable()  # disable globally first
    try:
        ic(x)   # silent — disabled
    except Exception:
        pass

    with DebugSession(prefix="session| "):
        ic(x)   # prints inside context

    print("  (ic disabled again after session)")

    print("\n=== Custom serializer ===")
    use_rich_serializer()
    ic("a" * 250)   # truncated repr

    print("\nDone.")

For the print() / pprint alternative — bare print() requires you to type the variable name twice (print("x:", x)) and doesn’t include context (file/line); ic(x) automatically names the expression, prints ic| x: 42, and optionally shows the filename and line number — use print() for production logging/output, ic() as a drop-in replacement during interactive debugging that adds zero boilerplate. For the logging alternative — Python’s logging module is the right tool for structured, level-gated, destination-flexible production logging; IceCream is a development-time inspection tool designed to be added and removed quickly — use logging for anything you want to see in CI/production, ic() when you’re actively stepping through logic and want to inspect intermediate values with minimal typing. The Claude Skills 360 bundle includes IceCream skill sets covering configure_ic()/configure_for_logging()/auto_configure() setup, configure_for_production()/configure_for_development(), make_ic() named instances, ic_context() scoped config, ic_capture() output capture, ic_return()/trace()/ic_list()/ic_dict() passthrough helpers, rich_arg_to_string() for DataFrames/arrays, use_rich_serializer(), and DebugSession context manager. Start with the free tier to try developer debugging and print inspection 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