koi-net 1.0.0b19__tar.gz → 1.1.0b2__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 (50) hide show
  1. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/.gitignore +2 -0
  2. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/PKG-INFO +5 -4
  3. koi_net-1.1.0b2/examples/coordinator.py +76 -0
  4. koi_net-1.0.0b19/examples/basic_partial_node.py → koi_net-1.1.0b2/examples/partial.py +9 -18
  5. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/pyproject.toml +6 -5
  6. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/config.py +44 -18
  7. koi_net-1.1.0b2/src/koi_net/context.py +55 -0
  8. koi_net-1.1.0b2/src/koi_net/core.py +176 -0
  9. koi_net-1.1.0b2/src/koi_net/default_actions.py +15 -0
  10. koi_net-1.1.0b2/src/koi_net/effector.py +139 -0
  11. koi_net-1.1.0b2/src/koi_net/identity.py +24 -0
  12. koi_net-1.1.0b2/src/koi_net/lifecycle.py +76 -0
  13. koi_net-1.1.0b2/src/koi_net/network/behavior.py +42 -0
  14. koi_net-1.1.0b2/src/koi_net/network/error_handler.py +50 -0
  15. koi_net-1.0.0b19/src/koi_net/network/interface.py → koi_net-1.1.0b2/src/koi_net/network/event_queue.py +52 -131
  16. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/network/graph.py +18 -33
  17. koi_net-1.1.0b2/src/koi_net/network/request_handler.py +195 -0
  18. koi_net-1.1.0b2/src/koi_net/network/resolver.py +150 -0
  19. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/network/response_handler.py +11 -3
  20. koi_net-1.1.0b2/src/koi_net/poller.py +45 -0
  21. koi_net-1.1.0b2/src/koi_net/processor/__init__.py +0 -0
  22. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/processor/default_handlers.py +57 -41
  23. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/processor/handler.py +3 -7
  24. koi_net-1.1.0b2/src/koi_net/processor/interface.py +101 -0
  25. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/processor/knowledge_object.py +10 -17
  26. koi_net-1.0.0b19/src/koi_net/processor/interface.py → koi_net-1.1.0b2/src/koi_net/processor/knowledge_pipeline.py +63 -143
  27. koi_net-1.1.0b2/src/koi_net/protocol/__init__.py +0 -0
  28. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/protocol/api_models.py +7 -1
  29. koi_net-1.0.0b19/src/koi_net/protocol/helpers.py → koi_net-1.1.0b2/src/koi_net/protocol/edge.py +21 -4
  30. koi_net-1.1.0b2/src/koi_net/protocol/envelope.py +54 -0
  31. koi_net-1.1.0b2/src/koi_net/protocol/errors.py +23 -0
  32. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/protocol/node.py +2 -1
  33. koi_net-1.1.0b2/src/koi_net/protocol/secure.py +106 -0
  34. koi_net-1.1.0b2/src/koi_net/secure.py +117 -0
  35. koi_net-1.1.0b2/src/koi_net/server.py +128 -0
  36. koi_net-1.0.0b19/examples/basic_coordinator_node.py +0 -141
  37. koi_net-1.0.0b19/src/koi_net/core.py +0 -126
  38. koi_net-1.0.0b19/src/koi_net/identity.py +0 -42
  39. koi_net-1.0.0b19/src/koi_net/network/__init__.py +0 -1
  40. koi_net-1.0.0b19/src/koi_net/network/request_handler.py +0 -149
  41. koi_net-1.0.0b19/src/koi_net/processor/__init__.py +0 -1
  42. koi_net-1.0.0b19/src/koi_net/protocol/edge.py +0 -20
  43. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/.github/workflows/publish-to-pypi.yml +0 -0
  44. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/LICENSE +0 -0
  45. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/README.md +0 -0
  46. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/requirements.txt +0 -0
  47. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/__init__.py +0 -0
  48. {koi_net-1.0.0b19/src/koi_net/protocol → koi_net-1.1.0b2/src/koi_net/network}/__init__.py +0 -0
  49. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/protocol/consts.py +0 -0
  50. {koi_net-1.0.0b19 → koi_net-1.1.0b2}/src/koi_net/protocol/event.py +0 -0
