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.

Files changed (33) hide show
  1. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/PKG-INFO +98 -34
  2. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/README.md +97 -33
  3. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/examples/basic_coordinator_node.py +21 -15
  4. koi_net-1.0.0b18/examples/basic_partial_node.py +46 -0
  5. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/pyproject.toml +1 -1
  6. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/config.py +1 -1
  7. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/interface.py +1 -1
  8. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/default_handlers.py +4 -0
  9. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/interface.py +1 -3
  10. koi_net-1.0.0b16/examples/basic_partial_node.py +0 -84
  11. koi_net-1.0.0b16/examples/full_node_template.py +0 -94
  12. koi_net-1.0.0b16/examples/partial_node_template.py +0 -41
  13. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/.github/workflows/publish-to-pypi.yml +0 -0
  14. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/.gitignore +0 -0
  15. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/LICENSE +0 -0
  16. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/requirements.txt +0 -0
  17. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/__init__.py +0 -0
  18. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/core.py +0 -0
  19. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/identity.py +0 -0
  20. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/__init__.py +0 -0
  21. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/graph.py +0 -0
  22. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/request_handler.py +0 -0
  23. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/network/response_handler.py +0 -0
  24. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/__init__.py +0 -0
  25. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/handler.py +0 -0
  26. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/processor/knowledge_object.py +0 -0
  27. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/__init__.py +0 -0
  28. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/api_models.py +0 -0
  29. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/consts.py +0 -0
  30. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/edge.py +0 -0
  31. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/event.py +0 -0
  32. {koi_net-1.0.0b16 → koi_net-1.0.0b18}/src/koi_net/protocol/helpers.py +0 -0
  33. {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.0b16
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
- node = NodeInterface(
159
- name="mypartialnode",
160
- profile=NodeProfile(
161
- node_type=NodeType.PARTIAL,
162
- provides=NodeProvides(
163
- event=[]
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
- name="myfullnode",
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
- name="mypartialnode",
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
- first_contact: str
460
+
445
461
  use_kobj_processor_thread: bool
446
-
462
+
447
463
  def __init__(
448
464
  self,
449
- name: str,
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
- first_contact: str | None = None,
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 name and profile are required. The other fields allow for additional customization if needed.
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 `NodeInterface` constructor. 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.
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
- node = NodeInterface(
115
- name="mypartialnode",
116
- profile=NodeProfile(
117
- node_type=NodeType.PARTIAL,
118
- provides=NodeProvides(
119
- event=[]
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
- name="myfullnode",
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
- name="mypartialnode",
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
- first_contact: str
416
+
401
417
  use_kobj_processor_thread: bool
402
-
418
+
403
419
  def __init__(
404
420
  self,
405
- name: str,
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
- first_contact: str | None = None,
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 name and profile are required. The other fields allow for additional customization if needed.
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 `NodeInterface` constructor. 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.
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
- node = NodeInterface(
47
- name="coordinator",
48
- profile=NodeProfile(
49
- base_url=f"http://127.0.0.1:{port}/koi-net",
50
- node_type=NodeType.FULL,
51
- provides=NodeProvides(
52
- event=[KoiNetNode, KoiNetEdge],
53
- state=[KoiNetNode, KoiNetEdge]
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
- use_kobj_processor_thread=True,
57
- cache_directory_path="coordinator_node_rid_cache",
58
- event_queues_file_path="coordinator_node_event_queus.json",
59
- identity_file_path="coordinator_node_identity.json",
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)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "koi-net"
7
- version = "1.0.0-beta.16"
7
+ version = "1.0.0-beta.18"
8
8
  description = "Implementation of KOI-net protocol in Python"
9
9
  authors = [
10
10
  {name = "Luke Miller", email = "luke@block.science"}
@@ -13,7 +13,7 @@ class ServerConfig(BaseModel):
13
13
  path: str | None = "/koi-net"
14
14
 
15
15
  @property
16
- def url(self):
16
+ def url(self) -> str:
17
17
  return f"http://{self.host}:{self.port}{self.path or ''}"
18
18
 
19
19
  class KoiNetConfig(BaseModel):
@@ -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, dropping node")
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
- if not self.network.flush_webhook_queue(node):
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