Claude Code for codeop: Python Incomplete Code Detection — Claude Skills 360 Blog
Blog / AI / Claude Code for codeop: Python Incomplete Code Detection
AI

Claude Code for codeop: Python Incomplete Code Detection

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

Python’s codeop module provides the incomplete-input detector used by code.InteractiveConsole — returning None when more input is needed, a code object when the statement is complete, or raising SyntaxError on bad input. import codeop. compile_command: codeop.compile_command(source, filename="<input>", symbol="single")code object if complete; None if incomplete (needs more lines); raises SyntaxError / OverflowError / ValueError on bad syntax. symbol: "single" (interactive, prints expression values), "exec" (module), "eval" (expression). CommandCompiler: cc = codeop.CommandCompiler() — stateful compiler; call cc(source, filename, symbol) — same return contract but accumulates __future__ imports (e.g. from __future__ import annotations) so they apply to all subsequent compiles in the session. The key use pattern: send each input line to compile_command; if None, prompt for continuation; if a code object, exec it; if SyntaxError, display the error. Works incrementally: "if True:" → None; "if True:\n pass" → code object. Claude Code generates REPL harnesses, notebook kernels, multi-line shell input handlers, and incremental syntax validators.

CLAUDE.md for codeop

## codeop Stack
- Stdlib: import codeop
- One-shot:  code = codeop.compile_command(src, "<input>", "single")
             # None = need more; code object = complete; SyntaxError = bad
- Stateful:  cc = codeop.CommandCompiler()
             code = cc(src, "<repl>", "single")  # tracks __future__ imports
- Loop:      buf = []
             while True:
                 line = input("... " if buf else ">>> ")
                 buf.append(line)
                 code = codeop.compile_command("\n".join(buf))
                 if code: exec(code); buf = []
                 elif code is None: continue   # incomplete
                 else: buf = []               # SyntaxError printed

codeop Interactive Input Pipeline

# app/codeoputil.py — compile, REPL loop, multi-line buffer, batch runner, validator
from __future__ import annotations

import codeop
import sys
import traceback
import types
from dataclasses import dataclass, field
from io import StringIO
from typing import Any, Callable


# ─────────────────────────────────────────────────────────────────────────────
# 1. Low-level compile helpers
# ─────────────────────────────────────────────────────────────────────────────

class CompileResult:
    """Discriminated result of an attempted compile."""
    __slots__ = ("code", "incomplete", "error", "error_msg")

    def __init__(
        self,
        code:       types.CodeType | None = None,
        incomplete: bool = False,
        error:      Exception | None = None,
    ) -> None:
        self.code = code
        self.incomplete = incomplete
        self.error = error
        self.error_msg = str(error) if error else ""

    @property
    def ok(self) -> bool:
        return self.code is not None

    def __repr__(self) -> str:
        if self.ok:       return f"<CompileResult: complete>"
        if self.incomplete: return f"<CompileResult: incomplete>"
        return f"<CompileResult: error: {self.error_msg}>"


def try_compile(
    source: str,
    filename: str = "<input>",
    symbol: str = "single",
) -> CompileResult:
    """
    Attempt to compile source. Returns a CompileResult discriminating between
    complete code, incomplete input (needs more lines), and syntax errors.

    Example:
        r = try_compile("if True:")
        print(r.incomplete)   # True — more lines needed

        r = try_compile("if True:\n    pass")
        print(r.ok)           # True — complete
    """
    try:
        code = codeop.compile_command(source, filename, symbol)
        if code is None:
            return CompileResult(incomplete=True)
        return CompileResult(code=code)
    except (SyntaxError, OverflowError, ValueError) as e:
        return CompileResult(error=e)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Multi-line input buffer
# ─────────────────────────────────────────────────────────────────────────────

class InputBuffer:
    """
    Accumulates lines of input and detects when a complete Python statement
    has been entered.

    Example:
        buf = InputBuffer()
        buf.push("def add(a, b):")   # → incomplete
        buf.push("    return a + b") # → incomplete
        buf.push("")                  # blank line → complete
        code = buf.code_object        # ready to exec
    """

    def __init__(
        self,
        filename: str = "<input>",
        symbol: str = "single",
        compiler: codeop.CommandCompiler | None = None,
    ) -> None:
        self._filename = filename
        self._symbol = symbol
        self._compiler = compiler or codeop.CommandCompiler()
        self._lines: list[str] = []
        self._code: types.CodeType | None = None
        self._error: Exception | None = None
        self._complete = False

    def push(self, line: str) -> bool:
        """
        Add a line. Returns True if the buffer is now complete (ready to exec).
        Raises SyntaxError (stored in .error) on bad input.
        """
        self._lines.append(line)
        source = "\n".join(self._lines)
        try:
            code = self._compiler(source, self._filename, self._symbol)
        except (SyntaxError, OverflowError, ValueError) as e:
            self._error = e
            self._lines.clear()
            self._complete = False
            return False

        if code is None:
            self._complete = False
            return False
        else:
            self._code = code
            self._complete = True
            return True

    def reset(self) -> None:
        self._lines.clear()
        self._code = None
        self._error = None
        self._complete = False

    @property
    def is_complete(self) -> bool:
        return self._complete

    @property
    def is_empty(self) -> bool:
        return len(self._lines) == 0

    @property
    def source(self) -> str:
        return "\n".join(self._lines)

    @property
    def code_object(self) -> types.CodeType | None:
        return self._code

    @property
    def error(self) -> Exception | None:
        return self._error

    @property
    def prompt(self) -> str:
        return "... " if self._lines else ">>> "