@@ -1,6 +1,8 @@
1
1
  rid-lib
2
2
  __pycache__
3
3
  *.json
4
+ *.pem
5
+ *.yaml
4
6
  venv
5
7
  .env
6
8
  prototypes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.0.0b19
3
+ Version: 1.1.0b2
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>
@@ -27,19 +27,20 @@ License: MIT License
27
27
  SOFTWARE.
28
28
  License-File: LICENSE
29
29
  Requires-Python: >=3.10
30
+ Requires-Dist: cryptography>=45.0.3
31
+ Requires-Dist: fastapi>=0.115.12
30
32
  Requires-Dist: httpx>=0.28.1
31
33
  Requires-Dist: networkx>=3.4.2
32
34
  Requires-Dist: pydantic>=2.10.6
33
35
  Requires-Dist: python-dotenv>=1.1.0
34
- Requires-Dist: rid-lib>=3.2.3
36
+ Requires-Dist: rid-lib>=3.2.7
35
37
  Requires-Dist: ruamel-yaml>=0.18.10
38
+ Requires-Dist: uvicorn>=0.34.2
36
39
  Provides-Extra: dev
37
40
  Requires-Dist: build; extra == 'dev'
38
41
  Requires-Dist: twine>=6.0; extra == 'dev'
39
42
  Provides-Extra: examples
40
- Requires-Dist: fastapi; extra == 'examples'
41
43
  Requires-Dist: rich; extra == 'examples'
42
- Requires-Dist: uvicorn; extra == 'examples'
43
44
  Description-Content-Type: text/markdown
44
45
 
45
46
  # KOI-net
@@ -0,0 +1,76 @@
1
+ import logging
2
+ from rich.logging import RichHandler
3
+ from pydantic import Field
4
+ from rid_lib.types import KoiNetNode, KoiNetEdge
5
+ from koi_net.config import NodeConfig, KoiNetConfig
6
+ from koi_net.protocol.node import NodeProfile, NodeProvides, NodeType
7
+ from koi_net import NodeInterface
8
+ from koi_net.context import HandlerContext
9
+ from koi_net.processor.handler import HandlerType
10
+ from koi_net.processor.knowledge_object import KnowledgeObject
11
+ from koi_net.protocol.event import Event, EventType
12
+ from koi_net.protocol.edge import EdgeType, generate_edge_bundle
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
+ class CoordinatorConfig(NodeConfig):
25
+ koi_net: KoiNetConfig = Field(default_factory = lambda:
26
+ KoiNetConfig(
27
+ node_name="coordinator",
28
+ node_profile=NodeProfile(
29
+ node_type=NodeType.FULL,
30
+ provides=NodeProvides(
31
+ event=[KoiNetNode, KoiNetEdge],
32
+ state=[KoiNetNode, KoiNetEdge]
33
+ )
34
+ ),
35
+ cache_directory_path=".coordinator_rid_cache",
36
+ event_queues_path="coordinator_event_queues.json",
37
+ private_key_pem_path="coordinator_priv_key.pem"
38
+ )
39
+ )
40
+
41
+ node = NodeInterface(
42
+ config=CoordinatorConfig.load_from_yaml("coordinator_config.yaml"),
43
+ use_kobj_processor_thread=True
44
+ )
45
+
46
+ @node.processor.pipeline.register_handler(HandlerType.Network, rid_types=[KoiNetNode])
47
+ def handshake_handler(ctx: HandlerContext, kobj: KnowledgeObject):
48
+ logger.info("Handling node handshake")
49
+
50
+ # only respond if node declares itself as NEW
51
+ if kobj.event_type != EventType.NEW:
52
+ return
53
+
54
+ logger.info("Sharing this node's bundle with peer")
55
+ identity_bundle = ctx.effector.deref(ctx.identity.rid)
56
+ ctx.event_queue.push_event_to(
57
+ event=Event.from_bundle(EventType.NEW, identity_bundle),
58
+ node=kobj.rid,
59
+ flush=True
60
+ )
61
+
62
+ logger.info("Proposing new edge")
63
+ # defer handling of proposed edge
64
+
65
+ edge_bundle = generate_edge_bundle(
66
+ source=kobj.rid,
67
+ target=ctx.identity.rid,
68
+ edge_type=EdgeType.WEBHOOK,
69
+ rid_types=[KoiNetNode, KoiNetEdge]
70
+ )
71
+
72
+ ctx.handle(rid=edge_bundle.rid, event_type=EventType.FORGET)
73
+ ctx.handle(bundle=edge_bundle)
74
+
75
+ if __name__ == "__main__":
76
+ node.server.run()
@@ -1,9 +1,7 @@
1
- import time
2
1
  import logging
