Claude Code for pyclbr: Python Class Browser — Claude Skills 360 Blog
Blog / AI / Claude Code for pyclbr: Python Class Browser
AI

Claude Code for pyclbr: Python Class Browser

Published: November 12, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s pyclbr module scans Python source files for class and function definitions without importing them, returning structural metadata via static analysis. import pyclbr. Scan module: pyclbr.readmodule(module, path=None){name: Class} — top-level classes only; pyclbr.readmodule_ex(module, path=None){name: Class|Function} — classes and top-level functions. Class attributes: .name → class name string; .module → module name; .file → source file path; .lineno → definition line number; .super → list of base class names or Class objects (resolved if in same module); .methods{method_name: lineno} dict of all methods defined in the class body. Function attributes: .name, .module, .file, .lineno. Path argument: pass a list of extra directories to prepend to the module search path — pyclbr.readmodule("mymodule", path=["/project/src"]). The module uses tokenize internally and never executes the source. Claude Code generates class hierarchy explorers, method inventories, documentation extractors, and API surface scanners.

CLAUDE.md for pyclbr

## pyclbr Stack
- Stdlib: import pyclbr
- Scan:   classes = pyclbr.readmodule("mymodule")
-         all_   = pyclbr.readmodule_ex("mymodule", path=["/src"])
- Class:  cls.name  cls.file  cls.lineno  cls.super  cls.methods
- Func:   fn.name   fn.file   fn.lineno
- Note:   Static analysis only — never imports or executes the module

pyclbr Class and Function Browser Pipeline

# app/pyclbrutil.py — scan, hierarchy, method inventory, diff, API surface
from __future__ import annotations

import importlib.util
import os
import sys
from dataclasses import dataclass, field
from pathlib import Path

import pyclbr


# ─────────────────────────────────────────────────────────────────────────────
# 1. Scan helpers
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class ClassInfo:
    name:      str
    file:      str
    lineno:    int
    bases:     list[str]
    methods:   dict[str, int]    # method_name → lineno

    @property
    def method_names(self) -> list[str]:
        return sorted(self.methods)

    def __str__(self) -> str:
        bases_str = f"({', '.join(self.bases)})" if self.bases else ""
        return (f"class {self.name}{bases_str}  "
                f"[{Path(self.file).name}:{self.lineno}]  "
                f"{len(self.methods)} methods")


@dataclass
class FunctionInfo:
    name:   str
    file:   str
    lineno: int

    def __str__(self) -> str:
        return f"def {self.name}  [{Path(self.file).name}:{self.lineno}]"


def scan_module(
    module_name: str,
    extra_path: list[str] | None = None,
) -> tuple[list[ClassInfo], list[FunctionInfo]]:
    """
    Scan a module by name, returning (classes, functions).
    Uses static analysis — no import/execution.

    Example:
        classes, funcs = scan_module("collections")
        for cls in classes:
            print(cls)
    """
    path = extra_path or []
    raw = pyclbr.readmodule_ex(module_name, path=path)

    classes: list[ClassInfo] = []
    functions: list[FunctionInfo] = []

    for name, obj in raw.items():
        if isinstance(obj, pyclbr.Class):
            bases = []
            for b in obj.super:
                if isinstance(b, pyclbr.Class):
                    bases.append(b.name)
                else:
                    bases.append(str(b))
            classes.append(ClassInfo(
                name=name,
                file=obj.file,
                lineno=obj.lineno,
                bases=bases,
                methods=dict(obj.methods),
            ))
        elif isinstance(obj, pyclbr.Function):
            functions.append(FunctionInfo(
                name=name,
                file=obj.file,
                lineno=obj.lineno,
            ))

    classes.sort(key=lambda c: c.lineno)
    functions.sort(key=lambda f: f.lineno)
    return classes, functions


def scan_file(
    file_path: str | Path,
    module_name: str | None = None,
) -> tuple[list[ClassInfo], list[FunctionInfo]]:
    """
    Scan a .py file by path, returning (classes, functions).
    Derives a module name from the file stem if not provided.

    Example:
        classes, funcs = scan_file("/project/src/mymodule.py")
    """
    path = Path(file_path).resolve()
    name = module_name or path.stem
    directory = str(path.parent)
    return scan_module(name, extra_path=[directory])


