koi-net 1.1.0b8__py3-none-any.whl → 1.2.0b1__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/__init__.py +1 -1
- koi_net/behaviors.py +51 -0
- koi_net/cli/__init__.py +1 -0
- koi_net/cli/commands.py +99 -0
- koi_net/cli/models.py +41 -0
- koi_net/config.py +34 -0
- koi_net/context.py +12 -21
- koi_net/core.py +209 -170
- koi_net/default_actions.py +10 -1
- koi_net/effector.py +39 -24
- koi_net/handshaker.py +39 -0
- koi_net/kobj_worker.py +45 -0
- koi_net/lifecycle.py +67 -38
- koi_net/models.py +14 -0
- koi_net/network/error_handler.py +12 -10
- koi_net/network/event_queue.py +14 -184
- koi_net/network/graph.py +7 -2
- koi_net/network/request_handler.py +31 -19
- koi_net/network/resolver.py +5 -8
- koi_net/network/response_handler.py +5 -6
- koi_net/poll_event_buffer.py +17 -0
- koi_net/poller.py +12 -5
- koi_net/processor/default_handlers.py +84 -35
- koi_net/processor/event_worker.py +121 -0
- koi_net/processor/handler.py +4 -2
- koi_net/processor/knowledge_object.py +19 -7
- koi_net/processor/knowledge_pipeline.py +7 -26
- koi_net/processor/kobj_queue.py +51 -0
- koi_net/protocol/api_models.py +3 -2
- koi_net/protocol/node.py +3 -3
- koi_net/secure.py +28 -8
- koi_net/server.py +25 -9
- koi_net/utils.py +18 -0
- koi_net/worker.py +10 -0
- {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/METADATA +7 -3
- koi_net-1.2.0b1.dist-info/RECORD +49 -0
- koi_net-1.2.0b1.dist-info/entry_points.txt +2 -0
- koi_net/actor.py +0 -60
- koi_net/processor/interface.py +0 -101
- koi_net-1.1.0b8.dist-info/RECORD +0 -38
- {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/WHEEL +0 -0
- {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/licenses/LICENSE +0 -0
koi_net/lifecycle.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from contextlib import contextmanager, asynccontextmanager
|
|
3
3
|
|
|
4
|
+
from rid_lib.ext import Bundle, Cache
|
|
4
5
|
from rid_lib.types import KoiNetNode
|
|
5
6
|
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
7
|
+
from koi_net.behaviors import Behaviors
|
|
8
|
+
from koi_net.handshaker import Handshaker
|
|
9
|
+
from koi_net.kobj_worker import KnowledgeProcessingWorker
|
|
10
|
+
from koi_net.models import END
|
|
11
|
+
from koi_net.network.event_queue import EventQueue
|
|
12
|
+
from koi_net.processor.event_worker import EventProcessingWorker
|
|
13
|
+
|
|
8
14
|
from .config import NodeConfig
|
|
9
|
-
from .processor.
|
|
15
|
+
from .processor.kobj_queue import KobjQueue
|
|
10
16
|
from .network.graph import NetworkGraph
|
|
11
17
|
from .identity import NodeIdentity
|
|
12
18
|
|
|
@@ -14,32 +20,46 @@ logger = logging.getLogger(__name__)
|
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
class NodeLifecycle:
|
|
23
|
+
"""Manages node startup and shutdown processes."""
|
|
24
|
+
|
|
17
25
|
config: NodeConfig
|
|
26
|
+
identity: NodeIdentity
|
|
18
27
|
graph: NetworkGraph
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
kobj_queue: KobjQueue
|
|
29
|
+
kobj_worker: KnowledgeProcessingWorker
|
|
30
|
+
event_queue: EventQueue
|
|
31
|
+
event_worker: EventProcessingWorker
|
|
32
|
+
cache: Cache
|
|
33
|
+
handshaker: Handshaker
|
|
34
|
+
behaviors: Behaviors
|
|
22
35
|
|
|
23
36
|
def __init__(
|
|
24
37
|
self,
|
|
25
38
|
config: NodeConfig,
|
|
26
39
|
identity: NodeIdentity,
|
|
27
40
|
graph: NetworkGraph,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
kobj_queue: KobjQueue,
|
|
42
|
+
kobj_worker: KnowledgeProcessingWorker,
|
|
43
|
+
event_queue: EventQueue,
|
|
44
|
+
event_worker: EventProcessingWorker,
|
|
45
|
+
cache: Cache,
|
|
46
|
+
handshaker: Handshaker,
|
|
47
|
+
behaviors: Behaviors
|
|
32
48
|
):
|
|
33
49
|
self.config = config
|
|
34
50
|
self.identity = identity
|
|
35
51
|
self.graph = graph
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
52
|
+
self.kobj_queue = kobj_queue
|
|
53
|
+
self.kobj_worker = kobj_worker
|
|
54
|
+
self.event_queue = event_queue
|
|
55
|
+
self.event_worker = event_worker
|
|
56
|
+
self.cache = cache
|
|
57
|
+
self.handshaker = handshaker
|
|
58
|
+
self.behaviors = behaviors
|
|
40
59
|
|
|
41
60
|
@contextmanager
|
|
42
61
|
def run(self):
|
|
62
|
+
"""Synchronous context manager for node startup and shutdown."""
|
|
43
63
|
try:
|
|
44
64
|
logger.info("Starting node lifecycle...")
|
|
45
65
|
self.start()
|
|
@@ -52,6 +72,7 @@ class NodeLifecycle:
|
|
|
52
72
|
|
|
53
73
|
@asynccontextmanager
|
|
54
74
|
async def async_run(self):
|
|
75
|
+
"""Asynchronous context manager for node startup and shutdown."""
|
|
55
76
|
try:
|
|
56
77
|
logger.info("Starting async node lifecycle...")
|
|
57
78
|
self.start()
|
|
@@ -63,42 +84,50 @@ class NodeLifecycle:
|
|
|
63
84
|
self.stop()
|
|
64
85
|
|
|
65
86
|
def start(self):
|
|
66
|
-
"""Starts a node
|
|
87
|
+
"""Starts a node.
|
|
67
88
|
|
|
68
|
-
Starts the processor thread (if enabled).
|
|
89
|
+
Starts the processor thread (if enabled). Generates network
|
|
90
|
+
graph from nodes and edges in cache. Processes any state changes
|
|
91
|
+
of node bundle. Initiates handshake with first contact if node
|
|
92
|
+
doesn't have any neighbors. Catches up with coordinator state.
|
|
69
93
|
"""
|
|
70
|
-
|
|
71
|
-
logger.info("Starting processor worker thread")
|
|
72
|
-
self.processor.worker_thread.start()
|
|
94
|
+
logger.info("Starting processor worker thread")
|
|
73
95
|
|
|
96
|
+
self.kobj_worker.thread.start()
|
|
97
|
+
self.event_worker.thread.start()
|
|
74
98
|
self.graph.generate()
|
|
75
99
|
|
|
76
100
|
# refresh to reflect changes (if any) in config.yaml
|
|
77
|
-
|
|
101
|
+
|
|
102
|
+
self.kobj_queue.put_kobj(bundle=Bundle.generate(
|
|
103
|
+
rid=self.identity.rid,
|
|
104
|
+
contents=self.identity.profile.model_dump()
|
|
105
|
+
))
|
|
78
106
|
|
|
79
107
|
logger.debug("Waiting for kobj queue to empty")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if
|
|
87
|
-
logger.debug(f"I don't have any
|
|
108
|
+
self.kobj_queue.q.join()
|
|
109
|
+
|
|
110
|
+
# TODO: FACTOR OUT BEHAVIOR
|
|
111
|
+
|
|
112
|
+
coordinators = self.graph.get_neighbors(direction="in", allowed_type=KoiNetNode)
|
|
113
|
+
|
|
114
|
+
if len(coordinators) == 0 and self.config.koi_net.first_contact.rid:
|
|
115
|
+
logger.debug(f"I don't have any edges with coordinators, reaching out to first contact {self.config.koi_net.first_contact.rid!r}")
|
|
88
116
|
|
|
89
|
-
self.
|
|
117
|
+
self.handshaker.handshake_with(self.config.koi_net.first_contact.rid)
|
|
118
|
+
|
|
90
119
|
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
|
|
121
|
+
for coordinator in self.behaviors.identify_coordinators():
|
|
122
|
+
self.behaviors.catch_up_with(coordinator, rid_types=[KoiNetNode])
|
|
93
123
|
|
|
94
124
|
|
|
95
125
|
def stop(self):
|
|
96
|
-
"""Stops a node
|
|
126
|
+
"""Stops a node.
|
|
97
127
|
|
|
98
|
-
Finishes processing knowledge object queue.
|
|
128
|
+
Finishes processing knowledge object queue.
|
|
99
129
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.processor.flush_kobj_queue()
|
|
130
|
+
logger.info(f"Waiting for kobj queue to empty ({self.kobj_queue.q.unfinished_tasks} tasks remaining)")
|
|
131
|
+
|
|
132
|
+
self.kobj_queue.q.put(END)
|
|
133
|
+
self.event_queue.q.put(END)
|
koi_net/models.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from rid_lib.types import KoiNetNode
|
|
3
|
+
from koi_net.protocol.event import Event
|
|
4
|
+
|
|
5
|
+
class End:
|
|
6
|
+
"""Class for a sentinel value by knowledge handlers."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
END = End()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QueuedEvent(BaseModel):
|
|
13
|
+
event: Event
|
|
14
|
+
target: KoiNetNode
|
koi_net/network/error_handler.py
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
from logging import getLogger
|
|
2
|
+
from koi_net.handshaker import Handshaker
|
|
2
3
|
from koi_net.protocol.errors import ErrorType
|
|
3
4
|
from koi_net.protocol.event import EventType
|
|
4
5
|
from rid_lib.types import KoiNetNode
|
|
5
|
-
from ..processor.
|
|
6
|
-
from ..actor import Actor
|
|
6
|
+
from ..processor.kobj_queue import KobjQueue
|
|
7
7
|
|
|
8
8
|
logger = getLogger(__name__)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ErrorHandler:
|
|
12
|
+
"""Handles network errors that may occur during requests."""
|
|
12
13
|
timeout_counter: dict[KoiNetNode, int]
|
|
13
|
-
|
|
14
|
-
actor: Actor
|
|
14
|
+
kobj_queue: KobjQueue
|
|
15
15
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
kobj_queue: KobjQueue,
|
|
19
|
+
handshaker: Handshaker
|
|
20
20
|
):
|
|
21
|
-
self.
|
|
22
|
-
self.
|
|
21
|
+
self.kobj_queue = kobj_queue
|
|
22
|
+
self.handshaker = handshaker
|
|
23
23
|
self.timeout_counter = {}
|
|
24
24
|
|
|
25
25
|
def handle_connection_error(self, node: KoiNetNode):
|
|
26
|
+
"""Drops nodes after timing out three times."""
|
|
26
27
|
self.timeout_counter.setdefault(node, 0)
|
|
27
28
|
self.timeout_counter[node] += 1
|
|
28
29
|
|
|
@@ -30,7 +31,7 @@ class ErrorHandler:
|
|
|
30
31
|
|
|
31
32
|
if self.timeout_counter[node] > 3:
|
|
32
33
|
logger.debug(f"Exceeded time out limit, forgetting node")
|
|
33
|
-
self.
|
|
34
|
+
self.kobj_queue.put_kobj(rid=node, event_type=EventType.FORGET)
|
|
34
35
|
# do something
|
|
35
36
|
|
|
36
37
|
|
|
@@ -39,11 +40,12 @@ class ErrorHandler:
|
|
|
39
40
|
error_type: ErrorType,
|
|
40
41
|
node: KoiNetNode
|
|
41
42
|
):
|
|
43
|
+
"""Attempts handshake when this node is unknown to target."""
|
|
42
44
|
logger.info(f"Handling protocol error {error_type} for node {node!r}")
|
|
43
45
|
match error_type:
|
|
44
46
|
case ErrorType.UnknownNode:
|
|
45
47
|
logger.info("Peer doesn't know me, attempting handshake...")
|
|
46
|
-
self.
|
|
48
|
+
self.handshaker.handshake_with(node)
|
|
47
49
|
|
|
48
50
|
case ErrorType.InvalidKey: ...
|
|
49
51
|
case ErrorType.InvalidSignature: ...
|
koi_net/network/event_queue.py
CHANGED
|
@@ -1,199 +1,29 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from queue import Queue
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
from rid_lib import RID
|
|
6
|
-
from rid_lib.ext import Cache
|
|
3
|
+
|
|
7
4
|
from rid_lib.types import KoiNetNode
|
|
8
5
|
|
|
9
|
-
from
|
|
10
|
-
from .request_handler import NodeNotFoundError, RequestHandler
|
|
11
|
-
from ..protocol.node import NodeProfile, NodeType
|
|
12
|
-
from ..protocol.edge import EdgeProfile, EdgeType
|
|
6
|
+
from ..models import QueuedEvent
|
|
13
7
|
from ..protocol.event import Event
|
|
14
|
-
from ..identity import NodeIdentity
|
|
15
|
-
from ..config import NodeConfig
|
|
16
|
-
from ..effector import Effector
|
|
17
8
|
|
|
18
9
|
logger = logging.getLogger(__name__)
|
|
19
10
|
|
|
20
11
|
|
|
21
|
-
class
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
type EventQueue = dict[RID, Queue[Event]]
|
|
26
|
-
|
|
27
|
-
class NetworkEventQueue:
|
|
28
|
-
"""A collection of functions and classes to interact with the KOI network."""
|
|
29
|
-
|
|
30
|
-
config: NodeConfig
|
|
31
|
-
identity: NodeIdentity
|
|
32
|
-
effector: Effector
|
|
33
|
-
cache: Cache
|
|
34
|
-
graph: NetworkGraph
|
|
35
|
-
request_handler: RequestHandler
|
|
36
|
-
poll_event_queue: EventQueue
|
|
37
|
-
webhook_event_queue: EventQueue
|
|
12
|
+
class EventQueue:
|
|
13
|
+
"""Handles out going network event queues."""
|
|
14
|
+
q: Queue[QueuedEvent]
|
|
38
15
|
|
|
39
|
-
def __init__(
|
|
40
|
-
self
|
|
41
|
-
config: NodeConfig,
|
|
42
|
-
cache: Cache,
|
|
43
|
-
identity: NodeIdentity,
|
|
44
|
-
effector: Effector,
|
|
45
|
-
graph: NetworkGraph,
|
|
46
|
-
request_handler: RequestHandler,
|
|
47
|
-
):
|
|
48
|
-
self.config = config
|
|
49
|
-
self.identity = identity
|
|
50
|
-
self.cache = cache
|
|
51
|
-
self.graph = graph
|
|
52
|
-
self.request_handler = request_handler
|
|
53
|
-
self.effector = effector
|
|
54
|
-
|
|
55
|
-
self.poll_event_queue = dict()
|
|
56
|
-
self.webhook_event_queue = dict()
|
|
57
|
-
|
|
58
|
-
def _load_event_queues(self):
|
|
59
|
-
"""Loads event queues from storage."""
|
|
60
|
-
try:
|
|
61
|
-
with open(self.config.koi_net.event_queues_path, "r") as f:
|
|
62
|
-
queues = EventQueueModel.model_validate_json(f.read())
|
|
63
|
-
|
|
64
|
-
for node in queues.poll.keys():
|
|
65
|
-
for event in queues.poll[node]:
|
|
66
|
-
queue = self.poll_event_queue.setdefault(node, Queue())
|
|
67
|
-
queue.put(event)
|
|
68
|
-
|
|
69
|
-
for node in queues.webhook.keys():
|
|
70
|
-
for event in queues.webhook[node]:
|
|
71
|
-
queue = self.webhook_event_queue.setdefault(node, Queue())
|
|
72
|
-
queue.put(event)
|
|
73
|
-
|
|
74
|
-
except FileNotFoundError:
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
def _save_event_queues(self):
|
|
78
|
-
"""Writes event queues to storage."""
|
|
79
|
-
events_model = EventQueueModel(
|
|
80
|
-
poll={
|
|
81
|
-
node: list(queue.queue)
|
|
82
|
-
for node, queue in self.poll_event_queue.items()
|
|
83
|
-
if not queue.empty()
|
|
84
|
-
},
|
|
85
|
-
webhook={
|
|
86
|
-
node: list(queue.queue)
|
|
87
|
-
for node, queue in self.webhook_event_queue.items()
|
|
88
|
-
if not queue.empty()
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
if len(events_model.poll) == 0 and len(events_model.webhook) == 0:
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
with open(self.config.koi_net.event_queues_path, "w") as f:
|
|
96
|
-
f.write(events_model.model_dump_json(indent=2))
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.q = Queue()
|
|
97
18
|
|
|
98
|
-
def push_event_to(self, event: Event,
|
|
19
|
+
def push_event_to(self, event: Event, target: KoiNetNode):
|
|
99
20
|
"""Pushes event to queue of specified node.
|
|
100
21
|
|
|
101
|
-
Event will be sent to webhook or poll queue depending on the
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
node_bundle = self.effector.deref(node)
|
|
106
|
-
|
|
107
|
-
# if there's an edge from me to the target node, override broadcast type
|
|
108
|
-
edge_rid = self.graph.get_edge(
|
|
109
|
-
source=self.identity.rid,
|
|
110
|
-
target=node
|
|
111
|
-
)
|
|
112
|
-
|
|
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)
|
|
118
|
-
if edge_profile.edge_type == EdgeType.WEBHOOK:
|
|
119
|
-
event_queue = self.webhook_event_queue
|
|
120
|
-
elif edge_profile.edge_type == EdgeType.POLL:
|
|
121
|
-
event_queue = self.poll_event_queue
|
|
122
|
-
|
|
123
|
-
elif node_bundle:
|
|
124
|
-
logger.debug(f"Found bundle for {node!r}")
|
|
125
|
-
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
126
|
-
if node_profile.node_type == NodeType.FULL:
|
|
127
|
-
event_queue = self.webhook_event_queue
|
|
128
|
-
elif node_profile.node_type == NodeType.PARTIAL:
|
|
129
|
-
event_queue = self.poll_event_queue
|
|
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
|
-
|
|
140
|
-
queue = event_queue.setdefault(node, Queue())
|
|
141
|
-
queue.put(event)
|
|
142
|
-
|
|
143
|
-
if flush and event_queue is self.webhook_event_queue:
|
|
144
|
-
self.flush_webhook_queue(node)
|
|
145
|
-
|
|
146
|
-
def _flush_queue(self, event_queue: EventQueue, node: KoiNetNode) -> list[Event]:
|
|
147
|
-
"""Flushes a node's queue, returning list of events."""
|
|
148
|
-
queue = event_queue.get(node)
|
|
149
|
-
events = list()
|
|
150
|
-
if queue:
|
|
151
|
-
while not queue.empty():
|
|
152
|
-
event = queue.get()
|
|
153
|
-
logger.debug(f"Dequeued {event.event_type} {event.rid!r}")
|
|
154
|
-
events.append(event)
|
|
155
|
-
|
|
156
|
-
return events
|
|
157
|
-
|
|
158
|
-
def flush_poll_queue(self, node: KoiNetNode) -> list[Event]:
|
|
159
|
-
"""Flushes a node's poll queue, returning list of events."""
|
|
160
|
-
logger.debug(f"Flushing poll queue for {node}")
|
|
161
|
-
return self._flush_queue(self.poll_event_queue, node)
|
|
162
|
-
|
|
163
|
-
def flush_webhook_queue(self, node: KoiNetNode, requeue_on_fail: bool = True):
|
|
164
|
-
"""Flushes a node's webhook queue, and broadcasts events.
|
|
165
|
-
|
|
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.
|
|
22
|
+
Event will be sent to webhook or poll queue depending on the
|
|
23
|
+
node type and edge type of the specified node. If `flush` is set
|
|
24
|
+
to `True`, the webhook queued will be flushed after pushing the
|
|
25
|
+
event.
|
|
167
26
|
"""
|
|
168
27
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
# node_bundle = self.effector.deref(node)
|
|
172
|
-
|
|
173
|
-
# if not node_bundle:
|
|
174
|
-
# logger.warning(f"{node!r} not found")
|
|
175
|
-
# return
|
|
176
|
-
|
|
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
|
|
182
|
-
|
|
183
|
-
events = self._flush_queue(self.webhook_event_queue, node)
|
|
184
|
-
if not events: return
|
|
185
|
-
|
|
186
|
-
logger.debug(f"Broadcasting {len(events)} events")
|
|
187
|
-
|
|
188
|
-
try:
|
|
189
|
-
self.request_handler.broadcast_events(node, events=events)
|
|
190
|
-
|
|
191
|
-
except NodeNotFoundError:
|
|
192
|
-
logger.warning("Broadcast failed (node not found)")
|
|
193
|
-
|
|
194
|
-
except httpx.ConnectError:
|
|
195
|
-
logger.warning("Broadcast failed (couldn't connect)")
|
|
196
|
-
|
|
197
|
-
if requeue_on_fail:
|
|
198
|
-
for event in events:
|
|
199
|
-
self.push_event_to(event, node)
|
|
28
|
+
self.q.put(QueuedEvent(target=target, event=event))
|
|
29
|
+
|
koi_net/network/graph.py
CHANGED
|
@@ -56,7 +56,9 @@ class NetworkGraph:
|
|
|
56
56
|
) -> list[KoiNetEdge]:
|
|
57
57
|
"""Returns edges this node belongs to.
|
|
58
58
|
|
|
59
|
-
All edges returned by default, specify `direction` to restrict
|
|
59
|
+
All edges returned by default, specify `direction` to restrict
|
|
60
|
+
to incoming or outgoing edges only.
|
|
61
|
+
"""
|
|
60
62
|
|
|
61
63
|
edges = []
|
|
62
64
|
if direction != "in" and self.dg.out_edges:
|
|
@@ -85,7 +87,10 @@ class NetworkGraph:
|
|
|
85
87
|
) -> list[KoiNetNode]:
|
|
86
88
|
"""Returns neighboring nodes this node shares an edge with.
|
|
87
89
|
|
|
88
|
-
All neighboring nodes returned by default, specify `direction`
|
|
90
|
+
All neighboring nodes returned by default, specify `direction`
|
|
91
|
+
to restrict to neighbors connected by incoming or outgoing edges
|
|
92
|
+
only.
|
|
93
|
+
"""
|
|
89
94
|
|
|
90
95
|
neighbors = set()
|
|
91
96
|
for edge_rid in self.get_edges(direction):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import httpx
|
|
3
3
|
from rid_lib import RID
|
|
4
|
+
from rid_lib.ext import Cache
|
|
4
5
|
from rid_lib.types.koi_net_node import KoiNetNode
|
|
5
6
|
|
|
6
7
|
from ..identity import NodeIdentity
|
|
@@ -27,11 +28,7 @@ from ..protocol.consts import (
|
|
|
27
28
|
)
|
|
28
29
|
from ..protocol.node import NodeProfile, NodeType
|
|
29
30
|
from ..secure import Secure
|
|
30
|
-
from
|
|
31
|
-
|
|
32
|
-
from typing import TYPE_CHECKING
|
|
33
|
-
if TYPE_CHECKING:
|
|
34
|
-
from .error_handler import ErrorHandler
|
|
31
|
+
from .error_handler import ErrorHandler
|
|
35
32
|
|
|
36
33
|
|
|
37
34
|
logger = logging.getLogger(__name__)
|
|
@@ -57,26 +54,25 @@ class UnknownPathError(Exception):
|
|
|
57
54
|
class RequestHandler:
|
|
58
55
|
"""Handles making requests to other KOI nodes."""
|
|
59
56
|
|
|
60
|
-
|
|
57
|
+
cache: Cache
|
|
61
58
|
identity: NodeIdentity
|
|
62
59
|
secure: Secure
|
|
63
|
-
error_handler:
|
|
60
|
+
error_handler: ErrorHandler
|
|
64
61
|
|
|
65
62
|
def __init__(
|
|
66
63
|
self,
|
|
67
|
-
|
|
64
|
+
cache: Cache,
|
|
68
65
|
identity: NodeIdentity,
|
|
69
|
-
secure: Secure
|
|
66
|
+
secure: Secure,
|
|
67
|
+
error_handler: ErrorHandler
|
|
70
68
|
):
|
|
71
|
-
self.
|
|
69
|
+
self.cache = cache
|
|
72
70
|
self.identity = identity
|
|
73
71
|
self.secure = secure
|
|
74
|
-
|
|
75
|
-
def set_error_handler(self, error_handler: "ErrorHandler"):
|
|
76
72
|
self.error_handler = error_handler
|
|
77
73
|
|
|
78
74
|
def get_url(self, node_rid: KoiNetNode) -> str:
|
|
79
|
-
"""Retrieves URL of a node."""
|
|
75
|
+
"""Retrieves URL of a node from its RID."""
|
|
80
76
|
|
|
81
77
|
logger.debug(f"Getting URL for {node_rid!r}")
|
|
82
78
|
node_url = None
|
|
@@ -84,7 +80,7 @@ class RequestHandler:
|
|
|
84
80
|
if node_rid == self.identity.rid:
|
|
85
81
|
raise SelfRequestError("Don't talk to yourself")
|
|
86
82
|
|
|
87
|
-
node_bundle = self.
|
|
83
|
+
node_bundle = self.cache.read(node_rid)
|
|
88
84
|
|
|
89
85
|
if node_bundle:
|
|
90
86
|
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
@@ -110,6 +106,7 @@ class RequestHandler:
|
|
|
110
106
|
path: str,
|
|
111
107
|
request: RequestModels,
|
|
112
108
|
) -> ResponseModels | None:
|
|
109
|
+
"""Makes a request to a node."""
|
|
113
110
|
url = self.get_url(node) + path
|
|
114
111
|
logger.info(f"Making request to {url}")
|
|
115
112
|
|
|
@@ -154,7 +151,10 @@ class RequestHandler:
|
|
|
154
151
|
req: EventsPayload | None = None,
|
|
155
152
|
**kwargs
|
|
156
153
|
) -> None:
|
|
157
|
-
"""
|
|
154
|
+
"""Broadcasts events to a node.
|
|
155
|
+
|
|
156
|
+
Pass `EventsPayload` object, or see `protocol.api_models.EventsPayload` for available kwargs.
|
|
157
|
+
"""
|
|
158
158
|
request = req or EventsPayload.model_validate(kwargs)
|
|
159
159
|
self.make_request(node, BROADCAST_EVENTS_PATH, request)
|
|
160
160
|
logger.info(f"Broadcasted {len(request.events)} event(s) to {node!r}")
|
|
@@ -165,7 +165,10 @@ class RequestHandler:
|
|
|
165
165
|
req: PollEvents | None = None,
|
|
166
166
|
**kwargs
|
|
167
167
|
) -> EventsPayload:
|
|
168
|
-
"""
|
|
168
|
+
"""Polls events from a node.
|
|
169
|
+
|
|
170
|
+
Pass `PollEvents` object as `req` or fields as kwargs.
|
|
171
|
+
"""
|
|
169
172
|
request = req or PollEvents.model_validate(kwargs)
|
|
170
173
|
resp = self.make_request(node, POLL_EVENTS_PATH, request)
|
|
171
174
|
if type(resp) != ErrorResponse:
|
|
@@ -178,7 +181,10 @@ class RequestHandler:
|
|
|
178
181
|
req: FetchRids | None = None,
|
|
179
182
|
**kwargs
|
|
180
183
|
) -> RidsPayload:
|
|
181
|
-
"""
|
|
184
|
+
"""Fetches RIDs from a node.
|
|
185
|
+
|
|
186
|
+
Pass `FetchRids` object as `req` or fields as kwargs.
|
|
187
|
+
"""
|
|
182
188
|
request = req or FetchRids.model_validate(kwargs)
|
|
183
189
|
resp = self.make_request(node, FETCH_RIDS_PATH, request)
|
|
184
190
|
if type(resp) != ErrorResponse:
|
|
@@ -191,7 +197,10 @@ class RequestHandler:
|
|
|
191
197
|
req: FetchManifests | None = None,
|
|
192
198
|
**kwargs
|
|
193
199
|
) -> ManifestsPayload:
|
|
194
|
-
"""
|
|
200
|
+
"""Fetches manifests from a node.
|
|
201
|
+
|
|
202
|
+
Pass `FetchManifests` object as `req` or fields as kwargs.
|
|
203
|
+
"""
|
|
195
204
|
request = req or FetchManifests.model_validate(kwargs)
|
|
196
205
|
resp = self.make_request(node, FETCH_MANIFESTS_PATH, request)
|
|
197
206
|
if type(resp) != ErrorResponse:
|
|
@@ -204,7 +213,10 @@ class RequestHandler:
|
|
|
204
213
|
req: FetchBundles | None = None,
|
|
205
214
|
**kwargs
|
|
206
215
|
) -> BundlesPayload:
|
|
207
|
-
"""
|
|
216
|
+
"""Fetches bundles from a node.
|
|
217
|
+
|
|
218
|
+
Pass `FetchBundles` object as `req` or fields as kwargs.
|
|
219
|
+
"""
|
|
208
220
|
request = req or FetchBundles.model_validate(kwargs)
|
|
209
221
|
resp = self.make_request(node, FETCH_BUNDLES_PATH, request)
|
|
210
222
|
if type(resp) != ErrorResponse:
|
koi_net/network/resolver.py
CHANGED
|
@@ -12,17 +12,15 @@ from ..protocol.event import Event
|
|
|
12
12
|
from ..protocol.api_models import ErrorResponse
|
|
13
13
|
from ..identity import NodeIdentity
|
|
14
14
|
from ..config import NodeConfig
|
|
15
|
-
from ..effector import Effector
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class NetworkResolver:
|
|
21
|
-
"""
|
|
20
|
+
"""Handles resolving nodes or knowledge objects from the network."""
|
|
22
21
|
|
|
23
22
|
config: NodeConfig
|
|
24
23
|
identity: NodeIdentity
|
|
25
|
-
effector: Effector
|
|
26
24
|
cache: Cache
|
|
27
25
|
graph: NetworkGraph
|
|
28
26
|
request_handler: RequestHandler
|
|
@@ -32,7 +30,6 @@ class NetworkResolver:
|
|
|
32
30
|
config: NodeConfig,
|
|
33
31
|
cache: Cache,
|
|
34
32
|
identity: NodeIdentity,
|
|
35
|
-
effector: Effector,
|
|
36
33
|
graph: NetworkGraph,
|
|
37
34
|
request_handler: RequestHandler,
|
|
38
35
|
):
|
|
@@ -41,13 +38,12 @@ class NetworkResolver:
|
|
|
41
38
|
self.cache = cache
|
|
42
39
|
self.graph = graph
|
|
43
40
|
self.request_handler = request_handler
|
|
44
|
-
self.effector = effector
|
|
45
41
|
|
|
46
42
|
self.poll_event_queue = dict()
|
|
47
43
|
self.webhook_event_queue = dict()
|
|
48
44
|
|
|
49
45
|
def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
|
|
50
|
-
"""Returns list of node RIDs which provide state for
|
|
46
|
+
"""Returns list of node RIDs which provide state for specified RID type."""
|
|
51
47
|
|
|
52
48
|
logger.debug(f"Looking for state providers of {rid_type}")
|
|
53
49
|
provider_nodes = []
|
|
@@ -106,9 +102,10 @@ class NetworkResolver:
|
|
|
106
102
|
return remote_manifest, node_rid
|
|
107
103
|
|
|
108
104
|
def poll_neighbors(self) -> dict[KoiNetNode, list[Event]]:
|
|
109
|
-
"""Polls all
|
|
105
|
+
"""Polls all neighbor nodes and returns compiled list of events.
|
|
110
106
|
|
|
111
|
-
|
|
107
|
+
Neighbor nodes also include the first contact, regardless of
|
|
108
|
+
whether the first contact profile is known to this node.
|
|
112
109
|
"""
|
|
113
110
|
|
|
114
111
|
graph_neighbors = self.graph.get_neighbors()
|