#!/usr/bin/env python3 """ Pre-commit hook to check for incorrect research_id type hints. Research IDs are UUIDs and should always be treated as strings, never as integers. """ import sys import re import os from pathlib import Path # Set environment variable for pre-commit hooks to allow unencrypted databases os.environ["LDR_ALLOW_UNENCRYPTED"] = "true" def check_file(filepath): """Check a single file for incorrect research_id patterns.""" errors = [] with open(filepath, "r", encoding="utf-8") as f: lines = f.readlines() # Patterns to check for patterns = [ # Flask route with int type ( r"", "Flask route uses - should be ", ), # Type hints with int ( r"research_id:\s*int", "Type hint uses research_id: int - should be research_id: str", ), # Function parameters with int conversion ( r"int\(research_id\)", "Converting research_id to int - research IDs are UUIDs/strings", ), # Integer comparison patterns ( r"research_id\s*==\s*\d+", "Comparing research_id to integer - research IDs are UUIDs/strings", ), ] for line_num, line in enumerate(lines, 1): # Skip comment and docstring lines — comments like # "# Flask route: (old API)" should not fire. if line.lstrip().startswith(("#", '"""', "'''")): continue for pattern, message in patterns: if re.search(pattern, line): errors.append(f"{filepath}:{line_num}: {message}") errors.append(f" {line.strip()}") return errors def main(): """Main entry point.""" # Get files to check from command line arguments files_to_check = sys.argv[1:] if not files_to_check: print("No files to check") return 0 all_errors = [] for filepath in files_to_check: # Skip non-Python files if not filepath.endswith(".py"): continue # Skip test files, migration files, and pre-commit hooks (they might have legitimate int usage). # # Previously this used `"test_" in filepath` (bare substring) — that # matched production files like protest_handler.py and missed the # *_test.py convention and files under a /tests/ directory. Mirror # the guard pattern from _is_raw_sql_exempt in custom-checks.py. p = Path(filepath) if ( p.name.startswith("test_") or p.name.endswith("_test.py") or "tests" in p.parts or "migration" in filepath.lower() or ".pre-commit-hooks" in filepath ): continue errors = check_file(filepath) all_errors.extend(errors) if all_errors: print("Research ID type errors found:") print("-" * 80) for error in all_errors: print(error) print("-" * 80) print( f"Total errors: {len([e for e in all_errors if not e.startswith(' ')])}" ) return 1 return 0 if __name__ == "__main__": sys.exit(main())