Claude Code for linecache: Python Source Line Reading — Claude Skills 360 Blog
Blog / AI / Claude Code for linecache: Python Source Line Reading
AI

Claude Code for linecache: Python Source Line Reading

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

Python’s linecache module reads and caches source code lines by filename and line number — the same mechanism used by traceback, pdb, and tracemalloc internally. import linecache. getline: linecache.getline("app/main.py", 42) → string with trailing \n, or "" if not found; first arg is any filename the import system knows; n=1-indexed. getlines: linecache.getlines("app/main.py") → list of all lines. clearcache: linecache.clearcache() — free all cached content. checkcache: linecache.checkcache("app/main.py") — re-stat the file; checkcache() with no arg checks all cached entries; call after the file changes. updatecache: linecache.updatecache("app/main.py") — force reload. lazycache: linecache.lazycache("mymod", globals_dict) — register lines from __loader__ without reading; used by traceback for frozen/zip-imported modules. linecache.cache — the dict {filename: (size, mtime, lines, fullpath)}. Works transparently for zip-imported modules when the loader provides get_source(). Claude Code generates source annotators, traceback enrichers, inline profiler reporters, and interactive diff viewers.

CLAUDE.md for linecache

## linecache Stack
- Stdlib: import linecache
- Line:   linecache.getline("file.py", 42)          # "    return x + y\n" or ""
- All:    linecache.getlines("file.py")              # list of all lines
- Refresh: linecache.checkcache("file.py")           # re-stat after edit
- Clear:  linecache.clearcache()                     # free all cached data
- Lazy:   linecache.lazycache("mod", mod.__dict__)   # loader-backed registration

linecache Source Line Pipeline

# app/linecacheutil.py — getline, annotate, context window, diff, traceback enricher
from __future__ import annotations

import linecache
import os
import textwrap
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. Line reading helpers
# ─────────────────────────────────────────────────────────────────────────────

def get_line(filename: str, lineno: int, strip: bool = False) -> str:
    """
    Return a single source line (1-indexed). Empty string if not found.

    strip: remove leading/trailing whitespace and trailing newline.

    Example:
        line = get_line("app/main.py", 42)
        print(f"Line 42: {line!r}")
    """
    line = linecache.getline(filename, lineno)
    return line.strip() if strip else line


def get_lines(filename: str, start: int = 1, end: int | None = None) -> list[str]:
    """
    Return a slice of lines from a file [start, end] (1-indexed, inclusive).
    Returns all lines if end is None.

    Example:
        lines = get_lines("app/main.py", start=10, end=20)
    """
    all_lines = linecache.getlines(filename)
    # Convert to 0-indexed slice
    s = max(0, start - 1)
    e = end  # None → all; end is inclusive so we don't subtract
    return all_lines[s:e]


def file_exists_in_cache(filename: str) -> bool:
    """
    Return True if the file is already loaded in linecache.

    Example:
        if not file_exists_in_cache("app/main.py"):
            linecache.updatecache("app/main.py")
    """
    return filename in linecache.cache


def refresh(filename: str | None = None) -> None:
    """
    Invalidate stale cache entries.
    Pass a filename to check only that file, or None to check everything.

    Example:
        # After editing main.py
        refresh("app/main.py")
        line = get_line("app/main.py", 1)
    """
    linecache.checkcache(filename)


def clear() -> None:
    """Free all cached source content."""
    linecache.clearcache()


# ─────────────────────────────────────────────────────────────────────────────
# 2. Context window — source lines around a target line
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class SourceContext:
    filename: str
    target:   int          # the highlighted line number
    lines:    list[tuple[int, str, bool]]   # (lineno, text, is_target)

    def __str__(self) -> str:
        parts: list[str] = [f"  {self.filename}"]
        for lineno, text, is_target in self.lines:
            marker = ">>>" if is_target else "   "
            parts.append(f"  {marker} {lineno:4d}  {text.rstrip()}")
        return "\n".join(parts)


def get_context(
    filename: str,
    lineno: int,
    before: int = 3,
    after: int = 3,
) -> SourceContext:
    """
    Return a SourceContext with lines surrounding a target line.

    Example:
        ctx = get_context("app/main.py", 42, before=4, after=4)
        print(ctx)
    """
    all_lines = linecache.getlines(filename)
    total = len(all_lines)

    start = max(1, lineno - before)
    end   = min(total, lineno + after)

    lines_out: list[tuple[int, str, bool]] = []
    for n in range(start, end + 1):
        text = all_lines[n - 1] if n <= total else ""
        lines_out.append((n, text, n == lineno))

    return SourceContext(filename=filename, target=lineno, lines=lines_out)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Source annotator — attach source lines to tracebacks / allocation sites
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class AnnotatedFrame:
    filename:  str
    lineno:    int
    name:      str       # function / scope name
    source:    str       # stripped source line (or "")

    def __str__(self) -> str:
        src = f"  # {self.source}" if self.source else ""
        return f"  File {self.filename!r}, line {self.lineno}, in {self.name}{src}"


