koi-net 1.0.0b9__tar.gz → 1.0.0b10__tar.gz
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-1.0.0b9 → koi_net-1.0.0b10}/PKG-INFO +33 -2
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/README.md +32 -1
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/pyproject.toml +1 -1
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/core.py +5 -5
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/network/graph.py +4 -4
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/network/interface.py +17 -17
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/network/request_handler.py +27 -19
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/processor/default_handlers.py +14 -14
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/processor/interface.py +46 -29
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/processor/knowledge_object.py +1 -1
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/event.py +3 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/.gitignore +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/LICENSE +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/examples/basic_coordinator_node.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/examples/basic_partial_node.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/examples/full_node_template.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/examples/partial_node_template.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/requirements.txt +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/__init__.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/identity.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/network/__init__.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/network/response_handler.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/processor/__init__.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/processor/handler.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/__init__.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/api_models.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/consts.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/edge.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/helpers.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/src/koi_net/protocol/node.py +0 -0
- {koi_net-1.0.0b9 → koi_net-1.0.0b10}/test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: koi-net
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b10
|
|
4
4
|
Summary: Implementation of KOI-net protocol in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/BlockScience/koi-net/
|
|
6
6
|
Author-email: Luke Miller <luke@block.science>
|
|
@@ -44,6 +44,37 @@ Description-Content-Type: text/markdown
|
|
|
44
44
|
|
|
45
45
|
*This specification is the result of several iterations of KOI research, [read more here](https://github.com/BlockScience/koi).*
|
|
46
46
|
|
|
47
|
+
### Jump to Sections:
|
|
48
|
+
- [Protocol](#protocol)
|
|
49
|
+
- [Introduction](#introduction)
|
|
50
|
+
- [Communication Methods](#communication-methods)
|
|
51
|
+
- [Quickstart](#quickstart)
|
|
52
|
+
- [Setup](#setup)
|
|
53
|
+
- [Creating a Node](#creating-a-node)
|
|
54
|
+
- [Knowledge Processing](#knowledge-processing)
|
|
55
|
+
- [Try It Out!](#try-it-out)
|
|
56
|
+
- [Advanced](#advanced)
|
|
57
|
+
- [Knowledge Processing Pipeline](#knowledge-processing-pipeline)
|
|
58
|
+
- [Knowledge Handlers](#knowledge-handlers)
|
|
59
|
+
- [RID Handler](#rid-handler)
|
|
60
|
+
- [Manifest Handler](#manifest-handler)
|
|
61
|
+
- [Bundle Handler](#bundle-handler)
|
|
62
|
+
- [Network Handler](#network-handler)
|
|
63
|
+
- [Final Handler](#final-handler)
|
|
64
|
+
- [Registering Handlers](#registering-handlers)
|
|
65
|
+
- [Default Behavior](#default-behavior)
|
|
66
|
+
- [Implementation Reference](#implementation-reference)
|
|
67
|
+
- [Node Interface](#node-interface)
|
|
68
|
+
- [Node Identity](#node-identity)
|
|
69
|
+
- [Network Interface](#network-interface)
|
|
70
|
+
- [Network Graph](#network-graph)
|
|
71
|
+
- [Request Handler](#request-handler)
|
|
72
|
+
- [Response Handler](#response-handler)
|
|
73
|
+
- [Processor Interface](#processor-interface)
|
|
74
|
+
- [Development](#development)
|
|
75
|
+
- [Setup](#setup-1)
|
|
76
|
+
- [Distribution](#distribution)
|
|
77
|
+
|
|
47
78
|
# Protocol
|
|
48
79
|
## Introduction
|
|
49
80
|
|
|
@@ -653,5 +684,5 @@ python -m build
|
|
|
653
684
|
```
|
|
654
685
|
Push new package build to PyPI:
|
|
655
686
|
```shell
|
|
656
|
-
python -m twine upload dist/*
|
|
687
|
+
python -m twine upload --skip-existing dist/*
|
|
657
688
|
```
|
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
*This specification is the result of several iterations of KOI research, [read more here](https://github.com/BlockScience/koi).*
|
|
4
4
|
|
|
5
|
+
### Jump to Sections:
|
|
6
|
+
- [Protocol](#protocol)
|
|
7
|
+
- [Introduction](#introduction)
|
|
8
|
+
- [Communication Methods](#communication-methods)
|
|
9
|
+
- [Quickstart](#quickstart)
|
|
10
|
+
- [Setup](#setup)
|
|
11
|
+
- [Creating a Node](#creating-a-node)
|
|
12
|
+
- [Knowledge Processing](#knowledge-processing)
|
|
13
|
+
- [Try It Out!](#try-it-out)
|
|
14
|
+
- [Advanced](#advanced)
|
|
15
|
+
- [Knowledge Processing Pipeline](#knowledge-processing-pipeline)
|
|
16
|
+
- [Knowledge Handlers](#knowledge-handlers)
|
|
17
|
+
- [RID Handler](#rid-handler)
|
|
18
|
+
- [Manifest Handler](#manifest-handler)
|
|
19
|
+
- [Bundle Handler](#bundle-handler)
|
|
20
|
+
- [Network Handler](#network-handler)
|
|
21
|
+
- [Final Handler](#final-handler)
|
|
22
|
+
- [Registering Handlers](#registering-handlers)
|
|
23
|
+
- [Default Behavior](#default-behavior)
|
|
24
|
+
- [Implementation Reference](#implementation-reference)
|
|
25
|
+
- [Node Interface](#node-interface)
|
|
26
|
+
- [Node Identity](#node-identity)
|
|
27
|
+
- [Network Interface](#network-interface)
|
|
28
|
+
- [Network Graph](#network-graph)
|
|
29
|
+
- [Request Handler](#request-handler)
|
|
30
|
+
- [Response Handler](#response-handler)
|
|
31
|
+
- [Processor Interface](#processor-interface)
|
|
32
|
+
- [Development](#development)
|
|
33
|
+
- [Setup](#setup-1)
|
|
34
|
+
- [Distribution](#distribution)
|
|
35
|
+
|
|
5
36
|
# Protocol
|
|
6
37
|
## Introduction
|
|
7
38
|
|
|
@@ -611,5 +642,5 @@ python -m build
|
|
|
611
642
|
```
|
|
612
643
|
Push new package build to PyPI:
|
|
613
644
|
```shell
|
|
614
|
-
python -m twine upload dist/*
|
|
645
|
+
python -m twine upload --skip-existing dist/*
|
|
615
646
|
```
|
|
@@ -84,15 +84,15 @@ class NodeInterface:
|
|
|
84
84
|
)
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
-
logger.
|
|
87
|
+
logger.debug("Waiting for kobj queue to empty")
|
|
88
88
|
if self.use_kobj_processor_thread:
|
|
89
89
|
self.processor.kobj_queue.join()
|
|
90
90
|
else:
|
|
91
91
|
self.processor.flush_kobj_queue()
|
|
92
|
-
logger.
|
|
92
|
+
logger.debug("Done")
|
|
93
93
|
|
|
94
94
|
if not self.network.graph.get_neighbors() and self.first_contact:
|
|
95
|
-
logger.
|
|
95
|
+
logger.debug(f"I don't have any neighbors, reaching out to first contact {self.first_contact}")
|
|
96
96
|
|
|
97
97
|
events = [
|
|
98
98
|
Event.from_rid(EventType.FORGET, self.identity.rid),
|
|
@@ -106,7 +106,7 @@ class NodeInterface:
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
except httpx.ConnectError:
|
|
109
|
-
logger.
|
|
109
|
+
logger.warning("Failed to reach first contact")
|
|
110
110
|
return
|
|
111
111
|
|
|
112
112
|
|
|
@@ -118,7 +118,7 @@ class NodeInterface:
|
|
|
118
118
|
logger.info("Stopping node...")
|
|
119
119
|
|
|
120
120
|
if self.use_kobj_processor_thread:
|
|
121
|
-
logger.info("Waiting for kobj queue to empty")
|
|
121
|
+
logger.info(f"Waiting for kobj queue to empty ({self.processor.kobj_queue.unfinished_tasks} tasks remaining)")
|
|
122
122
|
self.processor.kobj_queue.join()
|
|
123
123
|
else:
|
|
124
124
|
self.processor.flush_kobj_queue()
|
|
@@ -25,12 +25,12 @@ class NetworkGraph:
|
|
|
25
25
|
|
|
26
26
|
def generate(self):
|
|
27
27
|
"""Generates directed graph from cached KOI nodes and edges."""
|
|
28
|
-
logger.
|
|
28
|
+
logger.debug("Generating network graph")
|
|
29
29
|
self.dg.clear()
|
|
30
30
|
for rid in self.cache.list_rids():
|
|
31
31
|
if type(rid) == KoiNetNode:
|
|
32
32
|
self.dg.add_node(rid)
|
|
33
|
-
logger.
|
|
33
|
+
logger.debug(f"Added node {rid}")
|
|
34
34
|
|
|
35
35
|
elif type(rid) == KoiNetEdge:
|
|
36
36
|
edge_profile = self.get_edge_profile(rid)
|
|
@@ -38,8 +38,8 @@ class NetworkGraph:
|
|
|
38
38
|
logger.warning(f"Failed to load {rid!r}")
|
|
39
39
|
continue
|
|
40
40
|
self.dg.add_edge(edge_profile.source, edge_profile.target, rid=rid)
|
|
41
|
-
logger.
|
|
42
|
-
logger.
|
|
41
|
+
logger.debug(f"Added edge {rid} ({edge_profile.source} -> {edge_profile.target})")
|
|
42
|
+
logger.debug("Done")
|
|
43
43
|
|
|
44
44
|
def get_node_profile(self, rid: KoiNetNode) -> NodeProfile | None:
|
|
45
45
|
"""Returns node profile given its RID."""
|
|
@@ -100,7 +100,7 @@ class NetworkInterface:
|
|
|
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.
|
|
103
|
+
logger.debug(f"Pushing event {event.event_type} {event.rid} to {node}")
|
|
104
104
|
|
|
105
105
|
node_profile = self.graph.get_node_profile(node)
|
|
106
106
|
if not node_profile:
|
|
@@ -136,14 +136,14 @@ class NetworkInterface:
|
|
|
136
136
|
if queue:
|
|
137
137
|
while not queue.empty():
|
|
138
138
|
event = queue.get()
|
|
139
|
-
logger.
|
|
139
|
+
logger.debug(f"Dequeued {event.event_type} '{event.rid}'")
|
|
140
140
|
events.append(event)
|
|
141
141
|
|
|
142
142
|
return events
|
|
143
143
|
|
|
144
144
|
def flush_poll_queue(self, node: KoiNetNode) -> list[Event]:
|
|
145
145
|
"""Flushes a node's poll queue, returning list of events."""
|
|
146
|
-
logger.
|
|
146
|
+
logger.debug(f"Flushing poll queue for {node}")
|
|
147
147
|
return self._flush_queue(self.poll_event_queue, node)
|
|
148
148
|
|
|
149
149
|
def flush_webhook_queue(self, node: KoiNetNode):
|
|
@@ -152,7 +152,7 @@ class NetworkInterface:
|
|
|
152
152
|
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.
|
|
153
153
|
"""
|
|
154
154
|
|
|
155
|
-
logger.
|
|
155
|
+
logger.debug(f"Flushing webhook queue for {node}")
|
|
156
156
|
|
|
157
157
|
node_profile = self.graph.get_node_profile(node)
|
|
158
158
|
|
|
@@ -167,7 +167,7 @@ class NetworkInterface:
|
|
|
167
167
|
events = self._flush_queue(self.webhook_event_queue, node)
|
|
168
168
|
if not events: return
|
|
169
169
|
|
|
170
|
-
logger.
|
|
170
|
+
logger.debug(f"Broadcasting {len(events)} events")
|
|
171
171
|
|
|
172
172
|
try:
|
|
173
173
|
self.request_handler.broadcast_events(node, events=events)
|
|
@@ -179,23 +179,23 @@ class NetworkInterface:
|
|
|
179
179
|
def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
|
|
180
180
|
"""Returns list of node RIDs which provide state for the specified RID type."""
|
|
181
181
|
|
|
182
|
-
logger.
|
|
182
|
+
logger.debug(f"Looking for state providers of '{rid_type}'")
|
|
183
183
|
provider_nodes = []
|
|
184
184
|
for node_rid in self.cache.list_rids(rid_types=[KoiNetNode]):
|
|
185
185
|
node = self.graph.get_node_profile(node_rid)
|
|
186
186
|
|
|
187
187
|
if node.node_type == NodeType.FULL and rid_type in node.provides.state:
|
|
188
|
-
logger.
|
|
188
|
+
logger.debug(f"Found provider '{node_rid}'")
|
|
189
189
|
provider_nodes.append(node_rid)
|
|
190
190
|
|
|
191
191
|
if not provider_nodes:
|
|
192
|
-
logger.
|
|
192
|
+
logger.debug("Failed to find providers")
|
|
193
193
|
return provider_nodes
|
|
194
194
|
|
|
195
195
|
def fetch_remote_bundle(self, rid: RID):
|
|
196
196
|
"""Attempts to fetch a bundle by RID from known peer nodes."""
|
|
197
197
|
|
|
198
|
-
logger.
|
|
198
|
+
logger.debug(f"Fetching remote bundle '{rid}'")
|
|
199
199
|
remote_bundle = None
|
|
200
200
|
for node_rid in self.get_state_providers(type(rid)):
|
|
201
201
|
payload = self.request_handler.fetch_bundles(
|
|
@@ -203,7 +203,7 @@ class NetworkInterface:
|
|
|
203
203
|
|
|
204
204
|
if payload.bundles:
|
|
205
205
|
remote_bundle = payload.bundles[0]
|
|
206
|
-
logger.
|
|
206
|
+
logger.debug(f"Got bundle from '{node_rid}'")
|
|
207
207
|
break
|
|
208
208
|
|
|
209
209
|
if not remote_bundle:
|
|
@@ -214,7 +214,7 @@ class NetworkInterface:
|
|
|
214
214
|
def fetch_remote_manifest(self, rid: RID):
|
|
215
215
|
"""Attempts to fetch a manifest by RID from known peer nodes."""
|
|
216
216
|
|
|
217
|
-
logger.
|
|
217
|
+
logger.debug(f"Fetching remote manifest '{rid}'")
|
|
218
218
|
remote_manifest = None
|
|
219
219
|
for node_rid in self.get_state_providers(type(rid)):
|
|
220
220
|
payload = self.request_handler.fetch_manifests(
|
|
@@ -222,7 +222,7 @@ class NetworkInterface:
|
|
|
222
222
|
|
|
223
223
|
if payload.manifests:
|
|
224
224
|
remote_manifest = payload.manifests[0]
|
|
225
|
-
logger.
|
|
225
|
+
logger.debug(f"Got bundle from '{node_rid}'")
|
|
226
226
|
break
|
|
227
227
|
|
|
228
228
|
if not remote_manifest:
|
|
@@ -239,17 +239,17 @@ class NetworkInterface:
|
|
|
239
239
|
neighbors = self.graph.get_neighbors()
|
|
240
240
|
|
|
241
241
|
if not neighbors and self.first_contact:
|
|
242
|
-
logger.
|
|
242
|
+
logger.debug("No neighbors found, polling first contact")
|
|
243
243
|
try:
|
|
244
244
|
payload = self.request_handler.poll_events(
|
|
245
245
|
url=self.first_contact,
|
|
246
246
|
rid=self.identity.rid
|
|
247
247
|
)
|
|
248
248
|
if payload.events:
|
|
249
|
-
logger.
|
|
249
|
+
logger.debug(f"Received {len(payload.events)} events from '{self.first_contact}'")
|
|
250
250
|
return payload.events
|
|
251
251
|
except httpx.ConnectError:
|
|
252
|
-
logger.
|
|
252
|
+
logger.debug(f"Failed to reach first contact '{self.first_contact}'")
|
|
253
253
|
|
|
254
254
|
events = []
|
|
255
255
|
for node_rid in neighbors:
|
|
@@ -263,10 +263,10 @@ class NetworkInterface:
|
|
|
263
263
|
rid=self.identity.rid
|
|
264
264
|
)
|
|
265
265
|
if payload.events:
|
|
266
|
-
logger.
|
|
266
|
+
logger.debug(f"Received {len(payload.events)} events from {node_rid!r}")
|
|
267
267
|
events.extend(payload.events)
|
|
268
268
|
except httpx.ConnectError:
|
|
269
|
-
logger.
|
|
269
|
+
logger.debug(f"Failed to reach node '{node_rid}'")
|
|
270
270
|
continue
|
|
271
271
|
|
|
272
272
|
return events
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import httpx
|
|
3
|
-
from pydantic import BaseModel
|
|
4
3
|
from rid_lib import RID
|
|
5
4
|
from rid_lib.ext import Cache
|
|
6
5
|
from rid_lib.types.koi_net_node import KoiNetNode
|
|
@@ -46,7 +45,7 @@ class RequestHandler:
|
|
|
46
45
|
request: RequestModels,
|
|
47
46
|
response_model: type[ResponseModels] | None = None
|
|
48
47
|
) -> ResponseModels | None:
|
|
49
|
-
logger.
|
|
48
|
+
logger.debug(f"Making request to {url}")
|
|
50
49
|
resp = httpx.post(
|
|
51
50
|
url=url,
|
|
52
51
|
data=request.model_dump_json()
|
|
@@ -66,7 +65,7 @@ class RequestHandler:
|
|
|
66
65
|
raise Exception("Node not found")
|
|
67
66
|
if node_profile.node_type != NodeType.FULL:
|
|
68
67
|
raise Exception("Can't query partial node")
|
|
69
|
-
logger.
|
|
68
|
+
logger.debug(f"Resolved {node_rid!r} to {node_profile.base_url}")
|
|
70
69
|
return node_profile.base_url
|
|
71
70
|
else:
|
|
72
71
|
return url
|
|
@@ -79,10 +78,11 @@ class RequestHandler:
|
|
|
79
78
|
**kwargs
|
|
80
79
|
) -> None:
|
|
81
80
|
"""See protocol.api_models.EventsPayload for available kwargs."""
|
|
81
|
+
request = req or EventsPayload.model_validate(kwargs)
|
|
82
82
|
self.make_request(
|
|
83
|
-
self.get_url(node, url) + BROADCAST_EVENTS_PATH,
|
|
84
|
-
req or EventsPayload.model_validate(kwargs)
|
|
83
|
+
self.get_url(node, url) + BROADCAST_EVENTS_PATH, request
|
|
85
84
|
)
|
|
85
|
+
logger.info(f"Broadcasted {len(request.events)} event(s) to {node or url!r}")
|
|
86
86
|
|
|
87
87
|
def poll_events(
|
|
88
88
|
self,
|
|
@@ -92,12 +92,14 @@ class RequestHandler:
|
|
|
92
92
|
**kwargs
|
|
93
93
|
) -> EventsPayload:
|
|
94
94
|
"""See protocol.api_models.PollEvents for available kwargs."""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
request = req or PollEvents.model_validate(kwargs)
|
|
96
|
+
resp = self.make_request(
|
|
97
|
+
self.get_url(node, url) + POLL_EVENTS_PATH, request,
|
|
98
98
|
response_model=EventsPayload
|
|
99
99
|
)
|
|
100
|
-
|
|
100
|
+
logger.info(f"Polled {len(resp.events)} events from {node or url!r}")
|
|
101
|
+
return resp
|
|
102
|
+
|
|
101
103
|
def fetch_rids(
|
|
102
104
|
self,
|
|
103
105
|
node: RID = None,
|
|
@@ -106,11 +108,13 @@ class RequestHandler:
|
|
|
106
108
|
**kwargs
|
|
107
109
|
) -> RidsPayload:
|
|
108
110
|
"""See protocol.api_models.FetchRids for available kwargs."""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
request = req or FetchRids.model_validate(kwargs)
|
|
112
|
+
resp = self.make_request(
|
|
113
|
+
self.get_url(node, url) + FETCH_RIDS_PATH, request,
|
|
112
114
|
response_model=RidsPayload
|
|
113
115
|
)
|
|
116
|
+
logger.info(f"Fetched {len(resp.rids)} RID(s) from {node or url!r}")
|
|
117
|
+
return resp
|
|
114
118
|
|
|
115
119
|
def fetch_manifests(
|
|
116
120
|
self,
|
|
@@ -120,11 +124,13 @@ class RequestHandler:
|
|
|
120
124
|
**kwargs
|
|
121
125
|
) -> ManifestsPayload:
|
|
122
126
|
"""See protocol.api_models.FetchManifests for available kwargs."""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
request = req or FetchManifests.model_validate(kwargs)
|
|
128
|
+
resp = self.make_request(
|
|
129
|
+
self.get_url(node, url) + FETCH_MANIFESTS_PATH, request,
|
|
126
130
|
response_model=ManifestsPayload
|
|
127
131
|
)
|
|
132
|
+
logger.info(f"Fetched {len(resp.manifests)} manifest(s) from {node or url!r}")
|
|
133
|
+
return resp
|
|
128
134
|
|
|
129
135
|
def fetch_bundles(
|
|
130
136
|
self,
|
|
@@ -134,8 +140,10 @@ class RequestHandler:
|
|
|
134
140
|
**kwargs
|
|
135
141
|
) -> BundlesPayload:
|
|
136
142
|
"""See protocol.api_models.FetchBundles for available kwargs."""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
request = req or FetchBundles.model_validate(kwargs)
|
|
144
|
+
resp = self.make_request(
|
|
145
|
+
self.get_url(node, url) + FETCH_BUNDLES_PATH, request,
|
|
140
146
|
response_model=BundlesPayload
|
|
141
|
-
)
|
|
147
|
+
)
|
|
148
|
+
logger.info(f"Fetched {len(resp.bundles)} bundle(s) from {node or url!r}")
|
|
149
|
+
return resp
|
|
@@ -22,7 +22,7 @@ def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
|
22
22
|
"""
|
|
23
23
|
if (kobj.rid == processor.identity.rid and
|
|
24
24
|
kobj.source == KnowledgeSource.External):
|
|
25
|
-
logger.
|
|
25
|
+
logger.debug("Don't let anyone else tell me who I am!")
|
|
26
26
|
return STOP_CHAIN
|
|
27
27
|
|
|
28
28
|
if kobj.event_type == EventType.FORGET:
|
|
@@ -41,17 +41,17 @@ def basic_manifest_handler(processor: ProcessorInterface, kobj: KnowledgeObject)
|
|
|
41
41
|
|
|
42
42
|
if prev_bundle:
|
|
43
43
|
if kobj.manifest.sha256_hash == prev_bundle.manifest.sha256_hash:
|
|
44
|
-
logger.
|
|
44
|
+
logger.debug("Hash of incoming manifest is same as existing knowledge, ignoring")
|
|
45
45
|
return STOP_CHAIN
|
|
46
46
|
if kobj.manifest.timestamp <= prev_bundle.manifest.timestamp:
|
|
47
|
-
logger.
|
|
47
|
+
logger.debug("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
|
|
48
48
|
return STOP_CHAIN
|
|
49
49
|
|
|
50
|
-
logger.
|
|
50
|
+
logger.debug("RID previously known to me, labeling as 'UPDATE'")
|
|
51
51
|
kobj.normalized_event_type = EventType.UPDATE
|
|
52
52
|
|
|
53
53
|
else:
|
|
54
|
-
logger.
|
|
54
|
+
logger.debug("RID previously unknown to me, labeling as 'NEW'")
|
|
55
55
|
kobj.normalized_event_type = EventType.NEW
|
|
56
56
|
|
|
57
57
|
return kobj
|
|
@@ -77,7 +77,7 @@ def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObjec
|
|
|
77
77
|
if edge_profile.status != EdgeStatus.PROPOSED:
|
|
78
78
|
return
|
|
79
79
|
|
|
80
|
-
logger.
|
|
80
|
+
logger.debug("Handling edge negotiation")
|
|
81
81
|
|
|
82
82
|
peer_rid = edge_profile.target
|
|
83
83
|
peer_profile = processor.network.graph.get_node_profile(peer_rid)
|
|
@@ -96,11 +96,11 @@ def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObjec
|
|
|
96
96
|
abort = False
|
|
97
97
|
if (edge_profile.edge_type == EdgeType.WEBHOOK and
|
|
98
98
|
peer_profile.node_type == NodeType.PARTIAL):
|
|
99
|
-
logger.
|
|
99
|
+
logger.debug("Partial nodes cannot use webhooks")
|
|
100
100
|
abort = True
|
|
101
101
|
|
|
102
102
|
if not set(edge_profile.rid_types).issubset(provided_events):
|
|
103
|
-
logger.
|
|
103
|
+
logger.debug("Requested RID types not provided by this node")
|
|
104
104
|
abort = True
|
|
105
105
|
|
|
106
106
|
if abort:
|
|
@@ -110,16 +110,16 @@ def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObjec
|
|
|
110
110
|
|
|
111
111
|
else:
|
|
112
112
|
# approve edge profile
|
|
113
|
-
logger.
|
|
113
|
+
logger.debug("Approving proposed edge")
|
|
114
114
|
edge_profile.status = EdgeStatus.APPROVED
|
|
115
115
|
updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
|
|
116
116
|
|
|
117
|
-
processor.handle(bundle=updated_bundle)
|
|
117
|
+
processor.handle(bundle=updated_bundle, event_type=EventType.UPDATE)
|
|
118
118
|
return
|
|
119
119
|
|
|
120
120
|
elif edge_profile.target == processor.identity.rid:
|
|
121
121
|
if edge_profile.status == EdgeStatus.APPROVED:
|
|
122
|
-
logger.
|
|
122
|
+
logger.debug("Edge approved by other node!")
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
# Network handlers
|
|
@@ -140,12 +140,12 @@ def basic_network_output_filter(processor: ProcessorInterface, kobj: KnowledgeOb
|
|
|
140
140
|
edge_profile = kobj.bundle.validate_contents(EdgeProfile)
|
|
141
141
|
|
|
142
142
|
if edge_profile.source == processor.identity.rid:
|
|
143
|
-
logger.
|
|
143
|
+
logger.debug(f"Adding edge target '{edge_profile.target!r}' to network targets")
|
|
144
144
|
kobj.network_targets.update([edge_profile.target])
|
|
145
145
|
involves_me = True
|
|
146
146
|
|
|
147
147
|
elif edge_profile.target == processor.identity.rid:
|
|
148
|
-
logger.
|
|
148
|
+
logger.debug(f"Adding edge source '{edge_profile.source!r}' to network targets")
|
|
149
149
|
kobj.network_targets.update([edge_profile.source])
|
|
150
150
|
involves_me = True
|
|
151
151
|
|
|
@@ -156,7 +156,7 @@ def basic_network_output_filter(processor: ProcessorInterface, kobj: KnowledgeOb
|
|
|
156
156
|
allowed_type=type(kobj.rid)
|
|
157
157
|
)
|
|
158
158
|
|
|
159
|
-
logger.
|
|
159
|
+
logger.debug(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
|
|
160
160
|
kobj.network_targets.update(subscribers)
|
|
161
161
|
|
|
162
162
|
return kobj
|
|
@@ -101,12 +101,12 @@ class ProcessorInterface:
|
|
|
101
101
|
if handler.event_types and kobj.event_type not in handler.event_types:
|
|
102
102
|
continue
|
|
103
103
|
|
|
104
|
-
logger.
|
|
104
|
+
logger.debug(f"Calling {handler_type} handler '{handler.func.__name__}'")
|
|
105
105
|
resp = handler.func(self, kobj.model_copy())
|
|
106
106
|
|
|
107
107
|
# stops handler chain execution
|
|
108
108
|
if resp is STOP_CHAIN:
|
|
109
|
-
logger.
|
|
109
|
+
logger.debug(f"Handler chain stopped by {handler.func.__name__}")
|
|
110
110
|
return STOP_CHAIN
|
|
111
111
|
# kobj unmodified
|
|
112
112
|
elif resp is None:
|
|
@@ -114,7 +114,7 @@ class ProcessorInterface:
|
|
|
114
114
|
# kobj modified by handler
|
|
115
115
|
elif isinstance(resp, KnowledgeObject):
|
|
116
116
|
kobj = resp
|
|
117
|
-
logger.
|
|
117
|
+
logger.debug(f"Knowledge object modified by {handler.func.__name__}")
|
|
118
118
|
else:
|
|
119
119
|
raise ValueError(f"Handler {handler.func.__name__} returned invalid response '{resp}'")
|
|
120
120
|
|
|
@@ -134,34 +134,41 @@ class ProcessorInterface:
|
|
|
134
134
|
The pipeline may be stopped by any point by a single handler returning the `STOP_CHAIN` sentinel. In that case, the process will exit immediately. Further handlers of that type and later handler chains will not be called.
|
|
135
135
|
"""
|
|
136
136
|
|
|
137
|
-
logger.
|
|
137
|
+
logger.debug(f"Handling {kobj!r}")
|
|
138
138
|
kobj = self.call_handler_chain(HandlerType.RID, kobj)
|
|
139
139
|
if kobj is STOP_CHAIN: return
|
|
140
140
|
|
|
141
141
|
if kobj.event_type == EventType.FORGET:
|
|
142
142
|
bundle = self.cache.read(kobj.rid)
|
|
143
143
|
if not bundle:
|
|
144
|
-
logger.
|
|
144
|
+
logger.debug("Local bundle not found")
|
|
145
145
|
return
|
|
146
146
|
|
|
147
147
|
# the bundle (to be deleted) attached to kobj for downstream analysis
|
|
148
|
-
logger.
|
|
148
|
+
logger.debug("Adding local bundle (to be deleted) to knowledge object")
|
|
149
149
|
kobj.manifest = bundle.manifest
|
|
150
150
|
kobj.contents = bundle.contents
|
|
151
151
|
|
|
152
152
|
else:
|
|
153
153
|
# attempt to retrieve manifest
|
|
154
154
|
if not kobj.manifest:
|
|
155
|
+
logger.debug("Manifest not found")
|
|
155
156
|
if kobj.source == KnowledgeSource.External:
|
|
156
|
-
logger.
|
|
157
|
+
logger.debug("Attempting to fetch remote manifest")
|
|
157
158
|
manifest = self.network.fetch_remote_manifest(kobj.rid)
|
|
158
|
-
if not manifest: return
|
|
159
159
|
|
|
160
160
|
elif kobj.source == KnowledgeSource.Internal:
|
|
161
|
-
logger.
|
|
161
|
+
logger.debug("Attempting to read manifest from cache")
|
|
162
162
|
bundle = self.cache.read(kobj.rid)
|
|
163
|
-
if
|
|
164
|
-
|
|
163
|
+
if bundle:
|
|
164
|
+
manifest = bundle.manifest
|
|
165
|
+
else:
|
|
166
|
+
manifest = None
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
if not manifest:
|
|
170
|
+
logger.debug("Failed to find manifest")
|
|
171
|
+
return
|
|
165
172
|
|
|
166
173
|
kobj.manifest = manifest
|
|
167
174
|
|
|
@@ -170,19 +177,22 @@ class ProcessorInterface:
|
|
|
170
177
|
|
|
171
178
|
# attempt to retrieve bundle
|
|
172
179
|
if not kobj.bundle:
|
|
180
|
+
logger.debug("Bundle not found")
|
|
173
181
|
if kobj.source == KnowledgeSource.External:
|
|
174
|
-
logger.
|
|
182
|
+
logger.debug("Attempting to fetch remote bundle")
|
|
175
183
|
bundle = self.network.fetch_remote_bundle(kobj.rid)
|
|
176
|
-
# TODO: WARNING MANIFEST MAY BE DIFFERENT
|
|
177
184
|
|
|
178
185
|
elif kobj.source == KnowledgeSource.Internal:
|
|
179
|
-
logger.
|
|
186
|
+
logger.debug("Attempting to read bundle from cache")
|
|
180
187
|
bundle = self.cache.read(kobj.rid)
|
|
181
188
|
|
|
182
189
|
if kobj.manifest != bundle.manifest:
|
|
183
190
|
logger.warning("Retrieved bundle contains a different manifest")
|
|
184
191
|
|
|
185
|
-
if not bundle:
|
|
192
|
+
if not bundle:
|
|
193
|
+
logger.debug("Failed to find bundle")
|
|
194
|
+
return
|
|
195
|
+
|
|
186
196
|
kobj.manifest = bundle.manifest
|
|
187
197
|
kobj.contents = bundle.contents
|
|
188
198
|
|
|
@@ -190,28 +200,28 @@ class ProcessorInterface:
|
|
|
190
200
|
if kobj is STOP_CHAIN: return
|
|
191
201
|
|
|
192
202
|
if kobj.normalized_event_type in (EventType.UPDATE, EventType.NEW):
|
|
193
|
-
logger.info(f"Writing {kobj!r}
|
|
203
|
+
logger.info(f"Writing to cache: {kobj!r}")
|
|
194
204
|
self.cache.write(kobj.bundle)
|
|
195
205
|
|
|
196
206
|
elif kobj.normalized_event_type == EventType.FORGET:
|
|
197
|
-
logger.info(f"Deleting {kobj!r}
|
|
207
|
+
logger.info(f"Deleting from cache: {kobj!r}")
|
|
198
208
|
self.cache.delete(kobj.rid)
|
|
199
209
|
|
|
200
210
|
else:
|
|
201
|
-
logger.
|
|
211
|
+
logger.debug("Normalized event type was never set, no cache or network operations will occur")
|
|
202
212
|
return
|
|
203
213
|
|
|
204
214
|
if type(kobj.rid) in (KoiNetNode, KoiNetEdge):
|
|
205
|
-
logger.
|
|
215
|
+
logger.debug("Change to node or edge, regenerating network graph")
|
|
206
216
|
self.network.graph.generate()
|
|
207
217
|
|
|
208
218
|
kobj = self.call_handler_chain(HandlerType.Network, kobj)
|
|
209
219
|
if kobj is STOP_CHAIN: return
|
|
210
220
|
|
|
211
221
|
if kobj.network_targets:
|
|
212
|
-
logger.
|
|
222
|
+
logger.debug(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
|
|
213
223
|
else:
|
|
214
|
-
logger.
|
|
224
|
+
logger.debug("No network targets set")
|
|
215
225
|
|
|
216
226
|
for node in kobj.network_targets:
|
|
217
227
|
self.network.push_event_to(kobj.normalized_event, node, flush=True)
|
|
@@ -228,18 +238,25 @@ class ProcessorInterface:
|
|
|
228
238
|
|
|
229
239
|
while not self.kobj_queue.empty():
|
|
230
240
|
kobj = self.kobj_queue.get()
|
|
231
|
-
logger.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
logger.debug(f"Dequeued {kobj!r}")
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
self.process_kobj(kobj)
|
|
245
|
+
finally:
|
|
246
|
+
self.kobj_queue.task_done()
|
|
247
|
+
logger.debug("Done")
|
|
235
248
|
|
|
236
249
|
def kobj_processor_worker(self, timeout=0.1):
|
|
237
250
|
while True:
|
|
238
251
|
try:
|
|
239
252
|
kobj = self.kobj_queue.get(timeout=timeout)
|
|
240
|
-
logger.
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
logger.debug(f"Dequeued {kobj!r}")
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
self.process_kobj(kobj)
|
|
257
|
+
finally:
|
|
258
|
+
self.kobj_queue.task_done()
|
|
259
|
+
logger.debug("Done")
|
|
243
260
|
|
|
244
261
|
except queue.Empty:
|
|
245
262
|
pass
|
|
@@ -275,4 +292,4 @@ class ProcessorInterface:
|
|
|
275
292
|
raise ValueError("One of 'rid', 'manifest', 'bundle', 'event', or 'kobj' must be provided")
|
|
276
293
|
|
|
277
294
|
self.kobj_queue.put(_kobj)
|
|
278
|
-
logger.
|
|
295
|
+
logger.debug(f"Queued {_kobj!r}")
|
|
@@ -35,7 +35,7 @@ class KnowledgeObject(BaseModel):
|
|
|
35
35
|
network_targets: set[KoiNetNode] = set()
|
|
36
36
|
|
|
37
37
|
def __repr__(self):
|
|
38
|
-
return f"<
|
|
38
|
+
return f"<KObj '{self.rid}' event type: '{self.event_type}' -> '{self.normalized_event_type}', source: '{self.source}'>"
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
41
|
def from_rid(
|
|
@@ -15,6 +15,9 @@ class Event(BaseModel):
|
|
|
15
15
|
manifest: Manifest | None = None
|
|
16
16
|
contents: dict | None = None
|
|
17
17
|
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f"<Event '{self.rid}' event type: '{self.event_type}'>"
|
|
20
|
+
|
|
18
21
|
@classmethod
|
|
19
22
|
def from_bundle(cls, event_type: EventType, bundle: Bundle):
|
|
20
23
|
return cls(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|