koi-net 1.1.0b8__py3-none-any.whl → 1.2.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of koi-net might be problematic. Click here for more details.

Files changed (42) hide show
  1. koi_net/__init__.py +1 -1
  2. koi_net/behaviors.py +51 -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 +12 -21
  8. koi_net/core.py +209 -170
  9. koi_net/default_actions.py +10 -1
  10. koi_net/effector.py +39 -24
  11. koi_net/handshaker.py +39 -0
  12. koi_net/kobj_worker.py +45 -0
  13. koi_net/lifecycle.py +67 -38
  14. koi_net/models.py +14 -0
  15. koi_net/network/error_handler.py +12 -10
  16. koi_net/network/event_queue.py +14 -184
  17. koi_net/network/graph.py +7 -2
  18. koi_net/network/request_handler.py +31 -19
  19. koi_net/network/resolver.py +5 -8
  20. koi_net/network/response_handler.py +5 -6
  21. koi_net/poll_event_buffer.py +17 -0
  22. koi_net/poller.py +12 -5
  23. koi_net/processor/default_handlers.py +84 -35
  24. koi_net/processor/event_worker.py +121 -0
  25. koi_net/processor/handler.py +4 -2
  26. koi_net/processor/knowledge_object.py +19 -7
  27. koi_net/processor/knowledge_pipeline.py +7 -26
  28. koi_net/processor/kobj_queue.py +51 -0
  29. koi_net/protocol/api_models.py +3 -2
  30. koi_net/protocol/node.py +3 -3
  31. koi_net/secure.py +28 -8
  32. koi_net/server.py +25 -9
  33. koi_net/utils.py +18 -0
  34. koi_net/worker.py +10 -0
  35. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/METADATA +7 -3
  36. koi_net-1.2.0b1.dist-info/RECORD +49 -0
  37. koi_net-1.2.0b1.dist-info/entry_points.txt +2 -0
  38. koi_net/actor.py +0 -60
  39. koi_net/processor/interface.py +0 -101
  40. koi_net-1.1.0b8.dist-info/RECORD +0 -38
  41. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/WHEEL +0 -0
  42. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -12,7 +12,6 @@ from ..protocol.api_models import (
12
12
  FetchManifests,
13
13
  FetchBundles,
14
14
  )
15
- from ..effector import Effector
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
17
 
@@ -21,30 +20,29 @@ class ResponseHandler:
21
20
  """Handles generating responses to requests from other KOI nodes."""
22
21
 
23
22
  cache: Cache
24
- effector: Effector
25
23
 
26
24
  def __init__(
27
25
  self,
28
26
  cache: Cache,
29
- effector: Effector,
30
27
  ):
31
28
  self.cache = cache
32
- self.effector = effector
33
29
 
34
30
  def fetch_rids(self, req: FetchRids, source: KoiNetNode) -> RidsPayload:
31
+ """Returns response to fetch RIDs request."""
35
32
  logger.info(f"Request to fetch rids, allowed types {req.rid_types}")
36
33
  rids = self.cache.list_rids(req.rid_types)
37
34
 
38
35
  return RidsPayload(rids=rids)
39
36
 
40
37
  def fetch_manifests(self, req: FetchManifests, source: KoiNetNode) -> ManifestsPayload:
38
+ """Returns response to fetch manifests request."""
41
39
  logger.info(f"Request to fetch manifests, allowed types {req.rid_types}, rids {req.rids}")
42
40
 
43
41
  manifests: list[Manifest] = []
44
42
  not_found: list[RID] = []
45
43
 
46
44
  for rid in (req.rids or self.cache.list_rids(req.rid_types)):
47
- bundle = self.effector.deref(rid)
45
+ bundle = self.cache.read(rid)
48
46
  if bundle:
49
47
  manifests.append(bundle.manifest)
50
48
  else:
@@ -53,13 +51,14 @@ class ResponseHandler:
53
51
  return ManifestsPayload(manifests=manifests, not_found=not_found)