def annotate_traceback(tb_frames: list[tuple[str, int, str]]) -> list[AnnotatedFrame]:
    """
    Attach source lines to a list of (filename, lineno, name) traceback tuples.

    Example:
        import traceback, sys
        try:
            1/0
        except ZeroDivisionError:
            frames = [(f.filename, f.lineno, f.name)
                      for f in traceback.extract_tb(sys.exc_info()[2])]
            for af in annotate_traceback(frames):
                print(af)
    """
    result: list[AnnotatedFrame] = []
    for filename, lineno, name in tb_frames:
        src = linecache.getline(filename, lineno).strip()
        result.append(AnnotatedFrame(filename=filename, lineno=lineno, name=name, source=src))
    return result


def annotate_sites(
    sites: list[tuple[str, int]],
) -> list[tuple[str, int, str]]:
    """
    Bulk-annotate (filename, lineno) pairs with their source lines.
    Returns [(filename, lineno, source_line), ...].

    Example:
        # From tracemalloc top_allocations
        pairs = [(s.file, s.line) for s in allocation_sites]
        for filename, lineno, src in annotate_sites(pairs):
            print(f"  {filename}:{lineno}  {src.strip()!r}")
    """
    return [
        (filename, lineno, linecache.getline(filename, lineno))
        for filename, lineno in sites
    ]


# ─────────────────────────────────────────────────────────────────────────────
# 4. File viewer — render numbered source with optional highlight range
# ─────────────────────────────────────────────────────────────────────────────

def render_source(
    filename: str,
    highlight: set[int] | None = None,
    start: int = 1,
    end: int | None = None,
    width: int = 80,
) -> str:
    """
    Render a source file (or slice) as a numbered listing.
    highlight: set of line numbers to mark with ">>>".

    Example:
        src = render_source("app/main.py", highlight={10, 11}, start=5, end=15)
        print(src)
    """
    lines = get_lines(filename, start=start, end=end)
    hl = highlight or set()
    out: list[str] = []
    for i, text in enumerate(lines):
        n = start + i
        marker = ">>>" if n in hl else "   "
        out.append(f"{marker} {n:4d}  {text.rstrip()}")
    return "\n".join(out)


# ─────────────────────────────────────────────────────────────────────────────
# 5. Cache inspector and lazy registration
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class CacheEntry:
    filename: str
    size:     int | None    # bytes (None for synthetic entries)
    mtime:    float | None  # mtime (None for lazycache entries)
    line_count: int

    def __str__(self) -> str:
        size_str = f"{self.size:,d}B" if self.size else "synthetic"
        return f"{self.filename}  lines={self.line_count}  {size_str}"


def inspect_cache() -> list[CacheEntry]:
    """
    Return a summary of all entries currently held in linecache.

    Example:
        for entry in inspect_cache():
            print(entry)
    """
    result: list[CacheEntry] = []
    for filename, value in linecache.cache.items():
        if isinstance(value, tuple) and len(value) >= 3:
            size, mtime, lines = value[0], value[1], value[2]
            result.append(CacheEntry(
                filename=filename,
                size=size,
                mtime=mtime,
                line_count=len(lines) if lines else 0,
            ))
    return sorted(result, key=lambda e: e.filename)


def register_source(module_name: str, source: str) -> None:
    """
    Register arbitrary source text into linecache under a synthetic filename.
    Useful for making eval'd or dynamically generated code inspectable.

    Example:
        code = "x = 1\\ny = x + 2\\n"
        register_source("<mycode>", code)
        print(linecache.getline("<mycode>", 2))   # "y = x + 2\\n"
    """
    lines = source.splitlines(keepends=True)
    linecache.cache[module_name] = (
        len(source),
        None,       # no mtime — synthetic
        lines,
        module_name,
    )


def lazy_register(module_name: str, module_globals: dict) -> bool:
    """
    Register a module's source via its __loader__ (lazycache).
    Returns True if registration succeeded.

    This allows traceback to display source lines for frozen/zip-imported modules
    without reading the whole file upfront.

    Example:
        import mymodule
        lazy_register(mymodule.__name__, mymodule.__dict__)
    """
    return linecache.lazycache(module_name, module_globals)


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

