McCabe cyclomatic complexity checker that functions as a plugin for flake8
npx @tessl/cli install tessl/pypi-mccabe@0.7.0A McCabe cyclomatic complexity checker that functions as a plugin for flake8, the Python code quality tool. This package analyzes Python code to measure cyclomatic complexity, helping developers identify overly complex functions that may be difficult to maintain and test.
pip install mccabeimport mccabeFor specific functionality:
from mccabe import get_code_complexity, get_module_complexity, McCabeCheckerfrom mccabe import get_code_complexity
# Analyze a code string
code = '''
def complex_function(x):
if x > 10:
if x > 20:
return "very high"
else:
return "high"
elif x > 5:
return "medium"
else:
return "low"
'''
# Check complexity with threshold of 5
violations = get_code_complexity(code, threshold=5)
print(f"Found {violations} complexity violations")from mccabe import get_module_complexity
# Analyze a Python file
violations = get_module_complexity("my_module.py", threshold=7)
print(f"Found {violations} complexity violations in my_module.py")The package integrates seamlessly with flake8:
# Enable McCabe checking with max complexity threshold
flake8 --max-complexity 10 my_project/
# Example output:
# my_project/complex_module.py:15:1: C901 'complex_function' is too complex (12)Analyzes Python source code strings for McCabe cyclomatic complexity violations.
def get_code_complexity(code, threshold=7, filename='stdin'):
"""
Analyze code string for McCabe complexity violations.
Parameters:
- code (str): Python source code to analyze
- threshold (int): Complexity threshold, default 7
- filename (str): Filename for error reporting, default 'stdin'
Returns:
int: Number of complexity violations found
Side effects:
Prints violations to stdout in format:
"filename:line:col: C901 'function_name' is too complex (N)"
"""Analyzes Python module files for McCabe cyclomatic complexity violations.
def get_module_complexity(module_path, threshold=7):
"""
Analyze Python module file for McCabe complexity violations.
Parameters:
- module_path (str): Path to Python module file
- threshold (int): Complexity threshold, default 7
Returns:
int: Number of complexity violations found
"""Provides standalone command-line complexity analysis with optional Graphviz output.
def main(argv=None):
"""
Command-line interface for standalone complexity analysis.
Parameters:
- argv (list): Command-line arguments, default sys.argv[1:]
Command-line options:
--dot, -d: Output Graphviz DOT format for visualization
--min, -m: Minimum complexity threshold for output (default 1)
Side effects:
Prints analysis results or DOT graph to stdout
"""McCabe checker class that integrates with flake8 for automated code quality checking.
class McCabeChecker:
"""
McCabe cyclomatic complexity checker for flake8 integration.
Class attributes:
- name (str): Plugin name 'mccabe'
- version (str): Plugin version
- max_complexity (int): Complexity threshold (-1 = disabled)
"""
def __init__(self, tree, filename):
"""
Initialize checker with AST and filename.
Parameters:
- tree: Python AST tree to analyze
- filename (str): Source filename
"""
@classmethod
def add_options(cls, parser):
"""
Add command-line options to flake8 parser.
Parameters:
- parser: Flake8 option parser
Adds --max-complexity option with config file support
"""
@classmethod
def parse_options(cls, options):
"""
Parse and store flake8 options.
Parameters:
- options: Parsed options object
Sets cls.max_complexity from options.max_complexity
"""
def run(self):
"""
Run complexity check and yield violations.
Yields:
tuple: (line_number, column, message, checker_class)
Message format: "C901 'function_name' is too complex (N)"
Only yields if complexity > max_complexity threshold
"""Core classes for building and analyzing control flow graphs used in complexity calculation.
class ASTVisitor:
"""
Base class for performing depth-first walk of Python AST.
"""
def __init__(self):
"""Initialize visitor with empty cache."""
def default(self, node, *args):
"""
Default visit method that dispatches to child nodes.
Parameters:
- node: AST node to visit
- *args: Additional arguments passed to visitor methods
"""
def dispatch(self, node, *args):
"""
Dispatch to appropriate visitor method based on node type.
Parameters:
- node: AST node to dispatch
- *args: Additional arguments
Returns:
Result of visitor method call
"""
def preorder(self, tree, visitor, *args):
"""
Perform preorder walk of AST tree using visitor.
Parameters:
- tree: AST tree to walk
- visitor: Visitor object with visit methods
- *args: Additional arguments passed to visitor methods
"""
class PathNode:
"""
Represents a node in the control flow graph.
"""
def __init__(self, name, look="circle"):
"""
Initialize node with name and optional shape.
Parameters:
- name (str): Node name/label
- look (str): Node shape for DOT output, default "circle"
"""
def to_dot(self):
"""Output node in Graphviz DOT format to stdout."""
def dot_id(self):
"""
Return unique ID for DOT output.
Returns:
int: Unique node identifier
"""
class PathGraph:
"""
Represents a control flow graph for complexity calculation.
"""
def __init__(self, name, entity, lineno, column=0):
"""
Initialize graph with metadata.
Parameters:
- name (str): Graph name
- entity (str): Associated code entity (function/class name)
- lineno (int): Line number in source
- column (int): Column number in source, default 0
"""
def connect(self, n1, n2):
"""
Connect two nodes in the graph.
Parameters:
- n1 (PathNode): Source node
- n2 (PathNode): Destination node
"""
def to_dot(self):
"""Output graph in Graphviz DOT format to stdout."""
def complexity(self):
"""
Calculate McCabe complexity using V-E+2 formula.
Returns:
int: McCabe complexity score
"""
class PathGraphingAstVisitor(ASTVisitor):
"""
AST visitor that builds control flow graphs for complexity analysis.
Inherits from ASTVisitor.
"""
def __init__(self):
"""Initialize visitor with empty state and graphs dictionary."""
def reset(self):
"""Reset visitor state for new analysis."""
def dispatch_list(self, node_list):
"""
Process list of AST nodes.
Parameters:
- node_list (list): List of AST nodes to process
"""
def visitFunctionDef(self, node):
"""
Process function definitions to build control flow graphs.
Parameters:
- node: FunctionDef AST node
"""
def visitAsyncFunctionDef(self, node):
"""
Process async function definitions.
Parameters:
- node: AsyncFunctionDef AST node
"""
def visitClassDef(self, node):
"""
Process class definitions.
Parameters:
- node: ClassDef AST node
"""
def visitIf(self, node):
"""
Process if statements for branching complexity.
Parameters:
- node: If AST node
"""
def visitLoop(self, node):
"""
Process loop constructs for cyclomatic complexity.
Parameters:
- node: Loop AST node (For, While, AsyncFor)
"""
def visitFor(self, node):
"""Process for loops. Alias for visitLoop."""
def visitAsyncFor(self, node):
"""Process async for loops. Alias for visitLoop."""
def visitWhile(self, node):
"""Process while loops. Alias for visitLoop."""
def visitTryExcept(self, node):
"""
Process try/except blocks for exception handling complexity.
Parameters:
- node: Try AST node
"""
def visitTry(self, node):
"""Process try blocks. Alias for visitTryExcept."""
def visitWith(self, node):
"""
Process with statements.
Parameters:
- node: With AST node
"""
def visitAsyncWith(self, node):
"""
Process async with statements.
Parameters:
- node: AsyncWith AST node
"""__version__ = '0.7.0' # Package version string"C901 'function_name' is too complex (N)"# noqa: C901 commentAccording to McCabe's original research:
# Basic complexity analysis
python -m mccabe my_script.py
# Set minimum complexity threshold
python -m mccabe --min 5 my_script.py
# Generate Graphviz DOT output for visualization
python -m mccabe --dot --min 3 my_script.py > complexity.dot
dot -Tpng complexity.dot -o complexity.png# Add to pre-commit hooks
flake8 --max-complexity 10 src/
# Configure in setup.cfg or tox.ini
[flake8]
max-complexity = 10
# Configure in pyproject.toml (for flake8 5.0+)
[tool.flake8]
max-complexity = 10