koi-net 1.0.0b9__py3-none-any.whl → 1.0.0b11__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/core.py CHANGED
@@ -50,7 +50,7 @@ class NodeInterface:
50
50
  )
51
51
 
52
52
  # pull all handlers defined in default_handlers module
53
- if not handlers:
53
+ if handlers is None:
54
54
  handlers = [
55
55
  obj for obj in vars(default_handlers).values()
56
56
  if isinstance(obj, KnowledgeHandler)
@@ -84,15 +84,15 @@ class NodeInterface:
84
84
  )
85
85
  )
86
86
 
87
- logger.info("Waiting for kobj queue to empty")
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.info("Done")
92
+ logger.debug("Done")
93
93
 
94
94
  if not self.network.graph.get_neighbors() and self.first_contact:
95
- logger.info(f"I don't have any neighbors, reaching out to first contact {self.first_contact}")
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.info("Failed to reach first contact")
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()
koi_net/network/graph.py CHANGED
@@ -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.info("Generating network graph")
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.info(f"Added node {rid}")
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.info(f"Added edge {rid} ({edge_profile.source} -> {edge_profile.target})")
42
- logger.info("Done")
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.info(f"Pushing event {event.event_type} {event.rid} to {node}")
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.info(f"Dequeued {event.event_type} '{event.rid}'")
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.info(f"Flushing poll queue for {node}")
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.info(f"Flushing webhook queue for {node}")
155
+ logger.debug(f"Flushing webhook queue for {node}")
156
156
 
157
157
  node_profile = self.graph.get_node_profile(node)
158
158
 
@@ -167,35 +167,37 @@ class NetworkInterface:
167
167
  events = self._flush_queue(self.webhook_event_queue, node)
168
168
  if not events: return
169
169
 
170
- logger.info(f"Broadcasting {len(events)} events")
170
+ logger.debug(f"Broadcasting {len(events)} events")
171
171
 
172
172
  try:
173
173
  self.request_handler.broadcast_events(node, events=events)
174
+ return True
174
175
  except httpx.ConnectError:
175
- logger.warning("Broadcast failed, requeuing events")
176
+ logger.warning("Broadcast failed, dropping node")
176
177
  for event in events:
177
178
  self.push_event_to(event, node)
179
+ return False
178
180
 
179
181
  def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
180
182
  """Returns list of node RIDs which provide state for the specified RID type."""
181
183
 
182
- logger.info(f"Looking for state providers of '{rid_type}'")
184
+ logger.debug(f"Looking for state providers of '{rid_type}'")
183
185
  provider_nodes = []
184
186
  for node_rid in self.cache.list_rids(rid_types=[KoiNetNode]):
185
187
  node = self.graph.get_node_profile(node_rid)
186
188
 
187
189
  if node.node_type == NodeType.FULL and rid_type in node.provides.state:
188
- logger.info(f"Found provider '{node_rid}'")
190
+ logger.debug(f"Found provider '{node_rid}'")
189
191
  provider_nodes.append(node_rid)
190
192
 
191
193
  if not provider_nodes:
192
- logger.info("Failed to find providers")
194
+ logger.debug("Failed to find providers")
193
195
  return provider_nodes
194
196
 
195
197
  def fetch_remote_bundle(self, rid: RID):
196
198
  """Attempts to fetch a bundle by RID from known peer nodes."""
197
199
 
198
- logger.info(f"Fetching remote bundle '{rid}'")
200
+ logger.debug(f"Fetching remote bundle '{rid}'")
199
201
  remote_bundle = None
200
202
  for node_rid in self.get_state_providers(type(rid)):
