Claude Code for readline: Python Interactive Line Editing — Claude Skills 360 Blog
Blog / AI / Claude Code for readline: Python Interactive Line Editing
AI

Claude Code for readline: Python Interactive Line Editing

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

Python’s readline module wraps the GNU Readline library to add line editing, persistent history, and tab completion to interactive Python programs. import readline. parse_and_bind: readline.parse_and_bind("tab: complete") — bind Tab to completion; "set editing-mode vi" for vi keys. set_completer: readline.set_completer(fn)fn(text, state) returns the state-th completion or None. get_line_buffer: readline.get_line_buffer() — current typed line during completion. get_begidx/get_endidx: cursor word boundaries for completion context. History: readline.read_history_file("~/.myapp_history") — load; readline.write_history_file("~/.myapp_history") — save. set_history_length: readline.set_history_length(1000) — limit entries (-1 = unlimited). add_history: readline.add_history("prev command") — inject into history. get_history_length / get_current_history_length: history size. get_history_item: readline.get_history_item(1) — 1-indexed. clear_history: readline.clear_history(). set_completion_display_matches_hook: custom display for completions. set_startup_hook / set_pre_input_hook: callbacks before prompt. readline is available on macOS (using libedit, quirks in parse_and_bind syntax) and Linux; not available on Windows. Claude Code generates persistent REPL histories, keyword completers, path completers, and custom readline-enabled shells.

CLAUDE.md for readline

## readline Stack
- Stdlib: import readline
- Bind:   readline.parse_and_bind("tab: complete")
- Complete: readline.set_completer(lambda text, state: matches[state])
- History: readline.read_history_file(hist); readline.write_history_file(hist)
- Limit:  readline.set_history_length(1000)
- Buffer: readline.get_line_buffer()   # current typed text in completion fn

readline Interactive Shell Pipeline

# app/readlineutil.py — history, completion, path, keyword, shell REPL
from __future__ import annotations

import os
import readline
import atexit
from pathlib import Path
from typing import Callable


# ─────────────────────────────────────────────────────────────────────────────
# 1. History management
# ─────────────────────────────────────────────────────────────────────────────

def setup_history(
    history_file: str | Path,
    max_entries: int = 1000,
) -> None:
    """
    Load history from file, register atexit save, and limit history length.
    Call once at application startup.

    Example:
        setup_history("~/.myapp_history")
        while True:
            line = input(">>> ")
    """
    hist_path = Path(history_file).expanduser()
    if hist_path.exists():
        try:
            readline.read_history_file(str(hist_path))
        except OSError:
            pass
    readline.set_history_length(max_entries)
    atexit.register(readline.write_history_file, str(hist_path))


def save_history(history_file: str | Path) -> None:
    """Manually save history to a file."""
    readline.write_history_file(str(Path(history_file).expanduser()))


def get_history() -> list[str]:
    """Return all history entries as a list of strings."""
    n = readline.get_current_history_length()
    return [readline.get_history_item(i + 1) for i in range(n)]


def clear_history() -> None:
    """Clear all history entries."""
    readline.clear_history()


def inject_history(entries: list[str]) -> None:
    """
    Inject a list of strings into readline history.

    Example:
        inject_history(["help", "list", "quit"])
    """
    for entry in entries:
        readline.add_history(entry)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Completion helpers
# ─────────────────────────────────────────────────────────────────────────────

def enable_tab_completion() -> None:
    """Enable Tab key for completion (must call set_completer first)."""
    readline.parse_and_bind("tab: complete")


def set_completer(fn: Callable[[str, int], str | None]) -> None:
    """
    Register a completion function fn(text, state) → str | None.
    Tab calls fn repeatedly with state=0,1,2,... until None is returned.

    Example:
        set_completer(keyword_completer(["help", "list", "quit"]))
        enable_tab_completion()
    """
    readline.set_completer(fn)


def get_line_buffer() -> str:
    """Return the current readline input buffer (usable inside completers)."""
    return readline.get_line_buffer()


# ─────────────────────────────────────────────────────────────────────────────
# 3. Completer factories
# ─────────────────────────────────────────────────────────────────────────────

