Claude Code for rlcompleter: Python Tab Completion for the REPL — Claude Skills 360 Blog
Blog / AI / Claude Code for rlcompleter: Python Tab Completion for the REPL
AI

Claude Code for rlcompleter: Python Tab Completion for the REPL

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

Python’s rlcompleter module provides a Completer class that hook into readline to complete Python names and attributes at the prompt. import rlcompleter. Completer: comp = rlcompleter.Completer(namespace) — namespace defaults to __main__.__dict__; pass a custom dict to complete from a specific scope. complete: comp.complete("os.pa", 0)"os.path"; comp.complete("os.pa", 1) → next match or None; used by readline as the completer callback. global_matches: comp.global_matches("imp") → list of all names starting with "imp" from namespace + builtins. attr_matches: comp.attr_matches("os.pa") → attribute completions after the dot. readline integration: import readline, rlcompleter; readline.set_completer(rlcompleter.Completer().complete); readline.parse_and_bind("tab: complete") — the canonical sitecustomize.py pattern that Python’s site module activates on python -i. PYTHONSTARTUP: set this env var to a startup script that calls these two lines to enable completion in any interactive session. Claude Code generates custom namespace completers, multi-source completion routers, fuzzy completion wrappers, and context-sensitive REPL enhancers.

CLAUDE.md for rlcompleter

## rlcompleter Stack
- Stdlib: import rlcompleter, readline
- Enable: readline.set_completer(rlcompleter.Completer().complete)
          readline.parse_and_bind("tab: complete")
- Custom: comp = rlcompleter.Completer({"x": 1, "y": 2})
          comp.complete("x", 0)   # "x"
- Attrs:  comp.attr_matches("os.pa")   # attribute matches
- Globals: comp.global_matches("pri")  # global/builtin matches

rlcompleter Tab Completion Pipeline

# app/rlcompleterutil.py — setup, custom namespace, multi-source, fuzzy, history
from __future__ import annotations

import builtins
import keyword
import os
import rlcompleter
import sys
from typing import Any, Callable


# ─────────────────────────────────────────────────────────────────────────────
# 1. Setup helpers
# ─────────────────────────────────────────────────────────────────────────────

def enable_completion(
    namespace: dict[str, Any] | None = None,
    bind: str = "tab: complete",
) -> bool:
    """
    Enable tab completion for the current interactive session via readline.
    Returns True if readline is available and completion was set up.

    namespace: dict to complete from (defaults to calling frame's globals).
    bind: readline key binding (default "tab: complete").

    Example:
        # In ~/.pythonrc or sitecustomize.py:
        from rlcompleterutil import enable_completion
        enable_completion()
    """
    try:
        import readline
    except ImportError:
        return False

    ns = namespace if namespace is not None else sys.modules["__main__"].__dict__
    comp = rlcompleter.Completer(ns)
    readline.set_completer(comp.complete)
    readline.parse_and_bind(bind)
    return True


def enable_completion_with_history(
    history_file: str = os.path.expanduser("~/.python_history"),
    namespace: dict[str, Any] | None = None,
) -> bool:
    """
    Enable tab completion AND persistent history.

    Example:
        enable_completion_with_history("~/.myapp_history")
    """
    try:
        import readline
        import atexit
    except ImportError:
        return False

    if not enable_completion(namespace):
        return False

    hist = os.path.expanduser(history_file)
    try:
        readline.read_history_file(hist)
    except FileNotFoundError:
        pass
    except OSError:
        pass

    readline.set_history_length(2000)
    atexit.register(readline.write_history_file, hist)
    return True


# ─────────────────────────────────────────────────────────────────────────────
# 2. Completer wrappers
# ─────────────────────────────────────────────────────────────────────────────

def all_completions(text: str, namespace: dict[str, Any] | None = None) -> list[str]:
    """
    Return all completion candidates for a text prefix.

    Example:
        all_completions("os.path.j")   # ["os.path.join"]
        all_completions("imp")         # ["import", "importlib", ...]
    """
    comp = rlcompleter.Completer(
        namespace if namespace is not None else sys.modules.get("__main__", object).__dict__
    )
    results: list[str] = []
    i = 0
    while True:
        match = comp.complete(text, i)
        if match is None:
            break
        results.append(match)
        i += 1
    return results


