koi-net 1.0.0b5__py3-none-any.whl → 1.0.0b7__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
@@ -11,25 +11,30 @@ from .protocol.event import Event, EventType
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
+
14
15
  class NodeInterface:
15
16
  cache: Cache
16
17
  identity: NodeIdentity
17
18
  network: NetworkInterface
18
19
  processor: ProcessorInterface
19
20
  first_contact: str
21
+ use_kobj_processor_thread: bool
20
22
 
21
23
  def __init__(
22
24
  self,
23
25
  name: str,
24
26
  profile: NodeProfile,
25
27
  identity_file_path: str = "identity.json",
28
+ event_queues_file_path: str = "event_queues.json",
29
+ cache_directory_path: str = "rid_cache",
30
+ use_kobj_processor_thread: bool = False,
26
31
  first_contact: str | None = None,
27
32
  handlers: list[KnowledgeHandler] | None = None,
28
33
  cache: Cache | None = None,
29
34
  network: NetworkInterface | None = None,
30
35
  processor: ProcessorInterface | None = None
31
36
  ):
32
- self.cache = cache or Cache(directory_path=f"{name}_cache")
37
+ self.cache = cache or Cache(cache_directory_path)
33
38
  self.identity = NodeIdentity(
34
39
  name=name,
35
40
  profile=profile,
@@ -38,7 +43,7 @@ class NodeInterface:
38
43
  )
39
44
  self.first_contact = first_contact
40
45
  self.network = network or NetworkInterface(
41
- file_path=f"{self.identity.rid.name}_event_queues.json",
46
+ file_path=event_queues_file_path,
42
47
  first_contact=self.first_contact,
43
48
  cache=self.cache,
44
49
  identity=self.identity
@@ -50,30 +55,41 @@ class NodeInterface:
50
55
  obj for obj in vars(default_handlers).values()
51
56
  if isinstance(obj, KnowledgeHandler)
52
57
  ]
53
-
58
+
59
+ self.use_kobj_processor_thread = use_kobj_processor_thread
54
60
  self.processor = processor or ProcessorInterface(
55
61
  cache=self.cache,
56
62
  network=self.network,
57
63
  identity=self.identity,
64
+ use_kobj_processor_thread=self.use_kobj_processor_thread,
58
65
  default_handlers=handlers
59
66
  )
60
67
 
61
- def initialize(self) -> None:
62
- """Initializes node, call on startup.
68
+ def start(self) -> None:
69
+ """Starts a node, call this method first.
63
70
 
64
- Loads event queues into memory. Generates network graph from nodes and edges in cache. Processes any state changes of node bundle. Initiates handshake with first contact (if provided) if node doesn't have any neighbors.
71
+ Starts the processor thread (if enabled). Loads event queues into memory. Generates network graph from nodes and edges in cache. Processes any state changes of node bundle. Initiates handshake with first contact (if provided) if node doesn't have any neighbors.
65
72
  """
66
- self.network._load_event_queues()
73
+ if self.use_kobj_processor_thread:
74
+ logger.info("Starting processor worker thread")
75
+ self.processor.worker_thread.start()
67
76
 
77
+ self.network._load_event_queues()
68
78
  self.network.graph.generate()
69
79
 
70
80
  self.processor.handle(
71
81
  bundle=Bundle.generate(
72
82
  rid=self.identity.rid,
73
83
  contents=self.identity.profile.model_dump()
74
- ),
75
- flush=True
84
+ )
76
85
  )
86
+
87
+ logger.info("Waiting for kobj queue to empty")
88
+ if self.use_kobj_processor_thread:
89
+ self.processor.kobj_queue.join()
90
+ else:
91
+ self.processor.flush_kobj_queue()
92
+ logger.info("Done")
77
93
 
78
94
  if not self.network.graph.get_neighbors() and self.first_contact:
79
95
  logger.info(f"I don't have any neighbors, reaching out to first contact {self.first_contact}")
@@ -94,9 +110,17 @@ class NodeInterface:
94
110
  return
95
111
 
96
112
 
97
- def finalize(self):
98
- """Finalizes node, call on shutdown.
113
+ def stop(self):
114
+ """Stops a node, call this method last.
99
115
 
100
- Saves event queues to storage.
116
+ Finishes processing knowledge object queue. Saves event queues to storage.
101
117
  """
