Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

189 lines
6.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Pre-commit hook to detect try/finally session patterns and suggest context managers.
This hook checks for SQLAlchemy session management patterns that use try/finally
blocks and suggests replacing them with context managers for better resource
management and cleaner code.
"""
import ast
import sys
from pathlib import Path
from typing import List, Tuple
class SessionPatternChecker(ast.NodeVisitor):
"""AST visitor to detect try/finally session patterns."""
def __init__(self, filename: str):
self.filename = filename
self.issues: List[Tuple[int, str]] = []
self.functions_and_methods = []
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""Visit function definitions to check for session patterns."""
self._check_function_for_pattern(node)
self.generic_visit(node)
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
"""Visit async function definitions to check for session patterns."""
self._check_function_for_pattern(node)
self.generic_visit(node)
def _check_function_for_pattern(self, func_node) -> None:
"""Check a function body for try/finally session patterns."""
# Look for session = Session() followed by try/finally
for i, stmt in enumerate(func_node.body):
# Check if this is a session assignment
if isinstance(stmt, ast.Assign):
session_var = self._get_session_var_from_assign(stmt)
if session_var:
# Look for a try/finally block that follows
for next_stmt in func_node.body[
i + 1 : i + 3
]: # Check next 2 statements
if (
isinstance(next_stmt, ast.Try)
and next_stmt.finalbody
):
# Check if finally has session.close()
if self._has_session_close_in_finally(
next_stmt.finalbody, session_var
):
self.issues.append(
(
stmt.lineno,
f"Found try/finally session pattern. Consider using 'with self.Session() as {session_var}:' instead",
)
)
break
def _get_session_var_from_assign(self, assign_node: ast.Assign) -> str:
"""Check if an assignment is creating a session and return the variable name."""
if isinstance(assign_node.value, ast.Call) and self._is_session_call(
assign_node.value
):
if assign_node.targets and isinstance(
assign_node.targets[0], ast.Name
):
return assign_node.targets[0].id
return None
def _is_session_call(self, call_node: ast.Call) -> bool:
"""Check if a call node is creating a SQLAlchemy session."""
# Check for self.Session() pattern
if isinstance(call_node.func, ast.Attribute):
if call_node.func.attr in (
"Session",
"get_session",
"create_session",
):
return True
# Check for Session() pattern
elif isinstance(call_node.func, ast.Name):
if call_node.func.id in (
"Session",
"get_session",
"create_session",
):
return True
return False
def _has_session_close_in_finally(
self, finalbody: List[ast.stmt], session_var: str
) -> bool:
"""Check if finally block contains session.close()."""
for stmt in finalbody:
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
# Check for session.close() pattern
if (
isinstance(stmt.value.func, ast.Attribute)
and stmt.value.func.attr == "close"
):
# Check if it's our session variable
if (
isinstance(stmt.value.func.value, ast.Name)
and stmt.value.func.value.id == session_var
):
return True
return False
def check_file(filepath: Path) -> List[Tuple[str, int, str]]:
"""Check a single Python file for try/finally session patterns."""
issues = []
try:
content = filepath.read_text(encoding="utf-8")
tree = ast.parse(content, filename=str(filepath))
checker = SessionPatternChecker(str(filepath))
checker.visit(tree)
for line_no, message in checker.issues:
issues.append((str(filepath), line_no, message))
except SyntaxError as e:
# Skip files with syntax errors
print(f"Syntax error in {filepath}: {e}", file=sys.stderr)
except Exception as e:
print(f"Error checking {filepath}: {e}", file=sys.stderr)
return issues
def main():
"""Main entry point for the pre-commit hook."""
# Get list of files to check from command line arguments
files_to_check = sys.argv[1:] if len(sys.argv) > 1 else []
if not files_to_check:
print("No files to check")
return 0
all_issues = []
for filepath_str in files_to_check:
filepath = Path(filepath_str)
# Skip non-Python files
if not filepath.suffix == ".py":
continue
# Skip test files and migration files
if "test" in filepath.parts or "migration" in filepath.parts:
continue
issues = check_file(filepath)
all_issues.extend(issues)
# Report issues
if all_issues:
print(
"\n❌ Found try/finally session patterns that should use context managers:\n"
)
for filepath, line_no, message in all_issues:
print(f" {filepath}:{line_no}: {message}")
print("\n💡 Tip: Replace try/finally blocks with context managers:")
print(" Before:")
print(" session = self.Session()")
print(" try:")
print(" # operations")
print(" session.commit()")
print(" finally:")
print(" session.close()")
print("\n After:")
print(" with self.Session() as session:")
print(" # operations")
print(" session.commit()")
print("\n")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())