koi-net 1.1.0b2__py3-none-any.whl → 1.1.0b4__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/context.py CHANGED
@@ -1,6 +1,6 @@
1
-
2
- from koi_net.effector import Effector
3
1
  from rid_lib.ext import Cache
2
+ from .config import NodeConfig
3
+ from .effector import Effector
4
4
  from .network.graph import NetworkGraph
5
5
  from .network.event_queue import NetworkEventQueue
6
6
  from .network.request_handler import RequestHandler
@@ -23,6 +23,7 @@ class ActionContext:
23
23
 
24
24
  class HandlerContext:
25
25
  identity: NodeIdentity
26
+ config: NodeConfig
26
27
  cache: Cache
27
28
  event_queue: NetworkEventQueue
28
29
  graph: NetworkGraph
@@ -33,6 +34,7 @@ class HandlerContext:
33
34
  def __init__(
34
35
  self,
35
36
  identity: NodeIdentity,
37
+ config: NodeConfig,
36
38
  cache: Cache,
37
39
  event_queue: NetworkEventQueue,
38
40
  graph: NetworkGraph,
@@ -40,6 +42,7 @@ class HandlerContext:
40
42
  effector: Effector
41
43
  ):
42
44
  self.identity = identity
45
+ self.config = config
43
46
  self.cache = cache
44
47
  self.event_queue = event_queue
45
48
  self.graph = graph
koi_net/core.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from koi_net.protocol.node import NodeType
2
+ from typing import Generic, TypeVar
3
3
  from rid_lib.ext import Cache
4
4
  from .network.resolver import NetworkResolver
5
5
  from .network.event_queue import NetworkEventQueue
@@ -25,9 +25,10 @@ from . import default_actions
25
25
  logger = logging.getLogger(__name__)
26
26
 
27
27
 
28
+ T = TypeVar("T", bound=NodeConfig)
28
29
 
29
- class NodeInterface:
30
- config: NodeConfig
30
+ class NodeInterface(Generic[T]):
31
+ config: T
31
32
  cache: Cache
32
33
  identity: NodeIdentity
33
34
  resolver: NetworkResolver
@@ -41,58 +42,75 @@ class NodeInterface:
41
42
 
42
43
  def __init__(
43
44
  self,
44
- config: NodeConfig,
45
+ config: T,
45
46
  use_kobj_processor_thread: bool = False,
46
47
  handlers: list[KnowledgeHandler] | None = None,
47
- cache: Cache | None = None,
48
- processor: ProcessorInterface | None = None
48
+
49
+ CacheOverride: type[Cache] | None = None,
50
+ NodeIdentityOverride: type[NodeIdentity] | None = None,
51
+ EffectorOverride: type[Effector] | None = None,
52
+ NetworkGraphOverride: type[NetworkGraph] | None = None,
53
+ SecureOverride: type[Secure] | None = None,
54
+ RequestHandlerOverride: type[RequestHandler] | None = None,
55
+ ResponseHandlerOverride: type[ResponseHandler] | None = None,
56
+ NetworkResolverOverride: type[NetworkResolver] | None = None,
57
+ NetworkEventQueueOverride: type[NetworkEventQueue] | None = None,
58
+ ActorOverride: type[Actor] | None = None,
59
+ ActionContextOverride: type[ActionContext] | None = None,
60
+ HandlerContextOverride: type[HandlerContext] | None = None,
61
+ KnowledgePipelineOverride: type[KnowledgePipeline] | None = None,
62
+ ProcessorInterfaceOverride: type[ProcessorInterface] | None = None,
63
+ ErrorHandlerOverride: type[ErrorHandler] | None = None,
64
+ NodeLifecycleOverride: type[NodeLifecycle] | None = None,
65
+ NodeServerOverride: type[NodeServer] | None = None,
66
+ NodePollerOverride: type[NodePoller] | None = None,
49
67
  ):
50
68
  self.config = config