118
+ logger.info("Stopping node...")
119
+
120
+ if self.use_kobj_processor_thread:
121
+ logger.info("Waiting for kobj queue to empty")
122
+ self.processor.kobj_queue.join()
123
+ else:
124
+ self.processor.flush_kobj_queue()
125
+
102
126
  self.network._save_event_queues()
koi_net/network/graph.py CHANGED
@@ -74,13 +74,13 @@ class NetworkGraph:
74
74
  """Returns edges this node belongs to.
75
75
 
76
76
  All edges returned by default, specify `direction` to restrict to incoming or outgoing edges only."""
77
-
77
+
78
78
  edges = []
79
- if direction != "in":
79
+ if direction != "in" and self.dg.out_edges:
80
80
  out_edges = self.dg.out_edges(self.identity.rid)
81
81
  edges.extend([e for e in out_edges])
82
82
 
83
- if direction != "out":
83
+ if direction != "out" and self.dg.in_edges:
84
84
  in_edges = self.dg.in_edges(self.identity.rid)
85
85
  edges.extend([e for e in in_edges])
86
86
 
@@ -165,6 +165,8 @@ class NetworkInterface:
165
165
  return
166
166
 
167
167
  events = self._flush_queue(self.webhook_event_queue, node)
168
+ if not events: return
169
+
168
170
  logger.info(f"Broadcasting {len(events)} events")
169
171
 
170
172
  try:
@@ -173,11 +175,6 @@ class NetworkInterface:
173
175
  logger.warning("Broadcast failed, requeuing events")
174
176
  for event in events:
175
177
  self.push_event_to(event, node)
176
-
177
- def flush_all_webhook_queues(self):
178
- """Flushes all nodes' webhook queues and broadcasts events."""
179
- for node in self.webhook_event_queue.keys():
180
- self.flush_webhook_queue(node)
181
178
 
182
179
  def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
183
180
  """Returns list of node RIDs which provide state for the specified RID type."""
@@ -1,5 +1,6 @@
1
1
  import logging
2
- from queue import Queue
2
+ import queue
3
+ import threading
3
4
  from typing import Callable
4
5
  from rid_lib.core import RID, RIDType
5
6
  from rid_lib.ext import Bundle, Cache, Manifest
@@ -30,20 +31,30 @@ class ProcessorInterface:
30
31
  network: NetworkInterface
31
32
  identity: NodeIdentity
32
33
  handlers: list[KnowledgeHandler]
33
- kobj_queue: Queue[KnowledgeObject]
34
+ kobj_queue: queue.Queue[KnowledgeObject]
35
+ use_kobj_processor_thread: bool
36
+ worker_thread: threading.Thread | None = None
34
37
 
35
38
  def __init__(
36
39
  self,
37
40
  cache: Cache,
38
41
  network: NetworkInterface,
39
42
  identity: NodeIdentity,
43
+ use_kobj_processor_thread: bool,
40
44
  default_handlers: list[KnowledgeHandler] = []
41
45
  ):
42
46
  self.cache = cache
43
47
  self.network = network
44
48
  self.identity = identity
49
+ self.use_kobj_processor_thread = use_kobj_processor_thread
45
50
  self.handlers: list[KnowledgeHandler] = default_handlers
46
- self.kobj_queue = Queue()
51
+ self.kobj_queue = queue.Queue()
52
+
53
+ if self.use_kobj_processor_thread:
54
+ self.worker_thread = threading.Thread(
55
+ target=self.kobj_processor_worker,
56
+ daemon=True
57
+ )
47
58
 
48
59
  def add_handler(self, handler: KnowledgeHandler):
49
60
  self.handlers.append(handler)
@@ -195,18 +206,38 @@ class ProcessorInterface:
195
206
  logger.info("No network targets set")
