koi-net 1.0.0b7__tar.gz → 1.0.0b9__tar.gz
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-1.0.0b7 → koi_net-1.0.0b9}/PKG-INFO +154 -13
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/README.md +153 -12
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/examples/basic_coordinator_node.py +2 -2
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/examples/full_node_template.py +2 -2
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/pyproject.toml +1 -1
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/network/interface.py +2 -2
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/network/response_handler.py +1 -1
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/processor/default_handlers.py +8 -15
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/processor/handler.py +9 -2
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/processor/interface.py +12 -4
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/api_models.py +1 -1
- koi_net-1.0.0b9/test.py +25 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/.gitignore +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/LICENSE +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/examples/basic_partial_node.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/examples/partial_node_template.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/requirements.txt +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/__init__.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/core.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/identity.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/network/__init__.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/network/graph.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/network/request_handler.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/processor/__init__.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/processor/knowledge_object.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/__init__.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/consts.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/edge.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/event.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/helpers.py +0 -0
- {koi_net-1.0.0b7 → koi_net-1.0.0b9}/src/koi_net/protocol/node.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: koi-net
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b9
|
|
4
4
|
Summary: Implementation of KOI-net protocol in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/BlockScience/koi-net/
|
|
6
6
|
Author-email: Luke Miller <luke@block.science>
|
|
@@ -87,10 +87,10 @@ The request and payload JSON objects are composed of the fundamental "knowledge
|
|
|
87
87
|
}
|
|
88
88
|
```
|
|
89
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
|
|
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
94
|
|
|
95
95
|
Nodes may broadcast events to other nodes to indicate their internal state changed. Conversely, nodes may also listen to events from other nodes and as a result decide to change their internal state, take some other action, or do nothing.
|
|
96
96
|
|
|
@@ -144,16 +144,19 @@ node = NodeInterface(
|
|
|
144
144
|
event=[],
|
|
145
145
|
state=[]
|
|
146
146
|
)
|
|
147
|
-
)
|
|
147
|
+
),
|
|
148
|
+
use_kobj_processor_thread=True
|
|
148
149
|
)
|
|
149
150
|
```
|
|
150
151
|
|
|
152
|
+
When creating a node, you optionally enable `use_kobj_processor_thread` which will run the knowledge processing pipeline on a separate thread. This thread will automatically dequeue and process knowledge objects as they are added to the `kobj_queue`, which happenes when you call `node.process.handle(...)`. This is required to prevent race conditions in asynchronous applications, like web servers, therefore it is recommended to enable this feature for all full nodes.
|
|
153
|
+
|
|
151
154
|
## Knowledge Processing
|
|
152
155
|
|
|
153
156
|
Next we'll set up the knowledge processing flow for our node. This is where most of the node's logic and behavior will come into play. For partial nodes this will be an event loop, and for full nodes we will use webhooks. Make sure to call `node.start()` and `node.stop()` at the beginning and end of your node's life cycle.
|
|
154
157
|
|
|
155
158
|
### Partial Node
|
|
156
|
-
Make sure to set `source=KnowledgeSource.External
|
|
159
|
+
Make sure to set `source=KnowledgeSource.External` when calling `handle` on external knowledge, this indicates to the knowledge processing pipeline that the incoming knowledge was received from another node. Where the knowledge is sourced from will impact decisions in the node's knowledge handlers.
|
|
157
160
|
```python
|
|
158
161
|
import time
|
|
159
162
|
from koi_net.processor.knowledge_object import KnowledgeSource
|
|
@@ -196,11 +199,9 @@ from koi_net.protocol.api_models import *
|
|
|
196
199
|
from koi_net.protocol.consts import *
|
|
197
200
|
|
|
198
201
|
@app.post(BROADCAST_EVENTS_PATH)
|
|
199
|
-
def broadcast_events(req: EventsPayload
|
|
202
|
+
def broadcast_events(req: EventsPayload):
|
|
200
203
|
for event in req.events:
|
|
201
204
|
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
202
|
-
|
|
203
|
-
background.add_task(node.processor.flush_kobj_queue)
|
|
204
205
|
```
|
|
205
206
|
|
|
206
207
|
Next we can add the event polling endpoint, this allows partial nodes to receive events from us.
|
|
@@ -253,6 +254,147 @@ python -m examples.basic_coordinator_node
|
|
|
253
254
|
python -m examples.basic_partial_node
|
|
254
255
|
```
|
|
255
256
|
|
|
257
|
+
# Advanced
|
|
258
|
+
|
|
259
|
+
## Knowledge Processing Pipeline
|
|
260
|
+
Beyond the `NodeInterface` setup and boiler plate for partial/full nodes, node behavior is mostly controlled through the use of knowledge handlers. Effectively creating your own handlers relies on a solid understanding of the knowledge processing pipeline, so we'll start with that. As a developer, you will interface with the pipeline through the `ProcessorInterface` accessed with `node.processor`. The pipeline handles knowledge objects, from the `KnowledgeObject` class, a container for all knowledge types in the RID / KOI-net ecosystem:
|
|
261
|
+
- RIDs
|
|
262
|
+
- Manifests
|
|
263
|
+
- Bundles
|
|
264
|
+
- Events
|
|
265
|
+
|
|
266
|
+
Here is the class definition for a knowledge object:
|
|
267
|
+
```python
|
|
268
|
+
type KnowledgeEventType = EventType | None
|
|
269
|
+
|
|
270
|
+
class KnowledgeSource(StrEnum):
|
|
271
|
+
Internal = "INTERNAL"
|
|
272
|
+
External = "EXTERNAL"
|
|
273
|
+
|
|
274
|
+
class KnowledgeObject(BaseModel):
|
|
275
|
+
rid: RID
|
|
276
|
+
manifest: Manifest | None = None
|
|
277
|
+
contents: dict | None = None
|
|
278
|
+
event_type: KnowledgeEventType = None
|
|
279
|
+
source: KnowledgeSource
|
|
280
|
+
normalized_event_type: KnowledgeEventType = None
|
|
281
|
+
network_targets: set[KoiNetNode] = set()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
In addition to the fields required to represent the knowledge types (`rid`, `manifest`, `contents`, `event_type`), knowledge objects also include a `source` field, indicating whether the knowledge originated from within the node (`KnowledgeSource.Internal`) or from another node (`KnowledgeSource.External`).
|
|
285
|
+
|
|
286
|
+
The final two fields are not inputs, but are set by handlers as the knowledge object moves through the processing pipeline. The normalized event type indicates the event type normalized to the perspective of the node's cache, and the network targets indicate where the resulting event should be broadcasted to.
|
|
287
|
+
|
|
288
|
+
Knowledge objects enter the processing pipeline through the `node.processor.handle(...)` method. Using kwargs you can pass any of the knowledge types listed above, a knowledge source, and an optional `event_type` (for non-event knowledge types). The handle function will simply normalize the provided knowledge type into a knowledge object, and put it in the `kobj_queue`, an internal, thread-safe queue of knowledge objects. If you have enabled `use_kobj_processor_thread` then the queue will be automatically processed on the processor thread, otherwise you will need to regularly call `flush_kobj_queue` to process queued knowledge objects (as in the partial node example). Both methods will process knowledge objects sequentially, in the order that they were queued in (FIFO).
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
## Knowledge Handlers
|
|
292
|
+
|
|
293
|
+
Processing happens through five distinct phases, corresponding to the handler types: `RID`, `Manifest`, `Bundle`, `Network`, and `Final`. Each handler type can be understood by describing (1) what knowledge object fields are available to the handler, and (2) what action takes place after this phase, which the handler can influence. As knowledge objects pass through the pipeline, fields may be added or updated.
|
|
294
|
+
|
|
295
|
+
Handlers are registered in a single handler array within the processor. There is no limit to the number of handlers in use, and multiple handlers can be assigned to the same handler type. At each phase of knowledge processing, we will chain together all of the handlers of the corresponding type and run them in their array order. The order handlers are registered in matters!
|
|
296
|
+
|
|
297
|
+
Each handler will be passed a knowledge object. They can choose to return one of three types: `None`, `KnowledgeObject`, or `STOP_CHAIN`. Returning `None` will pass the unmodified knowledge object (the same one the handler received) to the next handler in the chain. If a handler modified their knowledge object, they should return it to pass the new version to the next handler. Finally, a handler can return `STOP_CHAIN` to immediately stop processing the knowledge object. No further handlers will be called and it will not enter the next phase of processing.
|
|
298
|
+
|
|
299
|
+
Summary of processing pipeline:
|
|
300
|
+
```
|
|
301
|
+
RID -> Manifest -> Bundle -> [cache action] -> Network -> [network action] -> Final
|
|
302
|
+
|
|
|
303
|
+
(skip if event type is "FORGET")
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### RID Handler
|
|
307
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID and knowledge source field. This handler type acts as a filter, if none of the handlers return `STOP_CHAIN` the pipeline will progress to the next phase. The pipeline diverges slightly after this handler chain, based on the event type of the knowledge object.
|
|
308
|
+
|
|
309
|
+
If the event type is `"NEW"`, `"UPDATE"`, or `None` and the manifest is not already in the knowledge object, the node will attempt to retrieve it from (1) the local cache if the source is internal, or (2) from another node if the source is external. If it fails to retrieves the manifest, the pipeline will end. Next, the manifest handler chain will be called.
|
|
310
|
+
|
|
311
|
+
If the event type is `"FORGET"`, and the bundle (manifest + contents) is not already in the knowledge object, the node will attempt to retrieve it from the local cache, regardless of the source. In this case the knowledge object represents what we will delete from the cache, not new incoming knowledge. If it fails to retrieve the bundle, the pipeline will end. Next, the bundle handler chain will be called.
|
|
312
|
+
|
|
313
|
+
### Manifest Handler
|
|
314
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, and knowledge source field. This handler type acts as a filter, if none of the handlers return `STOP_CHAIN` the pipeline will progress to the next phase.
|
|
315
|
+
|
|
316
|
+
If the bundle (manifest + contents) is not already in the knowledge object, the node will attempt to retrieve it from (1) the local cache if the source is internal, or (2) from another node if the source is external. If it fails to retrieve the bundle, the pipeline will end. Next, the bundle handler chain will be called.
|
|
317
|
+
|
|
318
|
+
### Bundle Handler
|
|
319
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, bundle (manifest + contents), and knowledge source field. This handler type acts as a decider. In this phase, the knowledge object's normalized event type must be set to `"NEW"` or `"UPDATE"` to write it to cache, or `"FORGET"` to delete it from the cache. If the normalized event type remains unset (`None`), or a handler returns `STOP_CHAIN`, then the pipeline will end without taking any cache action.
|
|
320
|
+
|
|
321
|
+
The cache action will take place after the handler chain ends, so if multiple handlers set a normalized event type, the final handler will take precedence.
|
|
322
|
+
|
|
323
|
+
### Network Handler
|
|
324
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, bundle (manifest + contents), normalized event type, and knowledge source field. This handler type acts as a decider. In this phase, handlers decide which nodes to broadcast this knowledge object to by appending KOI-net node RIDs to the knowledge object's `network_targets` field. If a handler returns `STOP_CHAIN`, the pipeline will end without taking any network action.
|
|
325
|
+
|
|
326
|
+
The network action will take place after the handler chain ends. The node will attempt to broadcast a "normalized event", created from the knowledge object's RID, bundle, and normalized event type, to all of the node's in the network targets array.
|
|
327
|
+
|
|
328
|
+
### Final Handler
|
|
329
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, bundle (manifest + contents), normalized event type, and knowledge source field.
|
|
330
|
+
|
|
331
|
+
This is the final handler chain that is called, it doesn't make any decisions or filter for succesive handler types. Handlers here can be useful if you want to take some action after the network broadcast has ended.
|
|
332
|
+
|
|
333
|
+
## Registering Handlers
|
|
334
|
+
Knowledge handlers are registered with a node's processor by decorating a handler function. There are two types of decorators, the first way converts the function into a handler object which can be manually added to a processor. This is how the default handlers are defined, and makes them more portable (could be imported from another package). The second automatically registers a handler with your node instance. This is not portable but more convenient. The input of the decorated function will be the processor instance, and a knowledge object.
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from .handler import KnowledgeHandler, HandlerType, STOP_CHAIN
|
|
338
|
+
|
|
339
|
+
@KnowledgeHandler.create(HandlerType.RID)
|
|
340
|
+
def example_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
341
|
+
...
|
|
342
|
+
|
|
343
|
+
@node.processor.register_handler(HandlerType.RID)
|
|
344
|
+
def example_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
345
|
+
...
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
While handler's only require specifying the handler type, you can also specify the RID types, knowledge source, or event types you want to handle. If a knowledge object doesn't match all of the specified parameters, it won't be called. By default, handlers will match all RID types, all event types, and both internal and external sourced knowledge.
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
@KnowledgeHandler.create(
|
|
352
|
+
handler_type=HandlerType.Bundle,
|
|
353
|
+
rid_types=[KoiNetEdge],
|
|
354
|
+
source=KnowledgeSource.External,
|
|
355
|
+
event_types=[EventType.NEW, EventType.UPDATE])
|
|
356
|
+
def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
357
|
+
...
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The processor instance passed to your function should be used to take any necessary node actions (cache, network, etc.). It is also sometimes useful to add new knowledge objects to the queue while processing a different knowledge object. You can simply call `processor.handle(...)` in the same way as you would outside of a handler. It will put at the end of the queue and processed when it is dequeued like any other knowledge object.
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
## Default Behavior
|
|
364
|
+
|
|
365
|
+
The default configuration provides four default handlers which will take precedence over any handlers you add yourself. To override this behavior, you can set the `handlers` field in the `NodeInterface`:
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
from koi_net import NodeInterface
|
|
369
|
+
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
370
|
+
from koi_net.processor.default_handlers import (
|
|
371
|
+
basic_rid_handler,
|
|
372
|
+
basic_manifest_handler,
|
|
373
|
+
edge_negotiation_handler,
|
|
374
|
+
basic_network_output_filter
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
node = NodeInterface(
|
|
378
|
+
name="mypartialnode",
|
|
379
|
+
profile=NodeProfile(
|
|
380
|
+
node_type=NodeType.PARTIAL,
|
|
381
|
+
provides=NodeProvides(
|
|
382
|
+
event=[]
|
|
383
|
+
)
|
|
384
|
+
),
|
|
385
|
+
handlers=[
|
|
386
|
+
basic_rid_handler,
|
|
387
|
+
basic_manifest_handler,
|
|
388
|
+
edge_negotiation_handler,
|
|
389
|
+
basic_network_output_filter
|
|
390
|
+
|
|
391
|
+
# include all or none of the default handlers
|
|
392
|
+
]
|
|
393
|
+
)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Take a look at `src/koi_net/processor/default_handlers.py` to see some more in depth examples and better understand the default node behavior.
|
|
397
|
+
|
|
256
398
|
# Implementation Reference
|
|
257
399
|
This section provides high level explanations of the Python implementation. More detailed explanations of methods can be found in the docstrings within the codebase itself.
|
|
258
400
|
|
|
@@ -465,14 +607,13 @@ class ProcessorInterface:
|
|
|
465
607
|
event: Event | None = None,
|
|
466
608
|
kobj: KnowledgeObject | None = None,
|
|
467
609
|
event_type: KnowledgeEventType = None,
|
|
468
|
-
source: KnowledgeSource = KnowledgeSource.Internal
|
|
469
|
-
flush: bool = False
|
|
610
|
+
source: KnowledgeSource = KnowledgeSource.Internal
|
|
470
611
|
): ...
|
|
471
612
|
```
|
|
472
613
|
|
|
473
614
|
The `register_handler` method is a decorator which can wrap a function to create a new `KnowledgeHandler` and add it to the processing pipeline in a single step. The `add_handler` method adds an existing `KnowledgeHandler` to the processining pipeline.
|
|
474
615
|
|
|
475
|
-
The most commonly used functions in this class are `handle` and `flush_kobj_queue`. The `handle` method can be called on RIDs, manifests, bundles, and events to convert them to normalized to `KnowledgeObject` instances which are then added to the processing queue.
|
|
616
|
+
The most commonly used functions in this class are `handle` and `flush_kobj_queue`. The `handle` method can be called on RIDs, manifests, bundles, and events to convert them to normalized to `KnowledgeObject` instances which are then added to the processing queue. If you have enabled `use_kobj_processor_thread` then the queue will be automatically processed, otherwise you will need to regularly call `flush_kobj_queue` to process queued knolwedge objects. When calling the `handle` method, knowledge objects are marked as internally source by default. If you are handling RIDs, manifests, bundles, or events sourced from other nodes, `source` should be set to `KnowledgeSource.External`.
|
|
476
617
|
|
|
477
618
|
Here is an example of how an event polling loop would be implemented using the knowledge processing pipeline:
|
|
478
619
|
```python
|
|
@@ -45,10 +45,10 @@ The request and payload JSON objects are composed of the fundamental "knowledge
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
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:
|
|
49
|
-
- `NEW` - indicates an previously unknown RID was cached
|
|
50
|
-
- `UPDATE` - indicates a previously known RID was cached
|
|
51
|
-
- `FORGET` - indicates a previously known RID was deleted
|
|
48
|
+
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:
|
|
49
|
+
- `"NEW"` - indicates an previously unknown RID was cached
|
|
50
|
+
- `"UPDATE"` - indicates a previously known RID was cached
|
|
51
|
+
- `"FORGET"` - indicates a previously known RID was deleted
|
|
52
52
|
|
|
53
53
|
Nodes may broadcast events to other nodes to indicate their internal state changed. Conversely, nodes may also listen to events from other nodes and as a result decide to change their internal state, take some other action, or do nothing.
|
|
54
54
|
|
|
@@ -102,16 +102,19 @@ node = NodeInterface(
|
|
|
102
102
|
event=[],
|
|
103
103
|
state=[]
|
|
104
104
|
)
|
|
105
|
-
)
|
|
105
|
+
),
|
|
106
|
+
use_kobj_processor_thread=True
|
|
106
107
|
)
|
|
107
108
|
```
|
|
108
109
|
|
|
110
|
+
When creating a node, you optionally enable `use_kobj_processor_thread` which will run the knowledge processing pipeline on a separate thread. This thread will automatically dequeue and process knowledge objects as they are added to the `kobj_queue`, which happenes when you call `node.process.handle(...)`. This is required to prevent race conditions in asynchronous applications, like web servers, therefore it is recommended to enable this feature for all full nodes.
|
|
111
|
+
|
|
109
112
|
## Knowledge Processing
|
|
110
113
|
|
|
111
114
|
Next we'll set up the knowledge processing flow for our node. This is where most of the node's logic and behavior will come into play. For partial nodes this will be an event loop, and for full nodes we will use webhooks. Make sure to call `node.start()` and `node.stop()` at the beginning and end of your node's life cycle.
|
|
112
115
|
|
|
113
116
|
### Partial Node
|
|
114
|
-
Make sure to set `source=KnowledgeSource.External
|
|
117
|
+
Make sure to set `source=KnowledgeSource.External` when calling `handle` on external knowledge, this indicates to the knowledge processing pipeline that the incoming knowledge was received from another node. Where the knowledge is sourced from will impact decisions in the node's knowledge handlers.
|
|
115
118
|
```python
|
|
116
119
|
import time
|
|
117
120
|
from koi_net.processor.knowledge_object import KnowledgeSource
|
|
@@ -154,11 +157,9 @@ from koi_net.protocol.api_models import *
|
|
|
154
157
|
from koi_net.protocol.consts import *
|
|
155
158
|
|
|
156
159
|
@app.post(BROADCAST_EVENTS_PATH)
|
|
157
|
-
def broadcast_events(req: EventsPayload
|
|
160
|
+
def broadcast_events(req: EventsPayload):
|
|
158
161
|
for event in req.events:
|
|
159
162
|
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
160
|
-
|
|
161
|
-
background.add_task(node.processor.flush_kobj_queue)
|
|
162
163
|
```
|
|
163
164
|
|
|
164
165
|
Next we can add the event polling endpoint, this allows partial nodes to receive events from us.
|
|
@@ -211,6 +212,147 @@ python -m examples.basic_coordinator_node
|
|
|
211
212
|
python -m examples.basic_partial_node
|
|
212
213
|
```
|
|
213
214
|
|
|
215
|
+
# Advanced
|
|
216
|
+
|
|
217
|
+
## Knowledge Processing Pipeline
|
|
218
|
+
Beyond the `NodeInterface` setup and boiler plate for partial/full nodes, node behavior is mostly controlled through the use of knowledge handlers. Effectively creating your own handlers relies on a solid understanding of the knowledge processing pipeline, so we'll start with that. As a developer, you will interface with the pipeline through the `ProcessorInterface` accessed with `node.processor`. The pipeline handles knowledge objects, from the `KnowledgeObject` class, a container for all knowledge types in the RID / KOI-net ecosystem:
|
|
219
|
+
- RIDs
|
|
220
|
+
- Manifests
|
|
221
|
+
- Bundles
|
|
222
|
+
- Events
|
|
223
|
+
|
|
224
|
+
Here is the class definition for a knowledge object:
|
|
225
|
+
```python
|
|
226
|
+
type KnowledgeEventType = EventType | None
|
|
227
|
+
|
|
228
|
+
class KnowledgeSource(StrEnum):
|
|
229
|
+
Internal = "INTERNAL"
|
|
230
|
+
External = "EXTERNAL"
|
|
231
|
+
|
|
232
|
+
class KnowledgeObject(BaseModel):
|
|
233
|
+
rid: RID
|
|
234
|
+
manifest: Manifest | None = None
|
|
235
|
+
contents: dict | None = None
|
|
236
|
+
event_type: KnowledgeEventType = None
|
|
237
|
+
source: KnowledgeSource
|
|
238
|
+
normalized_event_type: KnowledgeEventType = None
|
|
239
|
+
network_targets: set[KoiNetNode] = set()
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
In addition to the fields required to represent the knowledge types (`rid`, `manifest`, `contents`, `event_type`), knowledge objects also include a `source` field, indicating whether the knowledge originated from within the node (`KnowledgeSource.Internal`) or from another node (`KnowledgeSource.External`).
|
|
243
|
+
|
|
244
|
+
The final two fields are not inputs, but are set by handlers as the knowledge object moves through the processing pipeline. The normalized event type indicates the event type normalized to the perspective of the node's cache, and the network targets indicate where the resulting event should be broadcasted to.
|
|
245
|
+
|
|
246
|
+
Knowledge objects enter the processing pipeline through the `node.processor.handle(...)` method. Using kwargs you can pass any of the knowledge types listed above, a knowledge source, and an optional `event_type` (for non-event knowledge types). The handle function will simply normalize the provided knowledge type into a knowledge object, and put it in the `kobj_queue`, an internal, thread-safe queue of knowledge objects. If you have enabled `use_kobj_processor_thread` then the queue will be automatically processed on the processor thread, otherwise you will need to regularly call `flush_kobj_queue` to process queued knowledge objects (as in the partial node example). Both methods will process knowledge objects sequentially, in the order that they were queued in (FIFO).
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
## Knowledge Handlers
|
|
250
|
+
|
|
251
|
+
Processing happens through five distinct phases, corresponding to the handler types: `RID`, `Manifest`, `Bundle`, `Network`, and `Final`. Each handler type can be understood by describing (1) what knowledge object fields are available to the handler, and (2) what action takes place after this phase, which the handler can influence. As knowledge objects pass through the pipeline, fields may be added or updated.
|
|
252
|
+
|
|
253
|
+
Handlers are registered in a single handler array within the processor. There is no limit to the number of handlers in use, and multiple handlers can be assigned to the same handler type. At each phase of knowledge processing, we will chain together all of the handlers of the corresponding type and run them in their array order. The order handlers are registered in matters!
|
|
254
|
+
|
|
255
|
+
Each handler will be passed a knowledge object. They can choose to return one of three types: `None`, `KnowledgeObject`, or `STOP_CHAIN`. Returning `None` will pass the unmodified knowledge object (the same one the handler received) to the next handler in the chain. If a handler modified their knowledge object, they should return it to pass the new version to the next handler. Finally, a handler can return `STOP_CHAIN` to immediately stop processing the knowledge object. No further handlers will be called and it will not enter the next phase of processing.
|
|
256
|
+
|
|
257
|
+
Summary of processing pipeline:
|
|
258
|
+
```
|
|
259
|
+
RID -> Manifest -> Bundle -> [cache action] -> Network -> [network action] -> Final
|
|
260
|
+
|
|
|
261
|
+
(skip if event type is "FORGET")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### RID Handler
|
|
265
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID and knowledge source field. This handler type acts as a filter, if none of the handlers return `STOP_CHAIN` the pipeline will progress to the next phase. The pipeline diverges slightly after this handler chain, based on the event type of the knowledge object.
|
|
266
|
+
|
|
267
|
+
If the event type is `"NEW"`, `"UPDATE"`, or `None` and the manifest is not already in the knowledge object, the node will attempt to retrieve it from (1) the local cache if the source is internal, or (2) from another node if the source is external. If it fails to retrieves the manifest, the pipeline will end. Next, the manifest handler chain will be called.
|
|
268
|
+
|
|
269
|
+
If the event type is `"FORGET"`, and the bundle (manifest + contents) is not already in the knowledge object, the node will attempt to retrieve it from the local cache, regardless of the source. In this case the knowledge object represents what we will delete from the cache, not new incoming knowledge. If it fails to retrieve the bundle, the pipeline will end. Next, the bundle handler chain will be called.
|
|
270
|
+
|
|
271
|
+
### Manifest Handler
|
|
272
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, and knowledge source field. This handler type acts as a filter, if none of the handlers return `STOP_CHAIN` the pipeline will progress to the next phase.
|
|
273
|
+
|
|
274
|
+
If the bundle (manifest + contents) is not already in the knowledge object, the node will attempt to retrieve it from (1) the local cache if the source is internal, or (2) from another node if the source is external. If it fails to retrieve the bundle, the pipeline will end. Next, the bundle handler chain will be called.
|
|
275
|
+
|
|
276
|
+
### Bundle Handler
|
|
277
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, bundle (manifest + contents), and knowledge source field. This handler type acts as a decider. In this phase, the knowledge object's normalized event type must be set to `"NEW"` or `"UPDATE"` to write it to cache, or `"FORGET"` to delete it from the cache. If the normalized event type remains unset (`None`), or a handler returns `STOP_CHAIN`, then the pipeline will end without taking any cache action.
|
|
278
|
+
|
|
279
|
+
The cache action will take place after the handler chain ends, so if multiple handlers set a normalized event type, the final handler will take precedence.
|
|
280
|
+
|
|
281
|
+
### Network Handler
|
|
282
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, bundle (manifest + contents), normalized event type, and knowledge source field. This handler type acts as a decider. In this phase, handlers decide which nodes to broadcast this knowledge object to by appending KOI-net node RIDs to the knowledge object's `network_targets` field. If a handler returns `STOP_CHAIN`, the pipeline will end without taking any network action.
|
|
283
|
+
|
|
284
|
+
The network action will take place after the handler chain ends. The node will attempt to broadcast a "normalized event", created from the knowledge object's RID, bundle, and normalized event type, to all of the node's in the network targets array.
|
|
285
|
+
|
|
286
|
+
### Final Handler
|
|
287
|
+
The knowledge object passed to handlers of this type are guaranteed to have an RID, manifest, bundle (manifest + contents), normalized event type, and knowledge source field.
|
|
288
|
+
|
|
289
|
+
This is the final handler chain that is called, it doesn't make any decisions or filter for succesive handler types. Handlers here can be useful if you want to take some action after the network broadcast has ended.
|
|
290
|
+
|
|
291
|
+
## Registering Handlers
|
|
292
|
+
Knowledge handlers are registered with a node's processor by decorating a handler function. There are two types of decorators, the first way converts the function into a handler object which can be manually added to a processor. This is how the default handlers are defined, and makes them more portable (could be imported from another package). The second automatically registers a handler with your node instance. This is not portable but more convenient. The input of the decorated function will be the processor instance, and a knowledge object.
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from .handler import KnowledgeHandler, HandlerType, STOP_CHAIN
|
|
296
|
+
|
|
297
|
+
@KnowledgeHandler.create(HandlerType.RID)
|
|
298
|
+
def example_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
299
|
+
...
|
|
300
|
+
|
|
301
|
+
@node.processor.register_handler(HandlerType.RID)
|
|
302
|
+
def example_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
303
|
+
...
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
While handler's only require specifying the handler type, you can also specify the RID types, knowledge source, or event types you want to handle. If a knowledge object doesn't match all of the specified parameters, it won't be called. By default, handlers will match all RID types, all event types, and both internal and external sourced knowledge.
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
@KnowledgeHandler.create(
|
|
310
|
+
handler_type=HandlerType.Bundle,
|
|
311
|
+
rid_types=[KoiNetEdge],
|
|
312
|
+
source=KnowledgeSource.External,
|
|
313
|
+
event_types=[EventType.NEW, EventType.UPDATE])
|
|
314
|
+
def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
315
|
+
...
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The processor instance passed to your function should be used to take any necessary node actions (cache, network, etc.). It is also sometimes useful to add new knowledge objects to the queue while processing a different knowledge object. You can simply call `processor.handle(...)` in the same way as you would outside of a handler. It will put at the end of the queue and processed when it is dequeued like any other knowledge object.
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
## Default Behavior
|
|
322
|
+
|
|
323
|
+
The default configuration provides four default handlers which will take precedence over any handlers you add yourself. To override this behavior, you can set the `handlers` field in the `NodeInterface`:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
from koi_net import NodeInterface
|
|
327
|
+
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
328
|
+
from koi_net.processor.default_handlers import (
|
|
329
|
+
basic_rid_handler,
|
|
330
|
+
basic_manifest_handler,
|
|
331
|
+
edge_negotiation_handler,
|
|
332
|
+
basic_network_output_filter
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
node = NodeInterface(
|
|
336
|
+
name="mypartialnode",
|
|
337
|
+
profile=NodeProfile(
|
|
338
|
+
node_type=NodeType.PARTIAL,
|
|
339
|
+
provides=NodeProvides(
|
|
340
|
+
event=[]
|
|
341
|
+
)
|
|
342
|
+
),
|
|
343
|
+
handlers=[
|
|
344
|
+
basic_rid_handler,
|
|
345
|
+
basic_manifest_handler,
|
|
346
|
+
edge_negotiation_handler,
|
|
347
|
+
basic_network_output_filter
|
|
348
|
+
|
|
349
|
+
# include all or none of the default handlers
|
|
350
|
+
]
|
|
351
|
+
)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Take a look at `src/koi_net/processor/default_handlers.py` to see some more in depth examples and better understand the default node behavior.
|
|
355
|
+
|
|
214
356
|
# Implementation Reference
|
|
215
357
|
This section provides high level explanations of the Python implementation. More detailed explanations of methods can be found in the docstrings within the codebase itself.
|
|
216
358
|
|
|
@@ -423,14 +565,13 @@ class ProcessorInterface:
|
|
|
423
565
|
event: Event | None = None,
|
|
424
566
|
kobj: KnowledgeObject | None = None,
|
|
425
567
|
event_type: KnowledgeEventType = None,
|
|
426
|
-
source: KnowledgeSource = KnowledgeSource.Internal
|
|
427
|
-
flush: bool = False
|
|
568
|
+
source: KnowledgeSource = KnowledgeSource.Internal
|
|
428
569
|
): ...
|
|
429
570
|
```
|
|
430
571
|
|
|
431
572
|
The `register_handler` method is a decorator which can wrap a function to create a new `KnowledgeHandler` and add it to the processing pipeline in a single step. The `add_handler` method adds an existing `KnowledgeHandler` to the processining pipeline.
|
|
432
573
|
|
|
433
|
-
The most commonly used functions in this class are `handle` and `flush_kobj_queue`. The `handle` method can be called on RIDs, manifests, bundles, and events to convert them to normalized to `KnowledgeObject` instances which are then added to the processing queue.
|
|
574
|
+
The most commonly used functions in this class are `handle` and `flush_kobj_queue`. The `handle` method can be called on RIDs, manifests, bundles, and events to convert them to normalized to `KnowledgeObject` instances which are then added to the processing queue. If you have enabled `use_kobj_processor_thread` then the queue will be automatically processed, otherwise you will need to regularly call `flush_kobj_queue` to process queued knolwedge objects. When calling the `handle` method, knowledge objects are marked as internally source by default. If you are handling RIDs, manifests, bundles, or events sourced from other nodes, `source` should be set to `KnowledgeSource.External`.
|
|
434
575
|
|
|
435
576
|
Here is an example of how an event polling loop would be implemented using the knowledge processing pipeline:
|
|
436
577
|
```python
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
import uvicorn
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
5
|
from rich.logging import RichHandler
|
|
6
|
-
from fastapi import FastAPI
|
|
6
|
+
from fastapi import FastAPI
|
|
7
7
|
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
8
8
|
from koi_net import NodeInterface
|
|
9
9
|
from koi_net.processor.handler import HandlerType
|
|
@@ -103,7 +103,7 @@ app = FastAPI(
|
|
|
103
103
|
)
|
|
104
104
|
|
|
105
105
|
@app.post(BROADCAST_EVENTS_PATH)
|
|
106
|
-
def broadcast_events(req: EventsPayload
|
|
106
|
+
def broadcast_events(req: EventsPayload):
|
|
107
107
|
logger.info(f"Request to {BROADCAST_EVENTS_PATH}, received {len(req.events)} event(s)")
|
|
108
108
|
for event in req.events:
|
|
109
109
|
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
import uvicorn
|
|
3
3
|
from contextlib import asynccontextmanager
|
|
4
4
|
from rich.logging import RichHandler
|
|
5
|
-
from fastapi import FastAPI
|
|
5
|
+
from fastapi import FastAPI
|
|
6
6
|
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
7
7
|
from koi_net import NodeInterface
|
|
8
8
|
from koi_net.processor.knowledge_object import KnowledgeSource
|
|
@@ -65,7 +65,7 @@ async def lifespan(app: FastAPI):
|
|
|
65
65
|
app = FastAPI(lifespan=lifespan, root_path="/koi-net")
|
|
66
66
|
|
|
67
67
|
@app.post(BROADCAST_EVENTS_PATH)
|
|
68
|
-
def broadcast_events(req: EventsPayload
|
|
68
|
+
def broadcast_events(req: EventsPayload):
|
|
69
69
|
logger.info(f"Request to {BROADCAST_EVENTS_PATH}, received {len(req.events)} event(s)")
|
|
70
70
|
for event in req.events:
|
|
71
71
|
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
@@ -201,8 +201,8 @@ class NetworkInterface:
|
|
|
201
201
|
payload = self.request_handler.fetch_bundles(
|
|
202
202
|
node=node_rid, rids=[rid])
|
|
203
203
|
|
|
204
|
-
if payload.
|
|
205
|
-
remote_bundle = payload.
|
|
204
|
+
if payload.bundles:
|
|
205
|
+
remote_bundle = payload.bundles[0]
|
|
206
206
|
logger.info(f"Got bundle from '{node_rid}'")
|
|
207
207
|
break
|
|
208
208
|
|
|
@@ -26,20 +26,13 @@ def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
|
26
26
|
return STOP_CHAIN
|
|
27
27
|
|
|
28
28
|
if kobj.event_type == EventType.FORGET:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
kobj.normalized_event_type = EventType.FORGET
|
|
32
|
-
return kobj
|
|
33
|
-
|
|
34
|
-
else:
|
|
35
|
-
# can't forget something I don't know about
|
|
36
|
-
return STOP_CHAIN
|
|
37
|
-
|
|
29
|
+
kobj.normalized_event_type = EventType.FORGET
|
|
30
|
+
return kobj
|
|
38
31
|
|
|
39
32
|
# Manifest handlers
|
|
40
33
|
|
|
41
34
|
@KnowledgeHandler.create(HandlerType.Manifest)
|
|
42
|
-
def
|
|
35
|
+
def basic_manifest_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
43
36
|
"""Default manifest handler.
|
|
44
37
|
|
|
45
38
|
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.
|
|
@@ -66,7 +59,11 @@ def basic_state_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
|
66
59
|
|
|
67
60
|
# Bundle handlers
|
|
68
61
|
|
|
69
|
-
@KnowledgeHandler.create(
|
|
62
|
+
@KnowledgeHandler.create(
|
|
63
|
+
handler_type=HandlerType.Bundle,
|
|
64
|
+
rid_types=[KoiNetEdge],
|
|
65
|
+
source=KnowledgeSource.External,
|
|
66
|
+
event_types=[EventType.NEW, EventType.UPDATE])
|
|
70
67
|
def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
71
68
|
"""Handles basic edge negotiation process.
|
|
72
69
|
|
|
@@ -74,10 +71,6 @@ def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObjec
|
|
|
74
71
|
"""
|
|
75
72
|
|
|
76
73
|
edge_profile = EdgeProfile.model_validate(kobj.contents)
|
|
77
|
-
|
|
78
|
-
# only want to handle external knowledge events (not edges this node created)
|
|
79
|
-
if kobj.source != KnowledgeSource.External:
|
|
80
|
-
return
|
|
81
74
|
|
|
82
75
|
# indicates peer subscribing to me
|
|
83
76
|
if edge_profile.source == processor.identity.rid:
|
|
@@ -3,6 +3,9 @@ from enum import StrEnum
|
|
|
3
3
|
from typing import Callable
|
|
4
4
|
from rid_lib import RIDType
|
|
5
5
|
|
|
6
|
+
from ..protocol.event import EventType
|
|
7
|
+
from .knowledge_object import KnowledgeSource, KnowledgeEventType
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
class StopChain:
|
|
8
11
|
"""Class for a sentinel value by knowledge handlers."""
|
|
@@ -34,19 +37,23 @@ class KnowledgeHandler:
|
|
|
34
37
|
func: Callable
|
|
35
38
|
handler_type: HandlerType
|
|
36
39
|
rid_types: list[RIDType] | None
|
|
40
|
+
source: KnowledgeSource | None = None
|
|
41
|
+
event_types: list[KnowledgeEventType] | None = None
|
|
37
42
|
|
|
38
43
|
@classmethod
|
|
39
44
|
def create(
|
|
40
45
|
cls,
|
|
41
46
|
handler_type: HandlerType,
|
|
42
|
-
rid_types: list[RIDType] | None = None
|
|
47
|
+
rid_types: list[RIDType] | None = None,
|
|
48
|
+
source: KnowledgeSource | None = None,
|
|
49
|
+
event_types: list[KnowledgeEventType] | None = None
|
|
43
50
|
):
|
|
44
51
|
"""Special decorator that returns a KnowledgeHandler instead of a function.
|
|
45
52
|
|
|
46
53
|
The function symbol will redefined as a `KnowledgeHandler`, which can be passed into the `ProcessorInterface` constructor. This is used to register default handlers.
|
|
47
54
|
"""
|
|
48
55
|
def decorator(func: Callable) -> KnowledgeHandler:
|
|
49
|
-
handler = cls(func, handler_type, rid_types)
|
|
56
|
+
handler = cls(func, handler_type, rid_types, source, event_types)
|
|
50
57
|
return handler
|
|
51
58
|
return decorator
|
|
52
59
|
|
|
@@ -62,11 +62,13 @@ class ProcessorInterface:
|
|
|
62
62
|
def register_handler(
|
|
63
63
|
self,
|
|
64
64
|
handler_type: HandlerType,
|
|
65
|
-
rid_types: list[RIDType] | None = None
|
|
65
|
+
rid_types: list[RIDType] | None = None,
|
|
66
|
+
source: KnowledgeSource | None = None,
|
|
67
|
+
event_types: list[KnowledgeEventType] | None = None
|
|
66
68
|
):
|
|
67
69
|
"""Assigns decorated function as handler for this processor."""
|
|
68
70
|
def decorator(func: Callable) -> Callable:
|
|
69
|
-
handler = KnowledgeHandler(func, handler_type, rid_types)
|
|
71
|
+
handler = KnowledgeHandler(func, handler_type, rid_types, source, event_types)
|
|
70
72
|
self.add_handler(handler)
|
|
71
73
|
return func
|
|
72
74
|
return decorator
|
|
@@ -93,6 +95,12 @@ class ProcessorInterface:
|
|
|
93
95
|
if handler.rid_types and type(kobj.rid) not in handler.rid_types:
|
|
94
96
|
continue
|
|
95
97
|
|
|
98
|
+
if handler.source and handler.source != kobj.source:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
if handler.event_types and kobj.event_type not in handler.event_types:
|
|
102
|
+
continue
|
|
103
|
+
|
|
96
104
|
logger.info(f"Calling {handler_type} handler '{handler.func.__name__}'")
|
|
97
105
|
resp = handler.func(self, kobj.model_copy())
|
|
98
106
|
|
|
@@ -178,8 +186,8 @@ class ProcessorInterface:
|
|
|
178
186
|
kobj.manifest = bundle.manifest
|
|
179
187
|
kobj.contents = bundle.contents
|
|
180
188
|
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
kobj = self.call_handler_chain(HandlerType.Bundle, kobj)
|
|
190
|
+
if kobj is STOP_CHAIN: return
|
|
183
191
|
|
|
184
192
|
if kobj.normalized_event_type in (EventType.UPDATE, EventType.NEW):
|
|
185
193
|
logger.info(f"Writing {kobj!r} to cache")
|
koi_net-1.0.0b9/test.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from koi_net import NodeInterface
|
|
2
|
+
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
3
|
+
from koi_net.processor.default_handlers import (
|
|
4
|
+
basic_rid_handler,
|
|
5
|
+
basic_manifest_handler,
|
|
6
|
+
edge_negotiation_handler,
|
|
7
|
+
basic_network_output_filter
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
node = NodeInterface(
|
|
11
|
+
name="mypartialnode",
|
|
12
|
+
profile=NodeProfile(
|
|
13
|
+
node_type=NodeType.PARTIAL,
|
|
14
|
+
provides=NodeProvides(
|
|
15
|
+
event=[]
|
|
16
|
+
)
|
|
17
|
+
),
|
|
18
|
+
handlers=[
|
|
19
|
+
basic_rid_handler,
|
|
20
|
+
basic_manifest_handler,
|
|
21
|
+
edge_negotiation_handler,
|
|
22
|
+
basic_network_output_filter
|
|
23
|
+
# include all or none of the default handlers imported above
|
|
24
|
+
]
|
|
25
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|