54
52
 
55
53
  def fetch_bundles(self, req: FetchBundles, source: KoiNetNode) -> BundlesPayload:
54
+ """Returns response to fetch bundles request."""
56
55
  logger.info(f"Request to fetch bundles, requested rids {req.rids}")
57
56
 
58
57
  bundles: list[Bundle] = []
59
58
  not_found: list[RID] = []
60
59
 
61
60
  for rid in req.rids:
62
- bundle = self.effector.deref(rid)
61
+ bundle = self.cache.read(rid)
63
62
  if bundle:
64
63
  bundles.append(bundle)
65
64
  else:
@@ -0,0 +1,17 @@
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):
17
+ return self.buffers.pop(node, [])
koi_net/poller.py CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  import time
3
3
  import logging
4
- from .processor.interface import ProcessorInterface
4
+ from .processor.kobj_queue import KobjQueue
5
5
  from .lifecycle import NodeLifecycle
6
6
  from .network.resolver import NetworkResolver
7
7
  from .config import NodeConfig
@@ -10,26 +10,33 @@ logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
12
  class NodePoller:
13
+ """Manages polling based event loop for partial nodes."""
14
+ kobj_queue: KobjQueue
15
+ lifecycle: NodeLifecycle
16
+ resolver: NetworkResolver
17
+ config: NodeConfig
18
+
13
19
  def __init__(
14
20
  self,
15
- processor: ProcessorInterface,
21
+ kobj_queue: KobjQueue,
16
22
  lifecycle: NodeLifecycle,
17
23
  resolver: NetworkResolver,
18
24
  config: NodeConfig
19
25
  ):
20
- self.processor = processor
26
+ self.kobj_queue = kobj_queue
21
27
  self.lifecycle = lifecycle
22
28
  self.resolver = resolver
23
29
  self.config = config
24
30
 
25
31
  def poll(self):
32
+ """Polls neighbors and processes returned events."""
26
33
  neighbors = self.resolver.poll_neighbors()
27
34
  for node_rid in neighbors:
28
35
  for event in neighbors[node_rid]:
29
- self.processor.handle(event=event, source=node_rid)
30
- self.processor.flush_kobj_queue()
36
+ self.kobj_queue.put_kobj(event=event, source=node_rid)
31
37
 
32
38
  def run(self):
39
+ """Runs polling event loop."""
33
40
  with self.lifecycle.run():
34
41
  while True:
35
42
  start_time = time.time()
@@ -1,4 +1,4 @@
1
- """Provides implementations of default knowledge handlers."""
1
+ """Implementation of default knowledge handlers."""
2
2
 
3
3
  import logging
4
4
  from rid_lib.ext import Bundle
@@ -20,7 +20,8 @@ logger = logging.getLogger(__name__)
20
20
  def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
21
21
  """Default RID handler.
22
22
 
23
- Blocks external events about this node. Allows `FORGET` events if RID is known to this node.
23
+ Blocks external events about this node. Allows `FORGET` events if
24
+ RID is known to this node.
24
25
  """
25
26
  if (kobj.rid == ctx.identity.rid and kobj.source):
26
27
  logger.debug("Don't let anyone else tell me who I am!")
@@ -34,9 +35,11 @@ def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
34
35
 
35
36
  @KnowledgeHandler.create(HandlerType.Manifest)
36
37
  def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
37
- """Default manifest handler.
38
+ """Decider based on incoming manifest and cache state.
38
39
 
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.
40
+ Blocks manifests which have the same hash, or aren't newer than the
41
+ cached version. Sets the normalized event type to `NEW` or `UPDATE`
42
+ depending on whether the RID was previously known.
40
43
  """
41
44
  prev_bundle = ctx.cache.read(kobj.rid)
42
45
 
@@ -63,9 +66,14 @@ def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
63
66
  @KnowledgeHandler.create(
64
67
  handler_type=HandlerType.Bundle,
65
68
  rid_types=[KoiNetNode],
66
- event_types=[EventType.NEW, EventType.UPDATE]
67
- )
69
+ event_types=[EventType.NEW, EventType.UPDATE])
68
70
  def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
71
+ """Maintains security of cached node profiles.
72
+
73
+ Blocks bundles with a mismatching public keys in their node profile
74
+ and RID from continuing through the pipeline.
75
+ """
76
+
69
77
  node_profile = kobj.bundle.validate_contents(NodeProfile)
70
78
  node_rid: KoiNetNode = kobj.rid
71
79
 
@@ -80,7 +88,10 @@ def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
80
88
  def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
81
89
  """Handles basic edge negotiation process.
