koi-net 1.1.0b3__py3-none-any.whl → 1.1.0b5__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/context.py CHANGED
@@ -1,6 +1,6 @@
1
-
2
- from koi_net.effector import Effector
3
1
  from rid_lib.ext import Cache
2
+ from .config import NodeConfig
3
+ from .effector import Effector
4
4
  from .network.graph import NetworkGraph
5
5
  from .network.event_queue import NetworkEventQueue
6
6
  from .network.request_handler import RequestHandler
@@ -23,6 +23,7 @@ class ActionContext:
23
23
 
24
24
  class HandlerContext:
25
25
  identity: NodeIdentity
26
+ config: NodeConfig
26
27
  cache: Cache
27
28
  event_queue: NetworkEventQueue
28
29
  graph: NetworkGraph
@@ -33,6 +34,7 @@ class HandlerContext:
33
34
  def __init__(
34
35
  self,
35
36
  identity: NodeIdentity,
37
+ config: NodeConfig,
36
38
  cache: Cache,
37
39
  event_queue: NetworkEventQueue,
38
40
  graph: NetworkGraph,
@@ -40,6 +42,7 @@ class HandlerContext:
40
42
  effector: Effector
41
43
  ):
42
44
  self.identity = identity
45
+ self.config = config
43
46
  self.cache = cache
44
47
  self.event_queue = event_queue
45
48
  self.graph = graph
koi_net/core.py CHANGED
@@ -132,6 +132,7 @@ class NodeInterface(Generic[T]):
132
132
 
133
133
  self.handler_context = (HandlerContextOverride or HandlerContext)(
134
134
  identity=self.identity,
135
+ config=self.config,
135
136
  cache=self.cache,
136
137
  event_queue=self.event_queue,
137
138
  graph=self.graph,
@@ -173,6 +174,7 @@ class NodeInterface(Generic[T]):
173
174
  processor=self.processor,
174
175
  effector=self.effector,
175
176
  actor=self.actor,
177
+ handler_context=self.handler_context,
176
178
  use_kobj_processor_thread=use_kobj_processor_thread
177
179
  )
178
180
 
koi_net/lifecycle.py CHANGED
@@ -1,4 +1,7 @@
1
1
  import logging
2
+ from contextlib import contextmanager, asynccontextmanager
3
+
4
+ from koi_net.context import HandlerContext
2
5
 
3
6
  from .network.behavior import Actor
4
7
  from .effector import Effector
@@ -25,6 +28,7 @@ class NodeLifecycle:
25
28
  processor: ProcessorInterface,
26
29
  effector: Effector,
27
30
  actor: Actor,
31
+ handler_context: HandlerContext,
28
32
  use_kobj_processor_thread: bool
29
33
  ):
30
34
  self.config = config
@@ -33,9 +37,34 @@ class NodeLifecycle:
33
37
  self.processor = processor
34
38
  self.effector = effector
35
39
  self.actor = actor
40
+ self.handler_context = handler_context
36
41
  self.use_kobj_processor_thread = use_kobj_processor_thread
42
+
43
+ @contextmanager
44
+ def run(self):
45
+ try:
46
+ logger.info("Starting node lifecycle...")
47
+ self.start()
48
+ yield
49
+ except KeyboardInterrupt:
50
+ logger.info("Keyboard interrupt!")
51
+ finally:
52
+ logger.info("Stopping node lifecycle...")
53
+ self.stop()
54
+
55
+ @asynccontextmanager
56
+ async def async_run(self):
57
+ try:
58
+ logger.info("Starting async node lifecycle...")
59
+ self.start()
60
+ yield
61
+ except KeyboardInterrupt:
62
+ logger.info("Keyboard interrupt!")
63
+ finally:
64
+ logger.info("Stopping async node lifecycle...")
65
+ self.stop()
37
66
 
38
- def start(self) -> None:
67
+ def start(self):
39
68
  """Starts a node, call this method first.
40
69
 
41
70
  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.
@@ -48,7 +77,7 @@ class NodeLifecycle:
48
77
 
49
78
  # refresh to reflect changes (if any) in config.yaml
50
79
  self.effector.deref(self.identity.rid, refresh_cache=True)
51
-
80
+
52
81
  logger.debug("Waiting for kobj queue to empty")
53
82
  if self.use_kobj_processor_thread:
54
83
  self.processor.kobj_queue.join()
@@ -66,9 +95,7 @@ class NodeLifecycle:
66
95
  """Stops a node, call this method last.
