Skip to content

Commit f56bccb

Browse files
Name leaked_seam after the services module, not a random function.
Service functions do not get queries_model edges, so picking peers[0] recommended unrelated mutators. Prefer a service that already queries the same model; otherwise name the services.py module as the seam. Rebuild the packaged UI so the Depth section ships. Co-authored-by: Damon <Modsofthenation@users.noreply.github.com>
1 parent 4641360 commit f56bccb

5 files changed

Lines changed: 58 additions & 30 deletions

File tree

‎src/loadpath/architecture/depth.py‎

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from __future__ import annotations
1010

11+
from pathlib import Path
12+
1113
from loadpath.architecture.rules import Finding
1214
from loadpath.config import LoadpathConfig
1315
from loadpath.graph.store import GraphStore
@@ -128,18 +130,24 @@ def _card_for(finding: Finding) -> dict | None:
128130
def _leaked_seams(store: GraphStore) -> list[Finding]:
129131
views = {n["id"]: n for n in store.nodes([NodeType.VIEW])}
130132
models = {n["id"]: n for n in store.nodes([NodeType.MODEL])}
131-
services = [n for n in store.nodes([NodeType.SERVICE]) if not (n.get("extra") or {}).get("referenced")]
132-
services_by_ctx: dict[str, list[dict]] = {}
133-
for svc in services:
134-
ctx = svc.get("context") or ""
135-
services_by_ctx.setdefault(ctx, []).append(svc)
133+
services = [
134+
n for n in store.nodes([NodeType.SERVICE]) if not (n.get("extra") or {}).get("referenced")
135+
]
136+
queries_by_src: dict[str, set[str]] = {}
136137
called_by_view: dict[str, set[str]] = {v: set() for v in views}
137138
for edge in store.edges():
138-
if edge["type"] != EdgeType.CALLS.value:
139-
continue
140-
if edge["src"] in called_by_view:
139+
if edge["type"] == EdgeType.QUERIES_MODEL.value:
140+
queries_by_src.setdefault(edge["src"], set()).add(edge["dst"])
141+
elif edge["type"] == EdgeType.CALLS.value and edge["src"] in called_by_view:
141142
called_by_view[edge["src"]].add(edge["dst"])
143+
modules_by_ctx: dict[str, list[dict]] = {}
144+
for svc in services:
145+
ctx = svc.get("context") or ""
146+
if not ctx or not svc.get("file_path"):
147+
continue
148+
modules_by_ctx.setdefault(ctx, []).append(svc)
142149
out: list[Finding] = []
150+
seen: set[tuple[str, str]] = set()
143151
for edge in store.edges():
144152
if edge["type"] != EdgeType.QUERIES_MODEL.value:
145153
continue
@@ -150,41 +158,57 @@ def _leaked_seams(store: GraphStore) -> list[Finding]:
150158
if (edge.get("extra") or {}).get("imported"):
151159
continue
152160
ctx = view.get("context") or ""
161+
if not ctx:
162+
continue
163+
key = (view["id"], model["id"])
164+
if key in seen:
165+
continue
166+
called = called_by_view.get(view["id"], set())
153167
peers = [
154168
s
155-
for s in services_by_ctx.get(ctx, [])
156-
if s["id"] not in called_by_view.get(view["id"], set())
157-
and s.get("file_path") != view.get("file_path")
169+
for s in modules_by_ctx.get(ctx, [])
170+
if s.get("file_path") != view.get("file_path")
158171
]
159172
if not peers:
160173
continue
161-
peer = peers[0]
174+
# Prefer a service that already queries this model; otherwise the services
175+
# module is the seam — not an arbitrary unused function in the same context.
176+
same_model = [s for s in peers if model["id"] in queries_by_src.get(s["id"], set())]
177+
same_model.sort(key=lambda s: s["name"])
178+
unused = [s for s in peers if s["id"] not in called]
179+
unused.sort(key=lambda s: s["name"])
180+
if same_model:
181+
peer_name = same_model[0]["name"]
182+
else:
183+
files = sorted({s["file_path"] for s in (unused or peers)})
184+
peer_name = Path(files[0]).stem
185+
seen.add(key)
162186
out.append(
163187
Finding(
164188
rule="leaked_seam",
165189
severity=RuleSeverity.WARNING,
166190
message=(
167191
f"{view['name']} queries {model.get('qualified_name')} past the "
168-
f"{peer['name']} module's seam. Callers of the view learn the queryset; "
192+
f"{peer_name} module's seam. Callers of the view learn the queryset; "
169193
f"depth (leverage at the interface) is lost."
170194
),
171195
node_id=view["id"],
172196
file_path=view.get("file_path"),
173197
extra={
174198
"strength": "strong",
175199
"module": view["name"],
176-
"query_module": peer["name"],
200+
"query_module": peer_name,
177201
"model": model.get("qualified_name"),
178202
"deletion_test": (
179-
f"Deleting {peer['name']} would not concentrate complexity — the view already "
203+
f"Deleting {peer_name} would not concentrate complexity — the view already "
180204
f"owns the queryset. Deleting the view's queryset would reappear on every action."
181205
),
182206
"leverage": (
183207
f"One query module interface would pay back across {view['name']} actions and tests."
184208
),
185209
"locality": "Queryset shape, select_related, and auth scoping should live in one module.",
186210
"before": f"{view['name']} → {model.get('name')} (queryset in the view)",
187-
"after": f"{view['name']} → {peer['name']} → {model.get('name')}",
211+
"after": f"{view['name']} → {peer_name} → {model.get('name')}",
188212
},
189213
)
190214
)

0 commit comments

Comments
 (0)