82
90
 
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.
91
+ Automatically approves proposed edges if they request RID types this
92
+ node can provide (or KOI nodes/edges). Validates the edge type is
93
+ allowed for the node type (partial nodes cannot use webhooks). If
94
+ edge is invalid, a `FORGET` event is sent to the other node.
84
95
  """
85
96
 
86
97
  # only respond when source is another node
@@ -96,7 +107,7 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
96
107
  logger.debug("Handling edge negotiation")
97
108
 
98
109
  peer_rid = edge_profile.target
99
- peer_bundle = ctx.effector.deref(peer_rid)
110
+ peer_bundle = ctx.cache.read(peer_rid)
100
111
 
101
112
  if not peer_bundle:
102
113
  logger.warning(f"Peer {peer_rid!r} unknown to me")
@@ -132,7 +143,7 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
132
143
  edge_profile.status = EdgeStatus.APPROVED
133
144
  updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
134
145
 
135
- ctx.handle(bundle=updated_bundle, event_type=EventType.UPDATE)
146
+ ctx.kobj_queue.put_kobj(bundle=updated_bundle, event_type=EventType.UPDATE)
136
147
  return
137
148
 
138
149
  elif edge_profile.target == ctx.identity.rid:
@@ -143,45 +154,74 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
143
154
  # Network handlers
144
155
 
145
156
  @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
157
+ def node_contact_handler(ctx: HandlerContext, kobj: KnowledgeObject):
158
+ """Makes contact with providers of RID types of interest.
152
159
 
