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/lifecycle.py CHANGED
@@ -1,12 +1,18 @@
1
1
  import logging
2
2
  from contextlib import contextmanager, asynccontextmanager
3
3
 
4
+ from rid_lib.ext import Bundle, Cache
4
5
  from rid_lib.types import KoiNetNode
5
6
 
6
- from .actor import Actor
7
- from .effector import Effector
7
+ from koi_net.behaviors import Behaviors
8
+ from koi_net.handshaker import Handshaker
9
+ from koi_net.kobj_worker import KnowledgeProcessingWorker
10
+ from koi_net.models import END
11
+ from koi_net.network.event_queue import EventQueue
12
+ from koi_net.processor.event_worker import EventProcessingWorker
13
+
8
14
  from .config import NodeConfig
9
- from .processor.interface import ProcessorInterface
15
+ from .processor.kobj_queue import KobjQueue
10
16
  from .network.graph import NetworkGraph
11
17
  from .identity import NodeIdentity
12
18
 
@@ -14,32 +20,46 @@ logger = logging.getLogger(__name__)
14
20
 
15
21
 
16
22
  class NodeLifecycle:
23
+ """Manages node startup and shutdown processes."""
24
+
17
25
  config: NodeConfig
26
+ identity: NodeIdentity
18
27
  graph: NetworkGraph
19
- processor: ProcessorInterface
20
- effector: Effector
21
- actor: Actor
28
+ kobj_queue: KobjQueue
29
+ kobj_worker: KnowledgeProcessingWorker
30
+ event_queue: EventQueue
31
+ event_worker: EventProcessingWorker
32
+ cache: Cache
33
+ handshaker: Handshaker
34
+ behaviors: Behaviors
22
35
 
23
36
  def __init__(
24
37
  self,
25
38
  config: NodeConfig,
26
39
  identity: NodeIdentity,
27
40
  graph: NetworkGraph,
28
- processor: ProcessorInterface,
29
- effector: Effector,
30
- actor: Actor,
31
- use_kobj_processor_thread: bool
41
+ kobj_queue: KobjQueue,
42
+ kobj_worker: KnowledgeProcessingWorker,
43
+ event_queue: EventQueue,
44
+ event_worker: EventProcessingWorker,
45
+ cache: Cache,
46
+ handshaker: Handshaker,
47
+ behaviors: Behaviors
32
48
  ):
33
49
  self.config = config
34
50
  self.identity = identity
35
51
  self.graph = graph
36
- self.processor = processor
37
- self.effector = effector
38
- self.actor = actor
39
- self.use_kobj_processor_thread = use_kobj_processor_thread
52
+ self.kobj_queue = kobj_queue
53
+ self.kobj_worker = kobj_worker
54
+ self.event_queue = event_queue
55
+ self.event_worker = event_worker
56
+ self.cache = cache
57
+ self.handshaker = handshaker
58
+ self.behaviors = behaviors
40
59
 
41
60
  @contextmanager
42
61
  def run(self):
62
+ """Synchronous context manager for node startup and shutdown."""
43
63
  try:
44
64
  logger.info("Starting node lifecycle...")
45
65
  self.start()
@@ -52,6 +72,7 @@ class NodeLifecycle:
52
72
 
53
73
  @asynccontextmanager
54
74
  async def async_run(self):
75
+ """Asynchronous context manager for node startup and shutdown."""
55
76
  try:
56
77
  logger.info("Starting async node lifecycle...")
57
78
  self.start()
@@ -63,42 +84,50 @@ class NodeLifecycle:
63
84
  self.stop()
64
85
 
65
86
  def start(self):
66
- """Starts a node, call this method first.
87
+ """Starts a node.
67
88
 
