python Python November 10, 2025

Python Clean Code Checker

A Python script that runs multiple code quality tools — mypy, ruff, bandit, and radon — and generates a unified quality report.

pythonmypyruffcode-qualitylinting

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/"