koi-net 1.2.0b1__tar.gz → 1.2.0b3__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 (87) hide show
  1. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/PKG-INFO +2 -1
  2. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/examples/coordinator.py +27 -32
  3. koi_net-1.2.0b3/examples/partial.py +30 -0
  4. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/pyproject.toml +2 -1
  5. koi_net-1.2.0b3/src/koi_net/__init__.py +1 -0
  6. koi_net-1.2.0b3/src/koi_net/assembler.py +95 -0
  7. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/cli/models.py +2 -2
  8. koi_net-1.2.0b3/src/koi_net/config/core.py +71 -0
  9. koi_net-1.2.0b3/src/koi_net/config/full_node.py +31 -0
  10. koi_net-1.2.0b3/src/koi_net/config/loader.py +46 -0
  11. koi_net-1.2.0b3/src/koi_net/config/partial_node.py +18 -0
  12. koi_net-1.2.0b3/src/koi_net/core.py +70 -0
  13. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/default_actions.py +1 -2
  14. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/effector.py +27 -15
  15. koi_net-1.2.0b3/src/koi_net/entrypoints/__init__.py +2 -0
  16. koi_net-1.2.0b3/src/koi_net/entrypoints/base.py +5 -0
  17. {koi_net-1.2.0b1/src/koi_net → koi_net-1.2.0b3/src/koi_net/entrypoints}/poller.py +14 -12
  18. koi_net-1.2.0b3/src/koi_net/entrypoints/server.py +94 -0
  19. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/handshaker.py +5 -5
  20. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/identity.py +3 -4
  21. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/lifecycle.py +42 -34
  22. koi_net-1.2.0b3/src/koi_net/logger.py +176 -0
  23. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/network/error_handler.py +7 -7
  24. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/network/event_queue.py +9 -7
  25. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/network/graph.py +8 -8
  26. koi_net-1.2.0b3/src/koi_net/network/poll_event_buffer.py +26 -0
  27. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/network/request_handler.py +23 -28
  28. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/network/resolver.py +14 -14
  29. koi_net-1.2.0b3/src/koi_net/network/response_handler.py +132 -0
  30. {koi_net-1.2.0b1/src/koi_net → koi_net-1.2.0b3/src/koi_net/processor}/context.py +11 -19
  31. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/processor/handler.py +4 -1
  32. koi_net-1.2.0b1/src/koi_net/processor/default_handlers.py → koi_net-1.2.0b3/src/koi_net/processor/knowledge_handlers.py +32 -31
  33. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/processor/knowledge_object.py +2 -3
  34. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/processor/kobj_queue.py +4 -4
  35. koi_net-1.2.0b1/src/koi_net/processor/knowledge_pipeline.py → koi_net-1.2.0b3/src/koi_net/processor/pipeline.py +25 -28
  36. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/api_models.py +5 -2
  37. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/envelope.py +5 -6
  38. koi_net-1.2.0b3/src/koi_net/protocol/model_map.py +61 -0
  39. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/node.py +3 -3
  40. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/secure.py +14 -8
  41. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/secure.py +6 -7
  42. koi_net-1.2.0b3/src/koi_net/workers/__init__.py +2 -0
  43. koi_net-1.2.0b1/src/koi_net/worker.py → koi_net-1.2.0b3/src/koi_net/workers/base.py +7 -0
  44. {koi_net-1.2.0b1/src/koi_net/processor → koi_net-1.2.0b3/src/koi_net/workers}/event_worker.py +19 -23
  45. {koi_net-1.2.0b1/src/koi_net → koi_net-1.2.0b3/src/koi_net/workers}/kobj_worker.py +12 -13
  46. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/uv.lock +15 -1
  47. koi_net-1.2.0b1/examples/partial.py +0 -35
  48. koi_net-1.2.0b1/src/koi_net/__init__.py +0 -1
  49. koi_net-1.2.0b1/src/koi_net/behaviors.py +0 -51
  50. koi_net-1.2.0b1/src/koi_net/config.py +0 -161
  51. koi_net-1.2.0b1/src/koi_net/core.py +0 -233
  52. koi_net-1.2.0b1/src/koi_net/models.py +0 -14
  53. koi_net-1.2.0b1/src/koi_net/network/response_handler.py +0 -67
  54. koi_net-1.2.0b1/src/koi_net/poll_event_buffer.py +0 -17
  55. koi_net-1.2.0b1/src/koi_net/server.py +0 -145
  56. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/.github/workflows/publish-to-pypi.yml +0 -0
  57. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/.gitignore +0 -0
  58. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/LICENSE +0 -0
  59. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/README.md +0 -0
  60. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/koi-net-protocol-openapi.json +0 -0
  61. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/requirements.txt +0 -0
  62. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/bundle.schema.json +0 -0
  63. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/bundles_payload.schema.json +0 -0
  64. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/edge_profile.schema.json +0 -0
  65. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/error_response.schema.json +0 -0
  66. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/event.schema.json +0 -0
  67. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/events_payload.schema.json +0 -0
  68. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/fetch_bundles.schema.json +0 -0
  69. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/fetch_manifests.schema.json +0 -0
  70. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/fetch_rids.schema.json +0 -0
  71. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/manifest.schema.json +0 -0
  72. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/manifests_payload.schema.json +0 -0
  73. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/node_profile.schema.json +0 -0
  74. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/poll_events.schema.json +0 -0
  75. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/rids_payload.schema.json +0 -0
  76. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/signed_envelope.schema.json +0 -0
  77. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/schemas/unsigned_envelope.json +0 -0
  78. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/cli/__init__.py +0 -0
  79. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/cli/commands.py +0 -0
  80. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/network/__init__.py +0 -0
  81. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/processor/__init__.py +0 -0
  82. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/__init__.py +0 -0
  83. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/consts.py +0 -0
  84. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/edge.py +0 -0
  85. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/errors.py +0 -0
  86. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/protocol/event.py +0 -0
  87. {koi_net-1.2.0b1 → koi_net-1.2.0b3}/src/koi_net/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.2.0b1