68
- Starts the processor thread (if enabled). Loads event queues into memory. Generates network graph from nodes and edges in cache. Processes any state changes of node bundle. Initiates handshake with first contact (if provided) if node doesn't have any neighbors.
89
+ Starts the processor thread (if enabled). Generates network
90
+ graph from nodes and edges in cache. Processes any state changes
91
+ of node bundle. Initiates handshake with first contact if node
92
+ doesn't have any neighbors. Catches up with coordinator state.
69
93
  """
70
- if self.use_kobj_processor_thread:
71
- logger.info("Starting processor worker thread")
72
- self.processor.worker_thread.start()
94
+ logger.info("Starting processor worker thread")
73
95
 
96
+ self.kobj_worker.thread.start()
97
+ self.event_worker.thread.start()
74
98
  self.graph.generate()
75
99
 
76
100
  # refresh to reflect changes (if any) in config.yaml
77
- self.effector.deref(self.identity.rid, refresh_cache=True)
101
+
102
+ self.kobj_queue.put_kobj(bundle=Bundle.generate(
103
+ rid=self.identity.rid,
104
+ contents=self.identity.profile.model_dump()
105
+ ))
78
106
 
79
107
  logger.debug("Waiting for kobj queue to empty")
80
- if self.use_kobj_processor_thread:
81
- self.processor.kobj_queue.join()
82
- else:
83
- self.processor.flush_kobj_queue()
84
- logger.debug("Done")
85
-
86
- if not self.graph.get_neighbors() and self.config.koi_net.first_contact.rid:
87
- logger.debug(f"I don't have any neighbors, reaching out to first contact {self.config.koi_net.first_contact.rid!r}")
108
+ self.kobj_queue.q.join()
109
+
110
+ # TODO: FACTOR OUT BEHAVIOR
111
+
112
+ coordinators = self.graph.get_neighbors(direction="in", allowed_type=KoiNetNode)
113
+
114
+ if len(coordinators) == 0 and self.config.koi_net.first_contact.rid:
115
+ logger.debug(f"I don't have any edges with coordinators, reaching out to first contact {self.config.koi_net.first_contact.rid!r}")
88
116
 
89
- self.actor.handshake_with(self.config.koi_net.first_contact.rid)
117
+ self.handshaker.handshake_with(self.config.koi_net.first_contact.rid)
118
+
90
119
 
91
- for coordinator in self.actor.identify_coordinators():
92
- self.actor.catch_up_with(coordinator, rid_types=[KoiNetNode])
120
+
121
+ for coordinator in self.behaviors.identify_coordinators():
122
+ self.behaviors.catch_up_with(coordinator, rid_types=[KoiNetNode])
93
123
 
94
124
 
95
125
  def stop(self):
96
- """Stops a node, call this method last.
126
+ """Stops a node.
97
127
 
98
- Finishes processing knowledge object queue. Saves event queues to storage.
128
+ Finishes processing knowledge object queue.
99
129
  """
100
- if self.use_kobj_processor_thread:
101
- logger.info(f"Waiting for kobj queue to empty ({self.processor.kobj_queue.unfinished_tasks} tasks remaining)")
102
- self.processor.kobj_queue.join()
103
- else:
104
- self.processor.flush_kobj_queue()
130
+ logger.info(f"Waiting for kobj queue to empty ({self.kobj_queue.q.unfinished_tasks} tasks remaining)")
131
+
132
+ self.kobj_queue.q.put(END)
133
+ self.event_queue.q.put(END)
koi_net/models.py ADDED
@@ -0,0 +1,14 @@
1
+ from pydantic import BaseModel
2
+ from rid_lib.types import KoiNetNode
3
+ from koi_net.protocol.event import Event
4
+
5
+ class End:
6
+ """Class for a sentinel value by knowledge handlers."""
7
+ pass
8
+
9
+ END = End()
10
+
11
+
12
+ class QueuedEvent(BaseModel):
13
+ event: Event
14
+ target: KoiNetNode
@@ -1,28 +1,29 @@
1
1
  from logging import getLogger
2
+ from koi_net.handshaker import Handshaker
2
3
  from koi_net.protocol.errors import ErrorType
3
4
  from koi_net.protocol.event import EventType
