koi-net 1.0.0b1__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/core.py +86 -0
- koi_net/identity.py +62 -0
- koi_net/network/__init__.py +1 -0
- koi_net/network/graph.py +112 -0
- koi_net/network/interface.py +249 -0
- koi_net/network/request_handler.py +105 -0
- koi_net/network/response_handler.py +57 -0
- koi_net/processor/__init__.py +1 -0
- koi_net/processor/default_handlers.py +151 -0
- koi_net/processor/handler.py +22 -0
- koi_net/processor/interface.py +222 -0
- koi_net/processor/knowledge_object.py +104 -0
- koi_net/protocol/__init__.py +0 -0
- koi_net/protocol/api_models.py +39 -0
- koi_net/protocol/consts.py +5 -0
- koi_net/protocol/edge.py +20 -0
- koi_net/protocol/event.py +48 -0
- koi_net/protocol/helpers.py +25 -0
- koi_net/protocol/node.py +17 -0
- koi_net-1.0.0b1.dist-info/METADATA +43 -0
- koi_net-1.0.0b1.dist-info/RECORD +24 -0
- koi_net-1.0.0b1.dist-info/WHEEL +4 -0
- koi_net-1.0.0b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from rid_lib.ext.bundle import Bundle
|
|
3
|
+
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
4
|
+
from koi_net.protocol.node import NodeType
|
|
5
|
+
from .interface import ProcessorInterface
|
|
6
|
+
from .handler import HandlerType, STOP_CHAIN
|
|
7
|
+
from .knowledge_object import KnowledgeObject, KnowledgeSource
|
|
8
|
+
from ..protocol.event import Event, EventType
|
|
9
|
+
from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# RID handlers
|
|
14
|
+
|
|
15
|
+
@ProcessorInterface.as_handler(handler_type=HandlerType.RID)
|
|
16
|
+
def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
17
|
+
if (kobj.rid == processor.identity.rid and
|
|
18
|
+
kobj.source == KnowledgeSource.External):
|
|
19
|
+
logger.info("Don't let anyone else tell me who I am!")
|
|
20
|
+
return STOP_CHAIN
|
|
21
|
+
|
|
22
|
+
if kobj.event_type == EventType.FORGET:
|
|
23
|
+
if processor.cache.exists(kobj.rid):
|
|
24
|
+
logger.info("Allowing cache forget")
|
|
25
|
+
kobj.normalized_event_type = EventType.FORGET
|
|
26
|
+
return kobj
|
|
27
|
+
|
|
28
|
+
else:
|
|
29
|
+
# can't forget something I don't know about
|
|
30
|
+
return STOP_CHAIN
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Manifest handlers
|
|
34
|
+
|
|
35
|
+
@ProcessorInterface.as_handler(handler_type=HandlerType.Manifest)
|
|
36
|
+
def basic_state_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
37
|
+
prev_bundle = processor.cache.read(kobj.rid)
|
|
38
|
+
|
|
39
|
+
if prev_bundle:
|
|
40
|
+
if kobj.manifest.sha256_hash == prev_bundle.manifest.sha256_hash:
|
|
41
|
+
logger.info("Hash of incoming manifest is same as existing knowledge, ignoring")
|
|
42
|
+
return STOP_CHAIN
|
|
43
|
+
if kobj.manifest.timestamp <= prev_bundle.manifest.timestamp:
|
|
44
|
+
logger.info("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
|
|
45
|
+
return STOP_CHAIN
|
|
46
|
+
|
|
47
|
+
logger.info("RID previously known to me, labeling as 'UPDATE'")
|
|
48
|
+
kobj.normalized_event_type = EventType.UPDATE
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
logger.info("RID previously unknown to me, labeling as 'NEW'")
|
|
52
|
+
kobj.normalized_event_type = EventType.NEW
|
|
53
|
+
|
|
54
|
+
return kobj
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Bundle handlers
|
|
58
|
+
|
|
59
|
+
@ProcessorInterface.as_handler(HandlerType.Bundle, rid_types=[KoiNetEdge])
|
|
60
|
+
def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
61
|
+
edge_profile = EdgeProfile.model_validate(kobj.contents)
|
|
62
|
+
|
|
63
|
+
# only want to handle external knowledge events (not edges this node created)
|
|
64
|
+
if kobj.source != KnowledgeSource.External:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# indicates peer subscribing to me
|
|
68
|
+
if edge_profile.source == processor.identity.rid:
|
|
69
|
+
if edge_profile.status != EdgeStatus.PROPOSED:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
logger.info("Handling edge negotiation")
|
|
73
|
+
|
|
74
|
+
peer_rid = edge_profile.target
|
|
75
|
+
peer_profile = processor.network.graph.get_node_profile(peer_rid)
|
|
76
|
+
|
|
77
|
+
if not peer_profile:
|
|
78
|
+
logger.warning(f"Peer {peer_rid} unknown to me")
|
|
79
|
+
return STOP_CHAIN
|
|
80
|
+
|
|
81
|
+
# explicitly provided event RID types and (self) node + edge objects
|
|
82
|
+
provided_events = (
|
|
83
|
+
*processor.identity.profile.provides.event,
|
|
84
|
+
KoiNetNode, KoiNetEdge
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
abort = False
|
|
89
|
+
if (edge_profile.edge_type == EdgeType.WEBHOOK and
|
|
90
|
+
peer_profile.node_type == NodeType.PARTIAL):
|
|
91
|
+
logger.info("Partial nodes cannot use webhooks")
|
|
92
|
+
abort = True
|
|
93
|
+
|
|
94
|
+
if not set(edge_profile.rid_types).issubset(provided_events):
|
|
95
|
+
logger.info("Requested RID types not provided by this node")
|
|
96
|
+
abort = True
|
|
97
|
+
|
|
98
|
+
if abort:
|
|
99
|
+
event = Event.from_rid(EventType.FORGET, kobj.rid)
|
|
100
|
+
processor.network.push_event_to(event, peer_rid, flush=True)
|
|
101
|
+
return STOP_CHAIN
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
# approve edge profile
|
|
105
|
+
logger.info("Approving proposed edge")
|
|
106
|
+
edge_profile.status = EdgeStatus.APPROVED
|
|
107
|
+
updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
|
|
108
|
+
|
|
109
|
+
processor.handle(bundle=updated_bundle)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
elif edge_profile.target == processor.identity.rid:
|
|
113
|
+
if edge_profile.status == EdgeStatus.APPROVED:
|
|
114
|
+
logger.info("Edge approved by other node!")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Network handlers
|
|
118
|
+
|
|
119
|
+
@ProcessorInterface.as_handler(HandlerType.Network)
|
|
120
|
+
def basic_network_output_filter(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
121
|
+
involves_me = False
|
|
122
|
+
if kobj.source == KnowledgeSource.Internal:
|
|
123
|
+
if (type(kobj.rid) == KoiNetNode):
|
|
124
|
+
if (kobj.rid == processor.identity.rid):
|
|
125
|
+
involves_me = True
|
|
126
|
+
|
|
127
|
+
elif type(kobj.rid) == KoiNetEdge:
|
|
128
|
+
edge_profile = kobj.bundle.validate_contents(EdgeProfile)
|
|
129
|
+
|
|
130
|
+
if edge_profile.source == processor.identity.rid:
|
|
131
|
+
logger.info(f"Adding edge target '{edge_profile.target!r}' to network targets")
|
|
132
|
+
kobj.network_targets.update([edge_profile.target])
|
|
133
|
+
involves_me = True
|
|
134
|
+
|
|
135
|
+
elif edge_profile.target == processor.identity.rid:
|
|
136
|
+
logger.info(f"Adding edge source '{edge_profile.source!r}' to network targets")
|
|
137
|
+
kobj.network_targets.update([edge_profile.source])
|
|
138
|
+
involves_me = True
|
|
139
|
+
|
|
140
|
+
if (type(kobj.rid) in processor.identity.profile.provides.event or involves_me):
|
|
141
|
+
# broadcasts to subscribers if I'm an event provider of this RID type OR it involves me
|
|
142
|
+
subscribers = processor.network.graph.get_neighbors(
|
|
143
|
+
direction="out",
|
|
144
|
+
allowed_type=type(kobj.rid)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
logger.info(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
|
|
148
|
+
kobj.network_targets.update(subscribers)
|
|
149
|
+
|
|
150
|
+
return kobj
|
|
151
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Callable
|
|
4
|
+
from rid_lib import RIDType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# sentinel
|
|
8
|
+
STOP_CHAIN = object()
|
|
9
|
+
|
|
10
|
+
class HandlerType(StrEnum):
|
|
11
|
+
RID = "rid", # guaranteed RID - decides whether validate manifest OR cache delete
|
|
12
|
+
Manifest = "manifest", # guaranteed Manifest - decides whether to validate bundle
|
|
13
|
+
Bundle = "bundle", # guaranteed Bundle - decides whether to write to cache
|
|
14
|
+
Network = "network", # guaranteed Bundle, after cache write/delete - decides network targets
|
|
15
|
+
Final = "final" # guaranteed Bundle, after network push - final action
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class KnowledgeHandler:
|
|
19
|
+
func: Callable
|
|
20
|
+
handler_type: HandlerType
|
|
21
|
+
rid_types: list[RIDType] | None
|
|
22
|
+
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from queue import Queue
|
|
3
|
+
from typing import Callable
|
|
4
|
+
from rid_lib.core import RID, RIDType
|
|
5
|
+
from rid_lib.ext import Bundle, Cache, Manifest
|
|
6
|
+
from rid_lib.types.koi_net_edge import KoiNetEdge
|
|
7
|
+
from rid_lib.types.koi_net_node import KoiNetNode
|
|
8
|
+
from ..identity import NodeIdentity
|
|
9
|
+
from ..network import NetworkInterface
|
|
10
|
+
from ..protocol.event import Event, EventType
|
|
11
|
+
from .handler import (
|
|
12
|
+
KnowledgeHandler,
|
|
13
|
+
HandlerType,
|
|
14
|
+
STOP_CHAIN
|
|
15
|
+
)
|
|
16
|
+
from .knowledge_object import (
|
|
17
|
+
KnowledgeObject,
|
|
18
|
+
KnowledgeSource,
|
|
19
|
+
KnowledgeEventType
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProcessorInterface:
|
|
26
|
+
cache: Cache
|
|
27
|
+
network: NetworkInterface
|
|
28
|
+
identity: NodeIdentity
|
|
29
|
+
handlers: list[KnowledgeHandler]
|
|
30
|
+
kobj_queue: Queue[KnowledgeObject]
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
cache: Cache,
|
|
35
|
+
network: NetworkInterface,
|
|
36
|
+
identity: NodeIdentity,
|
|
37
|
+
default_handlers: list[KnowledgeHandler] = []
|
|
38
|
+
):
|
|
39
|
+
self.cache = cache
|
|
40
|
+
self.network = network
|
|
41
|
+
self.identity = identity
|
|
42
|
+
self.handlers: list[KnowledgeHandler] = default_handlers
|
|
43
|
+
self.kobj_queue = Queue()
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def as_handler(
|
|
47
|
+
cls,
|
|
48
|
+
handler_type: HandlerType,
|
|
49
|
+
rid_types: list[RIDType] | None = None
|
|
50
|
+
):
|
|
51
|
+
"""Special decorator that returns a Handler instead of a function."""
|
|
52
|
+
def decorator(func: Callable) -> KnowledgeHandler:
|
|
53
|
+
handler = KnowledgeHandler(func, handler_type, rid_types, )
|
|
54
|
+
return handler
|
|
55
|
+
return decorator
|
|
56
|
+
|
|
57
|
+
def register_handler(
|
|
58
|
+
self,
|
|
59
|
+
handler_type: HandlerType,
|
|
60
|
+
rid_types: list[RIDType] | None = None
|
|
61
|
+
):
|
|
62
|
+
"""Assigns decorated function as handler for this Processor."""
|
|
63
|
+
def decorator(func: Callable) -> Callable:
|
|
64
|
+
handler = KnowledgeHandler(func, handler_type, rid_types)
|
|
65
|
+
self.handlers.append(handler)
|
|
66
|
+
return func
|
|
67
|
+
return decorator
|
|
68
|
+
|
|
69
|
+
def call_handler_chain(
|
|
70
|
+
self,
|
|
71
|
+
handler_type: HandlerType,
|
|
72
|
+
kobj: KnowledgeObject
|
|
73
|
+
):
|
|
74
|
+
for handler in self.handlers:
|
|
75
|
+
if handler_type != handler.handler_type:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if handler.rid_types and type(kobj.rid) not in handler.rid_types:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
logger.info(f"Calling {handler_type} handler '{handler.func.__name__}'")
|
|
82
|
+
resp = handler.func(self, kobj.model_copy())
|
|
83
|
+
|
|
84
|
+
# stops handler chain execution
|
|
85
|
+
if resp is STOP_CHAIN:
|
|
86
|
+
logger.info(f"Handler chain stopped by {handler.func.__name__}")
|
|
87
|
+
return STOP_CHAIN
|
|
88
|
+
# kobj unmodified
|
|
89
|
+
elif resp is None:
|
|
90
|
+
continue
|
|
91
|
+
# kobj modified by handler
|
|
92
|
+
elif isinstance(resp, KnowledgeObject):
|
|
93
|
+
kobj = resp
|
|
94
|
+
logger.info(f"Knowledge object modified by {handler.func.__name__}")
|
|
95
|
+
else:
|
|
96
|
+
raise ValueError(f"Handler {handler.func.__name__} returned invalid response '{resp}'")
|
|
97
|
+
|
|
98
|
+
return kobj
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handle_kobj(self, kobj: KnowledgeObject):
|
|
102
|
+
logger.info(f"Handling {kobj!r}")
|
|
103
|
+
kobj = self.call_handler_chain(HandlerType.RID, kobj)
|
|
104
|
+
if kobj is STOP_CHAIN: return
|
|
105
|
+
|
|
106
|
+
if kobj.event_type == EventType.FORGET:
|
|
107
|
+
bundle = self.cache.read(kobj.rid)
|
|
108
|
+
if not bundle:
|
|
109
|
+
logger.info("Local bundle not found")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# the bundle (to be deleted) attached to kobj for downstream analysis
|
|
113
|
+
logger.info("Adding local bundle (to be deleted) to knowledge object")
|
|
114
|
+
kobj.manifest = bundle.manifest
|
|
115
|
+
kobj.contents = bundle.contents
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
# attempt to retrieve manifest
|
|
119
|
+
if not kobj.manifest:
|
|
120
|
+
if kobj.source == KnowledgeSource.External:
|
|
121
|
+
logger.info("Manifest not found, attempting to fetch remotely")
|
|
122
|
+
manifest = self.network.fetch_remote_manifest(kobj.rid)
|
|
123
|
+
if not manifest: return
|
|
124
|
+
|
|
125
|
+
elif kobj.source == KnowledgeSource.Internal:
|
|
126
|
+
logger.info("Manifest not found, attempting to read cache")
|
|
127
|
+
bundle = self.cache.read(kobj.rid)
|
|
128
|
+
if not bundle: return
|
|
129
|
+
manifest = bundle.manifest
|
|
130
|
+
|
|
131
|
+
kobj.manifest = manifest
|
|
132
|
+
|
|
133
|
+
kobj = self.call_handler_chain(HandlerType.Manifest, kobj)
|
|
134
|
+
if kobj is STOP_CHAIN: return
|
|
135
|
+
|
|
136
|
+
# attempt to retrieve bundle
|
|
137
|
+
if not kobj.bundle:
|
|
138
|
+
if kobj.source == KnowledgeSource.External:
|
|
139
|
+
logger.info("Bundle not found, attempting to fetch")
|
|
140
|
+
bundle = self.network.fetch_remote_bundle(kobj.rid)
|
|
141
|
+
# TODO: WARNING MANIFEST MAY BE DIFFERENT
|
|
142
|
+
|
|
143
|
+
elif kobj.source == KnowledgeSource.Internal:
|
|
144
|
+
logger.info("Bundle not found, attempting to read cache")
|
|
145
|
+
bundle = self.cache.read(kobj.rid)
|
|
146
|
+
|
|
147
|
+
if kobj.manifest != bundle.manifest:
|
|
148
|
+
logger.warning("Retrieved bundle contains a different manifest")
|
|
149
|
+
|
|
150
|
+
if not bundle: return
|
|
151
|
+
kobj.manifest = bundle.manifest
|
|
152
|
+
kobj.contents = bundle.contents
|
|
153
|
+
|
|
154
|
+
kobj = self.call_handler_chain(HandlerType.Bundle, kobj)
|
|
155
|
+
if kobj is STOP_CHAIN: return
|
|
156
|
+
|
|
157
|
+
if kobj.normalized_event_type in (EventType.UPDATE, EventType.NEW):
|
|
158
|
+
logger.info(f"Writing {kobj!r} to cache")
|
|
159
|
+
self.cache.write(kobj.bundle)
|
|
160
|
+
|
|
161
|
+
elif kobj.normalized_event_type == EventType.FORGET:
|
|
162
|
+
logger.info(f"Deleting {kobj!r} from cache")
|
|
163
|
+
self.cache.delete(kobj.rid)
|
|
164
|
+
|
|
165
|
+
else:
|
|
166
|
+
logger.info("Normalized event type was never set, no cache or network operations will occur")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
if type(kobj.rid) in (KoiNetNode, KoiNetEdge):
|
|
170
|
+
logger.info("Change to node or edge, regenerating network graph")
|
|
171
|
+
self.network.graph.generate()
|
|
172
|
+
|
|
173
|
+
kobj = self.call_handler_chain(HandlerType.Network, kobj)
|
|
174
|
+
if kobj is STOP_CHAIN: return
|
|
175
|
+
|
|
176
|
+
if kobj.network_targets:
|
|
177
|
+
logger.info(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
|
|
178
|
+
else:
|
|
179
|
+
logger.info("No network targets set")
|
|
180
|
+
|
|
181
|
+
for node in kobj.network_targets:
|
|
182
|
+
self.network.push_event_to(kobj.normalized_event, node)
|
|
183
|
+
self.network.flush_all_webhook_queues()
|
|
184
|
+
|
|
185
|
+
kobj = self.call_handler_chain(HandlerType.Final, kobj)
|
|
186
|
+
|
|
187
|
+
def queue_kobj(self, kobj: KnowledgeObject, flush: bool = False):
|
|
188
|
+
self.kobj_queue.put(kobj)
|
|
189
|
+
logger.info(f"Queued {kobj!r}")
|
|
190
|
+
|
|
191
|
+
if flush:
|
|
192
|
+
self.flush_kobj_queue()
|
|
193
|
+
|
|
194
|
+
def flush_kobj_queue(self):
|
|
195
|
+
while not self.kobj_queue.empty():
|
|
196
|
+
kobj = self.kobj_queue.get()
|
|
197
|
+
logger.info(f"Dequeued {kobj!r}")
|
|
198
|
+
self.handle_kobj(kobj)
|
|
199
|
+
logger.info("Done handling")
|
|
200
|
+
|
|
201
|
+
def handle(
|
|
202
|
+
self,
|
|
203
|
+
rid: RID | None = None,
|
|
204
|
+
manifest: Manifest | None = None,
|
|
205
|
+
bundle: Bundle | None = None,
|
|
206
|
+
event: Event | None = None,
|
|
207
|
+
event_type: KnowledgeEventType = None,
|
|
208
|
+
source: KnowledgeSource = KnowledgeSource.Internal,
|
|
209
|
+
flush: bool = False
|
|
210
|
+
):
|
|
211
|
+
if rid:
|
|
212
|
+
kobj = KnowledgeObject.from_rid(rid, event_type, source)
|
|
213
|
+
elif manifest:
|
|
214
|
+
kobj = KnowledgeObject.from_manifest(manifest, event_type, source)
|
|
215
|
+
elif bundle:
|
|
216
|
+
kobj = KnowledgeObject.from_bundle(bundle, event_type, source)
|
|
217
|
+
elif event:
|
|
218
|
+
kobj = KnowledgeObject.from_event(event, source)
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError("One of 'rid', 'manifest', 'bundle', or 'event' must be provided")
|
|
221
|
+
|
|
222
|
+
self.queue_kobj(kobj, flush)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RID
|
|
4
|
+
from rid_lib.ext import Manifest
|
|
5
|
+
from rid_lib.ext.bundle import Bundle
|
|
6
|
+
from rid_lib.types.koi_net_node import KoiNetNode
|
|
7
|
+
from ..protocol.event import Event, EventType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
type KnowledgeEventType = EventType | None
|
|
11
|
+
|
|
12
|
+
class KnowledgeSource(StrEnum):
|
|
13
|
+
Internal = "INTERNAL"
|
|
14
|
+
External = "EXTERNAL"
|
|
15
|
+
|
|
16
|
+
class KnowledgeObject(BaseModel):
|
|
17
|
+
rid: RID
|
|
18
|
+
manifest: Manifest | None = None
|
|
19
|
+
contents: dict | None = None
|
|
20
|
+
event_type: KnowledgeEventType = None
|
|
21
|
+
normalized_event_type: KnowledgeEventType = None
|
|
22
|
+
source: KnowledgeSource
|
|
23
|
+
network_targets: set[KoiNetNode] = set()
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
return f"<Knowledge Object '{self.rid}' ({self.event_type}) -> ({self.normalized_event_type})>"
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_rid(
|
|
30
|
+
cls,
|
|
31
|
+
rid: RID,
|
|
32
|
+
event_type: KnowledgeEventType = None,
|
|
33
|
+
source: KnowledgeSource = KnowledgeSource.Internal
|
|
34
|
+
) -> "KnowledgeObject":
|
|
35
|
+
return cls(
|
|
36
|
+
rid=rid,
|
|
37
|
+
event_type=event_type,
|
|
38
|
+
source=source
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_manifest(
|
|
43
|
+
cls,
|
|
44
|
+
manifest: Manifest,
|
|
45
|
+
event_type: KnowledgeEventType = None,
|
|
46
|
+
source: KnowledgeSource = KnowledgeSource.Internal
|
|
47
|
+
) -> "KnowledgeObject":
|
|
48
|
+
return cls(
|
|
49
|
+
rid=manifest.rid,
|
|
50
|
+
manifest=manifest,
|
|
51
|
+
event_type=event_type,
|
|
52
|
+
source=source
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_bundle(
|
|
57
|
+
cls,
|
|
58
|
+
bundle: Bundle,
|
|
59
|
+
event_type: KnowledgeEventType = None,
|
|
60
|
+
source: KnowledgeSource = KnowledgeSource.Internal
|
|
61
|
+
) -> "KnowledgeObject":
|
|
62
|
+
return cls(
|
|
63
|
+
rid=bundle.rid,
|
|
64
|
+
manifest=bundle.manifest,
|
|
65
|
+
contents=bundle.contents,
|
|
66
|
+
event_type=event_type,
|
|
67
|
+
source=source
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_event(
|
|
72
|
+
cls,
|
|
73
|
+
event: Event,
|
|
74
|
+
source: KnowledgeSource = KnowledgeSource.Internal
|
|
75
|
+
) -> "KnowledgeObject":
|
|
76
|
+
return cls(
|
|
77
|
+
rid=event.rid,
|
|
78
|
+
manifest=event.manifest,
|
|
79
|
+
contents=event.contents,
|
|
80
|
+
event_type=event.event_type,
|
|
81
|
+
source=source
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def bundle(self):
|
|
86
|
+
if self.manifest is None or self.contents is None:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
return Bundle(
|
|
90
|
+
manifest=self.manifest,
|
|
91
|
+
contents=self.contents
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def normalized_event(self):
|
|
96
|
+
if not self.normalized_event_type:
|
|
97
|
+
raise ValueError("Internal event's normalized event type is None, cannot convert to Event")
|
|
98
|
+
|
|
99
|
+
return Event(
|
|
100
|
+
rid=self.rid,
|
|
101
|
+
event_type=self.normalized_event_type,
|
|
102
|
+
manifest=self.manifest,
|
|
103
|
+
contents=self.contents
|
|
104
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from rid_lib import RID, RIDType
|
|
3
|
+
from rid_lib.ext import Bundle, Manifest
|
|
4
|
+
from .event import Event
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# REQUEST MODELS
|
|
8
|
+
|
|
9
|
+
class PollEvents(BaseModel):
|
|
10
|
+
rid: RID
|
|
11
|
+
limit: int = 0
|
|
12
|
+
|
|
13
|
+
class FetchRids(BaseModel):
|
|
14
|
+
rid_types: list[RIDType] = []
|
|
15
|
+
|
|
16
|
+
class FetchManifests(BaseModel):
|
|
17
|
+
rid_types: list[RIDType] = []
|
|
18
|
+
rids: list[RID] = []
|
|
19
|
+
|
|
20
|
+
class FetchBundles(BaseModel):
|
|
21
|
+
rids: list[RID]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# RESPONSE/PAYLOAD MODELS
|
|
25
|
+
|
|
26
|
+
class RidsPayload(BaseModel):
|
|
27
|
+
rids: list[RID]
|
|
28
|
+
|
|
29
|
+
class ManifestsPayload(BaseModel):
|
|
30
|
+
manifests: list[Manifest]
|
|
31
|
+
not_found: list[RID] = []
|
|
32
|
+
|
|
33
|
+
class BundlesPayload(BaseModel):
|
|
34
|
+
manifests: list[Bundle]
|
|
35
|
+
not_found: list[RID] = []
|
|
36
|
+
deferred: list[RID] = []
|
|
37
|
+
|
|
38
|
+
class EventsPayload(BaseModel):
|
|
39
|
+
events: list[Event]
|
koi_net/protocol/edge.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RIDType
|
|
4
|
+
from rid_lib.types import KoiNetNode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EdgeStatus(StrEnum):
|
|
8
|
+
PROPOSED = "PROPOSED"
|
|
9
|
+
APPROVED = "APPROVED"
|
|
10
|
+
|
|
11
|
+
class EdgeType(StrEnum):
|
|
12
|
+
WEBHOOK = "WEBHOOK"
|
|
13
|
+
POLL = "POLL"
|
|
14
|
+
|
|
15
|
+
class EdgeProfile(BaseModel):
|
|
16
|
+
source: KoiNetNode
|
|
17
|
+
target: KoiNetNode
|
|
18
|
+
edge_type: EdgeType
|
|
19
|
+
status: EdgeStatus
|
|
20
|
+
rid_types: list[RIDType]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RID
|
|
4
|
+
from rid_lib.ext import Manifest, Bundle
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EventType(StrEnum):
|
|
8
|
+
NEW = "NEW"
|
|
9
|
+
UPDATE = "UPDATE"
|
|
10
|
+
FORGET = "FORGET"
|
|
11
|
+
|
|
12
|
+
class Event(BaseModel):
|
|
13
|
+
rid: RID
|
|
14
|
+
event_type: EventType
|
|
15
|
+
manifest: Manifest | None = None
|
|
16
|
+
contents: dict | None = None
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_bundle(cls, event_type: EventType, bundle: Bundle):
|
|
20
|
+
return cls(
|
|
21
|
+
rid=bundle.manifest.rid,
|
|
22
|
+
event_type=event_type,
|
|
23
|
+
manifest=bundle.manifest,
|
|
24
|
+
contents=bundle.contents
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_manifest(cls, event_type: EventType, manifest: Manifest):
|
|
29
|
+
return cls(
|
|
30
|
+
rid=manifest.rid,
|
|
31
|
+
event_type=event_type,
|
|
32
|
+
manifest=manifest
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_rid(cls, event_type: EventType, rid: RID):
|
|
37
|
+
return cls(
|
|
38
|
+
rid=rid,
|
|
39
|
+
event_type=event_type
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def bundle(self):
|
|
44
|
+
if self.manifest is not None and self.contents is not None:
|
|
45
|
+
return Bundle(
|
|
46
|
+
manifest=self.manifest,
|
|
47
|
+
contents=self.contents
|
|
48
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from rid_lib.core import RIDType
|
|
2
|
+
from rid_lib.ext.bundle import Bundle
|
|
3
|
+
from rid_lib.types import KoiNetEdge
|
|
4
|
+
from rid_lib.types.koi_net_node import KoiNetNode
|
|
5
|
+
from .edge import EdgeProfile, EdgeStatus, EdgeType
|
|
6
|
+
|
|
7
|
+
def generate_edge_bundle(
|
|
8
|
+
source: KoiNetNode,
|
|
9
|
+
target: KoiNetNode,
|
|
10
|
+
rid_types: list[RIDType],
|
|
11
|
+
edge_type: EdgeType
|
|
12
|
+
) -> Bundle:
|
|
13
|
+
edge_rid = KoiNetEdge.generate(source, target)
|
|
14
|
+
edge_profile = EdgeProfile(
|
|
15
|
+
source=source,
|
|
16
|
+
target=target,
|
|
17
|
+
rid_types=rid_types,
|
|
18
|
+
edge_type=edge_type,
|
|
19
|
+
status=EdgeStatus.PROPOSED
|
|
20
|
+
)
|
|
21
|
+
edge_bundle = Bundle.generate(
|
|
22
|
+
edge_rid,
|
|
23
|
+
edge_profile.model_dump()
|
|
24
|
+
)
|
|
25
|
+
return edge_bundle
|
koi_net/protocol/node.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RIDType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NodeType(StrEnum):
|
|
7
|
+
FULL = "FULL"
|
|
8
|
+
PARTIAL = "PARTIAL"
|
|
9
|
+
|
|
10
|
+
class NodeProvides(BaseModel):
|
|
11
|
+
event: list[RIDType] = []
|
|
12
|
+
state: list[RIDType] = []
|
|
13
|
+
|
|
14
|
+
class NodeProfile(BaseModel):
|
|
15
|
+
base_url: str | None = None
|
|
16
|
+
node_type: NodeType
|
|
17
|
+
provides: NodeProvides = NodeProvides()
|