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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Implementation of default knowledge handlers."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import structlog
|
|
4
4
|
from rid_lib.ext import Bundle
|
|
5
5
|
from rid_lib.ext.utils import sha256_hash
|
|
6
6
|
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
@@ -9,10 +9,11 @@ from .handler import KnowledgeHandler, HandlerType, STOP_CHAIN
|
|
|
9
9
|
from .knowledge_object import KnowledgeObject
|
|
10
10
|
from ..context import HandlerContext
|
|
11
11
|
from ..protocol.event import Event, EventType
|
|
12
|
-
from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
|
|
12
|
+
from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
|
|
13
13
|
from ..protocol.node import NodeProfile
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
log = structlog.stdlib.get_logger()
|
|
16
|
+
|
|
16
17
|
|
|
17
18
|
# RID handlers
|
|
18
19
|
|
|
@@ -20,10 +21,11 @@ logger = logging.getLogger(__name__)
|
|
|
20
21
|
def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
21
22
|
"""Default RID handler.
|
|
22
23
|
|
|
23
|
-
Blocks external events about this node. Allows `FORGET` events if
|
|
24
|
+
Blocks external events about this node. Allows `FORGET` events if
|
|
25
|
+
RID is known to this node.
|
|
24
26
|
"""
|
|
25
27
|
if (kobj.rid == ctx.identity.rid and kobj.source):
|
|
26
|
-
|
|
28
|
+
log.debug("Don't let anyone else tell me who I am!")
|
|
27
29
|
return STOP_CHAIN
|
|
28
30
|
|
|
29
31
|
if kobj.event_type == EventType.FORGET:
|
|
@@ -34,25 +36,27 @@ def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
34
36
|
|
|
35
37
|
@KnowledgeHandler.create(HandlerType.Manifest)
|
|
36
38
|
def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
37
|
-
"""
|
|
39
|
+
"""Decider based on incoming manifest and cache state.
|
|
38
40
|
|
|
39
|
-
Blocks manifests
|
|
41
|
+
Blocks manifests which have the same hash, or aren't newer than the
|
|
42
|
+
cached version. Sets the normalized event type to `NEW` or `UPDATE`
|
|
43
|
+
depending on whether the RID was previously known.
|
|
40
44
|
"""
|
|
41
45
|
prev_bundle = ctx.cache.read(kobj.rid)
|
|
42
46
|
|
|
43
47
|
if prev_bundle:
|
|
44
48
|
if kobj.manifest.sha256_hash == prev_bundle.manifest.sha256_hash:
|
|
45
|
-
|
|
49
|
+
log.debug("Hash of incoming manifest is same as existing knowledge, ignoring")
|
|
46
50
|
return STOP_CHAIN
|
|
47
51
|
if kobj.manifest.timestamp <= prev_bundle.manifest.timestamp:
|
|
48
|
-
|
|
52
|
+
log.debug("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
|
|
49
53
|
return STOP_CHAIN
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
log.debug("RID previously known to me, labeling as 'UPDATE'")
|
|
52
56
|
kobj.normalized_event_type = EventType.UPDATE
|
|
53
57
|
|
|
54
58
|
else:
|
|
55
|
-
|
|
59
|
+
log.debug("RID previously unknown to me, labeling as 'NEW'")
|
|
56
60
|
kobj.normalized_event_type = EventType.NEW
|
|
57
61
|
|
|
58
62
|
return kobj
|
|
@@ -63,14 +67,19 @@ def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
63
67
|
@KnowledgeHandler.create(
|
|
64
68
|
handler_type=HandlerType.Bundle,
|
|
65
69
|
rid_types=[KoiNetNode],
|
|
66
|
-
event_types=[EventType.NEW, EventType.UPDATE]
|
|
67
|
-
)
|
|
70
|
+
event_types=[EventType.NEW, EventType.UPDATE])
|
|
68
71
|
def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
72
|
+
"""Maintains security of cached node profiles.
|
|
73
|
+
|
|
74
|
+
Blocks bundles with a mismatching public keys in their node profile
|
|
75
|
+
and RID from continuing through the pipeline.
|
|
76
|
+
"""
|
|
77
|
+
|
|
69
78
|
node_profile = kobj.bundle.validate_contents(NodeProfile)
|
|
70
79
|
node_rid: KoiNetNode = kobj.rid
|
|
71
80
|
|
|
72
81
|
if sha256_hash(node_profile.public_key) != node_rid.hash:
|
|
73
|
-
|
|
82
|
+
log.warning(f"Public key hash mismatch for {node_rid!r}!")
|
|
74
83
|
return STOP_CHAIN
|
|
75
84
|
|
|
76
85
|
@KnowledgeHandler.create(
|
|
@@ -80,7 +89,10 @@ def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
80
89
|
def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
81
90
|
"""Handles basic edge negotiation process.
|
|
82
91
|
|
|
83
|
-
Automatically approves proposed edges if they request RID types this
|
|
92
|
+
Automatically approves proposed edges if they request RID types this
|
|
93
|
+
node can provide (or KOI nodes/edges). Validates the edge type is
|
|
94
|
+
allowed for the node type (partial nodes cannot use webhooks). If
|
|
95
|
+
edge is invalid, a `FORGET` event is sent to the other node.
|
|
84
96
|
"""
|
|
85
97
|
|
|
86
98
|
# only respond when source is another node
|
|
@@ -93,13 +105,13 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
93
105
|
if edge_profile.status != EdgeStatus.PROPOSED:
|
|
94
106
|
return
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
log.debug("Handling edge negotiation")
|
|
97
109
|
|
|
98
110
|
peer_rid = edge_profile.target
|
|
99
|
-
peer_bundle = ctx.
|
|
111
|
+
peer_bundle = ctx.cache.read(peer_rid)
|
|
100
112
|
|
|
101
113
|
if not peer_bundle:
|
|
102
|
-
|
|
114
|
+
log.warning(f"Peer {peer_rid!r} unknown to me")
|
|
103
115
|
return STOP_CHAIN
|
|
104
116
|
|
|
105
117
|
peer_profile = peer_bundle.validate_contents(NodeProfile)
|
|
@@ -114,11 +126,11 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
114
126
|
abort = False
|
|
115
127
|
if (edge_profile.edge_type == EdgeType.WEBHOOK and
|
|
116
128
|
peer_profile.node_type == NodeType.PARTIAL):
|
|
117
|
-
|
|
129
|
+
log.debug("Partial nodes cannot use webhooks")
|
|
118
130
|
abort = True
|
|
119
131
|
|
|
120
132
|
if not set(edge_profile.rid_types).issubset(provided_events):
|
|
121
|
-
|
|
133
|
+
log.debug("Requested RID types not provided by this node")
|
|
122
134
|
abort = True
|
|
123
135
|
|
|
124
136
|
if abort:
|
|
@@ -128,80 +140,116 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
128
140
|
|
|
129
141
|
else:
|
|
130
142
|
# approve edge profile
|
|
131
|
-
|
|
143
|
+
log.debug("Approving proposed edge")
|
|
132
144
|
edge_profile.status = EdgeStatus.APPROVED
|
|
133
145
|
updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
|
|
134
146
|
|
|
135
|
-
ctx.
|
|
147
|
+
ctx.kobj_queue.put_kobj(bundle=updated_bundle, event_type=EventType.UPDATE)
|
|
136
148
|
return
|
|
137
149
|
|
|
138
150
|
elif edge_profile.target == ctx.identity.rid:
|
|
139
151
|
if edge_profile.status == EdgeStatus.APPROVED:
|
|
140
|
-
|
|
152
|
+
log.debug("Edge approved by other node!")
|
|
141
153
|
|
|
142
154
|
|
|
143
155
|
# Network handlers
|
|
144
156
|
|
|
145
157
|
@KnowledgeHandler.create(HandlerType.Network, rid_types=[KoiNetNode])
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# looking for event provider of nodes
|
|
150
|
-
if KoiNetNode not in node_profile.provides.event:
|
|
151
|
-
return
|
|
158
|
+
def node_contact_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
159
|
+
"""Makes contact with providers of RID types of interest.
|
|
152
160
|
|
|
153
|
-
|
|
161
|
+
When an incoming node knowledge object is identified as a provider
|
|
162
|
+
of an RID type of interest, this handler will propose a new edge
|
|
163
|
+
subscribing to future node events, and fetch existing nodes to catch
|
|
164
|
+
up to the current state.
|
|
165
|
+
"""
|
|
166
|
+
# prevents nodes from attempting to form a self loop
|
|
154
167
|
if kobj.rid == ctx.identity.rid:
|
|
155
168
|
return
|
|
156
169
|
|
|
170
|
+
node_profile = kobj.bundle.validate_contents(NodeProfile)
|
|
171
|
+
|
|
172
|
+
available_rid_types = list(
|
|
173
|
+
set(ctx.config.koi_net.rid_types_of_interest) &
|
|
174
|
+
set(node_profile.provides.event)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if not available_rid_types:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
log.info("Identified a coordinator!")
|
|
181
|
+
log.info("Proposing new edge")
|
|
182
|
+
|
|
157
183
|
# already have an edge established
|
|
158
|
-
|
|
184
|
+
edge_rid = ctx.graph.get_edge(
|
|
159
185
|
source=kobj.rid,
|
|
160
186
|
target=ctx.identity.rid,
|
|
161
|
-
)
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
logger.info("Identified a coordinator!")
|
|
165
|
-
logger.info("Proposing new edge")
|
|
187
|
+
)
|
|
166
188
|
|
|
167
|
-
if
|
|
168
|
-
|
|
189
|
+
if edge_rid:
|
|
190
|
+
prev_edge_bundle = ctx.cache.read(edge_rid)
|
|
191
|
+
edge_profile = prev_edge_bundle.validate_contents(EdgeProfile)
|
|
192
|
+
|
|
193
|
+
if set(edge_profile.rid_types) == set(available_rid_types):
|
|
194
|
+
# no change in rid types
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
edge_profile.rid_types = available_rid_types
|
|
198
|
+
edge_profile.status = EdgeStatus.PROPOSED
|
|
199
|
+
|
|
169
200
|
else:
|
|
170
|
-
|
|
201
|
+
source = kobj.rid
|
|
202
|
+
target = ctx.identity.rid
|
|
203
|
+
if ctx.identity.profile.node_type == NodeType.FULL:
|
|
204
|
+
edge_type = EdgeType.WEBHOOK
|
|
205
|
+
else:
|
|
206
|
+
edge_type = EdgeType.POLL
|
|
207
|
+
|
|
208
|
+
edge_rid = KoiNetEdge(sha256_hash(str(source) + str(target)))
|
|
209
|
+
edge_profile = EdgeProfile(
|
|
210
|
+
source=source,
|
|
211
|
+
target=target,
|
|
212
|
+
rid_types=available_rid_types,
|
|
213
|
+
edge_type=edge_type,
|
|
214
|
+
status=EdgeStatus.PROPOSED
|
|
215
|
+
)
|
|
171
216
|
|
|
172
217
|
# queued for processing
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
target=ctx.identity.rid,
|
|
176
|
-
edge_type=edge_type,
|
|
177
|
-
rid_types=[KoiNetNode]
|
|
178
|
-
))
|
|
218
|
+
edge_bundle = Bundle.generate(edge_rid, edge_profile.model_dump())
|
|
219
|
+
ctx.kobj_queue.put_kobj(bundle=edge_bundle)
|
|
179
220
|
|
|
180
|
-
|
|
221
|
+
log.info("Catching up on network state")
|
|
181
222
|
|
|
182
223
|
payload = ctx.request_handler.fetch_rids(
|
|
183
224
|
node=kobj.rid,
|
|
184
|
-
rid_types=
|
|
225
|
+
rid_types=available_rid_types
|
|
185
226
|
)
|
|
186
227
|
for rid in payload.rids:
|
|
187
228
|
if rid == ctx.identity.rid:
|
|
188
|
-
|
|
229
|
+
log.info("Skipping myself")
|
|
189
230
|
continue
|
|
190
231
|
if ctx.cache.exists(rid):
|
|
191
|
-
|
|
232
|
+
log.info(f"Skipping known RID {rid!r}")
|
|
192
233
|
continue
|
|
193
234
|
|
|
194
235
|
# marked as external since we are handling RIDs from another node
|
|
195
236
|
# will fetch remotely instead of checking local cache
|
|
196
|
-
ctx.
|
|
197
|
-
|
|
237
|
+
ctx.kobj_queue.put_kobj(rid=rid, source=kobj.rid)
|
|
238
|
+
log.info("Done")
|
|
198
239
|
|
|
199
240
|
|
|
200
241
|
@KnowledgeHandler.create(HandlerType.Network)
|
|
201
242
|
def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
202
|
-
"""
|
|
243
|
+
"""Adds subscriber nodes to network targetes.
|
|
203
244
|
|
|
204
|
-
Allows broadcasting of all RID types this node is an event provider
|
|
245
|
+
Allows broadcasting of all RID types this node is an event provider
|
|
246
|
+
for (set in node profile), and other nodes have subscribed to. All
|
|
247
|
+
nodes will also broadcast about their own (internally sourced) KOI
|
|
248
|
+
node, and KOI edges that they are part of, regardless of their node
|
|
249
|
+
profile configuration. Finally, nodes will also broadcast about
|
|
250
|
+
edges to the other node involved (regardless of if they are
|
|
251
|
+
subscribed).
|
|
252
|
+
"""
|
|
205
253
|
|
|
206
254
|
involves_me = False
|
|
207
255
|
if kobj.source is None:
|
|
@@ -213,12 +261,12 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
213
261
|
edge_profile = kobj.bundle.validate_contents(EdgeProfile)
|
|
214
262
|
|
|
215
263
|
if edge_profile.source == ctx.identity.rid:
|
|
216
|
-
|
|
264
|
+
log.debug(f"Adding edge target '{edge_profile.target!r}' to network targets")
|
|
217
265
|
kobj.network_targets.update([edge_profile.target])
|
|
218
266
|
involves_me = True
|
|
219
267
|
|
|
220
268
|
elif edge_profile.target == ctx.identity.rid:
|
|
221
|
-
|
|
269
|
+
log.debug(f"Adding edge source '{edge_profile.source!r}' to network targets")
|
|
222
270
|
kobj.network_targets.update([edge_profile.source])
|
|
223
271
|
involves_me = True
|
|
224
272
|
|
|
@@ -229,13 +277,15 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
229
277
|
allowed_type=type(kobj.rid)
|
|
230
278
|
)
|
|
231
279
|
|
|
232
|
-
|
|
280
|
+
log.debug(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
|
|
233
281
|
kobj.network_targets.update(subscribers)
|
|
234
282
|
|
|
235
283
|
return kobj
|
|
236
284
|
|
|
237
285
|
@KnowledgeHandler.create(HandlerType.Final, rid_types=[KoiNetNode])
|
|
238
286
|
def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
287
|
+
"""Removes edges to forgotten nodes."""
|
|
288
|
+
|
|
239
289
|
if kobj.normalized_event_type != EventType.FORGET:
|
|
240
290
|
return
|
|
241
291
|
|
|
@@ -245,5 +295,5 @@ def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
245
295
|
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
246
296
|
|
|
247
297
|
if kobj.rid in (edge_profile.source, edge_profile.target):
|
|
248
|
-
|
|
249
|
-
ctx.
|
|
298
|
+
log.debug("Identified edge with forgotten node")
|
|
299
|
+
ctx.kobj_queue.put_kobj(rid=edge_rid, event_type=EventType.FORGET)
|
|
@@ -9,15 +9,21 @@ from ..protocol.event import Event, EventType
|
|
|
9
9
|
class KnowledgeObject(BaseModel):
|
|
10
10
|
"""A normalized knowledge representation for internal processing.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Represents an RID, manifest, bundle, or event. Contains three fields
|
|
13
|
+
(`normalized_event_type`, `source`, `network_targets`) used for
|
|
14
|
+
decision making in the knowledge processing pipeline. The source
|
|
15
|
+
indicates which node this object originated from, or `None` if it
|
|
16
|
+
was generated by this node.
|
|
13
17
|
|
|
14
|
-
The
|
|
18
|
+
The normalized event type indicates how the knowledge object is
|
|
19
|
+
viewed from the perspective of this node, and what cache actions
|
|
20
|
+
will take place. (`NEW`, `UPDATE`) -> cache write, `FORGET` ->
|
|
21
|
+
cache delete, `None` -> no cache action.
|
|
15
22
|
|
|
16
|
-
The
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Constructors are provided to create a knowledge object from an RID, manifest, bundle, or event.
|
|
23
|
+
The network targets indicate other nodes in the network this
|
|
24
|
+
knowledge object will be sent to. The event sent to them will be
|
|
25
|
+
constructed from this knowledge object's RID, manifest, contents,
|
|
26
|
+
and normalized event type.
|
|
21
27
|
"""
|
|
22
28
|
rid: RID
|
|
23
29
|
manifest: Manifest | None = None
|
|
@@ -37,6 +43,7 @@ class KnowledgeObject(BaseModel):
|
|
|
37
43
|
event_type: EventType | None = None,
|
|
38
44
|
source: KoiNetNode | None = None
|
|
39
45
|
) -> "KnowledgeObject":
|
|
46
|
+
"""Creates a `KnowledgeObject` from an `RID`."""
|
|
40
47
|
return cls(
|
|
41
48
|
rid=rid,
|
|
42
49
|
event_type=event_type,
|
|
@@ -50,6 +57,7 @@ class KnowledgeObject(BaseModel):
|
|
|
50
57
|
event_type: EventType | None = None,
|
|
51
58
|
source: KoiNetNode | None = None
|
|
52
59
|
) -> "KnowledgeObject":
|
|
60
|
+
"""Creates a `KnowledgeObject` from a `Manifest`."""
|
|
53
61
|
return cls(
|
|
54
62
|
rid=manifest.rid,
|
|
55
63
|
manifest=manifest,
|
|
@@ -64,6 +72,7 @@ class KnowledgeObject(BaseModel):
|
|
|
64
72
|
event_type: EventType | None = None,
|
|
65
73
|
source: KoiNetNode | None = None
|
|
66
74
|
) -> "KnowledgeObject":
|
|
75
|
+
"""Creates a `KnowledgeObject` from a `Bundle`."""
|
|
67
76
|
return cls(
|
|
68
77
|
rid=bundle.rid,
|
|
69
78
|
manifest=bundle.manifest,
|
|
@@ -78,6 +87,7 @@ class KnowledgeObject(BaseModel):
|
|
|
78
87
|
event: Event,
|
|
79
88
|
source: KoiNetNode | None = None
|
|
80
89
|
) -> "KnowledgeObject":
|
|
90
|
+
"""Creates a `KnowledgeObject` from an `Event`."""
|
|
81
91
|
return cls(
|
|
82
92
|
rid=event.rid,
|
|
83
93
|
manifest=event.manifest,
|
|
@@ -88,6 +98,7 @@ class KnowledgeObject(BaseModel):
|
|
|
88
98
|
|
|
89
99
|
@property
|
|
90
100
|
def bundle(self):
|
|
101
|
+
"""Bundle representation of knowledge object."""
|
|
91
102
|
if self.manifest is None or self.contents is None:
|
|
92
103
|
return
|
|
93
104
|
|
|
@@ -98,6 +109,7 @@ class KnowledgeObject(BaseModel):
|
|
|
98
109
|
|
|
99
110
|
@property
|
|
100
111
|
def normalized_event(self):
|
|
112
|
+
"""Event representation of knowledge object."""
|
|
101
113
|
if self.normalized_event_type is None:
|
|
102
114
|
raise ValueError("Internal event's normalized event type is None, cannot convert to Event")
|
|
103
115
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from queue import Queue
|
|
3
|
+
from rid_lib.core import RID
|
|
4
|
+
from rid_lib.ext import Bundle, Manifest
|
|
5
|
+
from rid_lib.types import KoiNetNode
|
|
6
|
+
from ..protocol.event import Event, EventType
|
|
7
|
+
from .knowledge_object import KnowledgeObject
|
|
8
|
+
|
|
9
|
+
log = structlog.stdlib.get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KobjQueue:
|
|
13
|
+
"""Provides access to this node's knowledge processing pipeline."""
|
|
14
|
+
q: Queue[KnowledgeObject]
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.q = Queue()
|
|
18
|
+
|
|
19
|
+
def put_kobj(
|
|
20
|
+
self,
|
|
21
|
+
rid: RID | None = None,
|
|
22
|
+
manifest: Manifest | None = None,
|
|
23
|
+
bundle: Bundle | None = None,
|
|
24
|
+
event: Event | None = None,
|
|
25
|
+
kobj: KnowledgeObject | None = None,
|
|
26
|
+
event_type: EventType | None = None,
|
|
27
|
+
source: KoiNetNode | None = None
|
|
28
|
+
):
|
|
29
|
+
"""Queues knowledge object to be handled by processing pipeline.
|
|
30
|
+
|
|
31
|
+
Knowledge may take the form of an RID, manifest, bundle, event,
|
|
32
|
+
or knowledge object (with an optional event type for RID,
|
|
33
|
+
manifest, or bundle objects). All objects will be normalized
|
|
34
|
+
to knowledge objects and queued. If `flush` is `True`, the queue
|
|
35
|
+
will be flushed immediately after adding the new knowledge.
|
|
36
|
+
"""
|
|
37
|
+
if rid:
|
|
38
|
+
_kobj = KnowledgeObject.from_rid(rid, event_type, source)
|
|
39
|
+
elif manifest:
|
|
40
|
+
_kobj = KnowledgeObject.from_manifest(manifest, event_type, source)
|
|
41
|
+
elif bundle:
|
|
42
|
+
_kobj = KnowledgeObject.from_bundle(bundle, event_type, source)
|
|
43
|
+
elif event:
|
|
44
|
+
_kobj = KnowledgeObject.from_event(event, source)
|
|
45
|
+
elif kobj:
|
|
46
|
+
_kobj = kobj
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError("One of 'rid', 'manifest', 'bundle', 'event', or 'kobj' must be provided")
|
|
49
|
+
|
|
50
|
+
self.q.put(_kobj)
|
|
51
|
+
log.debug(f"Queued {_kobj!r}")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
import traceback
|
|
3
|
+
import structlog
|
|
4
|
+
|
|
5
|
+
from .pipeline import KnowledgePipeline
|
|
6
|
+
from .kobj_queue import KobjQueue
|
|
7
|
+
from koi_net.interfaces.worker import ThreadWorker, STOP_WORKER
|
|
8
|
+
|
|
9
|
+
log = structlog.stdlib.get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KnowledgeProcessingWorker(ThreadWorker):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
kobj_queue: KobjQueue,
|
|
16
|
+
pipeline: KnowledgePipeline
|
|
17
|
+
):
|
|
18
|
+
self.kobj_queue = kobj_queue
|
|
19
|
+
self.pipeline = pipeline
|
|
20
|
+
self.timeout: float = 0.1
|
|
21
|
+
|
|
22
|
+
super().__init__()
|
|
23
|
+
|
|
24
|
+
def run(self):
|
|
25
|
+
log.info("Started kobj worker")
|
|
26
|
+
while True:
|
|
27
|
+
try:
|
|
28
|
+
item = self.kobj_queue.q.get(timeout=self.timeout)
|
|
29
|
+
try:
|
|
30
|
+
if item is STOP_WORKER:
|
|
31
|
+
log.info("Received 'STOP_WORKER' signal, shutting down...")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
log.info(f"Dequeued {item!r}")
|
|
35
|
+
|
|
36
|
+
self.pipeline.process(item)
|
|
37
|
+
finally:
|
|
38
|
+
self.kobj_queue.q.task_done()
|
|
39
|
+
|
|
40
|
+
except queue.Empty:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
except Exception as e:
|
|
44
|
+
traceback.print_exc()
|