4
5
  from rid_lib.types import KoiNetNode
5
- from ..processor.interface import ProcessorInterface
6
- from ..actor import Actor
6
+ from ..processor.kobj_queue import KobjQueue
7
7
 
8
8
  logger = getLogger(__name__)
9
9
 
10
10
 
11
11
  class ErrorHandler:
12
+ """Handles network errors that may occur during requests."""
12
13
  timeout_counter: dict[KoiNetNode, int]
13
- processor: ProcessorInterface
14
- actor: Actor
14
+ kobj_queue: KobjQueue
15
15
 
16
16
  def __init__(
17
17
  self,
18
- processor: ProcessorInterface,
19
- actor: Actor
18
+ kobj_queue: KobjQueue,
19
+ handshaker: Handshaker
20
20
  ):
21
- self.processor = processor
22
- self.actor = actor
21
+ self.kobj_queue = kobj_queue
22
+ self.handshaker = handshaker
23
23
  self.timeout_counter = {}
24
24
 
25
25
  def handle_connection_error(self, node: KoiNetNode):
26
+ """Drops nodes after timing out three times."""
26
27
  self.timeout_counter.setdefault(node, 0)
27
28
  self.timeout_counter[node] += 1
28
29
 
@@ -30,7 +31,7 @@ class ErrorHandler:
30
31
 
31
32
  if self.timeout_counter[node] > 3:
32
33
  logger.debug(f"Exceeded time out limit, forgetting node")
33
- self.processor.handle(rid=node, event_type=EventType.FORGET)
34
+ self.kobj_queue.put_kobj(rid=node, event_type=EventType.FORGET)
34
35
  # do something
35
36
 
36
37
 
@@ -39,11 +40,12 @@ class ErrorHandler:
39
40
  error_type: ErrorType,
40
41
  node: KoiNetNode
41
42
  ):
43
+ """Attempts handshake when this node is unknown to target."""
42
44
  logger.info(f"Handling protocol error {error_type} for node {node!r}")
43
45
  match error_type:
44
46
  case ErrorType.UnknownNode:
45
47
  logger.info("Peer doesn't know me, attempting handshake...")
46
- self.actor.handshake_with(node)
48
+ self.handshaker.handshake_with(node)
47
49
 
48
50
  case ErrorType.InvalidKey: ...
49
51
  case ErrorType.InvalidSignature: ...
@@ -1,199 +1,29 @@
1
1
  import logging
2
2
  from queue import Queue
3
- import httpx
4
- from pydantic import BaseModel
5
- from rid_lib import RID
6
- from rid_lib.ext import Cache
3
+
7
4
  from rid_lib.types import KoiNetNode
8
5
 
9
- from .graph import NetworkGraph
10
- from .request_handler import NodeNotFoundError, RequestHandler
11
- from ..protocol.node import NodeProfile, NodeType
12
- from ..protocol.edge import EdgeProfile, EdgeType
6
+ from ..models import QueuedEvent
13
7
  from ..protocol.event import Event
14
- from ..identity import NodeIdentity
15
- from ..config import NodeConfig
16
- from ..effector import Effector
17
8
 
18
9
  logger = logging.getLogger(__name__)
19
10
 
20
11
 
21
- class EventQueueModel(BaseModel):
22
- webhook: dict[KoiNetNode, list[Event]]
23
- poll: dict[KoiNetNode, list[Event]]
24
-
25
- type EventQueue = dict[RID, Queue[Event]]
26
-
27
- class NetworkEventQueue:
28
- """A collection of functions and classes to interact with the KOI network."""
29
-
30
- config: NodeConfig
31
- identity: NodeIdentity
32
- effector: Effector
33
- cache: Cache
34
- graph: NetworkGraph
35
- request_handler: RequestHandler
36
- poll_event_queue: EventQueue
37
- webhook_event_queue: EventQueue
12
+ class EventQueue:
13
+ """Handles out going network event queues."""
14
+ q: Queue[QueuedEvent]
38
15
 
