koi-net 1.1.0b8__py3-none-any.whl → 1.2.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/__init__.py +2 -1
- koi_net/assembler.py +82 -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 +11 -28
- koi_net/core.py +63 -179
- koi_net/default_actions.py +10 -1
- koi_net/effector.py +61 -34
- koi_net/handshaker.py +39 -0
- koi_net/identity.py +2 -3
- koi_net/interfaces/entrypoint.py +5 -0
- koi_net/interfaces/worker.py +17 -0
- koi_net/lifecycle.py +85 -48
- koi_net/logger.py +176 -0
- koi_net/network/error_handler.py +18 -16
- koi_net/network/event_queue.py +17 -185
- koi_net/network/graph.py +15 -10
- koi_net/network/poll_event_buffer.py +26 -0
- koi_net/network/request_handler.py +54 -47
- koi_net/network/resolver.py +18 -21
- koi_net/network/response_handler.py +79 -15
- koi_net/poller.py +18 -9
- koi_net/processor/event_worker.py +117 -0
- koi_net/processor/handler.py +4 -2
- koi_net/processor/{default_handlers.py → handlers.py} +109 -59
- koi_net/processor/knowledge_object.py +19 -7
- koi_net/processor/kobj_queue.py +51 -0
- koi_net/processor/kobj_worker.py +44 -0
- koi_net/processor/{knowledge_pipeline.py → pipeline.py} +31 -53
- koi_net/protocol/api_models.py +7 -3
- koi_net/protocol/envelope.py +5 -6
- koi_net/protocol/model_map.py +61 -0
- koi_net/protocol/node.py +3 -3
- koi_net/protocol/secure.py +8 -8
- koi_net/secure.py +33 -13
- koi_net/sentry.py +13 -0
- koi_net/server.py +44 -78
- koi_net/utils.py +18 -0
- {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/METADATA +8 -3
- koi_net-1.2.0b2.dist-info/RECORD +52 -0
- koi_net-1.2.0b2.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.0b2.dist-info}/WHEEL +0 -0
- {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/licenses/LICENSE +0 -0
koi_net/network/error_handler.py
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import structlog
|
|
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
|
+
log = structlog.stdlib.get_logger()
|
|
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
|
|
|
29
|
-
|
|
30
|
+
log.debug(f"{node} has timed out {self.timeout_counter[node]} time(s)")
|
|
30
31
|
|
|
31
32
|
if self.timeout_counter[node] > 3:
|
|
32
|
-
|
|
33
|
-
self.
|
|
33
|
+
log.debug(f"Exceeded time out limit, forgetting node")
|
|
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
|
):
|
|
42
|
-
|
|
43
|
+
"""Attempts handshake when this node is unknown to target."""
|
|
44
|
+
log.info(f"Handling protocol error {error_type} for node {node!r}")
|
|
43
45
|
match error_type:
|
|
44
46
|
case ErrorType.UnknownNode:
|
|
45
|
-
|
|
46
|
-
self.
|
|
47
|
+
log.info("Peer doesn't know me, attempting handshake...")
|
|
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,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
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
|
|
5
|
+
from pydantic import BaseModel
|
|
8
6
|
|
|
9
|
-
from .graph import NetworkGraph
|
|
10
|
-
from .request_handler import NodeNotFoundError, RequestHandler
|
|
11
|
-
from ..protocol.node import NodeProfile, NodeType
|
|
12
|
-
from ..protocol.edge import EdgeProfile, EdgeType
|
|
13
7
|
from ..protocol.event import Event
|
|
14
|
-
from ..identity import NodeIdentity
|
|
15
|
-
from ..config import NodeConfig
|
|
16
|
-
from ..effector import Effector
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
8
|
|
|
9
|
+
log = structlog.stdlib.get_logger()
|
|
20
10
|
|
|
21
|
-
class EventQueueModel(BaseModel):
|
|
22
|
-
webhook: dict[KoiNetNode, list[Event]]
|
|
23
|
-
poll: dict[KoiNetNode, list[Event]]
|
|
24
11
|
|
|
25
|
-
|
|
12
|
+
class QueuedEvent(BaseModel):
|
|
13
|
+
event: Event
|
|
14
|
+
target: KoiNetNode
|
|
26
15
|
|
|
27
|
-
class
|
|
28
|
-
"""
|
|
16
|
+
class EventQueue:
|
|
17
|
+
"""Handles out going network event queues."""
|
|
18
|
+
q: Queue[QueuedEvent]
|
|
29
19
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
effector: Effector
|
|
33
|
-
cache: Cache
|
|
34
|
-
graph: NetworkGraph
|
|
35
|
-
request_handler: RequestHandler
|
|
36
|
-
poll_event_queue: EventQueue
|
|
37
|
-
webhook_event_queue: EventQueue
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.q = Queue()
|
|
38
22
|
|
|
39
|
-
def
|
|
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))
|
|
97
|
-
|
|
98
|
-
def push_event_to(self, event: Event, node: KoiNetNode, flush=False):
|
|
23
|
+
def push_event_to(self, event: Event, target: KoiNetNode):
|
|
99
24
|
"""Pushes event to queue of specified node.
|
|
100
25
|
|
|
101
|
-
Event will be sent to webhook or poll queue depending on the
|
|
26
|
+
Event will be sent to webhook or poll queue depending on the
|
|
27
|
+
node type and edge type of the specified node.
|
|
102
28
|
"""
|
|
103
|
-
logger.debug(f"Pushing event {event.event_type} {event.rid!r} to {node}")
|
|
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
29
|
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
167
|
-
"""
|
|
168
|
-
|
|
169
|
-
logger.debug(f"Flushing webhook queue for {node}")
|
|
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)
|
|
30
|
+
self.q.put(QueuedEvent(target=target, event=event))
|
|
31
|
+
|
koi_net/network/graph.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
from typing import Literal
|
|
3
3
|
import networkx as nx
|
|
4
4
|
from rid_lib import RIDType
|
|
@@ -7,7 +7,7 @@ from rid_lib.types import KoiNetEdge, KoiNetNode
|
|
|
7
7
|
from ..identity import NodeIdentity
|
|
8
8
|
from ..protocol.edge import EdgeProfile, EdgeStatus
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
log = structlog.stdlib.get_logger()
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class NetworkGraph:
|
|
@@ -24,22 +24,22 @@ class NetworkGraph:
|
|
|
24
24
|
|
|
25
25
|
def generate(self):
|
|
26
26
|
"""Generates directed graph from cached KOI nodes and edges."""
|
|
27
|
-
|
|
27
|
+
log.debug("Generating network graph")
|
|
28
28
|
self.dg.clear()
|
|
29
29
|
for rid in self.cache.list_rids():
|
|
30
30
|
if type(rid) == KoiNetNode:
|
|
31
31
|
self.dg.add_node(rid)
|
|
32
|
-
|
|
32
|
+
log.debug(f"Added node {rid!r}")
|
|
33
33
|
|
|
34
34
|
elif type(rid) == KoiNetEdge:
|
|
35
35
|
edge_bundle = self.cache.read(rid)
|
|
36
36
|
if not edge_bundle:
|
|
37
|
-
|
|
37
|
+
log.warning(f"Failed to load {rid!r}")
|
|
38
38
|
continue
|
|
39
39
|
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
40
40
|
self.dg.add_edge(edge_profile.source, edge_profile.target, rid=rid)
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
log.debug(f"Added edge {rid!r} ({edge_profile.source} -> {edge_profile.target})")
|
|
42
|
+
log.debug("Done")
|
|
43
43
|
|
|
44
44
|
def get_edge(self, source: KoiNetNode, target: KoiNetNode,) -> KoiNetEdge | None:
|
|
45
45
|
"""Returns edge RID given the RIDs of a source and target node."""
|
|
@@ -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,14 +87,17 @@ 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):
|
|
92
97
|
edge_bundle = self.cache.read(edge_rid)
|
|
93
98
|
|
|
94
99
|
if not edge_bundle:
|
|
95
|
-
|
|
100
|
+
log.warning(f"Failed to find edge {edge_rid!r} in cache")
|
|
96
101
|
continue
|
|
97
102
|
|
|
98
103
|
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from rid_lib.types import KoiNetNode
|
|
2
|
+
|
|
3
|
+
from koi_net.protocol.event import Event
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PollEventBuffer:
|
|
7
|
+
buffers: dict[KoiNetNode, list[Event]]
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.buffers = dict()
|
|
11
|
+
|
|
12
|
+
def put(self, node: KoiNetNode, event: Event):
|
|
13
|
+
event_buf = self.buffers.setdefault(node, [])
|
|
14
|
+
event_buf.append(event)
|
|
15
|
+
|
|
16
|
+
def flush(self, node: KoiNetNode, limit: int = 0):
|
|
17
|
+
event_buf = self.buffers.get(node, [])
|
|
18
|
+
|
|
19
|
+
if limit and len(event_buf) > limit:
|
|
20
|
+
to_return = event_buf[:limit]
|
|
21
|
+
self.buffers[node] = event_buf[limit:]
|
|
22
|
+
else:
|
|
23
|
+
to_return = event_buf.copy()
|
|
24
|
+
self.buffers[node] = []
|
|
25
|
+
|
|
26
|
+
return to_return
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
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
|
|
|
7
|
+
from koi_net.protocol.model_map import API_MODEL_MAP
|
|
8
|
+
|
|
6
9
|
from ..identity import NodeIdentity
|
|
7
10
|
from ..protocol.api_models import (
|
|
8
11
|
RidsPayload,
|
|
@@ -27,14 +30,9 @@ from ..protocol.consts import (
|
|
|
27
30
|
)
|
|
28
31
|
from ..protocol.node import NodeProfile, NodeType
|
|
29
32
|
from ..secure import Secure
|
|
30
|
-
from
|
|
31
|
-
|
|
32
|
-
from typing import TYPE_CHECKING
|
|
33
|
-
if TYPE_CHECKING:
|
|
34
|
-
from .error_handler import ErrorHandler
|
|
35
|
-
|
|
33
|
+
from .error_handler import ErrorHandler
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
log = structlog.stdlib.get_logger()
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
# Custom error types for request handling
|
|
@@ -57,51 +55,50 @@ class UnknownPathError(Exception):
|
|
|
57
55
|
class RequestHandler:
|
|
58
56
|
"""Handles making requests to other KOI nodes."""
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
cache: Cache
|
|
61
59
|
identity: NodeIdentity
|
|
62
60
|
secure: Secure
|
|
63
|
-
error_handler:
|
|
61
|
+
error_handler: ErrorHandler
|
|
64
62
|
|
|
65
63
|
def __init__(
|
|
66
64
|
self,
|
|
67
|
-
|
|
65
|
+
cache: Cache,
|
|
68
66
|
identity: NodeIdentity,
|
|
69
|
-
secure: Secure
|
|
67
|
+
secure: Secure,
|
|
68
|
+
error_handler: ErrorHandler
|
|
70
69
|
):
|
|
71
|
-
self.
|
|
70
|
+
self.cache = cache
|
|
72
71
|
self.identity = identity
|
|
73
72
|
self.secure = secure
|
|
74
|
-
|
|
75
|
-
def set_error_handler(self, error_handler: "ErrorHandler"):
|
|
76
73
|
self.error_handler = error_handler
|
|
77
74
|
|
|
78
75
|
def get_url(self, node_rid: KoiNetNode) -> str:
|
|
79
|
-
"""Retrieves URL of a node."""
|
|
76
|
+
"""Retrieves URL of a node from its RID."""
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
log.debug(f"Getting URL for {node_rid!r}")
|
|
82
79
|
node_url = None
|
|
83
80
|
|
|
84
81
|
if node_rid == self.identity.rid:
|
|
85
82
|
raise SelfRequestError("Don't talk to yourself")
|
|
86
83
|
|
|
87
|
-
node_bundle = self.
|
|
84
|
+
node_bundle = self.cache.read(node_rid)
|
|
88
85
|
|
|
89
86
|
if node_bundle:
|
|
90
87
|
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
91
|
-
|
|
88
|
+
log.debug(f"Found node profile: {node_profile}")
|
|
92
89
|
if node_profile.node_type != NodeType.FULL:
|
|
93
90
|
raise PartialNodeQueryError("Can't query partial node")
|
|
94
91
|
node_url = node_profile.base_url
|
|
95
92
|
|
|
96
93
|
else:
|
|
97
94
|
if node_rid == self.identity.config.koi_net.first_contact.rid:
|
|
98
|
-
|
|
95
|
+
log.debug("Found URL of first contact")
|
|
99
96
|
node_url = self.identity.config.koi_net.first_contact.url
|
|
100
97
|
|
|
101
98
|
if not node_url:
|
|
102
99
|
raise NodeNotFoundError("Node not found")
|
|
103
100
|
|
|
104
|
-
|
|
101
|
+
log.debug(f"Resolved {node_rid!r} to {node_url}")
|
|
105
102
|
return node_url
|
|
106
103
|
|
|
107
104
|
def make_request(
|
|
@@ -110,8 +107,9 @@ class RequestHandler:
|
|
|
110
107
|
path: str,
|
|
111
108
|
request: RequestModels,
|
|
112
109
|
) -> ResponseModels | None:
|
|
110
|
+
"""Makes a request to a node."""
|
|
113
111
|
url = self.get_url(node) + path
|
|
114
|
-
|
|
112
|
+
log.info(f"Making request to {url}")
|
|
115
113
|
|
|
116
114
|
signed_envelope = self.secure.create_envelope(
|
|
117
115
|
payload=request,
|
|
@@ -119,9 +117,12 @@ class RequestHandler:
|
|
|
119
117
|
)
|
|
120
118
|
|
|
121
119
|
try:
|
|
122
|
-
result = httpx.post(
|
|
120
|
+
result = httpx.post(
|
|
121
|
+
url,
|
|
122
|
+
data=signed_envelope.model_dump_json(exclude_none=True)
|
|
123
|
+
)
|
|
123
124
|
except httpx.ConnectError as err:
|
|
124
|
-
|
|
125
|
+
log.debug("Failed to connect")
|
|
125
126
|
self.error_handler.handle_connection_error(node)
|
|
126
127
|
raise err
|
|
127
128
|
|
|
@@ -130,20 +131,11 @@ class RequestHandler:
|
|
|
130
131
|
self.error_handler.handle_protocol_error(resp.error, node)
|
|
131
132
|
return resp
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
EnvelopeModel = SignedEnvelope[EventsPayload]
|
|
137
|
-
elif path == FETCH_RIDS_PATH:
|
|
138
|
-
EnvelopeModel = SignedEnvelope[RidsPayload]
|
|
139
|
-
elif path == FETCH_MANIFESTS_PATH:
|
|
140
|
-
EnvelopeModel = SignedEnvelope[ManifestsPayload]
|
|
141
|
-
elif path == FETCH_BUNDLES_PATH:
|
|
142
|
-
EnvelopeModel = SignedEnvelope[BundlesPayload]
|
|
143
|
-
else:
|
|
144
|
-
raise UnknownPathError(f"Unknown path '{path}'")
|
|
134
|
+
resp_env_model = API_MODEL_MAP[path].response_envelope
|
|
135
|
+
if resp_env_model is None:
|
|
136
|
+
return
|
|
145
137
|
|
|
146
|
-
resp_envelope =
|
|
138
|
+
resp_envelope = resp_env_model.model_validate_json(result.text)
|
|
147
139
|
self.secure.validate_envelope(resp_envelope)
|
|
148
140
|
|
|
149
141
|
return resp_envelope.payload
|
|
@@ -154,10 +146,13 @@ class RequestHandler:
|
|
|
154
146
|
req: EventsPayload | None = None,
|
|
155
147
|
**kwargs
|
|
156
148
|
) -> None:
|
|
157
|
-
"""
|
|
149
|
+
"""Broadcasts events to a node.
|
|
150
|
+
|
|
151
|
+
Pass `EventsPayload` object, or see `protocol.api_models.EventsPayload` for available kwargs.
|
|
152
|
+
"""
|
|
158
153
|
request = req or EventsPayload.model_validate(kwargs)
|
|
159
154
|
self.make_request(node, BROADCAST_EVENTS_PATH, request)
|
|
160
|
-
|
|
155
|
+
log.info(f"Broadcasted {len(request.events)} event(s) to {node!r}")
|
|
161
156
|
|
|
162
157
|
def poll_events(
|
|
163
158
|
self,
|
|
@@ -165,11 +160,14 @@ class RequestHandler:
|
|
|
165
160
|
req: PollEvents | None = None,
|
|
166
161
|
**kwargs
|
|
167
162
|
) -> EventsPayload:
|
|
168
|
-
"""
|
|
163
|
+
"""Polls events from a node.
|
|
164
|
+
|
|
165
|
+
Pass `PollEvents` object as `req` or fields as kwargs.
|
|
166
|
+
"""
|
|
169
167
|
request = req or PollEvents.model_validate(kwargs)
|
|
170
168
|
resp = self.make_request(node, POLL_EVENTS_PATH, request)
|
|
171
169
|
if type(resp) != ErrorResponse:
|
|
172
|
-
|
|
170
|
+
log.info(f"Polled {len(resp.events)} events from {node!r}")
|
|
173
171
|
return resp
|
|
174
172
|
|
|
175
173
|
def fetch_rids(
|
|
@@ -178,11 +176,14 @@ class RequestHandler:
|
|
|
178
176
|
req: FetchRids | None = None,
|
|
179
177
|
**kwargs
|
|
180
178
|
) -> RidsPayload:
|
|
181
|
-
"""
|
|
179
|
+
"""Fetches RIDs from a node.
|
|
180
|
+
|
|
181
|
+
Pass `FetchRids` object as `req` or fields as kwargs.
|
|
182
|
+
"""
|
|
182
183
|
request = req or FetchRids.model_validate(kwargs)
|
|
183
184
|
resp = self.make_request(node, FETCH_RIDS_PATH, request)
|
|
184
185
|
if type(resp) != ErrorResponse:
|
|
185
|
-
|
|
186
|
+
log.info(f"Fetched {len(resp.rids)} RID(s) from {node!r}")
|
|
186
187
|
return resp
|
|
187
188
|
|
|
188
189
|
def fetch_manifests(
|
|
@@ -191,11 +192,14 @@ class RequestHandler:
|
|
|
191
192
|
req: FetchManifests | None = None,
|
|
192
193
|
**kwargs
|
|
193
194
|
) -> ManifestsPayload:
|
|
194
|
-
"""
|
|
195
|
+
"""Fetches manifests from a node.
|
|
196
|
+
|
|
197
|
+
Pass `FetchManifests` object as `req` or fields as kwargs.
|
|
198
|
+
"""
|
|
195
199
|
request = req or FetchManifests.model_validate(kwargs)
|
|
196
200
|
resp = self.make_request(node, FETCH_MANIFESTS_PATH, request)
|
|
197
201
|
if type(resp) != ErrorResponse:
|
|
198
|
-
|
|
202
|
+
log.info(f"Fetched {len(resp.manifests)} manifest(s) from {node!r}")
|
|
199
203
|
return resp
|
|
200
204
|
|
|
201
205
|
def fetch_bundles(
|
|
@@ -204,9 +208,12 @@ class RequestHandler:
|
|
|
204
208
|
req: FetchBundles | None = None,
|
|
205
209
|
**kwargs
|
|
206
210
|
) -> BundlesPayload:
|
|
207
|
-
"""
|
|
211
|
+
"""Fetches bundles from a node.
|
|
212
|
+
|
|
213
|
+
Pass `FetchBundles` object as `req` or fields as kwargs.
|
|
214
|
+
"""
|
|
208
215
|
request = req or FetchBundles.model_validate(kwargs)
|
|
209
216
|
resp = self.make_request(node, FETCH_BUNDLES_PATH, request)
|
|
210
217
|
if type(resp) != ErrorResponse:
|
|
211
|
-
|
|
218
|
+
log.info(f"Fetched {len(resp.bundles)} bundle(s) from {node!r}")
|
|
212
219
|
return resp
|