def global_completions(text: str, namespace: dict[str, Any] | None = None) -> list[str]:
    """
    Return completions from globals and builtins only (no attribute completion).

    Example:
        global_completions("pr")   # ["print", "property", ...]
    """
    ns = namespace or {}
    comp = rlcompleter.Completer(ns)
    return comp.global_matches(text)


def attr_completions(expr: str, namespace: dict[str, Any] | None = None) -> list[str]:
    """
    Return attribute completions for an expression ending in a dot prefix.
    expr must contain a dot: e.g. "os.path.j" or "myobj.att".

    Example:
        attr_completions("os.path.j")   # ["os.path.join"]
    """
    comp = rlcompleter.Completer(
        namespace if namespace is not None else sys.modules.get("__main__", object).__dict__
    )
    return comp.attr_matches(expr)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Multi-source completion router
# ─────────────────────────────────────────────────────────────────────────────

class MultiCompleter:
    """
    Chain multiple completion sources. Returns the first non-empty source's
    candidates, or merges all sources.

    Each source is a callable(text) -> list[str].

    Example:
        mc = MultiCompleter()
        mc.add_source(make_keyword_completer())
        mc.add_source(make_namespace_completer({"x": 1, "y": 2}))
        candidates = mc.complete("x", 0)
    """

    def __init__(self, merge: bool = True) -> None:
        self._sources: list[Callable[[str], list[str]]] = []
        self._merge = merge
        self._cache: tuple[str, list[str]] = ("", [])

    def add_source(self, fn: Callable[[str], list[str]]) -> None:
        self._sources.append(fn)

    def _get_matches(self, text: str) -> list[str]:
        if self._cache[0] == text:
            return self._cache[1]
        matches: list[str] = []
        seen: set[str] = set()
        for source in self._sources:
            try:
                for m in source(text):
                    if m not in seen:
                        seen.add(m)
                        matches.append(m)
                if not self._merge and matches:
                    break
            except Exception:
                pass
        self._cache = (text, matches)
        return matches

    def complete(self, text: str, state: int) -> str | None:
        matches = self._get_matches(text)
        return matches[state] if state < len(matches) else None

    def install(self) -> bool:
        """Register this completer with readline."""
        try:
            import readline
            readline.set_completer(self.complete)
            readline.parse_and_bind("tab: complete")
            return True
        except ImportError:
            return False


def make_namespace_completer(
    namespace: dict[str, Any],
) -> Callable[[str], list[str]]:
    """Build a completion source from a namespace dict."""
    comp = rlcompleter.Completer(namespace)
    def _complete(text: str) -> list[str]:
        results: list[str] = []
        i = 0
        while True:
            m = comp.complete(text, i)
            if m is None:
                break
            results.append(m)
            i += 1
        return results
    return _complete


def make_keyword_completer() -> Callable[[str], list[str]]:
    """Build a completion source for Python keywords and soft keywords."""
    kws = sorted(keyword.kwlist + getattr(keyword, "softkwlist", []))
    def _complete(text: str) -> list[str]:
        return [k for k in kws if k.startswith(text)]
    return _complete


def make_path_completer() -> Callable[[str], list[str]]:
    """Build a completion source for filesystem paths."""
    def _complete(text: str) -> list[str]:
        if not text:
            return []
        expanded = os.path.expanduser(text)
        dirname  = os.path.dirname(expanded) or "."
        basename = os.path.basename(expanded)
        try:
            names = os.listdir(dirname)
        except OSError:
            return []
        matches = [
            os.path.join(dirname, n) + ("/" if os.path.isdir(os.path.join(dirname, n)) else "")
            for n in names
            if n.startswith(basename)
        ]
        return sorted(matches)
    return _complete


# ─────────────────────────────────────────────────────────────────────────────
# 4. Fuzzy completer
# ─────────────────────────────────────────────────────────────────────────────