67
96
 
68
97
  Finishes processing knowledge object queue. Saves event queues to storage.
69
- """
70
- logger.info("Stopping node...")
71
-
98
+ """
72
99
  if self.use_kobj_processor_thread:
73
100
  logger.info(f"Waiting for kobj queue to empty ({self.processor.kobj_queue.unfinished_tasks} tasks remaining)")
74
101
  self.processor.kobj_queue.join()
@@ -7,7 +7,7 @@ from rid_lib.ext import Cache
7
7
  from rid_lib.types import KoiNetNode
8
8
 
9
9
  from .graph import NetworkGraph
10
- from .request_handler import RequestHandler
10
+ from .request_handler import NodeNotFoundError, RequestHandler
11
11
  from ..protocol.node import NodeProfile, NodeType
12
12
  from ..protocol.edge import EdgeProfile, EdgeType
13
13
  from ..protocol.event import Event
@@ -187,11 +187,13 @@ class NetworkEventQueue:
187
187
 
188
188
  try:
189
189
  self.request_handler.broadcast_events(node, events=events)
190
- return True
190
+
191
+ except NodeNotFoundError:
192
+ logger.warning("Broadcast failed (node not found)")
193
+
191
194
  except httpx.ConnectError:
192
- logger.warning("Broadcast failed")
195
+ logger.warning("Broadcast failed (couldn't connect)")
193
196
 
194
197
  if requeue_on_fail:
195
198
  for event in events:
196
- self.push_event_to(event, node)
197
- return False
199
+ self.push_event_to(event, node)
@@ -37,6 +37,23 @@ if TYPE_CHECKING:
37
37
  logger = logging.getLogger(__name__)
38
38
 
39
39
 
40
+ # Custom error types for request handling
41
+ class SelfRequestError(Exception):
42
+ """Raised when a node tries to request itself."""
43
+ pass
44
+
45
+ class PartialNodeQueryError(Exception):
46
+ """Raised when attempting to query a partial node."""
47
+ pass
48
+
49
+ class NodeNotFoundError(Exception):
50
+ """Raised when a node URL cannot be found."""
51
+ pass
52
+
53
+ class UnknownPathError(Exception):
54
+ """Raised when an unknown path is requested."""
55
+ pass
56
+
40
57
  class RequestHandler:
41
58
  """Handles making requests to other KOI nodes."""
42
59
 
@@ -65,7 +82,7 @@ class RequestHandler:
65
82
  node_url = None
66
83
 
67
84
  if node_rid == self.identity.rid:
68
- raise Exception("Don't talk to yourself")
85
+ raise SelfRequestError("Don't talk to yourself")
69
86
 
70
87
  node_bundle = self.effector.deref(node_rid)
71
88
 
@@ -73,7 +90,7 @@ class RequestHandler:
73
90
  node_profile = node_bundle.validate_contents(NodeProfile)
74
91
  logger.debug(f"Found node profile: {node_profile}")
75
92
  if node_profile.node_type != NodeType.FULL:
76
- raise Exception("Can't query partial node")
93
+ raise PartialNodeQueryError("Can't query partial node")
77
94
  node_url = node_profile.base_url
78
95
 
79
96
  else:
@@ -82,7 +99,7 @@ class RequestHandler:
82
99
  node_url = self.identity.config.koi_net.first_contact.url
83
100
 
84
101
  if not node_url:
85
- raise Exception("Node not found")
102
+ raise NodeNotFoundError("Node not found")
86
103
 
87
104
  logger.debug(f"Resolved {node_rid!r} to {node_url}")
88
105
  return node_url
@@ -124,7 +141,7 @@ class RequestHandler:
124
141
  elif path == FETCH_BUNDLES_PATH:
125
142
  EnvelopeModel = SignedEnvelope[BundlesPayload]
126
143
  else:
127
- raise Exception(f"Unknown path '{path}'")
144
+ raise UnknownPathError(f"Unknown path '{path}'")
128
145
 
129
146
  resp_envelope = EnvelopeModel.model_validate_json(result.text)
130
147
  self.secure.validate_envelope(resp_envelope)
koi_net/poller.py CHANGED
@@ -30,18 +30,11 @@ class NodePoller:
30
30
  self.processor.flush_kobj_queue()
31
31
 
32
32
  def run(self):
33
- try:
34
- self.lifecycle.start()
33
+ with self.lifecycle.run():
35
34
  while True:
36
35
  start_time = time.time()
37
36
  self.poll()
38
37
  elapsed = time.time() - start_time
39
38
  sleep_time = self.config.koi_net.polling_interval - elapsed
40
39
  if sleep_time > 0:
41
- time.sleep(sleep_time)
42
-
43
- except KeyboardInterrupt:
44
- logger.info("Polling interrupted by user.")
45
-
46
- finally:
47
- self.lifecycle.stop()
40
+ time.sleep(sleep_time)
@@ -233,4 +233,17 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
233
233
  kobj.network_targets.update(subscribers)
234
234
 
235
235
  return kobj
236
-
236
+
237
+ @KnowledgeHandler.create(HandlerType.Final, rid_types=[KoiNetNode])
238
+ def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
239
+ if kobj.normalized_event_type != EventType.FORGET:
240
+ return
241
+
242
+ for edge_rid in ctx.graph.get_edges():
243
+ edge_bundle = ctx.cache.read(edge_rid)
244
+ if not edge_bundle: continue
245
+ edge_profile = edge_bundle.validate_contents(EdgeProfile)
246
+
247
+ 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)
koi_net/server.py CHANGED
@@ -54,8 +54,14 @@ class NodeServer:
54
54
  self._build_app()
55
55
 
56
56
  def _build_app(self):
57
+
58
+ @asynccontextmanager
59
+ async def lifespan(*args, **kwargs):
60
+ async with self.lifecycle.async_run():
61
+ yield
62
+
57
63
  self.app = FastAPI(
58
- lifespan=self.lifespan,
64
+ lifespan=lifespan,
59
65
  title="KOI-net Protocol API",
60
66
  version="1.0.0"
61
67
  )
@@ -85,12 +91,6 @@ class NodeServer:
85
91
  port=self.config.server.port
86
92
  )
87
93
 
88
- @asynccontextmanager
89
- async def lifespan(self, app: FastAPI):
90
- self.lifecycle.start()
91
- yield
92
- self.lifecycle.stop()
93
-
94
94
  def protocol_error_handler(self, request, exc: ProtocolError):
95
95
  logger.info(f"caught protocol error: {exc}")
96
96
  resp = ErrorResponse(error=exc.error_type)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.1.0b3
3
+ Version: 1.1.0b5
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>
@@ -1,24 +1,24 @@
1
1
  koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
2
2
  koi_net/config.py,sha256=47XbQ59GRYFi4rlsoWKlnzMQATcnK70i3qmKTZAGOQk,4087
3
- koi_net/context.py,sha256=JmbpCzusXFq_NCXiUp5Z56N6vpBdYMUK8eOs7ogO68A,1428
4
- koi_net/core.py,sha256=jF3xhIhaKrClutmumHO6MjOlA_L5UNU1BUOsxFuHQAI,7068
3
+ koi_net/context.py,sha256=FwkzjmsyNqUeOeGCuZXtONqs5_MSl1R8-e3IxHuyzTI,1531
4
+ koi_net/core.py,sha256=aO9caoS8dLafRGheJWzhbp_ym7o7bi_wxds683bly48,7150
5
5
  koi_net/default_actions.py,sha256=TkQR9oj9CpO37Gb5bZLmFNl-Q8n3OxGiX4dvxQR7SaA,421
6
6
  koi_net/effector.py,sha256=gSyZgRxQ91X04UL261e2pXWUfBHnQTGtjSHpc2JufxA,4097
7
7
  koi_net/identity.py,sha256=FvIWksGTqwM7HCevIwmo_6l-t-2tnYkaaR4CanZatL4,569
8
- koi_net/lifecycle.py,sha256=-J5y4i0JdernatLTed8hQbkK26Cs6rm1kZqlx3aQzZA,2727
9
- koi_net/poller.py,sha256=ZvQcZ0RQhViXVhbIpdTZb7_ql3nG0mTcdBamneZKLpA,1361
8
+ koi_net/lifecycle.py,sha256=GL2zltmh-aeBuNVg_rbIgsXMch672w7xkWAXCTjxst4,3550
9
+ koi_net/poller.py,sha256=bIrlqdac5vLQYAid35xiQJLDMR85GnOSPCXSTQ07-Mc,1173
10
10
  koi_net/secure.py,sha256=cGNF2assqCaYq0i0fhQBm7aREoAdpY-XVypDsE1ALaU,3970
