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"
|
PAGES_DIR = UI_DIR / "pages"
|
||||||
COMPONENTS_DIR = UI_DIR / "components"
|
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"})
|
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]:
|
def _page_paths() -> list[Path]:
|
||||||
return sorted(path for path in PAGES_DIR.glob("*.py") if path.stem != "__init__")
|
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
|
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():
|
def test_page_modules_are_discovered():
|
||||||
"""Guard the guard: the rules below are meaningless if nothing is scanned."""
|
"""Guard the guard: the rules below are meaningless if nothing is scanned."""
|
||||||
discovered = {path.stem for path in _page_paths()}
|
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."""
|
"""HIGH-07: pages orchestrate services; they do not own sessions or settings."""
|
||||||
violations: dict[str, list[str]] = {}
|
violations: dict[str, list[str]] = {}
|
||||||
for path in _page_paths():
|
for path in _page_paths():
|
||||||
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
found = sorted((names & FORBIDDEN_PAGE_IMPORTS) | (modules & FORBIDDEN_PAGE_MODULES))
|
_, modules = _imports(tree)
|
||||||
|
allowlist_violations = _page_allowlist_violations(tree)
|
||||||
|
found = sorted((modules & FORBIDDEN_PAGE_MODULES) | allowlist_violations)
|
||||||
if found:
|
if found:
|
||||||
violations[path.stem] = found
|
violations[path.stem] = found
|
||||||
assert violations == {}
|
assert violations == {}
|
||||||
@@ -79,7 +88,8 @@ def test_no_component_resolves_request_or_application_state():
|
|||||||
violations: dict[str, list[str]] = {}
|
violations: dict[str, list[str]] = {}
|
||||||
for path in _component_paths():
|
for path in _component_paths():
|
||||||
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
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:
|
if found:
|
||||||
violations[str(path.relative_to(COMPONENTS_DIR))] = found
|
violations[str(path.relative_to(COMPONENTS_DIR))] = found
|
||||||
assert violations == {}
|
assert violations == {}
|
||||||
|
|||||||
Reference in New Issue
Block a user