Python Clean Code Checker
A Python script that runs multiple code quality tools — mypy, ruff, bandit, and radon — and generates a unified quality report.
Description
A single script that orchestrates multiple Python code quality tools and produces a combined report. It checks types, lint rules, security issues, and complexity — all in one pass.
Features
- mypy for static type checking
- ruff for linting and formatting (replaces flake8 + isort + black)
- bandit for security vulnerability scanning
- radon for cyclomatic complexity analysis
- Unified exit code for CI — fails if any tool reports issues
The Script
#!/usr/bin/env python3
"""clean_checker.py — Unified Python code quality checker."""
import subprocess
import sys
import json
from dataclasses import dataclass, field
from pathlib import Path
@dataclass
class CheckResult:
name: str
passed: bool
issues: int = 0
output: str = ""
@dataclass
class QualityReport:
results: list[CheckResult] = field(default_factory=list)
@property
def passed(self) -> bool:
return all(r.passed for r in self.results)
@property
def total_issues(self) -> int:
return sum(r.issues for r in self.results)
def run_tool(name: str, cmd: list[str]) -> CheckResult:
"""Run a tool and capture its output."""
print(f" 🔍 Running {name}...")
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=120,
)
output = result.stdout + result.stderr
issues = output.strip().count("\n") + (1 if output.strip() else 0)
passed = result.returncode == 0
if passed:
issues = 0
return CheckResult(name=name, passed=passed, issues=issues, output=output.strip())
except FileNotFoundError:
return CheckResult(
name=name, passed=True, issues=0,
output=f"⏭️ {name} not installed, skipping",
)
except subprocess.TimeoutExpired:
return CheckResult(
name=name, passed=False, issues=1,
output=f"⏰ {name} timed out after 120s",
)
def check_types(src: str) -> CheckResult:
return run_tool("mypy", ["mypy", src, "--no-error-summary"])
def check_lint(src: str) -> CheckResult:
return run_tool("ruff", ["ruff", "check", src, "--output-format=concise"])
def check_format(src: str) -> CheckResult:
return run_tool("ruff-format", ["ruff", "format", "--check", src])
def check_security(src: str) -> CheckResult:
return run_tool("bandit", ["bandit", "-r", src, "-q", "-ll"])
def check_complexity(src: str) -> CheckResult:
"""Check cyclomatic complexity with radon — flag functions above C grade."""
result = run_tool("radon", ["radon", "cc", src, "-a", "-nc"])
if result.output and "Average complexity" in result.output:
# Parse average complexity
for line in result.output.split("\n"):
if "Average complexity" in line:
# Grade A/B = pass, C+ = fail
grade = line.strip().split("(")[-1].replace(")", "")
if grade in ("A", "B"):
result.passed = True
result.issues = 0
return result
def main() -> int:
src = sys.argv[1] if len(sys.argv) > 1 else "src"
src_path = Path(src)
if not src_path.exists():
print(f"❌ Source directory '{src}' not found")
return 1
print(f"\n🐍 Python Clean Code Checker")
print(f" Source: {src}\n")
report = QualityReport()
# Run all checks
report.results.append(check_types(src))
report.results.append(check_lint(src))
report.results.append(check_format(src))
report.results.append(check_security(src))
report.results.append(check_complexity(src))
# Print results
print("\n" + "━" * 50)
print("📊 Quality Report\n")
for r in report.results:
icon = "✅" if r.passed else "❌"
issue_str = f" ({r.issues} issue{'s' if r.issues != 1 else ''})" if r.issues > 0 else ""
print(f" {icon} {r.name}{issue_str}")
if not r.passed and r.output:
for line in r.output.split("\n")[:5]:
print(f" {line}")
remaining = r.output.count("\n") - 5
if remaining > 0:
print(f" ... and {remaining} more lines")
print("\n" + "━" * 50)
if report.passed:
print("✅ All checks passed!\n")
return 0
else:
print(f"❌ {report.total_issues} issue(s) found across {sum(1 for r in report.results if not r.passed)} check(s)\n")
return 1
if __name__ == "__main__":
sys.exit(main())
Installation
pip install mypy ruff bandit radon
# or
uv pip install mypy ruff bandit radon
pyproject.toml Config
[tool.ruff]
target-version = "py312"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "S", "B", "A", "C4", "SIM", "TCH"]
ignore = ["E501"]
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
Usage
# Run all checks
python clean_checker.py src/
# Add to CI
# "quality": "python clean_checker.py src/"