196
207
 
197
208
  for node in kobj.network_targets:
198
- self.network.push_event_to(kobj.normalized_event, node)
199
- self.network.flush_all_webhook_queues()
209
+ self.network.push_event_to(kobj.normalized_event, node, flush=True)
200
210
 
201
211
  kobj = self.call_handler_chain(HandlerType.Final, kobj)
202
-
212
+
203
213
  def flush_kobj_queue(self):
204
- """Flushes all knowledge objects from queue and processes them."""
214
+ """Flushes all knowledge objects from queue and processes them.
215
+
216
+ NOTE: ONLY CALL THIS METHOD IN SINGLE THREADED NODES, OTHERWISE THIS WILL CAUSE RACE CONDITIONS.
217
+ """
218
+ if self.use_kobj_processor_thread:
219
+ logger.warning("You are using a worker thread, calling this method can cause race conditions!")
220
+
205
221
  while not self.kobj_queue.empty():
206
222
  kobj = self.kobj_queue.get()
207
223
  logger.info(f"Dequeued {kobj!r}")
208
224
  self.process_kobj(kobj)
225
+ self.kobj_queue.task_done()
209
226
  logger.info("Done handling")
227
+
228
+ def kobj_processor_worker(self, timeout=0.1):
229
+ while True:
230
+ try:
231
+ kobj = self.kobj_queue.get(timeout=timeout)
232
+ logger.info(f"Dequeued {kobj!r}")
233
+ self.process_kobj(kobj)
234
+ self.kobj_queue.task_done()
235
+
236
+ except queue.Empty:
237
+ pass
238
+
239
+ except Exception as e:
240
+ logger.warning(f"Error processing kobj: {e}")
210
241
 
211
242
  def handle(
212
243
  self,
@@ -216,8 +247,7 @@ class ProcessorInterface:
216
247
  event: Event | None = None,
217
248
  kobj: KnowledgeObject | None = None,
218
249
  event_type: KnowledgeEventType = None,
219
- source: KnowledgeSource = KnowledgeSource.Internal,
220
- flush: bool = False
250
+ source: KnowledgeSource = KnowledgeSource.Internal
221
251
  ):