def keyword_completer(keywords: list[str]) -> Callable[[str, int], str | None]:
    """
    Create a completer that matches against a fixed keyword list.

    Example:
        set_completer(keyword_completer(["help", "list", "add", "remove", "quit"]))
        enable_tab_completion()
    """
    def _complete(text: str, state: int) -> str | None:
        matches = [k for k in keywords if k.startswith(text)]
        return matches[state] if state < len(matches) else None
    return _complete


def path_completer(only_dirs: bool = False) -> Callable[[str, int], str | None]:
    """
    Create a completer that expands filesystem paths.

    Example:
        set_completer(path_completer())
        enable_tab_completion()
        # Tab-complete file paths at the prompt
    """
    _matches: list[str] = []

    def _complete(text: str, state: int) -> str | None:
        nonlocal _matches
        if state == 0:
            expanded = os.path.expanduser(text)
            if os.path.isdir(expanded):
                prefix = expanded if expanded.endswith(os.sep) else expanded + os.sep
                raw_text_prefix = text if text.endswith(os.sep) else text + os.sep
            else:
                prefix = os.path.dirname(expanded) or "."
                raw_text_prefix = os.path.dirname(text) + os.sep if os.path.dirname(text) else ""
            try:
                entries = os.listdir(prefix)
            except OSError:
                entries = []
            base = os.path.basename(expanded)
            results: list[str] = []
            for entry in entries:
                if entry.startswith(base):
                    full = os.path.join(prefix, entry)
                    suffix = os.sep if os.path.isdir(full) else ""
                    if not only_dirs or os.path.isdir(full):
                        results.append(raw_text_prefix + entry + suffix)
            _matches = sorted(results)
        return _matches[state] if state < len(_matches) else None

    return _complete


def dict_completer(
    data: dict,
    separator: str = ".",
) -> Callable[[str, int], str | None]:
    """
    Create a completer that navigates a nested dict by separator-delimited keys.
    Useful for config key completion.

    Example:
        cfg = {"server": {"host": "localhost", "port": 8080}, "debug": True}
        set_completer(dict_completer(cfg))
        enable_tab_completion()
        # "server." → completes to "server.host", "server.port"
    """
    def _all_keys(obj: dict, prefix: str) -> list[str]:
        keys: list[str] = []
        for k, v in obj.items():
            full = prefix + k if prefix else k
            keys.append(full)
            if isinstance(v, dict):
                keys.extend(_all_keys(v, full + separator))
        return keys

    all_keys = _all_keys(data, "")

    def _complete(text: str, state: int) -> str | None:
        matches = [k for k in all_keys if k.startswith(text)]
        return matches[state] if state < len(matches) else None

    return _complete


# ─────────────────────────────────────────────────────────────────────────────
# 4. Composing completers
# ─────────────────────────────────────────────────────────────────────────────

def dispatch_completer(
    command_completers: dict[str, Callable[[str, int], str | None]],
    commands: list[str],
) -> Callable[[str, int], str | None]:
    """
    A top-level completer that:
    - completes the first word from commands
    - dispatches to a per-command completer for arguments

    Example:
        cmpl = dispatch_completer(
            {"open": path_completer(), "set": dict_completer(config)},
            commands=["help", "open", "set", "quit"],
        )
        set_completer(cmpl); enable_tab_completion()
    """
    _cache: list[str] = []

    def _complete(text: str, state: int) -> str | None:
        nonlocal _cache
        if state == 0:
            buf = readline.get_line_buffer()
            parts = buf.lstrip().split()
            # Completing the first word
            if len(parts) == 0 or (len(parts) == 1 and not buf.endswith(" ")):
                _cache = [c for c in commands if c.startswith(text)]
            else:
                cmd = parts[0]
                sub = command_completers.get(cmd)
                if sub is not None:
                    _cache = []
                    i = 0
                    while True:
                        m = sub(text, i)
                        if m is None:
                            break
                        _cache.append(m)
                        i += 1
                else:
                    _cache = []
        return _cache[state] if state < len(_cache) else None

    return _complete


# ─────────────────────────────────────────────────────────────────────────────
# 5. Simple REPL harness
# ─────────────────────────────────────────────────────────────────────────────

