#!/usr/bin/env python3 import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] ALLOW_PREFIXES = ( ROOT / "devplacepy" / "database", ROOT / "devplacepy" / "db_client.py", ROOT / "devplacepy_services" / "database", ROOT / "scripts" / "migrate_db_imports.py", ROOT / "scripts" / "check_database_imports.py", ) PATTERNS = [ re.compile(r"from devplacepy\.database(?:\.[a-z_]+)? import"), re.compile(r"import devplacepy\.database\b"), ] # Only flagged outside tests/: unit tests may legitimately use `local_db` / # `from devplacepy import database` directly (Section 13.4). Source code must # always go through `devplacepy.db_client` so the remote broker patch applies. SOURCE_ONLY_PATTERNS = [ re.compile(r"from devplacepy import(?:\s+\w+,)*\s+database\b"), ] def allowed(path: Path) -> bool: for prefix in ALLOW_PREFIXES: if path == prefix or str(path).startswith(str(prefix) + "/"): return True return False def main() -> int: errors: list[str] = [] for pattern in ("devplacepy/**/*.py", "devplacepy_services/**/*.py", "tests/**/*.py"): is_source = not pattern.startswith("tests/") for path in ROOT.glob(pattern): if not path.is_file() or allowed(path): continue try: text = path.read_text(encoding="utf-8", errors="ignore") except OSError: continue active_patterns = PATTERNS + SOURCE_ONLY_PATTERNS if is_source else PATTERNS for lineno, line in enumerate(text.splitlines(), 1): for rx in active_patterns: if rx.search(line): errors.append(f"{path.relative_to(ROOT)}:{lineno}: {line.strip()}") break if errors: print("FORBIDDEN database imports (use devplacepy.db_client):") for line in sorted(errors)[:80]: print(f" {line}") if len(errors) > 80: print(f" ... and {len(errors) - 80} more") return 1 print("check_database_imports: ok") return 0 if __name__ == "__main__": raise SystemExit(main())