if __name__ == "__main__":
    import tempfile
    import traceback
    import sys

    print("=== linecache demo ===")

    # ── create a temp source file ──────────────────────────────────────────────
    with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f:
        f.write(
            "# demo module\n"
            "import math\n"
            "\n"
            "def compute(x: float) -> float:\n"
            "    '''Compute something.'''\n"
            "    return math.sqrt(x) + math.pi\n"
            "\n"
            "def bad() -> None:\n"
            "    raise ValueError('intentional error')\n"
        )
        tmpfile = f.name

    try:
        # ── get_line ───────────────────────────────────────────────────────────
        print("\n--- get_line ---")
        for n in [1, 4, 6, 99]:
            line = get_line(tmpfile, n, strip=True)
            print(f"  line {n:2d}: {line!r}")

        # ── get_lines slice ────────────────────────────────────────────────────
        print("\n--- get_lines(4..6) ---")
        for i, ln in enumerate(get_lines(tmpfile, start=4, end=6), start=4):
            print(f"  {i}: {ln.rstrip()}")

        # ── get_context ────────────────────────────────────────────────────────
        print("\n--- get_context(line 6, before=2, after=2) ---")
        ctx = get_context(tmpfile, 6, before=2, after=2)
        print(ctx)

        # ── render_source ──────────────────────────────────────────────────────
        print("\n--- render_source (lines 3-9, highlight {6}) ---")
        rendered = render_source(tmpfile, highlight={6}, start=3, end=9)
        for line in rendered.splitlines():
            print(f"  {line}")

        # ── annotate_traceback ─────────────────────────────────────────────────
        print("\n--- annotate_traceback ---")
        try:
            # Execute the bad() function by importing the temp file
            spec_obj = None
            import importlib.util
            spec_obj = importlib.util.spec_from_file_location("_demo", tmpfile)
            demo_mod = importlib.util.module_from_spec(spec_obj)
            spec_obj.loader.exec_module(demo_mod)
            demo_mod.bad()
        except ValueError:
            tb = sys.exc_info()[2]
            frames = [
                (fr.filename, fr.lineno, fr.name)
                for fr in traceback.extract_tb(tb)
            ]
            for af in annotate_traceback(frames):
                print(af)

        # ── inspect_cache ──────────────────────────────────────────────────────
        print("\n--- inspect_cache ---")
        entries = inspect_cache()
        for e in entries[:4]:
            print(f"  {e}")
        print(f"  ... ({len(entries)} total cached files)")

        # ── register_source (synthetic) ────────────────────────────────────────
        print("\n--- register_source (synthetic eval code) ---")
        synthetic = "result = 42\nprint('hello from eval')\n"
        register_source("<eval-block>", synthetic)
        line1 = linecache.getline("<eval-block>", 1)
        line2 = linecache.getline("<eval-block>", 2)
        print(f"  line 1: {line1!r}")
        print(f"  line 2: {line2!r}")

        # ── refresh + clear ────────────────────────────────────────────────────
        print("\n--- checkcache + clearcache ---")
        before = len(linecache.cache)
        refresh(tmpfile)
        print(f"  cache size before clear: {before}")
        clear()
        print(f"  cache size after clear: {len(linecache.cache)}")

    finally:
        os.unlink(tmpfile)

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

For the inspect alternative — inspect.getsource(obj) and inspect.getsourcelines(obj) retrieve source code for live objects (functions, classes, modules) using inspect.getfile() to find the file and then linecache.getlines() internally — use inspect when you have a reference to the live Python object and want its full source block with correct indentation and decorators; use linecache directly when you have only a (filename, lineno) pair (as returned by traceback, tracemalloc, or cProfile) and need to fetch surrounding context lines without importing or instantiating the module. For the ast.get_source_segment / tokenize alternative — ast.get_source_segment(source, node) extracts the exact source text for an AST node given the full source string; tokenize.generate_tokens() provides token-level access including whitespace and comments — use these when doing static analysis or source transformation where you already have the full source text in memory; use linecache for on-demand line fetching during runtime observation (tracebacks, profiler annotation, debugger displays) where you want the OS page cache and linecache’s own LRU to minimize I/O. The Claude Skills 360 bundle includes linecache skill sets covering get_line()/get_lines()/refresh()/clear() reading primitives, SourceContext with get_context() context window builder, AnnotatedFrame with annotate_traceback()/annotate_sites() traceback enrichers, render_source() numbered listing renderer with highlight support, and CacheEntry with inspect_cache()/register_source()/lazy_register() cache management tools. Start with the free tier to try source annotation patterns and linecache 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