koi-net 1.0.0b19__py3-none-any.whl → 1.1.0b2__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.
Potentially problematic release.
This version of koi-net might be problematic. Click here for more details.
- koi_net/config.py +44 -18
- koi_net/context.py +55 -0
- koi_net/core.py +134 -84
- koi_net/default_actions.py +15 -0
- koi_net/effector.py +139 -0
- koi_net/identity.py +4 -22
- koi_net/lifecycle.py +76 -0
- koi_net/network/__init__.py +0 -1
- koi_net/network/behavior.py +42 -0
- koi_net/network/error_handler.py +50 -0
- koi_net/network/{interface.py → event_queue.py} +52 -131
- koi_net/network/graph.py +18 -33
- koi_net/network/request_handler.py +112 -66
- koi_net/network/resolver.py +150 -0
- koi_net/network/response_handler.py +11 -3
- koi_net/poller.py +45 -0
- koi_net/processor/__init__.py +0 -1
- koi_net/processor/default_handlers.py +57 -41
- koi_net/processor/handler.py +3 -7
- koi_net/processor/interface.py +15 -214
- koi_net/processor/knowledge_object.py +10 -17
- koi_net/processor/knowledge_pipeline.py +220 -0
- koi_net/protocol/api_models.py +7 -1
- koi_net/protocol/edge.py +23 -1
- koi_net/protocol/envelope.py +54 -0
- koi_net/protocol/errors.py +23 -0
- koi_net/protocol/node.py +2 -1
- koi_net/protocol/secure.py +106 -0
- koi_net/secure.py +117 -0
- koi_net/server.py +128 -0
- {koi_net-1.0.0b19.dist-info → koi_net-1.1.0b2.dist-info}/METADATA +5 -4
- koi_net-1.1.0b2.dist-info/RECORD +38 -0
- koi_net/protocol/helpers.py +0 -25
- koi_net-1.0.0b19.dist-info/RECORD +0 -25
- {koi_net-1.0.0b19.dist-info → koi_net-1.1.0b2.dist-info}/WHEEL +0 -0
- {koi_net-1.0.0b19.dist-info → koi_net-1.1.0b2.dist-info}/licenses/LICENSE +0 -0
koi_net/network/__init__.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .interface import NetworkInterface
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
from rid_lib.types import KoiNetNode
|
|
3
|
+
from ..protocol.event import Event, EventType
|
|
4
|
+
from ..identity import NodeIdentity
|
|
5
|
+
from ..effector import Effector
|
|
6
|
+
from ..network.event_queue import NetworkEventQueue
|
|
7
|
+
|
|
8
|
+
logger = getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Actor:
|
|
12
|
+
identity: NodeIdentity
|
|
13
|
+
effector: Effector
|
|
14
|
+
event_queue: NetworkEventQueue
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
identity: NodeIdentity,
|
|
19
|
+
effector: Effector,
|
|
20
|
+
event_queue: NetworkEventQueue
|
|
21
|
+
):
|
|
22
|
+
self.identity = identity
|
|
23
|
+
self.effector = effector
|
|
24
|
+
self.event_queue = event_queue
|
|
25
|
+
|
|
26
|
+
def handshake_with(self, target: KoiNetNode):
|
|
27
|
+
logger.debug(f"Initiating handshake with {target}")
|
|
28
|
+
self.event_queue.push_event_to(
|
|
29
|
+
Event.from_rid(
|
|
30
|
+
event_type=EventType.FORGET,
|
|
31
|
+
rid=self.identity.rid),
|
|
32
|
+
node=target
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self.event_queue.push_event_to(
|
|
36
|
+
event=Event.from_bundle(
|
|
37
|
+
event_type=EventType.NEW,
|
|
38
|
+
bundle=self.effector.deref(self.identity.rid)),
|
|
39
|
+
node=target
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.event_queue.flush_webhook_queue(target)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
from koi_net.protocol.errors import ErrorTypes
|
|
3
|
+
from koi_net.protocol.event import EventType
|
|
4
|
+
from rid_lib.types import KoiNetNode
|
|
5
|
+
from ..processor.interface import ProcessorInterface
|
|
6
|
+
from ..network.behavior import Actor
|
|
7
|
+
|
|
8
|
+
logger = getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ErrorHandler:
|
|
12
|
+
timeout_counter: dict[KoiNetNode, int]
|
|
13
|
+
processor: ProcessorInterface
|
|
14
|
+
actor: Actor
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
processor: ProcessorInterface,
|
|
19
|
+
actor: Actor
|
|
20
|
+
):
|
|
21
|
+
self.processor = processor
|
|
22
|
+
self.actor = actor
|
|
23
|
+
self.timeout_counter = {}
|
|
24
|
+
|
|
25
|
+
def handle_connection_error(self, node: KoiNetNode):
|
|
26
|
+
self.timeout_counter.setdefault(node, 0)
|
|
27
|
+
self.timeout_counter[node] += 1
|
|
28
|
+
|
|
29
|
+
logger.debug(f"{node} has timed out {self.timeout_counter[node]} time(s)")
|
|
30
|
+
|
|
31
|
+
if self.timeout_counter[node] > 3:
|
|
32
|
+
logger.debug(f"Exceeded time out limit, forgetting node")
|
|
33
|
+
self.processor.handle(rid=node, event_type=EventType.FORGET)
|
|
34
|
+
# do something
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def handle_protocol_error(
|
|
38
|
+
self,
|
|
39
|
+
error_type: ErrorTypes,
|
|
40
|
+
node: KoiNetNode
|
|
41
|
+
):
|
|
42
|
+
logger.info(f"Handling protocol error {error_type} for node {node!r}")
|
|
43
|
+
match error_type:
|
|
44
|
+
case ErrorTypes.UnknownNode:
|
|
45
|
+
logger.info("Peer doesn't know me, attempting handshake...")
|
|
46
|
+
self.actor.handshake_with(node)
|
|
47
|
+
|
|
48
|
+
case ErrorTypes.InvalidKey: ...
|
|
49
|
+
case ErrorTypes.InvalidSignature: ...
|
|
50
|
+
case ErrorTypes.InvalidTarget: ...
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from queue import Queue
|
|
3
|
-
from typing import Generic
|
|
4
3
|
import httpx
|
|
5
4
|
from pydantic import BaseModel
|
|
6
5
|
from rid_lib import RID
|
|
7
|
-
from rid_lib.core import RIDType
|
|
8
6
|
from rid_lib.ext import Cache
|
|
9
7
|
from rid_lib.types import KoiNetNode
|
|
10
8
|
|
|
11
9
|
from .graph import NetworkGraph
|
|
12
10
|
from .request_handler import RequestHandler
|
|
13
|
-
from .
|
|
14
|
-
from ..protocol.
|
|
15
|
-
from ..protocol.edge import EdgeType
|
|
11
|
+
from ..protocol.node import NodeProfile, NodeType
|
|
12
|
+
from ..protocol.edge import EdgeProfile, EdgeType
|
|
16
13
|
from ..protocol.event import Event
|
|
17
14
|
from ..identity import NodeIdentity
|
|
18
|
-
from ..config import
|
|
15
|
+
from ..config import NodeConfig
|
|
16
|
+
from ..effector import Effector
|
|
19
17
|
|
|
20
18
|
logger = logging.getLogger(__name__)
|
|
21
19
|
|
|
@@ -26,34 +24,36 @@ class EventQueueModel(BaseModel):
|
|
|
26
24
|
|
|
27
25
|
type EventQueue = dict[RID, Queue[Event]]
|
|
28
26
|
|
|
29
|
-
class
|
|
27
|
+
class NetworkEventQueue:
|
|
30
28
|
"""A collection of functions and classes to interact with the KOI network."""
|
|
31
29
|
|
|
32
|
-
config:
|
|
30
|
+
config: NodeConfig
|
|
33
31
|
identity: NodeIdentity
|
|
32
|
+
effector: Effector
|
|
34
33
|
cache: Cache
|
|
35
34
|
graph: NetworkGraph
|
|
36
35
|
request_handler: RequestHandler
|
|
37
|
-
response_handler: ResponseHandler
|
|
38
36
|
poll_event_queue: EventQueue
|
|
39
37
|
webhook_event_queue: EventQueue
|
|
40
38
|
|
|
41
39
|
def __init__(
|
|
42
40
|
self,
|
|
43
|
-
config:
|
|
41
|
+
config: NodeConfig,
|
|
44
42
|
cache: Cache,
|
|
45
|
-
identity: NodeIdentity
|
|
43
|
+
identity: NodeIdentity,
|
|
44
|
+
effector: Effector,
|
|
45
|
+
graph: NetworkGraph,
|
|
46
|
+
request_handler: RequestHandler,
|
|
46
47
|
):
|
|
47
48
|
self.config = config
|
|
48
49
|
self.identity = identity
|
|
49
50
|
self.cache = cache
|
|
50
|
-
self.graph =
|
|
51
|
-
self.request_handler =
|
|
52
|
-
self.
|
|
51
|
+
self.graph = graph
|
|
52
|
+
self.request_handler = request_handler
|
|
53
|
+
self.effector = effector
|
|
53
54
|
|
|
54
55
|
self.poll_event_queue = dict()
|
|
55
56
|
self.webhook_event_queue = dict()
|
|
56
|
-
self._load_event_queues()
|
|
57
57
|
|
|
58
58
|
def _load_event_queues(self):
|
|
59
59
|
"""Loads event queues from storage."""
|
|
@@ -100,29 +100,43 @@ class NetworkInterface(Generic[ConfigType]):
|
|
|
100
100
|
|
|
101
101
|
Event will be sent to webhook or poll queue depending on the node type and edge type of the specified node. If `flush` is set to `True`, the webhook queued will be flushed after pushing the event.
|
|
102
102
|
"""
|
|
103
|
-
logger.debug(f"Pushing event {event.event_type} {event.rid} to {node}")
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if not node_profile:
|
|
107
|
-
logger.warning(f"Node {node!r} unknown to me")
|
|
103
|
+
logger.debug(f"Pushing event {event.event_type} {event.rid!r} to {node}")
|
|
104
|
+
|
|
105
|
+
node_bundle = self.effector.deref(node)
|
|
108
106
|
|
|
109
107
|
# if there's an edge from me to the target node, override broadcast type
|
|
110
|
-
|
|
108
|
+
edge_rid = self.graph.get_edge(
|
|
111
109
|
source=self.identity.rid,
|
|
112
110
|
target=node
|
|
113
111
|
)
|
|
114
112
|
|
|
115
|
-
if
|
|
113
|
+
edge_bundle = self.effector.deref(edge_rid) if edge_rid else None
|
|
114
|
+
|
|
115
|
+
if edge_bundle:
|
|
116
|
+
logger.debug(f"Found edge from me to {node!r}")
|
|
117
|
+
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
116
118
|
if edge_profile.edge_type == EdgeType.WEBHOOK:
|
|
117
119
|
event_queue = self.webhook_event_queue
|
|
118
120
|
elif edge_profile.edge_type == EdgeType.POLL:
|
|
119
121
|
event_queue = self.poll_event_queue
|
|
120
|
-
|
|
122
|
+
|
|
123
|
+
elif node_bundle:
|
|
124
|
+
logger.debug(f"Found bundle for {node!r}")
|
|
125
|
+
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
121
126
|
if node_profile.node_type == NodeType.FULL:
|
|
122
127
|
event_queue = self.webhook_event_queue
|
|
123
128
|
elif node_profile.node_type == NodeType.PARTIAL:
|
|
124
129
|
event_queue = self.poll_event_queue
|
|
125
130
|
|
|
131
|
+
elif node == self.config.koi_net.first_contact.rid:
|
|
132
|
+
logger.debug(f"Node {node!r} is my first contact")
|
|
133
|
+
# first contact node is always a webhook node
|
|
134
|
+
event_queue = self.webhook_event_queue
|
|
135
|
+
|
|
136
|
+
else:
|
|
137
|
+
logger.warning(f"Node {node!r} unknown to me")
|
|
138
|
+
return
|
|
139
|
+
|
|
126
140
|
queue = event_queue.setdefault(node, Queue())
|
|
127
141
|
queue.put(event)
|
|
128
142
|
|
|
@@ -136,7 +150,7 @@ class NetworkInterface(Generic[ConfigType]):
|
|
|
136
150
|
if queue:
|
|
137
151
|
while not queue.empty():
|
|
138
152
|
event = queue.get()
|
|
139
|
-
logger.debug(f"Dequeued {event.event_type}
|
|
153
|
+
logger.debug(f"Dequeued {event.event_type} {event.rid!r}")
|
|
140
154
|
events.append(event)
|
|
141
155
|
|
|
142
156
|
return events
|
|
@@ -146,7 +160,7 @@ class NetworkInterface(Generic[ConfigType]):
|
|
|
146
160
|
logger.debug(f"Flushing poll queue for {node}")
|
|
147
161
|
return self._flush_queue(self.poll_event_queue, node)
|
|
148
162
|
|
|
149
|
-
def flush_webhook_queue(self, node: KoiNetNode):
|
|
163
|
+
def flush_webhook_queue(self, node: KoiNetNode, requeue_on_fail: bool = True):
|
|
150
164
|
"""Flushes a node's webhook queue, and broadcasts events.
|
|
151
165
|
|
|
152
166
|
If node profile is unknown, or node type is not `FULL`, this operation will fail silently. If the remote node cannot be reached, all events will be requeued.
|
|
@@ -154,15 +168,17 @@ class NetworkInterface(Generic[ConfigType]):
|
|
|
154
168
|
|
|
155
169
|
logger.debug(f"Flushing webhook queue for {node}")
|
|
156
170
|
|
|
157
|
-
|
|
171
|
+
# node_bundle = self.effector.deref(node)
|
|
158
172
|
|
|
159
|
-
if not
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
# if not node_bundle:
|
|
174
|
+
# logger.warning(f"{node!r} not found")
|
|
175
|
+
# return
|
|
162
176
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
177
|
+
# node_profile = node_bundle.validate_contents(NodeProfile)
|
|
178
|
+
|
|
179
|
+
# if node_profile.node_type != NodeType.FULL:
|
|
180
|
+
# logger.warning(f"{node!r} is a partial node!")
|
|
181
|
+
# return
|
|
166
182
|
|
|
167
183
|
events = self._flush_queue(self.webhook_event_queue, node)
|
|
168
184
|
if not events: return
|
|
@@ -174,103 +190,8 @@ class NetworkInterface(Generic[ConfigType]):
|
|
|
174
190
|
return True
|
|
175
191
|
except httpx.ConnectError:
|
|
176
192
|
logger.warning("Broadcast failed")
|
|
177
|
-
for event in events:
|
|
178
|
-
self.push_event_to(event, node)
|
|
179
|
-
return False
|
|
180
|
-
|
|
181
|
-
def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
|
|
182
|
-
"""Returns list of node RIDs which provide state for the specified RID type."""
|
|
183
|
-
|
|
184
|
-
logger.debug(f"Looking for state providers of '{rid_type}'")
|
|
185
|
-
provider_nodes = []
|
|
186
|
-
for node_rid in self.cache.list_rids(rid_types=[KoiNetNode]):
|
|
187
|
-
node = self.graph.get_node_profile(node_rid)
|
|
188
|
-
|
|
189
|
-
if node.node_type == NodeType.FULL and rid_type in node.provides.state:
|
|
190
|
-
logger.debug(f"Found provider '{node_rid}'")
|
|
191
|
-
provider_nodes.append(node_rid)
|
|
192
|
-
|
|
193
|
-
if not provider_nodes:
|
|
194
|
-
logger.debug("Failed to find providers")
|
|
195
|
-
return provider_nodes
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
remote_bundle = None
|
|
202
|
-
for node_rid in self.get_state_providers(type(rid)):
|
|
203
|
-
payload = self.request_handler.fetch_bundles(
|
|
204
|
-
node=node_rid, rids=[rid])
|
|
205
|
-
|
|
206
|
-
if payload.bundles:
|
|
207
|
-
remote_bundle = payload.bundles[0]
|
|
208
|
-
logger.debug(f"Got bundle from '{node_rid}'")
|
|
209
|
-
break
|
|
210
|
-
|
|
211
|
-
if not remote_bundle:
|
|
212
|
-
logger.warning("Failed to fetch remote bundle")
|
|
213
|
-
|
|
214
|
-
return remote_bundle
|
|
215
|
-
|
|
216
|
-
def fetch_remote_manifest(self, rid: RID):
|
|
217
|
-
"""Attempts to fetch a manifest by RID from known peer nodes."""
|
|
218
|
-
|
|
219
|
-
logger.debug(f"Fetching remote manifest '{rid}'")
|
|
220
|
-
remote_manifest = None
|
|
221
|
-
for node_rid in self.get_state_providers(type(rid)):
|
|
222
|
-
payload = self.request_handler.fetch_manifests(
|
|
223
|
-
node=node_rid, rids=[rid])
|
|
224
|
-
|
|
225
|
-
if payload.manifests:
|
|
226
|
-
remote_manifest = payload.manifests[0]
|
|
227
|
-
logger.debug(f"Got bundle from '{node_rid}'")
|
|
228
|
-
break
|
|
229
|
-
|
|
230
|
-
if not remote_manifest:
|
|
231
|
-
logger.warning("Failed to fetch remote bundle")
|
|
232
|
-
|
|
233
|
-
return remote_manifest
|
|
234
|
-
|
|
235
|
-
def poll_neighbors(self) -> list[Event]:
|
|
236
|
-
"""Polls all neighboring nodes and returns compiled list of events.
|
|
237
|
-
|
|
238
|
-
If this node has no neighbors, it will instead attempt to poll the provided first contact URL.
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
neighbors = self.graph.get_neighbors()
|
|
242
|
-
|
|
243
|
-
if not neighbors and self.config.koi_net.first_contact:
|
|
244
|
-
logger.debug("No neighbors found, polling first contact")
|
|
245
|
-
try:
|
|
246
|
-
payload = self.request_handler.poll_events(
|
|
247
|
-
url=self.config.koi_net.first_contact,
|
|
248
|
-
rid=self.identity.rid
|
|
249
|
-
)
|
|
250
|
-
if payload.events:
|
|
251
|
-
logger.debug(f"Received {len(payload.events)} events from '{self.config.koi_net.first_contact}'")
|
|
252
|
-
return payload.events
|
|
253
|
-
except httpx.ConnectError:
|
|
254
|
-
logger.debug(f"Failed to reach first contact '{self.config.koi_net.first_contact}'")
|
|
255
|
-
|
|
256
|
-
events = []
|
|
257
|
-
for node_rid in neighbors:
|
|
258
|
-
node = self.graph.get_node_profile(node_rid)
|
|
259
|
-
if not node: continue
|
|
260
|
-
if node.node_type != NodeType.FULL: continue
|
|
261
|
-
|
|
262
|
-
try:
|
|
263
|
-
payload = self.request_handler.poll_events(
|
|
264
|
-
node=node_rid,
|
|
265
|
-
rid=self.identity.rid
|
|
266
|
-
)
|
|
267
|
-
if payload.events:
|
|
268
|
-
logger.debug(f"Received {len(payload.events)} events from {node_rid!r}")
|
|
269
|
-
events.extend(payload.events)
|
|
270
|
-
except httpx.ConnectError:
|
|
271
|
-
logger.debug(f"Failed to reach node '{node_rid}'")
|
|
272
|
-
continue
|
|
273
|
-
|
|
274
|
-
return events
|
|
275
|
-
|
|
276
|
-
|
|
194
|
+
if requeue_on_fail:
|
|
195
|
+
for event in events:
|
|
196
|
+
self.push_event_to(event, node)
|
|
197
|
+
return False
|
koi_net/network/graph.py
CHANGED
|
@@ -6,7 +6,6 @@ from rid_lib.ext import Cache
|
|
|
6
6
|
from rid_lib.types import KoiNetEdge, KoiNetNode
|
|
7
7
|
from ..identity import NodeIdentity
|
|
8
8
|
from ..protocol.edge import EdgeProfile, EdgeStatus
|
|
9
|
-
from ..protocol.node import NodeProfile
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
@@ -30,43 +29,27 @@ class NetworkGraph:
|
|
|
30
29
|
for rid in self.cache.list_rids():
|
|
31
30
|
if type(rid) == KoiNetNode:
|
|
32
31
|
self.dg.add_node(rid)
|
|
33
|
-
logger.debug(f"Added node {rid}")
|
|
32
|
+
logger.debug(f"Added node {rid!r}")
|
|
34
33
|
|
|
35
34
|
elif type(rid) == KoiNetEdge:
|
|
36
|
-
|
|
37
|
-
if not
|
|
35
|
+
edge_bundle = self.cache.read(rid)
|
|
36
|
+
if not edge_bundle:
|
|
38
37
|
logger.warning(f"Failed to load {rid!r}")
|
|
39
38
|
continue
|
|
39
|
+
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
40
40
|
self.dg.add_edge(edge_profile.source, edge_profile.target, rid=rid)
|
|
41
|
-
logger.debug(f"Added edge {rid} ({edge_profile.source} -> {edge_profile.target})")
|
|
41
|
+
logger.debug(f"Added edge {rid!r} ({edge_profile.source} -> {edge_profile.target})")
|
|
42
42
|
logger.debug("Done")
|
|
43
43
|
|
|
44
|
-
def
|
|
45
|
-
"""Returns
|
|
46
|
-
|
|
47
|
-
if bundle:
|
|
48
|
-
return bundle.validate_contents(NodeProfile)
|
|
49
|
-
|
|
50
|
-
def get_edge_profile(
|
|
51
|
-
self,
|
|
52
|
-
rid: KoiNetEdge | None = None,
|
|
53
|
-
source: KoiNetNode | None = None,
|
|
54
|
-
target: KoiNetNode | None = None,
|
|
55
|
-
) -> EdgeProfile | None:
|
|
56
|
-
"""Returns edge profile given its RID, or source and target node RIDs."""
|
|
57
|
-
if source and target:
|
|
58
|
-
if (source, target) not in self.dg.edges: return
|
|
44
|
+
def get_edge(self, source: KoiNetNode, target: KoiNetNode,) -> EdgeProfile | None:
|
|
45
|
+
"""Returns edge RID given the RIDs of a source and target node."""
|
|
46
|
+
if (source, target) in self.dg.edges:
|
|
59
47
|
edge_data = self.dg.get_edge_data(source, target)
|
|
60
|
-
if
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
bundle = self.cache.read(rid)
|
|
67
|
-
if bundle:
|
|
68
|
-
return bundle.validate_contents(EdgeProfile)
|
|
69
|
-
|
|
48
|
+
if edge_data:
|
|
49
|
+
return edge_data.get("rid")
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
70
53
|
def get_edges(
|
|
71
54
|
self,
|
|
72
55
|
direction: Literal["in", "out"] | None = None,
|
|
@@ -106,12 +89,14 @@ class NetworkGraph:
|
|
|
106
89
|
|
|
107
90
|
neighbors = []
|
|
108
91
|
for edge_rid in self.get_edges(direction):
|
|
109
|
-
|
|
92
|
+
edge_bundle = self.cache.read(edge_rid)
|
|
110
93
|
|
|
111
|
-
if not
|
|
94
|
+
if not edge_bundle:
|
|
112
95
|
logger.warning(f"Failed to find edge {edge_rid!r} in cache")
|
|
113
96
|
continue
|
|
114
|
-
|
|
97
|
+
|
|
98
|
+
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
99
|
+
|
|
115
100
|
if status and edge_profile.status != status:
|
|
116
101
|
continue
|
|
117
102
|
|