koi-net 1.0.0b16__tar.gz → 1.0.0b18__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.0b16 → koi_net-1.0.0b18}/PKG-INFO +98 -34
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/README.md +97 -33
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/examples/basic_coordinator_node.py +21 -15
- koi_net-1.0.0b18/examples/basic_partial_node.py +46 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/pyproject.toml +1 -1
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/config.py +1 -1
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/interface.py +1 -1
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/default_handlers.py +4 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/interface.py +1 -3
- koi_net-1.0.0b16/examples/basic_partial_node.py +0 -84
- koi_net-1.0.0b16/examples/full_node_template.py +0 -94
- koi_net-1.0.0b16/examples/partial_node_template.py +0 -41
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/.github/workflows/publish-to-pypi.yml +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/.gitignore +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/LICENSE +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/requirements.txt +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/__init__.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/core.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/identity.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/__init__.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/graph.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/request_handler.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/response_handler.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/__init__.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/handler.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/knowledge_object.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/__init__.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/api_models.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/consts.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/edge.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/event.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/helpers.py +0 -0
- {koi_net-1.0.0b16 → koi_net-1.0.0b18}/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.0b18
|
|
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>
|
|
@@ -150,36 +150,56 @@ Your first decision will be whether to setup a partial or full node:
|
|
|
150
150
|
- Partial nodes only need to indicate their type, and optionally the RID types of events they provide.
|
|
151
151
|
- 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.
|
|
152
152
|
|
|
153
|
+
Nodes are configured using the provided `NodeConfig` class. Defaults can be set as shown below, and will automatically load from and save to YAML files. See the `koi_net.config` module for more info.
|
|
154
|
+
|
|
153
155
|
### Partial Node
|
|
154
156
|
```python
|
|
155
157
|
from koi_net import NodeInterface
|
|
156
158
|
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
from koi_net.config import NodeConfig, KoiNetConfig
|
|
160
|
+
|
|
161
|
+
class CoordinatorNodeConfig(NodeConfig):
|
|
162
|
+
koi_net: KoiNetConfig | None = Field(default_factory = lambda:
|
|
163
|
+
KoiNetConfig(
|
|
164
|
+
node_name="coordinator",
|
|
165
|
+
node_profile=NodeProfile(
|
|
166
|
+
node_type=NodeType.FULL
|
|
167
|
+
),
|
|
168
|
+
cache_directory_path=".basic_partial_rid_cache",
|
|
169
|
+
event_queues_path="basic_partial_event_queues.json",
|
|
170
|
+
first_contact="http://127.0.0.1:8000/koi-net"
|
|
164
171
|
)
|
|
165
172
|
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
node = NodeInterface(
|
|
176
|
+
config=CoordinatorNodeConfig.load_from_yaml("basical_partial_config.yaml")
|
|
166
177
|
)
|
|
167
178
|
```
|
|
168
179
|
### Full Node
|
|
169
180
|
```python
|
|
170
181
|
from koi_net import NodeInterface
|
|
171
182
|
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
183
|
+
from koi_net.config import NodeConfig, KoiNetConfig
|
|
184
|
+
|
|
185
|
+
class CoordinatorNodeConfig(NodeConfig):
|
|
186
|
+
koi_net: KoiNetConfig | None = Field(default_factory = lambda:
|
|
187
|
+
KoiNetConfig(
|
|
188
|
+
node_name="coordinator",
|
|
189
|
+
node_profile=NodeProfile(
|
|
190
|
+
node_type=NodeType.FULL,
|
|
191
|
+
provides=NodeProvides(
|
|
192
|
+
event=[KoiNetNode, KoiNetEdge],
|
|
193
|
+
state=[KoiNetNode, KoiNetEdge]
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
cache_directory_path=".coordinator_rid_cache",
|
|
197
|
+
event_queues_path="coordinator_event_queues.json"
|
|
198
|
+
)
|
|
199
|
+
)
|
|
172
200
|
|
|
173
201
|
node = NodeInterface(
|
|
174
|
-
|
|
175
|
-
profile=NodeProfile(
|
|
176
|
-
base_url="http://127.0.0.1:8000",
|
|
177
|
-
node_type=NodeType.FULL,
|
|
178
|
-
provides=NodeProvides(
|
|
179
|
-
event=[],
|
|
180
|
-
state=[]
|
|
181
|
-
)
|
|
182
|
-
),
|
|
202
|
+
config=CoordinatorNodeConfig.load_from_yaml("coordinator_config.yaml"),
|
|
183
203
|
use_kobj_processor_thread=True
|
|
184
204
|
)
|
|
185
205
|
```
|
|
@@ -402,6 +422,7 @@ The default configuration provides four default handlers which will take precede
|
|
|
402
422
|
```python
|
|
403
423
|
from koi_net import NodeInterface
|
|
404
424
|
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
425
|
+
from koi_net.config import NodeConfig
|
|
405
426
|
from koi_net.processor.default_handlers import (
|
|
406
427
|
basic_rid_handler,
|
|
407
428
|
basic_manifest_handler,
|
|
@@ -410,13 +431,7 @@ from koi_net.processor.default_handlers import (
|
|
|
410
431
|
)
|
|
411
432
|
|
|
412
433
|
node = NodeInterface(
|
|
413
|
-
|
|
414
|
-
profile=NodeProfile(
|
|
415
|
-
node_type=NodeType.PARTIAL,
|
|
416
|
-
provides=NodeProvides(
|
|
417
|
-
event=[]
|
|
418
|
-
)
|
|
419
|
-
),
|
|
434
|
+
config=NodeConfig.load_from_yaml(),
|
|
420
435
|
handlers=[
|
|
421
436
|
basic_rid_handler,
|
|
422
437
|
basic_manifest_handler,
|
|
@@ -437,23 +452,21 @@ This section provides high level explanations of the Python implementation. More
|
|
|
437
452
|
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:
|
|
438
453
|
```python
|
|
439
454
|
class NodeInterface:
|
|
455
|
+
config: ConfigType
|
|
440
456
|
cache: Cache
|
|
441
457
|
identity: NodeIdentity
|
|
442
458
|
network: NetworkInterface
|
|
443
459
|
processor: ProcessorInterface
|
|
444
|
-
|
|
460
|
+
|
|
445
461
|
use_kobj_processor_thread: bool
|
|
446
|
-
|
|
462
|
+
|
|
447
463
|
def __init__(
|
|
448
464
|
self,
|
|
449
|
-
|
|
450
|
-
profile: NodeProfile,
|
|
451
|
-
identity_file_path: str = "identity.json",
|
|
452
|
-
event_queues_file_path: str = "event_queues.json",
|
|
453
|
-
cache_directory_path: str = "rid_cache",
|
|
465
|
+
config: ConfigType,
|
|
454
466
|
use_kobj_processor_thread: bool = False,
|
|
455
|
-
|
|
467
|
+
|
|
456
468
|
handlers: list[KnowledgeHandler] | None = None,
|
|
469
|
+
|
|
457
470
|
cache: Cache | None = None,
|
|
458
471
|
network: NetworkInterface | None = None,
|
|
459
472
|
processor: ProcessorInterface | None = None
|
|
@@ -462,7 +475,58 @@ class NodeInterface:
|
|
|
462
475
|
def start(self): ...
|
|
463
476
|
def stop(self): ...
|
|
464
477
|
```
|
|
465
|
-
As you can see, only a
|
|
478
|
+
As you can see, only a node config is required, all other fields are optional.
|
|
479
|
+
|
|
480
|
+
## Node Config
|
|
481
|
+
|
|
482
|
+
The node config class is a mix of configuration groups providing basic shared behavior across nodes through a standard interface. The class is implemented as Pydantic model, but provides functions to load from and save to a YAML file, the expected format within a node repo.
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
class ServerConfig(BaseModel):
|
|
486
|
+
host: str | None = "127.0.0.1"
|
|
487
|
+
port: int | None = 8000
|
|
488
|
+
path: str | None = "/koi-net"
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def url(self) -> str: ...
|
|
492
|
+
|
|
493
|
+
class KoiNetConfig(BaseModel):
|
|
494
|
+
node_name: str
|
|
495
|
+
node_rid: KoiNetNode | None = None
|
|
496
|
+
node_profile: NodeProfile
|
|
497
|
+
|
|
498
|
+
cache_directory_path: str | None = ".rid_cache"
|
|
499
|
+
event_queues_path: str | None = "event_queues.json"
|
|
500
|
+
|
|
501
|
+
first_contact: str | None = None
|
|
502
|
+
|
|
503
|
+
class EnvConfig(BaseModel):
|
|
504
|
+
...
|
|
505
|
+
|
|
506
|
+
class NodeConfig(BaseModel):
|
|
507
|
+
server: ServerConfig | None = Field(default_factory=ServerConfig)
|
|
508
|
+
koi_net: KoiNetConfig
|
|
509
|
+
|
|
510
|
+
@classmethod
|
|
511
|
+
def load_from_yaml(
|
|
512
|
+
cls,
|
|
513
|
+
file_path: str = "config.yaml",
|
|
514
|
+
generate_missing: bool = True
|
|
515
|
+
): ...
|
|
516
|
+
|
|
517
|
+
def save_to_yaml(self): ...
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Nodes are expected to create new node config classes inheriting from `NodeConfig`. You may want to set a default KoiNetConfig (see examples) to allow for a default config to be generated if not provided by the user. Environment variables can be handled by inheriting from the `EnvConfig` class and adding new string fields. The value of this field should be equivalent to the environment variable name, for example:
|
|
521
|
+
|
|
522
|
+
```python
|
|
523
|
+
class SlackEnvConfig(EnvConfig):
|
|
524
|
+
slack_bot_token: str | None = "SLACK_BOT_TOKEN"
|
|
525
|
+
slack_signing_secret: str | None = "SLACK_SIGNING_SECRET"
|
|
526
|
+
slack_app_token: str | None = "SLACK_APP_TOKEN"
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
This special config class will automatically load in the variables from the current environment, or local `.env` file. Beyond these base config classes, you are free to add your own config groups. See `config.py` in the [koi-net-slack-sensor-node](https://github.com/BlockScience/koi-net-slack-sensor-node/blob/main/slack_sensor_node/config.py) repo for a more complete example.
|
|
466
530
|
|
|
467
531
|
## Node Identity
|
|
468
532
|
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`.
|
|
@@ -472,7 +536,7 @@ class NodeIdentity:
|
|
|
472
536
|
profile: NodeProfile
|
|
473
537
|
bundle: Bundle
|
|
474
538
|
```
|
|
475
|
-
This it what is initialized from the required `name` and `profile` fields in the `
|
|
539
|
+
This it what is initialized from the required `name` and `profile` fields in the `NodeConfig` class. Node RIDs take the form of `orn:koi-net.node:<name>+<uuid>`, and are generated on first use to the identity JSON file along with a the node profile.
|
|
476
540
|
|
|
477
541
|
## Network Interface
|
|
478
542
|
The `NetworkInterface` class provides access to high level network actions, and contains several other network related classes. It is accessed with `node.network`.
|
|
@@ -106,36 +106,56 @@ Your first decision will be whether to setup a partial or full node:
|
|
|
106
106
|
- Partial nodes only need to indicate their type, and optionally the RID types of events they provide.
|
|
107
107
|
- 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.
|
|
108
108
|
|
|
109
|
+
Nodes are configured using the provided `NodeConfig` class. Defaults can be set as shown below, and will automatically load from and save to YAML files. See the `koi_net.config` module for more info.
|
|
110
|
+
|
|
109
111
|
### Partial Node
|
|
110
112
|
```python
|
|
111
113
|
from koi_net import NodeInterface
|
|
112
114
|
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
from koi_net.config import NodeConfig, KoiNetConfig
|
|
116
|
+
|
|
117
|
+
class CoordinatorNodeConfig(NodeConfig):
|
|
118
|
+
koi_net: KoiNetConfig | None = Field(default_factory = lambda:
|
|
119
|
+
KoiNetConfig(
|
|
120
|
+
node_name="coordinator",
|
|
121
|
+
node_profile=NodeProfile(
|
|
122
|
+
node_type=NodeType.FULL
|
|
123
|
+
),
|
|
124
|
+
cache_directory_path=".basic_partial_rid_cache",
|
|
125
|
+
event_queues_path="basic_partial_event_queues.json",
|
|
126
|
+
first_contact="http://127.0.0.1:8000/koi-net"
|
|
120
127
|
)
|
|
121
128
|
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
node = NodeInterface(
|
|
132
|
+
config=CoordinatorNodeConfig.load_from_yaml("basical_partial_config.yaml")
|
|
122
133
|
)
|
|
123
134
|
```
|
|
124
135
|
### Full Node
|
|
125
136
|
```python
|
|
126
137
|
from koi_net import NodeInterface
|
|
127
138
|
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
139
|
+
from koi_net.config import NodeConfig, KoiNetConfig
|
|
140
|
+
|
|
141
|
+
class CoordinatorNodeConfig(NodeConfig):
|
|
142
|
+
koi_net: KoiNetConfig | None = Field(default_factory = lambda:
|
|
143
|
+
KoiNetConfig(
|
|
144
|
+
node_name="coordinator",
|
|
145
|
+
node_profile=NodeProfile(
|
|
146
|
+
node_type=NodeType.FULL,
|
|
147
|
+
provides=NodeProvides(
|
|
148
|
+
event=[KoiNetNode, KoiNetEdge],
|
|
149
|
+
state=[KoiNetNode, KoiNetEdge]
|
|
150
|
+
)
|
|
151
|
+
),
|
|
152
|
+
cache_directory_path=".coordinator_rid_cache",
|
|
153
|
+
event_queues_path="coordinator_event_queues.json"
|
|
154
|
+
)
|
|
155
|
+
)
|
|
128
156
|
|
|
129
157
|
node = NodeInterface(
|
|
130
|
-
|
|
131
|
-
profile=NodeProfile(
|
|
132
|
-
base_url="http://127.0.0.1:8000",
|
|
133
|
-
node_type=NodeType.FULL,
|
|
134
|
-
provides=NodeProvides(
|
|
135
|
-
event=[],
|
|
136
|
-
state=[]
|
|
137
|
-
)
|
|
138
|
-
),
|
|
158
|
+
config=CoordinatorNodeConfig.load_from_yaml("coordinator_config.yaml"),
|
|
139
159
|
use_kobj_processor_thread=True
|
|
140
160
|
)
|
|
141
161
|
```
|
|
@@ -358,6 +378,7 @@ The default configuration provides four default handlers which will take precede
|
|
|
358
378
|
```python
|
|
359
379
|
from koi_net import NodeInterface
|
|
360
380
|
from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
|
|
381
|
+
from koi_net.config import NodeConfig
|
|
361
382
|
from koi_net.processor.default_handlers import (
|
|
362
383
|
basic_rid_handler,
|
|
363
384
|
basic_manifest_handler,
|
|
@@ -366,13 +387,7 @@ from koi_net.processor.default_handlers import (
|
|
|
366
387
|
)
|
|
367
388
|
|
|
368
389
|
node = NodeInterface(
|
|
369
|
-
|
|
370
|
-
profile=NodeProfile(
|
|
371
|
-
node_type=NodeType.PARTIAL,
|
|
372
|
-
provides=NodeProvides(
|
|
373
|
-
event=[]
|
|
374
|
-
)
|
|
375
|
-
),
|
|
390
|
+
config=NodeConfig.load_from_yaml(),
|
|
376
391
|
handlers=[
|
|
377
392
|
basic_rid_handler,
|
|
378
393
|
basic_manifest_handler,
|
|
@@ -393,23 +408,21 @@ This section provides high level explanations of the Python implementation. More
|
|
|
393
408
|
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:
|
|
394
409
|
```python
|
|
395
410
|
class NodeInterface:
|
|
411
|
+
config: ConfigType
|
|
396
412
|
cache: Cache
|
|
397
413
|
identity: NodeIdentity
|
|
398
414
|
network: NetworkInterface
|
|
399
415
|
processor: ProcessorInterface
|
|
400
|
-
|
|
416
|
+
|
|
401
417
|
use_kobj_processor_thread: bool
|
|
402
|
-
|
|
418
|
+
|
|
403
419
|
def __init__(
|
|
404
420
|
self,
|
|
405
|
-
|
|
406
|
-
profile: NodeProfile,
|
|
407
|
-
identity_file_path: str = "identity.json",
|
|
408
|
-
event_queues_file_path: str = "event_queues.json",
|
|
409
|
-
cache_directory_path: str = "rid_cache",
|
|
421
|
+
config: ConfigType,
|
|
410
422
|
use_kobj_processor_thread: bool = False,
|
|
411
|
-
|
|
423
|
+
|
|
412
424
|
handlers: list[KnowledgeHandler] | None = None,
|
|
425
|
+
|
|
413
426
|
cache: Cache | None = None,
|
|
414
427
|
network: NetworkInterface | None = None,
|
|
415
428
|
processor: ProcessorInterface | None = None
|
|
@@ -418,7 +431,58 @@ class NodeInterface:
|
|
|
418
431
|
def start(self): ...
|
|
419
432
|
def stop(self): ...
|
|
420
433
|
```
|
|
421
|
-
As you can see, only a
|
|
434
|
+
As you can see, only a node config is required, all other fields are optional.
|
|
435
|
+
|
|
436
|
+
## Node Config
|
|
437
|
+
|
|
438
|
+
The node config class is a mix of configuration groups providing basic shared behavior across nodes through a standard interface. The class is implemented as Pydantic model, but provides functions to load from and save to a YAML file, the expected format within a node repo.
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
class ServerConfig(BaseModel):
|
|
442
|
+
host: str | None = "127.0.0.1"
|
|
443
|
+
port: int | None = 8000
|
|
444
|
+
path: str | None = "/koi-net"
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
def url(self) -> str: ...
|
|
448
|
+
|
|
449
|
+
class KoiNetConfig(BaseModel):
|
|
450
|
+
node_name: str
|
|
451
|
+
node_rid: KoiNetNode | None = None
|
|
452
|
+
node_profile: NodeProfile
|
|
453
|
+
|
|
454
|
+
cache_directory_path: str | None = ".rid_cache"
|
|
455
|
+
event_queues_path: str | None = "event_queues.json"
|
|
456
|
+
|
|
457
|
+
first_contact: str | None = None
|
|
458
|
+
|
|
459
|
+
class EnvConfig(BaseModel):
|
|
460
|
+
...
|
|
461
|
+
|
|
462
|
+
class NodeConfig(BaseModel):
|
|
463
|
+
server: ServerConfig | None = Field(default_factory=ServerConfig)
|
|
464
|
+
koi_net: KoiNetConfig
|
|
465
|
+
|
|
466
|
+
@classmethod
|
|
467
|
+
def load_from_yaml(
|
|
468
|
+
cls,
|
|
469
|
+
file_path: str = "config.yaml",
|
|
470
|
+
generate_missing: bool = True
|
|
471
|
+
): ...
|
|
472
|
+
|
|
473
|
+
def save_to_yaml(self): ...
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Nodes are expected to create new node config classes inheriting from `NodeConfig`. You may want to set a default KoiNetConfig (see examples) to allow for a default config to be generated if not provided by the user. Environment variables can be handled by inheriting from the `EnvConfig` class and adding new string fields. The value of this field should be equivalent to the environment variable name, for example:
|
|
477
|
+
|
|
478
|
+
```python
|
|
479
|
+
class SlackEnvConfig(EnvConfig):
|
|
480
|
+
slack_bot_token: str | None = "SLACK_BOT_TOKEN"
|
|
481
|
+
slack_signing_secret: str | None = "SLACK_SIGNING_SECRET"
|
|
482
|
+
slack_app_token: str | None = "SLACK_APP_TOKEN"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
This special config class will automatically load in the variables from the current environment, or local `.env` file. Beyond these base config classes, you are free to add your own config groups. See `config.py` in the [koi-net-slack-sensor-node](https://github.com/BlockScience/koi-net-slack-sensor-node/blob/main/slack_sensor_node/config.py) repo for a more complete example.
|
|
422
486
|
|
|
423
487
|
## Node Identity
|
|
424
488
|
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`.
|
|
@@ -428,7 +492,7 @@ class NodeIdentity:
|
|
|
428
492
|
profile: NodeProfile
|
|
429
493
|
bundle: Bundle
|
|
430
494
|
```
|
|
431
|
-
This it what is initialized from the required `name` and `profile` fields in the `
|
|
495
|
+
This it what is initialized from the required `name` and `profile` fields in the `NodeConfig` class. Node RIDs take the form of `orn:koi-net.node:<name>+<uuid>`, and are generated on first use to the identity JSON file along with a the node profile.
|
|
432
496
|
|
|
433
497
|
## Network Interface
|
|
434
498
|
The `NetworkInterface` class provides access to high level network actions, and contains several other network related classes. It is accessed with `node.network`.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
from pydantic import Field
|
|
3
4
|
import uvicorn
|
|
4
5
|
from contextlib import asynccontextmanager
|
|
5
6
|
from rich.logging import RichHandler
|
|
6
7
|
from fastapi import FastAPI
|
|
7
8
|
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
8
9
|
from koi_net import NodeInterface
|
|
10
|
+
from koi_net.config import NodeConfig, KoiNetConfig
|
|
9
11
|
from koi_net.processor.handler import HandlerType
|
|
10
12
|
from koi_net.processor.knowledge_object import KnowledgeObject, KnowledgeSource
|
|
11
13
|
from koi_net.protocol.edge import EdgeType
|
|
@@ -41,22 +43,26 @@ logging.basicConfig(
|
|
|
41
43
|
|
|
42
44
|
logging.getLogger("koi_net").setLevel(logging.DEBUG)
|
|
43
45
|
|
|
44
|
-
port = 8000
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
class CoordinatorNodeConfig(NodeConfig):
|
|
48
|
+
koi_net: KoiNetConfig | None = Field(default_factory = lambda:
|
|
49
|
+
KoiNetConfig(
|
|
50
|
+
node_name="coordinator",
|
|
51
|
+
node_profile=NodeProfile(
|
|
52
|
+
node_type=NodeType.FULL,
|
|
53
|
+
provides=NodeProvides(
|
|
54
|
+
event=[KoiNetNode, KoiNetEdge],
|
|
55
|
+
state=[KoiNetNode, KoiNetEdge]
|
|
56
|
+
)
|
|
57
|
+
),
|
|
58
|
+
cache_directory_path=".coordinator_rid_cache",
|
|
59
|
+
event_queues_path="coordinator_event_queues.json"
|
|
54
60
|
)
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
node = NodeInterface(
|
|
64
|
+
config=CoordinatorNodeConfig.load_from_yaml("coordinator_config.yaml"),
|
|
65
|
+
use_kobj_processor_thread=True
|
|
60
66
|
)
|
|
61
67
|
|
|
62
68
|
|
|
@@ -132,4 +138,4 @@ if __name__ == "__main__":
|
|
|
132
138
|
with open("koi-net-protocol-openapi.json", "w") as f:
|
|
133
139
|
json.dump(openapi_spec, f, indent=2)
|
|
134
140
|
|
|
135
|
-
uvicorn.run("examples.basic_coordinator_node:app", port=port)
|
|
141
|
+
uvicorn.run("examples.basic_coordinator_node:app", port=node.config.server.port)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from rich.logging import RichHandler
|
|
5
|
+
from koi_net import NodeInterface
|
|
6
|
+
from koi_net.processor.knowledge_object import KnowledgeSource
|
|
7
|
+
from koi_net.protocol.node import NodeProfile, NodeType
|
|
8
|
+
from koi_net.config import NodeConfig, KoiNetConfig
|
|
9
|
+
|
|
10
|
+
logging.basicConfig(
|
|
11
|
+
level=logging.INFO,
|
|
12
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
13
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
14
|
+
handlers=[RichHandler()]
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logging.getLogger("koi_net").setLevel(logging.DEBUG)
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CoordinatorNodeConfig(NodeConfig):
|
|
22
|
+
koi_net: KoiNetConfig | None = Field(default_factory = lambda:
|
|
23
|
+
KoiNetConfig(
|
|
24
|
+
node_name="coordinator",
|
|
25
|
+
node_profile=NodeProfile(
|
|
26
|
+
node_type=NodeType.FULL
|
|
27
|
+
),
|
|
28
|
+
cache_directory_path=".basic_partial_rid_cache",
|
|
29
|
+
event_queues_path="basic_partial_event_queues.json",
|
|
30
|
+
first_contact="http://127.0.0.1:8000/koi-net"
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
node = NodeInterface(
|
|
36
|
+
config=CoordinatorNodeConfig.load_from_yaml("basical_partial_config.yaml")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
node.start()
|
|
40
|
+
|
|
41
|
+
while True:
|
|
42
|
+
for event in node.network.poll_neighbors():
|
|
43
|
+
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
44
|
+
node.processor.flush_kobj_queue()
|
|
45
|
+
|
|
46
|
+
time.sleep(5)
|
|
@@ -173,7 +173,7 @@ class NetworkInterface(Generic[ConfigType]):
|
|
|
173
173
|
self.request_handler.broadcast_events(node, events=events)
|
|
174
174
|
return True
|
|
175
175
|
except httpx.ConnectError:
|
|
176
|
-
logger.warning("Broadcast failed
|
|
176
|
+
logger.warning("Broadcast failed")
|
|
177
177
|
for event in events:
|
|
178
178
|
self.push_event_to(event, node)
|
|
179
179
|
return False
|
|
@@ -134,6 +134,10 @@ def coordinator_contact(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
|
134
134
|
if KoiNetNode not in node_profile.provides.event:
|
|
135
135
|
return
|
|
136
136
|
|
|
137
|
+
# prevents coordinators from attempting to form a self loop
|
|
138
|
+
if kobj.rid == processor.identity.rid:
|
|
139
|
+
return
|
|
140
|
+
|
|
137
141
|
# already have an edge established
|
|
138
142
|
if processor.network.graph.get_edge_profile(
|
|
139
143
|
source=kobj.rid,
|
|
@@ -229,9 +229,7 @@ class ProcessorInterface():
|
|
|
229
229
|
|
|
230
230
|
for node in kobj.network_targets:
|
|
231
231
|
self.network.push_event_to(kobj.normalized_event, node)
|
|
232
|
-
|
|
233
|
-
logger.warning("Dropping unresponsive node")
|
|
234
|
-
self.handle(rid=node, event_type=EventType.FORGET)
|
|
232
|
+
self.network.flush_webhook_queue(node)
|
|
235
233
|
|
|
236
234
|
kobj = self.call_handler_chain(HandlerType.Final, kobj)
|
|
237
235
|
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import logging
|
|
3
|
-
from rich.logging import RichHandler
|
|
4
|
-
from koi_net import NodeInterface
|
|
5
|
-
from koi_net.processor.handler import HandlerType
|
|
6
|
-
from koi_net.processor.knowledge_object import KnowledgeSource, KnowledgeObject
|
|
7
|
-
from koi_net.processor.interface import ProcessorInterface
|
|
8
|
-
from koi_net.protocol.event import EventType
|
|
9
|
-
from koi_net.protocol.edge import EdgeType
|
|
10
|
-
from koi_net.protocol.node import NodeProfile, NodeType
|
|
11
|
-
from koi_net.protocol.helpers import generate_edge_bundle
|
|
12
|
-
from rid_lib.types import KoiNetNode
|
|
13
|
-
|
|
14
|
-
logging.basicConfig(
|
|
15
|
-
level=logging.INFO,
|
|
16
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
17
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
|
18
|
-
handlers=[RichHandler()]
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
logging.getLogger("koi_net").setLevel(logging.DEBUG)
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
node = NodeInterface(
|
|
26
|
-
name="partial",
|
|
27
|
-
profile=NodeProfile(
|
|
28
|
-
node_type=NodeType.PARTIAL
|
|
29
|
-
),
|
|
30
|
-
cache_directory_path="partial_node_rid_cache",
|
|
31
|
-
event_queues_file_path="parital_node_event_queus.json",
|
|
32
|
-
identity_file_path="partial_node_identity.json",
|
|
33
|
-
first_contact="http://127.0.0.1:8000/koi-net"
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
@node.processor.register_handler(HandlerType.Network, rid_types=[KoiNetNode])
|
|
37
|
-
def coordinator_contact(processor: ProcessorInterface, kobj: KnowledgeObject):
|
|
38
|
-
# when I found out about a new node
|
|
39
|
-
if kobj.normalized_event_type != EventType.NEW:
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
node_profile = kobj.bundle.validate_contents(NodeProfile)
|
|
43
|
-
|
|
44
|
-
# looking for event provider of nodes
|
|
45
|
-
if KoiNetNode not in node_profile.provides.event:
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
logger.info("Identified a coordinator!")
|
|
49
|
-
logger.info("Proposing new edge")
|
|
50
|
-
|
|
51
|
-
# queued for processing
|
|
52
|
-
processor.handle(bundle=generate_edge_bundle(
|
|
53
|
-
source=kobj.rid,
|
|
54
|
-
target=node.identity.rid,
|
|
55
|
-
edge_type=EdgeType.POLL,
|
|
56
|
-
rid_types=[KoiNetNode]
|
|
57
|
-
))
|
|
58
|
-
|
|
59
|
-
logger.info("Catching up on network state")
|
|
60
|
-
|
|
61
|
-
payload = processor.network.request_handler.fetch_rids(kobj.rid, rid_types=[KoiNetNode])
|
|
62
|
-
for rid in payload.rids:
|
|
63
|
-
if rid == processor.identity.rid:
|
|
64
|
-
logger.info("Skipping myself")
|
|
65
|
-
continue
|
|
66
|
-
if processor.cache.exists(rid):
|
|
67
|
-
logger.info(f"Skipping known RID '{rid}'")
|
|
68
|
-
continue
|
|
69
|
-
|
|
70
|
-
# marked as external since we are handling RIDs from another node
|
|
71
|
-
# will fetch remotely instead of checking local cache
|
|
72
|
-
processor.handle(rid=rid, source=KnowledgeSource.External)
|
|
73
|
-
logger.info("Done")
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
node.start()
|
|
78
|
-
|
|
79
|
-
while True:
|
|
80
|
-
for event in node.network.poll_neighbors():
|
|
81
|
-
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
82
|
-
node.processor.flush_kobj_queue()
|
|
83
|
-
|
|
84
|
-
time.sleep(5)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import uvicorn
|
|
3
|
-
from contextlib import asynccontextmanager
|
|
4
|
-
from rich.logging import RichHandler
|
|
5
|
-
from fastapi import FastAPI
|
|
6
|
-
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
7
|
-
from koi_net import NodeInterface
|
|
8
|
-
from koi_net.processor.knowledge_object import KnowledgeSource
|
|
9
|
-
|
|
10
|
-
from koi_net.protocol.node import NodeProfile, NodeType, NodeProvides
|
|
11
|
-
from koi_net.protocol.api_models import (
|
|
12
|
-
PollEvents,
|
|
13
|
-
FetchRids,
|
|
14
|
-
FetchManifests,
|
|
15
|
-
FetchBundles,
|
|
16
|
-
EventsPayload,
|
|
17
|
-
RidsPayload,
|
|
18
|
-
ManifestsPayload,
|
|
19
|
-
BundlesPayload
|
|
20
|
-
)
|
|
21
|
-
from koi_net.protocol.consts import (
|
|
22
|
-
BROADCAST_EVENTS_PATH,
|
|
23
|
-
POLL_EVENTS_PATH,
|
|
24
|
-
FETCH_RIDS_PATH,
|
|
25
|
-
FETCH_MANIFESTS_PATH,
|
|
26
|
-
FETCH_BUNDLES_PATH
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
logging.basicConfig(
|
|
31
|
-
level=logging.INFO,
|
|
32
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
33
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
|
34
|
-
handlers=[RichHandler()]
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
logger = logging.getLogger(__name__)
|
|
38
|
-
logging.getLogger("koi_net").setLevel(logging.DEBUG)
|
|
39
|
-
|
|
40
|
-
port = 5000
|
|
41
|
-
coordinator_url = "http://127.0.0.1:8000/koi-net"
|
|
42
|
-
|
|
43
|
-
node = NodeInterface(
|
|
44
|
-
name="coordinator",
|
|
45
|
-
profile=NodeProfile(
|
|
46
|
-
base_url=f"http://127.0.0.1:{port}/koi-net",
|
|
47
|
-
node_type=NodeType.FULL,
|
|
48
|
-
provides=NodeProvides(
|
|
49
|
-
event=[KoiNetNode, KoiNetEdge],
|
|
50
|
-
state=[KoiNetNode, KoiNetEdge]
|
|
51
|
-
)
|
|
52
|
-
),
|
|
53
|
-
use_kobj_processor_thread=True,
|
|
54
|
-
first_contact=coordinator_url
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@asynccontextmanager
|
|
59
|
-
async def lifespan(app: FastAPI):
|
|
60
|
-
node.start()
|
|
61
|
-
yield
|
|
62
|
-
node.stop()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
app = FastAPI(lifespan=lifespan, root_path="/koi-net")
|
|
66
|
-
|
|
67
|
-
@app.post(BROADCAST_EVENTS_PATH)
|
|
68
|
-
def broadcast_events(req: EventsPayload):
|
|
69
|
-
logger.info(f"Request to {BROADCAST_EVENTS_PATH}, received {len(req.events)} event(s)")
|
|
70
|
-
for event in req.events:
|
|
71
|
-
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
72
|
-
|
|
73
|
-
@app.post(POLL_EVENTS_PATH)
|
|
74
|
-
def poll_events(req: PollEvents) -> EventsPayload:
|
|
75
|
-
logger.info(f"Request to {POLL_EVENTS_PATH}")
|
|
76
|
-
events = node.network.flush_poll_queue(req.rid)
|
|
77
|
-
return EventsPayload(events=events)
|
|
78
|
-
|
|
79
|
-
@app.post(FETCH_RIDS_PATH)
|
|
80
|
-
def fetch_rids(req: FetchRids) -> RidsPayload:
|
|
81
|
-
return node.network.response_handler.fetch_rids(req)
|
|
82
|
-
|
|
83
|
-
@app.post(FETCH_MANIFESTS_PATH)
|
|
84
|
-
def fetch_manifests(req: FetchManifests) -> ManifestsPayload:
|
|
85
|
-
return node.network.response_handler.fetch_manifests(req)
|
|
86
|
-
|
|
87
|
-
@app.post(FETCH_BUNDLES_PATH)
|
|
88
|
-
def fetch_bundles(req: FetchBundles) -> BundlesPayload:
|
|
89
|
-
return node.network.response_handler.fetch_bundles(req)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if __name__ == "__main__":
|
|
93
|
-
# update this path to the Python module that defines "app"
|
|
94
|
-
uvicorn.run("examples.full_node_template:app", port=port)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import logging
|
|
3
|
-
from rich.logging import RichHandler
|
|
4
|
-
from koi_net import NodeInterface
|
|
5
|
-
from koi_net.processor.knowledge_object import KnowledgeSource
|
|
6
|
-
from koi_net.protocol.node import NodeProfile, NodeType
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
logging.basicConfig(
|
|
10
|
-
level=logging.INFO,
|
|
11
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
12
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
|
13
|
-
handlers=[RichHandler()]
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
coordinator_url = "http://127.0.0.1:8000/koi-net"
|
|
20
|
-
|
|
21
|
-
node = NodeInterface(
|
|
22
|
-
name="partial",
|
|
23
|
-
profile=NodeProfile(
|
|
24
|
-
node_type=NodeType.PARTIAL,
|
|
25
|
-
),
|
|
26
|
-
first_contact=coordinator_url
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
if __name__ == "__main__":
|
|
30
|
-
node.start()
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
while True:
|
|
34
|
-
for event in node.network.poll_neighbors():
|
|
35
|
-
node.processor.handle(event=event, source=KnowledgeSource.External)
|
|
36
|
-
node.processor.flush_kobj_queue()
|
|
37
|
-
|
|
38
|
-
time.sleep(5)
|
|
39
|
-
|
|
40
|
-
finally:
|
|
41
|
-
node.stop()
|
|
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
|
|
File without changes
|
|
File without changes
|