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.