koi-net 1.1.0b7__py3-none-any.whl → 1.2.0b1__py3-none-any.whl

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 (41) hide show
  1. koi_net/__init__.py +1 -1
  2. koi_net/behaviors.py +51 -0
  3. koi_net/cli/__init__.py +1 -0
  4. koi_net/cli/commands.py +99 -0
  5. koi_net/cli/models.py +41 -0
  6. koi_net/config.py +34 -0
  7. koi_net/context.py +16 -20
  8. koi_net/core.py +208 -171
  9. koi_net/default_actions.py +10 -1
  10. koi_net/effector.py +39 -24
  11. koi_net/{network/behavior.py → handshaker.py} +17 -20
  12. koi_net/kobj_worker.py +45 -0
  13. koi_net/lifecycle.py +71 -41
  14. koi_net/models.py +14 -0
  15. koi_net/network/error_handler.py +12 -10
  16. koi_net/network/event_queue.py +14 -184
  17. koi_net/network/graph.py +14 -9
  18. koi_net/network/request_handler.py +31 -19
  19. koi_net/network/resolver.py +6 -9
  20. koi_net/network/response_handler.py +5 -6
  21. koi_net/poll_event_buffer.py +17 -0
  22. koi_net/poller.py +12 -5
  23. koi_net/processor/default_handlers.py +84 -35
  24. koi_net/processor/event_worker.py +121 -0
  25. koi_net/processor/handler.py +4 -2
  26. koi_net/processor/knowledge_object.py +19 -7
  27. koi_net/processor/knowledge_pipeline.py +7 -26
  28. koi_net/processor/kobj_queue.py +51 -0
  29. koi_net/protocol/api_models.py +3 -2
  30. koi_net/protocol/node.py +3 -3
  31. koi_net/secure.py +28 -8
  32. koi_net/server.py +25 -9
  33. koi_net/utils.py +18 -0
  34. koi_net/worker.py +10 -0
  35. {koi_net-1.1.0b7.dist-info → koi_net-1.2.0b1.dist-info}/METADATA +7 -3
  36. koi_net-1.2.0b1.dist-info/RECORD +49 -0
  37. koi_net-1.2.0b1.dist-info/entry_points.txt +2 -0
  38. koi_net/processor/interface.py +0 -101
  39. koi_net-1.1.0b7.dist-info/RECORD +0 -38
  40. {koi_net-1.1.0b7.dist-info → koi_net-1.2.0b1.dist-info}/WHEEL +0 -0
  41. {koi_net-1.1.0b7.dist-info → koi_net-1.2.0b1.dist-info}/licenses/LICENSE +0 -0
