generated from john/python-template
Co-authored-by: Copilot App <[email protected]>
This commit is contained in:
co-authored by
Copilot App
parent
803237371e
commit
12f125761a
+28
-18
@@ -15,23 +15,15 @@ UI_DIR = Path(__file__).resolve().parents[1] / "src" / "transcription" / "ui"
|
||||
PAGES_DIR = UI_DIR / "pages"
|
||||
COMPONENTS_DIR = UI_DIR / "components"
|
||||
|
||||
# Names a page must not pull in: they hand the page a session, a transaction, ORM
|
||||
# loader introspection, or process-global configuration.
|
||||
FORBIDDEN_PAGE_IMPORTS = frozenset(
|
||||
{
|
||||
"session_scope",
|
||||
"transaction_scope",
|
||||
"get_session_factory",
|
||||
"resolve_session_factory",
|
||||
"get_settings",
|
||||
"get_engine",
|
||||
"upgrade_schema",
|
||||
"create_all",
|
||||
}
|
||||
)
|
||||
|
||||
FORBIDDEN_PAGE_MODULES = frozenset({"sqlalchemy", "sqlmodel"})
|
||||
|
||||
# Sensitive modules are allowlisted, not blocklisted, so newly added persistence
|
||||
# helpers cannot slip through by using an unlisted name.
|
||||
PAGE_IMPORT_ALLOWLIST: dict[tuple[str, int], frozenset[str]] = {
|
||||
("db.session", 3): frozenset({"SessionFactoryDep"}),
|
||||
("transcription.config", 0): frozenset({"Settings"}),
|
||||
}
|
||||
|
||||
|
||||
def _page_paths() -> list[Path]:
|
||||
return sorted(path for path in PAGES_DIR.glob("*.py") if path.stem != "__init__")
|
||||
@@ -57,6 +49,21 @@ def _imports(tree: ast.Module) -> tuple[set[str], set[str]]:
|
||||
return names, modules
|
||||
|
||||
|
||||
def _page_allowlist_violations(tree: ast.Module) -> set[str]:
|
||||
violations: set[str] = set()
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.ImportFrom) or not node.module:
|
||||
continue
|
||||
allowed = PAGE_IMPORT_ALLOWLIST.get((node.module, node.level))
|
||||
if allowed is None:
|
||||
continue
|
||||
for alias in node.names:
|
||||
imported_name = alias.name
|
||||
if imported_name not in allowed:
|
||||
violations.add(f"{'.' * node.level}{node.module}.{imported_name}")
|
||||
return violations
|
||||
|
||||
|
||||
def test_page_modules_are_discovered():
|
||||
"""Guard the guard: the rules below are meaningless if nothing is scanned."""
|
||||
discovered = {path.stem for path in _page_paths()}
|
||||
@@ -67,8 +74,10 @@ def test_no_page_imports_persistence_or_process_globals():
|
||||
"""HIGH-07: pages orchestrate services; they do not own sessions or settings."""
|
||||
violations: dict[str, list[str]] = {}
|
||||
for path in _page_paths():
|
||||
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
||||
found = sorted((names & FORBIDDEN_PAGE_IMPORTS) | (modules & FORBIDDEN_PAGE_MODULES))
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||
_, modules = _imports(tree)
|
||||
allowlist_violations = _page_allowlist_violations(tree)
|
||||
found = sorted((modules & FORBIDDEN_PAGE_MODULES) | allowlist_violations)
|
||||
if found:
|
||||
violations[path.stem] = found
|
||||
assert violations == {}
|
||||
@@ -79,7 +88,8 @@ def test_no_component_resolves_request_or_application_state():
|
||||
violations: dict[str, list[str]] = {}
|
||||
for path in _component_paths():
|
||||
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
||||
found = sorted((names & FORBIDDEN_PAGE_IMPORTS) | (modules & (FORBIDDEN_PAGE_MODULES | {"fastapi"})))
|
||||
forbidden_names = {"get_settings", "get_engine", "session_scope", "transaction_scope"}
|
||||
found = sorted((names & forbidden_names) | (modules & (FORBIDDEN_PAGE_MODULES | {"fastapi"})))
|
||||
if found:
|
||||
violations[str(path.relative_to(COMPONENTS_DIR))] = found
|
||||
assert violations == {}
|
||||
|
||||
Reference in New Issue
Block a user