201
203
  payload = self.request_handler.fetch_bundles(
@@ -203,7 +205,7 @@ class NetworkInterface:
203
205
 
204
206
  if payload.bundles:
205
207
  remote_bundle = payload.bundles[0]
206
- logger.info(f"Got bundle from '{node_rid}'")
208
+ logger.debug(f"Got bundle from '{node_rid}'")
207
209
  break
208
210
 
209
211
  if not remote_bundle:
@@ -214,7 +216,7 @@ class NetworkInterface:
214
216
  def fetch_remote_manifest(self, rid: RID):
215
217
  """Attempts to fetch a manifest by RID from known peer nodes."""
216
218
 
217
- logger.info(f"Fetching remote manifest '{rid}'")
219
+ logger.debug(f"Fetching remote manifest '{rid}'")
218
220
  remote_manifest = None
219
221
  for node_rid in self.get_state_providers(type(rid)):
220
222
  payload = self.request_handler.fetch_manifests(
@@ -222,7 +224,7 @@ class NetworkInterface:
222
224
 
223
225
  if payload.manifests:
224
226
  remote_manifest = payload.manifests[0]
225
- logger.info(f"Got bundle from '{node_rid}'")
227
+ logger.debug(f"Got bundle from '{node_rid}'")
226
228
  break
227
229
 
228
230
  if not remote_manifest:
@@ -239,17 +241,17 @@ class NetworkInterface:
239
241
  neighbors = self.graph.get_neighbors()
240
242
 
241
243
  if not neighbors and self.first_contact:
242
- logger.info("No neighbors found, polling first contact")
244
+ logger.debug("No neighbors found, polling first contact")
243
245
  try:
244
246
  payload = self.request_handler.poll_events(
245
247
  url=self.first_contact,
246
248
  rid=self.identity.rid
247
249
  )
248
250
  if payload.events:
249
- logger.info(f"Received {len(payload.events)} events from '{self.first_contact}'")
251
+ logger.debug(f"Received {len(payload.events)} events from '{self.first_contact}'")
250
252
  return payload.events
251
253
  except httpx.ConnectError:
252
- logger.info(f"Failed to reach first contact '{self.first_contact}'")
254
+ logger.debug(f"Failed to reach first contact '{self.first_contact}'")
253
255
 
254
256
  events = []
255
257
  for node_rid in neighbors:
@@ -263,10 +265,10 @@ class NetworkInterface:
263
265
  rid=self.identity.rid
264
266
  )
265
267
  if payload.events:
266
- logger.info(f"Received {len(payload.events)} events from {node_rid!r}")
268
+ logger.debug(f"Received {len(payload.events)} events from {node_rid!r}")
267
269
  events.extend(payload.events)
268
270
  except httpx.ConnectError:
269
- logger.info(f"Failed to reach node '{node_rid}'")
271
+ logger.debug(f"Failed to reach node '{node_rid}'")
270
272
  continue
271
273
 
272
274
  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.info(f"Making request to {url}")
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.info(f"Resolved {node_rid!r} to {node_profile.base_url}")
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
- return self.make_request(
96
- self.get_url(node, url) + POLL_EVENTS_PATH,
97
- req or PollEvents.model_validate(kwargs),
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
- return self.make_request(
110
- self.get_url(node, url) + FETCH_RIDS_PATH,
111
- req or FetchRids.model_validate(kwargs),
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
- return self.make_request(
124
- self.get_url(node, url) + FETCH_MANIFESTS_PATH,
125
- req or FetchManifests.model_validate(kwargs),
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
- return self.make_request(
138
- self.get_url(node, url) + FETCH_BUNDLES_PATH,
139
- req or FetchBundles.model_validate(kwargs),
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.info("Don't let anyone else tell me who I am!")
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.info("Hash of incoming manifest is same as existing knowledge, ignoring")
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.info("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
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.info("RID previously known to me, labeling as 'UPDATE'")
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.info("RID previously unknown to me, labeling as 'NEW'")
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.info("Handling edge negotiation")
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.info("Partial nodes cannot use webhooks")
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.info("Requested RID types not provided by this node")
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.info("Approving proposed edge")
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.info("Edge approved by other node!")
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.info(f"Adding edge target '{edge_profile.target!r}' to network targets")
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.info(f"Adding edge source '{edge_profile.source!r}' to network targets")
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.info(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
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.info(f"Calling {handler_type} handler '{handler.func.__name__}'")
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.info(f"Handler chain stopped by {handler.func.__name__}")
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.info(f"Knowledge object modified by {handler.func.__name__}")
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.info(f"Handling {kobj!r}")
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.info("Local bundle not found")
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.info("Adding local bundle (to be deleted) to knowledge object")
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.info("Manifest not found, attempting to fetch remotely")
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.info("Manifest not found, attempting to read cache")
161
+ logger.debug("Attempting to read manifest from cache")
162
162
  bundle = self.cache.read(kobj.rid)
163
- if not bundle: return
164
- manifest = bundle.manifest
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.info("Bundle not found, attempting to fetch")
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.info("Bundle not found, attempting to read cache")
186
+ logger.debug("Attempting to read bundle from cache")
180
187
  bundle = self.cache.read(kobj.rid)
181
188
 
189
+ if not bundle:
190
+ logger.debug("Failed to find bundle")
191
+ return
192
+
182
193
  if kobj.manifest != bundle.manifest:
183
194
  logger.warning("Retrieved bundle contains a different manifest")
184
195
 
185
- if not bundle: return
186
196
  kobj.manifest = bundle.manifest
187
197
  kobj.contents = bundle.contents
188
198
 
@@ -190,31 +200,34 @@ 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} to cache")
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} from cache")
207
+ logger.info(f"Deleting from cache: {kobj!r}")
198
208
  self.cache.delete(kobj.rid)
199
209
 
200
210
  else:
201
- logger.info("Normalized event type was never set, no cache or network operations will occur")
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.info("Change to node or edge, regenerating network graph")
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.info(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
222
+ logger.debug(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
213
223
  else:
214
- logger.info("No network targets set")
224
+ logger.debug("No network targets set")
215
225
 
216
226
  for node in kobj.network_targets:
217
- self.network.push_event_to(kobj.normalized_event, node, flush=True)
227
+ self.network.push_event_to(kobj.normalized_event, node)
228
+ if not self.network.flush_webhook_queue(node):
229
+ logger.warning("Dropping unresponsive node")
230
+ self.handle(rid=node, event_type=EventType.FORGET)
218
231
 
219
232
  kobj = self.call_handler_chain(HandlerType.Final, kobj)
220
233
 
@@ -228,18 +241,25 @@ class ProcessorInterface:
228
241
 
229
242
  while not self.kobj_queue.empty():
230
243
  kobj = self.kobj_queue.get()
231
- logger.info(f"Dequeued {kobj!r}")
232
- self.process_kobj(kobj)
233
- self.kobj_queue.task_done()
234
- logger.info("Done handling")
244
+ logger.debug(f"Dequeued {kobj!r}")
245
+
246
+ try:
247
+ self.process_kobj(kobj)
248
+ finally:
249
+ self.kobj_queue.task_done()
250
+ logger.debug("Done")
235
251
 
236
252
  def kobj_processor_worker(self, timeout=0.1):
237
253
  while True:
238
254
  try:
239
255
  kobj = self.kobj_queue.get(timeout=timeout)
240
- logger.info(f"Dequeued {kobj!r}")
241
- self.process_kobj(kobj)
242
- self.kobj_queue.task_done()
256
+ logger.debug(f"Dequeued {kobj!r}")
257
+
258
+ try:
259
+ self.process_kobj(kobj)
260
+ finally:
261
+ self.kobj_queue.task_done()
262
+ logger.debug("Done")
243
263
 
244
264
  except queue.Empty:
245
265
  pass
@@ -275,4 +295,4 @@ class ProcessorInterface:
275
295
  raise ValueError("One of 'rid', 'manifest', 'bundle', 'event', or 'kobj' must be provided")
276
296
 
277
297
  self.kobj_queue.put(_kobj)
278
- logger.info(f"Queued {_kobj!r}")
298
+ 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"<Knowledge Object '{self.rid}' ({self.event_type}) -> ({self.normalized_event_type})>"
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(
koi_net/protocol/event.py CHANGED
@@ -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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.0.0b9
3
+ Version: 1.0.0b11
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
 
@@ -87,7 +118,9 @@ The request and payload JSON objects are composed of the fundamental "knowledge
87
118
  }
88
119
  ```
89
120
 
90
- This means that events are essentially just an RID, manifest, or bundle with an event type attached. Event types can be one of `"FORGET"`, `"UPDATE"`, or `"NEW"` forming the "FUN" acronym. While these types roughly correspond to delete, update, and create from CRUD operations, but they are not commands, they are signals. A node emits an event to indicate that its internal state has changed:
121
+ An event is a signalling construct that conveys information about RID objects between networked nodes. Events are composed of an RID, manifest, or bundle with an event type attached. Event types can be one of `"FORGET"`, `"UPDATE"`, or `"NEW"` forming the "FUN" acronym.
122
+
123
+ As opposed to CRUD (create, read, update, delete), events are a series of messages, not operations. Each node has its own autonomy in deciding how to react based on the message it receives. For example, a processor node may receive a `"NEW"` event for an RID object its not interested in, and ignore it. Or it may decide that an `"UPDATE"` event should trigger fetching a bundle from another node. A node emits an event to indicate that its internal state has changed:
91
124
  - `"NEW"` - indicates an previously unknown RID was cached
92
125
  - `"UPDATE"` - indicates a previously known RID was cached
93
126
  - `"FORGET"` - indicates a previously known RID was deleted
@@ -653,5 +686,5 @@ python -m build
653
686
  ```
654
687
  Push new package build to PyPI:
655
688
  ```shell
656
- python -m twine upload dist/*
689
+ python -m twine upload --skip-existing dist/*
657
690
  ```
@@ -0,0 +1,24 @@
1
+ koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
2
+ koi_net/core.py,sha256=975g-IcEQ72wZCulg34Orh7vJNRWMCh5iC5kppz_18Q,4687
3
+ koi_net/identity.py,sha256=PBgmAx5f3zzQmHASB1TJW2g19n9TLfmSJMXg2eQFg0A,2386
4
+ koi_net/network/__init__.py,sha256=r_RN-q_mDYC-2RAkN-lJoMUX76TXyfEUc_MVKW87z0g,39
5
+ koi_net/network/graph.py,sha256=KyS4Vv94DhHQk12u6rmE8nOIuTxeoJjrnoYEwVMNM74,4837
6
+ koi_net/network/interface.py,sha256=ogVnVufGPv_x83KnGf3AwhCBTF8xETBH3QId6oqpyNs,10708
7
+ koi_net/network/request_handler.py,sha256=dufyJRwKD5w578UM-wzFVT3EU78NMvrh_yNSiI7vgfw,4938
8
+ koi_net/network/response_handler.py,sha256=HaP8Fl0bp_lfMmevhdVY8s9o0Uf8CR1ZaW5g3jsX8gw,1888
9
+ koi_net/processor/__init__.py,sha256=x4fAY0hvQEDcpfdTB3POIzxBQjYAtn0qQazPo1Xm0m4,41
10
+ koi_net/processor/default_handlers.py,sha256=ebFJ_MqvGBAb0SxmreZ9Pvyi0tD7ygDvJKECglw8uAQ,6972
11
+ koi_net/processor/handler.py,sha256=a2wxG3kgux-07YP13HSj_PH5VH_PjbnTS4uZEHjxMw0,2582
12
+ koi_net/processor/interface.py,sha256=IH3rh2lAliqNSKsuH8PwY9N098SnVUBy3Ccel8YgiRM,13188
13
+ koi_net/processor/knowledge_object.py,sha256=xI1K4lIGJrWbb7yStjpU4eqZGI7t4wFklv72V7TRfXY,4212
14
+ koi_net/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ koi_net/protocol/api_models.py,sha256=RDwVHAahiWzwzUnlj5MIm9et5WVpQOaG-Uscv1B9coU,1116
16
+ koi_net/protocol/consts.py,sha256=zeWJvRpqcERrqJq39heyNHb6f_9QrvoBZJHd70yE914,249
17
+ koi_net/protocol/edge.py,sha256=G3D9Ie0vbTSMJdoTw9g_oBmFCqzJ1gO7U1PVrw7p3j8,447
18
+ koi_net/protocol/event.py,sha256=YHsLD9HPwib-HiCWv9ep0sPqL2VHQqx_egDmbriChXM,1378
19
+ koi_net/protocol/helpers.py,sha256=9E9PaoIuSNrTBATGCLJ_kSBMZ2z-KIMnLJzGOTqQDC0,719
20
+ koi_net/protocol/node.py,sha256=Ntrx01dbm39ViKGtr4gLmztcMwKpTIweS6rRL-zoU_Y,391
21
+ koi_net-1.0.0b11.dist-info/METADATA,sha256=CAQb9AH_yDb2FO1XT9FzmDrrfUsCYTDUUo3mCPq8VQ4,34131
22
+ koi_net-1.0.0b11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ koi_net-1.0.0b11.dist-info/licenses/LICENSE,sha256=XBcvl8yjCAezfuqN1jadQykrX7H2g4nr2WRDmHLW6ik,1090
24
+ koi_net-1.0.0b11.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
2
- koi_net/core.py,sha256=dE4sE2qsoIRUU1zsnrjx7aqYtYdHyCx-Dv4cwbkRjy4,4613
3
- koi_net/identity.py,sha256=PBgmAx5f3zzQmHASB1TJW2g19n9TLfmSJMXg2eQFg0A,2386
4
- koi_net/network/__init__.py,sha256=r_RN-q_mDYC-2RAkN-lJoMUX76TXyfEUc_MVKW87z0g,39
5
- koi_net/network/graph.py,sha256=KMUCU3AweRvivwy7GuWgX2zX74FPgHeVMO5ydvhVyvA,4833
6
- koi_net/network/interface.py,sha256=4JTeg8Eah0z5YKhcVKJbCVZw_Ghl_6xfG8aa1I5PCWI,10643
7
- koi_net/network/request_handler.py,sha256=fhuCDsxI8fZ4p5TntcTZR4mnLrLQ61zDy7Oca3ooFCE,4402
8
- koi_net/network/response_handler.py,sha256=HaP8Fl0bp_lfMmevhdVY8s9o0Uf8CR1ZaW5g3jsX8gw,1888
9
- koi_net/processor/__init__.py,sha256=x4fAY0hvQEDcpfdTB3POIzxBQjYAtn0qQazPo1Xm0m4,41
10
- koi_net/processor/default_handlers.py,sha256=cdGDFb4z1vnDmAXQO5XeDjhbUHbB8TndC-E6AFJzp2M,6930
11
- koi_net/processor/handler.py,sha256=a2wxG3kgux-07YP13HSj_PH5VH_PjbnTS4uZEHjxMw0,2582
12
- koi_net/processor/interface.py,sha256=5zYxaD_ATjjp1xFunRomCrrzjyspi8zZzeY2Rca7bVU,12522
13
- koi_net/processor/knowledge_object.py,sha256=cGv33fwNZQMylkhlTaQTbk96FVIVbdOUaBsG06u0m4k,4187
14
- koi_net/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- koi_net/protocol/api_models.py,sha256=RDwVHAahiWzwzUnlj5MIm9et5WVpQOaG-Uscv1B9coU,1116
16
- koi_net/protocol/consts.py,sha256=zeWJvRpqcERrqJq39heyNHb6f_9QrvoBZJHd70yE914,249
17
- koi_net/protocol/edge.py,sha256=G3D9Ie0vbTSMJdoTw9g_oBmFCqzJ1gO7U1PVrw7p3j8,447
18
- koi_net/protocol/event.py,sha256=dzJmcHbimo7p5NwH2drccF0vMcAj9oQRj3iZ9Bjf7kg,1275
19
- koi_net/protocol/helpers.py,sha256=9E9PaoIuSNrTBATGCLJ_kSBMZ2z-KIMnLJzGOTqQDC0,719
20
- koi_net/protocol/node.py,sha256=Ntrx01dbm39ViKGtr4gLmztcMwKpTIweS6rRL-zoU_Y,391
21
- koi_net-1.0.0b9.dist-info/METADATA,sha256=GZbTzNd_k5uVlvaik7_YXdYd17Ya6XtVTf6NXrxbRes,32539
22
- koi_net-1.0.0b9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- koi_net-1.0.0b9.dist-info/licenses/LICENSE,sha256=XBcvl8yjCAezfuqN1jadQykrX7H2g4nr2WRDmHLW6ik,1090
24
- koi_net-1.0.0b9.dist-info/RECORD,,