koi_net/__init__.py CHANGED
@@ -1 +1 @@
1
- from .core import NodeInterface
1
+ from .core import NodeContainer
koi_net/behaviors.py ADDED
@@ -0,0 +1,51 @@
1
+ from logging import getLogger
2
+ from rid_lib.ext import Cache
3
+ from rid_lib.types import KoiNetNode
4
+ from rid_lib import RIDType
5
+ from koi_net.identity import NodeIdentity
6
+ from koi_net.network.event_queue import EventQueue
7
+ from koi_net.network.request_handler import RequestHandler
8
+ from koi_net.network.resolver import NetworkResolver
9
+ from koi_net.processor.kobj_queue import KobjQueue
10
+ from koi_net.protocol.api_models import ErrorResponse
11
+ from .protocol.event import Event, EventType
12
+
13
+
14
+ logger = getLogger(__name__)
15
+
16
+
17
+
18
+ class Behaviors:
19
+ def __init__(self, cache: Cache, identity: NodeIdentity, event_queue: EventQueue, resolver: NetworkResolver, request_handler: RequestHandler, kobj_queue: KobjQueue):
20
+ self.cache = cache
21
+ self.identity = identity
22
+ self.event_queue = event_queue
23
+ self.resolver = resolver
24
+ self.request_handler = request_handler
25
+ self.kobj_queue = kobj_queue
26
+
27
+ def identify_coordinators(self) -> list[KoiNetNode]:
28
+ """Returns node's providing state for `orn:koi-net.node`."""
29
+ return self.resolver.get_state_providers(KoiNetNode)
30
+
31
+ def catch_up_with(self, target: KoiNetNode, rid_types: list[RIDType] = []):
32
+ """Fetches and processes knowledge objects from target node.
33
+ Args:
34
+ target: Node to catch up with
35
+ rid_types: RID types to fetch from target (all types if list is empty)
36
+ """
37
+ logger.debug(f"catching up with {target} on {rid_types or 'all types'}")
38
+ payload = self.request_handler.fetch_manifests(
39
+ node=target,
40
+ rid_types=rid_types
41
+ )
42
+ if type(payload) == ErrorResponse:
43
+ logger.debug("failed to reach node")
44
+ return
45
+ for manifest in payload.manifests:
46
+ if manifest.rid == self.identity.rid:
47
+ continue
48
+ self.kobj_queue.put_kobj(
49
+ manifest=manifest,
50
+ source=target
51
+ )
@@ -0,0 +1 @@
1
+ from .commands import app
@@ -0,0 +1,99 @@
1
+ import os
2
+ import typer
3
+ from typing import Callable
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+
7
+ from importlib.metadata import entry_points
8
+
9
+ from koi_net.cli.models import KoiNetworkConfig
10
+ from koi_net.core import NodeInterface
11
+ import shutil
12
+
13
+ app = typer.Typer()
14
+ console = Console()
15
+
16
+ installed_nodes = entry_points(group='koi_net.node')
17
+
18
+ net_config = KoiNetworkConfig.load_from_yaml()
19
+
20
+ @app.command()
21
+ def list_node_types():
22
+ table = Table(title="installed node types")
23
+ table.add_column("name", style="cyan")
24
+ table.add_column("module", style="magenta")
25
+
26
+ for node in installed_nodes:
27
+ table.add_row(node.name, node.module)
28
+ console.print(table)
29
+
30
+ @app.command()
31
+ def list_nodes():
32
+ table = Table(title="created nodes")
33
+ table.add_column("name", style="cyan")
34
+ table.add_column("rid", style="magenta")
35
+
36
+ for dir in os.listdir('.'):
37
+ if not os.path.isdir(dir):
38
+ continue
39
+ for file in os.listdir(dir):
40
+ file_path = os.path.join(dir, file)
41
+ if not (os.path.isfile(file_path) and file == "config.yaml"):
42
+ continue
43
+
44
+ print(os.getcwd())
45
+ os.chdir(dir)
46
+ print(os.getcwd())
47
+
48
+ node_type = net_config.nodes.get(dir)
49
+
50
+ ep = list(installed_nodes.select(name=node_type))[0]
51
+ create_node: Callable[[], NodeInterface] = ep.load()
52
+
53
+ node = create_node()
54
+
55
+ print(ep)
56
+ print(dir)
57
+ print(node.identity.rid)
58
+
59
+ table.add_row(dir, str(node.identity.rid))
60
+
61
+ os.chdir('..')
62
+ print(os.getcwd())
63
+
64
+ console.print(table)
65
+
66
+ @app.command()
67
+ def create(type: str, name: str):
68
+ # if name not in installed_nodes:
69
+ # console.print(f"[bold red]Error:[/bold red] node type '{name}' doesn't exist")
70
+ # raise typer.Exit(code=1)
71
+
72
+ eps = installed_nodes.select(name=type)
73
+ if eps:
74
+ ep = list(eps)[0]
75
+
76
+ os.mkdir(name)
77
+ os.chdir(name)
78
+
79
+ ep.load()
80
+
81
+ os.chdir('..')
82
+
83
+ net_config.nodes[name] = type
84
+ net_config.save_to_yaml()
85
+
86
+ @app.command()
87
+ def remove(name: str):
88
+ shutil.rmtree(name)
89
+ net_config.nodes.pop(name, None)
90
+ net_config.save_to_yaml()
91
+
92
+ @app.command()
93
+ def start(name: str):
94
+ os.chdir(name)
95
+ node_type = net_config.nodes.get(name)
96
+ ep = list(installed_nodes.select(name=node_type))[0]
97
+ create_node: Callable[[], NodeInterface] = ep.load()
98
+
99
+ create_node().server.run()
koi_net/cli/models.py ADDED
@@ -0,0 +1,41 @@
1
+ from pydantic import BaseModel, Field, PrivateAttr
2
+ from ruamel.yaml import YAML
3
+
4
+
5
+ class KoiNetworkConfig(BaseModel):
6
+ nodes: dict[str, str] = Field(default_factory=dict)
7
+ _file_path: str = PrivateAttr(default="koi-net-config.yaml")
8
+
9
+ @classmethod
10
+ def load_from_yaml(
11
+ cls,
12
+ file_path: str = "koi-net-config.yaml",
13
+ ):
14
+ yaml = YAML()
15
+
16
+ try:
17
+ with open(file_path, "r") as f:
18
+ file_content = f.read()
19
+ config_data = yaml.load(file_content)
20
+ config = cls.model_validate(config_data)
21
+
22
+ except FileNotFoundError:
23
+ config = cls()
24
+
25
+ config._file_path = file_path
26
+ config.save_to_yaml()
27
+ return config
28
+
29
+ def save_to_yaml(self):
30
+ yaml = YAML()
31
+
32
+ with open(self._file_path, "w") as f:
33
+ try:
34
+ config_data = self.model_dump(mode="json")
35
+ yaml.dump(config_data, f)
36
+ except Exception as e:
37
+ if self._file_content:
38
+ f.seek(0)
39
+ f.truncate()
40
+ f.write(self._file_content)
41
+ raise e
koi_net/config.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from rid_lib import RIDType
2
3
  from ruamel.yaml import YAML
