File size: 6,698 Bytes
383cb38 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | #!/usr/bin/env python3
"""
Print to Logger Migration Script
Automatically replaces print() statements with logger.info/error/debug calls.
Usage:
python scripts/migrate_print_to_logging.py [--dry-run] [--file path/to/file.py]
Options:
--dry-run: Show changes without applying them
--file: Migrate a single file (default: migrate all non-test files)
"""
import argparse
import ast
from pathlib import Path
import re
from typing import List, Tuple
class PrintToLoggerMigrator:
"""Migrate print() statements to logger calls"""
def __init__(self, dry_run: bool = False):
self.dry_run = dry_run
self.changes_made = 0
def migrate_file(self, filepath: str) -> Tuple[bool, List[str]]:
"""Migrate a single Python file"""
try:
with open(filepath, 'r') as f:
content = f.read()
changes = []
original_content = content
# Check if file already has logging
has_logger = 'logger = logging.getLogger(__name__)' in content
# Add logger import if not present
if not has_logger:
content, import_changes = self._add_logger_import(content)
changes.extend(import_changes)
# Replace print() statements
content, print_changes = self._replace_print_statements(content)
changes.extend(print_changes)
# Write changes
if content != original_content and changes:
if self.dry_run:
print(f"\n{'='*60}")
print(f"File: {filepath}")
print(f"{'='*60}")
for change in changes:
print(f" - {change}")
else:
with open(filepath, 'w') as f:
f.write(content)
self.changes_made += 1
return True, changes
return False, ["No print() statements found"]
except Exception as e:
print(f"Error migrating {filepath}: {e}")
return False, [f"Error: {e}"]
def _add_logger_import(self, content: str) -> Tuple[str, List[str]]:
"""Add logging import and logger initialization"""
changes = []
# Check if logging is already imported
if 'import logging' in content:
# Just add logger initialization after imports
if 'logger = logging.getLogger(__name__)' not in content:
# Find the end of imports
import_end = content.find('\n\n')
if import_end == -1:
import_end = 0
# Insert logger initialization
content = content[:import_end] + '\n\nlogger = logging.getLogger(__name__)' + content[import_end:]
changes.append("Added logger initialization")
else:
# Add import logging at the top
content = 'import logging\n\n' + content
# Add logger initialization
if 'logger = logging.getLogger(__name__)' not in content:
content = 'import logging\n\nlogger = logging.getLogger(__name__)\\n\\n' + content[content.find('\\n')+1:]
changes.append("Added logging import and logger initialization")
return content, changes
def _replace_print_statements(self, content: str) -> Tuple[str, List[str]]:
"""Replace print() statements with logger calls"""
changes = []
# Pattern 1: print(string) -> logger.info(string)
# Pattern 2: print(f"...") -> logger.info(f"...")
# Pattern 3: print("error:", e) -> logger.error(f"error: {e}")
# Simple print statements
patterns = [
# print("message") -> logger.info("message")
(r'print\("([^"]+)"\)', r'logger.info("\1")'),
# print('message') -> logger.info('message')
(r"print\('([^']+)'\)", r"logger.info('\1')"),
# print(f"...") -> logger.info(f"...")
(r'print\(f"([^"]+)"\)', r'logger.info(f"\1")'),
(r"print\(f'([^']+)'\)", r"logger.info(f'\1')"),
# print(variable) -> logger.info(str(variable))
(r'print\((\w+)\)', r'logger.info(str(\1))'),
]
for pattern, replacement in patterns:
matches = re.findall(pattern, content)
if matches:
content = re.sub(pattern, replacement, content)
changes.append(f"Replaced {len(matches)} print() statement(s)")
return content, changes
def find_python_files() -> List[str]:
"""Find all Python files (excluding tests)"""
base_dir = Path(__file__).parent.parent
python_files = []
# Find all Python files
for py_file in base_dir.rglob('*.py'):
# Exclude test files, __init__.py, and venv
if (
not py_file.name.startswith('test_')
and py_file.name != '__init__.py'
and 'venv' not in py_file.parts
and '.venv' not in py_file.parts
and 'site-packages' not in py_file.parts
):
python_files.append(str(py_file))
return sorted(python_files)
def main():
parser = argparse.ArgumentParser(description='Migrate print() to logger')
parser.add_argument('--dry-run', action='store_true', help='Show changes without applying')
parser.add_argument('--file', type=str, help='Migrate a single file')
args = parser.parse_args()
migrator = PrintToLoggerMigrator(dry_run=args.dry_run)
if args.file:
files = [args.file]
else:
files = find_python_files()
print(f"Found {len(files)} Python files to check\n")
results = {
'success': 0,
'no_changes': 0,
'errors': 0
}
for filepath in files:
success, changes = migrator.migrate_file(filepath)
if success:
results['success'] += 1
if not args.dry_run:
print(f"✓ {filepath}")
for change in changes:
print(f" - {change}")
elif 'No print()' in changes[0]:
results['no_changes'] += 1
else:
results['errors'] += 1
print(f"✗ {filepath}: {changes[0]}")
print(f"\n{'='*60}")
print("Migration Summary:")
print(f" Files migrated: {results['success']}")
print(f" Files unchanged: {results['no_changes']}")
print(f" Files with errors: {results['errors']}")
print(f"{'='*60}")
if args.dry_run:
print("\nDRY RUN MODE - No changes were applied")
print("Run without --dry-run to apply changes")
if __name__ == '__main__':
main()
|