153
- # prevents coordinators from attempting to form a self loop
160
+ When an incoming node knowledge object is identified as a provider
161
+ of an RID type of interest, this handler will propose a new edge
162
+ subscribing to future node events, and fetch existing nodes to catch
163
+ up to the current state.
164
+ """
165
+ # prevents nodes from attempting to form a self loop
154
166
  if kobj.rid == ctx.identity.rid:
155
167
  return
156
168
 
157
- # already have an edge established
158
- if ctx.graph.get_edge(
159
- source=kobj.rid,
160
- target=ctx.identity.rid,
161
- ) is not None:
169
+ node_profile = kobj.bundle.validate_contents(NodeProfile)
170
+
171
+ available_rid_types = list(
172
+ set(ctx.config.koi_net.rid_types_of_interest) &
173
+ set(node_profile.provides.event)
174
+ )
175
+
176
+ if not available_rid_types:
162
177
  return
163
178
 
164
179
  logger.info("Identified a coordinator!")
165
180
  logger.info("Proposing new edge")
166
181
 
167
- if ctx.identity.profile.node_type == NodeType.FULL:
168
- edge_type = EdgeType.WEBHOOK
182
+ # already have an edge established
183
+ edge_rid = ctx.graph.get_edge(
184
+ source=kobj.rid,
185
+ target=ctx.identity.rid,
186
+ )
187
+
188
+ if edge_rid:
189
+ prev_edge_bundle = ctx.cache.read(edge_rid)
190
+ edge_profile = prev_edge_bundle.validate_contents(EdgeProfile)
191
+
192
+ if set(edge_profile.rid_types) == set(available_rid_types):
193
+ # no change in rid types
194
+ return
195
+
196
+ edge_profile.rid_types = available_rid_types
197
+ edge_profile.status = EdgeStatus.PROPOSED
198
+
169
199
  else:
170
- edge_type = EdgeType.POLL
200
+ source = kobj.rid
201
+ target = ctx.identity.rid
202
+ if ctx.identity.profile.node_type == NodeType.FULL:
203
+ edge_type = EdgeType.WEBHOOK
204
+ else:
205
+ edge_type = EdgeType.POLL
206
+
207
+ edge_rid = KoiNetEdge(sha256_hash(str(source) + str(target)))
208
+ edge_profile = EdgeProfile(
209
+ source=source,
210
+ target=target,
211
+ rid_types=available_rid_types,
212
+ edge_type=edge_type,
213
+ status=EdgeStatus.PROPOSED
214
+ )
171
215
 
172
216
  # 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
- ))
217
+ edge_bundle = Bundle.generate(edge_rid, edge_profile.model_dump())
218
+ ctx.kobj_queue.put_kobj(bundle=edge_bundle)
179
219
 
180
220
  logger.info("Catching up on network state")
181
221
 
182
222
  payload = ctx.request_handler.fetch_rids(
183
223
  node=kobj.rid,
184
- rid_types=[KoiNetNode]
224
+ rid_types=available_rid_types
185
225
  )
186
226
  for rid in payload.rids:
187
227
  if rid == ctx.identity.rid:
@@ -193,15 +233,22 @@ def coordinator_contact(ctx: HandlerContext, kobj: KnowledgeObject):
193
233
 
194
234
  # marked as external since we are handling RIDs from another node
195
235
  # will fetch remotely instead of checking local cache
196
- ctx.handle(rid=rid, source=kobj.rid)
236
+ ctx.kobj_queue.put_kobj(rid=rid, source=kobj.rid)
197
237
  logger.info("Done")
198
238
 
199
239
 
200
240
  @KnowledgeHandler.create(HandlerType.Network)
201
241
  def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
202
- """Default network handler.
242
+ """Adds subscriber nodes to network targetes.
203
243
 
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)."""
244
+ Allows broadcasting of all RID types this node is an event provider
245
+ for (set in node profile), and other nodes have subscribed to. All
246
+ nodes will also broadcast about their own (internally sourced) KOI
247
+ node, and KOI edges that they are part of, regardless of their node
248
+ profile configuration. Finally, nodes will also broadcast about
249
+ edges to the other node involved (regardless of if they are
250
+ subscribed).
251
+ """
205
252
 
206
253
  involves_me = False
207
254
  if kobj.source is None:
@@ -236,6 +283,8 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
236
283
 
237
284
  @KnowledgeHandler.create(HandlerType.Final, rid_types=[KoiNetNode])
238
285
  def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
286
+ """Removes edges to forgotten nodes."""
287
+
239
288
  if kobj.normalized_event_type != EventType.FORGET:
240
289
  return
241
290
 
@@ -246,4 +295,4 @@ def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
246
295
 
247
296
  if kobj.rid in (edge_profile.source, edge_profile.target):
248
297
  logger.debug("Identified edge with forgotten node")
249
- ctx.handle(rid=edge_rid, event_type=EventType.FORGET)
298
+ ctx.kobj_queue.put_kobj(rid=edge_rid, event_type=EventType.FORGET)
@@ -0,0 +1,121 @@
1
+ import queue
2
+ import traceback
3
+ import time
4
+ import logging
5
+
6
+ from rid_lib.ext import Cache
7
+ from rid_lib.types import KoiNetNode
8
+
9
+ from koi_net.config import NodeConfig
10
+ from koi_net.models import END, QueuedEvent
11
+ from koi_net.network.event_queue import EventQueue
12
+ from koi_net.network.request_handler import RequestHandler
13
+ from koi_net.poll_event_buffer import PollEventBuffer
14
+ from koi_net.protocol.event import Event
15
+ from koi_net.protocol.node import NodeProfile, NodeType
16
+ from koi_net.worker import ThreadWorker
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class EventProcessingWorker(ThreadWorker):
22
+ event_buffer: dict[KoiNetNode, list[Event]]
23
+ buffer_times: dict[KoiNetNode, float]
24
+
25
+ def __init__(
26
+ self,
27
+ event_queue: EventQueue,
28
+ request_handler: RequestHandler,
29
+ config: NodeConfig,
30
+ cache: Cache,
31
+ poll_event_buf: PollEventBuffer,
32
+ queue_timeout: float = 0.1,
33
+ max_buf_len: int = 5,
34
+ max_wait_time: float = 1.0
35
+ ):
36
+ self.event_queue = event_queue
37
+ self.request_handler = request_handler
38
+
39
+ self.config = config
40
+ self.cache = cache
41
+ self.poll_event_buf = poll_event_buf
42
+
43
+ self.timeout = queue_timeout
44
+ self.max_buf_len = max_buf_len
45
+ self.max_wait_time = max_wait_time
46
+
47
+ self.event_buffer = dict()
48
+ self.buffer_times = dict()
49
+
50
+ super().__init__()
51
+
52
+ def flush_buffer(self, target: KoiNetNode, buffer: list[Event]):
53
+ try:
54
+ self.request_handler.broadcast_events(target, events=buffer)
55
+ except Exception as e:
56
+ traceback.print_exc()
57
+
58
+ self.event_buffer[target] = []
59
+ self.buffer_times[target] = None
60
+
61
+ def decide_event(self, item: QueuedEvent) -> bool:
62
+ node_bundle = self.cache.read(item.target)
63
+ if node_bundle:
64
+ node_profile = node_bundle.validate_contents(NodeProfile)
65
+
66
+ if node_profile.node_type == NodeType.FULL:
67
+ return True
68
+
69
+ elif node_profile.node_type == NodeType.PARTIAL:
70
+ self.poll_event_buf.put(item.target, item.event)
71
+ return False
72
+
73
+ elif item.target == self.config.koi_net.first_contact.rid:
74
+ return True
75
+
76
+ else:
77
+ logger.warning(f"Couldn't handle event {item.event!r} in queue, node {item.target!r} unknown to me")
78
+ return False
79
+
80
+
81
+ def run(self):
82
+ logger.info("Started event worker")
83
+ while True:
84
+ now = time.time()
85
+ try:
86
+ item = self.event_queue.q.get(timeout=self.timeout)
87
+
88
+ try:
89
+ if item is END:
90
+ logger.info("Received 'END' signal, flushing buffer...")
91
+ for target in self.event_buffer.keys():
92
+ self.flush_buffer(target, self.event_buffer[target])
93
+ return
94
+
95
+ logger.info(f"Dequeued {item.event!r} -> {item.target!r}")
96
+
97
+ if not self.decide_event(item):
98
+ continue
99
+
100
+ event_buf = self.event_buffer.setdefault(item.target, [])
101
+ if not event_buf:
102
+ self.buffer_times[item.target] = now
103
+
104
+ event_buf.append(item.event)
105
+
106
+ # When new events are dequeued, check buffer for max length
107
+ if len(event_buf) >= self.max_buf_len:
108
+ self.flush_buffer(item.target, event_buf)
109
+ finally:
110
+ self.event_queue.q.task_done()
111
+
112
+ except queue.Empty:
113
+ # On timeout, check all buffers for max wait time
114
+ for target, event_buf in self.event_buffer.items():
115
+ if (len(event_buf) == 0) or (self.buffer_times.get(target) is None):
116
+ continue
117
+ if (now - self.buffer_times[target]) >= self.max_wait_time:
118
+ self.flush_buffer(target, event_buf)
119
+
120
+ except Exception as e:
121
+ traceback.print_exc()
@@ -44,9 +44,11 @@ class KnowledgeHandler:
44
44
  rid_types: list[RIDType] | None = None,
45
45
  event_types: list[EventType | None] | None = None
46
46
  ):
47
- """Special decorator that returns a KnowledgeHandler instead of a function.
47
+ """Decorator wraps a function, returns a KnowledgeHandler.
48
48
 
49
- The function symbol will redefined as a `KnowledgeHandler`, which can be passed into the `ProcessorInterface` constructor. This is used to register default handlers.
49
+ The function symbol will redefined as a `KnowledgeHandler`,
50
+ which can be passed into the `ProcessorInterface` constructor.
51
+ This is used to register default handlers.
50
52
  """
