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.

Files changed (48) hide show
  1. koi_net/__init__.py +2 -1
  2. koi_net/assembler.py +82 -0
  3. koi_net/cli/__init__.py +1 -0
  4. koi_net/cli/commands.py +99 -0
  5. koi_net/cli/models.py +41 -0
  6. koi_net/config.py +34 -0
  7. koi_net/context.py +11 -28
  8. koi_net/core.py +63 -179
  9. koi_net/default_actions.py +10 -1
  10. koi_net/effector.py +61 -34
  11. koi_net/handshaker.py +39 -0
  12. koi_net/identity.py +2 -3
  13. koi_net/interfaces/entrypoint.py +5 -0
  14. koi_net/interfaces/worker.py +17 -0
  15. koi_net/lifecycle.py +85 -48
  16. koi_net/logger.py +176 -0
  17. koi_net/network/error_handler.py +18 -16
  18. koi_net/network/event_queue.py +17 -185
  19. koi_net/network/graph.py +15 -10
  20. koi_net/network/poll_event_buffer.py +26 -0
  21. koi_net/network/request_handler.py +54 -47
  22. koi_net/network/resolver.py +18 -21
  23. koi_net/network/response_handler.py +79 -15
  24. koi_net/poller.py +18 -9
  25. koi_net/processor/event_worker.py +117 -0
  26. koi_net/processor/handler.py +4 -2
  27. koi_net/processor/{default_handlers.py → handlers.py} +109 -59
  28. koi_net/processor/knowledge_object.py +19 -7
  29. koi_net/processor/kobj_queue.py +51 -0
  30. koi_net/processor/kobj_worker.py +44 -0
  31. koi_net/processor/{knowledge_pipeline.py → pipeline.py} +31 -53
  32. koi_net/protocol/api_models.py +7 -3
  33. koi_net/protocol/envelope.py +5 -6
  34. koi_net/protocol/model_map.py +61 -0
  35. koi_net/protocol/node.py +3 -3
  36. koi_net/protocol/secure.py +8 -8
  37. koi_net/secure.py +33 -13
  38. koi_net/sentry.py +13 -0
  39. koi_net/server.py +44 -78
  40. koi_net/utils.py +18 -0
  41. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/METADATA +8 -3
  42. koi_net-1.2.0b2.dist-info/RECORD +52 -0
  43. koi_net-1.2.0b2.dist-info/entry_points.txt +2 -0
  44. koi_net/actor.py +0 -60
  45. koi_net/processor/interface.py +0 -101
  46. koi_net-1.1.0b8.dist-info/RECORD +0 -38
  47. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/WHEEL +0 -0
  48. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
- """Provides implementations of default knowledge handlers."""
1
+ """Implementation of default knowledge handlers."""
2
2
 
3
- import logging
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, generate_edge_bundle
12
+ from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
13
13
  from ..protocol.node import NodeProfile
14
14
 
15
- logger = logging.getLogger(__name__)
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 RID is known to this node.
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
- logger.debug("Don't let anyone else tell me who I am!")
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
- """Default manifest handler.
39
+ """Decider based on incoming manifest and cache state.
38
40
 
39
- Blocks manifests with the same hash, or aren't newer than the cached version. Sets the normalized event type to `NEW` or `UPDATE` depending on whether the RID was previously known to this node.
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
- logger.debug("Hash of incoming manifest is same as existing knowledge, ignoring")
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
- logger.debug("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
52
+ log.debug("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
49
53
  return STOP_CHAIN
50
54
 
51
- logger.debug("RID previously known to me, labeling as 'UPDATE'")
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
- logger.debug("RID previously unknown to me, labeling as 'NEW'")
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
- logger.warning(f"Public key hash mismatch for {node_rid!r}!")
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 node can provide (or KOI nodes/edges). Validates the edge type is allowed for the node type (partial nodes cannot use webhooks). If edge is invalid, a `FORGET` event is sent to the other node.
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
- logger.debug("Handling edge negotiation")
108
+ log.debug("Handling edge negotiation")
97
109
 
98
110
  peer_rid = edge_profile.target
99
- peer_bundle = ctx.effector.deref(peer_rid)
111
+ peer_bundle = ctx.cache.read(peer_rid)
100
112
 
101
113
  if not peer_bundle:
102
- logger.warning(f"Peer {peer_rid!r} unknown to me")
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
- logger.debug("Partial nodes cannot use webhooks")
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
- logger.debug("Requested RID types not provided by this node")
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
- logger.debug("Approving proposed edge")
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.handle(bundle=updated_bundle, event_type=EventType.UPDATE)
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
- logger.debug("Edge approved by other node!")
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 coordinator_contact(ctx: HandlerContext, kobj: KnowledgeObject):
147
- node_profile = kobj.bundle.validate_contents(NodeProfile)
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
- # prevents coordinators from attempting to form a self loop
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
- if ctx.graph.get_edge(
184
+ edge_rid = ctx.graph.get_edge(
159
185
  source=kobj.rid,
160
186
  target=ctx.identity.rid,
161
- ) is not None:
162
- return
163
-
164
- logger.info("Identified a coordinator!")
165
- logger.info("Proposing new edge")
187
+ )
166
188
 
167
- if ctx.identity.profile.node_type == NodeType.FULL:
168
- edge_type = EdgeType.WEBHOOK
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
- edge_type = EdgeType.POLL
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
- ctx.handle(bundle=generate_edge_bundle(
174
- source=kobj.rid,
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
- logger.info("Catching up on network state")
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=[KoiNetNode]
225
+ rid_types=available_rid_types
185
226
  )
186
227
  for rid in payload.rids:
187
228
  if rid == ctx.identity.rid:
188
- logger.info("Skipping myself")
229
+ log.info("Skipping myself")
189
230
  continue
190
231
  if ctx.cache.exists(rid):
191
- logger.info(f"Skipping known RID {rid!r}")
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.handle(rid=rid, source=kobj.rid)
197
- logger.info("Done")
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
- """Default network handler.
243
+ """Adds subscriber nodes to network targetes.
203
244
 
204
- Allows broadcasting of all RID types this node is an event provider for (set in node profile), and other nodes have subscribed to. All nodes will also broadcast about their own (internally sourced) KOI node, and KOI edges that they are part of, regardless of their node profile configuration. Finally, nodes will also broadcast about edges to the other node involved (regardless of if they are subscribed)."""
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
- logger.debug(f"Adding edge target '{edge_profile.target!r}' to network targets")
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
- logger.debug(f"Adding edge source '{edge_profile.source!r}' to network targets")
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
- logger.debug(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
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
- logger.debug("Identified edge with forgotten node")
249
- ctx.handle(rid=edge_rid, event_type=EventType.FORGET)
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
- Capable of representing an RID, manifest, bundle, or event. Contains three additional fields use for decision making in the knowledge processing pipeline.
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 source indicates whether this object was generated by this node, or sourced from another node in the network.
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 normalized event type indicates how the knowledge object is viewed from the perspective of this node, and what cache actions will take place. `NEW`, `UPDATE` -> cache write, `FORGET` -> cache delete, `None` -> no cache action.
17
-
18
- The network targets indicate other nodes in the network this knowledge object will be sent to. The event sent to them will be constructed from this knowledge object's RID, manifest, contents, and normalized event type.
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()