39
- def __init__(
40
- self,
41
- config: NodeConfig,
42
- cache: Cache,
43
- identity: NodeIdentity,
44
- effector: Effector,
45
- graph: NetworkGraph,
46
- request_handler: RequestHandler,
47
- ):
48
- self.config = config
49
- self.identity = identity
50
- self.cache = cache
51
- self.graph = graph
52
- self.request_handler = request_handler
53
- self.effector = effector
54
-
55
- self.poll_event_queue = dict()
56
- self.webhook_event_queue = dict()
57
-
58
- def _load_event_queues(self):
59
- """Loads event queues from storage."""
60
- try:
61
- with open(self.config.koi_net.event_queues_path, "r") as f:
62
- queues = EventQueueModel.model_validate_json(f.read())
63
-
64
- for node in queues.poll.keys():
65
- for event in queues.poll[node]:
66
- queue = self.poll_event_queue.setdefault(node, Queue())
67
- queue.put(event)
68
-
69
- for node in queues.webhook.keys():
70
- for event in queues.webhook[node]:
71
- queue = self.webhook_event_queue.setdefault(node, Queue())
72
- queue.put(event)
73
-
74
- except FileNotFoundError:
75
- return
76
-
77
- def _save_event_queues(self):
78
- """Writes event queues to storage."""
79
- events_model = EventQueueModel(
80
- poll={
81
- node: list(queue.queue)
82
- for node, queue in self.poll_event_queue.items()
83
- if not queue.empty()
84
- },
85
- webhook={
86
- node: list(queue.queue)
87
- for node, queue in self.webhook_event_queue.items()
88
- if not queue.empty()
89
- }
90
- )
91
-
92
- if len(events_model.poll) == 0 and len(events_model.webhook) == 0:
93
- return
94
-
95
- with open(self.config.koi_net.event_queues_path, "w") as f:
96
- f.write(events_model.model_dump_json(indent=2))
16
+ def __init__(self):
17
+ self.q = Queue()
97
18
 
98
- def push_event_to(self, event: Event, node: KoiNetNode, flush=False):
19
+ def push_event_to(self, event: Event, target: KoiNetNode):
99
20
  """Pushes event to queue of specified node.
100
21
 
101
- Event will be sent to webhook or poll queue depending on the node type and edge type of the specified node. If `flush` is set to `True`, the webhook queued will be flushed after pushing the event.
102
- """
103
- logger.debug(f"Pushing event {event.event_type} {event.rid!r} to {node}")
104
-
105
- node_bundle = self.effector.deref(node)
106
-
107
- # if there's an edge from me to the target node, override broadcast type
108
- edge_rid = self.graph.get_edge(
109
- source=self.identity.rid,
110
- target=node
111
- )
112
-
113
- edge_bundle = self.effector.deref(edge_rid) if edge_rid else None
114
-
115
- if edge_bundle:
116
- logger.debug(f"Found edge from me to {node!r}")
117
- edge_profile = edge_bundle.validate_contents(EdgeProfile)
118
- if edge_profile.edge_type == EdgeType.WEBHOOK:
119
- event_queue = self.webhook_event_queue
120
- elif edge_profile.edge_type == EdgeType.POLL:
121
- event_queue = self.poll_event_queue
122
-
123
- elif node_bundle:
124
- logger.debug(f"Found bundle for {node!r}")
125
- node_profile = node_bundle.validate_contents(NodeProfile)
126
- if node_profile.node_type == NodeType.FULL:
127
- event_queue = self.webhook_event_queue
128
- elif node_profile.node_type == NodeType.PARTIAL:
129
- event_queue = self.poll_event_queue
130
-
131
- elif node == self.config.koi_net.first_contact.rid:
132
- logger.debug(f"Node {node!r} is my first contact")
133
- # first contact node is always a webhook node
134
- event_queue = self.webhook_event_queue
135
-
136
- else:
137
- logger.warning(f"Node {node!r} unknown to me")
138
- return
139
-
140
- queue = event_queue.setdefault(node, Queue())
141
- queue.put(event)
142
-
143
- if flush and event_queue is self.webhook_event_queue:
144
- self.flush_webhook_queue(node)
145
-
146
- def _flush_queue(self, event_queue: EventQueue, node: KoiNetNode) -> list[Event]:
147
- """Flushes a node's queue, returning list of events."""
148
- queue = event_queue.get(node)
149
- events = list()
150
- if queue:
151
- while not queue.empty():
152
- event = queue.get()
153
- logger.debug(f"Dequeued {event.event_type} {event.rid!r}")
154
- events.append(event)
155
-
156
- return events
157
-
158
- def flush_poll_queue(self, node: KoiNetNode) -> list[Event]:
159
- """Flushes a node's poll queue, returning list of events."""
160
- logger.debug(f"Flushing poll queue for {node}")
161
- return self._flush_queue(self.poll_event_queue, node)
162
-
163
- def flush_webhook_queue(self, node: KoiNetNode, requeue_on_fail: bool = True):
164
- """Flushes a node's webhook queue, and broadcasts events.
165
-
166
- If node profile is unknown, or node type is not `FULL`, this operation will fail silently. If the remote node cannot be reached, all events will be requeued.
22
+ Event will be sent to webhook or poll queue depending on the
23
+ node type and edge type of the specified node. If `flush` is set
24
+ to `True`, the webhook queued will be flushed after pushing the
25
+ event.
167
26
  """
