koi-net 1.0.0b1__py3-none-any.whl → 1.0.0b2__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 +24 -8
- koi_net/identity.py +8 -0
- koi_net/network/graph.py +11 -0
- koi_net/network/interface.py +38 -10
- koi_net/network/request_handler.py +24 -12
- koi_net/network/response_handler.py +2 -0
- koi_net/processor/default_handlers.py +19 -0
- koi_net/processor/handler.py +22 -7
- koi_net/processor/interface.py +39 -5
- koi_net/processor/knowledge_object.py +26 -7
- koi_net/protocol/api_models.py +2 -0
- koi_net/protocol/consts.py +2 -0
- koi_net-1.0.0b2.dist-info/METADATA +209 -0
- koi_net-1.0.0b2.dist-info/RECORD +24 -0
- koi_net-1.0.0b1.dist-info/METADATA +0 -43
- koi_net-1.0.0b1.dist-info/RECORD +0 -24
- {koi_net-1.0.0b1.dist-info → koi_net-1.0.0b2.dist-info}/WHEEL +0 -0
- {koi_net-1.0.0b1.dist-info → koi_net-1.0.0b2.dist-info}/licenses/LICENSE +0 -0
koi_net/core.py
CHANGED
|
@@ -3,7 +3,7 @@ import httpx
|
|
|
3
3
|
from rid_lib.ext import Cache, Bundle
|
|
4
4
|
from .network import NetworkInterface
|
|
5
5
|
from .processor import ProcessorInterface
|
|
6
|
-
from .processor import default_handlers
|
|
6
|
+
from .processor import default_handlers
|
|
7
7
|
from .processor.handler import KnowledgeHandler
|
|
8
8
|
from .identity import NodeIdentity
|
|
9
9
|
from .protocol.node import NodeProfile
|
|
@@ -12,13 +12,19 @@ from .protocol.event import Event, EventType
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
14
|
class NodeInterface:
|
|
15
|
+
cache: Cache
|
|
16
|
+
identity: NodeIdentity
|
|
17
|
+
network: NetworkInterface
|
|
18
|
+
processor: ProcessorInterface
|
|
19
|
+
first_contact: str
|
|
20
|
+
|
|
15
21
|
def __init__(
|
|
16
22
|
self,
|
|
17
23
|
name: str,
|
|
18
24
|
profile: NodeProfile,
|
|
19
25
|
identity_file_path: str = "identity.json",
|
|
20
26
|
first_contact: str | None = None,
|
|
21
|
-
|
|
27
|
+
handlers: list[KnowledgeHandler] | None = None,
|
|
22
28
|
cache: Cache | None = None,
|
|
23
29
|
network: NetworkInterface | None = None,
|
|
24
30
|
processor: ProcessorInterface | None = None
|
|
@@ -39,9 +45,9 @@ class NodeInterface:
|
|
|
39
45
|
)
|
|
40
46
|
|
|
41
47
|
# pull all handlers defined in default_handlers module
|
|
42
|
-
if not
|
|
43
|
-
|
|
44
|
-
obj for obj in vars(
|
|
48
|
+
if not handlers:
|
|
49
|
+
handlers = [
|
|
50
|
+
obj for obj in vars(default_handlers).values()
|
|
45
51
|
if isinstance(obj, KnowledgeHandler)
|
|
46
52
|
]
|
|
47
53
|
|
|
@@ -49,10 +55,16 @@ class NodeInterface:
|
|
|
49
55
|
cache=self.cache,
|
|
50
56
|
network=self.network,
|
|
51
57
|
identity=self.identity,
|
|
52
|
-
default_handlers=
|
|
58
|
+
default_handlers=handlers
|
|
53
59
|
)
|
|
54
60
|
|
|
55
|
-
def initialize(self):
|
|
61
|
+
def initialize(self) -> None:
|
|
62
|
+
"""Initializes node, call on startup.
|
|
63
|
+
|
|
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.
|
|
65
|
+
"""
|
|
66
|
+
self.network._load_event_queues()
|
|
67
|
+
|
|
56
68
|
self.network.graph.generate()
|
|
57
69
|
|
|
58
70
|
self.processor.handle(
|
|
@@ -83,4 +95,8 @@ class NodeInterface:
|
|
|
83
95
|
|
|
84
96
|
|
|
85
97
|
def finalize(self):
|
|
86
|
-
|
|
98
|
+
"""Finalizes node, call on shutdown.
|
|
99
|
+
|
|
100
|
+
Saves event queues to storage.
|
|
101
|
+
"""
|
|
102
|
+
self.network._save_event_queues()
|
koi_net/identity.py
CHANGED
|
@@ -13,6 +13,8 @@ class NodeIdentityModel(BaseModel):
|
|
|
13
13
|
profile: NodeProfile
|
|
14
14
|
|
|
15
15
|
class NodeIdentity:
|
|
16
|
+
"""Represents a node's identity (RID, profile, bundle)."""
|
|
17
|
+
|
|
16
18
|
_identity: NodeIdentityModel
|
|
17
19
|
file_path: str
|
|
18
20
|
cache: Cache
|
|
@@ -24,6 +26,12 @@ class NodeIdentity:
|
|
|
24
26
|
cache: Cache,
|
|
25
27
|
file_path: str = "identity.json"
|
|
26
28
|
):
|
|
29
|
+
"""Initializes node identity from a name and profile.
|
|
30
|
+
|
|
31
|
+
Attempts to read identity from storage. If it doesn't already exist, a new RID is generated from the provided name, and that RID and profile are written to storage. Changes to the name or profile will update the stored identity.
|
|
32
|
+
|
|
33
|
+
WARNING: If the name is changed, the RID will be overwritten which will have consequences for the rest of the network.
|
|
34
|
+
"""
|
|
27
35
|
self.cache = cache
|
|
28
36
|
self.file_path = file_path
|
|
29
37
|
|
koi_net/network/graph.py
CHANGED
|
@@ -12,12 +12,15 @@ logger = logging.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class NetworkGraph:
|
|
15
|
+
"""Graph functions for this node's view of its network."""
|
|
16
|
+
|
|
15
17
|
def __init__(self, cache: Cache, identity: NodeIdentity):
|
|
16
18
|
self.cache = cache
|
|
17
19
|
self.dg = nx.DiGraph()
|
|
18
20
|
self.identity = identity
|
|
19
21
|
|
|
20
22
|
def generate(self):
|
|
23
|
+
"""Generates directed graph from cached KOI nodes and edges."""
|
|
21
24
|
logger.info("Generating network graph")
|
|
22
25
|
self.dg.clear()
|
|
23
26
|
for rid in self.cache.list_rids():
|
|
@@ -35,6 +38,7 @@ class NetworkGraph:
|
|
|
35
38
|
logger.info("Done")
|
|
36
39
|
|
|
37
40
|
def get_node_profile(self, rid: KoiNetNode) -> NodeProfile | None:
|
|
41
|
+
"""Returns node profile given its RID."""
|
|
38
42
|
bundle = self.cache.read(rid)
|
|
39
43
|
if bundle:
|
|
40
44
|
return bundle.validate_contents(NodeProfile)
|
|
@@ -45,6 +49,7 @@ class NetworkGraph:
|
|
|
45
49
|
source: KoiNetNode | None = None,
|
|
46
50
|
target: KoiNetNode | None = None,
|
|
47
51
|
) -> EdgeProfile | None:
|
|
52
|
+
"""Returns edge profile given its RID, or source and target node RIDs."""
|
|
48
53
|
if source and target:
|
|
49
54
|
if (source, target) not in self.dg.edges: return
|
|
50
55
|
edge_data = self.dg.get_edge_data(source, target)
|
|
@@ -62,6 +67,9 @@ class NetworkGraph:
|
|
|
62
67
|
self,
|
|
63
68
|
direction: Literal["in", "out"] | None = None,
|
|
64
69
|
) -> list[KoiNetEdge]:
|
|
70
|
+
"""Returns edges this node belongs to.
|
|
71
|
+
|
|
72
|
+
All edges returned by default, specify `direction` to restrict to incoming or outgoing edges only."""
|
|
65
73
|
|
|
66
74
|
edges = []
|
|
67
75
|
if direction != "in":
|
|
@@ -88,6 +96,9 @@ class NetworkGraph:
|
|
|
88
96
|
status: EdgeStatus | None = None,
|
|
89
97
|
allowed_type: RIDType | None = None
|
|
90
98
|
) -> list[KoiNetNode]:
|
|
99
|
+
"""Returns neighboring nodes this node shares an edge with.
|
|
100
|
+
|
|
101
|
+
All neighboring nodes returned by default, specify `direction` to restrict to neighbors connected by incoming or outgoing edges only."""
|
|
91
102
|
|
|
92
103
|
neighbors = []
|
|
93
104
|
for edge_rid in self.get_edges(direction):
|
koi_net/network/interface.py
CHANGED
|
@@ -24,6 +24,8 @@ class EventQueueModel(BaseModel):
|
|
|
24
24
|
type EventQueue = dict[RID, Queue[Event]]
|
|
25
25
|
|
|
26
26
|
class NetworkInterface:
|
|
27
|
+
"""A collection of functions and classes to interact with the KOI network."""
|
|
28
|
+
|
|
27
29
|
identity: NodeIdentity
|
|
28
30
|
cache: Cache
|
|
29
31
|
first_contact: str | None
|
|
@@ -45,15 +47,16 @@ class NetworkInterface:
|
|
|
45
47
|
self.cache = cache
|
|
46
48
|
self.first_contact = first_contact
|
|
47
49
|
self.graph = NetworkGraph(cache, identity)
|
|
48
|
-
self.request_handler = RequestHandler(cache)
|
|
50
|
+
self.request_handler = RequestHandler(cache, self.graph)
|
|
49
51
|
self.response_handler = ResponseHandler(cache)
|
|
50
52
|
self.event_queues_file_path = file_path
|
|
51
53
|
|
|
52
54
|
self.poll_event_queue = dict()
|
|
53
55
|
self.webhook_event_queue = dict()
|
|
54
|
-
self.
|
|
56
|
+
self._load_event_queues()
|
|
55
57
|
|
|
56
|
-
def
|
|
58
|
+
def _load_event_queues(self):
|
|
59
|
+
"""Loads event queues from storage."""
|
|
57
60
|
try:
|
|
58
61
|
with open(self.event_queues_file_path, "r") as f:
|
|
59
62
|
queues = EventQueueModel.model_validate_json(f.read())
|
|
@@ -71,7 +74,8 @@ class NetworkInterface:
|
|
|
71
74
|
except FileNotFoundError:
|
|
72
75
|
return
|
|
73
76
|
|
|
74
|
-
def
|
|
77
|
+
def _save_event_queues(self):
|
|
78
|
+
"""Writes event queues to storage."""
|
|
75
79
|
events_model = EventQueueModel(
|
|
76
80
|
poll={
|
|
77
81
|
node: list(queue.queue)
|
|
@@ -92,6 +96,10 @@ class NetworkInterface:
|
|
|
92
96
|
f.write(events_model.model_dump_json(indent=2))
|
|
93
97
|
|
|
94
98
|
def push_event_to(self, event: Event, node: KoiNetNode, flush=False):
|
|
99
|
+
"""Pushes event to queue of specified node.
|
|
100
|
+
|
|
101
|
+
Event will be sent to webhook or poll queue depending on the node type and edge type of the specified node. If `flush` is set to `True`, the webhook queued will be flushed after pushing the event.
|
|
102
|
+
"""
|
|
95
103
|
logger.info(f"Pushing event {event.event_type} {event.rid} to {node}")
|
|
96
104
|
|
|
97
105
|
node_profile = self.graph.get_node_profile(node)
|
|
@@ -121,7 +129,8 @@ class NetworkInterface:
|
|
|
121
129
|
if flush and event_queue is self.webhook_event_queue:
|
|
122
130
|
self.flush_webhook_queue(node)
|
|
123
131
|
|
|
124
|
-
def
|
|
132
|
+
def _flush_queue(self, event_queue: EventQueue, node: KoiNetNode) -> list[Event]:
|
|
133
|
+
"""Flushes a node's queue, returning list of events."""
|
|
125
134
|
queue = event_queue.get(node)
|
|
126
135
|
events = list()
|
|
127
136
|
if queue:
|
|
@@ -133,22 +142,29 @@ class NetworkInterface:
|
|
|
133
142
|
return events
|
|
134
143
|
|
|
135
144
|
def flush_poll_queue(self, node: KoiNetNode) -> list[Event]:
|
|
145
|
+
"""Flushes a node's poll queue, returning list of events."""
|
|
136
146
|
logger.info(f"Flushing poll queue for {node}")
|
|
137
|
-
return self.
|
|
147
|
+
return self._flush_queue(self.poll_event_queue, node)
|
|
138
148
|
|
|
139
|
-
def flush_webhook_queue(self, node:
|
|
149
|
+
def flush_webhook_queue(self, node: KoiNetNode):
|
|
150
|
+
"""Flushes a node's webhook queue, and broadcasts events.
|
|
151
|
+
|
|
152
|
+
If node profile is unknown, or node type is not `FULL`, this operation will fail silently. If the remote node cannot be reached, all events will be requeued.
|
|
153
|
+
"""
|
|
154
|
+
|
|
140
155
|
logger.info(f"Flushing webhook queue for {node}")
|
|
141
156
|
|
|
142
157
|
node_profile = self.graph.get_node_profile(node)
|
|
143
158
|
|
|
144
159
|
if not node_profile:
|
|
145
160
|
logger.warning(f"{node!r} not found")
|
|
161
|
+
return
|
|
146
162
|
|
|
147
163
|
if node_profile.node_type != NodeType.FULL:
|
|
148
164
|
logger.warning(f"{node!r} is a partial node!")
|
|
149
165
|
return
|
|
150
166
|
|
|
151
|
-
events = self.
|
|
167
|
+
events = self._flush_queue(self.webhook_event_queue, node)
|
|
152
168
|
logger.info(f"Broadcasting {len(events)} events")
|
|
153
169
|
|
|
154
170
|
try:
|
|
@@ -159,10 +175,13 @@ class NetworkInterface:
|
|
|
159
175
|
self.push_event_to(event, node)
|
|
160
176
|
|
|
161
177
|
def flush_all_webhook_queues(self):
|
|
178
|
+
"""Flushes all nodes' webhook queues and broadcasts events."""
|
|
162
179
|
for node in self.webhook_event_queue.keys():
|
|
163
180
|
self.flush_webhook_queue(node)
|
|
164
181
|
|
|
165
|
-
def get_state_providers(self, rid_type: RIDType):
|
|
182
|
+
def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
|
|
183
|
+
"""Returns list of node RIDs which provide state for the specified RID type."""
|
|
184
|
+
|
|
166
185
|
logger.info(f"Looking for state providers of '{rid_type}'")
|
|
167
186
|
provider_nodes = []
|
|
168
187
|
for node_rid in self.cache.list_rids(rid_types=[KoiNetNode]):
|
|
@@ -177,6 +196,8 @@ class NetworkInterface:
|
|
|
177
196
|
return provider_nodes
|
|
178
197
|
|
|
179
198
|
def fetch_remote_bundle(self, rid: RID):
|
|
199
|
+
"""Attempts to fetch a bundle by RID from known peer nodes."""
|
|
200
|
+
|
|
180
201
|
logger.info(f"Fetching remote bundle '{rid}'")
|
|
181
202
|
remote_bundle = None
|
|
182
203
|
for node_rid in self.get_state_providers(type(rid)):
|
|
@@ -194,6 +215,8 @@ class NetworkInterface:
|
|
|
194
215
|
return remote_bundle
|
|
195
216
|
|
|
196
217
|
def fetch_remote_manifest(self, rid: RID):
|
|
218
|
+
"""Attempts to fetch a manifest by RID from known peer nodes."""
|
|
219
|
+
|
|
197
220
|
logger.info(f"Fetching remote manifest '{rid}'")
|
|
198
221
|
remote_manifest = None
|
|
199
222
|
for node_rid in self.get_state_providers(type(rid)):
|
|
@@ -211,9 +234,14 @@ class NetworkInterface:
|
|
|
211
234
|
return remote_manifest
|
|
212
235
|
|
|
213
236
|
def poll_neighbors(self) -> list[Event]:
|
|
237
|
+
"""Polls all neighboring nodes and returns compiled list of events.
|
|
238
|
+
|
|
239
|
+
If this node has no neighbors, it will instead attempt to poll the provided first contact URL.
|
|
240
|
+
"""
|
|
241
|
+
|
|
214
242
|
neighbors = self.graph.get_neighbors()
|
|
215
243
|
|
|
216
|
-
if not neighbors:
|
|
244
|
+
if not neighbors and self.first_contact:
|
|
217
245
|
logger.info("No neighbors found, polling first contact")
|
|
218
246
|
try:
|
|
219
247
|
payload = self.request_handler.poll_events(
|
|
@@ -21,17 +21,22 @@ from ..protocol.consts import (
|
|
|
21
21
|
FETCH_MANIFESTS_PATH,
|
|
22
22
|
FETCH_BUNDLES_PATH
|
|
23
23
|
)
|
|
24
|
-
from ..protocol.node import
|
|
24
|
+
from ..protocol.node import NodeType
|
|
25
|
+
from .graph import NetworkGraph
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class RequestHandler:
|
|
32
|
+
"""Handles making requests to other KOI nodes."""
|
|
33
|
+
|
|
31
34
|
cache: Cache
|
|
35
|
+
graph: NetworkGraph
|
|
32
36
|
|
|
33
|
-
def __init__(self, cache: Cache):
|
|
37
|
+
def __init__(self, cache: Cache, graph: NetworkGraph):
|
|
34
38
|
self.cache = cache
|
|
39
|
+
self.graph = graph
|
|
35
40
|
|
|
36
41
|
def make_request(self, url, request: BaseModel) -> httpx.Response:
|
|
37
42
|
logger.info(f"Making request to {url}")
|
|
@@ -42,23 +47,26 @@ class RequestHandler:
|
|
|
42
47
|
return resp
|
|
43
48
|
|
|
44
49
|
def get_url(self, node_rid: KoiNetNode, url: str) -> str:
|
|
50
|
+
"""Retrieves URL of a node, or returns provided URL."""
|
|
51
|
+
|
|
45
52
|
if not node_rid and not url:
|
|
46
53
|
raise ValueError("One of 'node_rid' and 'url' must be provided")
|
|
47
54
|
|
|
48
55
|
if node_rid:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if
|
|
56
|
+
node_profile = self.graph.get_node_profile(node_rid)
|
|
57
|
+
if not node_profile:
|
|
58
|
+
raise Exception("Node not found")
|
|
59
|
+
if node_profile.node_type != NodeType.FULL:
|
|
53
60
|
raise Exception("Can't query partial node")
|
|
54
|
-
logger.info(f"Resolved {node_rid!r} to {
|
|
55
|
-
return
|
|
61
|
+
logger.info(f"Resolved {node_rid!r} to {node_profile.base_url}")
|
|
62
|
+
return node_profile.base_url
|
|
56
63
|
else:
|
|
57
64
|
return url
|
|
58
65
|
|
|
59
66
|
def broadcast_events(
|
|
60
67
|
self, node: RID = None, url: str = None, **kwargs
|
|
61
68
|
) -> None:
|
|
69
|
+
"""See protocol.api_models.EventsPayload for available kwargs."""
|
|
62
70
|
self.make_request(
|
|
63
71
|
self.get_url(node, url) + BROADCAST_EVENTS_PATH,
|
|
64
72
|
EventsPayload.model_validate(kwargs)
|
|
@@ -66,7 +74,8 @@ class RequestHandler:
|
|
|
66
74
|
|
|
67
75
|
def poll_events(
|
|
68
76
|
self, node: RID = None, url: str = None, **kwargs
|
|
69
|
-
) -> EventsPayload:
|
|
77
|
+
) -> EventsPayload:
|
|
78
|
+
"""See protocol.api_models.PollEvents for available kwargs."""
|
|
70
79
|
resp = self.make_request(
|
|
71
80
|
self.get_url(node, url) + POLL_EVENTS_PATH,
|
|
72
81
|
PollEvents.model_validate(kwargs)
|
|
@@ -76,7 +85,8 @@ class RequestHandler:
|
|
|
76
85
|
|
|
77
86
|
def fetch_rids(
|
|
78
87
|
self, node: RID = None, url: str = None, **kwargs
|
|
79
|
-
) -> RidsPayload:
|
|
88
|
+
) -> RidsPayload:
|
|
89
|
+
"""See protocol.api_models.FetchRids for available kwargs."""
|
|
80
90
|
resp = self.make_request(
|
|
81
91
|
self.get_url(node, url) + FETCH_RIDS_PATH,
|
|
82
92
|
FetchRids.model_validate(kwargs)
|
|
@@ -86,7 +96,8 @@ class RequestHandler:
|
|
|
86
96
|
|
|
87
97
|
def fetch_manifests(
|
|
88
98
|
self, node: RID = None, url: str = None, **kwargs
|
|
89
|
-
) -> ManifestsPayload:
|
|
99
|
+
) -> ManifestsPayload:
|
|
100
|
+
"""See protocol.api_models.FetchManifests for available kwargs."""
|
|
90
101
|
resp = self.make_request(
|
|
91
102
|
self.get_url(node, url) + FETCH_MANIFESTS_PATH,
|
|
92
103
|
FetchManifests.model_validate(kwargs)
|
|
@@ -96,7 +107,8 @@ class RequestHandler:
|
|
|
96
107
|
|
|
97
108
|
def fetch_bundles(
|
|
98
109
|
self, node: RID = None, url: str = None, **kwargs
|
|
99
|
-
) -> BundlesPayload:
|
|
110
|
+
) -> BundlesPayload:
|
|
111
|
+
"""See protocol.api_models.FetchBundles for available kwargs."""
|
|
100
112
|
resp = self.make_request(
|
|
101
113
|
self.get_url(node, url) + FETCH_BUNDLES_PATH,
|
|
102
114
|
FetchBundles.model_validate(kwargs)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Provides implementations of default knowledge handlers."""
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
4
|
from rid_lib.ext.bundle import Bundle
|
|
3
5
|
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
@@ -14,6 +16,10 @@ logger = logging.getLogger(__name__)
|
|
|
14
16
|
|
|
15
17
|
@ProcessorInterface.as_handler(handler_type=HandlerType.RID)
|
|
16
18
|
def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
19
|
+
"""Default RID handler.
|
|
20
|
+
|
|
21
|
+
Blocks external events about this node. Allows `FORGET` events if RID is known to this node.
|
|
22
|
+
"""
|
|
17
23
|
if (kobj.rid == processor.identity.rid and
|
|
18
24
|
kobj.source == KnowledgeSource.External):
|
|
19
25
|
logger.info("Don't let anyone else tell me who I am!")
|
|
@@ -34,6 +40,10 @@ def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
|
34
40
|
|
|
35
41
|
@ProcessorInterface.as_handler(handler_type=HandlerType.Manifest)
|
|
36
42
|
def basic_state_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
43
|
+
"""Default manifest handler.
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
"""
|
|
37
47
|
prev_bundle = processor.cache.read(kobj.rid)
|
|
38
48
|
|
|
39
49
|
if prev_bundle:
|
|
@@ -58,6 +68,11 @@ def basic_state_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
|
58
68
|
|
|
59
69
|
@ProcessorInterface.as_handler(HandlerType.Bundle, rid_types=[KoiNetEdge])
|
|
60
70
|
def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
71
|
+
"""Handles basic edge negotiation process.
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
"""
|
|
75
|
+
|
|
61
76
|
edge_profile = EdgeProfile.model_validate(kobj.contents)
|
|
62
77
|
|
|
63
78
|
# only want to handle external knowledge events (not edges this node created)
|
|
@@ -118,6 +133,10 @@ def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObjec
|
|
|
118
133
|
|
|
119
134
|
@ProcessorInterface.as_handler(HandlerType.Network)
|
|
120
135
|
def basic_network_output_filter(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
136
|
+
"""Default network handler.
|
|
137
|
+
|
|
138
|
+
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)."""
|
|
139
|
+
|
|
121
140
|
involves_me = False
|
|
122
141
|
if kobj.source == KnowledgeSource.Internal:
|
|
123
142
|
if (type(kobj.rid) == KoiNetNode):
|
koi_net/processor/handler.py
CHANGED
|
@@ -4,18 +4,33 @@ from typing import Callable
|
|
|
4
4
|
from rid_lib import RIDType
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
class StopChain:
|
|
8
|
+
"""Class for a sentinel value by knowledge handlers."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
STOP_CHAIN = StopChain()
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
class HandlerType(StrEnum):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
"""Types of handlers used in knowledge processing pipeline.
|
|
16
|
+
|
|
17
|
+
- 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).
|
|
18
|
+
- Manifest - provided RID, manifest; decides whether to validate the bundle (and fetch it if not provided).
|
|
19
|
+
- Bundle - provided RID, manifest, contents (bundle); decides whether to write knowledge to the cache by setting the normalized event type to `NEW` or `UPDATE`.
|
|
20
|
+
- 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.)
|
|
21
|
+
- Final - provided RID, manifests, contents (bundle); final action taken after network broadcast.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
RID = "rid",
|
|
25
|
+
Manifest = "manifest",
|
|
26
|
+
Bundle = "bundle",
|
|
27
|
+
Network = "network",
|
|
28
|
+
Final = "final"
|
|
16
29
|
|
|
17
30
|
@dataclass
|
|
18
31
|
class KnowledgeHandler:
|
|
32
|
+
"""Handles knowledge processing events of the provided types."""
|
|
33
|
+
|
|
19
34
|
func: Callable
|
|
20
35
|
handler_type: HandlerType
|
|
21
36
|
rid_types: list[RIDType] | None
|
koi_net/processor/interface.py
CHANGED
|
@@ -11,7 +11,8 @@ from ..protocol.event import Event, EventType
|
|
|
11
11
|
from .handler import (
|
|
12
12
|
KnowledgeHandler,
|
|
13
13
|
HandlerType,
|
|
14
|
-
STOP_CHAIN
|
|
14
|
+
STOP_CHAIN,
|
|
15
|
+
StopChain
|
|
15
16
|
)
|
|
16
17
|
from .knowledge_object import (
|
|
17
18
|
KnowledgeObject,
|
|
@@ -23,6 +24,8 @@ logger = logging.getLogger(__name__)
|
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class ProcessorInterface:
|
|
27
|
+
"""Provides access to this node's knowledge processing pipeline."""
|
|
28
|
+
|
|
26
29
|
cache: Cache
|
|
27
30
|
network: NetworkInterface
|
|
28
31
|
identity: NodeIdentity
|
|
@@ -48,7 +51,10 @@ class ProcessorInterface:
|
|
|
48
51
|
handler_type: HandlerType,
|
|
49
52
|
rid_types: list[RIDType] | None = None
|
|
50
53
|
):
|
|
51
|
-
"""Special decorator that returns a
|
|
54
|
+
"""Special decorator that returns a handler instead of a function.
|
|
55
|
+
|
|
56
|
+
The function symbol will redefined as a `KnowledgeHandler`, which can be passed into the `ProcessorInterface` constructor. This is used to register the default handlers.
|
|
57
|
+
"""
|
|
52
58
|
def decorator(func: Callable) -> KnowledgeHandler:
|
|
53
59
|
handler = KnowledgeHandler(func, handler_type, rid_types, )
|
|
54
60
|
return handler
|
|
@@ -59,7 +65,7 @@ class ProcessorInterface:
|
|
|
59
65
|
handler_type: HandlerType,
|
|
60
66
|
rid_types: list[RIDType] | None = None
|
|
61
67
|
):
|
|
62
|
-
"""Assigns decorated function as handler for this
|
|
68
|
+
"""Assigns decorated function as handler for this processor."""
|
|
63
69
|
def decorator(func: Callable) -> Callable:
|
|
64
70
|
handler = KnowledgeHandler(func, handler_type, rid_types)
|
|
65
71
|
self.handlers.append(handler)
|
|
@@ -70,7 +76,17 @@ class ProcessorInterface:
|
|
|
70
76
|
self,
|
|
71
77
|
handler_type: HandlerType,
|
|
72
78
|
kobj: KnowledgeObject
|
|
73
|
-
):
|
|
79
|
+
) -> KnowledgeObject | StopChain:
|
|
80
|
+
"""Calls handlers of provided type, chaining their inputs and outputs together.
|
|
81
|
+
|
|
82
|
+
The knowledge object provided when this function is called will be passed to the first handler. A handler may return one of three types:
|
|
83
|
+
- `KnowledgeObject` - to modify the knowledge object for the next handler in the chain
|
|
84
|
+
- `None` - to keep the same knowledge object for the next handler in the chain
|
|
85
|
+
- `STOP_CHAIN` - to stop the handler chain and immediately exit the processing pipeline
|
|
86
|
+
|
|
87
|
+
Handlers will only be called in the chain if their handler and RID type match that of the inputted knowledge object.
|
|
88
|
+
"""
|
|
89
|
+
|
|
74
90
|
for handler in self.handlers:
|
|
75
91
|
if handler_type != handler.handler_type:
|
|
76
92
|
continue
|
|
@@ -98,7 +114,19 @@ class ProcessorInterface:
|
|
|
98
114
|
return kobj
|
|
99
115
|
|
|
100
116
|
|
|
101
|
-
def handle_kobj(self, kobj: KnowledgeObject):
|
|
117
|
+
def handle_kobj(self, kobj: KnowledgeObject) -> None:
|
|
118
|
+
"""Sends provided knowledge obejct through knowledge processing pipeline.
|
|
119
|
+
|
|
120
|
+
Handler chains are called in between major events in the pipeline, indicated by their handler type. Each handler type is guaranteed to have access to certain knowledge, and may affect a subsequent action in the pipeline. The five handler types are as follows:
|
|
121
|
+
- 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).
|
|
122
|
+
- Manifest - provided RID, manifest; decides whether to validate the bundle (and fetch it if not provided).
|
|
123
|
+
- Bundle - provided RID, manifest, contents (bundle); decides whether to write knowledge to the cache by setting the normalized event type to `NEW` or `UPDATE`.
|
|
124
|
+
- 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.)
|
|
125
|
+
- Final - provided RID, manifests, contents (bundle); final action taken after network broadcast.
|
|
126
|
+
|
|
127
|
+
The pipeline may be stopped by any point by a single handler returning the `STOP_CHAIN` sentinel. In that case, the process will exit immediately. Further handlers of that type and later handler chains will not be called.
|
|
128
|
+
"""
|
|
129
|
+
|
|
102
130
|
logger.info(f"Handling {kobj!r}")
|
|
103
131
|
kobj = self.call_handler_chain(HandlerType.RID, kobj)
|
|
104
132
|
if kobj is STOP_CHAIN: return
|
|
@@ -185,6 +213,7 @@ class ProcessorInterface:
|
|
|
185
213
|
kobj = self.call_handler_chain(HandlerType.Final, kobj)
|
|
186
214
|
|
|
187
215
|
def queue_kobj(self, kobj: KnowledgeObject, flush: bool = False):
|
|
216
|
+
"""Queues a knowledge object to be put processed in the pipeline."""
|
|
188
217
|
self.kobj_queue.put(kobj)
|
|
189
218
|
logger.info(f"Queued {kobj!r}")
|
|
190
219
|
|
|
@@ -192,6 +221,7 @@ class ProcessorInterface:
|
|
|
192
221
|
self.flush_kobj_queue()
|
|
193
222
|
|
|
194
223
|
def flush_kobj_queue(self):
|
|
224
|
+
"""Flushes all knowledge objects from queue and processes them."""
|
|
195
225
|
while not self.kobj_queue.empty():
|
|
196
226
|
kobj = self.kobj_queue.get()
|
|
197
227
|
logger.info(f"Dequeued {kobj!r}")
|
|
@@ -208,6 +238,10 @@ class ProcessorInterface:
|
|
|
208
238
|
source: KnowledgeSource = KnowledgeSource.Internal,
|
|
209
239
|
flush: bool = False
|
|
210
240
|
):
|
|
241
|
+
"""Queues provided knowledge to be handled by processing pipeline.
|
|
242
|
+
|
|
243
|
+
Knowledge may take the form of an RID, manifest, bundle, or event (with an optional event type for non event objects). All objects will be normalized into knowledge objects and queued. If `flush` is `True`, the queue will be flushed immediately after adding the new knowledge.
|
|
244
|
+
"""
|
|
211
245
|
if rid:
|
|
212
246
|
kobj = KnowledgeObject.from_rid(rid, event_type, source)
|
|
213
247
|
elif manifest:
|
|
@@ -14,6 +14,18 @@ class KnowledgeSource(StrEnum):
|
|
|
14
14
|
External = "EXTERNAL"
|
|
15
15
|
|
|
16
16
|
class KnowledgeObject(BaseModel):
|
|
17
|
+
"""A normalized knowledge representation for internal processing.
|
|
18
|
+
|
|
19
|
+
Capable of representing an RID, manifest, bundle, or event. Contains three additional fields use for decision making in the knowledge processing pipeline.
|
|
20
|
+
|
|
21
|
+
The source indicates whether this object was generated by this node, or sourced from another node in the network.
|
|
22
|
+
|
|
23
|
+
The normalized event type indicates how the knowledge object is viewed from the perspective of this node, and what cache actions will take place. `NEW`, `UPDATE` -> cache write, `FORGET` -> cache delete, `None` -> no cache action.
|
|
24
|
+
|
|
25
|
+
The network targets indicate other nodes in the network this knowledge object will be sent to. The event sent to them will be constructed from this knowledge object's RID, manifest, contents, and normalized event type.
|
|
26
|
+
|
|
27
|
+
Constructors are provided to create a knowledge object from an RID, manifest, bundle, or event.
|
|
28
|
+
"""
|
|
17
29
|
rid: RID
|
|
18
30
|
manifest: Manifest | None = None
|
|
19
31
|
contents: dict | None = None
|
|
@@ -93,12 +105,19 @@ class KnowledgeObject(BaseModel):
|
|
|
93
105
|
|
|
94
106
|
@property
|
|
95
107
|
def normalized_event(self):
|
|
96
|
-
if
|
|
108
|
+
if self.normalized_event_type is None:
|
|
97
109
|
raise ValueError("Internal event's normalized event type is None, cannot convert to Event")
|
|
98
110
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
elif self.normalized_event_type == EventType.FORGET:
|
|
112
|
+
return Event(
|
|
113
|
+
rid=self.rid,
|
|
114
|
+
event_type=EventType.FORGET
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
return Event(
|
|
119
|
+
rid=self.rid,
|
|
120
|
+
event_type=self.normalized_event_type,
|
|
121
|
+
manifest=self.manifest,
|
|
122
|
+
contents=self.contents
|
|
123
|
+
)
|
koi_net/protocol/api_models.py
CHANGED
koi_net/protocol/consts.py
CHANGED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: koi-net
|
|
3
|
+
Version: 1.0.0b2
|
|
4
|
+
Summary: Implementation of KOI-net protocol in Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/BlockScience/koi-net/
|
|
6
|
+
Author-email: Luke Miller <luke@block.science>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 BlockScience
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Requires-Dist: httpx>=0.28.1
|
|
31
|
+
Requires-Dist: networkx>=3.4.2
|
|
32
|
+
Requires-Dist: pydantic>=2.10.6
|
|
33
|
+
Requires-Dist: rid-lib>=3.2.1
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=6.0; extra == 'dev'
|
|
37
|
+
Provides-Extra: examples
|
|
38
|
+
Requires-Dist: fastapi; extra == 'examples'
|
|
39
|
+
Requires-Dist: rich; extra == 'examples'
|
|
40
|
+
Requires-Dist: uvicorn; extra == 'examples'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# KOI-net
|
|
44
|
+
|
|
45
|
+
*This specification is the result of several iterations of KOI research, [read more here](https://github.com/BlockScience/koi).*
|
|
46
|
+
|
|
47
|
+
# Protocol
|
|
48
|
+
## Introduction
|
|
49
|
+
|
|
50
|
+
*This project builds upon and uses the [RID protocol](https://github.com/BlockScience/rid-lib) to identify and coordinate around knowledge objects.*
|
|
51
|
+
|
|
52
|
+
This protocol defines the standard communication patterns and coordination norms needed to establish and maintain Knowledge Organization Infrastructure (KOI) networks. KOI-nets are heterogenous compositions of KOI nodes, each of which is capable of autonomously inputting, processing, and outputting knowledge. The behavior of each node and configuration of each network can vary greatly, thus the protocol is designed to be a simple and flexible but interoperable foundation for future projects to build on. The protocol only governs communication between nodes, not how they operate internally. As a result we consider KOI-nets to be fractal-like, in that a network of nodes may act like a single node from an outside perspective.
|
|
53
|
+
|
|
54
|
+
Generated OpenAPI documentation is provided in this repository, and can be [viewed interactively with Swagger](https://generator.swagger.io/?url=https://raw.githubusercontent.com/BlockScience/koi/refs/heads/main/koi_v3_node_api.yaml).
|
|
55
|
+
|
|
56
|
+
## Communication Methods
|
|
57
|
+
|
|
58
|
+
There are two classes of communication methods, event and state communication.
|
|
59
|
+
- Event communication is one way, a node send an event to another node.
|
|
60
|
+
- State communication is two way, a node asks another node for RIDs, manifests, or bundles and receives a response containing the requested resource (if available).
|
|
61
|
+
|
|
62
|
+
There are also two types of nodes, full and partial nodes.
|
|
63
|
+
- Full nodes are web servers, implementing the endpoints defined in the KOi-net protocol. They are capable of receiving events via webhooks (another node calls their endpoint), and serving state queries. They can also call the endpoints of other full nodes to broadcast events or retrieve state.
|
|
64
|
+
- Partial nodes are web clients and don't implement any API endpoints. They are capable of receiving events via polling (asking another node for events). They can also call the endpoints of full nodes to broadcast events or retrieve state.
|
|
65
|
+
|
|
66
|
+
There are five endpoints defined by the API spec. The first two are for event communication with full and partial nodes respectively. The remaining three are for state communication with full nodes. As a result, partial nodes are unable to directly transfer state and may only output events to other nodes.
|
|
67
|
+
- Broadcast events - `/events/broadcast`
|
|
68
|
+
- Poll events - `/events/poll`
|
|
69
|
+
- Fetch bundles - `/bundles/fetch`
|
|
70
|
+
- Fetch manifests - `/manifests/fetch`
|
|
71
|
+
- Fetch RIDs - `/rids/fetch`
|
|
72
|
+
|
|
73
|
+
All endpoints are called with via POST request with a JSON body, and will receive a response containing a JSON payload (with the exception of broadcast events, which won't return anything). The JSON schemas can be found in the attached OpenAPI specification or the Pydantic models in the "protocol" module.
|
|
74
|
+
|
|
75
|
+
The request and payload JSON objects are composed of the fundamental "knowledge types" from the RID / KOI-net system: RIDs, manifests, bundles, and events. RIDs, manifests, and bundles are defined by the RID protocol and imported from rid-lib, which you can [read about here](https://github.com/BlockScience/rid-lib). Events are now part of the KOI-net protocol, and are defined as an RID and an event type with an optional manifest and contents.
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"rid": "...",
|
|
80
|
+
"event_type": "NEW | UPDATE | FORGET",
|
|
81
|
+
"manifest": {
|
|
82
|
+
"rid": "...",
|
|
83
|
+
"timestamp": "...",
|
|
84
|
+
"sha256_hash": "...",
|
|
85
|
+
},
|
|
86
|
+
"contents": {}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This means that events are essentially just an RID, manifest, or bundle with an event type attached. Event types can be one of `FORGET`, `UPDATE`, or `NEW` forming the "FUN" acronym. While these types roughly correspond to delete, update, and create from CRUD operations, but they are not commands, they are signals. A node emits an event to indicate that its internal state has changed:
|
|
91
|
+
- `NEW` - indicates an previously unknown RID was cached
|
|
92
|
+
- `UPDATE` - indicates a previously known RID was cached
|
|
93
|
+
- `FORGET` - indicates a previously known RID was deleted
|
|
94
|
+
|
|
95
|
+
Nodes may broadcast events to other nodes to indicate its internal state changed. Conversely, nodes may also listen to events from other nodes and decide to change their internal state, take some other action, or do nothing.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Implementation
|
|
99
|
+
## Setup
|
|
100
|
+
|
|
101
|
+
The bulk of the code in this repo is taken up by the Python reference implementation, which can be used in other projects to easily set up and configure your own KOI node.
|
|
102
|
+
|
|
103
|
+
This package can be installed with pip:
|
|
104
|
+
```
|
|
105
|
+
pip install koi-net
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Node Interface
|
|
109
|
+
All of the KOI-net functionality comes from the `NodeInterface` class which provides methods to interact with the protocol API, a local RID cache, a view of the network, and an internal processing pipeline. To create a new node, you will need to give it a name and a profile. The name will be used to generate its unique node RID, and the profile stores basic configuration data which will be shared with other nodes you communciate with (it will be stored in the bundle associated with your node's RID).
|
|
110
|
+
- Partial nodes only need to indicate their type, and optionally the RID types of events they provide.
|
|
111
|
+
- Full nodes need to indicate their type, the base URL for their KOI-net API, and optionally the RID types of events and state they provide.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from koi_net import NodeInterface
|
|
115
|
+
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
116
|
+
|
|
117
|
+
# partial node configuration
|
|
118
|
+
|
|
119
|
+
partial_node = NodeInterface(
|
|
120
|
+
name="mypartialnode",
|
|
121
|
+
profile=NodeProfile(
|
|
122
|
+
node_type=NodeType.PARTIAL,
|
|
123
|
+
provides=NodeProvides(
|
|
124
|
+
event=[]
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# full node configuration
|
|
130
|
+
|
|
131
|
+
full_node = NodeInterface(
|
|
132
|
+
name="myfullnode",
|
|
133
|
+
profile=NodeProfile(
|
|
134
|
+
base_url="http://127.0.0.1:8000",
|
|
135
|
+
node_type=NodeType.FULL,
|
|
136
|
+
provides=NodeProvides(
|
|
137
|
+
event=[],
|
|
138
|
+
state=[]
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The node class mostly acts as a container for other classes with more specialized behavior, with special functions that should be called to start up and shut down a node. We'll take a look at each of these components in turn, but here is the class stub:
|
|
145
|
+
```python
|
|
146
|
+
class NodeInterface:
|
|
147
|
+
cache: Cache
|
|
148
|
+
identity: NodeIdentity
|
|
149
|
+
network: NetworkInterface
|
|
150
|
+
processor: ProcessorInterface
|
|
151
|
+
first_contact: str
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
name: str,
|
|
156
|
+
profile: NodeProfile,
|
|
157
|
+
identity_file_path: str = "identity.json",
|
|
158
|
+
first_contact: str | None = None,
|
|
159
|
+
handlers: list[KnowledgeHandler] | None = None,
|
|
160
|
+
cache: Cache | None = None,
|
|
161
|
+
network: NetworkInterface | None = None,
|
|
162
|
+
processor: ProcessorInterface | None = None
|
|
163
|
+
): ...
|
|
164
|
+
|
|
165
|
+
def initialize(self): ...
|
|
166
|
+
def finalize(self): ...
|
|
167
|
+
```
|
|
168
|
+
As you can see, only a name and profile are required. The other fields allow for additional customization if needed.
|
|
169
|
+
|
|
170
|
+
## Node Identity
|
|
171
|
+
The `NodeIdentity` class provides easy access to a node's own RID, profile, and bundle. It provides access to the following properties after initialization, accessed with `node.identity`.
|
|
172
|
+
```
|
|
173
|
+
class NodeIdentity:
|
|
174
|
+
rid: KoiNetNode # an RID type
|
|
175
|
+
profile: NodeProfile
|
|
176
|
+
bundle: Bundle
|
|
177
|
+
```
|
|
178
|
+
This it what is initialized from the required `name` and `profile` fields in the `NodeInterface` constructor.
|
|
179
|
+
|
|
180
|
+
## Network Interface
|
|
181
|
+
The `NetworkInterface` class provides access to high level network actions, and contains several other network related classes. It is accessed with `node.network`.
|
|
182
|
+
```python
|
|
183
|
+
class NetworkInterface:
|
|
184
|
+
graph: NetworkGraph
|
|
185
|
+
request_handler: RequestHandler
|
|
186
|
+
response_handler: ResponseHandler
|
|
187
|
+
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
file_path: str,
|
|
191
|
+
first_contact: str | None,
|
|
192
|
+
cache: Cache,
|
|
193
|
+
identity: NodeIdentity
|
|
194
|
+
): ...
|
|
195
|
+
|
|
196
|
+
def push_event_to(self, event: Event, node: KoiNetNode, flush=False): ...
|
|
197
|
+
|
|
198
|
+
def flush_poll_queue(self, node: KoiNetNode) -> list[Event]: ...
|
|
199
|
+
def flush_webhook_queue(self, node: RID): ...
|
|
200
|
+
def flush_all_webhook_queues(self): ...
|
|
201
|
+
|
|
202
|
+
def get_state_providers(self, rid_type: RIDType): ...
|
|
203
|
+
|
|
204
|
+
def fetch_remote_bundle(self, rid: RID): ...
|
|
205
|
+
|
|
206
|
+
def fetch_remote_manifest(self, rid: RID): ...
|
|
207
|
+
|
|
208
|
+
def poll_neighbors(self) -> list[Event]: ...
|
|
209
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
|
|
2
|
+
koi_net/core.py,sha256=ZsBoTay7Z1_7JKzKt-vB3x_zl9GEit-fFkCSifLPoOk,3582
|
|
3
|
+
koi_net/identity.py,sha256=PBgmAx5f3zzQmHASB1TJW2g19n9TLfmSJMXg2eQFg0A,2386
|
|
4
|
+
koi_net/network/__init__.py,sha256=r_RN-q_mDYC-2RAkN-lJoMUX76TXyfEUc_MVKW87z0g,39
|
|
5
|
+
koi_net/network/graph.py,sha256=VSvjF2p_EsuJsNPFhK4MEjgXW9oZL0Vg9Tn6X4-c3WY,4710
|
|
6
|
+
koi_net/network/interface.py,sha256=paBJjQFJC8UkUz-BWeRwvuWD2cv8WFt7PyhoV7VOhWI,10823
|
|
7
|
+
koi_net/network/request_handler.py,sha256=FDS6b3MLjeM_w6josbDtHZZUg0D1kuGDwE1H0si8mus,3870
|
|
8
|
+
koi_net/network/response_handler.py,sha256=mA3FtrN3aTZATcLaHQhJUWrJdIKNv6d24fhvOl-nDKY,1890
|
|
9
|
+
koi_net/processor/__init__.py,sha256=x4fAY0hvQEDcpfdTB3POIzxBQjYAtn0qQazPo1Xm0m4,41
|
|
10
|
+
koi_net/processor/default_handlers.py,sha256=kmXBKBCAs6ygsPWl9IFbjZe1KDC_6M0YgkRF7hfI7kU,7224
|
|
11
|
+
koi_net/processor/handler.py,sha256=9ZHsoPyAwFhtJPmvrLY-7C9dQ7KhLwI3DS5_Ms4HWAY,1640
|
|
12
|
+
koi_net/processor/interface.py,sha256=4oiSWz91WWQywbfRdT2H2-aVh_6BTYE2AHbZSovzd0U,11671
|
|
13
|
+
koi_net/processor/knowledge_object.py,sha256=cGv33fwNZQMylkhlTaQTbk96FVIVbdOUaBsG06u0m4k,4187
|
|
14
|
+
koi_net/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
koi_net/protocol/api_models.py,sha256=Vm2Szp2OZ3EsZrYRwkAiBi_smk4MF1dAt-xPGOLi6ME,919
|
|
16
|
+
koi_net/protocol/consts.py,sha256=zeWJvRpqcERrqJq39heyNHb6f_9QrvoBZJHd70yE914,249
|
|
17
|
+
koi_net/protocol/edge.py,sha256=G3D9Ie0vbTSMJdoTw9g_oBmFCqzJ1gO7U1PVrw7p3j8,447
|
|
18
|
+
koi_net/protocol/event.py,sha256=dzJmcHbimo7p5NwH2drccF0vMcAj9oQRj3iZ9Bjf7kg,1275
|
|
19
|
+
koi_net/protocol/helpers.py,sha256=9E9PaoIuSNrTBATGCLJ_kSBMZ2z-KIMnLJzGOTqQDC0,719
|
|
20
|
+
koi_net/protocol/node.py,sha256=Ntrx01dbm39ViKGtr4gLmztcMwKpTIweS6rRL-zoU_Y,391
|
|
21
|
+
koi_net-1.0.0b2.dist-info/METADATA,sha256=Gry4tH3P7Ed1_d0EiC3oBnLIwHa6BmQaM4gTw6V1cbc,10358
|
|
22
|
+
koi_net-1.0.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
koi_net-1.0.0b2.dist-info/licenses/LICENSE,sha256=XBcvl8yjCAezfuqN1jadQykrX7H2g4nr2WRDmHLW6ik,1090
|
|
24
|
+
koi_net-1.0.0b2.dist-info/RECORD,,
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: koi-net
|
|
3
|
-
Version: 1.0.0b1
|
|
4
|
-
Summary: Implementation of KOI-net protocol in Python
|
|
5
|
-
Project-URL: Homepage, https://github.com/BlockScience/koi-net/
|
|
6
|
-
Author-email: Luke Miller <luke@block.science>
|
|
7
|
-
License: MIT License
|
|
8
|
-
|
|
9
|
-
Copyright (c) 2025 BlockScience
|
|
10
|
-
|
|
11
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
-
in the Software without restriction, including without limitation the rights
|
|
14
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
-
furnished to do so, subject to the following conditions:
|
|
17
|
-
|
|
18
|
-
The above copyright notice and this permission notice shall be included in all
|
|
19
|
-
copies or substantial portions of the Software.
|
|
20
|
-
|
|
21
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
-
SOFTWARE.
|
|
28
|
-
License-File: LICENSE
|
|
29
|
-
Requires-Python: >=3.10
|
|
30
|
-
Requires-Dist: httpx>=0.28.1
|
|
31
|
-
Requires-Dist: networkx>=3.4.2
|
|
32
|
-
Requires-Dist: pydantic>=2.10.6
|
|
33
|
-
Requires-Dist: rid-lib>=3.2.1
|
|
34
|
-
Provides-Extra: dev
|
|
35
|
-
Requires-Dist: build; extra == 'dev'
|
|
36
|
-
Requires-Dist: twine>=6.0; extra == 'dev'
|
|
37
|
-
Provides-Extra: examples
|
|
38
|
-
Requires-Dist: fastapi; extra == 'examples'
|
|
39
|
-
Requires-Dist: rich; extra == 'examples'
|
|
40
|
-
Requires-Dist: uvicorn; extra == 'examples'
|
|
41
|
-
Description-Content-Type: text/markdown
|
|
42
|
-
|
|
43
|
-
# koi-net
|
koi_net-1.0.0b1.dist-info/RECORD
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
|
|
2
|
-
koi_net/core.py,sha256=71ntaewhGBZ2zFQ7BxoSu2hhzPHQRsYYcRZGgTcdLhU,3024
|
|
3
|
-
koi_net/identity.py,sha256=0ISIzQHUbwtPhXl6ZzrphzvwhBmb3K9t5wHmupr2_eA,1854
|
|
4
|
-
koi_net/network/__init__.py,sha256=r_RN-q_mDYC-2RAkN-lJoMUX76TXyfEUc_MVKW87z0g,39
|
|
5
|
-
koi_net/network/graph.py,sha256=qSHlilXZe2ikbDkE1tDRhazQfE2S_bxkNcExYmbxXqs,4039
|
|
6
|
-
koi_net/network/interface.py,sha256=8WADUb5qADgw65fyPbUagYwK0rjYRa1XId7zegGJSa4,9316
|
|
7
|
-
koi_net/network/request_handler.py,sha256=y7E80jgGfrXYxrOrHMMMzQCKrm5yV_GDessRE0GwOq4,3273
|
|
8
|
-
koi_net/network/response_handler.py,sha256=EWOJzSBdbMYfsUx51avhZXAJEU0QqeR6USfKafHK-f8,1810
|
|
9
|
-
koi_net/processor/__init__.py,sha256=x4fAY0hvQEDcpfdTB3POIzxBQjYAtn0qQazPo1Xm0m4,41
|
|
10
|
-
koi_net/processor/default_handlers.py,sha256=djgPWj_bcNsT8Wxp41JcCk-kjlDIHTK6r4mcjdQWasQ,5973
|
|
11
|
-
koi_net/processor/handler.py,sha256=6WkBJavbAs61cUwShcMjMb6lcWXh1Xu4g1PlMa3lu_A,747
|
|
12
|
-
koi_net/processor/interface.py,sha256=uEAktg8cfFYJGO9rbCSja0wsdcnbDhe44iBXKCxyT9k,8511
|
|
13
|
-
koi_net/processor/knowledge_object.py,sha256=fr0TBqSbjd8tdgbAxG_B5onKHRez52-dtsrz71K8Vew,2998
|
|
14
|
-
koi_net/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
koi_net/protocol/api_models.py,sha256=dUvNtOor8cI_xhhMvuQuIgcEY7JhVrBg-6KoC_Jih6I,833
|
|
16
|
-
koi_net/protocol/consts.py,sha256=GmaODi2V-BGZ5rMyFgRsrPz4iDfJZwkNpul2aSTQY5Q,208
|
|
17
|
-
koi_net/protocol/edge.py,sha256=G3D9Ie0vbTSMJdoTw9g_oBmFCqzJ1gO7U1PVrw7p3j8,447
|
|
18
|
-
koi_net/protocol/event.py,sha256=dzJmcHbimo7p5NwH2drccF0vMcAj9oQRj3iZ9Bjf7kg,1275
|
|
19
|
-
koi_net/protocol/helpers.py,sha256=9E9PaoIuSNrTBATGCLJ_kSBMZ2z-KIMnLJzGOTqQDC0,719
|
|
20
|
-
koi_net/protocol/node.py,sha256=Ntrx01dbm39ViKGtr4gLmztcMwKpTIweS6rRL-zoU_Y,391
|
|
21
|
-
koi_net-1.0.0b1.dist-info/METADATA,sha256=7nR4xHkuAJI3yctTJEaEZGtLjkYXOlpN43DEZsukehc,1927
|
|
22
|
-
koi_net-1.0.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
-
koi_net-1.0.0b1.dist-info/licenses/LICENSE,sha256=XBcvl8yjCAezfuqN1jadQykrX7H2g4nr2WRDmHLW6ik,1090
|
|
24
|
-
koi_net-1.0.0b1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|