# ─────────────────────────────────────────────────────────────────────────────
# 2. Class hierarchy
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class HierarchyNode:
    name:     str
    bases:    list[str]
    children: list["HierarchyNode"] = field(default_factory=list)

    def render(self, indent: int = 0) -> str:
        prefix = "  " * indent
        bases_str = f"({', '.join(self.bases)})" if self.bases else ""
        lines = [f"{prefix}{self.name}{bases_str}"]
        for child in self.children:
            lines.append(child.render(indent + 1))
        return "\n".join(lines)


def build_hierarchy(
    classes: list[ClassInfo],
    root_filter: list[str] | None = None,
) -> list[HierarchyNode]:
    """
    Build a class inheritance tree from a list of ClassInfo objects.
    Returns root nodes (classes with no in-module base).

    Example:
        classes, _ = scan_module("http.server")
        roots = build_hierarchy(classes)
        for r in roots:
            print(r.render())
    """
    by_name: dict[str, HierarchyNode] = {
        c.name: HierarchyNode(name=c.name, bases=c.bases)
        for c in classes
    }
    known = set(by_name)

    # Wire up parent → child relationships for in-module parents
    for node in by_name.values():
        for base in node.bases:
            if base in by_name:
                by_name[base].children.append(node)

    # Roots: classes whose bases are all outside this module
    roots = [
        node for node in by_name.values()
        if not any(b in known for b in node.bases)
    ]
    if root_filter:
        roots = [r for r in roots if r.name in root_filter]
    return sorted(roots, key=lambda n: n.name)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Method inventory
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class MethodEntry:
    class_name: str
    method:     str
    lineno:     int

    def __str__(self) -> str:
        return f"{self.class_name}.{self.method}  (line {self.lineno})"


def method_inventory(
    classes: list[ClassInfo],
    pattern: str | None = None,
) -> list[MethodEntry]:
    """
    Flatten all class methods into a list, optionally filtered by name substring.

    Example:
        classes, _ = scan_module("email.message")
        for entry in method_inventory(classes, pattern="get"):
            print(entry)
    """
    entries = []
    for cls in classes:
        for method, lineno in sorted(cls.methods.items(), key=lambda kv: kv[1]):
            if pattern is None or pattern in method:
                entries.append(MethodEntry(cls.name, method, lineno))
    return entries


def find_class(classes: list[ClassInfo], name: str) -> ClassInfo | None:
    """
    Find a ClassInfo by exact name.

    Example:
        cls = find_class(classes, "HTTPServer")
    """
    for c in classes:
        if c.name == name:
            return c
    return None


# ─────────────────────────────────────────────────────────────────────────────
# 4. API surface diff
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class ApiDiff:
    added_classes:    list[str]
    removed_classes:  list[str]
    added_functions:  list[str]
    removed_functions: list[str]
    changed_methods:  dict[str, dict]   # class_name → {added, removed}

    def summary(self) -> str:
        lines = []
        if self.added_classes:
            lines.append(f"  + classes:   {', '.join(self.added_classes)}")
        if self.removed_classes:
            lines.append(f"  - classes:   {', '.join(self.removed_classes)}")
        if self.added_functions:
            lines.append(f"  + functions: {', '.join(self.added_functions)}")
        if self.removed_functions:
            lines.append(f"  - functions: {', '.join(self.removed_functions)}")
        for cls_name, delta in self.changed_methods.items():
            if delta.get("added"):
                lines.append(f"  + {cls_name}: {', '.join(delta['added'])}")
            if delta.get("removed"):
                lines.append(f"  - {cls_name}: {', '.join(delta['removed'])}")
        return "\n".join(lines) if lines else "  (no changes)"