168
27
 
169
- logger.debug(f"Flushing webhook queue for {node}")
170
-
171
- # node_bundle = self.effector.deref(node)
172
-
173
- # if not node_bundle:
174
- # logger.warning(f"{node!r} not found")
175
- # return
176
-
177
- # node_profile = node_bundle.validate_contents(NodeProfile)
178
-
179
- # if node_profile.node_type != NodeType.FULL:
180
- # logger.warning(f"{node!r} is a partial node!")
181
- # return
182
-
183
- events = self._flush_queue(self.webhook_event_queue, node)
184
- if not events: return
185
-
186
- logger.debug(f"Broadcasting {len(events)} events")
187
-
188
- try:
189
- self.request_handler.broadcast_events(node, events=events)
190
-
191
- except NodeNotFoundError:
192
- logger.warning("Broadcast failed (node not found)")
193
-
194
- except httpx.ConnectError:
195
- logger.warning("Broadcast failed (couldn't connect)")
196
-
197
- if requeue_on_fail:
198
- for event in events:
199
- self.push_event_to(event, node)
28
+ self.q.put(QueuedEvent(target=target, event=event))
29
+
koi_net/network/graph.py CHANGED
@@ -56,7 +56,9 @@ class NetworkGraph:
56
56
  ) -> list[KoiNetEdge]:
57
57
  """Returns edges this node belongs to.
58
58
 
59
- All edges returned by default, specify `direction` to restrict to incoming or outgoing edges only."""
59
+ All edges returned by default, specify `direction` to restrict
60
+ to incoming or outgoing edges only.
61
+ """
60
62
 
61
63
  edges = []
62
64
  if direction != "in" and self.dg.out_edges:
@@ -85,7 +87,10 @@ class NetworkGraph:
85
87
  ) -> list[KoiNetNode]:
86
88
  """Returns neighboring nodes this node shares an edge with.
87
89
 
88
- All neighboring nodes returned by default, specify `direction` to restrict to neighbors connected by incoming or outgoing edges only."""
90
+ All neighboring nodes returned by default, specify `direction`
91
+ to restrict to neighbors connected by incoming or outgoing edges
92
+ only.
93
+ """
89
94
 
90
95
  neighbors = set()
91
96
  for edge_rid in self.get_edges(direction):
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import httpx
3
3
  from rid_lib import RID
4
+ from rid_lib.ext import Cache
4
5
  from rid_lib.types.koi_net_node import KoiNetNode