3
+ Version: 1.2.0b3
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>
@@ -36,6 +36,7 @@ Requires-Dist: python-dotenv>=1.1.0
36
36
  Requires-Dist: rich>=14.1.0
37
37
  Requires-Dist: rid-lib>=3.2.7
38
38
  Requires-Dist: ruamel-yaml>=0.18.10
39
+ Requires-Dist: structlog>=25.4.0
39
40
  Requires-Dist: uvicorn>=0.34.2
40
41
  Provides-Extra: dev
41
42
  Requires-Dist: build; extra == 'dev'
@@ -1,11 +1,15 @@
1
1
  import logging
2
2
  from rich.logging import RichHandler
3
- from pydantic import Field
4
3
  from rid_lib.types import KoiNetNode, KoiNetEdge
5
- from koi_net.config import NodeConfig, KoiNetConfig, ServerConfig
6
- from koi_net.core import NodeAssembler
7
- from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
8
- from koi_net.context import HandlerContext
4
+ from koi_net.config.full_node import (
5
+ FullNodeConfig,
6
+ ServerConfig,
7
+ KoiNetConfig,
8
+ NodeProfile,
9
+ NodeProvides
10
+ )
11
+ from koi_net.core import FullNode
12
+ from koi_net.processor.context import HandlerContext
9
13
  from koi_net.processor.handler import HandlerType, KnowledgeHandler
10
14
  from koi_net.processor.knowledge_object import KnowledgeObject
11
15
  from koi_net.protocol.event import Event, EventType
@@ -22,22 +26,17 @@ logging.getLogger("koi_net").setLevel(logging.DEBUG)
22
26
  logger = logging.getLogger(__name__)
23
27
 
24
28
 
