graphlens-link 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Cross-language boundary linker: a pure ``graph -> graph`` transform."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from graphlens import NodeKind, Relation, RelationKind
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from graphlens import GraphLens
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class LinkResult:
|
|
16
|
+
"""Summary of a single cross-language link pass."""
|
|
17
|
+
|
|
18
|
+
relations_added: int
|
|
19
|
+
boundaries_total: int
|
|
20
|
+
boundaries_linked: int
|
|
21
|
+
"""Boundaries that had at least one provider *and* one consumer."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _as_float(value: object, default: float = 1.0) -> float:
|
|
25
|
+
"""Coerce a metadata value to a float, falling back to ``default``."""
|
|
26
|
+
if isinstance(value, bool):
|
|
27
|
+
return default
|
|
28
|
+
if isinstance(value, (int, float)):
|
|
29
|
+
return float(value)
|
|
30
|
+
return default
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def link_graph(graph: GraphLens, *, min_confidence: float = 0.0) -> LinkResult:
|
|
34
|
+
"""
|
|
35
|
+
Add ``COMMUNICATES_WITH`` edges across cross-language boundaries.
|
|
36
|
+
|
|
37
|
+
Adapters emit a shared ``BOUNDARY`` node per contract (id derived purely
|
|
38
|
+
from mechanism + key) with ``EXPOSES`` edges from servers and
|
|
39
|
+
``CONSUMES`` edges from clients. This pass pairs, for every boundary,
|
|
40
|
+
each consumer with each provider and adds a directed
|
|
41
|
+
``consumer -> provider`` edge carrying the boundary's ``mechanism`` and
|
|
42
|
+
the product of the two sides' confidences.
|
|
43
|
+
|
|
44
|
+
The graph is mutated in place. The pass is idempotent: an edge for the
|
|
45
|
+
same ``(source, target, boundary)`` is never added twice, so it is safe
|
|
46
|
+
to run after re-analyzing part of the graph. Two distinct boundaries
|
|
47
|
+
between the same consumer/provider pair each get their own edge. Pairs
|
|
48
|
+
whose combined confidence is below ``min_confidence`` are skipped.
|
|
49
|
+
"""
|
|
50
|
+
added = 0
|
|
51
|
+
linked = 0
|
|
52
|
+
boundaries = graph.nodes_by_kind(NodeKind.BOUNDARY)
|
|
53
|
+
existing: set[tuple[str, str, str]] = {
|
|
54
|
+
(r.source_id, r.target_id, str(r.metadata.get("boundary_id", "")))
|
|
55
|
+
for r in graph.relations
|
|
56
|
+
if r.kind == RelationKind.COMMUNICATES_WITH
|
|
57
|
+
}
|
|
58
|
+
for boundary in boundaries:
|
|
59
|
+
consumers = graph.incoming(boundary.id, RelationKind.CONSUMES)
|
|
60
|
+
providers = graph.incoming(boundary.id, RelationKind.EXPOSES)
|
|
61
|
+
if not consumers or not providers:
|
|
62
|
+
continue
|
|
63
|
+
linked += 1
|
|
64
|
+
mechanism = str(boundary.metadata.get("mechanism", ""))
|
|
65
|
+
boundary_key = str(boundary.metadata.get("key", ""))
|
|
66
|
+
for consumer in consumers:
|
|
67
|
+
for provider in providers:
|
|
68
|
+
if consumer.source_id == provider.source_id:
|
|
69
|
+
continue
|
|
70
|
+
confidence = _as_float(
|
|
71
|
+
consumer.metadata.get("confidence")
|
|
72
|
+
) * _as_float(provider.metadata.get("confidence"))
|
|
73
|
+
if confidence < min_confidence:
|
|
74
|
+
continue
|
|
75
|
+
dedupe = (
|
|
76
|
+
consumer.source_id,
|
|
77
|
+
provider.source_id,
|
|
78
|
+
boundary.id,
|
|
79
|
+
)
|
|
80
|
+
if dedupe in existing:
|
|
81
|
+
continue
|
|
82
|
+
existing.add(dedupe)
|
|
83
|
+
graph.add_relation(
|
|
84
|
+
Relation(
|
|
85
|
+
source_id=consumer.source_id,
|
|
86
|
+
target_id=provider.source_id,
|
|
87
|
+
kind=RelationKind.COMMUNICATES_WITH,
|
|
88
|
+
metadata={
|
|
89
|
+
"mechanism": mechanism,
|
|
90
|
+
"boundary_id": boundary.id,
|
|
91
|
+
"boundary_key": boundary_key,
|
|
92
|
+
"confidence": confidence,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
added += 1
|
|
97
|
+
return LinkResult(
|
|
98
|
+
relations_added=added,
|
|
99
|
+
boundaries_total=len(boundaries),
|
|
100
|
+
boundaries_linked=linked,
|
|
101
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
graphlens_link/__init__.py,sha256=HrDx4MRYJ4JfdnScfNWJZfur-9J0pekuDIVIT9uQ4lM,158
|
|
2
|
+
graphlens_link/_linker.py,sha256=Un80YgVcuMGlNtWdbeQfMgxgPVu_srQExjNQKP59Bro,3826
|
|
3
|
+
graphlens_link-0.5.0.dist-info/WHEEL,sha256=oBsDExVIEya4llboy9Ce1l6on8xt3GrtT29y6pYVypw,81
|
|
4
|
+
graphlens_link-0.5.0.dist-info/METADATA,sha256=6dj1vBsmtV2mw3iOW2huP8oVvzuj3vWNasTosanghBU,161
|
|
5
|
+
graphlens_link-0.5.0.dist-info/RECORD,,
|