koi-net 1.1.0b8__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 (42) 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 +12 -21
  8. koi_net/core.py +209 -170
  9. koi_net/default_actions.py +10 -1
  10. koi_net/effector.py +39 -24
  11. koi_net/handshaker.py +39 -0
  12. koi_net/kobj_worker.py +45 -0
  13. koi_net/lifecycle.py +67 -38
  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 +7 -2
  18. koi_net/network/request_handler.py +31 -19
  19. koi_net/network/resolver.py +5 -8
  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.0b8.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/actor.py +0 -60
  39. koi_net/processor/interface.py +0 -101
  40. koi_net-1.1.0b8.dist-info/RECORD +0 -38
  41. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b1.dist-info}/WHEEL +0 -0
  42. {koi_net-1.1.0b8.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
@@ -2,62 +2,53 @@ from rid_lib.ext import Cache
2
2
 
3
3
  from koi_net.network.resolver import NetworkResolver
4
4
  from .config import NodeConfig
5
- from .effector import Effector
6
5
  from .network.graph import NetworkGraph
7
- from .network.event_queue import NetworkEventQueue
6
+ from .network.event_queue import EventQueue
8
7
  from .network.request_handler import RequestHandler
9
8
  from .identity import NodeIdentity
10
- from .processor.interface import ProcessorInterface
9
+ from .processor.kobj_queue import KobjQueue
11
10
 
12
11
 
13
12
  class ActionContext:
13
+ """Provides action handlers access to other subsystems."""
14
+
14
15
  identity: NodeIdentity
15
- effector: Effector
16
16
 
17
17
  def __init__(
18
18
  self,
19
19
  identity: NodeIdentity,
20
- effector: Effector
21
20
  ):
22
21
  self.identity = identity
23
- self.effector = effector
24
22
 
25
23
 
26
24
  class HandlerContext:
25
+ """Provides knowledge handlers access to other subsystems."""
26
+
27
27
  identity: NodeIdentity
28
28
  config: NodeConfig
29
29
  cache: Cache
30
- event_queue: NetworkEventQueue
30
+ event_queue: EventQueue
31
+ kobj_queue: KobjQueue
31
32
  graph: NetworkGraph
32
33
  request_handler: RequestHandler
33
34
  resolver: NetworkResolver
34
- effector: Effector
35
- _processor: ProcessorInterface | None
36
35
 
37
36
  def __init__(
38
37
  self,
39
38
  identity: NodeIdentity,
40
39
  config: NodeConfig,
41
40
  cache: Cache,
42
- event_queue: NetworkEventQueue,
41
+ event_queue: EventQueue,
42
+ kobj_queue: KobjQueue,
43
43
  graph: NetworkGraph,
44
44
  request_handler: RequestHandler,
45
45
  resolver: NetworkResolver,
46
- effector: Effector
47
46
  ):
48
47
  self.identity = identity
49
48
  self.config = config
50
49
  self.cache = cache
51
50
  self.event_queue = event_queue
51
+ self.kobj_queue = kobj_queue
52
52
  self.graph = graph
53
53
  self.request_handler = request_handler
54
- self.resolver = resolver
55
- self.effector = effector
56
- self._processor = None
57
-
58
- def set_processor(self, processor: ProcessorInterface):
59
- self._processor = processor
60
-
61
- @property
62
- def handle(self):
63
- return self._processor.handle
54
+ self.resolver = resolver