fix: protect critical re-export imports from linter removal
Problem: - Ruff removed 'from app.core.database import Base' from models/database/base.py - Import appeared "unused" (F401) but was actually a critical re-export - Caused ImportError: cannot import name 'Base' at runtime - Re-export pattern: import in one file to export from package Solution: 1. Added F401 ignore for models/database/base.py in pyproject.toml 2. Created scripts/verify_critical_imports.py verification script 3. Integrated verification into make check and CI pipeline 4. Updated documentation with explanation New Verification Script: - Checks all critical re-export imports exist - Detects import variations (parentheses, 'as' clauses) - Handles SQLAlchemy declarative_base alternatives - Runs as part of make check automatically Protected Files: - models/database/base.py - Re-exports Base for all models - models/__init__.py - Exports Base for Alembic - models/database/__init__.py - Exports Base from package - All __init__.py files (already protected) Makefile Changes: - make verify-imports - Run import verification - make check - Now includes verify-imports - make ci - Includes verify-imports in pipeline Documentation Updated: - Code quality guide explains re-export protection - Pre-commit workflow includes verification - Examples of why re-exports matter This prevents future issues where linters remove seemingly "unused" imports that are actually critical for application structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ Usage: python route_diagnostics.py
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def check_route_order():
|
||||
"""Check if routes are registered in the correct order."""
|
||||
print("🔍 Checking FastAPI Route Configuration...\n")
|
||||
|
||||
@@ -20,7 +20,6 @@ Requirements:
|
||||
* Customer: username=customer, password=customer123, vendor_id=1
|
||||
"""
|
||||
|
||||
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
@@ -9,7 +9,6 @@ Tests:
|
||||
- Transfer ownership
|
||||
"""
|
||||
|
||||
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
156
scripts/verify_critical_imports.py
Executable file
156
scripts/verify_critical_imports.py
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Verify Critical Imports
|
||||
========================
|
||||
Checks that critical imports (re-exports) haven't been removed by linters.
|
||||
|
||||
This script verifies that essential import statements exist in key files,
|
||||
preventing issues where tools like Ruff might remove imports that appear
|
||||
unused but are actually critical for the application structure.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Define critical imports that must exist
|
||||
# Format: {file_path: [(import_line, description)]}
|
||||
CRITICAL_IMPORTS: dict[str, list[tuple[str, str]]] = {
|
||||
"models/database/base.py": [
|
||||
("from app.core.database import Base", "Re-export Base for all models"),
|
||||
],
|
||||
"models/__init__.py": [
|
||||
("from .database.base import Base", "Export Base for Alembic and models"),
|
||||
],
|
||||
"models/database/__init__.py": [
|
||||
("from .base import Base", "Export Base from database package"),
|
||||
],
|
||||
"app/core/database.py": [
|
||||
(
|
||||
"from sqlalchemy.ext.declarative import declarative_base",
|
||||
"SQLAlchemy Base declaration",
|
||||
),
|
||||
# Note: Might also use sqlalchemy.orm declarative_base in newer versions
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class ImportVerifier:
|
||||
"""Verifies critical imports exist in codebase"""
|
||||
|
||||
def __init__(self, project_root: Path):
|
||||
self.project_root = project_root
|
||||
self.issues: list[str] = []
|
||||
|
||||
def verify_all(self) -> bool:
|
||||
"""Verify all critical imports"""
|
||||
print("🔍 Verifying critical imports...\n")
|
||||
|
||||
all_good = True
|
||||
for file_path, imports in CRITICAL_IMPORTS.items():
|
||||
if not self.verify_file(file_path, imports):
|
||||
all_good = False
|
||||
|
||||
return all_good
|
||||
|
||||
def verify_file(
|
||||
self, file_path: str, required_imports: list[tuple[str, str]]
|
||||
) -> bool:
|
||||
"""Verify imports in a single file"""
|
||||
full_path = self.project_root / file_path
|
||||
|
||||
if not full_path.exists():
|
||||
self.issues.append(f"❌ File not found: {file_path}")
|
||||
print(f"❌ {file_path}: File not found")
|
||||
return False
|
||||
|
||||
content = full_path.read_text()
|
||||
file_ok = True
|
||||
|
||||
for import_line, description in required_imports:
|
||||
# Check for exact import or variations
|
||||
if import_line in content:
|
||||
print(f"✅ {file_path}: {import_line}")
|
||||
else:
|
||||
# Check for alternative import formats
|
||||
alternatives = self._get_import_alternatives(import_line)
|
||||
found = any(alt in content for alt in alternatives)
|
||||
|
||||
if found:
|
||||
print(f"✅ {file_path}: {import_line} (alternative format)")
|
||||
else:
|
||||
self.issues.append(
|
||||
f"❌ {file_path}: Missing critical import\n"
|
||||
f" Expected: {import_line}\n"
|
||||
f" Purpose: {description}"
|
||||
)
|
||||
print(f"❌ {file_path}: Missing {import_line}")
|
||||
file_ok = False
|
||||
|
||||
print()
|
||||
return file_ok
|
||||
|
||||
def _get_import_alternatives(self, import_line: str) -> list[str]:
|
||||
"""Get alternative formats for an import"""
|
||||
alternatives = [import_line]
|
||||
|
||||
# Handle 'from x import y' vs 'from x import (y)'
|
||||
if "from" in import_line and "import" in import_line:
|
||||
parts = import_line.split("import")
|
||||
if len(parts) == 2:
|
||||
from_part = parts[0].strip()
|
||||
import_part = parts[1].strip()
|
||||
|
||||
# Add parenthesized version
|
||||
alternatives.append(f"{from_part} import ({import_part})")
|
||||
|
||||
# Add version with 'as' clause
|
||||
alternatives.append(f"{import_line} as")
|
||||
|
||||
# Handle declarative_base alternatives (sqlalchemy changes)
|
||||
if "declarative_base" in import_line:
|
||||
# Old style
|
||||
alternatives.append(
|
||||
"from sqlalchemy.ext.declarative import declarative_base"
|
||||
)
|
||||
# New style (SQLAlchemy 1.4+)
|
||||
alternatives.append("from sqlalchemy.orm import declarative_base")
|
||||
|
||||
return alternatives
|
||||
|
||||
def print_summary(self):
|
||||
"""Print summary of verification"""
|
||||
print("\n" + "=" * 80)
|
||||
print("📊 CRITICAL IMPORTS VERIFICATION SUMMARY")
|
||||
print("=" * 80)
|
||||
|
||||
if not self.issues:
|
||||
print("\n✅ All critical imports verified successfully!")
|
||||
print("\nAll re-export patterns are intact.")
|
||||
else:
|
||||
print(f"\n❌ Found {len(self.issues)} issue(s):\n")
|
||||
for issue in self.issues:
|
||||
print(issue)
|
||||
print()
|
||||
|
||||
print("💡 RESOLUTION:")
|
||||
print(" 1. Check if imports were removed by linter (Ruff)")
|
||||
print(" 2. Add missing imports back to the files")
|
||||
print(" 3. Update pyproject.toml to ignore F401 for these files")
|
||||
print(" 4. Run this script again to verify")
|
||||
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
project_root = Path(__file__).parent.parent
|
||||
|
||||
verifier = ImportVerifier(project_root)
|
||||
success = verifier.verify_all()
|
||||
verifier.print_summary()
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user