Finance / modules /code_cleaner.py
BOLO-KESARI
Initial commit for deployment
d2426db
"""
Code Cleaner Module - Automated code formatting and cleaning.
This module handles:
- Code formatting with Black
- Import sorting with isort
- Removing unused imports with autoflake
- Fixing trailing whitespace and line endings
"""
import os
import subprocess
import logging
from pathlib import Path
from typing import List, Dict, Any
logger = logging.getLogger(__name__)
class CodeCleaner:
"""Automated code cleaning and formatting."""
def __init__(self, config: Dict[str, Any], project_root: str):
"""
Initialize the code cleaner.
Args:
config: Configuration dictionary for code cleaner
project_root: Root directory of the project
"""
self.config = config
self.project_root = Path(project_root)
self.results = {
"formatted_files": [],
"sorted_imports": [],
"removed_unused": [],
"errors": [],
"total_files_processed": 0
}
def run(self, dry_run: bool = False) -> Dict[str, Any]:
"""
Run all code cleaning operations.
Args:
dry_run: If True, preview changes without applying them
Returns:
Dictionary containing results of cleaning operations
"""
logger.info("Starting code cleaning...")
if not self.config.get("enabled", True):
logger.info("Code cleaner is disabled in configuration")
return self.results
# Find all Python files
python_files = self._find_python_files()
self.results["total_files_processed"] = len(python_files)
logger.info(f"Found {len(python_files)} Python files to process")
# Run Black formatter
if self.config.get("format_with_black", True):
self._run_black(python_files, dry_run)
# Sort imports with isort
if self.config.get("sort_imports", True):
self._run_isort(python_files, dry_run)
# Remove unused imports
if self.config.get("remove_unused_imports", True):
self._run_autoflake(python_files, dry_run)
logger.info("Code cleaning completed")
return self.results
def _find_python_files(self) -> List[Path]:
"""Find all Python files in the project."""
python_files = []
exclude_dirs = set(self.config.get("exclude_dirs", []))
exclude_files = self.config.get("exclude_files", [])
for root, dirs, files in os.walk(self.project_root):
# Remove excluded directories from search
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for file in files:
if file.endswith('.py'):
# Check if file matches exclude patterns
if not any(file.endswith(pattern.replace('*', '')) for pattern in exclude_files):
python_files.append(Path(root) / file)
return python_files
def _run_black(self, files: List[Path], dry_run: bool):
"""Run Black formatter on Python files."""
logger.info("Running Black formatter...")
try:
line_length = self.config.get("line_length", 100)
cmd = ["black"]
if dry_run:
cmd.append("--check")
cmd.extend([
"--line-length", str(line_length),
*[str(f) for f in files]
])
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=self.project_root
)
if result.returncode == 0:
if not dry_run:
self.results["formatted_files"] = [str(f) for f in files]
logger.info(f"Successfully formatted {len(files)} files with Black")
else:
logger.info("Black check passed - all files are properly formatted")
else:
if dry_run:
logger.warning("Some files would be reformatted by Black")
logger.debug(result.stdout)
else:
logger.error(f"Black formatting failed: {result.stderr}")
self.results["errors"].append(f"Black error: {result.stderr}")
except FileNotFoundError:
error_msg = "Black is not installed. Install with: pip install black"
logger.error(error_msg)
self.results["errors"].append(error_msg)
except Exception as e:
logger.error(f"Error running Black: {str(e)}")
self.results["errors"].append(f"Black error: {str(e)}")
def _run_isort(self, files: List[Path], dry_run: bool):
"""Run isort to sort imports."""
logger.info("Running isort for import sorting...")
try:
cmd = ["isort"]
if dry_run:
cmd.append("--check-only")
cmd.extend([
"--profile", "black", # Compatible with Black
*[str(f) for f in files]
])
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=self.project_root
)
if result.returncode == 0:
if not dry_run:
self.results["sorted_imports"] = [str(f) for f in files]
logger.info(f"Successfully sorted imports in {len(files)} files")
else:
logger.info("isort check passed - all imports are properly sorted")
else:
if dry_run:
logger.warning("Some imports would be reordered by isort")
logger.debug(result.stdout)
except FileNotFoundError:
error_msg = "isort is not installed. Install with: pip install isort"
logger.error(error_msg)
self.results["errors"].append(error_msg)
except Exception as e:
logger.error(f"Error running isort: {str(e)}")
self.results["errors"].append(f"isort error: {str(e)}")
def _run_autoflake(self, files: List[Path], dry_run: bool):
"""Run autoflake to remove unused imports."""
logger.info("Running autoflake to remove unused imports...")
try:
cmd = ["autoflake"]
if not dry_run:
cmd.append("--in-place")
cmd.extend([
"--remove-all-unused-imports",
"--remove-unused-variables",
*[str(f) for f in files]
])
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=self.project_root
)
if result.returncode == 0:
if not dry_run:
self.results["removed_unused"] = [str(f) for f in files]
logger.info(f"Successfully cleaned {len(files)} files with autoflake")
else:
logger.info("autoflake check completed")
if result.stdout:
logger.debug(f"Would remove unused imports:\n{result.stdout}")
except FileNotFoundError:
error_msg = "autoflake is not installed. Install with: pip install autoflake"
logger.error(error_msg)
self.results["errors"].append(error_msg)
except Exception as e:
logger.error(f"Error running autoflake: {str(e)}")
self.results["errors"].append(f"autoflake error: {str(e)}")
def get_summary(self) -> str:
"""Get a summary of cleaning results."""
summary = f"""
Code Cleaning Summary:
- Total files processed: {self.results['total_files_processed']}
- Files formatted: {len(self.results['formatted_files'])}
- Files with sorted imports: {len(self.results['sorted_imports'])}
- Files with removed unused code: {len(self.results['removed_unused'])}
- Errors encountered: {len(self.results['errors'])}
"""
return summary