222
252
  """Queues provided knowledge to be handled by processing pipeline.
223
253
 
@@ -238,6 +268,3 @@ class ProcessorInterface:
238
268
 
239
269
  self.kobj_queue.put(_kobj)
240
270
  logger.info(f"Queued {_kobj!r}")
241
-
242
- if flush:
243
- self.flush_kobj_queue()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.0.0b5
3
+ Version: 1.0.0b7
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>
@@ -150,7 +150,7 @@ node = NodeInterface(
150
150
 
151
151
  ## Knowledge Processing
152
152
 
153
- Next we'll set up the knowledge processing flow for our node. This is where most of the node's logic and behavior will come into play. For partial nodes this will be an event loop, and for full nodes we will use webhooks. Make sure to call `node.initialize()` and `node.finalize()` at the beginning and end of your node's life cycle.
153
+ Next we'll set up the knowledge processing flow for our node. This is where most of the node's logic and behavior will come into play. For partial nodes this will be an event loop, and for full nodes we will use webhooks. Make sure to call `node.start()` and `node.stop()` at the beginning and end of your node's life cycle.
154
154
 
155
155
  ### Partial Node
156
156
  Make sure to set `source=KnowledgeSource.External`, this indicates to the knowledge processing pipeline that the incoming knowledge was received from an external source. Where the knowledge is sourced from will impact decisions in the node's knowledge handlers.
@@ -159,7 +159,7 @@ import time
159
159
  from koi_net.processor.knowledge_object import KnowledgeSource
160
160
 
161
161
  if __name__ == "__main__":
162
- node.initialize()
162
+ node.start()
163
163
 
164
164
  try:
165
165
  while True:
@@ -170,20 +170,20 @@ if __name__ == "__main__":
170
170
  time.sleep(5)
171
171
 
172
172
  finally:
173
- node.finalize()
173
+ node.stop()
174
174
  ```
175
175
 
176
176
  ### Full Node
177
- Setting up a full node is slightly more complex as we'll need a webserver. For this example, we'll use FastAPI and uvicorn. First we need to setup the "lifespan" of the server, to initialize and finalize the node before and after execution, as well as the FastAPI app which will be our web server.
177
+ Setting up a full node is slightly more complex as we'll need a webserver. For this example, we'll use FastAPI and uvicorn. First we need to setup the "lifespan" of the server, to start and stop the node before and after execution, as well as the FastAPI app which will be our web server.
178
178
  ```python
179
179
  from contextlib import asynccontextmanager
180
180
  from fastapi import FastAPI
181
181
 
182
182
  @asynccontextmanager
183
183
  async def lifespan(app: FastAPI):
184
- node.initialize()
184
+ node.start()
185
185
  yield
186
- node.finalize()
186
+ node.stop()
187
187
 
188
188
 
189
189
  app = FastAPI(lifespan=lifespan, root_path="/koi-net")
@@ -265,12 +265,16 @@ class NodeInterface:
265
265
  network: NetworkInterface
266
266
  processor: ProcessorInterface
267
267
  first_contact: str
268
+ use_kobj_processor_thread: bool
268
269
 
269
270
  def __init__(
270
271
  self,
271
272
  name: str,
272
273
  profile: NodeProfile,
273
274
  identity_file_path: str = "identity.json",
275
+ event_queues_file_path: str = "event_queues.json",
276
+ cache_directory_path: str = "rid_cache",
277
+ use_kobj_processor_thread: bool = False,
274
278
  first_contact: str | None = None,
275
279
  handlers: list[KnowledgeHandler] | None = None,
276
280
  cache: Cache | None = None,
@@ -278,8 +282,8 @@ class NodeInterface:
278
282
  processor: ProcessorInterface | None = None
279
283
  ): ...
280
284
 
281
- def initialize(self): ...
282
- def finalize(self): ...
285
+ def start(self): ...
286
+ def stop(self): ...
283
287
  ```
284
288
  As you can see, only a name and profile are required. The other fields allow for additional customization if needed.
285
289
 
@@ -313,7 +317,6 @@ class NetworkInterface:
313
317
 
314
318
  def flush_poll_queue(self, node: KoiNetNode) -> list[Event]: ...
315
319
  def flush_webhook_queue(self, node: RID): ...
316
- def flush_all_webhook_queues(self): ...
317
320
 
318
321
  def fetch_remote_bundle(self, rid: RID): ...
319
322
  def fetch_remote_manifest(self, rid: RID): ...
@@ -432,11 +435,14 @@ def poll_events(req: PollEvents) -> EventsPayload:
432
435
  The `ProcessorInterface` class provides access to a node's internal knowledge processing pipeline.
433
436
  ```python
434
437
  class ProcessorInterface:
438
+ worker_thread: threading.Thread | None = None
439
+
435
440
  def __init__(
436
441
  self,
437
442
  cache: Cache,
438
443
  network: NetworkInterface,
439
444
  identity: NodeIdentity,
445
+ use_kobj_processor_thread: bool,
440
446
  default_handlers: list[KnowledgeHandler] = []
441
447
  ): ...
442
448
 
@@ -506,5 +512,5 @@ python -m build
506
512
  ```
507
513
  Push new package build to PyPI:
508
514
  ```shell
509
- python -m twine upload -r pypi dist/*
515
+ python -m twine upload dist/*
510
516
  ```
@@ -1,15 +1,15 @@
1
1
  koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
2
- koi_net/core.py,sha256=ZsBoTay7Z1_7JKzKt-vB3x_zl9GEit-fFkCSifLPoOk,3582
2
+ koi_net/core.py,sha256=dE4sE2qsoIRUU1zsnrjx7aqYtYdHyCx-Dv4cwbkRjy4,4613
3
3
  koi_net/identity.py,sha256=PBgmAx5f3zzQmHASB1TJW2g19n9TLfmSJMXg2eQFg0A,2386
4
4
  koi_net/network/__init__.py,sha256=r_RN-q_mDYC-2RAkN-lJoMUX76TXyfEUc_MVKW87z0g,39
5
- koi_net/network/graph.py,sha256=SbA0ATIYaiBnq6PCEN2Mu7dgmfyZW3p5tRhZu23UR4E,4782
6
- koi_net/network/interface.py,sha256=paBJjQFJC8UkUz-BWeRwvuWD2cv8WFt7PyhoV7VOhWI,10823
5
+ koi_net/network/graph.py,sha256=KMUCU3AweRvivwy7GuWgX2zX74FPgHeVMO5ydvhVyvA,4833
6
+ koi_net/network/interface.py,sha256=MpqIW-mf1y9eWteMUyTG7kQ9pwB1ubuiAiF9cVBTA84,10647
7
7
  koi_net/network/request_handler.py,sha256=fhuCDsxI8fZ4p5TntcTZR4mnLrLQ61zDy7Oca3ooFCE,4402
8
8
  koi_net/network/response_handler.py,sha256=mA3FtrN3aTZATcLaHQhJUWrJdIKNv6d24fhvOl-nDKY,1890
9
9
  koi_net/processor/__init__.py,sha256=x4fAY0hvQEDcpfdTB3POIzxBQjYAtn0qQazPo1Xm0m4,41
10
10
  koi_net/processor/default_handlers.py,sha256=Yc7a9n5sAOYMHzzY59VMXYOxQL-6O9zbMQzd61XbIEs,7184
11
11
  koi_net/processor/handler.py,sha256=APCECwU7MFcgP7Vu6UTngs0XIjaXSQ_f8rqy8cH5_rM,2242
12
- koi_net/processor/interface.py,sha256=qd4hBzNkNuNoDflzJSV7tuZOx0iRp6dG6tnafh1AWt8,11093
12
+ koi_net/processor/interface.py,sha256=szLLeDfMgeqU35F2na-LvzytJ0irpCtR9g0empo4JoI,12169
13
13
  koi_net/processor/knowledge_object.py,sha256=cGv33fwNZQMylkhlTaQTbk96FVIVbdOUaBsG06u0m4k,4187
14
14
  koi_net/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  koi_net/protocol/api_models.py,sha256=79B5IWQ7gsJ_QIsSRv9424F1frF_DMGkhBbYWkXgtOI,1118
@@ -18,7 +18,7 @@ koi_net/protocol/edge.py,sha256=G3D9Ie0vbTSMJdoTw9g_oBmFCqzJ1gO7U1PVrw7p3j8,447
18
18
  koi_net/protocol/event.py,sha256=dzJmcHbimo7p5NwH2drccF0vMcAj9oQRj3iZ9Bjf7kg,1275
19
19
  koi_net/protocol/helpers.py,sha256=9E9PaoIuSNrTBATGCLJ_kSBMZ2z-KIMnLJzGOTqQDC0,719
20
20
  koi_net/protocol/node.py,sha256=Ntrx01dbm39ViKGtr4gLmztcMwKpTIweS6rRL-zoU_Y,391
21
- koi_net-1.0.0b5.dist-info/METADATA,sha256=ZXkTuagCIqkMl77uy_hqUqo36OwJ5FFEUMDTAPzKL14,21219
22
- koi_net-1.0.0b5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- koi_net-1.0.0b5.dist-info/licenses/LICENSE,sha256=XBcvl8yjCAezfuqN1jadQykrX7H2g4nr2WRDmHLW6ik,1090
24
- koi_net-1.0.0b5.dist-info/RECORD,,
21
+ koi_net-1.0.0b7.dist-info/METADATA,sha256=18EhyJV1HMwYJi0wFRw-wDstKE-Q0KRFq07fGth93jI,21407
22
+ koi_net-1.0.0b7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ koi_net-1.0.0b7.dist-info/licenses/LICENSE,sha256=XBcvl8yjCAezfuqN1jadQykrX7H2g4nr2WRDmHLW6ik,1090
24
+ koi_net-1.0.0b7.dist-info/RECORD,,