51
- self.cache = cache or Cache(
69
+ self.cache = (CacheOverride or Cache)(
52
70
  directory_path=self.config.koi_net.cache_directory_path
53
71
  )
54
72
 
55
- self.identity = NodeIdentity(config=self.config)
56
- self.effector = Effector(cache=self.cache)
73
+ self.identity = (NodeIdentityOverride or NodeIdentity)(config=self.config)
74
+ self.effector = (EffectorOverride or Effector)(cache=self.cache)
57
75
 
58
- self.graph = NetworkGraph(
59
- cache=self.cache,
76
+ self.graph = (NetworkGraphOverride or NetworkGraph)(
77
+ cache=self.cache,
60
78
  identity=self.identity
61
79
  )
62
-
63
- self.secure = Secure(
64
- identity=self.identity,
65
- effector=self.effector,
80
+
81
+ self.secure = (SecureOverride or Secure)(
82
+ identity=self.identity,
83
+ effector=self.effector,
66
84
  config=self.config
67
85
  )
68
-
69
- self.request_handler = RequestHandler(
70
- effector=self.effector,
86
+
87
+ self.request_handler = (RequestHandlerOverride or RequestHandler)(
88
+ effector=self.effector,
71
89
  identity=self.identity,
72
90
  secure=self.secure
73
91
  )
74
-
75
- self.response_handler = ResponseHandler(self.cache, self.effector)
76
-
77
- self.resolver = NetworkResolver(
92
+
93
+ self.response_handler = (ResponseHandlerOverride or ResponseHandler)(self.cache, self.effector)
94
+
95
+ self.resolver = (NetworkResolverOverride or NetworkResolver)(
78
96
  config=self.config,
79
- cache=self.cache,
97
+ cache=self.cache,
80
98
  identity=self.identity,
81
99
  graph=self.graph,
82
100
  request_handler=self.request_handler,
83
101
  effector=self.effector
84
102
  )
85
-
86
- self.event_queue = NetworkEventQueue(
103
+
104
+ self.event_queue = (NetworkEventQueueOverride or NetworkEventQueue)(
87
105
  config=self.config,
88
- cache=self.cache,
106
+ cache=self.cache,
89
107
  identity=self.identity,
90
108
  graph=self.graph,
91
109
  request_handler=self.request_handler,
92
110
  effector=self.effector
93
111
  )
94
112
 
95
- self.actor = Actor(
113
+ self.actor = (ActorOverride or Actor)(
96
114
  identity=self.identity,
97
115
  effector=self.effector,
98
116
  event_queue=self.event_queue
@@ -107,13 +125,14 @@ class NodeInterface:
107
125
 
108
126
  self.use_kobj_processor_thread = use_kobj_processor_thread
109
127
 
110
- self.action_context = ActionContext(
128
+ self.action_context = (ActionContextOverride or ActionContext)(
111
129
  identity=self.identity,
112
130
  effector=self.effector
113
131
  )
114
132
 
115
- self.handler_context = HandlerContext(
133
+ self.handler_context = (HandlerContextOverride or HandlerContext)(
116
134
  identity=self.identity,
135
+ config=self.config,
117
136
  cache=self.cache,
118
137
  event_queue=self.event_queue,
119
138
  graph=self.graph,
@@ -121,7 +140,7 @@ class NodeInterface:
121
140
  effector=self.effector
122
141
  )
123
142
 
124
- self.pipeline = KnowledgePipeline(
143
+ self.pipeline = (KnowledgePipelineOverride or KnowledgePipeline)(
125
144
  handler_context=self.handler_context,
126
145
  cache=self.cache,
127
146
  request_handler=self.request_handler,
@@ -130,12 +149,12 @@ class NodeInterface:
130
149
  default_handlers=handlers
131
150
  )
132
151
 
133
- self.processor = processor or ProcessorInterface(
152
+ self.processor = (ProcessorInterfaceOverride or ProcessorInterface)(
134
153
  pipeline=self.pipeline,
135
154
  use_kobj_processor_thread=self.use_kobj_processor_thread
136
155
  )
137
156
 
138
- self.error_handler = ErrorHandler(
157
+ self.error_handler = (ErrorHandlerOverride or ErrorHandler)(
139
158
  processor=self.processor,
140
159
  actor=self.actor
141
160
  )
@@ -148,7 +167,7 @@ class NodeInterface:
148
167
  self.effector.set_resolver(self.resolver)
149
168
  self.effector.set_action_context(self.action_context)
150
169
 
151
- self.lifecycle = NodeLifecycle(
170
+ self.lifecycle = (NodeLifecycleOverride or NodeLifecycle)(
152
171
  config=self.config,
153
172
  identity=self.identity,
154
173
  graph=self.graph,
@@ -159,7 +178,7 @@ class NodeInterface:
159
178
  )
160
179
 
161
180
  # if self.config.koi_net.node_profile.node_type == NodeType.FULL:
162
- self.server = NodeServer(
181
+ self.server = (NodeServerOverride or NodeServer)(
163
182
  config=self.config,
164
183
  lifecycle=self.lifecycle,
165
184
  secure=self.secure,
@@ -168,7 +187,7 @@ class NodeInterface:
168
187
  response_handler=self.response_handler
169
188
  )
170
189
 
171
- self.poller = NodePoller(
190
+ self.poller = (NodePollerOverride or NodePoller)(
172
191
  processor=self.processor,
173
192
  lifecycle=self.lifecycle,
174
193
  resolver=self.resolver,
@@ -7,7 +7,7 @@ from rid_lib.ext import Cache
7
7
  from rid_lib.types import KoiNetNode
8
8
 
9
9
  from .graph import NetworkGraph
10
- from .request_handler import RequestHandler
10
+ from .request_handler import NodeNotFoundError, RequestHandler
11
11
  from ..protocol.node import NodeProfile, NodeType
12
12
  from ..protocol.edge import EdgeProfile, EdgeType
13
13
  from ..protocol.event import Event
@@ -187,11 +187,13 @@ class NetworkEventQueue:
187
187
 
188
188
  try:
189
189
  self.request_handler.broadcast_events(node, events=events)
190
- return True
190
+
191
+ except NodeNotFoundError:
192
+ logger.warning("Broadcast failed (node not found)")
193
+
191
194
  except httpx.ConnectError:
192
- logger.warning("Broadcast failed")
195
+ logger.warning("Broadcast failed (couldn't connect)")
193
196
 
194
197
  if requeue_on_fail:
195
198
  for event in events:
196
- self.push_event_to(event, node)
197
- return False
199
+ self.push_event_to(event, node)
@@ -37,6 +37,23 @@ if TYPE_CHECKING:
37
37
  logger = logging.getLogger(__name__)
38
38
 
39
39
 
40
+ # Custom error types for request handling
41
+ class SelfRequestError(Exception):
42
+ """Raised when a node tries to request itself."""
43
+ pass
44
+
45
+ class PartialNodeQueryError(Exception):
46
+ """Raised when attempting to query a partial node."""
47
+ pass
48
+
49
+ class NodeNotFoundError(Exception):
50
+ """Raised when a node URL cannot be found."""
51
+ pass
52
+
53
+ class UnknownPathError(Exception):
54
+ """Raised when an unknown path is requested."""
55
+ pass
56
+
40
57
  class RequestHandler:
41
58
  """Handles making requests to other KOI nodes."""
42
59
 
@@ -65,7 +82,7 @@ class RequestHandler:
65
82
  node_url = None
66
83
 
67
84
  if node_rid == self.identity.rid:
68
- raise Exception("Don't talk to yourself")
85
+ raise SelfRequestError("Don't talk to yourself")
69
86
 
70
87
  node_bundle = self.effector.deref(node_rid)
71
88
 
@@ -73,7 +90,7 @@ class RequestHandler:
73
90
  node_profile = node_bundle.validate_contents(NodeProfile)
74
91
  logger.debug(f"Found node profile: {node_profile}")
75
92
  if node_profile.node_type != NodeType.FULL:
76
- raise Exception("Can't query partial node")
93
+ raise PartialNodeQueryError("Can't query partial node")
77
94
  node_url = node_profile.base_url
78
95
 
79
96
  else:
@@ -82,7 +99,7 @@ class RequestHandler:
82
99
  node_url = self.identity.config.koi_net.first_contact.url
83
100
 
84
101
  if not node_url:
85
- raise Exception("Node not found")
102
+ raise NodeNotFoundError("Node not found")
86
103
 
87
104
  logger.debug(f"Resolved {node_rid!r} to {node_url}")
88
105
  return node_url
@@ -124,7 +141,7 @@ class RequestHandler:
124
141
  elif path == FETCH_BUNDLES_PATH:
125
142
  EnvelopeModel = SignedEnvelope[BundlesPayload]
126
143
  else:
127
- raise Exception(f"Unknown path '{path}'")
144
+ raise UnknownPathError(f"Unknown path '{path}'")
128
145
 
129
146
  resp_envelope = EnvelopeModel.model_validate_json(result.text)
130
147
  self.secure.validate_envelope(resp_envelope)
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from rid_lib import RID
3
+ from rid_lib.types import KoiNetNode
3
4
  from rid_lib.ext import Manifest, Cache
4
5
  from rid_lib.ext.bundle import Bundle
5
6
 
@@ -30,13 +31,13 @@ class ResponseHandler:
30
31
  self.cache = cache
31
32
  self.effector = effector
32
33
 
33
- def fetch_rids(self, req: FetchRids) -> RidsPayload:
34
+ def fetch_rids(self, req: FetchRids, source: KoiNetNode) -> RidsPayload:
34
35
  logger.info(f"Request to fetch rids, allowed types {req.rid_types}")
35
36
  rids = self.cache.list_rids(req.rid_types)
36
37
 
37
38
  return RidsPayload(rids=rids)
38
39
 
39
- def fetch_manifests(self, req: FetchManifests) -> ManifestsPayload:
40
+ def fetch_manifests(self, req: FetchManifests, source: KoiNetNode) -> ManifestsPayload:
40
41
  logger.info(f"Request to fetch manifests, allowed types {req.rid_types}, rids {req.rids}")
41
42
 
42
43
  manifests: list[Manifest] = []
@@ -51,7 +52,7 @@ class ResponseHandler:
51
52
 
52
53
  return ManifestsPayload(manifests=manifests, not_found=not_found)
53
54
 
54
- def fetch_bundles(self, req: FetchBundles) -> BundlesPayload:
55
+ def fetch_bundles(self, req: FetchBundles, source: KoiNetNode) -> BundlesPayload:
55
56
  logger.info(f"Request to fetch bundles, requested rids {req.rids}")
56
57
 
57
58
  bundles: list[Bundle] = []
koi_net/poller.py CHANGED
@@ -22,17 +22,19 @@ class NodePoller:
22
22
  self.resolver = resolver
23
23
  self.config = config
24
24
 
25
+ def poll(self):
26
+ neighbors = self.resolver.poll_neighbors()
27
+ for node_rid in neighbors:
28
+ for event in neighbors[node_rid]:
29
+ self.processor.handle(event=event, source=node_rid)
30
+ self.processor.flush_kobj_queue()
31
+
25
32
  def run(self):
26
33
  try:
27
34
  self.lifecycle.start()
28
35
  while True:
29
36
  start_time = time.time()
30
- neighbors = self.resolver.poll_neighbors()
31
- for node_rid in neighbors:
32
- for event in neighbors[node_rid]:
33
- self.processor.handle(event=event, source=node_rid)
34
- self.processor.flush_kobj_queue()
35
-
37
+ self.poll()
36
38
  elapsed = time.time() - start_time
37
39
  sleep_time = self.config.koi_net.polling_interval - elapsed
38
40
  if sleep_time > 0:
@@ -233,4 +233,17 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
233
233
  kobj.network_targets.update(subscribers)
234
234
 
235
235
  return kobj
236
-
236
+
237
+ @KnowledgeHandler.create(HandlerType.Final, rid_types=[KoiNetNode])
238
+ def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
239
+ if kobj.normalized_event_type != EventType.FORGET:
240
+ return
241
+
242
+ for edge_rid in ctx.graph.get_edges():
243
+ edge_bundle = ctx.cache.read(edge_rid)
244
+ if not edge_bundle: continue
245
+ edge_profile = edge_bundle.validate_contents(EdgeProfile)
246
+
247
+ if kobj.rid in (edge_profile.source, edge_profile.target):
248
+ logger.debug("Identified edge with forgotten node")
249
+ ctx.handle(rid=edge_rid, event_type=EventType.FORGET)
koi_net/server.py CHANGED
@@ -109,20 +109,20 @@ class NodeServer:
109
109
  self, req: SignedEnvelope[PollEvents]
110
110
  ) -> SignedEnvelope[EventsPayload] | ErrorResponse:
111
111
  logger.info(f"Request to {POLL_EVENTS_PATH}")
112
- events = self.event_queue.flush_poll_queue(req.payload.rid)
112
+ events = self.event_queue.flush_poll_queue(req.source_node)
113
113
  return EventsPayload(events=events)
114
114
 
115
115
  async def fetch_rids(
116
116
  self, req: SignedEnvelope[FetchRids]
117
117
  ) -> SignedEnvelope[RidsPayload] | ErrorResponse:
118
- return self.response_handler.fetch_rids(req.payload)
118
+ return self.response_handler.fetch_rids(req.payload, req.source_node)
119
119
 
120
120
  async def fetch_manifests(
121
121
  self, req: SignedEnvelope[FetchManifests]
122
122
  ) -> SignedEnvelope[ManifestsPayload] | ErrorResponse:
123
- return self.response_handler.fetch_manifests(req.payload)
123
+ return self.response_handler.fetch_manifests(req.payload, req.source_node)
124
124
 
125
125
  async def fetch_bundles(
126
126
  self, req: SignedEnvelope[FetchBundles]
127
127
  ) -> SignedEnvelope[BundlesPayload] | ErrorResponse:
128
- return self.response_handler.fetch_bundles(req.payload)
128
+ return self.response_handler.fetch_bundles(req.payload, req.source_node)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.1.0b2
3
+ Version: 1.1.0b4
4
4
  Summary: Implementation of KOI-net protocol in Python
5
5
  Project-URL: Homepage, https://github.com/BlockScience/koi-net/
6
6
  Author-email: Luke Miller <luke@block.science>
@@ -1,24 +1,24 @@
1
1
  koi_net/__init__.py,sha256=b0Ze0pZmJAuygpWUFHM6Kvqo3DkU_uzmkptv1EpAArw,31
2
2
  koi_net/config.py,sha256=47XbQ59GRYFi4rlsoWKlnzMQATcnK70i3qmKTZAGOQk,4087
3
- koi_net/context.py,sha256=JmbpCzusXFq_NCXiUp5Z56N6vpBdYMUK8eOs7ogO68A,1428
4
- koi_net/core.py,sha256=wJjEaza-Q_QqTZL8xOWdlMkjOhCmX8Lj3uBy2GUGDF0,5565
3
+ koi_net/context.py,sha256=FwkzjmsyNqUeOeGCuZXtONqs5_MSl1R8-e3IxHuyzTI,1531
4
+ koi_net/core.py,sha256=UFERsvnphUCWSQYunMgXlLpJ_XU5yD_1tcqlGO0BV9A,7100
5
5
  koi_net/default_actions.py,sha256=TkQR9oj9CpO37Gb5bZLmFNl-Q8n3OxGiX4dvxQR7SaA,421
6
6
  koi_net/effector.py,sha256=gSyZgRxQ91X04UL261e2pXWUfBHnQTGtjSHpc2JufxA,4097
7
7
  koi_net/identity.py,sha256=FvIWksGTqwM7HCevIwmo_6l-t-2tnYkaaR4CanZatL4,569
8
8
  koi_net/lifecycle.py,sha256=-J5y4i0JdernatLTed8hQbkK26Cs6rm1kZqlx3aQzZA,2727
9
- koi_net/poller.py,sha256=tlqbDMTdkU8vCbi95rOBeHUEWLkrECKW3gnF9MYhKQw,1369
9
+ koi_net/poller.py,sha256=ZvQcZ0RQhViXVhbIpdTZb7_ql3nG0mTcdBamneZKLpA,1361
10
10
  koi_net/secure.py,sha256=cGNF2assqCaYq0i0fhQBm7aREoAdpY-XVypDsE1ALaU,3970
11
- koi_net/server.py,sha256=ONJMhC_9VNBTVTdHRMP66T9QIZ1UuSZY7C_KoRcVb9k,4199
11
+ koi_net/server.py,sha256=5BVVrgn69r_HMC3c4npWywPsjBDqUnHFWLKdtnfRTbA,4250
12
12
  koi_net/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  koi_net/network/behavior.py,sha256=NZLvWlrxR0uWriE3ZzCXmocUVccQthy7Xx8E_8KBwsg,1208
14
14
  koi_net/network/error_handler.py,sha256=CrmCpBY2oj4nl7uXrIYusUHDKxPZ1HDuQAtiBSZarRI,1623
15
- koi_net/network/event_queue.py,sha256=QV1dLOe-H85fNJVkc-e0ZRsUpHTkFcMLMbp3WU5gRNg,7225
15
+ koi_net/network/event_queue.py,sha256=DWs26C235iYkP4koKcdbhmIOHGZRJ48d072BoNWyiHo,7325
16
16
  koi_net/network/graph.py,sha256=NLstBsPa9By0luxcTjThnqVd3hxfQdFwn8tWgJ6u4l4,4144
17
- koi_net/network/request_handler.py,sha256=77SHLO92gVHTusUXWo89KgjRnxU9vLG_Qi8HxTFtFBg,6376
17
+ koi_net/network/request_handler.py,sha256=vBXw2GO5vGAYCs18flWbln4kO_HmY1G9uuFu9MYqedY,6852
18
18
  koi_net/network/resolver.py,sha256=coIp4M6k0-8sUfAy4h2NMx_7zCNroWlCHKOj3AXZVhc,5412
19
- koi_net/network/response_handler.py,sha256=tEfzSZWXKCRwUlXhM8Qp3Y6BKTQ4abfi-MrSaatlITI,1980
19
+ koi_net/network/response_handler.py,sha256=__R_EvEpjaMz3PCDvkNgWF_EAHe2nePGk-zK_cT4C4g,2077
20
20
  koi_net/processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- koi_net/processor/default_handlers.py,sha256=SX1eP740W9LeG-IEoBOmsNrKJfOEDDuEQUSwnJEG_8w,8954
21
+ koi_net/processor/default_handlers.py,sha256=1OTC4p0luTadNm90q6Fr_dbvysFzgRCbltp-YP6cRXo,9562
22
22
  koi_net/processor/handler.py,sha256=_loaHjgVGVUxtCQdvAY9dQ0iqiq5co7wB2tK-usuv3Y,2355
23
23
  koi_net/processor/interface.py,sha256=ebDwqggznFRfp2PT8-UJPUAvCwX8nZaaQ68FUeWQvmw,3682
24
24
  koi_net/processor/knowledge_object.py,sha256=avQnsaeqqiJxy40P1VGljuQMtAGmJB-TBa4pmBXTaIs,3863
@@ -32,7 +32,7 @@ koi_net/protocol/errors.py,sha256=A83QiYe_fJdxW2lsNsLCujWxDr5sk1UmYYd3TGbSNJA,60
32
32
  koi_net/protocol/event.py,sha256=eGgihEj1gliLoQRk8pVB2q_was0AGo-PbT3Hqnpn3oU,1379
33
33
  koi_net/protocol/node.py,sha256=7GQzHORFr9cP4BqJgir6EGSWCskL-yqmvJksIiLfcWU,409
34
34
  koi_net/protocol/secure.py,sha256=Reem9Z4le4uWXM9uczNOdmgVBg8p4YQav-7_c3pZ1CQ,3366
35
- koi_net-1.1.0b2.dist-info/METADATA,sha256=k341N2GlyWwem7ojdmX1PH8tij0D4CQHfOjGnuv5XYQ,37118
36
- koi_net-1.1.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- koi_net-1.1.0b2.dist-info/licenses/LICENSE,sha256=03mgCL5qth2aD9C3F3qNVs4sFJSpK9kjtYCyOwdSp7s,1069
38
- koi_net-1.1.0b2.dist-info/RECORD,,
35
+ koi_net-1.1.0b4.dist-info/METADATA,sha256=olgGAhiNBqMAwhtLYCaK0QNNS3s0LuNzHBwazVELAqk,37118
36
+ koi_net-1.1.0b4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ koi_net-1.1.0b4.dist-info/licenses/LICENSE,sha256=03mgCL5qth2aD9C3F3qNVs4sFJSpK9kjtYCyOwdSp7s,1069
38
+ koi_net-1.1.0b4.dist-info/RECORD,,