# ─────────────────────────────────────────────────────────────────────────────
# 3. REPL executor
# ─────────────────────────────────────────────────────────────────────────────

class ReplExecutor:
    """
    Execute code objects in a shared namespace with output capture.

    Example:
        repl = ReplExecutor()
        code = codeop.compile_command("x = 42", "<r>", "single")
        repl.run(code)
        code2 = codeop.compile_command("print(x)", "<r>", "single")
        out, err = repl.run_capture(code2)
        print(out)   # "42\n"
    """

    def __init__(self, namespace: dict[str, Any] | None = None) -> None:
        self.namespace: dict[str, Any] = namespace if namespace is not None else {
            "__name__": "__console__",
            "__doc__": None,
        }

    def run(self, code: types.CodeType) -> None:
        """Execute code in the shared namespace, printing exceptions."""
        try:
            exec(code, self.namespace)
        except SystemExit:
            raise
        except Exception:
            traceback.print_exc()

    def run_capture(
        self, code: types.CodeType
    ) -> tuple[str, str]:
        """Execute code and capture stdout/stderr. Returns (stdout, stderr)."""
        old_out, old_err = sys.stdout, sys.stderr
        sys.stdout = buf_out = StringIO()
        sys.stderr = buf_err = StringIO()
        try:
            exec(code, self.namespace)
        except SystemExit:
            raise
        except Exception:
            traceback.print_exc(file=sys.stderr)
        finally:
            sys.stdout, sys.stderr = old_out, old_err
        return buf_out.getvalue(), buf_err.getvalue()

    def get(self, name: str) -> Any:
        return self.namespace.get(name)

    def set(self, name: str, value: Any) -> None:
        self.namespace[name] = value


# ─────────────────────────────────────────────────────────────────────────────
# 4. Batch source runner (split on complete statements)
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class BatchResult:
    statements:  int
    errors:      list[tuple[int, str]] = field(default_factory=list)   # (stmt_idx, msg)
    outputs:     list[str] = field(default_factory=list)

    @property
    def success(self) -> bool:
        return len(self.errors) == 0

    def __str__(self) -> str:
        return (f"statements={self.statements}  "
                f"errors={len(self.errors)}  "
                f"output_bytes={sum(len(o) for o in self.outputs)}")


def run_batch(
    source: str,
    namespace: dict[str, Any] | None = None,
    capture: bool = True,
) -> BatchResult:
    """
    Compile and execute multi-statement source, splitting it into individual
    complete statements using codeop.

    Example:
        result = run_batch("x = 1\\ny = x + 1\\nprint(y)", capture=True)
        print(result.outputs)   # ["2\\n"]
    """
    executor = ReplExecutor(namespace)
    buf = InputBuffer(filename="<batch>", symbol="exec")

    result = BatchResult(statements=0)
    stmt_idx = 0

    for line in (source + "\n").splitlines():
        complete = buf.push(line)
        if buf.error:
            result.errors.append((stmt_idx, str(buf.error)))
            buf.reset()
            continue
        if complete and buf.code_object:
            if capture:
                out, err = executor.run_capture(buf.code_object)
                if out:
                    result.outputs.append(out)
                if err:
                    result.errors.append((stmt_idx, err.strip()))
            else:
                executor.run(buf.code_object)
            result.statements += 1
            stmt_idx += 1
            buf.reset()

    # flush any remaining incomplete buffer
    if not buf.is_empty:
        # try adding blank line to flush compound statement
        buf.push("")
        if buf.is_complete and buf.code_object:
            if capture:
                out, err = executor.run_capture(buf.code_object)
                if out:
                    result.outputs.append(out)
            else:
                executor.run(buf.code_object)
            result.statements += 1

    return result