3
4
  from pydantic import BaseModel, Field, PrivateAttr
4
5
  from dotenv import load_dotenv
@@ -9,6 +10,8 @@ from .protocol.node import NodeProfile, NodeType
9
10
 
10
11
 
11
12
  class ServerConfig(BaseModel):
13
+ """Config for the node server (full node only)."""
14
+
12
15
  host: str = "127.0.0.1"
13
16
  port: int = 8000
14
17
  path: str | None = "/koi-net"
@@ -22,10 +25,15 @@ class NodeContact(BaseModel):
22
25
  url: str | None = None
23
26
 
24
27
  class KoiNetConfig(BaseModel):
28
+ """Config for KOI-net."""
29
+
25
30
  node_name: str
26
31
  node_rid: KoiNetNode | None = None
27
32
  node_profile: NodeProfile
28
33
 
34
+ rid_types_of_interest: list[RIDType] = Field(
35
+ default_factory=lambda: [KoiNetNode])
36
+
29
37
  cache_directory_path: str = ".rid_cache"
30
38
  event_queues_path: str = "event_queues.json"
31
39
  private_key_pem_path: str = "priv_key.pem"
@@ -36,6 +44,15 @@ class KoiNetConfig(BaseModel):
36
44
  _priv_key: PrivateKey | None = PrivateAttr(default=None)
37
45
 
38
46
  class EnvConfig(BaseModel):
47
+ """Config for environment variables.
48
+
49
+ Values set in the config are the variables names, and are loaded
50
+ from the environment at runtime. For example, if the config YAML
51
+ sets `priv_key_password: PRIV_KEY_PASSWORD` accessing
52
+ `priv_key_password` would retrieve the value of `PRIV_KEY_PASSWORD`
53
+ from the environment.
54
+ """
55
+
39
56
  priv_key_password: str | None = "PRIV_KEY_PASSWORD"
40
57
 
41
58
  def __init__(self, **kwargs):
@@ -52,6 +69,12 @@ class EnvConfig(BaseModel):
52
69
  return value