def simple_repl(
    prompt: str = ">>> ",
    handler: Callable[[str], str | None] | None = None,
    history_file: str | Path | None = None,
    completer: Callable | None = None,
    banner: str = "",
) -> None:
    """
    Run a simple readline-enabled REPL.
    handler(line) → response string or None; return "EXIT" to quit.

    Example:
        def handle(line):
            if line.lower() == "quit": return "EXIT"
            return f"You typed: {line!r}"

        simple_repl(
            prompt="myapp> ",
            handler=handle,
            history_file="~/.myapp_history",
        )
    """
    if history_file:
        setup_history(history_file)
    if completer:
        set_completer(completer)
        enable_tab_completion()
    if banner:
        print(banner)
    while True:
        try:
            line = input(prompt)
        except (EOFError, KeyboardInterrupt):
            print()
            break
        line = line.strip()
        if not line:
            continue
        if handler:
            result = handler(line)
            if result == "EXIT":
                break
            if result is not None:
                print(result)


# ─────────────────────────────────────────────────────────────────────────────
# Demo (non-interactive — demonstrates the API without blocking)
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import tempfile

    print("=== readline demo ===")

    # ── inject and inspect history ────────────────────────────────────────────
    print("\n--- history inject / get ---")
    clear_history()
    inject_history(["help", "list all", "add item", "quit"])
    hist = get_history()
    print(f"  history ({len(hist)} entries): {hist}")

    # ── keyword completer ─────────────────────────────────────────────────────
    print("\n--- keyword_completer ---")
    cmpl = keyword_completer(["add", "remove", "list", "help", "quit"])
    matches = [cmpl("l", i) for i in range(5) if cmpl("l", i) is not None]
    print(f"  completions for 'l': {matches}")

    # ── dict completer ────────────────────────────────────────────────────────
    print("\n--- dict_completer ---")
    cfg = {
        "server": {"host": "localhost", "port": 8080},
        "database": {"url": "sqlite:///app.db"},
        "debug": True,
    }
    dc = dict_completer(cfg)
    for prefix in ("s", "server.", "d"):
        matches = [dc(prefix, i) for i in range(10) if dc(prefix, i) is not None]
        print(f"  '{prefix}' → {matches}")

    # ── path completer smoke test ──────────────────────────────────────────────
    print("\n--- path_completer ---")
    pc = path_completer()
    # complete "/" → should find /tmp etc.
    m = pc("/tm", 0)
    print(f"  path_completer('/tm', 0) → {m!r}")

    # ── history file round-trip ────────────────────────────────────────────────
    print("\n--- history file ---")
    with tempfile.TemporaryDirectory() as tmpdir:
        hfile = Path(tmpdir) / "hist.txt"
        clear_history()
        inject_history(["cmd1", "cmd2", "cmd3"])
        save_history(hfile)
        print(f"  saved to {hfile.name} ({hfile.stat().st_size} bytes)")
        clear_history()
        setup_history(hfile)
        print(f"  reloaded: {get_history()}")

    print("\n=== done ===")

For the prompt_toolkit alternative — prompt_toolkit (PyPI) provides a full-featured readline replacement with multi-line editing, syntax highlighting, fuzzy completion, async support, and cross-platform Windows support (which readline lacks) — use prompt_toolkit for polished interactive applications, modern TUI shells, or when you need Windows compatibility; use readline when you want a lightweight stdlib-only solution for Unix command-line tools where GNU Readline is already installed, or when you’re adding basic history and Tab completion to an existing input()-based CLI without adding dependencies. For the cmd alternative — cmd.Cmd (stdlib) is a higher-level framework that wraps readline internally and provides do_* method dispatch, built-in help system, and complete_* method hooks for per-command Tab completion — use cmd.Cmd when building a structured multi-command shell with a do_help/do_list method pattern; use readline directly when you need custom completion logic, history management, or integration with a non-cmd.Cmd-based input loop. The Claude Skills 360 bundle includes readline skill sets covering setup_history()/save_history()/get_history()/clear_history()/inject_history() history management, enable_tab_completion()/set_completer() setup, keyword_completer()/path_completer()/dict_completer() completion factories, dispatch_completer() multi-command router, and simple_repl() complete REPL harness. Start with the free tier to try interactive shell patterns and readline pipeline 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