def diff_api(
    old_file: str | Path,
    new_file: str | Path,
    module_name: str | None = None,
) -> ApiDiff:
    """
    Compare the public API surface of two versions of the same module file.
    Returns an ApiDiff describing what changed.

    Example:
        delta = diff_api("mymodule_v1.py", "mymodule_v2.py")
        print(delta.summary())
    """
    old_classes, old_funcs = scan_file(old_file, module_name)
    new_classes, new_funcs = scan_file(new_file, module_name)

    old_cls_map = {c.name: c for c in old_classes}
    new_cls_map = {c.name: c for c in new_classes}
    old_fn_names = {f.name for f in old_funcs}
    new_fn_names = {f.name for f in new_funcs}

    changed: dict[str, dict] = {}
    for name in old_cls_map.keys() & new_cls_map.keys():
        old_m = set(old_cls_map[name].methods)
        new_m = set(new_cls_map[name].methods)
        added = sorted(new_m - old_m)
        removed = sorted(old_m - new_m)
        if added or removed:
            changed[name] = {"added": added, "removed": removed}

    return ApiDiff(
        added_classes=sorted(set(new_cls_map) - set(old_cls_map)),
        removed_classes=sorted(set(old_cls_map) - set(new_cls_map)),
        added_functions=sorted(new_fn_names - old_fn_names),
        removed_functions=sorted(old_fn_names - new_fn_names),
        changed_methods=changed,
    )


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

if __name__ == "__main__":
    import tempfile
    print("=== pyclbr demo ===")

    # ── scan stdlib module ────────────────────────────────────────────────────
    print("\n--- scan_module('email.message') ---")
    classes, funcs = scan_module("email.message")
    for c in classes:
        print(f"  {c}")
    for f in funcs:
        print(f"  {f}")

    # ── hierarchy ─────────────────────────────────────────────────────────────
    print("\n--- build_hierarchy ---")
    roots = build_hierarchy(classes)
    for r in roots:
        print(r.render())

    # ── method_inventory (filter 'get') ───────────────────────────────────────
    print("\n--- method_inventory(pattern='get') ---")
    for entry in method_inventory(classes, pattern="get")[:8]:
        print(f"  {entry}")

    # ── scan_file on a temp file ──────────────────────────────────────────────
    print("\n--- scan_file on temp source ---")
    src_v1 = '''\
class Animal:
    def speak(self): pass
    def move(self): pass

class Dog(Animal):
    def fetch(self): pass

def helper(): pass
'''
    src_v2 = '''\
class Animal:
    def speak(self): pass
    def move(self): pass
    def breathe(self): pass

class Cat(Animal):
    def purr(self): pass

def helper(): pass
def new_util(): pass
'''
    with tempfile.TemporaryDirectory() as tmp:
        v1 = Path(tmp) / "creatures.py"
        v2 = Path(tmp) / "creatures_new.py"
        v1.write_text(src_v1)
        v2.write_text(src_v2)

        classes_v1, funcs_v1 = scan_file(v1, "creatures")
        print(f"  v1 classes: {[c.name for c in classes_v1]}")
        print(f"  v1 funcs:   {[f.name for f in funcs_v1]}")

        # ── diff_api ──────────────────────────────────────────────────────────
        print("\n--- diff_api(v1, v2) ---")
        delta = diff_api(v1, v2, "creatures")
        print(delta.summary())

        # ── find_class ────────────────────────────────────────────────────────
        print("\n--- find_class ---")
        animal = find_class(classes_v1, "Animal")
        if animal:
            print(f"  {animal.name}: methods={animal.method_names}")

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

For the ast alternative — ast.parse(source) builds a full AST for the entire module, allowing inspection of every expression, decorator, default value, and nested class, while ast.NodeVisitor / ast.NodeTransformer traverse or rewrite the tree — use ast when you need to analyze or transform code structure beyond class/function definitions (e.g., extract all decorator usage, count return statements, or rewrite expressions); use pyclbr for quick, lightweight class-and-method metadata without building a full AST or dealing with visitor boilerplate. For the inspect alternative — inspect.getmembers(module, inspect.isclass), inspect.getsourcelines(cls), and inspect.getmro(cls) provide live runtime introspection including inherited attributes, MRO, and actual source lines — use inspect when the module is already imported and you need live runtime data (docstrings, signatures, MRO); use pyclbr for static analysis of modules you cannot safely import (untrusted code, heavy dependencies, compile-time errors) or when you want to scan source files without executing them. The Claude Skills 360 bundle includes pyclbr skill sets covering ClassInfo/FunctionInfo with scan_module()/scan_file() static scanners, HierarchyNode with build_hierarchy() inheritance tree builder, MethodEntry with method_inventory()/find_class() method lookup, and ApiDiff with diff_api() API surface comparator. Start with the free tier to try class browser patterns and pyclbr 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