source.py


Overview

The [source.py](/projects/286/67491) module provides a robust and immutable abstraction for handling and manipulating source code fragments in Python. Its primary class, `Source`, encapsulates source code lines while automatically normalizing indentation and enabling intuitive slicing, iteration, and querying of code statements.

This module is designed to facilitate introspection and manipulation of source code, especially useful in contexts such as debugging, code analysis, dynamic code modification, and testing frameworks. It leverages Python's standard libraries such as `inspect`, `ast`, and `tokenize` to parse and analyze source code structure accurately.


Detailed Documentation

Class: Source

**Purpose:** An immutable container for a source code fragment. It stores source code lines in a normalized form by deindenting them and provides various utility methods to query, slice, and manipulate these fragments conveniently.


Constructor: __init__(self, obj: object = None) -> None

**Description:** Creates a `Source` instance from various input types:

**Parameters:**

**Usage Example:**

src1 = Source()
src2 = Source(["    def foo():", "        return 42"])
src3 = Source("def bar():\n    pass\n")
src4 = Source(some_function)

Equality and Hashing


Item Access: __getitem__(self, key: int | slice) -> str | Source

**Description:** Supports indexing and slicing:

**Restrictions:** Slices with step values other than 1 are not supported and raise `IndexError`.

**Usage Example:**

line = src[2]          # Get the third line
sub_source = src[1:4]  # Get lines 1, 2, and 3 as a new Source instance

Iteration and Length


strip(self) -> Source

**Description:** Returns a new `Source` instance with leading and trailing blank lines removed.

**Usage Example:**

clean_source = src.strip()

indent(self, indent: str = " ") -> Source

**Description:** Returns a new `Source` instance with every line indented by the specified string (default 4 spaces).

**Parameters:**

**Usage Example:**

indented_source = src.indent("\t")  # Indents each line with a tab

getstatement(self, lineno: int) -> Source

**Description:** Returns the minimal statement containing the given line number (0-based). A statement may span multiple lines (like function definitions, loops, etc.).

**Parameters:**

**Usage Example:**

statement = src.getstatement(5)
print(str(statement))  # Prints the entire statement containing line 5

getstatementrange(self, lineno: int) -> tuple[int, int]

**Description:** Returns the `(start, end)` line indices (0-based, end exclusive) that span the minimal statement containing the given line number.

**Parameters:**

**Returns:**


deindent(self) -> Source

**Description:** Returns a new `Source` object with all lines deindented (leading whitespace removed uniformly).


__str__(self) -> str

**Description:** Returns the source code as a single string, joining lines with newline characters.


Helper Functions


findsource(obj) -> tuple[Source | None, int]

**Description:** Attempts to find and return the source code and starting line number of a given object using `inspect.findsource`.

**Returns:**


getrawcode(obj: object, trycall: bool = True) -> types.CodeType

**Description:** Fetches the raw code object (`__code__`) for a given function or callable. If `obj` is not directly a function, attempts to retrieve the `__call__` method's code.

**Raises:**


deindent(lines: Iterable[str]) -> list[str]

**Description:** Deindents a sequence of lines by removing common leading whitespace.


get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]

**Description:** Given a line number and an AST node, determines the start and end line indices of the minimal statement containing that line.

**Implementation Details:**


getstatementrange_ast(lineno: int, source: Source, assertion: bool = False, astnode: ast.AST | None = None) -> tuple[ast.AST, int, int]

**Description:** Returns the AST node and line range `(start, end)` for the minimal statement containing the given line number.

**Parameters:**

**Implementation Details:**

**Returns:**


Important Implementation Details and Algorithms


Interactions with Other System Components


Visual Diagram: Class Structure of Source

classDiagram
    class Source {
        - lines: list[str]
        - raw_lines: list[str]
        + __init__(obj: object = None)
        + __eq__(other: object) bool
        + __getitem__(key: int | slice) str | Source
        + __iter__() Iterator[str]
        + __len__() int
        + strip() Source
        + indent(indent: str = "    ") Source
        + getstatement(lineno: int) Source
        + getstatementrange(lineno: int) (int, int)
        + deindent() Source
        + __str__() str
    }

Summary

The [source.py](/projects/286/67491) module provides a powerful abstraction for handling Python source code fragments with precise control over indentation and statement boundaries. It leverages introspection and syntax analysis to support advanced use cases that require understanding or manipulating code structure at a granular level. The `Source` class is the core, offering immutable, sliceable, and iterable source representations, complemented by utility functions that integrate tightly with Python's introspection APIs.