McCabe cyclomatic complexity checker that functions as a plugin for flake8
npx @tessl/cli install tessl/pypi-mccabe@0.7.00
# McCabe
1
2
A 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.
3
4
## Package Information
5
6
- **Package Name**: mccabe
7
- **Language**: Python
8
- **Installation**: `pip install mccabe`
9
10
## Core Imports
11
12
```python
13
import mccabe
14
```
15
16
For specific functionality:
17
18
```python
19
from mccabe import get_code_complexity, get_module_complexity, McCabeChecker
20
```
21
22
## Basic Usage
23
24
### Analyzing Code Complexity
25
26
```python
27
from mccabe import get_code_complexity
28
29
# Analyze a code string
30
code = '''
31
def complex_function(x):
32
if x > 10:
33
if x > 20:
34
return "very high"
35
else:
36
return "high"
37
elif x > 5:
38
return "medium"
39
else:
40
return "low"
41
'''
42
43
# Check complexity with threshold of 5
44
violations = get_code_complexity(code, threshold=5)
45
print(f"Found {violations} complexity violations")
46
```
47
48
### Analyzing Python Files
49
50
```python
51
from mccabe import get_module_complexity
52
53
# Analyze a Python file
54
violations = get_module_complexity("my_module.py", threshold=7)
55
print(f"Found {violations} complexity violations in my_module.py")
56
```
57
58
### Using as Flake8 Plugin
59
60
The package integrates seamlessly with flake8:
61
62
```bash
63
# Enable McCabe checking with max complexity threshold
64
flake8 --max-complexity 10 my_project/
65
66
# Example output:
67
# my_project/complex_module.py:15:1: C901 'complex_function' is too complex (12)
68
```
69
70
## Capabilities
71
72
### Code Complexity Analysis
73
74
Analyzes Python source code strings for McCabe cyclomatic complexity violations.
75
76
```python { .api }
77
def get_code_complexity(code, threshold=7, filename='stdin'):
78
"""
79
Analyze code string for McCabe complexity violations.
80
81
Parameters:
82
- code (str): Python source code to analyze
83
- threshold (int): Complexity threshold, default 7
84
- filename (str): Filename for error reporting, default 'stdin'
85
86
Returns:
87
int: Number of complexity violations found
88
89
Side effects:
90
Prints violations to stdout in format:
91
"filename:line:col: C901 'function_name' is too complex (N)"
92
"""
93
```
94
95
### Module Complexity Analysis
96
97
Analyzes Python module files for McCabe cyclomatic complexity violations.
98
99
```python { .api }
100
def get_module_complexity(module_path, threshold=7):
101
"""
102
Analyze Python module file for McCabe complexity violations.
103
104
Parameters:
105
- module_path (str): Path to Python module file
106
- threshold (int): Complexity threshold, default 7
107
108
Returns:
109
int: Number of complexity violations found
110
"""
111
```
112
113
### Command Line Interface
114
115
Provides standalone command-line complexity analysis with optional Graphviz output.
116
117
```python { .api }
118
def main(argv=None):
119
"""
120
Command-line interface for standalone complexity analysis.
121
122
Parameters:
123
- argv (list): Command-line arguments, default sys.argv[1:]
124
125
Command-line options:
126
--dot, -d: Output Graphviz DOT format for visualization
127
--min, -m: Minimum complexity threshold for output (default 1)
128
129
Side effects:
130
Prints analysis results or DOT graph to stdout
131
"""
132
```
133
134
### Flake8 Plugin Integration
135
136
McCabe checker class that integrates with flake8 for automated code quality checking.
137
138
```python { .api }
139
class McCabeChecker:
140
"""
141
McCabe cyclomatic complexity checker for flake8 integration.
142
143
Class attributes:
144
- name (str): Plugin name 'mccabe'
145
- version (str): Plugin version
146
- max_complexity (int): Complexity threshold (-1 = disabled)
147
"""
148
149
def __init__(self, tree, filename):
150
"""
151
Initialize checker with AST and filename.
152
153
Parameters:
154
- tree: Python AST tree to analyze
155
- filename (str): Source filename
156
"""
157
158
@classmethod
159
def add_options(cls, parser):
160
"""
161
Add command-line options to flake8 parser.
162
163
Parameters:
164
- parser: Flake8 option parser
165
166
Adds --max-complexity option with config file support
167
"""
168
169
@classmethod
170
def parse_options(cls, options):
171
"""
172
Parse and store flake8 options.
173
174
Parameters:
175
- options: Parsed options object
176
177
Sets cls.max_complexity from options.max_complexity
178
"""
179
180
def run(self):
181
"""
182
Run complexity check and yield violations.
183
184
Yields:
185
tuple: (line_number, column, message, checker_class)
186
187
Message format: "C901 'function_name' is too complex (N)"
188
Only yields if complexity > max_complexity threshold
189
"""
190
```
191
192
### Control Flow Graph Classes
193
194
Core classes for building and analyzing control flow graphs used in complexity calculation.
195
196
```python { .api }
197
class ASTVisitor:
198
"""
199
Base class for performing depth-first walk of Python AST.
200
"""
201
202
def __init__(self):
203
"""Initialize visitor with empty cache."""
204
205
def default(self, node, *args):
206
"""
207
Default visit method that dispatches to child nodes.
208
209
Parameters:
210
- node: AST node to visit
211
- *args: Additional arguments passed to visitor methods
212
"""
213
214
def dispatch(self, node, *args):
215
"""
216
Dispatch to appropriate visitor method based on node type.
217
218
Parameters:
219
- node: AST node to dispatch
220
- *args: Additional arguments
221
222
Returns:
223
Result of visitor method call
224
"""
225
226
def preorder(self, tree, visitor, *args):
227
"""
228
Perform preorder walk of AST tree using visitor.
229
230
Parameters:
231
- tree: AST tree to walk
232
- visitor: Visitor object with visit methods
233
- *args: Additional arguments passed to visitor methods
234
"""
235
236
class PathNode:
237
"""
238
Represents a node in the control flow graph.
239
"""
240
241
def __init__(self, name, look="circle"):
242
"""
243
Initialize node with name and optional shape.
244
245
Parameters:
246
- name (str): Node name/label
247
- look (str): Node shape for DOT output, default "circle"
248
"""
249
250
def to_dot(self):
251
"""Output node in Graphviz DOT format to stdout."""
252
253
def dot_id(self):
254
"""
255
Return unique ID for DOT output.
256
257
Returns:
258
int: Unique node identifier
259
"""
260
261
class PathGraph:
262
"""
263
Represents a control flow graph for complexity calculation.
264
"""
265
266
def __init__(self, name, entity, lineno, column=0):
267
"""
268
Initialize graph with metadata.
269
270
Parameters:
271
- name (str): Graph name
272
- entity (str): Associated code entity (function/class name)
273
- lineno (int): Line number in source
274
- column (int): Column number in source, default 0
275
"""
276
277
def connect(self, n1, n2):
278
"""
279
Connect two nodes in the graph.
280
281
Parameters:
282
- n1 (PathNode): Source node
283
- n2 (PathNode): Destination node
284
"""
285
286
def to_dot(self):
287
"""Output graph in Graphviz DOT format to stdout."""
288
289
def complexity(self):
290
"""
291
Calculate McCabe complexity using V-E+2 formula.
292
293
Returns:
294
int: McCabe complexity score
295
"""
296
297
class PathGraphingAstVisitor(ASTVisitor):
298
"""
299
AST visitor that builds control flow graphs for complexity analysis.
300
Inherits from ASTVisitor.
301
"""
302
303
def __init__(self):
304
"""Initialize visitor with empty state and graphs dictionary."""
305
306
def reset(self):
307
"""Reset visitor state for new analysis."""
308
309
def dispatch_list(self, node_list):
310
"""
311
Process list of AST nodes.
312
313
Parameters:
314
- node_list (list): List of AST nodes to process
315
"""
316
317
def visitFunctionDef(self, node):
318
"""
319
Process function definitions to build control flow graphs.
320
321
Parameters:
322
- node: FunctionDef AST node
323
"""
324
325
def visitAsyncFunctionDef(self, node):
326
"""
327
Process async function definitions.
328
329
Parameters:
330
- node: AsyncFunctionDef AST node
331
"""
332
333
def visitClassDef(self, node):
334
"""
335
Process class definitions.
336
337
Parameters:
338
- node: ClassDef AST node
339
"""
340
341
def visitIf(self, node):
342
"""
343
Process if statements for branching complexity.
344
345
Parameters:
346
- node: If AST node
347
"""
348
349
def visitLoop(self, node):
350
"""
351
Process loop constructs for cyclomatic complexity.
352
353
Parameters:
354
- node: Loop AST node (For, While, AsyncFor)
355
"""
356
357
def visitFor(self, node):
358
"""Process for loops. Alias for visitLoop."""
359
360
def visitAsyncFor(self, node):
361
"""Process async for loops. Alias for visitLoop."""
362
363
def visitWhile(self, node):
364
"""Process while loops. Alias for visitLoop."""
365
366
def visitTryExcept(self, node):
367
"""
368
Process try/except blocks for exception handling complexity.
369
370
Parameters:
371
- node: Try AST node
372
"""
373
374
def visitTry(self, node):
375
"""Process try blocks. Alias for visitTryExcept."""
376
377
def visitWith(self, node):
378
"""
379
Process with statements.
380
381
Parameters:
382
- node: With AST node
383
"""
384
385
def visitAsyncWith(self, node):
386
"""
387
Process async with statements.
388
389
Parameters:
390
- node: AsyncWith AST node
391
"""
392
```
393
394
## Constants
395
396
```python { .api }
397
__version__ = '0.7.0' # Package version string
398
```
399
400
## Error Codes
401
402
- **C901**: McCabe complexity violation - function exceeds complexity threshold
403
- Format: `"C901 'function_name' is too complex (N)"`
404
- Reported at function definition line
405
- Can be suppressed with `# noqa: C901` comment
406
407
## Usage Patterns
408
409
### McCabe Complexity Guidelines
410
411
According to McCabe's original research:
412
- **1-10**: Simple, well-structured code
413
- **11-20**: Moderately complex, may need refactoring
414
- **21-50**: Complex, difficult to test and maintain
415
- **>50**: Extremely complex, high risk of defects
416
417
### Standalone Script Usage
418
419
```bash
420
# Basic complexity analysis
421
python -m mccabe my_script.py
422
423
# Set minimum complexity threshold
424
python -m mccabe --min 5 my_script.py
425
426
# Generate Graphviz DOT output for visualization
427
python -m mccabe --dot --min 3 my_script.py > complexity.dot
428
dot -Tpng complexity.dot -o complexity.png
429
```
430
431
### Integration with Development Workflow
432
433
```bash
434
# Add to pre-commit hooks
435
flake8 --max-complexity 10 src/
436
437
# Configure in setup.cfg or tox.ini
438
[flake8]
439
max-complexity = 10
440
441
# Configure in pyproject.toml (for flake8 5.0+)
442
[tool.flake8]
443
max-complexity = 10
444
```