25
- class CoordinatorConfig(NodeConfig):
26
- server: ServerConfig = Field(default_factory=lambda:
27
- ServerConfig(port=8080)
28
- )
29
- koi_net: KoiNetConfig = Field(default_factory = lambda:
30
- KoiNetConfig(
31
- node_name="coordinator",
32
- node_profile=NodeProfile(
33
- node_type=NodeType.FULL,
34
- provides=NodeProvides(
35
- event=[KoiNetNode, KoiNetEdge],
36
- state=[KoiNetNode, KoiNetEdge]
37
- )
38
- ),
39
- rid_types_of_interest=[KoiNetNode, KoiNetEdge]
40
- )
29
+ class CoordinatorConfig(FullNodeConfig):
30
+ server: ServerConfig = ServerConfig(port=8080)
31
+ koi_net: KoiNetConfig = KoiNetConfig(
32
+ node_name="coordinator",
33
+ node_profile=NodeProfile(
34
+ provides=NodeProvides(
35
+ event=[KoiNetNode, KoiNetEdge],
36
+ state=[KoiNetNode, KoiNetEdge]
37
+ )
38
+ ),
39
+ rid_types_of_interest=[KoiNetNode, KoiNetEdge]
41
40
  )
42
41
 
43
42
  @KnowledgeHandler.create(
@@ -52,7 +51,7 @@ def handshake_handler(ctx: HandlerContext, kobj: KnowledgeObject):
52
51
 
53
52
  logger.info("Sharing this node's bundle with peer")
54
53
  identity_bundle = ctx.cache.read(ctx.identity.rid)
55
- ctx.event_queue.push_event_to(
54
+ ctx.event_queue.push(
56
55
  event=Event.from_bundle(EventType.NEW, identity_bundle),
57
56
  target=kobj.rid
58
57
  )
@@ -67,17 +66,13 @@ def handshake_handler(ctx: HandlerContext, kobj: KnowledgeObject):
67
66
  rid_types=[KoiNetNode, KoiNetEdge]
68
67
  )
69
68
 
70
- ctx.kobj_queue.put_kobj(rid=edge_bundle.rid, event_type=EventType.FORGET)
71
- ctx.kobj_queue.put_kobj(bundle=edge_bundle)
69
+ ctx.kobj_queue.push(rid=edge_bundle.rid, event_type=EventType.FORGET)
70
+ ctx.kobj_queue.push(bundle=edge_bundle)
72
71
 
73
- class CoordinatorNodeAssembler(NodeAssembler):
72
+ class CoordinatorNode(FullNode):
74
73
  config = CoordinatorConfig
75
- knowledge_handlers = [
76
- *NodeAssembler.knowledge_handlers,
77
- handshake_handler
78
- ]
79
-
74
+ knowledge_handlers = FullNode.knowledge_handlers + [handshake_handler]
80
75
 
81
76
  if __name__ == "__main__":
82
- node = CoordinatorNodeAssembler.create()
83
- node.server.run()
77
+ node = CoordinatorNode()
78
+ node.entrypoint.run()
@@ -0,0 +1,30 @@
1
+ import logging
2
+ from rich.logging import RichHandler
3
+ from koi_net.config.partial_node import PartialNodeConfig, KoiNetConfig, NodeProfile
4
+ from koi_net.core import PartialNode
5
+
6
+
7
+ logging.basicConfig(
8
+ level=logging.INFO,
9
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
10
+ datefmt="%Y-%m-%d %H:%M:%S",
11
+ handlers=[RichHandler()]
12
+ )
13
+
14
+ logging.getLogger("koi_net").setLevel(logging.DEBUG)
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class MyPartialNodeConfig(PartialNodeConfig):
19
+ koi_net: KoiNetConfig = KoiNetConfig(
20
+ node_name="partial",
21
+ node_profile=NodeProfile()
22
+ )
23
+
24
+ class MyPartialNode(PartialNode):
25
+ config_cls = MyPartialNodeConfig
26
+
27
+
28
+ if __name__ == "__main__":
29
+ node = MyPartialNode()
30
+ # node.entrypoint.run()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "koi-net"
7
- version = "1.2.0b1"
7
+ version = "1.2.0b3"
8
8
  description = "Implementation of KOI-net protocol in Python"
9
9
  authors = [
10
10
  {name = "Luke Miller", email = "luke@block.science"}
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "fastapi>=0.115.12",
24
24
  "uvicorn>=0.34.2",
25
25
  "rich>=14.1.0",
26
+ "structlog>=25.4.0",
26
27
  ]
27
28
 
28
29
  [project.optional-dependencies]
@@ -0,0 +1 @@
1
+ from . import logger
@@ -0,0 +1,95 @@
1
+ import inspect
2
+ from typing import Protocol
3
+ from dataclasses import make_dataclass
4
+
5
+ from pydantic import BaseModel
6
+ import structlog
7
+
8
+ from .entrypoints.base import EntryPoint
9
+
10
+ log = structlog.stdlib.get_logger()
11
+
12
+
13
+ class BuildOrderer(type):
14
+ def __new__(cls, name: str, bases: tuple, dct: dict[str]):
15
+ """Sets `cls._build_order` from component order in class definition."""
16
+ cls = super().__new__(cls, name, bases, dct)
17
+
18
+ if "_build_order" not in dct:
19
+ components = {}
20
+ # adds components from base classes (including cls)
21
+ for base in reversed(inspect.getmro(cls)[:-1]):
22
+ for k, v in vars(base).items():
23
+ # excludes built in and private attributes
24
+ if not k.startswith("_"):
25
+ components[k] = v
26
+
27
+ # recipe list constructed from names of non-None components
28
+ cls._build_order = [
29
+ name for name, _type in components.items()
30
+ if _type is not None
31
+ ]
32
+
33
+ return cls
34
+
35
+ class NodeContainer(Protocol):
36
+ entrypoint = EntryPoint
37
+
38
+ class NodeAssembler(metaclass=BuildOrderer):
39
+ def __new__(self) -> NodeContainer:
40
+ return self._build()
41
+
42
+ @classmethod
43
+ def _build(cls) -> NodeContainer:
44
+ components = {}
45
+ for comp_name in cls._build_order:
46
+ comp = getattr(cls, comp_name, None)
47
+
48
+ if comp is None:
49
+ raise Exception(f"Couldn't find factory for component '{comp_name}'")
50
+
51
+ print(comp_name)
52
+
53
+ if not callable(comp):
54
+ print(f"Treating {comp_name} as a literal")
55
+ components[comp_name] = comp
56
+ continue
57
+
58
+ if issubclass(comp, BaseModel):
59
+ print(f"Treating {comp_name} as a pydantic model")
60
+ components[comp_name] = comp
61
+ continue
62
+
63
+ sig = inspect.signature(comp)
64
+
65
+ required_comps = []
66
+ for name, param in sig.parameters.items():
67
+ required_comps.append((name, param.annotation))
68
+
69
+ if len(required_comps) == 0:
70
+ s = comp_name
71
+ else:
72
+ s = f"{comp_name} -> {', '.join([name for name, _type in required_comps])}"
73
+
74
+ # print(s.replace("graph", "_graph"), end=";\n")
75
+
76
+ dependencies = {}
77
+ for req_comp_name, req_comp_type in required_comps:
78
+ if req_comp_name not in components:
79
+ raise Exception(f"Couldn't find required component '{req_comp_name}'")
80
+
81
+ dependencies[req_comp_name] = components[req_comp_name]
82
+
83
+ components[comp_name] = comp(**dependencies)
84
+
85
+ NodeContainer = make_dataclass(
86
+ cls_name="NodeContainer",
87
+ fields=[
88
+ (name, type(component))
89
+ for name, component
90
+ in components.items()
91
+ ],
92
+ frozen=True
93
+ )
94
+
95
+ return NodeContainer(**components)
@@ -1,9 +1,9 @@
1
- from pydantic import BaseModel, Field, PrivateAttr
1
+ from pydantic import BaseModel, PrivateAttr
2
2
  from ruamel.yaml import YAML
3
3
 
4
4
 
5
5
  class KoiNetworkConfig(BaseModel):
6
- nodes: dict[str, str] = Field(default_factory=dict)
6
+ nodes: dict[str, str] = {}
7
7
  _file_path: str = PrivateAttr(default="koi-net-config.yaml")
8
8
 
9
9
  @classmethod
@@ -0,0 +1,71 @@
1
+ import os
2
+ from pydantic import BaseModel, model_validator
3
+ from dotenv import load_dotenv
4
+ from rid_lib import RIDType
5
+ from rid_lib.types import KoiNetNode
6
+
7
+ from koi_net.protocol.secure import PrivateKey
8
+ from ..protocol.node import NodeProfile
9
+
10
+
11
+ class NodeContact(BaseModel):
12
+ rid: KoiNetNode | None = None
13
+ url: str | None = None
14
+
15
+ class KoiNetConfig(BaseModel):
16
+ """Config for KOI-net."""
17
+
18
+ node_name: str
19
+ node_rid: KoiNetNode | None = None
20
+ node_profile: NodeProfile
21
+
22
+ rid_types_of_interest: list[RIDType] = [KoiNetNode]
23
+
24
+ cache_directory_path: str = ".rid_cache"
25
+ event_queues_path: str = "event_queues.json"
26
+ private_key_pem_path: str = "priv_key.pem"
27
+
28
+ first_contact: NodeContact = NodeContact()
29
+
30
+ class EnvConfig(BaseModel):
31
+ """Config for environment variables.
32
+
33
+ Values set in the config are the variables names, and are loaded
34
+ from the environment at runtime. For example, if the config YAML
35
+ sets `priv_key_password: PRIV_KEY_PASSWORD` accessing
36
+ `priv_key_password` would retrieve the value of `PRIV_KEY_PASSWORD`
37
+ from the environment.
38
+ """
39
+
40
+ priv_key_password: str = "PRIV_KEY_PASSWORD"
41
+
42
+ def __init__(self, **kwargs):
43
+ super().__init__(**kwargs)
44
+ load_dotenv()
45
+
46
+ def __getattribute__(self, name):
47
+ value = super().__getattribute__(name)
48
+ if name in type(self).model_fields:
49
+ env_val = os.getenv(value)
50
+ if env_val is None:
51
+ raise ValueError(f"Required environment variable {value} not set")
52
+ return env_val
53
+ return value
54
+
55
+ class NodeConfig(BaseModel):
56
+ koi_net: KoiNetConfig
57
+ env: EnvConfig = EnvConfig()
58
+
59
+ @model_validator(mode="after")
60
+ def generate_rid_cascade(self):
61
+ if not self.koi_net.node_rid:
62
+ priv_key = PrivateKey.generate()
63
+ pub_key = priv_key.public_key()
64
+
65
+ self.koi_net.node_rid = pub_key.to_node_rid(self.koi_net.node_name)
66
+
67
+ with open(self.koi_net.private_key_pem_path, "w") as f:
68
+ f.write(priv_key.to_pem(self.env.priv_key_password))
69
+
70
+ self.koi_net.node_profile.public_key = pub_key.to_der()
71
+ return self
@@ -0,0 +1,31 @@
1
+ from pydantic import BaseModel, model_validator
2
+ from koi_net.config.core import NodeConfig, KoiNetConfig as BaseKoiNetConfig
3
+ from ..protocol.node import NodeProfile as BaseNodeProfile, NodeType, NodeProvides
4
+
5
+
6
+ class NodeProfile(BaseNodeProfile):
7
+ node_type: NodeType = NodeType.FULL
8
+
9
+ class KoiNetConfig(BaseKoiNetConfig):
10
+ node_profile: NodeProfile
11
+
12
+ class ServerConfig(BaseModel):
13
+ """Config for the node server (full node only)."""
14
+
15
+ host: str = "127.0.0.1"
16
+ port: int = 8000
17
+ path: str | None = "/koi-net"
18
+
19
+ @property
20
+ def url(self) -> str:
21
+ return f"http://{self.host}:{self.port}{self.path or ''}"
22
+
23
+ class FullNodeConfig(NodeConfig):
24
+ koi_net: KoiNetConfig
25
+ server: ServerConfig = ServerConfig()
26
+
27
+ @model_validator(mode="after")
28
+ def check_url(self):
29
+ if not self.koi_net.node_profile.base_url:
30
+ self.koi_net.node_profile.base_url = self.server.url
31
+ return self
@@ -0,0 +1,46 @@
1
+ from ruamel.yaml import YAML
2
+ from .core import NodeConfig
3
+
4
+
5
+ class ConfigLoader:
6
+ _config: NodeConfig
7
+
8
+ _file_path: str = "config.yaml"
9
+ _file_content: str
10
+
11
+ def __init__(self, config_cls: type[NodeConfig]):
12
+ self._config_cls = config_cls
13
+ self.load_from_yaml()
14
+
15
+ def __getattr__(self, name):
16
+ return getattr(self._config, name)
17
+
18
+ def load_from_yaml(self):
19
+ """Loads config state from YAML file."""
20
+ yaml = YAML()
21
+
22
+ try:
23
+ with open(self._file_path, "r") as f:
24
+ self._file_content = f.read()
25
+ config_data = yaml.load(self._file_content)
26
+ self._config = self._config_cls.model_validate(config_data)
27
+
28
+ except FileNotFoundError:
29
+ self._config = self._config_cls()
30
+
31
+ self.save_to_yaml()
32
+
33
+
34
+ def save_to_yaml(self):
35
+ yaml = YAML()
36
+
37
+ with open(self._file_path, "w") as f:
38
+ try:
39
+ config_data = self._config.model_dump(mode="json")
40
+ yaml.dump(config_data, f)
41
+ except Exception as e:
42
+ if self._file_content:
43
+ f.seek(0)
44
+ f.truncate()
45
+ f.write(self._file_content)
46
+ raise e
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel
2
+ from koi_net.config.core import NodeConfig, KoiNetConfig
3
+ from ..protocol.node import NodeProfile, NodeType, NodeProvides
4
+
5
+
6
+ class NodeProfile(NodeProfile):
7
+ base_url: str | None = None
8
+ node_type: NodeType = NodeType.PARTIAL
9
+
10
+ class KoiNetConfig(KoiNetConfig):
11
+ node_profile: NodeProfile
12
+
13
+ class PollerConfig(BaseModel):
14
+ polling_interval: int = 5
15
+
16
+ class PartialNodeConfig(NodeConfig):
17
+ koi_net: KoiNetConfig
18
+ poller: PollerConfig = PollerConfig()
@@ -0,0 +1,70 @@
1
+ from rid_lib.ext import Cache
2
+
3
+ from .config.loader import ConfigLoader
4
+ from .assembler import NodeAssembler
5
+ from .config.core import NodeConfig
6
+ from .processor.context import HandlerContext
7
+ from .effector import Effector
8
+ from .handshaker import Handshaker
9
+ from .identity import NodeIdentity
10
+ from .workers import KnowledgeProcessingWorker, EventProcessingWorker
11
+ from .lifecycle import NodeLifecycle
12
+ from .network.error_handler import ErrorHandler
13
+ from .network.event_queue import EventQueue
14
+ from .network.graph import NetworkGraph
15
+ from .network.request_handler import RequestHandler
16
+ from .network.resolver import NetworkResolver
17
+ from .network.response_handler import ResponseHandler
18
+ from .network.poll_event_buffer import PollEventBuffer
19
+ from .processor.pipeline import KnowledgePipeline
20
+ from .processor.kobj_queue import KobjQueue
21
+ from .secure import Secure
22
+ from .entrypoints import NodeServer, NodePoller
23
+ from .processor.knowledge_handlers import (
24
+ basic_manifest_handler,
25
+ basic_network_output_filter,
26
+ basic_rid_handler,
27
+ node_contact_handler,
28
+ edge_negotiation_handler,
29
+ forget_edge_on_node_deletion,
30
+ secure_profile_handler
31
+ )
32
+
33
+
34
+ class BaseNode(NodeAssembler):
35
+ config_cls = NodeConfig
36
+ kobj_queue = KobjQueue
37
+ event_queue = EventQueue
38
+ poll_event_buf = PollEventBuffer
39
+ config = ConfigLoader
40
+ knowledge_handlers = [
41
+ basic_rid_handler,
42
+ basic_manifest_handler,
43
+ secure_profile_handler,
44
+ edge_negotiation_handler,
45
+ node_contact_handler,
46
+ basic_network_output_filter,
47
+ forget_edge_on_node_deletion
48
+ ]
49
+ cache = lambda config: Cache(
50
+ directory_path=config.koi_net.cache_directory_path)
51
+ identity = NodeIdentity
52
+ graph = NetworkGraph
53
+ secure = Secure
54
+ handshaker = Handshaker
55
+ error_handler = ErrorHandler
56
+ request_handler = RequestHandler
57
+ response_handler = ResponseHandler
58
+ resolver = NetworkResolver
59
+ effector = Effector
60
+ handler_context = HandlerContext
61
+ pipeline = KnowledgePipeline
62
+ kobj_worker = KnowledgeProcessingWorker
63
+ event_worker = EventProcessingWorker
64
+ lifecycle = NodeLifecycle
65
+
66
+ class FullNode(BaseNode):
67
+ entrypoint = NodeServer
68
+
69
+ class PartialNode(BaseNode):
70
+ entrypoint = NodePoller
@@ -1,9 +1,8 @@
1
1
  """Implementations of default dereference actions."""
2
2
 
3
- from .context import ActionContext
4
3
  from rid_lib.types import KoiNetNode
5
4
  from rid_lib.ext import Bundle
6
- from .effector import Effector
5
+ from .effector import Effector, ActionContext
7
6
 
8
7
 
9
8
  @Effector.register_default_action(KoiNetNode)
@@ -1,4 +1,4 @@
1
- import logging
1
+ import structlog
2
2
  from typing import Callable
3
3
  from enum import StrEnum
4
4
  from rid_lib.ext import Cache, Bundle
@@ -6,11 +6,23 @@ from rid_lib.core import RID, RIDType
6
6
  from rid_lib.types import KoiNetNode
7
7
  from .network.resolver import NetworkResolver
8
8
  from .processor.kobj_queue import KobjQueue
9
- from .context import ActionContext
9
+ from .identity import NodeIdentity
10
10
 
11
- logger = logging.getLogger(__name__)
11
+ log = structlog.stdlib.get_logger()
12
12
 
13
13
 
14
+ class ActionContext:
15
+ """Provides action handlers access to other subsystems."""
16
+
17
+ identity: NodeIdentity
18
+
19
+ def __init__(
20
+ self,
21
+ identity: NodeIdentity,
22
+ ):
23
+ self.identity = identity
24
+
25
+
14
26
  class BundleSource(StrEnum):
15
27
  CACHE = "CACHE"
16
28
  ACTION = "ACTION"
@@ -35,12 +47,12 @@ class Effector:
35
47
  cache: Cache,
36
48
  resolver: NetworkResolver,
37
49
  kobj_queue: KobjQueue,
38
- action_context: ActionContext
50
+ identity: NodeIdentity
39
51
  ):
40
52
  self.cache = cache
41
53
  self.resolver = resolver
42
54
  self.kobj_queue = kobj_queue
43
- self.action_context = action_context
55
+ self.action_context = ActionContext(identity)
44
56
  self._action_table = self.__class__._action_table.copy()
45
57
 
46
58
  @classmethod
@@ -70,18 +82,18 @@ class Effector:
70
82
  bundle = self.cache.read(rid)
71
83
 
72
84
  if bundle:
73
- logger.debug("Cache hit")
85
+ log.debug("Cache hit")
74
86
  return bundle, BundleSource.CACHE
75
87
  else:
76
- logger.debug("Cache miss")
88
+ log.debug("Cache miss")
77
89
  return None
78
90
 
79
91
  def _try_action(self, rid: RID) -> tuple[Bundle, BundleSource] | None:
80
92
  if type(rid) not in self._action_table:
81
- logger.debug("No action available")
93
+ log.debug("No action available")
82
94
  return None
83
95
 
84
- logger.debug("Action available")
96
+ log.debug("Action available")
85
97
  func = self._action_table[type(rid)]
86
98
  bundle = func(
87
99
  ctx=self.action_context,
@@ -89,10 +101,10 @@ class Effector:
89
101
  )
90
102
 
91
103
  if bundle:
92
- logger.debug("Action hit")
104
+ log.debug("Action hit")
93
105
  return bundle, BundleSource.ACTION
94
106
  else:
95
- logger.debug("Action miss")
107
+ log.debug("Action miss")
96
108
  return None
97
109
 
98
110
 
@@ -100,10 +112,10 @@ class Effector:
100
112
  bundle, source = self.resolver.fetch_remote_bundle(rid)
101
113
 
102
114
  if bundle:
103
- logger.debug("Network hit")
115
+ log.debug("Network hit")
104
116
  return bundle, source
105
117
  else:
106
- logger.debug("Network miss")
118
+ log.debug("Network miss")
107
119
  return None
108
120
 
109
121
 
@@ -127,7 +139,7 @@ class Effector:
127
139
  handle_result: handles resulting bundle with knowledge pipeline when `True`
128
140
  """
129
141
 
130
- logger.debug(f"Dereferencing {rid!r}")
142
+ log.debug(f"Dereferencing {rid!r}")
131
143
 
132
144
  bundle, source = (
133
145
  # if `refresh_cache`, skip try cache
@@ -143,7 +155,7 @@ class Effector:
143
155
  and bundle is not None
144
156
  and source != BundleSource.CACHE
145
157
  ):
146
- self.kobj_queue.put_kobj(
158
+ self.kobj_queue.push(
147
159
  bundle=bundle,
148
160
  source=source if type(source) is KoiNetNode else None
149
161
  )
@@ -0,0 +1,2 @@
1
+ from .poller import NodePoller
2
+ from .server import NodeServer
@@ -0,0 +1,5 @@
1
+ from koi_net.config.core import NodeConfig
2
+
3
+
4
+ class EntryPoint:
5
+ def run(self): ...