Interrogate a codebase for docstring coverage.
—
Core functionality for analyzing docstring coverage in Python codebases. The coverage analysis engine traverses Python Abstract Syntax Trees (AST) to identify missing docstrings across modules, classes, functions, and methods.
The primary class for running docstring coverage analysis on Python files and directories.
class InterrogateCoverage:
"""Main class for analyzing docstring coverage."""
# Class constants
COMMON_EXCLUDE: List[str] = [".tox", ".venv", "venv", ".git", ".hg"]
VALID_EXT: List[str] = [".py", ".pyi"]
def __init__(self, paths, conf=None, excluded=None, extensions=None):
"""
Initialize coverage analyzer.
Args:
paths: List of file/directory paths to analyze
conf: Configuration object with analysis options (optional)
excluded: Tuple of files and directories to exclude (optional)
extensions: Set of file extensions to analyze (optional)
"""
def get_filenames_from_paths(self) -> List[str]:
"""
Get all Python filenames from configured paths.
Returns:
List[str]: Python file paths to analyze
"""
def get_coverage(self) -> InterrogateResults:
"""
Analyze docstring coverage for configured paths.
Returns:
InterrogateResults: Aggregated coverage results
"""
def print_results(self, results: InterrogateResults, output_file: Optional[str] = None) -> None:
"""
Print formatted coverage results.
Args:
results: Coverage analysis results
output_file: Optional file path for output (defaults to stdout)
"""Data structures for storing and managing coverage analysis results.
class BaseInterrogateResult:
"""Base class for coverage results."""
total: int # Total number of items analyzed
covered: int # Number of items with docstrings
missing: int # Number of items missing docstrings
perc_covered: float # Percentage coverage (0-100)
class InterrogateFileResult(BaseInterrogateResult):
"""Coverage results for a single file."""
filename: str # Path to analyzed file
ignore_module: bool # Whether module-level docstring was ignored
nodes: List[CovNode] # List of analyzed AST nodes
def combine(self) -> None:
"""
Tally results from each AST node visited.
Updates the total, covered, and missing counts based on nodes.
"""
class InterrogateResults(BaseInterrogateResult):
"""Aggregated coverage results for all analyzed files."""
ret_code: int # Exit code based on coverage
file_results: Dict[str, InterrogateFileResult] # Per-file results
def combine(self) -> None:
"""
Tally results from each file.
Updates the total, covered, and missing counts from all file results.
"""Data structures representing coverage information for individual code elements.
class CovNode:
"""Coverage information for an AST node."""
name: str # Name of the code element
path: str # File path containing the element
level: int # Nesting level (0 = module, 1 = class/function, etc.)
lineno: int # Line number in source file
covered: bool # Whether element has docstring
node_type: str # Type of AST node ("module", "class", "function", "method")
is_nested_func: bool # Whether function is nested inside another function
is_nested_cls: bool # Whether class is nested inside another class
parent: Optional[CovNode] # Parent node (for nested elements)The visitor class that traverses Python AST to collect docstring coverage information.
class CoverageVisitor(ast.NodeVisitor):
"""AST visitor for collecting docstring coverage data."""
def __init__(self, filename: str, config: InterrogateConfig):
"""
Initialize AST visitor.
Args:
filename: Path to file being analyzed
config: Configuration with analysis options
"""
def visit_Module(self, node: ast.Module) -> None:
"""Visit module-level node."""
def visit_ClassDef(self, node: ast.ClassDef) -> None:
"""Visit class definition node."""
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""Visit function definition node."""
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
"""Visit async function definition node."""from interrogate.coverage import InterrogateCoverage
from interrogate.config import InterrogateConfig
# Configure analysis
config = InterrogateConfig(
ignore_init_method=True,
ignore_private=True,
fail_under=80.0
)
# Run analysis
coverage = InterrogateCoverage(["src/myproject"], config)
results = coverage.get_coverage()
# Check results
print(f"Coverage: {results.perc_covered:.1f}%")
print(f"Files analyzed: {len(results.file_results)}")
print(f"Missing docstrings: {results.missing}")
# Print detailed results
coverage.print_results(results)from interrogate.coverage import InterrogateCoverage
from interrogate.config import InterrogateConfig
config = InterrogateConfig()
coverage = InterrogateCoverage(["src/"], config)
results = coverage.get_coverage()
# Process individual file results
for filename, file_result in results.file_results.items():
print(f"{filename}: {file_result.perc_covered:.1f}% coverage")
# Examine individual nodes
for node in file_result.nodes:
if not node.covered:
print(f" Missing docstring: {node.name} ({node.node_type}) at line {node.lineno}")
# Check if coverage meets threshold
if results.ret_code != 0:
print(f"Coverage {results.perc_covered:.1f}% below required threshold")from interrogate.coverage import InterrogateCoverage
from interrogate.config import InterrogateConfig
# Create coverage analyzer
config = InterrogateConfig()
coverage = InterrogateCoverage(["src/"], config)
# Get all Python files that would be analyzed
python_files = list(coverage.get_filenames_from_paths())
print(f"Files to analyze: {len(python_files)}")
for filepath in python_files:
print(f" {filepath}")Install with Tessl CLI
npx tessl i tessl/pypi-interrogate