koi-net 1.1.0b8__py3-none-any.whl → 1.2.0b2__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 (48) hide show
  1. koi_net/__init__.py +2 -1
  2. koi_net/assembler.py +82 -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 +11 -28
  8. koi_net/core.py +63 -179
  9. koi_net/default_actions.py +10 -1
  10. koi_net/effector.py +61 -34
  11. koi_net/handshaker.py +39 -0
  12. koi_net/identity.py +2 -3
  13. koi_net/interfaces/entrypoint.py +5 -0
  14. koi_net/interfaces/worker.py +17 -0
  15. koi_net/lifecycle.py +85 -48
  16. koi_net/logger.py +176 -0
  17. koi_net/network/error_handler.py +18 -16
  18. koi_net/network/event_queue.py +17 -185
  19. koi_net/network/graph.py +15 -10
  20. koi_net/network/poll_event_buffer.py +26 -0
  21. koi_net/network/request_handler.py +54 -47
  22. koi_net/network/resolver.py +18 -21
  23. koi_net/network/response_handler.py +79 -15
  24. koi_net/poller.py +18 -9
  25. koi_net/processor/event_worker.py +117 -0
  26. koi_net/processor/handler.py +4 -2
  27. koi_net/processor/{default_handlers.py → handlers.py} +109 -59
  28. koi_net/processor/knowledge_object.py +19 -7
  29. koi_net/processor/kobj_queue.py +51 -0
  30. koi_net/processor/kobj_worker.py +44 -0
  31. koi_net/processor/{knowledge_pipeline.py → pipeline.py} +31 -53
  32. koi_net/protocol/api_models.py +7 -3
  33. koi_net/protocol/envelope.py +5 -6
  34. koi_net/protocol/model_map.py +61 -0
  35. koi_net/protocol/node.py +3 -3
  36. koi_net/protocol/secure.py +8 -8
  37. koi_net/secure.py +33 -13
  38. koi_net/sentry.py +13 -0
  39. koi_net/server.py +44 -78
  40. koi_net/utils.py +18 -0
  41. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/METADATA +8 -3
  42. koi_net-1.2.0b2.dist-info/RECORD +52 -0
  43. koi_net-1.2.0b2.dist-info/entry_points.txt +2 -0
  44. koi_net/actor.py +0 -60
  45. koi_net/processor/interface.py +0 -101
  46. koi_net-1.1.0b8.dist-info/RECORD +0 -38
  47. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/WHEEL +0 -0
  48. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/licenses/LICENSE +0 -0
@@ -1,36 +1,37 @@
1
- from logging import getLogger
1
+ import structlog
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
- logger = getLogger(__name__)
8
+ log = structlog.stdlib.get_logger()
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
 
29
- logger.debug(f"{node} has timed out {self.timeout_counter[node]} time(s)")
30
+ log.debug(f"{node} has timed out {self.timeout_counter[node]} time(s)")
30
31
 
31
32
  if self.timeout_counter[node] > 3:
32
- logger.debug(f"Exceeded time out limit, forgetting node")
33
- self.processor.handle(rid=node, event_type=EventType.FORGET)
33
+ log.debug(f"Exceeded time out limit, forgetting node")
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
  ):
42
- logger.info(f"Handling protocol error {error_type} for node {node!r}")
43
+ """Attempts handshake when this node is unknown to target."""
44
+ log.info(f"Handling protocol error {error_type} for node {node!r}")
43
45
  match error_type:
44
46
  case ErrorType.UnknownNode:
45
- logger.info("Peer doesn't know me, attempting handshake...")
46
- self.actor.handshake_with(node)
47
+ log.info("Peer doesn't know me, attempting handshake...")
48
+ self.handshaker.handshake_with(node)
47
49
 
48
50
  case ErrorType.InvalidKey: ...
49
51
  case ErrorType.InvalidSignature: ...
@@ -1,199 +1,31 @@
1
- import logging
1
+ import structlog
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
5
+ from pydantic import BaseModel
8
6
 
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
13
7
  from ..protocol.event import Event
14
- from ..identity import NodeIdentity
15
- from ..config import NodeConfig
16
- from ..effector import Effector
17
-
18
- logger = logging.getLogger(__name__)
19
8
 
9
+ log = structlog.stdlib.get_logger()
20
10
 
21
- class EventQueueModel(BaseModel):
22
- webhook: dict[KoiNetNode, list[Event]]
23
- poll: dict[KoiNetNode, list[Event]]
24
11
 
25
- type EventQueue = dict[RID, Queue[Event]]
12
+ class QueuedEvent(BaseModel):
13
+ event: Event
14
+ target: KoiNetNode
26
15
 
