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 +5 -2
- koi_net/core.py +2 -0
- koi_net/lifecycle.py +32 -5
- koi_net/network/event_queue.py +7 -5
- koi_net/network/request_handler.py +21 -4
- koi_net/poller.py +2 -9
- koi_net/processor/default_handlers.py +14 -1
- koi_net/server.py +7 -7
- {koi_net-1.1.0b3.dist-info → koi_net-1.1.0b5.dist-info}/METADATA +1 -1
- {koi_net-1.1.0b3.dist-info → koi_net-1.1.0b5.dist-info}/RECORD +12 -12
- {koi_net-1.1.0b3.dist-info → koi_net-1.1.0b5.dist-info}/WHEEL +0 -0
- {koi_net-1.1.0b3.dist-info → koi_net-1.1.0b5.dist-info}/licenses/LICENSE +0 -0
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)
|
|
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()
|
koi_net/network/event_queue.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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=
|
|
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,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=
|
|
4
|
-
koi_net/core.py,sha256=
|
|
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
|
|
9
|
-
koi_net/poller.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
36
|
-
koi_net-1.1.
|
|
37
|
-
koi_net-1.1.
|
|
38
|
-
koi_net-1.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|