51
53
  def decorator(func: Callable) -> KnowledgeHandler:
52
54
  handler = cls(func, handler_type, rid_types, event_types)
@@ -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
 
@@ -1,11 +1,9 @@
1
1
  import logging
2
- from typing import Callable
3
- from rid_lib.core import RIDType
4
2
  from rid_lib.types import KoiNetEdge, KoiNetNode
5
3
  from rid_lib.ext import Cache
6
4
  from ..protocol.event import EventType
7
5
  from ..network.request_handler import RequestHandler
8
- from ..network.event_queue import NetworkEventQueue
6
+ from ..network.event_queue import EventQueue
9
7
  from ..network.graph import NetworkGraph
10
8
  from ..identity import NodeIdentity
11
9
  from .handler import (
@@ -28,42 +26,26 @@ class KnowledgePipeline:
28
26
  cache: Cache
29
27
  identity: NodeIdentity
30
28
  request_handler: RequestHandler
31
- event_queue: NetworkEventQueue
29
+ event_queue: EventQueue
32
30
  graph: NetworkGraph
33
- handlers: list[KnowledgeHandler]
31
+ knowledge_handlers: list[KnowledgeHandler]
34
32
 
35
33
  def __init__(
36
34
  self,
37
35
  handler_context: "HandlerContext",
38
36
  cache: Cache,
39
37
  request_handler: RequestHandler,
40
- event_queue: NetworkEventQueue,
38
+ event_queue: EventQueue,
41
39
  graph: NetworkGraph,
42
- default_handlers: list[KnowledgeHandler] = []
40
+ knowledge_handlers: list[KnowledgeHandler] = []
43
41
  ):
44
42
  self.handler_context = handler_context
45
43
  self.cache = cache
46
44
  self.request_handler = request_handler
47
45
  self.event_queue = event_queue
48
46
  self.graph = graph
49
- self.handlers = default_handlers
47
+ self.knowledge_handlers = knowledge_handlers
50
48
 
51
- def add_handler(self, handler: KnowledgeHandler):
52
- self.handlers.append(handler)
53
-
54
- def register_handler(
55
- self,
56
- handler_type: HandlerType,
57
- rid_types: list[RIDType] | None = None,
58
- event_types: list[EventType | None] | None = None
59
- ):
60
- """Assigns decorated function as handler for this processor."""
61
- def decorator(func: Callable) -> Callable:
62
- handler = KnowledgeHandler(func, handler_type, rid_types, event_types)
63
- self.add_handler(handler)
64
- return func
65
- return decorator
66
-
67
49
  def call_handler_chain(
68
50
  self,
69
51
  handler_type: HandlerType,
@@ -79,7 +61,7 @@ class KnowledgePipeline:
79
61
  Handlers will only be called in the chain if their handler and RID type match that of the inputted knowledge object.
80
62
  """
81
63
 
82
- for handler in self.handlers:
64
+ for handler in self.knowledge_handlers:
83
65
  if handler_type != handler.handler_type:
84
66
  continue
85
67
 
@@ -215,6 +197,5 @@ class KnowledgePipeline:
215
197
 
216
198
  for node in kobj.network_targets:
217
199
  self.event_queue.push_event_to(kobj.normalized_event, node)
218
- self.event_queue.flush_webhook_queue(node)
219
200
 
220
201
  kobj = self.call_handler_chain(HandlerType.Final, kobj)
@@ -0,0 +1,51 @@
1
+ import logging
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
+ logger = logging.getLogger(__name__)
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
+ logger.debug(f"Queued {_kobj!r}")