5
6
 
6
7
  from ..identity import NodeIdentity
@@ -27,11 +28,7 @@ from ..protocol.consts import (
27
28
  )
28
29
  from ..protocol.node import NodeProfile, NodeType
29
30
  from ..secure import Secure
30
- from ..effector import Effector
31
-
32
- from typing import TYPE_CHECKING
33
- if TYPE_CHECKING:
34
- from .error_handler import ErrorHandler
31
+ from .error_handler import ErrorHandler
35
32
 
36
33
 
37
34
  logger = logging.getLogger(__name__)
@@ -57,26 +54,25 @@ class UnknownPathError(Exception):
57
54
  class RequestHandler:
58
55
  """Handles making requests to other KOI nodes."""
59
56
 
60
- effector: Effector
57
+ cache: Cache
61
58
  identity: NodeIdentity
62
59
  secure: Secure
63
- error_handler: "ErrorHandler"
60
+ error_handler: ErrorHandler
64
61
 
65
62
  def __init__(
66
63
  self,
67
- effector: Effector,
64
+ cache: Cache,
68
65
  identity: NodeIdentity,
69
- secure: Secure
66
+ secure: Secure,
67
+ error_handler: ErrorHandler
70
68
  ):
71
- self.effector = effector
69
+ self.cache = cache
72
70
  self.identity = identity
73
71
  self.secure = secure
74
-
75
- def set_error_handler(self, error_handler: "ErrorHandler"):
76
72
  self.error_handler = error_handler
77
73
 
78
74
  def get_url(self, node_rid: KoiNetNode) -> str:
79
- """Retrieves URL of a node."""
75
+ """Retrieves URL of a node from its RID."""
80
76
 
81
77
  logger.debug(f"Getting URL for {node_rid!r}")
82
78
  node_url = None
@@ -84,7 +80,7 @@ class RequestHandler:
84
80
  if node_rid == self.identity.rid:
85
81
  raise SelfRequestError("Don't talk to yourself")
86
82
 
87
- node_bundle = self.effector.deref(node_rid)
83
+ node_bundle = self.cache.read(node_rid)
88
84
 
89
85
  if node_bundle:
90
86
  node_profile = node_bundle.validate_contents(NodeProfile)
@@ -110,6 +106,7 @@ class RequestHandler:
110
106
  path: str,
111
107
  request: RequestModels,
112
108
  ) -> ResponseModels | None:
109
+ """Makes a request to a node."""
113
110
  url = self.get_url(node) + path
114
111
  logger.info(f"Making request to {url}")
115
112
 
@@ -154,7 +151,10 @@ class RequestHandler:
154
151
  req: EventsPayload | None = None,
155
152
  **kwargs
156
153
  ) -> None:
157
- """See protocol.api_models.EventsPayload for available kwargs."""
154
+ """Broadcasts events to a node.
155
+
156
+ Pass `EventsPayload` object, or see `protocol.api_models.EventsPayload` for available kwargs.
157
+ """
158
158
  request = req or EventsPayload.model_validate(kwargs)
159
159
  self.make_request(node, BROADCAST_EVENTS_PATH, request)
160
160
  logger.info(f"Broadcasted {len(request.events)} event(s) to {node!r}")
@@ -165,7 +165,10 @@ class RequestHandler:
165
165
  req: PollEvents | None = None,
166
166
  **kwargs
167
167
  ) -> EventsPayload:
168
- """See protocol.api_models.PollEvents for available kwargs."""
168
+ """Polls events from a node.
169
+
170
+ Pass `PollEvents` object as `req` or fields as kwargs.
171
+ """
169
172
  request = req or PollEvents.model_validate(kwargs)
170
173
  resp = self.make_request(node, POLL_EVENTS_PATH, request)
171
174
  if type(resp) != ErrorResponse:
@@ -178,7 +181,10 @@ class RequestHandler:
178
181
  req: FetchRids | None = None,
179
182
  **kwargs