3
2
  from pydantic import Field
4
3
  from rich.logging import RichHandler
5
4
  from koi_net import NodeInterface
6
- from koi_net.processor.knowledge_object import KnowledgeSource
7
5
  from koi_net.protocol.node import NodeProfile, NodeType
8
6
  from koi_net.config import NodeConfig, KoiNetConfig
9
7
 
@@ -18,29 +16,22 @@ logging.getLogger("koi_net").setLevel(logging.DEBUG)
18
16
  logger = logging.getLogger(__name__)
19
17
 
20
18
 
21
- class CoordinatorNodeConfig(NodeConfig):
19
+ class PartialNodeConfig(NodeConfig):
22
20
  koi_net: KoiNetConfig | None = Field(default_factory = lambda:
23
21
  KoiNetConfig(
24
- node_name="coordinator",
22
+ node_name="partial",
25
23
  node_profile=NodeProfile(
26
- node_type=NodeType.FULL
24
+ node_type=NodeType.PARTIAL
27
25
  ),
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"
26
+ cache_directory_path=".partial_rid_cache",
27
+ event_queues_path="partial_event_queues.json",
28
+ private_key_pem_path="partial_priv_key.pem"
31
29
  )
32
30
  )
33
31
 
34
-
35
32
  node = NodeInterface(
36
- config=CoordinatorNodeConfig.load_from_yaml("basical_partial_config.yaml")
33
+ config=PartialNodeConfig.load_from_yaml("partial_config.yaml")
37
34
  )
38
35
 
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)
36
+ if __name__ == "__main__":
37
+ node.poller.run()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "koi-net"
7
- version = "1.0.0-beta.19"
7
+ version = "1.1.0-beta.2"
8
8
  description = "Implementation of KOI-net protocol in Python"
