Python’s code module supplies the classes behind python -i — interactive console and interpreter utilities for embedding a REPL in your application. import code. Quick REPL: code.interact(banner=None, local=None, exitmsg=None) — starts a read-eval-print loop in the current process; local dict becomes the interpreter namespace. Console class: c = code.InteractiveConsole(locals=None) — manages a read-eval-print session; c.push(line) → bool (True = more input needed); c.runsource(source, filename="<input>", symbol="single") — compile + run a source string; c.resetbuffer() — clear accumulated input. Interpreter class: i = code.InteractiveInterpreter(locals=None) — lower level; i.runsource(source), i.runcode(code_obj), i.showsyntaxerror(filename=None), i.showtraceback(). Compile check: code.compile_command(source, filename="<input>", symbol="single") → compiled code (complete), None (incomplete), raises SyntaxError (bad). Add tab completion: import readline, rlcompleter; readline.parse_and_bind("tab: complete") before calling interact. Claude Code generates embedded REPLs, in-process sandboxes, notebook kernels, and debug console helpers.
CLAUDE.md for code
## code Stack
- Stdlib: import code, readline, rlcompleter
- Quick: code.interact(local={"db": conn}) # drop into REPL
- Console: c = code.InteractiveConsole(locals={"x": 42})
- c.interact(banner="Custom REPL")
- Push: more = c.push("def f(x):") # True = needs more
- more = c.push(" return x") # still True
- more = c.push("") # False = executed
- Note: runsource returns False when source is complete
code Interactive Console Pipeline
# app/codeutil.py — console, sandbox, capture, completion, kernel
from __future__ import annotations
import code
import io
import sys
import traceback
from contextlib import contextmanager, redirect_stdout, redirect_stderr
from dataclasses import dataclass, field
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic console helpers
# ─────────────────────────────────────────────────────────────────────────────
def start_repl(
local: "dict[str, Any] | None" = None,
banner: str = "Python REPL (Ctrl-D to exit)",
exitmsg: str = "",
with_completion: bool = True,
) -> None:
"""
Start an interactive REPL with optional tab completion.
Example:
from myapp import db, User
start_repl(local={"db": db, "User": User}, banner="App Shell")
"""
namespace = dict(local or {})
if with_completion:
try:
import readline
import rlcompleter
readline.set_completer(rlcompleter.Completer(namespace).complete)
readline.parse_and_bind("tab: complete")
except ImportError:
pass
code.interact(banner=banner, local=namespace, exitmsg=exitmsg)
def compile_check(source: str) -> "str":
"""
Check whether source is complete, incomplete, or invalid.
Returns 'complete', 'incomplete', or 'invalid'.
Example:
print(compile_check("x = 1 + 2")) # "complete"
print(compile_check("def f():")) # "incomplete"
print(compile_check("x = 1 +")) # "invalid"
"""
try:
result = code.compile_command(source)
return "complete" if result is not None else "incomplete"
except SyntaxError:
return "invalid"
# ─────────────────────────────────────────────────────────────────────────────
# 2. Capturing interpreter output
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ExecResult:
"""
Result of running source in a captured interpreter.
Example:
result = exec_source("x = 1 + 2\nprint(x)")
print(result.stdout) # "3\n"
print(result.ok) # True
"""
stdout: str
stderr: str
ok: bool
error: str
more: bool # True if source ended with an incomplete statement
def __str__(self) -> str:
status = "OK" if self.ok else f"ERROR({self.error[:60]})"
return f"ExecResult({status} stdout={self.stdout!r} stderr={self.stderr!r})"
def exec_source(
source: str,
namespace: "dict[str, Any] | None" = None,
) -> ExecResult:
"""
Execute Python source in an isolated namespace, capturing stdout/stderr.
Example:
r = exec_source("import math\nprint(math.pi)")
print(r.stdout) # "3.141592653589793\n"
"""
ns = dict(namespace or {})
stdout_buf = io.StringIO()
stderr_buf = io.StringIO()
interp = code.InteractiveInterpreter(locals=ns)
class Capture(code.InteractiveInterpreter):
def write(self, data: str) -> None:
stderr_buf.write(data)
cap = Capture(locals=ns)
# Redirect stdout for print() output
old_out, old_err = sys.stdout, sys.stderr
sys.stdout = stdout_buf
sys.stderr = stderr_buf
try:
more = cap.runsource(source, filename="<exec>", symbol="exec")
finally:
sys.stdout = old_out
sys.stderr = old_err
out = stdout_buf.getvalue()
err = stderr_buf.getvalue()
return ExecResult(
stdout=out,
stderr=err,
ok=not err,
error=err.strip(),
more=more,
)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Sandboxed console
# ─────────────────────────────────────────────────────────────────────────────
class SandboxConsole:
"""
An in-process sandboxed console that captures output per-execution.
Maintains state across pushes (like a real REPL session).
Example:
sb = SandboxConsole({"x": 10})
r1 = sb.push("y = x * 2")
r2 = sb.push("print(y)")
print(r2.stdout) # "20\n"
"""
def __init__(self, initial_locals: "dict[str, Any] | None" = None) -> None:
self.namespace: dict[str, Any] = dict(initial_locals or {})
self._console = code.InteractiveConsole(locals=self.namespace)
self._buffer: list[str] = []
def push(self, line: str) -> ExecResult:
"""
Feed one line to the console. Returns ExecResult with captured output.
"""
stdout_buf = io.StringIO()
stderr_buf = io.StringIO()
old_out, old_err = sys.stdout, sys.stderr
sys.stdout = stdout_buf
sys.stderr = stderr_buf
try:
more = self._console.push(line)
finally:
sys.stdout = old_out
sys.stderr = old_err
out = stdout_buf.getvalue()
err = stderr_buf.getvalue()
return ExecResult(
stdout=out,
stderr=err,
ok=not err,
error=err.strip(),
more=more,
)
def run_block(self, source: str) -> list[ExecResult]:
"""
Push each line of a multi-line source block, return all results.
Example:
results = sb.run_block("for i in range(3):\\n print(i)")
"""
results = []
for line in source.splitlines():
results.append(self.push(line))
# Flush any pending compound statement
results.append(self.push(""))
return results
def reset(self) -> None:
"""Reset the input buffer (for incomplete statements)."""
self._console.resetbuffer()
def get_namespace(self) -> dict[str, Any]:
"""Return a snapshot of the current interpreter namespace."""
return dict(self.namespace)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Multi-line input accumulator
# ─────────────────────────────────────────────────────────────────────────────
class MultiLineBuffer:
"""
Accumulate lines from a text stream until a complete Python statement
is ready, then hand it off for execution.
Example:
buf = MultiLineBuffer()
buf.feed("def add(a, b):") # → more=True
buf.feed(" return a + b") # → more=True
buf.feed("") # → more=False, source ready
if buf.ready:
print(buf.source)
"""
def __init__(self) -> None:
self._lines: list[str] = []
self.ready = False
self.source = ""
def feed(self, line: str) -> bool:
"""
Add a line. Returns True if more input is needed.
When complete, sets self.ready=True and self.source.
"""
self._lines.append(line)
joined = "\n".join(self._lines)
try:
result = code.compile_command(joined)
except SyntaxError:
self.ready = True
self.source = joined
self._lines.clear()
return False
if result is not None:
self.ready = True
self.source = joined
self._lines.clear()
return False
return True
def reset(self) -> None:
"""Clear pending lines without executing."""
self._lines.clear()
self.ready = False
self.source = ""
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== code demo ===")
# ── compile_check ─────────────────────────────────────────────────────────
print("\n--- compile_check ---")
for src in [
"x = 1 + 2",
"def f():",
"if True:",
"x = 1 +",
"print('hello')",
]:
print(f" {src!r:30s} → {compile_check(src)}")
# ── exec_source ───────────────────────────────────────────────────────────
print("\n--- exec_source ---")
r = exec_source("import math\nprint(f'pi={math.pi:.4f}')")
print(f" {r}")
r2 = exec_source("x = 1 + # syntax error")
print(f" {r2}")
# ── SandboxConsole ────────────────────────────────────────────────────────
print("\n--- SandboxConsole ---")
sb = SandboxConsole({"greeting": "Hello"})
lines = [
"name = 'World'",
"print(f'{greeting}, {name}!')",
"result = 6 * 7",
]
for line in lines:
r3 = sb.push(line)
if r3.stdout:
print(f" output: {r3.stdout!r}")
ns = sb.get_namespace()
print(f" result = {ns.get('result')}")
# ── run_block ─────────────────────────────────────────────────────────────
print("\n--- SandboxConsole.run_block ---")
sb2 = SandboxConsole()
block = "for i in range(3):\n print(i)"
results = sb2.run_block(block)
all_stdout = "".join(r.stdout for r in results)
print(f" loop output: {all_stdout!r}")
# ── MultiLineBuffer ────────────────────────────────────────────────────────
print("\n--- MultiLineBuffer ---")
buf = MultiLineBuffer()
inputs = ["def square(x):", " return x * x", ""]
for line in inputs:
more = buf.feed(line)
print(f" push({line!r}) more={more} ready={buf.ready}")
if buf.ready:
print(f" source: {buf.source!r}")
print("\n=== done ===")
For the exec() built-in alternative — exec(compile(source, "<string>", "exec"), namespace) runs one code block with full namespace control — use exec for single-shot script execution where you have the complete source; use code.InteractiveConsole / code.InteractiveInterpreter when you need to support incremental line-by-line input with automatic multi-line statement detection (the push → more loop) and the standard sys.ps1/sys.ps2 prompt handling. For the IPython / ptpython (PyPI) alternative — these provide syntax highlighting, multi-line editing, magic commands, and rich output display — use IPython for data science and exploratory REPL sessions; use code.InteractiveConsole when embedding a minimal, zero-dependency REPL inside an application (CLI tools, debugger prompts, admin shells). The Claude Skills 360 bundle includes code skill sets covering start_repl() with tab completion setup, compile_check() → complete/incomplete/invalid, ExecResult dataclass, exec_source() captured stdout/stderr, SandboxConsole with push()/run_block()/get_namespace() stateful session, and MultiLineBuffer incremental input accumulator with feed()/reset(). Start with the free tier to try interactive REPL patterns and code module pipeline code generation.