180
183
  ) -> RidsPayload:
181
- """See protocol.api_models.FetchRids for available kwargs."""
184
+ """Fetches RIDs from a node.
185
+
186
+ Pass `FetchRids` object as `req` or fields as kwargs.
187
+ """
182
188
  request = req or FetchRids.model_validate(kwargs)
183
189
  resp = self.make_request(node, FETCH_RIDS_PATH, request)
184
190
  if type(resp) != ErrorResponse:
@@ -191,7 +197,10 @@ class RequestHandler:
191
197
  req: FetchManifests | None = None,
192
198
  **kwargs
193
199
  ) -> ManifestsPayload:
194
- """See protocol.api_models.FetchManifests for available kwargs."""
200
+ """Fetches manifests from a node.
201
+
202
+ Pass `FetchManifests` object as `req` or fields as kwargs.
203
+ """
195
204
  request = req or FetchManifests.model_validate(kwargs)
196
205
  resp = self.make_request(node, FETCH_MANIFESTS_PATH, request)
197
206
  if type(resp) != ErrorResponse:
@@ -204,7 +213,10 @@ class RequestHandler:
204
213
  req: FetchBundles | None = None,
205
214
  **kwargs
206
215
  ) -> BundlesPayload:
207
- """See protocol.api_models.FetchBundles for available kwargs."""
216
+ """Fetches bundles from a node.
217
+
218
+ Pass `FetchBundles` object as `req` or fields as kwargs.
219
+ """
208
220
  request = req or FetchBundles.model_validate(kwargs)
209
221
  resp = self.make_request(node, FETCH_BUNDLES_PATH, request)
210
222
  if type(resp) != ErrorResponse:
@@ -12,17 +12,15 @@ from ..protocol.event import Event
12
12
  from ..protocol.api_models import ErrorResponse
13
13
  from ..identity import NodeIdentity
14
14
  from ..config import NodeConfig
15
- from ..effector import Effector
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
17
 
19
18
 
20
19
  class NetworkResolver:
21
- """A collection of functions and classes to interact with the KOI network."""
20
+ """Handles resolving nodes or knowledge objects from the network."""
22
21
 
23
22
  config: NodeConfig
24
23
  identity: NodeIdentity
25
- effector: Effector
26
24
  cache: Cache
27
25
  graph: NetworkGraph
28
26
  request_handler: RequestHandler
@@ -32,7 +30,6 @@ class NetworkResolver:
32
30
  config: NodeConfig,
33
31
  cache: Cache,
34
32
  identity: NodeIdentity,
35
- effector: Effector,
36
33
  graph: NetworkGraph,
37
34
  request_handler: RequestHandler,
38
35
  ):
@@ -41,13 +38,12 @@ class NetworkResolver:
41
38
  self.cache = cache
42
39
  self.graph = graph
43
40
  self.request_handler = request_handler
44
- self.effector = effector
45
41
 
46
42
  self.poll_event_queue = dict()
47
43
  self.webhook_event_queue = dict()
48
44
 
49
45
  def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
50
- """Returns list of node RIDs which provide state for the specified RID type."""
46
+ """Returns list of node RIDs which provide state for specified RID type."""
51
47
 
52
48
  logger.debug(f"Looking for state providers of {rid_type}")
53
49
  provider_nodes = []
@@ -106,9 +102,10 @@ class NetworkResolver:
106
102
  return remote_manifest, node_rid
107
103
 
108
104
  def poll_neighbors(self) -> dict[KoiNetNode, list[Event]]:
109
- """Polls all neighboring nodes and returns compiled list of events.
105
+ """Polls all neighbor nodes and returns compiled list of events.
110
106
 
111
- If this node has no neighbors, it will instead attempt to poll the provided first contact URL.
107
+ Neighbor nodes also include the first contact, regardless of
108
+ whether the first contact profile is known to this node.
112
109
  """
113
110
 
114
111
  graph_neighbors = self.graph.get_neighbors()