Code Metrics in Python - comprehensive tool for computing various software metrics
—
Comprehensive CLI with four analysis commands (cc, raw, mi, hal) supporting multiple output formats, configuration files, and batch processing capabilities. Designed for integration with CI/CD pipelines, code quality tools, and automated analysis workflows.
Central program instance and configuration management for all radon commands.
# Main program instance (mando.Program)
program = Program(version=radon.__version__)
class Config:
"""
Configuration object for analysis parameters.
Stores analysis settings and provides access to configuration values
through attribute access and keyword parameters.
"""
def __init__(self, **kwargs):
"""
Create configuration with keyword parameters.
Parameters:
- **kwargs: Configuration values as keyword arguments
"""
@classmethod
def from_function(cls, func):
"""
Create Config object from function defaults.
Extracts default parameter values from a function's signature
to create a configuration object.
Parameters:
- func: Function with default parameters
Returns:
Config: Configuration object with function defaults
"""
class FileConfig:
"""
File-based configuration reader supporting multiple formats.
Reads configuration from setup.cfg, radon.cfg, pyproject.toml,
and environment variables.
"""
def get_value(self, key, type, default):
"""
Get configuration value with type conversion.
Parameters:
- key (str): Configuration key name
- type: Target type (int, bool, str)
- default: Default value if key not found
Returns:
Converted configuration value or default
"""
@staticmethod
def file_config():
"""
Read configuration from various file sources.
Checks for configuration in:
- Environment variable RADONCFG
- radon.cfg in current directory
- pyproject.toml [tool.radon] section
- setup.cfg [radon] section
- ~/.radon.cfg in user home directory
Returns:
configparser.ConfigParser: Merged configuration
"""
@staticmethod
def toml_config():
"""
Read configuration from pyproject.toml file.
Returns:
dict: Configuration dictionary from TOML file
"""Core commands for different types of code analysis.
@program.command
def cc(paths, min='A', max='F', show_complexity=False, average=False,
exclude=None, ignore=None, order='SCORE', json=False,
no_assert=False, show_closures=False, total_average=False,
xml=False, md=False, codeclimate=False, output_file=None,
include_ipynb=False, ipynb_cells=False):
"""
Analyze cyclomatic complexity (CC) of Python modules.
Computes McCabe's cyclomatic complexity and provides A-F rankings
for functions, methods, and classes.
Parameters:
- paths (list): Paths to Python files or directories to analyze
- min (str): Minimum complexity grade to display ('A'-'F')
- max (str): Maximum complexity grade to display ('A'-'F')
- show_complexity (bool): Show numeric complexity scores
- average (bool): Display average complexity at end
- exclude (str): Glob patterns for files to exclude
- ignore (str): Glob patterns for directories to ignore
- order (str): Sort order ('SCORE', 'LINES', 'ALPHA')
- json (bool): Output results as JSON
- no_assert (bool): Don't count assert statements
- show_closures (bool): Include nested functions/classes
- total_average (bool): Average unfiltered by min/max
- xml (bool): Output results as XML (CCM compatible)
- md (bool): Output results as Markdown
- codeclimate (bool): Output Code Climate format
- output_file (str): Write output to file instead of stdout
- include_ipynb (bool): Include Jupyter notebook files
- ipynb_cells (bool): Report individual notebook cells
"""
@program.command
def raw(paths, exclude=None, ignore=None, summary=False, json=False,
output_file=None, include_ipynb=False, ipynb_cells=False):
"""
Analyze raw code metrics (LOC, LLOC, comments, etc.).
Computes lines of code, logical lines, comments, blank lines,
and other basic code statistics.
Parameters:
- paths (list): Paths to Python files or directories to analyze
- exclude (str): Glob patterns for files to exclude
- ignore (str): Glob patterns for directories to ignore
- summary (bool): Display summary statistics
- json (bool): Output results as JSON
- output_file (str): Write output to file instead of stdout
- include_ipynb (bool): Include Jupyter notebook files
- ipynb_cells (bool): Report individual notebook cells
"""
@program.command
def mi(paths, min='A', max='C', multi=True, exclude=None, ignore=None,
show=False, json=False, sort=False, output_file=None,
include_ipynb=False, ipynb_cells=False):
"""
Analyze Maintainability Index of Python modules.
Computes compound maintainability metric combining Halstead volume,
cyclomatic complexity, and lines of code.
Parameters:
- paths (list): Paths to Python files or directories to analyze
- min (str): Minimum MI grade to display ('A', 'B', 'C')
- max (str): Maximum MI grade to display ('A', 'B', 'C')
- multi (bool): Count multi-line strings as comments
- exclude (str): Glob patterns for files to exclude
- ignore (str): Glob patterns for directories to ignore
- show (bool): Show actual MI numeric values
- json (bool): Output results as JSON
- sort (bool): Sort results in ascending order
- output_file (str): Write output to file instead of stdout
- include_ipynb (bool): Include Jupyter notebook files
- ipynb_cells (bool): Report individual notebook cells
"""
@program.command
def hal(paths, exclude=None, ignore=None, json=False, functions=False,
output_file=None, include_ipynb=False, ipynb_cells=False):
"""
Analyze Halstead complexity metrics of Python modules.
Computes software science metrics including volume, difficulty,
effort, time estimates, and bug predictions.
Parameters:
- paths (list): Paths to Python files or directories to analyze
- exclude (str): Glob patterns for files to exclude
- ignore (str): Glob patterns for directories to ignore
- json (bool): Output results as JSON
- functions (bool): Analyze by top-level functions instead of files
- output_file (str): Write output to file instead of stdout
- include_ipynb (bool): Include Jupyter notebook files
- ipynb_cells (bool): Report individual notebook cells
"""Functions for formatting and displaying analysis results.
def log_result(harvester, **kwargs):
"""
Log results from a Harvester object with format selection.
Automatically selects output format based on keyword arguments
and delegates to appropriate formatter.
Parameters:
- harvester: Harvester instance with analysis results
- json (bool): Format as JSON
- xml (bool): Format as XML
- md (bool): Format as Markdown
- codeclimate (bool): Format for Code Climate
- stream: Output stream (default stdout)
- **kwargs: Additional formatting options
"""
def log(msg, *args, **kwargs):
"""
Log a message with formatting and indentation support.
Parameters:
- msg (str): Message template
- *args: Arguments for message formatting
- indent (int): Indentation level (4 spaces per level)
- delimiter (str): Line delimiter (default newline)
- noformat (bool): Skip string formatting
- stream: Output stream (default stdout)
"""
def log_list(lst, *args, **kwargs):
"""
Log a list of messages line by line.
Parameters:
- lst (list): List of messages to log
- *args, **kwargs: Passed to log() function
"""
def log_error(msg, *args, **kwargs):
"""
Log an error message with colored formatting.
Parameters:
- msg (str): Error message
- *args, **kwargs: Passed to log() function
"""
@contextmanager
def outstream(outfile=None):
"""
Context manager for output stream management.
Parameters:
- outfile (str): Output filename, or None for stdout
Yields:
file: Open file handle or stdout
"""# Basic complexity analysis
radon cc src/
# Show numeric complexity scores
radon cc --show-complexity src/
# Filter by complexity range
radon cc --min B --max F src/
# Include average complexity
radon cc --average src/
# Sort by different criteria
radon cc --order LINES src/ # Sort by line number
radon cc --order ALPHA src/ # Sort alphabetically
# Different output formats
radon cc --json src/ # JSON output
radon cc --xml src/ # XML output (CCM compatible)
radon cc --md src/ # Markdown output
radon cc --codeclimate src/ # Code Climate format
# Save to file
radon cc --output-file complexity.json --json src/
# Include Jupyter notebooks
radon cc --include-ipynb --ipynb-cells notebooks/# Basic raw metrics
radon raw src/
# Show summary statistics
radon raw --summary src/
# JSON output for processing
radon raw --json src/
# Exclude test files
radon raw --exclude "*/tests/*" src/
# Ignore specific directories
radon raw --ignore "__pycache__,*.egg-info" src/
# Include Jupyter notebooks
radon raw --include-ipynb notebooks/# Basic MI analysis
radon mi src/
# Show numeric MI values
radon mi --show src/
# Filter by maintainability grade
radon mi --min B --max A src/ # Only high maintainability
# Sort results
radon mi --sort src/
# Don't count docstrings as comments
radon mi --no-multi src/
# JSON output
radon mi --json src/# Basic Halstead analysis
radon hal src/
# Analyze by functions instead of files
radon hal --functions src/
# JSON output for detailed metrics
radon hal --json src/
# Save detailed analysis
radon hal --output-file halstead.json --json src/Radon supports configuration through multiple file formats:
[radon]
exclude = */tests/*,*/migrations/*
ignore = __pycache__,.git
cc_min = B
cc_max = F
show_complexity = true
average = true[tool.radon]
exclude = "*/tests/*"
ignore = "__pycache__"
cc_min = "B"
show_complexity = true
average = true[radon]
exclude = build/*,dist/*
cc_min = A
cc_max = F
show_complexity = trueUsing the CLI components from Python code:
from radon.cli import Config, program
from radon.cli.harvest import CCHarvester
from radon.cli.tools import iter_filenames
import sys
# Create configuration
config = Config(
min='B',
max='F',
show_complexity=True,
exclude='*/tests/*',
order='SCORE'
)
# Use file discovery
paths = ['src/']
filenames = list(iter_filenames(paths, exclude=config.exclude))
print(f"Found {len(filenames)} Python files")
# Run complexity analysis
harvester = CCHarvester(paths, config)
harvester.run()
# Get results programmatically
results = harvester.results
for filename, analysis in results.items():
print(f"{filename}: {len(analysis)} blocks analyzed")
# Format as JSON
json_output = harvester.as_json()
print("JSON output generated")
# Format for terminal
for msg, args, kwargs in harvester.to_terminal():
if not kwargs.get('error', False):
print(msg.format(*args) if args else msg)from radon.cli.harvest import RawHarvester, MIHarvester, HCHarvester
from radon.cli import Config
config = Config(summary=True, exclude='*/tests/*')
# Raw metrics harvester
raw_harvester = RawHarvester(['src/'], config)
raw_harvester.run()
print("Raw metrics summary:")
raw_results = raw_harvester.results
total_loc = sum(metrics.loc for metrics in raw_results.values())
total_sloc = sum(metrics.sloc for metrics in raw_results.values())
print(f"Total LOC: {total_loc}")
print(f"Total SLOC: {total_sloc}")
# Maintainability index harvester
mi_config = Config(show=True, sort=True)
mi_harvester = MIHarvester(['src/'], mi_config)
mi_harvester.run()
# Halstead harvester
hal_config = Config(by_function=True)
hal_harvester = HCHarvester(['src/'], hal_config)
hal_harvester.run()#!/bin/bash
# ci-quality-check.sh
# Check complexity - fail if any function/class has F grade
if radon cc --min F --json src/ | grep -q '"rank": "F"'; then
echo "Error: Code contains F-grade complexity"
exit 1
fi
# Check maintainability - fail if average MI is too low
MI_SCORE=$(radon mi --json src/ | jq '.[] | .mi' | awk '{sum+=$1; count++} END {print sum/count}')
if (( $(echo "$MI_SCORE < 20" | bc -l) )); then
echo "Error: Maintainability index too low: $MI_SCORE"
exit 1
fi
echo "Code quality checks passed"import json
import subprocess
from pathlib import Path
def generate_quality_report(src_path):
"""Generate comprehensive code quality report."""
# Run all radon analyses
analyses = {}
# Complexity analysis
cc_result = subprocess.run(
['radon', 'cc', '--json', src_path],
capture_output=True, text=True
)
analyses['complexity'] = json.loads(cc_result.stdout)
# Raw metrics
raw_result = subprocess.run(
['radon', 'raw', '--json', src_path],
capture_output=True, text=True
)
analyses['raw'] = json.loads(raw_result.stdout)
# Maintainability index
mi_result = subprocess.run(
['radon', 'mi', '--json', src_path],
capture_output=True, text=True
)
analyses['maintainability'] = json.loads(mi_result.stdout)
# Generate summary report
report = {
'total_files': len(analyses['raw']),
'total_loc': sum(m['loc'] for m in analyses['raw'].values()),
'total_sloc': sum(m['sloc'] for m in analyses['raw'].values()),
'avg_complexity': calculate_avg_complexity(analyses['complexity']),
'avg_maintainability': calculate_avg_mi(analyses['maintainability']),
'quality_grade': determine_quality_grade(analyses)
}
return report
def calculate_avg_complexity(cc_data):
"""Calculate average complexity across all functions."""
total_complexity = 0
total_functions = 0
for file_data in cc_data.values():
for block in file_data:
total_complexity += block['complexity']
total_functions += 1
return total_complexity / total_functions if total_functions > 0 else 0
def calculate_avg_mi(mi_data):
"""Calculate average maintainability index."""
scores = [data['mi'] for data in mi_data.values()]
return sum(scores) / len(scores) if scores else 0
def determine_quality_grade(analyses):
"""Determine overall quality grade."""
avg_complexity = calculate_avg_complexity(analyses['complexity'])
avg_mi = calculate_avg_mi(analyses['maintainability'])
if avg_complexity <= 5 and avg_mi >= 20:
return 'A'
elif avg_complexity <= 10 and avg_mi >= 15:
return 'B'
elif avg_complexity <= 20 and avg_mi >= 10:
return 'C'
else:
return 'D'
# Usage
# report = generate_quality_report('src/')
# print(f"Quality Grade: {report['quality_grade']}")The CLI handles various error conditions gracefully:
# Enable verbose output for troubleshooting
RADON_DEBUG=1 radon cc src/
# Check configuration loading
radon cc --help # Shows all available options and defaults--exclude and --ignore to skip unnecessary filesThe CLI provides a comprehensive interface for all radon analysis capabilities while maintaining compatibility with automated workflows and integration tools.
Install with Tessl CLI
npx tessl i tessl/pypi-radon