def fuzzy_complete(
    text: str,
    candidates: list[str],
    max_results: int = 20,
) -> list[str]:
    """
    Fuzzy-match text against candidates using subsequence matching.
    Returns results sorted by match quality (prefix matches first).

    Example:
        fuzzy_complete("ptj", ["os.path.join", "os.path.exists", "print"])
        # ["os.path.join", "print"]  (both contain p, t/a, j/i)
    """
    if not text:
        return candidates[:max_results]

    prefix_hits: list[str] = []
    subseq_hits: list[str] = []

    pattern = text.lower()

    for c in candidates:
        cl = c.lower()
        if cl.startswith(pattern):
            prefix_hits.append(c)
        else:
            # subsequence check
            idx = 0
            for ch in pattern:
                pos = cl.find(ch, idx)
                if pos == -1:
                    break
                idx = pos + 1
            else:
                subseq_hits.append(c)

    return (prefix_hits + subseq_hits)[:max_results]


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

if __name__ == "__main__":
    import os as _os

    print("=== rlcompleter demo ===")

    # Build a test namespace
    ns: dict[str, Any] = {
        "os": _os,
        "sys": sys,
        "my_variable": 42,
        "my_function": lambda x: x,
        "my_list": [1, 2, 3],
    }

    # ── all_completions ────────────────────────────────────────────────────────
    print("\n--- all_completions ---")
    for prefix in ["os.pa", "my_", "sys.v", "pri"]:
        matches = all_completions(prefix, ns)
        print(f"  {prefix!r:12s}: {matches[:5]}")

    # ── global_completions ─────────────────────────────────────────────────────
    print("\n--- global_completions ---")
    for prefix in ["my", "os", "pr"]:
        matches = global_completions(prefix, ns)
        print(f"  {prefix!r:8s}: {matches[:6]}")

    # ── attr_completions ───────────────────────────────────────────────────────
    print("\n--- attr_completions ---")
    for expr in ["os.path.j", "sys.std", "os.sep"]:
        matches = attr_completions(expr, ns)
        print(f"  {expr!r:15s}: {matches[:4]}")

    # ── MultiCompleter ─────────────────────────────────────────────────────────
    print("\n--- MultiCompleter ---")
    mc = MultiCompleter(merge=True)
    mc.add_source(make_keyword_completer())
    mc.add_source(make_namespace_completer(ns))
    for prefix in ["my", "imp", "os"]:
        matches: list[str] = []
        i = 0
        while True:
            m = mc.complete(prefix, i)
            if m is None: break
            matches.append(m)
            i += 1
        print(f"  {prefix!r:8s}: {matches[:5]}")

    # ── fuzzy_complete ─────────────────────────────────────────────────────────
    print("\n--- fuzzy_complete ---")
    candidates = [
        "os.path.join", "os.path.exists", "os.path.abspath",
        "print", "property", "my_function", "my_variable",
    ]
    for text in ["ptj", "myf", "osp"]:
        hits = fuzzy_complete(text, candidates)
        print(f"  fuzzy({text!r}): {hits[:4]}")

    # ── keyword completions ────────────────────────────────────────────────────
    print("\n--- keyword completions ---")
    kw_comp = make_keyword_completer()
    for prefix in ["im", "re", "de"]:
        print(f"  {prefix!r}: {kw_comp(prefix)}")

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

For the readline direct API alternative — readline.set_completer() accepts any callable (text, state) -> str | None; you can write a custom completer from scratch without rlcompleter using dir(), vars(), and getattr() — use a custom completer when you need domain-specific completion (e.g. completing SQL keywords, CLI subcommands, or database field names) that is not Python-name-aware; use rlcompleter.Completer as the base layer when you want Python attribute and global-name completion and then layer domain completions on top via MultiCompleter. For the prompt_toolkit alternative — prompt_toolkit (PyPI) provides a full-featured readline replacement with async support, syntax highlighting, multi-line editing, fuzzy history search, and rich word completer APIs including WordCompleter, FuzzyCompleter, and NestedCompleter — use prompt_toolkit for polished user-facing CLI applications where cross-platform consistency, async integration, and rich visual feedback matter; use rlcompleter + readline when you want zero pip dependencies, are enhancing the standard python -i REPL or a code.InteractiveConsole-based shell, or are targeting environments where prompt_toolkit is unavailable. The Claude Skills 360 bundle includes rlcompleter skill sets covering enable_completion()/enable_completion_with_history() readline setup, all_completions()/global_completions()/attr_completions() query helpers, MultiCompleter with make_namespace_completer()/make_keyword_completer()/make_path_completer() completion source factories, and fuzzy_complete() subsequence matcher. Start with the free tier to try tab completion patterns and rlcompleter 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