# ─────────────────────────────────────────────────────────────────────────────
# 5. Syntax validator
# ─────────────────────────────────────────────────────────────────────────────

def validate_source(source: str, filename: str = "<string>") -> list[str]:
    """
    Check a multi-line source string for syntax errors using codeop.
    Returns a list of error messages (empty if valid).

    Example:
        errors = validate_source("def bad(\\n    # missing close")
        print(errors)   # ["unexpected EOF while parsing ..."]
    """
    errors: list[str] = []
    buf = InputBuffer(filename=filename, symbol="exec")
    for line in source.splitlines():
        buf.push(line)
        if buf.error:
            errors.append(str(buf.error))
            buf.reset()
    # Final flush
    if not buf.is_empty:
        buf.push("")
        if buf.error:
            errors.append(str(buf.error))
    return errors


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

if __name__ == "__main__":
    print("=== codeop demo ===")

    # ── try_compile ────────────────────────────────────────────────────────────
    print("\n--- try_compile ---")
    cases = [
        ("x = 1 + 2",           "single"),
        ("if True:",             "single"),
        ("if True:\n    pass",   "single"),
        ("def f(\n",             "single"),
        ("1 +",                  "single"),
        ("import os; os.x(??)", "single"),
    ]
    for src, sym in cases:
        r = try_compile(src, symbol=sym)
        print(f"  {src[:35]!r:38s}{r!r}")

    # ── InputBuffer ────────────────────────────────────────────────────────────
    print("\n--- InputBuffer multi-line ---")
    ibuf = InputBuffer()
    lines = [
        "def greet(name: str) -> str:",
        "    return f'Hello, {name}!'",
        "",   # blank line completes the function
    ]
    for line in lines:
        complete = ibuf.push(line)
        print(f"  push({line!r:35s}) → complete={complete}  prompt={ibuf.prompt!r}")
    if ibuf.code_object:
        print(f"  code object: {ibuf.code_object}")

    # ── ReplExecutor ───────────────────────────────────────────────────────────
    print("\n--- ReplExecutor ---")
    repl = ReplExecutor()
    snippets = [
        "x = 10",
        "y = x * x",
        "print(f'y = {y}')",
        "from __future__ import annotations",
    ]
    for snippet in snippets:
        code = codeop.compile_command(snippet, "<repl>", "single")
        if code:
            out, err = repl.run_capture(code)
            if out:
                print(f"  output: {out.rstrip()!r}")
    print(f"  x={repl.get('x')}  y={repl.get('y')}")

    # ── run_batch ──────────────────────────────────────────────────────────────
    print("\n--- run_batch ---")
    batch_src = """
import math
def circle_area(r):
    return math.pi * r ** 2

for r in [1, 2, 3]:
    print(f"r={r}  area={circle_area(r):.3f}")
"""
    result = run_batch(batch_src.strip(), capture=True)
    print(f"  {result}")
    for out in result.outputs:
        for line in out.splitlines():
            print(f"  {line}")

    # ── validate_source ────────────────────────────────────────────────────────
    print("\n--- validate_source ---")
    valid_src   = "x = 1\ny = x + 1\n"
    invalid_src = "def bad(\n    # missing close"
    print(f"  valid errors:   {validate_source(valid_src)}")
    errs = validate_source(invalid_src)
    print(f"  invalid errors: {[e[:60] for e in errs]}")

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

For the code.InteractiveConsole / code.InteractiveInterpreter alternative — code.InteractiveConsole is a higher-level REPL that uses codeop.CommandCompiler internally; interact() runs a full read-eval-print loop using readline if available — use code.InteractiveConsole when you want a batteries-included REPL with banner, interact() loop, and push() API; use codeop directly when you need fine-grained control over input buffering and compilation outside of a standard console loop, such as in a web notebook kernel, a socket-based REPL, or a testing harness where you want to drive execution line-by-line and capture output after each statement. For the ast.parse / compile alternative — ast.parse(source, mode="exec") raises SyntaxError on bad input but does not distinguish between incomplete and invalid source the way codeop.compile_command() does (returning None for incomplete); compile(source, filename, mode) similarly raises SyntaxError without the incomplete-input contract — use codeop when you specifically need the three-way incomplete/complete/error discrimination for an interactive input loop; use ast.parse() when you need AST-level access to the parsed tree for static analysis or transformation. The Claude Skills 360 bundle includes codeop skill sets covering CompileResult with try_compile() one-shot discriminating compiler, InputBuffer multi-line accumulator with is_complete/prompt/code_object, ReplExecutor shared-namespace executor with run()/run_capture(), BatchResult with run_batch() multi-statement source runner, and validate_source() incremental syntax validator. Start with the free tier to try REPL construction patterns and codeop 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