11
- koi_net/server.py,sha256=5BVVrgn69r_HMC3c4npWywPsjBDqUnHFWLKdtnfRTbA,4250
11
+ koi_net/server.py,sha256=dZfSKrNHqIVD1qc9WkYRO30L4so-A7iW4IsX63oSmlE,4265
12
12
  koi_net/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  koi_net/network/behavior.py,sha256=NZLvWlrxR0uWriE3ZzCXmocUVccQthy7Xx8E_8KBwsg,1208
14
14
  koi_net/network/error_handler.py,sha256=CrmCpBY2oj4nl7uXrIYusUHDKxPZ1HDuQAtiBSZarRI,1623
15
- koi_net/network/event_queue.py,sha256=QV1dLOe-H85fNJVkc-e0ZRsUpHTkFcMLMbp3WU5gRNg,7225
15
+ koi_net/network/event_queue.py,sha256=DWs26C235iYkP4koKcdbhmIOHGZRJ48d072BoNWyiHo,7325
16
16
  koi_net/network/graph.py,sha256=NLstBsPa9By0luxcTjThnqVd3hxfQdFwn8tWgJ6u4l4,4144
17
- koi_net/network/request_handler.py,sha256=77SHLO92gVHTusUXWo89KgjRnxU9vLG_Qi8HxTFtFBg,6376
17
+ koi_net/network/request_handler.py,sha256=vBXw2GO5vGAYCs18flWbln4kO_HmY1G9uuFu9MYqedY,6852
18
18
  koi_net/network/resolver.py,sha256=coIp4M6k0-8sUfAy4h2NMx_7zCNroWlCHKOj3AXZVhc,5412
19
19
  koi_net/network/response_handler.py,sha256=__R_EvEpjaMz3PCDvkNgWF_EAHe2nePGk-zK_cT4C4g,2077
20
20
  koi_net/processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- koi_net/processor/default_handlers.py,sha256=SX1eP740W9LeG-IEoBOmsNrKJfOEDDuEQUSwnJEG_8w,8954
21
+ koi_net/processor/default_handlers.py,sha256=1OTC4p0luTadNm90q6Fr_dbvysFzgRCbltp-YP6cRXo,9562
22
22
  koi_net/processor/handler.py,sha256=_loaHjgVGVUxtCQdvAY9dQ0iqiq5co7wB2tK-usuv3Y,2355
23
23
  koi_net/processor/interface.py,sha256=ebDwqggznFRfp2PT8-UJPUAvCwX8nZaaQ68FUeWQvmw,3682
24
24
  koi_net/processor/knowledge_object.py,sha256=avQnsaeqqiJxy40P1VGljuQMtAGmJB-TBa4pmBXTaIs,3863
@@ -32,7 +32,7 @@ koi_net/protocol/errors.py,sha256=A83QiYe_fJdxW2lsNsLCujWxDr5sk1UmYYd3TGbSNJA,60
32
32
  koi_net/protocol/event.py,sha256=eGgihEj1gliLoQRk8pVB2q_was0AGo-PbT3Hqnpn3oU,1379
33
33
  koi_net/protocol/node.py,sha256=7GQzHORFr9cP4BqJgir6EGSWCskL-yqmvJksIiLfcWU,409
34
34
  koi_net/protocol/secure.py,sha256=Reem9Z4le4uWXM9uczNOdmgVBg8p4YQav-7_c3pZ1CQ,3366
35
- koi_net-1.1.0b3.dist-info/METADATA,sha256=LEE8Js4IHoORcyZ7Hoy6kgggQxwbaYCurbs0AEmnQUs,37118
36
- koi_net-1.1.0b3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- koi_net-1.1.0b3.dist-info/licenses/LICENSE,sha256=03mgCL5qth2aD9C3F3qNVs4sFJSpK9kjtYCyOwdSp7s,1069
38
- koi_net-1.1.0b3.dist-info/RECORD,,
35
+ koi_net-1.1.0b5.dist-info/METADATA,sha256=mEMpi26qrfQMeiULMmoDf3iBUVGb8bvgXhTVa9BqhvU,37118
36
+ koi_net-1.1.0b5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ koi_net-1.1.0b5.dist-info/licenses/LICENSE,sha256=03mgCL5qth2aD9C3F3qNVs4sFJSpK9kjtYCyOwdSp7s,1069
38
+ koi_net-1.1.0b5.dist-info/RECORD,,