53
70
 
54
71
  class NodeConfig(BaseModel):
72
+ """Base configuration class for all nodes.
73
+
74
+ Designed to be extensible for custom node implementations. Classes
75
+ inheriting from `NodeConfig` may add additional config groups.
76
+ """
77
+
55
78
  server: ServerConfig = Field(default_factory=ServerConfig)
56
79
  koi_net: KoiNetConfig
57
80
  env: EnvConfig = Field(default_factory=EnvConfig)
@@ -65,6 +88,12 @@ class NodeConfig(BaseModel):
65
88
  file_path: str = "config.yaml",
66
89
  generate_missing: bool = True
67
90
  ):
91
+ """Loads config state from YAML file.
92
+
93
+ Defaults to `config.yaml`. If `generate_missing` is set to
94
+ `True`, a private key and RID will be generated if not already
95
+ present in the config.
96
+ """
68
97
  yaml = YAML()
69
98
 
70
99
  try:
@@ -112,6 +141,11 @@ class NodeConfig(BaseModel):
112
141
  return config
113
142
 
114
143
  def save_to_yaml(self):
144
+ """Saves config state to YAML file.
145
+
146
+ File path is set by `load_from_yaml` class method.
147
+ """
148
+
115
149
  yaml = YAML()
116
150
 
117
151
  with open(self._file_path, "w") as f:
koi_net/context.py CHANGED
@@ -1,58 +1,54 @@
1
1
  from rid_lib.ext import Cache
2
+
3
+ from koi_net.network.resolver import NetworkResolver
2
4
  from .config import NodeConfig
3
- from .effector import Effector
4
5
  from .network.graph import NetworkGraph
5
- from .network.event_queue import NetworkEventQueue
6
+ from .network.event_queue import EventQueue
6
7
  from .network.request_handler import RequestHandler
7
8
  from .identity import NodeIdentity
8
- from .processor.interface import ProcessorInterface
9
+ from .processor.kobj_queue import KobjQueue
9
10
 
10
11
 
11
12
  class ActionContext:
13
+ """Provides action handlers access to other subsystems."""
14
+
12
15
  identity: NodeIdentity
13
- effector: Effector
14
16
 
15
17
  def __init__(
16
18
  self,
17
19
  identity: NodeIdentity,
18
- effector: Effector
19
20
  ):
20
21
  self.identity = identity
21
- self.effector = effector
22
22
 
23
23
 
24
24
  class HandlerContext:
25
+ """Provides knowledge handlers access to other subsystems."""
26
+
25
27
  identity: NodeIdentity
26
28
  config: NodeConfig
27
29
  cache: Cache
28
- event_queue: NetworkEventQueue
30
+ event_queue: EventQueue
31
+ kobj_queue: KobjQueue
29
32
  graph: NetworkGraph
30
33
  request_handler: RequestHandler
31
- effector: Effector
32
- _processor: ProcessorInterface | None
34
+ resolver: NetworkResolver
33
35
 
34
36
  def __init__(
35
37
  self,
36
38
  identity: NodeIdentity,
37
39
  config: NodeConfig,
38
40
  cache: Cache,
39
- event_queue: NetworkEventQueue,
41
+ event_queue: EventQueue,
42
+ kobj_queue: KobjQueue,
40
43
  graph: NetworkGraph,
41
44
  request_handler: RequestHandler,
42
- effector: Effector
45
+ resolver: NetworkResolver,
43
46
  ):
44
47
  self.identity = identity
45
48
  self.config = config
46
49
  self.cache = cache
47
50
  self.event_queue = event_queue
51
+ self.kobj_queue = kobj_queue
48
52
  self.graph = graph
49
53
  self.request_handler = request_handler
50
- self.effector = effector
51
- self._processor = None
52
-
53
- def set_processor(self, processor: ProcessorInterface):
54
- self._processor = processor
55
-
56
- @property
57
- def handle(self):
58
- return self._processor.handle
54
+ self.resolver = resolver