9
9
  authors = [
10
10
  {name = "Luke Miller", email = "luke@block.science"}
@@ -13,20 +13,21 @@ readme = "README.md"
13
13
  requires-python = ">=3.10"
14
14
  license = {file = "LICENSE"}
15
15
  dependencies = [
16
- "rid-lib>=3.2.3",
16
+ "rid-lib>=3.2.7",
17
17
  "networkx>=3.4.2",
18
18
  "httpx>=0.28.1",
19
19
  "pydantic>=2.10.6",
20
20
  "ruamel.yaml>=0.18.10",
21
- "python-dotenv>=1.1.0"
21
+ "python-dotenv>=1.1.0",
22
+ "cryptography>=45.0.3",
23
+ "fastapi>=0.115.12",
24
+ "uvicorn>=0.34.2"
22
25
  ]
23
26
 
24
27
  [project.optional-dependencies]
25
28
  dev = ["twine>=6.0", "build"]
26
29
  examples = [
27
30
  "rich",
28
- "fastapi",
29
- "uvicorn"
30
31
  ]
31
32
 
32
33
  [project.urls]
@@ -1,32 +1,43 @@
1
1
  import os
2
- from typing import TypeVar
3
2
  from ruamel.yaml import YAML
4
- from koi_net.protocol.node import NodeProfile
5
- from rid_lib.types import KoiNetNode
6
3
  from pydantic import BaseModel, Field, PrivateAttr
7
4
  from dotenv import load_dotenv
5
+ from rid_lib.ext.utils import sha256_hash
6
+ from rid_lib.types import KoiNetNode
7
+ from .protocol.secure import PrivateKey
8
+ from .protocol.node import NodeProfile, NodeType
8
9
 
9
10
 
10
11
  class ServerConfig(BaseModel):
11
- host: str | None = "127.0.0.1"
12
- port: int | None = 8000
12
+ host: str = "127.0.0.1"
13
+ port: int = 8000
13
14
  path: str | None = "/koi-net"
14
15
 
15
16
  @property
16
17
  def url(self) -> str:
17
18
  return f"http://{self.host}:{self.port}{self.path or ''}"
18
19
 
20
+ class NodeContact(BaseModel):
21
+ rid: KoiNetNode | None = None
22
+ url: str | None = None
23
+
19
24
  class KoiNetConfig(BaseModel):
20
25
  node_name: str
21
26
  node_rid: KoiNetNode | None = None
22
27
  node_profile: NodeProfile
23
28
 
24
- cache_directory_path: str | None = ".rid_cache"
25
- event_queues_path: str | None = "event_queues.json"
26
-
27
- first_contact: str | None = None
29
+ cache_directory_path: str = ".rid_cache"
30
+ event_queues_path: str = "event_queues.json"
31
+ private_key_pem_path: str = "priv_key.pem"
32
+ polling_interval: int = 5
33
+
34
+ first_contact: NodeContact = Field(default_factory=NodeContact)
35
+
36
+ _priv_key: PrivateKey | None = PrivateAttr(default=None)
28
37
 
29
38
  class EnvConfig(BaseModel):
39
+ priv_key_password: str | None = "PRIV_KEY_PASSWORD"
40
+
30
41
  def __init__(self, **kwargs):
31
42
  super().__init__(**kwargs)
32
43
  load_dotenv()
@@ -41,8 +52,10 @@ class EnvConfig(BaseModel):
41
52
  return value
42
53
 
43
54
  class NodeConfig(BaseModel):
44
- server: ServerConfig | None = Field(default_factory=ServerConfig)
55
+ server: ServerConfig = Field(default_factory=ServerConfig)
45
56
  koi_net: KoiNetConfig
57
+ env: EnvConfig = Field(default_factory=EnvConfig)
58
+
46
59
  _file_path: str = PrivateAttr(default="config.yaml")
47
60
  _file_content: str | None = PrivateAttr(default=None)
48
61
 
@@ -72,13 +85,27 @@ class NodeConfig(BaseModel):
72
85
 
73
86
  config._file_path = file_path
74
87
 
75
- if generate_missing:
76
- config.koi_net.node_rid = (
77
- config.koi_net.node_rid or KoiNetNode.generate(config.koi_net.node_name)
78
- )
79
- config.koi_net.node_profile.base_url = (
80
- config.koi_net.node_profile.base_url or config.server.url
81
- )
88
+ if generate_missing:
89
+ if not config.koi_net.node_rid:
90
+ priv_key = PrivateKey.generate()
91
+ pub_key = priv_key.public_key()
92
+
93
+ config.koi_net.node_rid = KoiNetNode(
94
+ config.koi_net.node_name,
95
+ sha256_hash(pub_key.to_der())
96
+ )
97
+
98
+ with open(config.koi_net.private_key_pem_path, "w") as f:
99
+ f.write(
100
+ priv_key.to_pem(config.env.priv_key_password)
101
+ )
102
+
103
+ config.koi_net.node_profile.public_key = pub_key.to_der()
104
+
105
+ if config.koi_net.node_profile.node_type == NodeType.FULL:
106
+ config.koi_net.node_profile.base_url = (
107
+ config.koi_net.node_profile.base_url or config.server.url
108
+ )
82
109
 
83
110
  config.save_to_yaml()
84
111
 
@@ -98,4 +125,3 @@ class NodeConfig(BaseModel):
98
125
  f.write(self._file_content)
99
126
  raise e
100
127
 
101
- ConfigType = TypeVar("ConfigType", bound=NodeConfig)
@@ -0,0 +1,55 @@
1
+
2
+ from koi_net.effector import Effector
3
+ from rid_lib.ext import Cache
4
+ from .network.graph import NetworkGraph
5
+ from .network.event_queue import NetworkEventQueue
6
+ from .network.request_handler import RequestHandler
7
+ from .identity import NodeIdentity
8
+ from .processor.interface import ProcessorInterface
9
+
10
+
11
+ class ActionContext:
12
+ identity: NodeIdentity
13
+ effector: Effector
14
+
15
+ def __init__(
16
+ self,
17
+ identity: NodeIdentity,
18
+ effector: Effector
19
+ ):
20
+ self.identity = identity
21
+ self.effector = effector
22
+
23
+
24
+ class HandlerContext:
25
+ identity: NodeIdentity
26
+ cache: Cache
27
+ event_queue: NetworkEventQueue
28
+ graph: NetworkGraph
29
+ request_handler: RequestHandler
30
+ effector: Effector
31
+ _processor: ProcessorInterface | None
32
+
33
+ def __init__(
34
+ self,
35
+ identity: NodeIdentity,
36
+ cache: Cache,
37
+ event_queue: NetworkEventQueue,
38
+ graph: NetworkGraph,
39
+ request_handler: RequestHandler,
40
+ effector: Effector
41
+ ):
42
+ self.identity = identity
43
+ self.cache = cache
44
+ self.event_queue = event_queue
45
+ self.graph = graph
46
+ self.request_handler = request_handler
47
+ self.effector = effector
48
+ self._processor = None
49
+
50
+ def set_processor(self, processor: ProcessorInterface):
51
+ self._processor = processor
52
+
53
+ @property
54
+ def handle(self):
55
+ return self._processor.handle
@@ -0,0 +1,176 @@
1
+ import logging
2
+ from koi_net.protocol.node import NodeType
3
+ from rid_lib.ext import Cache
4
+ from .network.resolver import NetworkResolver
5
+ from .network.event_queue import NetworkEventQueue
6
+ from .network.graph import NetworkGraph
7
+ from .network.request_handler import RequestHandler
8
+ from .network.response_handler import ResponseHandler
9
+ from .network.error_handler import ErrorHandler
10
+ from .network.behavior import Actor
11
+ from .processor.interface import ProcessorInterface
12
+ from .processor import default_handlers
13
+ from .processor.handler import KnowledgeHandler
14
+ from .processor.knowledge_pipeline import KnowledgePipeline
15
+ from .identity import NodeIdentity
16
+ from .secure import Secure
17
+ from .config import NodeConfig
18
+ from .context import HandlerContext, ActionContext
19
+ from .effector import Effector
20
+ from .server import NodeServer
21
+ from .lifecycle import NodeLifecycle
22
+ from .poller import NodePoller
23
+ from . import default_actions
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+
29
+ class NodeInterface:
30
+ config: NodeConfig
31
+ cache: Cache
32
+ identity: NodeIdentity
33
+ resolver: NetworkResolver
34
+ event_queue: NetworkEventQueue
35
+ graph: NetworkGraph
36
+ processor: ProcessorInterface
37
+ secure: Secure
38
+ server: NodeServer
39
+
40
+ use_kobj_processor_thread: bool
41
+
42
+ def __init__(
43
+ self,
44
+ config: NodeConfig,
45
+ use_kobj_processor_thread: bool = False,
46
+ handlers: list[KnowledgeHandler] | None = None,
47
+ cache: Cache | None = None,
48
+ processor: ProcessorInterface | None = None
49
+ ):
50
+ self.config = config
51
+ self.cache = cache or Cache(
52
+ directory_path=self.config.koi_net.cache_directory_path
53
+ )
54
+
55
+ self.identity = NodeIdentity(config=self.config)
56
+ self.effector = Effector(cache=self.cache)
57
+
58
+ self.graph = NetworkGraph(
59
+ cache=self.cache,
60
+ identity=self.identity
61
+ )
62
+
63
+ self.secure = Secure(
64
+ identity=self.identity,
65
+ effector=self.effector,
66
+ config=self.config
67
+ )
68
+
69
+ self.request_handler = RequestHandler(
70
+ effector=self.effector,
71
+ identity=self.identity,
72
+ secure=self.secure
73
+ )
74
+
75
+ self.response_handler = ResponseHandler(self.cache, self.effector)
76
+
77
+ self.resolver = NetworkResolver(
78
+ config=self.config,
79
+ cache=self.cache,
80
+ identity=self.identity,
81
+ graph=self.graph,
82
+ request_handler=self.request_handler,
83
+ effector=self.effector
84
+ )
85
+
86
+ self.event_queue = NetworkEventQueue(
87
+ config=self.config,
88
+ cache=self.cache,
89
+ identity=self.identity,
90
+ graph=self.graph,
91
+ request_handler=self.request_handler,
92
+ effector=self.effector
93
+ )
94
+
95
+ self.actor = Actor(
96
+ identity=self.identity,
97
+ effector=self.effector,
98
+ event_queue=self.event_queue
99
+ )
100
+
101
+ # pull all handlers defined in default_handlers module
102
+ if handlers is None:
103
+ handlers = [
104
+ obj for obj in vars(default_handlers).values()
105
+ if isinstance(obj, KnowledgeHandler)
106
+ ]
107
+
108
+ self.use_kobj_processor_thread = use_kobj_processor_thread
109
+
110
+ self.action_context = ActionContext(
111
+ identity=self.identity,
112
+ effector=self.effector
113
+ )
114
+
115
+ self.handler_context = HandlerContext(
116
+ identity=self.identity,
117
+ cache=self.cache,
118
+ event_queue=self.event_queue,
119
+ graph=self.graph,
120
+ request_handler=self.request_handler,
121
+ effector=self.effector
122
+ )
123
+
124
+ self.pipeline = KnowledgePipeline(
125
+ handler_context=self.handler_context,
126
+ cache=self.cache,
127
+ request_handler=self.request_handler,
128
+ event_queue=self.event_queue,
129
+ graph=self.graph,
130
+ default_handlers=handlers
131
+ )
132
+
133
+ self.processor = processor or ProcessorInterface(
134
+ pipeline=self.pipeline,
135
+ use_kobj_processor_thread=self.use_kobj_processor_thread
136
+ )
137
+
138
+ self.error_handler = ErrorHandler(
139
+ processor=self.processor,
140
+ actor=self.actor
141
+ )
142
+
143
+ self.request_handler.set_error_handler(self.error_handler)
144
+
145
+ self.handler_context.set_processor(self.processor)
146
+
147
+ self.effector.set_processor(self.processor)
148
+ self.effector.set_resolver(self.resolver)
149
+ self.effector.set_action_context(self.action_context)
150
+
151
+ self.lifecycle = NodeLifecycle(
152
+ config=self.config,
153
+ identity=self.identity,
154
+ graph=self.graph,
155
+ processor=self.processor,
156
+ effector=self.effector,
157
+ actor=self.actor,
158
+ use_kobj_processor_thread=use_kobj_processor_thread
159
+ )
160
+
161
+ # if self.config.koi_net.node_profile.node_type == NodeType.FULL:
162
+ self.server = NodeServer(
163
+ config=self.config,
164
+ lifecycle=self.lifecycle,
165
+ secure=self.secure,
166
+ processor=self.processor,
167
+ event_queue=self.event_queue,
168
+ response_handler=self.response_handler
169
+ )
170
+
171
+ self.poller = NodePoller(
172
+ processor=self.processor,
173
+ lifecycle=self.lifecycle,
174
+ resolver=self.resolver,
175
+ config=self.config
176
+ )
@@ -0,0 +1,15 @@
1
+ from .context import ActionContext
2
+ from rid_lib.types import KoiNetNode
3
+ from rid_lib.ext import Bundle
4
+ from .effector import Effector
5
+
6
+
7
+ @Effector.register_default_action(KoiNetNode)
8
+ def dereference_koi_node(ctx: ActionContext, rid: KoiNetNode) -> Bundle:
9
+ if rid != ctx.identity.rid:
10
+ return
11
+
12
+ return Bundle.generate(
13
+ rid=ctx.identity.rid,
14
+ contents=ctx.identity.profile.model_dump()
15
+ )
@@ -0,0 +1,139 @@
1
+ import logging
2
+ from typing import Callable
3
+ from enum import StrEnum
4
+ from rid_lib.ext import Cache, Bundle
5
+ from rid_lib.core import RID, RIDType
6
+ from rid_lib.types import KoiNetNode
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from .network.resolver import NetworkResolver
12
+ from .processor.interface import ProcessorInterface
13
+ from .context import ActionContext
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class BundleSource(StrEnum):
19
+ CACHE = "CACHE"
20
+ ACTION = "ACTION"
21
+
22
+ class Effector:
23
+ cache: Cache
24
+ resolver: "NetworkResolver | None"
25
+ processor: "ProcessorInterface | None"
26
+ action_context: "ActionContext | None"
27
+ _action_table: dict[
28
+ type[RID],
29
+ Callable[
30
+ ["ActionContext", RID],
31
+ Bundle | None
32
+ ]
33
+ ] = dict()
34
+
35
+ def __init__(
36
+ self,
37
+ cache: Cache,
38
+ ):
39
+ self.cache = cache
40
+ self.resolver = None
41
+ self.processor = None
42
+ self.action_context = None
43
+ self._action_table = self.__class__._action_table.copy()
44
+
45
+ def set_processor(self, processor: "ProcessorInterface"):
46
+ self.processor = processor
47
+
48
+ def set_resolver(self, resolver: "NetworkResolver"):
49
+ self.resolver = resolver
50
+
51
+ def set_action_context(self, action_context: "ActionContext"):
52
+ self.action_context = action_context
53
+
54
+ @classmethod
55
+ def register_default_action(cls, rid_type: RIDType):
56
+ def decorator(func: Callable) -> Callable:
57
+ cls._action_table[rid_type] = func
58
+ return func
59
+ return decorator
60
+
61
+ def register_action(self, rid_type: RIDType):
62
+ def decorator(func: Callable) -> Callable:
63
+ self._action_table[rid_type] = func
64
+ return func
65
+ return decorator
66
+
67
+ def _try_cache(self, rid: RID) -> tuple[Bundle, BundleSource] | None:
68
+ bundle = self.cache.read(rid)
69
+
70
+ if bundle:
71
+ logger.debug("Cache hit")
72
+ return bundle, BundleSource.CACHE
73
+ else:
74
+ logger.debug("Cache miss")
75
+ return None
76
+
77
+ def _try_action(self, rid: RID) -> tuple[Bundle, BundleSource] | None:
78
+ if type(rid) not in self._action_table:
79
+ logger.debug("No action available")
80
+ return None
81
+
82
+ logger.debug("Action available")
83
+ func = self._action_table[type(rid)]
84
+ bundle = func(
85
+ ctx=self.action_context,
86
+ rid=rid
87
+ )
88
+
89
+ if bundle:
90
+ logger.debug("Action hit")
91
+ return bundle, BundleSource.ACTION
92
+ else:
93
+ logger.debug("Action miss")
94
+ return None
95
+
96
+
97
+ def _try_network(self, rid: RID) -> tuple[Bundle, KoiNetNode] | None:
98
+ bundle, source = self.resolver.fetch_remote_bundle(rid)
99
+
100
+ if bundle:
101
+ logger.debug("Network hit")
102
+ return bundle, source
103
+ else:
104
+ logger.debug("Network miss")
105
+ return None
106
+
107
+
108
+ def deref(
109
+ self,
110
+ rid: RID,
111
+ refresh_cache: bool = False,
112
+ use_network: bool = False,
113
+ handle_result: bool = True
114
+ ) -> Bundle | None:
115
+ logger.debug(f"Dereferencing {rid!r}")
116
+
117
+ bundle, source = (
118
+ # if `refresh_cache`, skip try cache
119
+ not refresh_cache and self._try_cache(rid) or
120
+ self._try_action(rid) or
121
+ use_network and self._try_network(rid) or
122
+ # if not found, bundle and source set to None
123
+ (None, None)
124
+ )
125
+
126
+ if (
127
+ handle_result
128
+ and bundle is not None
129
+ and source != BundleSource.CACHE
130
+ ):
131
+ self.processor.handle(
132
+ bundle=bundle,
133
+ source=source if type(source) is KoiNetNode else None
134
+ )
135
+
136
+ # TODO: refactor for general solution, param to write through to cache before continuing
137
+ # like `self.processor.kobj_queue.join()``
138
+
139
+ return bundle