27
- class NetworkEventQueue:
28
- """A collection of functions and classes to interact with the KOI network."""
16
+ class EventQueue:
17
+ """Handles out going network event queues."""
18
+ q: Queue[QueuedEvent]
29
19
 
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
20
+ def __init__(self):
21
+ self.q = Queue()
38
22
 
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))
97
-
98
- def push_event_to(self, event: Event, node: KoiNetNode, flush=False):
23
+ def push_event_to(self, event: Event, target: KoiNetNode):
99
24
  """Pushes event to queue of specified node.
100
25
 
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.
26
+ Event will be sent to webhook or poll queue depending on the
27
+ node type and edge type of the specified node.
102
28
  """
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
29
 
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.
167
- """
168
-
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)
30
+ self.q.put(QueuedEvent(target=target, event=event))
31
+
koi_net/network/graph.py CHANGED
@@ -1,4 +1,4 @@
1
- import logging
1
+ import structlog
2
2
  from typing import Literal
3
3
  import networkx as nx
4
4
  from rid_lib import RIDType
@@ -7,7 +7,7 @@ from rid_lib.types import KoiNetEdge, KoiNetNode
7
7
  from ..identity import NodeIdentity
8
8
  from ..protocol.edge import EdgeProfile, EdgeStatus
9
9
 
10
- logger = logging.getLogger(__name__)
10
+ log = structlog.stdlib.get_logger()
11
11
 
12
12
 
13
13
  class NetworkGraph:
@@ -24,22 +24,22 @@ class NetworkGraph:
24
24
 
25
25
  def generate(self):
26
26
  """Generates directed graph from cached KOI nodes and edges."""
27
- logger.debug("Generating network graph")
27
+ log.debug("Generating network graph")
28
28
  self.dg.clear()
29
29
  for rid in self.cache.list_rids():
30
30
  if type(rid) == KoiNetNode:
31
31
  self.dg.add_node(rid)
32
- logger.debug(f"Added node {rid!r}")
32
+ log.debug(f"Added node {rid!r}")
33
33
 
34
34
  elif type(rid) == KoiNetEdge:
35
35
  edge_bundle = self.cache.read(rid)
36
36
  if not edge_bundle:
37
- logger.warning(f"Failed to load {rid!r}")
37
+ log.warning(f"Failed to load {rid!r}")
38
38
  continue
39
39
  edge_profile = edge_bundle.validate_contents(EdgeProfile)
40
40
  self.dg.add_edge(edge_profile.source, edge_profile.target, rid=rid)
41
- logger.debug(f"Added edge {rid!r} ({edge_profile.source} -> {edge_profile.target})")
42
- logger.debug("Done")
41
+ log.debug(f"Added edge {rid!r} ({edge_profile.source} -> {edge_profile.target})")
42
+ log.debug("Done")
43
43
 
44
44
  def get_edge(self, source: KoiNetNode, target: KoiNetNode,) -> KoiNetEdge | None:
45
45
  """Returns edge RID given the RIDs of a source and target node."""
@@ -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,14 +87,17 @@ 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):
92
97
  edge_bundle = self.cache.read(edge_rid)
93
98
 
94
99
  if not edge_bundle:
95
- logger.warning(f"Failed to find edge {edge_rid!r} in cache")
100
+ log.warning(f"Failed to find edge {edge_rid!r} in cache")
96
101
  continue
97
102
 
98
103
  edge_profile = edge_bundle.validate_contents(EdgeProfile)
@@ -0,0 +1,26 @@
1
+ from rid_lib.types import KoiNetNode
2
+
3
+ from koi_net.protocol.event import Event
4
+
5
+
6
+ class PollEventBuffer:
7
+ buffers: dict[KoiNetNode, list[Event]]
8
+
9
+ def __init__(self):
10
+ self.buffers = dict()
11
+
12
+ def put(self, node: KoiNetNode, event: Event):
13
+ event_buf = self.buffers.setdefault(node, [])
14
+ event_buf.append(event)
15
+
16
+ def flush(self, node: KoiNetNode, limit: int = 0):
17
+ event_buf = self.buffers.get(node, [])
18
+
19
+ if limit and len(event_buf) > limit:
20
+ to_return = event_buf[:limit]
21
+ self.buffers[node] = event_buf[limit:]
22
+ else:
23
+ to_return = event_buf.copy()
24
+ self.buffers[node] = []
25
+
26
+ return to_return
@@ -1,8 +1,11 @@
1
- import logging
1
+ import structlog
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
 
7
+ from koi_net.protocol.model_map import API_MODEL_MAP
8
+
6
9
  from ..identity import NodeIdentity
7
10
  from ..protocol.api_models import (
8
11
  RidsPayload,
@@ -27,14 +30,9 @@ from ..protocol.consts import (
27
30
  )
28
31
  from ..protocol.node import NodeProfile, NodeType
29
32
  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
35
-
33
+ from .error_handler import ErrorHandler
36
34
 
37
- logger = logging.getLogger(__name__)
35
+ log = structlog.stdlib.get_logger()
38
36
 
39
37
 
40
38
  # Custom error types for request handling
@@ -57,51 +55,50 @@ class UnknownPathError(Exception):
57
55
  class RequestHandler:
58
56
  """Handles making requests to other KOI nodes."""
59
57
 
60
- effector: Effector
58
+ cache: Cache
61
59
  identity: NodeIdentity
62
60
  secure: Secure
63
- error_handler: "ErrorHandler"
61
+ error_handler: ErrorHandler
64
62
 
65
63
  def __init__(
66
64
  self,
67
- effector: Effector,
65
+ cache: Cache,
68
66
  identity: NodeIdentity,
69
- secure: Secure
67
+ secure: Secure,
68
+ error_handler: ErrorHandler
70
69
  ):
71
- self.effector = effector
70
+ self.cache = cache
72
71
  self.identity = identity
73
72
  self.secure = secure
74
-
75
- def set_error_handler(self, error_handler: "ErrorHandler"):
76
73
  self.error_handler = error_handler
77
74
 
78
75
  def get_url(self, node_rid: KoiNetNode) -> str:
79
- """Retrieves URL of a node."""
76
+ """Retrieves URL of a node from its RID."""
80
77
 
81
- logger.debug(f"Getting URL for {node_rid!r}")
78
+ log.debug(f"Getting URL for {node_rid!r}")
82
79
  node_url = None
83
80
 
84
81
  if node_rid == self.identity.rid:
85
82
  raise SelfRequestError("Don't talk to yourself")
86
83
 
87
- node_bundle = self.effector.deref(node_rid)
84
+ node_bundle = self.cache.read(node_rid)
88
85
 
89
86
  if node_bundle:
90
87
  node_profile = node_bundle.validate_contents(NodeProfile)
91
- logger.debug(f"Found node profile: {node_profile}")
88
+ log.debug(f"Found node profile: {node_profile}")
92
89
  if node_profile.node_type != NodeType.FULL:
93
90
  raise PartialNodeQueryError("Can't query partial node")
94
91
  node_url = node_profile.base_url
95
92
 
96
93
  else:
97
94
  if node_rid == self.identity.config.koi_net.first_contact.rid:
98
- logger.debug("Found URL of first contact")
95
+ log.debug("Found URL of first contact")
99
96
  node_url = self.identity.config.koi_net.first_contact.url
100
97
 
101
98
  if not node_url:
102
99
  raise NodeNotFoundError("Node not found")
103
100
 
104
- logger.debug(f"Resolved {node_rid!r} to {node_url}")
101
+ log.debug(f"Resolved {node_rid!r} to {node_url}")
105
102
  return node_url
106
103
 
107
104
  def make_request(
@@ -110,8 +107,9 @@ class RequestHandler:
110
107
  path: str,
111
108
  request: RequestModels,
112
109
  ) -> ResponseModels | None:
110
+ """Makes a request to a node."""
113
111
  url = self.get_url(node) + path
114
- logger.info(f"Making request to {url}")
112
+ log.info(f"Making request to {url}")
115
113
 
116
114
  signed_envelope = self.secure.create_envelope(
117
115
  payload=request,
@@ -119,9 +117,12 @@ class RequestHandler:
119
117
  )
120
118
 
121
119
  try:
122
- result = httpx.post(url, data=signed_envelope.model_dump_json(exclude_none=True))
120
+ result = httpx.post(
121
+ url,
122
+ data=signed_envelope.model_dump_json(exclude_none=True)
123
+ )
123
124
  except httpx.ConnectError as err:
124
- logger.debug("Failed to connect")
125
+ log.debug("Failed to connect")
125
126
  self.error_handler.handle_connection_error(node)
126
127
  raise err
127
128
 
@@ -130,20 +131,11 @@ class RequestHandler:
130
131
  self.error_handler.handle_protocol_error(resp.error, node)
131
132
  return resp
132
133
 
133
- if path == BROADCAST_EVENTS_PATH:
134
- return None
135
- elif path == POLL_EVENTS_PATH:
136
- EnvelopeModel = SignedEnvelope[EventsPayload]
137
- elif path == FETCH_RIDS_PATH:
138
- EnvelopeModel = SignedEnvelope[RidsPayload]
139
- elif path == FETCH_MANIFESTS_PATH:
140
- EnvelopeModel = SignedEnvelope[ManifestsPayload]
141
- elif path == FETCH_BUNDLES_PATH:
142
- EnvelopeModel = SignedEnvelope[BundlesPayload]
143
- else:
144
- raise UnknownPathError(f"Unknown path '{path}'")
134
+ resp_env_model = API_MODEL_MAP[path].response_envelope
135
+ if resp_env_model is None:
136
+ return
145
137
 
146
- resp_envelope = EnvelopeModel.model_validate_json(result.text)
138
+ resp_envelope = resp_env_model.model_validate_json(result.text)
147
139
  self.secure.validate_envelope(resp_envelope)
148
140
 
149
141
  return resp_envelope.payload
@@ -154,10 +146,13 @@ class RequestHandler:
154
146
  req: EventsPayload | None = None,
155
147
  **kwargs
156
148
  ) -> None:
157
- """See protocol.api_models.EventsPayload for available kwargs."""
149
+ """Broadcasts events to a node.
150
+
151
+ Pass `EventsPayload` object, or see `protocol.api_models.EventsPayload` for available kwargs.
152
+ """
158
153
  request = req or EventsPayload.model_validate(kwargs)
159
154
  self.make_request(node, BROADCAST_EVENTS_PATH, request)
160
- logger.info(f"Broadcasted {len(request.events)} event(s) to {node!r}")
155
+ log.info(f"Broadcasted {len(request.events)} event(s) to {node!r}")
161
156
 
162
157
  def poll_events(
163
158
  self,
@@ -165,11 +160,14 @@ class RequestHandler:
165
160
  req: PollEvents | None = None,
166
161
  **kwargs
167
162
  ) -> EventsPayload:
168
- """See protocol.api_models.PollEvents for available kwargs."""
163
+ """Polls events from a node.
164
+
165
+ Pass `PollEvents` object as `req` or fields as kwargs.
166
+ """
169
167
  request = req or PollEvents.model_validate(kwargs)
170
168
  resp = self.make_request(node, POLL_EVENTS_PATH, request)
171
169
  if type(resp) != ErrorResponse:
172
- logger.info(f"Polled {len(resp.events)} events from {node!r}")
170
+ log.info(f"Polled {len(resp.events)} events from {node!r}")
173
171
  return resp
174
172
 
175
173
  def fetch_rids(
@@ -178,11 +176,14 @@ class RequestHandler:
178
176
  req: FetchRids | None = None,
179
177
  **kwargs
180
178
  ) -> RidsPayload:
181
- """See protocol.api_models.FetchRids for available kwargs."""
179
+ """Fetches RIDs from a node.
180
+
181
+ Pass `FetchRids` object as `req` or fields as kwargs.
182
+ """
182
183
  request = req or FetchRids.model_validate(kwargs)
183
184
  resp = self.make_request(node, FETCH_RIDS_PATH, request)
184
185
  if type(resp) != ErrorResponse:
185
- logger.info(f"Fetched {len(resp.rids)} RID(s) from {node!r}")
186
+ log.info(f"Fetched {len(resp.rids)} RID(s) from {node!r}")
186
187
  return resp
187
188
 
188
189
  def fetch_manifests(
@@ -191,11 +192,14 @@ class RequestHandler:
191
192
  req: FetchManifests | None = None,
192
193
  **kwargs
193
194
  ) -> ManifestsPayload:
194
- """See protocol.api_models.FetchManifests for available kwargs."""
195
+ """Fetches manifests from a node.
196
+
197
+ Pass `FetchManifests` object as `req` or fields as kwargs.
198
+ """
195
199
  request = req or FetchManifests.model_validate(kwargs)
196
200
  resp = self.make_request(node, FETCH_MANIFESTS_PATH, request)
197
201
  if type(resp) != ErrorResponse:
198
- logger.info(f"Fetched {len(resp.manifests)} manifest(s) from {node!r}")
202
+ log.info(f"Fetched {len(resp.manifests)} manifest(s) from {node!r}")
199
203
  return resp
200
204
 
201
205
  def fetch_bundles(
@@ -204,9 +208,12 @@ class RequestHandler:
204
208
  req: FetchBundles | None = None,
205
209
  **kwargs
206
210
  ) -> BundlesPayload:
207
- """See protocol.api_models.FetchBundles for available kwargs."""
211
+ """Fetches bundles from a node.
212
+
213
+ Pass `FetchBundles` object as `req` or fields as kwargs.
214
+ """
208
215
  request = req or FetchBundles.model_validate(kwargs)
209
216
  resp = self.make_request(node, FETCH_BUNDLES_PATH, request)
210
217
  if type(resp) != ErrorResponse:
211
- logger.info(f"Fetched {len(resp.bundles)} bundle(s) from {node!r}")
218
+ log.info(f"Fetched {len(resp.bundles)} bundle(s) from {node!r}")
212
219
  return resp