API Documentation for Python Projects with focus on simplicity and automatic HTML generation from docstrings
—
Abstract syntax tree processing for extracting Python code structure, type annotations, and source code metadata. Provides deep analysis of Python source code to generate accurate documentation with proper type information and cross-references.
Parse Python objects and source code into abstract syntax trees for analysis.
def parse(obj: Any) -> ast.AST | None:
"""
Parse Python object into abstract syntax tree.
Parameters:
- obj: Any - Python object to parse (module, class, function, etc.)
Returns:
- ast.AST | None: Parsed AST node, None if parsing fails
Features:
- Handles various Python object types
- Preserves source location information
- Robust error handling for unparseable objects
"""
def unparse(tree: ast.AST) -> str:
"""
Convert AST back to Python source code.
Parameters:
- tree: ast.AST - AST node to convert
Returns:
- str: Python source code representation
Features:
- Preserves original formatting where possible
- Handles complex expressions and statements
- Compatible with various Python versions
"""Extract source code and metadata from Python objects.
def get_source(obj: Any) -> str | None:
"""
Get source code for Python object.
Parameters:
- obj: Any - Python object to get source for
Returns:
- str | None: Source code string, None if unavailable
Features:
- Works with modules, classes, functions, methods
- Handles dynamically created objects gracefully
- Preserves indentation and formatting
"""Traverse and analyze AST structures recursively.
def walk_tree(tree: ast.AST) -> Iterator[ast.AST]:
"""
Walk AST tree recursively, yielding all nodes.
Parameters:
- tree: ast.AST - Root AST node to walk
Yields:
- ast.AST: Each AST node in depth-first order
Features:
- Depth-first traversal of entire tree
- Handles all AST node types
- Preserves parent-child relationships
"""Sort documentation objects by their source code position.
def sort_by_source(items: list[Doc]) -> list[Doc]:
"""
Sort documentation objects by source file position.
Parameters:
- items: list[Doc] - Documentation objects to sort
Returns:
- list[Doc]: Sorted list in source file order
Features:
- Maintains natural source code ordering
- Handles objects from multiple files
- Preserves relative positions within files
"""Analyze TYPE_CHECKING blocks and conditional imports.
def type_checking_sections(tree: ast.AST) -> list[tuple[int, int]]:
"""
Find TYPE_CHECKING conditional blocks in AST.
Parameters:
- tree: ast.AST - Module AST to analyze
Returns:
- list[tuple[int, int]]: List of (start_line, end_line) for TYPE_CHECKING blocks
Features:
- Identifies typing.TYPE_CHECKING conditions
- Handles various conditional patterns
- Supports forward reference resolution
"""Container for AST-related metadata and analysis results.
class AstInfo:
"""
Container for AST information and metadata.
Stores parsed AST data along with source location,
type information, and analysis results.
"""
passfrom pdoc.doc_ast import parse, get_source, unparse
import ast
def analyze_function(func):
"""Analyze a function's AST structure"""
# Get source code
source = get_source(func)
if source:
print(f"Source code:\n{source}")
# Parse into AST
tree = parse(func)
if tree:
print(f"AST type: {type(tree).__name__}")
# Convert back to source
reconstructed = unparse(tree)
print(f"Reconstructed:\n{reconstructed}")
# Example usage
def example_function(x: int, y: str = "default") -> bool:
"""Example function for AST analysis."""
return len(y) > x
analyze_function(example_function)from pdoc.doc_ast import parse, walk_tree
import ast
def analyze_ast_structure(obj):
"""Walk through AST and analyze structure"""
tree = parse(obj)
if not tree:
return
node_counts = {}
for node in walk_tree(tree):
node_type = type(node).__name__
node_counts[node_type] = node_counts.get(node_type, 0) + 1
# Print function definitions
if isinstance(node, ast.FunctionDef):
print(f"Function: {node.name}")
print(f" Args: {len(node.args.args)}")
print(f" Line: {node.lineno}")
# Print class definitions
elif isinstance(node, ast.ClassDef):
print(f"Class: {node.name}")
print(f" Bases: {len(node.bases)}")
print(f" Line: {node.lineno}")
print(f"\nAST node counts: {node_counts}")
# Example usage
class ExampleClass:
def method1(self, x: int) -> str:
return str(x)
def method2(self, y: list) -> int:
return len(y)
analyze_ast_structure(ExampleClass)from pdoc.doc_ast import sort_by_source
from pdoc.doc import Module
def display_source_order(module_name: str):
"""Display module members in source file order"""
module = Module.from_name(module_name)
# Get all members as list
members = list(module.members.values())
# Sort by source position
sorted_members = sort_by_source(members)
print(f"Members of {module_name} in source order:")
for member in sorted_members:
print(f" {member.name} ({type(member).__name__})")
# Example usage
display_source_order("math")from pdoc.doc_ast import type_checking_sections, parse
import ast
def analyze_type_checking(module_source: str):
"""Analyze TYPE_CHECKING blocks in module source"""
# Parse module source
tree = ast.parse(module_source)
# Find TYPE_CHECKING sections
sections = type_checking_sections(tree)
if sections:
print("TYPE_CHECKING blocks found:")
for start, end in sections:
print(f" Lines {start}-{end}")
# Extract the conditional block
lines = module_source.split('\n')
block_lines = lines[start-1:end]
print(" Content:")
for line in block_lines:
print(f" {line}")
else:
print("No TYPE_CHECKING blocks found")
# Example module source with TYPE_CHECKING
example_source = '''
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Optional
def process_data(items):
return len(items)
'''
analyze_type_checking(example_source)from pdoc.doc_ast import parse, walk_tree, get_source
import ast
class DocstringExtractor(ast.NodeVisitor):
"""Custom AST visitor to extract docstrings"""
def __init__(self):
self.docstrings = {}
def visit_FunctionDef(self, node):
"""Visit function definitions and extract docstrings"""
docstring = ast.get_docstring(node)
if docstring:
self.docstrings[node.name] = docstring
self.generic_visit(node)
def visit_ClassDef(self, node):
"""Visit class definitions and extract docstrings"""
docstring = ast.get_docstring(node)
if docstring:
self.docstrings[node.name] = docstring
self.generic_visit(node)
def extract_all_docstrings(obj):
"""Extract all docstrings from Python object"""
source = get_source(obj)
if not source:
return {}
tree = ast.parse(source)
extractor = DocstringExtractor()
extractor.visit(tree)
return extractor.docstrings
# Example usage
class Example:
"""Class docstring"""
def method(self):
"""Method docstring"""
pass
docstrings = extract_all_docstrings(Example)
for name, doc in docstrings.items():
print(f"{name}: {doc}")from pdoc.doc_ast import parse, sort_by_source, get_source
from pdoc.doc import Module, Function, Class
def enhanced_module_analysis(module_name: str):
"""Enhanced module analysis using AST processing"""
module = Module.from_name(module_name)
# Sort members by source order
sorted_members = sort_by_source(list(module.members.values()))
print(f"Analysis of {module_name}:")
for member in sorted_members:
print(f"\n{member.name} ({type(member).__name__}):")
# Get source code
source = get_source(member.obj)
if source:
lines = len(source.split('\n'))
print(f" Source lines: {lines}")
# Parse AST for additional analysis
tree = parse(member.obj)
if tree:
if isinstance(member, Function) and isinstance(tree, ast.FunctionDef):
print(f" Parameters: {len(tree.args.args)}")
print(f" Has return annotation: {tree.returns is not None}")
elif isinstance(member, Class) and isinstance(tree, ast.ClassDef):
print(f" Base classes: {len(tree.bases)}")
methods = [n for n in tree.body if isinstance(n, ast.FunctionDef)]
print(f" Methods: {len(methods)}")
# Example usage
enhanced_module_analysis("json")The AST processing system handles various error conditions gracefully:
Install with Tessl CLI
npx tessl i tessl/pypi-pdoc