koi-net 1.0.0__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/__init__.py +1 -0
- koi_net/config.py +101 -0
- koi_net/core.py +126 -0
- koi_net/identity.py +42 -0
- koi_net/network/__init__.py +1 -0
- koi_net/network/graph.py +127 -0
- koi_net/network/interface.py +276 -0
- koi_net/network/request_handler.py +149 -0
- koi_net/network/response_handler.py +59 -0
- koi_net/processor/__init__.py +1 -0
- koi_net/processor/default_handlers.py +220 -0
- koi_net/processor/handler.py +59 -0
- koi_net/processor/interface.py +300 -0
- koi_net/processor/knowledge_object.py +123 -0
- koi_net/protocol/__init__.py +0 -0
- koi_net/protocol/api_models.py +47 -0
- koi_net/protocol/consts.py +7 -0
- koi_net/protocol/edge.py +20 -0
- koi_net/protocol/event.py +54 -0
- koi_net/protocol/helpers.py +25 -0
- koi_net/protocol/node.py +17 -0
- koi_net-1.0.0.dist-info/METADATA +756 -0
- koi_net-1.0.0.dist-info/RECORD +25 -0
- koi_net-1.0.0.dist-info/WHEEL +4 -0
- koi_net-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import httpx
|
|
3
|
+
from rid_lib import RID
|
|
4
|
+
from rid_lib.ext import Cache
|
|
5
|
+
from rid_lib.types.koi_net_node import KoiNetNode
|
|
6
|
+
from ..protocol.api_models import (
|
|
7
|
+
RidsPayload,
|
|
8
|
+
ManifestsPayload,
|
|
9
|
+
BundlesPayload,
|
|
10
|
+
EventsPayload,
|
|
11
|
+
FetchRids,
|
|
12
|
+
FetchManifests,
|
|
13
|
+
FetchBundles,
|
|
14
|
+
PollEvents,
|
|
15
|
+
RequestModels,
|
|
16
|
+
ResponseModels
|
|
17
|
+
)
|
|
18
|
+
from ..protocol.consts import (
|
|
19
|
+
BROADCAST_EVENTS_PATH,
|
|
20
|
+
POLL_EVENTS_PATH,
|
|
21
|
+
FETCH_RIDS_PATH,
|
|
22
|
+
FETCH_MANIFESTS_PATH,
|
|
23
|
+
FETCH_BUNDLES_PATH
|
|
24
|
+
)
|
|
25
|
+
from ..protocol.node import NodeType
|
|
26
|
+
from .graph import NetworkGraph
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RequestHandler:
|
|
33
|
+
"""Handles making requests to other KOI nodes."""
|
|
34
|
+
|
|
35
|
+
cache: Cache
|
|
36
|
+
graph: NetworkGraph
|
|
37
|
+
|
|
38
|
+
def __init__(self, cache: Cache, graph: NetworkGraph):
|
|
39
|
+
self.cache = cache
|
|
40
|
+
self.graph = graph
|
|
41
|
+
|
|
42
|
+
def make_request(
|
|
43
|
+
self,
|
|
44
|
+
url: str,
|
|
45
|
+
request: RequestModels,
|
|
46
|
+
response_model: type[ResponseModels] | None = None
|
|
47
|
+
) -> ResponseModels | None:
|
|
48
|
+
logger.debug(f"Making request to {url}")
|
|
49
|
+
resp = httpx.post(
|
|
50
|
+
url=url,
|
|
51
|
+
data=request.model_dump_json()
|
|
52
|
+
)
|
|
53
|
+
if response_model:
|
|
54
|
+
return response_model.model_validate_json(resp.text)
|
|
55
|
+
|
|
56
|
+
def get_url(self, node_rid: KoiNetNode, url: str) -> str:
|
|
57
|
+
"""Retrieves URL of a node, or returns provided URL."""
|
|
58
|
+
|
|
59
|
+
if not node_rid and not url:
|
|
60
|
+
raise ValueError("One of 'node_rid' and 'url' must be provided")
|
|
61
|
+
|
|
62
|
+
if node_rid:
|
|
63
|
+
node_profile = self.graph.get_node_profile(node_rid)
|
|
64
|
+
if not node_profile:
|
|
65
|
+
raise Exception("Node not found")
|
|
66
|
+
if node_profile.node_type != NodeType.FULL:
|
|
67
|
+
raise Exception("Can't query partial node")
|
|
68
|
+
logger.debug(f"Resolved {node_rid!r} to {node_profile.base_url}")
|
|
69
|
+
return node_profile.base_url
|
|
70
|
+
else:
|
|
71
|
+
return url
|
|
72
|
+
|
|
73
|
+
def broadcast_events(
|
|
74
|
+
self,
|
|
75
|
+
node: RID = None,
|
|
76
|
+
url: str = None,
|
|
77
|
+
req: EventsPayload | None = None,
|
|
78
|
+
**kwargs
|
|
79
|
+
) -> None:
|
|
80
|
+
"""See protocol.api_models.EventsPayload for available kwargs."""
|
|
81
|
+
request = req or EventsPayload.model_validate(kwargs)
|
|
82
|
+
self.make_request(
|
|
83
|
+
self.get_url(node, url) + BROADCAST_EVENTS_PATH, request
|
|
84
|
+
)
|
|
85
|
+
logger.info(f"Broadcasted {len(request.events)} event(s) to {node or url!r}")
|
|
86
|
+
|
|
87
|
+
def poll_events(
|
|
88
|
+
self,
|
|
89
|
+
node: RID = None,
|
|
90
|
+
url: str = None,
|
|
91
|
+
req: PollEvents | None = None,
|
|
92
|
+
**kwargs
|
|
93
|
+
) -> EventsPayload:
|
|
94
|
+
"""See protocol.api_models.PollEvents for available 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
|
+
response_model=EventsPayload
|
|
99
|
+
)
|
|
100
|
+
logger.info(f"Polled {len(resp.events)} events from {node or url!r}")
|
|
101
|
+
return resp
|
|
102
|
+
|
|
103
|
+
def fetch_rids(
|
|
104
|
+
self,
|
|
105
|
+
node: RID = None,
|
|
106
|
+
url: str = None,
|
|
107
|
+
req: FetchRids | None = None,
|
|
108
|
+
**kwargs
|
|
109
|
+
) -> RidsPayload:
|
|
110
|
+
"""See protocol.api_models.FetchRids for available kwargs."""
|
|
111
|
+
request = req or FetchRids.model_validate(kwargs)
|
|
112
|
+
resp = self.make_request(
|
|
113
|
+
self.get_url(node, url) + FETCH_RIDS_PATH, request,
|
|
114
|
+
response_model=RidsPayload
|
|
115
|
+
)
|
|
116
|
+
logger.info(f"Fetched {len(resp.rids)} RID(s) from {node or url!r}")
|
|
117
|
+
return resp
|
|
118
|
+
|
|
119
|
+
def fetch_manifests(
|
|
120
|
+
self,
|
|
121
|
+
node: RID = None,
|
|
122
|
+
url: str = None,
|
|
123
|
+
req: FetchManifests | None = None,
|
|
124
|
+
**kwargs
|
|
125
|
+
) -> ManifestsPayload:
|
|
126
|
+
"""See protocol.api_models.FetchManifests for available kwargs."""
|
|
127
|
+
request = req or FetchManifests.model_validate(kwargs)
|
|
128
|
+
resp = self.make_request(
|
|
129
|
+
self.get_url(node, url) + FETCH_MANIFESTS_PATH, request,
|
|
130
|
+
response_model=ManifestsPayload
|
|
131
|
+
)
|
|
132
|
+
logger.info(f"Fetched {len(resp.manifests)} manifest(s) from {node or url!r}")
|
|
133
|
+
return resp
|
|
134
|
+
|
|
135
|
+
def fetch_bundles(
|
|
136
|
+
self,
|
|
137
|
+
node: RID = None,
|
|
138
|
+
url: str = None,
|
|
139
|
+
req: FetchBundles | None = None,
|
|
140
|
+
**kwargs
|
|
141
|
+
) -> BundlesPayload:
|
|
142
|
+
"""See protocol.api_models.FetchBundles for available kwargs."""
|
|
143
|
+
request = req or FetchBundles.model_validate(kwargs)
|
|
144
|
+
resp = self.make_request(
|
|
145
|
+
self.get_url(node, url) + FETCH_BUNDLES_PATH, request,
|
|
146
|
+
response_model=BundlesPayload
|
|
147
|
+
)
|
|
148
|
+
logger.info(f"Fetched {len(resp.bundles)} bundle(s) from {node or url!r}")
|
|
149
|
+
return resp
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from rid_lib import RID
|
|
3
|
+
from rid_lib.ext import Manifest, Cache
|
|
4
|
+
from rid_lib.ext.bundle import Bundle
|
|
5
|
+
from ..protocol.api_models import (
|
|
6
|
+
RidsPayload,
|
|
7
|
+
ManifestsPayload,
|
|
8
|
+
BundlesPayload,
|
|
9
|
+
FetchRids,
|
|
10
|
+
FetchManifests,
|
|
11
|
+
FetchBundles,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ResponseHandler:
|
|
18
|
+
"""Handles generating responses to requests from other KOI nodes."""
|
|
19
|
+
|
|
20
|
+
cache: Cache
|
|
21
|
+
|
|
22
|
+
def __init__(self, cache: Cache):
|
|
23
|
+
self.cache = cache
|
|
24
|
+
|
|
25
|
+
def fetch_rids(self, req: FetchRids) -> RidsPayload:
|
|
26
|
+
logger.info(f"Request to fetch rids, allowed types {req.rid_types}")
|
|
27
|
+
rids = self.cache.list_rids(req.rid_types)
|
|
28
|
+
|
|
29
|
+
return RidsPayload(rids=rids)
|
|
30
|
+
|
|
31
|
+
def fetch_manifests(self, req: FetchManifests) -> ManifestsPayload:
|
|
32
|
+
logger.info(f"Request to fetch manifests, allowed types {req.rid_types}, rids {req.rids}")
|
|
33
|
+
|
|
34
|
+
manifests: list[Manifest] = []
|
|
35
|
+
not_found: list[RID] = []
|
|
36
|
+
|
|
37
|
+
for rid in (req.rids or self.cache.list_rids(req.rid_types)):
|
|
38
|
+
bundle = self.cache.read(rid)
|
|
39
|
+
if bundle:
|
|
40
|
+
manifests.append(bundle.manifest)
|
|
41
|
+
else:
|
|
42
|
+
not_found.append(rid)
|
|
43
|
+
|
|
44
|
+
return ManifestsPayload(manifests=manifests, not_found=not_found)
|
|
45
|
+
|
|
46
|
+
def fetch_bundles(self, req: FetchBundles) -> BundlesPayload:
|
|
47
|
+
logger.info(f"Request to fetch bundles, requested rids {req.rids}")
|
|
48
|
+
|
|
49
|
+
bundles: list[Bundle] = []
|
|
50
|
+
not_found: list[RID] = []
|
|
51
|
+
|
|
52
|
+
for rid in req.rids:
|
|
53
|
+
bundle = self.cache.read(rid)
|
|
54
|
+
if bundle:
|
|
55
|
+
bundles.append(bundle)
|
|
56
|
+
else:
|
|
57
|
+
not_found.append(rid)
|
|
58
|
+
|
|
59
|
+
return BundlesPayload(bundles=bundles, not_found=not_found)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .interface import ProcessorInterface
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Provides implementations of default knowledge handlers."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from rid_lib.ext.bundle import Bundle
|
|
5
|
+
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
6
|
+
from koi_net.protocol.node import NodeType
|
|
7
|
+
from .interface import ProcessorInterface
|
|
8
|
+
from .handler import KnowledgeHandler, HandlerType, STOP_CHAIN
|
|
9
|
+
from .knowledge_object import KnowledgeObject, KnowledgeSource
|
|
10
|
+
from ..protocol.event import Event, EventType
|
|
11
|
+
from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
|
|
12
|
+
from ..protocol.node import NodeProfile
|
|
13
|
+
from ..protocol.helpers import generate_edge_bundle
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# RID handlers
|
|
18
|
+
|
|
19
|
+
@KnowledgeHandler.create(HandlerType.RID)
|
|
20
|
+
def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
21
|
+
"""Default RID handler.
|
|
22
|
+
|
|
23
|
+
Blocks external events about this node. Allows `FORGET` events if RID is known to this node.
|
|
24
|
+
"""
|
|
25
|
+
if (kobj.rid == processor.identity.rid and
|
|
26
|
+
kobj.source == KnowledgeSource.External):
|
|
27
|
+
logger.debug("Don't let anyone else tell me who I am!")
|
|
28
|
+
return STOP_CHAIN
|
|
29
|
+
|
|
30
|
+
if kobj.event_type == EventType.FORGET:
|
|
31
|
+
kobj.normalized_event_type = EventType.FORGET
|
|
32
|
+
return kobj
|
|
33
|
+
|
|
34
|
+
# Manifest handlers
|
|
35
|
+
|
|
36
|
+
@KnowledgeHandler.create(HandlerType.Manifest)
|
|
37
|
+
def basic_manifest_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
38
|
+
"""Default manifest handler.
|
|
39
|
+
|
|
40
|
+
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.
|
|
41
|
+
"""
|
|
42
|
+
prev_bundle = processor.cache.read(kobj.rid)
|
|
43
|
+
|
|
44
|
+
if prev_bundle:
|
|
45
|
+
if kobj.manifest.sha256_hash == prev_bundle.manifest.sha256_hash:
|
|
46
|
+
logger.debug("Hash of incoming manifest is same as existing knowledge, ignoring")
|
|
47
|
+
return STOP_CHAIN
|
|
48
|
+
if kobj.manifest.timestamp <= prev_bundle.manifest.timestamp:
|
|
49
|
+
logger.debug("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
|
|
50
|
+
return STOP_CHAIN
|
|
51
|
+
|
|
52
|
+
logger.debug("RID previously known to me, labeling as 'UPDATE'")
|
|
53
|
+
kobj.normalized_event_type = EventType.UPDATE
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
logger.debug("RID previously unknown to me, labeling as 'NEW'")
|
|
57
|
+
kobj.normalized_event_type = EventType.NEW
|
|
58
|
+
|
|
59
|
+
return kobj
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Bundle handlers
|
|
63
|
+
|
|
64
|
+
@KnowledgeHandler.create(
|
|
65
|
+
handler_type=HandlerType.Bundle,
|
|
66
|
+
rid_types=[KoiNetEdge],
|
|
67
|
+
source=KnowledgeSource.External,
|
|
68
|
+
event_types=[EventType.NEW, EventType.UPDATE])
|
|
69
|
+
def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
70
|
+
"""Handles basic edge negotiation process.
|
|
71
|
+
|
|
72
|
+
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.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
edge_profile = EdgeProfile.model_validate(kobj.contents)
|
|
76
|
+
|
|
77
|
+
# indicates peer subscribing to me
|
|
78
|
+
if edge_profile.source == processor.identity.rid:
|
|
79
|
+
if edge_profile.status != EdgeStatus.PROPOSED:
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
logger.debug("Handling edge negotiation")
|
|
83
|
+
|
|
84
|
+
peer_rid = edge_profile.target
|
|
85
|
+
peer_profile = processor.network.graph.get_node_profile(peer_rid)
|
|
86
|
+
|
|
87
|
+
if not peer_profile:
|
|
88
|
+
logger.warning(f"Peer {peer_rid} unknown to me")
|
|
89
|
+
return STOP_CHAIN
|
|
90
|
+
|
|
91
|
+
# explicitly provided event RID types and (self) node + edge objects
|
|
92
|
+
provided_events = (
|
|
93
|
+
*processor.identity.profile.provides.event,
|
|
94
|
+
KoiNetNode, KoiNetEdge
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
abort = False
|
|
99
|
+
if (edge_profile.edge_type == EdgeType.WEBHOOK and
|
|
100
|
+
peer_profile.node_type == NodeType.PARTIAL):
|
|
101
|
+
logger.debug("Partial nodes cannot use webhooks")
|
|
102
|
+
abort = True
|
|
103
|
+
|
|
104
|
+
if not set(edge_profile.rid_types).issubset(provided_events):
|
|
105
|
+
logger.debug("Requested RID types not provided by this node")
|
|
106
|
+
abort = True
|
|
107
|
+
|
|
108
|
+
if abort:
|
|
109
|
+
event = Event.from_rid(EventType.FORGET, kobj.rid)
|
|
110
|
+
processor.network.push_event_to(event, peer_rid, flush=True)
|
|
111
|
+
return STOP_CHAIN
|
|
112
|
+
|
|
113
|
+
else:
|
|
114
|
+
# approve edge profile
|
|
115
|
+
logger.debug("Approving proposed edge")
|
|
116
|
+
edge_profile.status = EdgeStatus.APPROVED
|
|
117
|
+
updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
|
|
118
|
+
|
|
119
|
+
processor.handle(bundle=updated_bundle, event_type=EventType.UPDATE)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
elif edge_profile.target == processor.identity.rid:
|
|
123
|
+
if edge_profile.status == EdgeStatus.APPROVED:
|
|
124
|
+
logger.debug("Edge approved by other node!")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Network handlers
|
|
128
|
+
|
|
129
|
+
@KnowledgeHandler.create(HandlerType.Network, rid_types=[KoiNetNode])
|
|
130
|
+
def coordinator_contact(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
131
|
+
node_profile = kobj.bundle.validate_contents(NodeProfile)
|
|
132
|
+
|
|
133
|
+
# looking for event provider of nodes
|
|
134
|
+
if KoiNetNode not in node_profile.provides.event:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# prevents coordinators from attempting to form a self loop
|
|
138
|
+
if kobj.rid == processor.identity.rid:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# already have an edge established
|
|
142
|
+
if processor.network.graph.get_edge_profile(
|
|
143
|
+
source=kobj.rid,
|
|
144
|
+
target=processor.identity.rid,
|
|
145
|
+
) is not None:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
logger.info("Identified a coordinator!")
|
|
149
|
+
logger.info("Proposing new edge")
|
|
150
|
+
|
|
151
|
+
if processor.identity.profile.node_type == NodeType.FULL:
|
|
152
|
+
edge_type = EdgeType.WEBHOOK
|
|
153
|
+
else:
|
|
154
|
+
edge_type = EdgeType.POLL
|
|
155
|
+
|
|
156
|
+
# queued for processing
|
|
157
|
+
processor.handle(bundle=generate_edge_bundle(
|
|
158
|
+
source=kobj.rid,
|
|
159
|
+
target=processor.identity.rid,
|
|
160
|
+
edge_type=edge_type,
|
|
161
|
+
rid_types=[KoiNetNode]
|
|
162
|
+
))
|
|
163
|
+
|
|
164
|
+
logger.info("Catching up on network state")
|
|
165
|
+
|
|
166
|
+
payload = processor.network.request_handler.fetch_rids(
|
|
167
|
+
node=kobj.rid,
|
|
168
|
+
rid_types=[KoiNetNode]
|
|
169
|
+
)
|
|
170
|
+
for rid in payload.rids:
|
|
171
|
+
if rid == processor.identity.rid:
|
|
172
|
+
logger.info("Skipping myself")
|
|
173
|
+
continue
|
|
174
|
+
if processor.cache.exists(rid):
|
|
175
|
+
logger.info(f"Skipping known RID '{rid}'")
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
# marked as external since we are handling RIDs from another node
|
|
179
|
+
# will fetch remotely instead of checking local cache
|
|
180
|
+
processor.handle(rid=rid, source=KnowledgeSource.External)
|
|
181
|
+
logger.info("Done")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@KnowledgeHandler.create(HandlerType.Network)
|
|
185
|
+
def basic_network_output_filter(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
186
|
+
"""Default network handler.
|
|
187
|
+
|
|
188
|
+
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)."""
|
|
189
|
+
|
|
190
|
+
involves_me = False
|
|
191
|
+
if kobj.source == KnowledgeSource.Internal:
|
|
192
|
+
if (type(kobj.rid) == KoiNetNode):
|
|
193
|
+
if (kobj.rid == processor.identity.rid):
|
|
194
|
+
involves_me = True
|
|
195
|
+
|
|
196
|
+
elif type(kobj.rid) == KoiNetEdge:
|
|
197
|
+
edge_profile = kobj.bundle.validate_contents(EdgeProfile)
|
|
198
|
+
|
|
199
|
+
if edge_profile.source == processor.identity.rid:
|
|
200
|
+
logger.debug(f"Adding edge target '{edge_profile.target!r}' to network targets")
|
|
201
|
+
kobj.network_targets.update([edge_profile.target])
|
|
202
|
+
involves_me = True
|
|
203
|
+
|
|
204
|
+
elif edge_profile.target == processor.identity.rid:
|
|
205
|
+
logger.debug(f"Adding edge source '{edge_profile.source!r}' to network targets")
|
|
206
|
+
kobj.network_targets.update([edge_profile.source])
|
|
207
|
+
involves_me = True
|
|
208
|
+
|
|
209
|
+
if (type(kobj.rid) in processor.identity.profile.provides.event or involves_me):
|
|
210
|
+
# broadcasts to subscribers if I'm an event provider of this RID type OR it involves me
|
|
211
|
+
subscribers = processor.network.graph.get_neighbors(
|
|
212
|
+
direction="out",
|
|
213
|
+
allowed_type=type(kobj.rid)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
logger.debug(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
|
|
217
|
+
kobj.network_targets.update(subscribers)
|
|
218
|
+
|
|
219
|
+
return kobj
|
|
220
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Callable
|
|
4
|
+
from rid_lib import RIDType
|
|
5
|
+
|
|
6
|
+
from ..protocol.event import EventType
|
|
7
|
+
from .knowledge_object import KnowledgeSource, KnowledgeEventType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StopChain:
|
|
11
|
+
"""Class for a sentinel value by knowledge handlers."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
STOP_CHAIN = StopChain()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HandlerType(StrEnum):
|
|
18
|
+
"""Types of handlers used in knowledge processing pipeline.
|
|
19
|
+
|
|
20
|
+
- RID - provided RID; if event type is `FORGET`, this handler decides whether to delete the knowledge from the cache by setting the normalized event type to `FORGET`, otherwise this handler decides whether to validate the manifest (and fetch it if not provided).
|
|
21
|
+
- Manifest - provided RID, manifest; decides whether to validate the bundle (and fetch it if not provided).
|
|
22
|
+
- Bundle - provided RID, manifest, contents (bundle); decides whether to write knowledge to the cache by setting the normalized event type to `NEW` or `UPDATE`.
|
|
23
|
+
- Network - provided RID, manifest, contents (bundle); decides which nodes (if any) to broadcast an event about this knowledge to. (Note, if event type is `FORGET`, the manifest and contents will be retrieved from the local cache, and indicate the last state of the knowledge before it was deleted.)
|
|
24
|
+
- Final - provided RID, manifests, contents (bundle); final action taken after network broadcast.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
RID = "rid",
|
|
28
|
+
Manifest = "manifest",
|
|
29
|
+
Bundle = "bundle",
|
|
30
|
+
Network = "network",
|
|
31
|
+
Final = "final"
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class KnowledgeHandler:
|
|
35
|
+
"""Handles knowledge processing events of the provided types."""
|
|
36
|
+
|
|
37
|
+
func: Callable
|
|
38
|
+
handler_type: HandlerType
|
|
39
|
+
rid_types: list[RIDType] | None
|
|
40
|
+
source: KnowledgeSource | None = None
|
|
41
|
+
event_types: list[KnowledgeEventType] | None = None
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def create(
|
|
45
|
+
cls,
|
|
46
|
+
handler_type: HandlerType,
|
|
47
|
+
rid_types: list[RIDType] | None = None,
|
|
48
|
+
source: KnowledgeSource | None = None,
|
|
49
|
+
event_types: list[KnowledgeEventType] | None = None
|
|
50
|
+
):
|
|
51
|
+
"""Special decorator that returns a KnowledgeHandler instead of a function.
|
|
52
|
+
|
|
53
|
+
The function symbol will redefined as a `KnowledgeHandler`, which can be passed into the `ProcessorInterface` constructor. This is used to register default handlers.
|
|
54
|
+
"""
|
|
55
|
+
def decorator(func: Callable) -> KnowledgeHandler:
|
|
56
|
+
handler = cls(func, handler_type, rid_types, source, event_types)
|
|
57
|
+
return handler
|
|
58
|
+
return decorator
|
|
59
|
+
|