koi-net 1.2.4__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.
- koi_net/__init__.py +1 -0
- koi_net/behaviors/handshaker.py +68 -0
- koi_net/behaviors/profile_monitor.py +23 -0
- koi_net/behaviors/sync_manager.py +68 -0
- koi_net/build/artifact.py +209 -0
- koi_net/build/assembler.py +60 -0
- koi_net/build/comp_order.py +6 -0
- koi_net/build/comp_type.py +7 -0
- koi_net/build/consts.py +18 -0
- koi_net/build/container.py +46 -0
- koi_net/cache.py +81 -0
- koi_net/config/core.py +113 -0
- koi_net/config/full_node.py +45 -0
- koi_net/config/loader.py +60 -0
- koi_net/config/partial_node.py +26 -0
- koi_net/config/proxy.py +20 -0
- koi_net/core.py +78 -0
- koi_net/effector.py +147 -0
- koi_net/entrypoints/__init__.py +2 -0
- koi_net/entrypoints/base.py +8 -0
- koi_net/entrypoints/poller.py +43 -0
- koi_net/entrypoints/server.py +85 -0
- koi_net/exceptions.py +107 -0
- koi_net/identity.py +20 -0
- koi_net/log_system.py +133 -0
- koi_net/network/__init__.py +0 -0
- koi_net/network/error_handler.py +63 -0
- koi_net/network/event_buffer.py +91 -0
- koi_net/network/event_queue.py +31 -0
- koi_net/network/graph.py +123 -0
- koi_net/network/request_handler.py +244 -0
- koi_net/network/resolver.py +152 -0
- koi_net/network/response_handler.py +130 -0
- koi_net/processor/__init__.py +0 -0
- koi_net/processor/context.py +36 -0
- koi_net/processor/handler.py +61 -0
- koi_net/processor/knowledge_handlers.py +302 -0
- koi_net/processor/knowledge_object.py +135 -0
- koi_net/processor/kobj_queue.py +51 -0
- koi_net/processor/pipeline.py +222 -0
- koi_net/protocol/__init__.py +0 -0
- koi_net/protocol/api_models.py +67 -0
- koi_net/protocol/consts.py +7 -0
- koi_net/protocol/edge.py +50 -0
- koi_net/protocol/envelope.py +65 -0
- koi_net/protocol/errors.py +24 -0
- koi_net/protocol/event.py +51 -0
- koi_net/protocol/model_map.py +62 -0
- koi_net/protocol/node.py +18 -0
- koi_net/protocol/secure.py +167 -0
- koi_net/secure_manager.py +115 -0
- koi_net/workers/__init__.py +2 -0
- koi_net/workers/base.py +26 -0
- koi_net/workers/event_worker.py +111 -0
- koi_net/workers/kobj_worker.py +51 -0
- koi_net-1.2.4.dist-info/METADATA +485 -0
- koi_net-1.2.4.dist-info/RECORD +59 -0
- koi_net-1.2.4.dist-info/WHEEL +4 -0
- koi_net-1.2.4.dist-info/licenses/LICENSE +21 -0
koi_net/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import log_system
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from rid_lib.ext import Cache
|
|
3
|
+
from rid_lib.types import KoiNetNode
|
|
4
|
+
|
|
5
|
+
from ..network.graph import NetworkGraph
|
|
6
|
+
from ..config.core import NodeConfig
|
|
7
|
+
from ..identity import NodeIdentity
|
|
8
|
+
from ..network.event_queue import EventQueue
|
|
9
|
+
from ..protocol.event import Event, EventType
|
|
10
|
+
|
|
11
|
+
log = structlog.stdlib.get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Handshaker:
|
|
15
|
+
"""Handles handshaking with other nodes."""
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
cache: Cache,
|
|
19
|
+
identity: NodeIdentity,
|
|
20
|
+
event_queue: EventQueue,
|
|
21
|
+
config: NodeConfig,
|
|
22
|
+
graph: NetworkGraph
|
|
23
|
+
):
|
|
24
|
+
self.config = config
|
|
25
|
+
self.cache = cache
|
|
26
|
+
self.identity = identity
|
|
27
|
+
self.event_queue = event_queue
|
|
28
|
+
self.graph = graph
|
|
29
|
+
|
|
30
|
+
def start(self):
|
|
31
|
+
"""Attempts handshake with first contact on startup.
|
|
32
|
+
|
|
33
|
+
Handshake occurs if first contact is set in the config, the first
|
|
34
|
+
contact is not already known to this node, and this node does not
|
|
35
|
+
already have incoming edges with node providers.
|
|
36
|
+
"""
|
|
37
|
+
if not self.config.koi_net.first_contact.rid:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if self.cache.read(self.config.koi_net.first_contact.rid):
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
if self.graph.get_neighbors(
|
|
44
|
+
direction="in", allowed_type=KoiNetNode):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
self.handshake_with(self.config.koi_net.first_contact.rid)
|
|
48
|
+
|
|
49
|
+
def handshake_with(self, target: KoiNetNode):
|
|
50
|
+
"""Initiates a handshake with target node.
|
|
51
|
+
|
|
52
|
+
Pushes successive `FORGET` and `NEW` events to target node to
|
|
53
|
+
reset the target's cache in case it already knew this node.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
log.debug(f"Initiating handshake with {target}")
|
|
57
|
+
self.event_queue.push(
|
|
58
|
+
Event.from_rid(
|
|
59
|
+
event_type=EventType.FORGET,
|
|
60
|
+
rid=self.identity.rid),
|
|
61
|
+
target=target
|
|
62
|
+
)
|
|
63
|
+
self.event_queue.push(
|
|
64
|
+
event=Event.from_bundle(
|
|
65
|
+
event_type=EventType.NEW,
|
|
66
|
+
bundle=self.cache.read(self.identity.rid)),
|
|
67
|
+
target=target
|
|
68
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from rid_lib.ext import Bundle
|
|
2
|
+
from ..identity import NodeIdentity
|
|
3
|
+
from ..processor.kobj_queue import KobjQueue
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProfileMonitor:
|
|
7
|
+
"""Processes changes to node profile in the config."""
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
kobj_queue: KobjQueue,
|
|
11
|
+
identity: NodeIdentity
|
|
12
|
+
):
|
|
13
|
+
self.kobj_queue = kobj_queue
|
|
14
|
+
self.identity = identity
|
|
15
|
+
|
|
16
|
+
def start(self):
|
|
17
|
+
"""Processes identity bundle generated from config."""
|
|
18
|
+
self_bundle = Bundle.generate(
|
|
19
|
+
rid=self.identity.rid,
|
|
20
|
+
contents=self.identity.profile.model_dump()
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
self.kobj_queue.push(bundle=self_bundle)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from rid_lib.ext import Cache
|
|
3
|
+
from rid_lib.types import KoiNetNode
|
|
4
|
+
|
|
5
|
+
from ..exceptions import RequestError
|
|
6
|
+
from ..network.graph import NetworkGraph
|
|
7
|
+
from ..network.request_handler import RequestHandler
|
|
8
|
+
from ..processor.kobj_queue import KobjQueue
|
|
9
|
+
from ..protocol.node import NodeProfile, NodeType
|
|
10
|
+
|
|
11
|
+
log = structlog.stdlib.get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SyncManager:
|
|
15
|
+
"""Handles state synchronization actions with other nodes."""
|
|
16
|
+
graph: NetworkGraph
|
|
17
|
+
cache: Cache
|
|
18
|
+
request_handler: RequestHandler
|
|
19
|
+
kobj_queue: KobjQueue
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
graph: NetworkGraph,
|
|
24
|
+
cache: Cache,
|
|
25
|
+
request_handler: RequestHandler,
|
|
26
|
+
kobj_queue: KobjQueue
|
|
27
|
+
):
|
|
28
|
+
self.graph = graph
|
|
29
|
+
self.cache = cache
|
|
30
|
+
self.request_handler = request_handler
|
|
31
|
+
self.kobj_queue = kobj_queue
|
|
32
|
+
|
|
33
|
+
def start(self):
|
|
34
|
+
"""Catches up with node providers on startup."""
|
|
35
|
+
|
|
36
|
+
node_providers = self.graph.get_neighbors(
|
|
37
|
+
direction="in",
|
|
38
|
+
allowed_type=KoiNetNode
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if not node_providers:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
log.debug(f"Catching up with `orn:koi-net.node` providers: {node_providers}")
|
|
45
|
+
self.catch_up_with(node_providers, [KoiNetNode])
|
|
46
|
+
|
|
47
|
+
def catch_up_with(self, nodes, rid_types):
|
|
48
|
+
"""Catches up with the state of RID types within other nodes."""
|
|
49
|
+
|
|
50
|
+
for node in nodes:
|
|
51
|
+
node_bundle = self.cache.read(node)
|
|
52
|
+
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
53
|
+
|
|
54
|
+
# can't catch up with partial nodes
|
|
55
|
+
if node_profile.node_type != NodeType.FULL:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
payload = self.request_handler.fetch_manifests(
|
|
60
|
+
node, rid_types=rid_types)
|
|
61
|
+
except RequestError:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
for manifest in payload.manifests:
|
|
65
|
+
self.kobj_queue.push(
|
|
66
|
+
manifest=manifest,
|
|
67
|
+
source=node
|
|
68
|
+
)
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from collections import deque
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
from ..exceptions import BuildError
|
|
8
|
+
from .consts import (
|
|
9
|
+
COMP_ORDER_OVERRIDE,
|
|
10
|
+
COMP_TYPE_OVERRIDE,
|
|
11
|
+
START_FUNC_NAME,
|
|
12
|
+
START_ORDER_OVERRIDE,
|
|
13
|
+
STOP_FUNC_NAME,
|
|
14
|
+
STOP_ORDER_OVERRIDE,
|
|
15
|
+
CompOrder,
|
|
16
|
+
CompType
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .assembler import NodeAssembler
|
|
21
|
+
|
|
22
|
+
log = structlog.stdlib.get_logger()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BuildArtifact:
|
|
26
|
+
assembler: "NodeAssembler"
|
|
27
|
+
comp_dict: dict[str, Any]
|
|
28
|
+
dep_graph: dict[str, list[str]]
|
|
29
|
+
comp_types: dict[str, CompType]
|
|
30
|
+
init_order: list[str]
|
|
31
|
+
start_order: list[str]
|
|
32
|
+
stop_order: list[str]
|
|
33
|
+
graphviz: str
|
|
34
|
+
|
|
35
|
+
def __init__(self, assembler: "NodeAssembler"):
|
|
36
|
+
self.assembler = assembler
|
|
37
|
+
|
|
38
|
+
def collect_comps(self):
|
|
39
|
+
"""Collects components from class definition."""
|
|
40
|
+
|
|
41
|
+
self.comp_dict = {}
|
|
42
|
+
# adds components from class and all base classes. skips `type`, and runs in reverse so that sub classes override super class values
|
|
43
|
+
for base in reversed(inspect.getmro(self.assembler)[:-1]):
|
|
44
|
+
for k, v in vars(base).items():
|
|
45
|
+
# excludes built in, private, and `None` attributes
|
|
46
|
+
if k.startswith("_") or v is None:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
self.comp_dict[k] = v
|
|
50
|
+
log.debug(f"Collected {len(self.comp_dict)} components")
|
|
51
|
+
|
|
52
|
+
def build_dependencies(self):
|
|
53
|
+
"""Builds dependency graph and component type map.
|
|
54
|
+
|
|
55
|
+
Graph representation is an adjacency list: the key is a component
|
|
56
|
+
name, and the value is a tuple containing names of the depedencies.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
self.comp_types = {}
|
|
60
|
+
self.dep_graph = {}
|
|
61
|
+
for comp_name, comp in self.comp_dict.items():
|
|
62
|
+
|
|
63
|
+
dep_names = []
|
|
64
|
+
|
|
65
|
+
explicit_type = getattr(comp, COMP_TYPE_OVERRIDE, None)
|
|
66
|
+
if explicit_type:
|
|
67
|
+
self.comp_types[comp_name] = explicit_type
|
|
68
|
+
|
|
69
|
+
# non callable components are objects treated "as is"
|
|
70
|
+
elif not callable(comp):
|
|
71
|
+
self.comp_types[comp_name] = CompType.OBJECT
|
|
72
|
+
|
|
73
|
+
# callable components default to singletons
|
|
74
|
+
else:
|
|
75
|
+
sig = inspect.signature(comp)
|
|
76
|
+
self.comp_types[comp_name] = CompType.SINGLETON
|
|
77
|
+
dep_names = list(sig.parameters)
|
|
78
|
+
|
|
79
|
+
invalid_deps = set(dep_names) - set(self.comp_dict)
|
|
80
|
+
if invalid_deps:
|
|
81
|
+
raise BuildError(f"Dependencies {invalid_deps} of component '{comp_name}' are undefined")
|
|
82
|
+
|
|
83
|
+
self.dep_graph[comp_name] = dep_names
|
|
84
|
+
|
|
85
|
+
log.debug("Built dependency graph")
|
|
86
|
+
|
|
87
|
+
def build_init_order(self):
|
|
88
|
+
"""Builds component initialization order using Kahn's algorithm."""
|
|
89
|
+
|
|
90
|
+
# adj list: n -> outgoing neighbors
|
|
91
|
+
adj = self.dep_graph
|
|
92
|
+
# reverse adj list: n -> incoming neighbors
|
|
93
|
+
r_adj: dict[str, list[str]] = {}
|
|
94
|
+
|
|
95
|
+
# computes reverse adjacency list
|
|
96
|
+
for node in adj:
|
|
97
|
+
r_adj.setdefault(node, [])
|
|
98
|
+
for n in adj[node]:
|
|
99
|
+
r_adj.setdefault(n, [])
|
|
100
|
+
r_adj[n].append(node)
|
|
101
|
+
|
|
102
|
+
# how many outgoing edges each node has
|
|
103
|
+
out_degree = {
|
|
104
|
+
n: len(neighbors)
|
|
105
|
+
for n, neighbors in adj.items()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# initializing queue: nodes w/o dependencies
|
|
109
|
+
queue = deque()
|
|
110
|
+
for node in out_degree:
|
|
111
|
+
if out_degree[node] == 0:
|
|
112
|
+
queue.append(node)
|
|
113
|
+
|
|
114
|
+
self.init_order = []
|
|
115
|
+
while queue:
|
|
116
|
+
# removes node from graph
|
|
117
|
+
n = queue.popleft()
|
|
118
|
+
self.init_order.append(n)
|
|
119
|
+
|
|
120
|
+
# updates out degree for nodes dependent on this node
|
|
121
|
+
for next_n in r_adj[n]:
|
|
122
|
+
out_degree[next_n] -= 1
|
|
123
|
+
# adds nodes now without dependencies to queue
|
|
124
|
+
if out_degree[next_n] == 0:
|
|
125
|
+
queue.append(next_n)
|
|
126
|
+
|
|
127
|
+
if len(self.init_order) != len(self.dep_graph):
|
|
128
|
+
cycle_nodes = set(self.dep_graph) - set(self.init_order)
|
|
129
|
+
raise BuildError(f"Found cycle in dependency graph, the following nodes could not be ordered: {cycle_nodes}")
|
|
130
|
+
|
|
131
|
+
log.debug(f"Resolved initialization order: {' -> '.join(self.init_order)}")
|
|
132
|
+
|
|
133
|
+
def build_start_order(self):
|
|
134
|
+
"""Builds component start order.
|
|
135
|
+
|
|
136
|
+
Checks if components define a start function in init order. Can
|
|
137
|
+
be overridden by setting start order override in the `NodeAssembler`.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
self.start_order = getattr(self.assembler, START_ORDER_OVERRIDE, None)
|
|
141
|
+
|
|
142
|
+
if self.start_order:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
workers = []
|
|
146
|
+
start_order = []
|
|
147
|
+
for comp_name in self.init_order:
|
|
148
|
+
comp = self.comp_dict[comp_name]
|
|
149
|
+
if getattr(comp, START_FUNC_NAME, None):
|
|
150
|
+
if getattr(comp, COMP_ORDER_OVERRIDE, None) == CompOrder.WORKER:
|
|
151
|
+
workers.append(comp_name)
|
|
152
|
+
else:
|
|
153
|
+
start_order.append(comp_name)
|
|
154
|
+
|
|
155
|
+
# order workers first
|
|
156
|
+
self.start_order = workers + start_order
|
|
157
|
+
|
|
158
|
+
log.debug(f"Resolved start order: {' -> '.join(self.start_order)}")
|
|
159
|
+
|
|
160
|
+
def build_stop_order(self):
|
|
161
|
+
"""Builds component stop order.
|
|
162
|
+
|
|
163
|
+
Checks if components define a stop function in init order. Can
|
|
164
|
+
be overridden by setting stop order override in the `NodeAssembler`.
|
|
165
|
+
"""
|
|
166
|
+
self.stop_order = getattr(self.assembler, STOP_ORDER_OVERRIDE, None)
|
|
167
|
+
|
|
168
|
+
if self.stop_order:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
workers = []
|
|
172
|
+
stop_order = []
|
|
173
|
+
for comp_name in self.init_order:
|
|
174
|
+
comp = self.comp_dict[comp_name]
|
|
175
|
+
if getattr(comp, STOP_FUNC_NAME, None):
|
|
176
|
+
if getattr(comp, COMP_ORDER_OVERRIDE, None) == CompOrder.WORKER:
|
|
177
|
+
workers.append(comp_name)
|
|
178
|
+
else:
|
|
179
|
+
stop_order.append(comp_name)
|
|
180
|
+
|
|
181
|
+
# order workers first (last)
|
|
182
|
+
self.stop_order = workers + stop_order
|
|
183
|
+
# reverse order from start order
|
|
184
|
+
self.stop_order.reverse()
|
|
185
|
+
|
|
186
|
+
log.debug(f"Resolved stop order: {' -> '.join(self.stop_order)}")
|
|
187
|
+
|
|
188
|
+
def visualize(self) -> str:
|
|
189
|
+
"""Creates representation of dependency graph in Graphviz DOT language."""
|
|
190
|
+
|
|
191
|
+
s = "digraph G {\n"
|
|
192
|
+
for node, neighbors in self.dep_graph.items():
|
|
193
|
+
sub_s = node
|
|
194
|
+
if neighbors:
|
|
195
|
+
sub_s += f"-> {', '.join(neighbors)}"
|
|
196
|
+
sub_s = sub_s.replace("graph", "graph_") + ";"
|
|
197
|
+
s += " " * 4 + sub_s + "\n"
|
|
198
|
+
s += "}"
|
|
199
|
+
self.graphviz = s
|
|
200
|
+
|
|
201
|
+
def build(self):
|
|
202
|
+
log.debug("Creating build artifact...")
|
|
203
|
+
self.collect_comps()
|
|
204
|
+
self.build_dependencies()
|
|
205
|
+
self.build_init_order()
|
|
206
|
+
self.build_start_order()
|
|
207
|
+
self.build_stop_order()
|
|
208
|
+
self.visualize()
|
|
209
|
+
log.debug("Done")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any, Self
|
|
3
|
+
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
from ..exceptions import BuildError
|
|
7
|
+
from .artifact import BuildArtifact, CompType
|
|
8
|
+
from .container import NodeContainer
|
|
9
|
+
|
|
10
|
+
log = structlog.stdlib.get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NodeAssembler:
|
|
14
|
+
_artifact: BuildArtifact = None
|
|
15
|
+
|
|
16
|
+
# optional order overrides:
|
|
17
|
+
_start_order: list[str]
|
|
18
|
+
_stop_order: list[str]
|
|
19
|
+
|
|
20
|
+
# annotation hack to show the components and container methods
|
|
21
|
+
def __new__(cls) -> Self | NodeContainer:
|
|
22
|
+
"""Returns assembled node container."""
|
|
23
|
+
|
|
24
|
+
log.debug(f"Assembling '{cls.__name__}'")
|
|
25
|
+
|
|
26
|
+
# builds assembly artifact if it doesn't exist
|
|
27
|
+
if not cls._artifact:
|
|
28
|
+
cls._artifact = BuildArtifact(cls)
|
|
29
|
+
cls._artifact.build()
|
|
30
|
+
|
|
31
|
+
components = cls._build_components(cls._artifact)
|
|
32
|
+
|
|
33
|
+
log.debug("Returning assembled node")
|
|
34
|
+
return NodeContainer(cls._artifact, **components)
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _build_components(artifact: BuildArtifact):
|
|
38
|
+
"""Returns assembled components as a dict."""
|
|
39
|
+
|
|
40
|
+
log.debug("Building components...")
|
|
41
|
+
components: dict[str, Any] = {}
|
|
42
|
+
for comp_name in artifact.init_order:
|
|
43
|
+
# for comp_name, (comp_type, dep_names) in dep_graph.items():
|
|
44
|
+
comp = artifact.comp_dict[comp_name]
|
|
45
|
+
comp_type = artifact.comp_types[comp_name]
|
|
46
|
+
|
|
47
|
+
if comp_type == CompType.OBJECT:
|
|
48
|
+
components[comp_name] = comp
|
|
49
|
+
|
|
50
|
+
elif comp_type == CompType.SINGLETON:
|
|
51
|
+
# builds depedency dict for current component
|
|
52
|
+
dependencies = {}
|
|
53
|
+
for dep in artifact.dep_graph[comp_name]:
|
|
54
|
+
if dep not in components:
|
|
55
|
+
raise BuildError(f"Couldn't find required component '{dep}'")
|
|
56
|
+
dependencies[dep] = components[dep]
|
|
57
|
+
components[comp_name] = comp(**dependencies)
|
|
58
|
+
log.debug("Done")
|
|
59
|
+
|
|
60
|
+
return components
|
koi_net/build/consts.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
START_FUNC_NAME = "start"
|
|
5
|
+
STOP_FUNC_NAME = "stop"
|
|
6
|
+
|
|
7
|
+
START_ORDER_OVERRIDE = "_start_order"
|
|
8
|
+
STOP_ORDER_OVERRIDE = "_stop_order"
|
|
9
|
+
|
|
10
|
+
COMP_TYPE_OVERRIDE = "_comp_type"
|
|
11
|
+
COMP_ORDER_OVERRIDE = "_comp_order"
|
|
12
|
+
|
|
13
|
+
class CompType(StrEnum):
|
|
14
|
+
SINGLETON = "SINGLETON"
|
|
15
|
+
OBJECT = "OBJECT"
|
|
16
|
+
|
|
17
|
+
class CompOrder(StrEnum):
|
|
18
|
+
WORKER = "WORKER"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
|
|
3
|
+
from ..entrypoints.base import EntryPoint
|
|
4
|
+
from .artifact import BuildArtifact
|
|
5
|
+
from .consts import START_FUNC_NAME, STOP_FUNC_NAME
|
|
6
|
+
|
|
7
|
+
log = structlog.stdlib.get_logger()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NodeContainer:
|
|
11
|
+
"""Dummy 'shape' for node containers built by assembler."""
|
|
12
|
+
_artifact: BuildArtifact
|
|
13
|
+
|
|
14
|
+
entrypoint: EntryPoint
|
|
15
|
+
|
|
16
|
+
def __init__(self, artifact, **kwargs):
|
|
17
|
+
self._artifact = artifact
|
|
18
|
+
|
|
19
|
+
# adds all components as attributes of this instance
|
|
20
|
+
for name, comp in kwargs.items():
|
|
21
|
+
setattr(self, name, comp)
|
|
22
|
+
|
|
23
|
+
def run(self):
|
|
24
|
+
try:
|
|
25
|
+
self.start()
|
|
26
|
+
self.entrypoint.run()
|
|
27
|
+
except KeyboardInterrupt:
|
|
28
|
+
log.info("Keyboard interrupt!")
|
|
29
|
+
finally:
|
|
30
|
+
self.stop()
|
|
31
|
+
|
|
32
|
+
def start(self):
|
|
33
|
+
log.info("Starting node...")
|
|
34
|
+
for comp_name in self._artifact.start_order:
|
|
35
|
+
comp = getattr(self, comp_name)
|
|
36
|
+
start_func = getattr(comp, START_FUNC_NAME)
|
|
37
|
+
log.info(f"Starting {comp_name}...")
|
|
38
|
+
start_func()
|
|
39
|
+
|
|
40
|
+
def stop(self):
|
|
41
|
+
log.info("Stopping node...")
|
|
42
|
+
for comp_name in self._artifact.stop_order:
|
|
43
|
+
comp = getattr(self, comp_name)
|
|
44
|
+
stop_func = getattr(comp, STOP_FUNC_NAME)
|
|
45
|
+
log.info(f"Stopping {comp_name}...")
|
|
46
|
+
stop_func()
|
koi_net/cache.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from rid_lib.core import RID, RIDType
|
|
4
|
+
from rid_lib.ext import Bundle
|
|
5
|
+
from rid_lib.ext.utils import b64_encode, b64_decode
|
|
6
|
+
|
|
7
|
+
from .config.core import NodeConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Cache:
|
|
11
|
+
def __init__(self, config: NodeConfig):
|
|
12
|
+
self.config = config
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def directory_path(self):
|
|
16
|
+
return self.config.koi_net.cache_directory_path
|
|
17
|
+
|
|
18
|
+
def file_path_to(self, rid: RID) -> str:
|
|
19
|
+
encoded_rid_str = b64_encode(str(rid))
|
|
20
|
+
return f"{self.directory_path}/{encoded_rid_str}.json"
|
|
21
|
+
|
|
22
|
+
def write(self, bundle: Bundle) -> Bundle:
|
|
23
|
+
"""Writes bundle to cache, returns a Bundle."""
|
|
24
|
+
if not os.path.exists(self.directory_path):
|
|
25
|
+
os.makedirs(self.directory_path)
|
|
26
|
+
|
|
27
|
+
with open(
|
|
28
|
+
file=self.file_path_to(bundle.manifest.rid),
|
|
29
|
+
mode="w",
|
|
30
|
+
encoding="utf-8"
|
|
31
|
+
) as f:
|
|
32
|
+
f.write(bundle.model_dump_json(indent=2))
|
|
33
|
+
|
|
34
|
+
return bundle
|
|
35
|
+
|
|
36
|
+
def exists(self, rid: RID) -> bool:
|
|
37
|
+
return os.path.exists(
|
|
38
|
+
self.file_path_to(rid)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def read(self, rid: RID) -> Bundle | None:
|
|
42
|
+
"""Reads and returns CacheEntry from RID cache."""
|
|
43
|
+
try:
|
|
44
|
+
with open(
|
|
45
|
+
file=self.file_path_to(rid),
|
|
46
|
+
mode="r",
|
|
47
|
+
encoding="utf-8"
|
|
48
|
+
) as f:
|
|
49
|
+
return Bundle.model_validate_json(f.read())
|
|
50
|
+
except FileNotFoundError:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def list_rids(self, rid_types: list[RIDType] | None = None) -> list[RID]:
|
|
54
|
+
if not os.path.exists(self.directory_path):
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
rids = []
|
|
58
|
+
for filename in os.listdir(self.directory_path):
|
|
59
|
+
encoded_rid_str = filename.split(".")[0]
|
|
60
|
+
rid_str = b64_decode(encoded_rid_str)
|
|
61
|
+
rid = RID.from_string(rid_str)
|
|
62
|
+
|
|
63
|
+
if not rid_types or type(rid) in rid_types:
|
|
64
|
+
rids.append(rid)
|
|
65
|
+
|
|
66
|
+
return rids
|
|
67
|
+
|
|
68
|
+
def delete(self, rid: RID) -> None:
|
|
69
|
+
"""Deletes cache bundle."""
|
|
70
|
+
try:
|
|
71
|
+
os.remove(self.file_path_to(rid))
|
|
72
|
+
except FileNotFoundError:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
def drop(self) -> None:
|
|
76
|
+
"""Deletes all cache bundles."""
|
|
77
|
+
try:
|
|
78
|
+
shutil.rmtree(self.directory_path)
|
|
79
|
+
except FileNotFoundError:
|
|
80
|
+
return
|
|
81
|
+
|