koi-net 1.0.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.

@@ -0,0 +1,151 @@
1
+ import logging
2
+ from rid_lib.ext.bundle import Bundle
3
+ from rid_lib.types import KoiNetNode, KoiNetEdge
4
+ from koi_net.protocol.node import NodeType
5
+ from .interface import ProcessorInterface
6
+ from .handler import HandlerType, STOP_CHAIN
7
+ from .knowledge_object import KnowledgeObject, KnowledgeSource
8
+ from ..protocol.event import Event, EventType
9
+ from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # RID handlers
14
+
15
+ @ProcessorInterface.as_handler(handler_type=HandlerType.RID)
16
+ def basic_rid_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
17
+ if (kobj.rid == processor.identity.rid and
18
+ kobj.source == KnowledgeSource.External):
19
+ logger.info("Don't let anyone else tell me who I am!")
20
+ return STOP_CHAIN
21
+
22
+ if kobj.event_type == EventType.FORGET:
23
+ if processor.cache.exists(kobj.rid):
24
+ logger.info("Allowing cache forget")
25
+ kobj.normalized_event_type = EventType.FORGET
26
+ return kobj
27
+
28
+ else:
29
+ # can't forget something I don't know about
30
+ return STOP_CHAIN
31
+
32
+
33
+ # Manifest handlers
34
+
35
+ @ProcessorInterface.as_handler(handler_type=HandlerType.Manifest)
36
+ def basic_state_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
37
+ prev_bundle = processor.cache.read(kobj.rid)
38
+
39
+ if prev_bundle:
40
+ if kobj.manifest.sha256_hash == prev_bundle.manifest.sha256_hash:
41
+ logger.info("Hash of incoming manifest is same as existing knowledge, ignoring")
42
+ return STOP_CHAIN
43
+ if kobj.manifest.timestamp <= prev_bundle.manifest.timestamp:
44
+ logger.info("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
45
+ return STOP_CHAIN
46
+
47
+ logger.info("RID previously known to me, labeling as 'UPDATE'")
48
+ kobj.normalized_event_type = EventType.UPDATE
49
+
50
+ else:
51
+ logger.info("RID previously unknown to me, labeling as 'NEW'")
52
+ kobj.normalized_event_type = EventType.NEW
53
+
54
+ return kobj
55
+
56
+
57
+ # Bundle handlers
58
+
59
+ @ProcessorInterface.as_handler(HandlerType.Bundle, rid_types=[KoiNetEdge])
60
+ def edge_negotiation_handler(processor: ProcessorInterface, kobj: KnowledgeObject):
61
+ edge_profile = EdgeProfile.model_validate(kobj.contents)
62
+
63
+ # only want to handle external knowledge events (not edges this node created)
64
+ if kobj.source != KnowledgeSource.External:
65
+ return
66
+
67
+ # indicates peer subscribing to me
68
+ if edge_profile.source == processor.identity.rid:
69
+ if edge_profile.status != EdgeStatus.PROPOSED:
70
+ return
71
+
72
+ logger.info("Handling edge negotiation")
73
+
74
+ peer_rid = edge_profile.target
75
+ peer_profile = processor.network.graph.get_node_profile(peer_rid)
76
+
77
+ if not peer_profile:
78
+ logger.warning(f"Peer {peer_rid} unknown to me")
79
+ return STOP_CHAIN
80
+
81
+ # explicitly provided event RID types and (self) node + edge objects
82
+ provided_events = (
83
+ *processor.identity.profile.provides.event,
84
+ KoiNetNode, KoiNetEdge
85
+ )
86
+
87
+
88
+ abort = False
89
+ if (edge_profile.edge_type == EdgeType.WEBHOOK and
90
+ peer_profile.node_type == NodeType.PARTIAL):
91
+ logger.info("Partial nodes cannot use webhooks")
92
+ abort = True
93
+
94
+ if not set(edge_profile.rid_types).issubset(provided_events):
95
+ logger.info("Requested RID types not provided by this node")
96
+ abort = True
97
+
98
+ if abort:
99
+ event = Event.from_rid(EventType.FORGET, kobj.rid)
100
+ processor.network.push_event_to(event, peer_rid, flush=True)
101
+ return STOP_CHAIN
102
+
103
+ else:
104
+ # approve edge profile
105
+ logger.info("Approving proposed edge")
106
+ edge_profile.status = EdgeStatus.APPROVED
107
+ updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
108
+
109
+ processor.handle(bundle=updated_bundle)
110
+ return
111
+
112
+ elif edge_profile.target == processor.identity.rid:
113
+ if edge_profile.status == EdgeStatus.APPROVED:
114
+ logger.info("Edge approved by other node!")
115
+
116
+
117
+ # Network handlers
118
+
119
+ @ProcessorInterface.as_handler(HandlerType.Network)
120
+ def basic_network_output_filter(processor: ProcessorInterface, kobj: KnowledgeObject):
121
+ involves_me = False
122
+ if kobj.source == KnowledgeSource.Internal:
123
+ if (type(kobj.rid) == KoiNetNode):
124
+ if (kobj.rid == processor.identity.rid):
125
+ involves_me = True
126
+
127
+ elif type(kobj.rid) == KoiNetEdge:
128
+ edge_profile = kobj.bundle.validate_contents(EdgeProfile)
129
+
130
+ if edge_profile.source == processor.identity.rid:
131
+ logger.info(f"Adding edge target '{edge_profile.target!r}' to network targets")
132
+ kobj.network_targets.update([edge_profile.target])
133
+ involves_me = True
134
+
135
+ elif edge_profile.target == processor.identity.rid:
136
+ logger.info(f"Adding edge source '{edge_profile.source!r}' to network targets")
137
+ kobj.network_targets.update([edge_profile.source])
138
+ involves_me = True
139
+
140
+ if (type(kobj.rid) in processor.identity.profile.provides.event or involves_me):
141
+ # broadcasts to subscribers if I'm an event provider of this RID type OR it involves me
142
+ subscribers = processor.network.graph.get_neighbors(
143
+ direction="out",
144
+ allowed_type=type(kobj.rid)
145
+ )
146
+
147
+ logger.info(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
148
+ kobj.network_targets.update(subscribers)
149
+
150
+ return kobj
151
+
@@ -0,0 +1,22 @@
1
+ from dataclasses import dataclass
2
+ from enum import StrEnum
3
+ from typing import Callable
4
+ from rid_lib import RIDType
5
+
6
+
7
+ # sentinel
8
+ STOP_CHAIN = object()
9
+
10
+ class HandlerType(StrEnum):
11
+ RID = "rid", # guaranteed RID - decides whether validate manifest OR cache delete
12
+ Manifest = "manifest", # guaranteed Manifest - decides whether to validate bundle
13
+ Bundle = "bundle", # guaranteed Bundle - decides whether to write to cache
14
+ Network = "network", # guaranteed Bundle, after cache write/delete - decides network targets
15
+ Final = "final" # guaranteed Bundle, after network push - final action
16
+
17
+ @dataclass
18
+ class KnowledgeHandler:
19
+ func: Callable
20
+ handler_type: HandlerType
21
+ rid_types: list[RIDType] | None
22
+
@@ -0,0 +1,222 @@
1
+ import logging
2
+ from queue import Queue
3
+ from typing import Callable
4
+ from rid_lib.core import RID, RIDType
5
+ from rid_lib.ext import Bundle, Cache, Manifest
6
+ from rid_lib.types.koi_net_edge import KoiNetEdge
7
+ from rid_lib.types.koi_net_node import KoiNetNode
8
+ from ..identity import NodeIdentity
9
+ from ..network import NetworkInterface
10
+ from ..protocol.event import Event, EventType
11
+ from .handler import (
12
+ KnowledgeHandler,
13
+ HandlerType,
14
+ STOP_CHAIN
15
+ )
16
+ from .knowledge_object import (
17
+ KnowledgeObject,
18
+ KnowledgeSource,
19
+ KnowledgeEventType
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class ProcessorInterface:
26
+ cache: Cache
27
+ network: NetworkInterface
28
+ identity: NodeIdentity
29
+ handlers: list[KnowledgeHandler]
30
+ kobj_queue: Queue[KnowledgeObject]
31
+
32
+ def __init__(
33
+ self,
34
+ cache: Cache,
35
+ network: NetworkInterface,
36
+ identity: NodeIdentity,
37
+ default_handlers: list[KnowledgeHandler] = []
38
+ ):
39
+ self.cache = cache
40
+ self.network = network
41
+ self.identity = identity
42
+ self.handlers: list[KnowledgeHandler] = default_handlers
43
+ self.kobj_queue = Queue()
44
+
45
+ @classmethod
46
+ def as_handler(
47
+ cls,
48
+ handler_type: HandlerType,
49
+ rid_types: list[RIDType] | None = None
50
+ ):
51
+ """Special decorator that returns a Handler instead of a function."""
52
+ def decorator(func: Callable) -> KnowledgeHandler:
53
+ handler = KnowledgeHandler(func, handler_type, rid_types, )
54
+ return handler
55
+ return decorator
56
+
57
+ def register_handler(
58
+ self,
59
+ handler_type: HandlerType,
60
+ rid_types: list[RIDType] | None = None
61
+ ):
62
+ """Assigns decorated function as handler for this Processor."""
63
+ def decorator(func: Callable) -> Callable:
64
+ handler = KnowledgeHandler(func, handler_type, rid_types)
65
+ self.handlers.append(handler)
66
+ return func
67
+ return decorator
68
+
69
+ def call_handler_chain(
70
+ self,
71
+ handler_type: HandlerType,
72
+ kobj: KnowledgeObject
73
+ ):
74
+ for handler in self.handlers:
75
+ if handler_type != handler.handler_type:
76
+ continue
77
+
78
+ if handler.rid_types and type(kobj.rid) not in handler.rid_types:
79
+ continue
80
+
81
+ logger.info(f"Calling {handler_type} handler '{handler.func.__name__}'")
82
+ resp = handler.func(self, kobj.model_copy())
83
+
84
+ # stops handler chain execution
85
+ if resp is STOP_CHAIN:
86
+ logger.info(f"Handler chain stopped by {handler.func.__name__}")
87
+ return STOP_CHAIN
88
+ # kobj unmodified
89
+ elif resp is None:
90
+ continue
91
+ # kobj modified by handler
92
+ elif isinstance(resp, KnowledgeObject):
93
+ kobj = resp
94
+ logger.info(f"Knowledge object modified by {handler.func.__name__}")
95
+ else:
96
+ raise ValueError(f"Handler {handler.func.__name__} returned invalid response '{resp}'")
97
+
98
+ return kobj
99
+
100
+
101
+ def handle_kobj(self, kobj: KnowledgeObject):
102
+ logger.info(f"Handling {kobj!r}")
103
+ kobj = self.call_handler_chain(HandlerType.RID, kobj)
104
+ if kobj is STOP_CHAIN: return
105
+
106
+ if kobj.event_type == EventType.FORGET:
107
+ bundle = self.cache.read(kobj.rid)
108
+ if not bundle:
109
+ logger.info("Local bundle not found")
110
+ return
111
+
112
+ # the bundle (to be deleted) attached to kobj for downstream analysis
113
+ logger.info("Adding local bundle (to be deleted) to knowledge object")
114
+ kobj.manifest = bundle.manifest
115
+ kobj.contents = bundle.contents
116
+
117
+ else:
118
+ # attempt to retrieve manifest
119
+ if not kobj.manifest:
120
+ if kobj.source == KnowledgeSource.External:
121
+ logger.info("Manifest not found, attempting to fetch remotely")
122
+ manifest = self.network.fetch_remote_manifest(kobj.rid)
123
+ if not manifest: return
124
+
125
+ elif kobj.source == KnowledgeSource.Internal:
126
+ logger.info("Manifest not found, attempting to read cache")
127
+ bundle = self.cache.read(kobj.rid)
128
+ if not bundle: return
129
+ manifest = bundle.manifest
130
+
131
+ kobj.manifest = manifest
132
+
133
+ kobj = self.call_handler_chain(HandlerType.Manifest, kobj)
134
+ if kobj is STOP_CHAIN: return
135
+
136
+ # attempt to retrieve bundle
137
+ if not kobj.bundle:
138
+ if kobj.source == KnowledgeSource.External:
139
+ logger.info("Bundle not found, attempting to fetch")
140
+ bundle = self.network.fetch_remote_bundle(kobj.rid)
141
+ # TODO: WARNING MANIFEST MAY BE DIFFERENT
142
+
143
+ elif kobj.source == KnowledgeSource.Internal:
144
+ logger.info("Bundle not found, attempting to read cache")
145
+ bundle = self.cache.read(kobj.rid)
146
+
147
+ if kobj.manifest != bundle.manifest:
148
+ logger.warning("Retrieved bundle contains a different manifest")
149
+
150
+ if not bundle: return
151
+ kobj.manifest = bundle.manifest
152
+ kobj.contents = bundle.contents
153
+
154
+ kobj = self.call_handler_chain(HandlerType.Bundle, kobj)
155
+ if kobj is STOP_CHAIN: return
156
+
157
+ if kobj.normalized_event_type in (EventType.UPDATE, EventType.NEW):
158
+ logger.info(f"Writing {kobj!r} to cache")
159
+ self.cache.write(kobj.bundle)
160
+
161
+ elif kobj.normalized_event_type == EventType.FORGET:
162
+ logger.info(f"Deleting {kobj!r} from cache")
163
+ self.cache.delete(kobj.rid)
164
+
165
+ else:
166
+ logger.info("Normalized event type was never set, no cache or network operations will occur")
167
+ return
168
+
169
+ if type(kobj.rid) in (KoiNetNode, KoiNetEdge):
170
+ logger.info("Change to node or edge, regenerating network graph")
171
+ self.network.graph.generate()
172
+
173
+ kobj = self.call_handler_chain(HandlerType.Network, kobj)
174
+ if kobj is STOP_CHAIN: return
175
+
176
+ if kobj.network_targets:
177
+ logger.info(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
178
+ else:
179
+ logger.info("No network targets set")
180
+
181
+ for node in kobj.network_targets:
182
+ self.network.push_event_to(kobj.normalized_event, node)
183
+ self.network.flush_all_webhook_queues()
184
+
185
+ kobj = self.call_handler_chain(HandlerType.Final, kobj)
186
+
187
+ def queue_kobj(self, kobj: KnowledgeObject, flush: bool = False):
188
+ self.kobj_queue.put(kobj)
189
+ logger.info(f"Queued {kobj!r}")
190
+
191
+ if flush:
192
+ self.flush_kobj_queue()
193
+
194
+ def flush_kobj_queue(self):
195
+ while not self.kobj_queue.empty():
196
+ kobj = self.kobj_queue.get()
197
+ logger.info(f"Dequeued {kobj!r}")
198
+ self.handle_kobj(kobj)
199
+ logger.info("Done handling")
200
+
201
+ def handle(
202
+ self,
203
+ rid: RID | None = None,
204
+ manifest: Manifest | None = None,
205
+ bundle: Bundle | None = None,
206
+ event: Event | None = None,
207
+ event_type: KnowledgeEventType = None,
208
+ source: KnowledgeSource = KnowledgeSource.Internal,
209
+ flush: bool = False
210
+ ):
211
+ if rid:
212
+ kobj = KnowledgeObject.from_rid(rid, event_type, source)
213
+ elif manifest:
214
+ kobj = KnowledgeObject.from_manifest(manifest, event_type, source)
215
+ elif bundle:
216
+ kobj = KnowledgeObject.from_bundle(bundle, event_type, source)
217
+ elif event:
218
+ kobj = KnowledgeObject.from_event(event, source)
219
+ else:
220
+ raise ValueError("One of 'rid', 'manifest', 'bundle', or 'event' must be provided")
221
+
222
+ self.queue_kobj(kobj, flush)
@@ -0,0 +1,104 @@
1
+ from enum import StrEnum
2
+ from pydantic import BaseModel
3
+ from rid_lib import RID
4
+ from rid_lib.ext import Manifest
5
+ from rid_lib.ext.bundle import Bundle
6
+ from rid_lib.types.koi_net_node import KoiNetNode
7
+ from ..protocol.event import Event, EventType
8
+
9
+
10
+ type KnowledgeEventType = EventType | None
11
+
12
+ class KnowledgeSource(StrEnum):
13
+ Internal = "INTERNAL"
14
+ External = "EXTERNAL"
15
+
16
+ class KnowledgeObject(BaseModel):
17
+ rid: RID
18
+ manifest: Manifest | None = None
19
+ contents: dict | None = None
20
+ event_type: KnowledgeEventType = None
21
+ normalized_event_type: KnowledgeEventType = None
22
+ source: KnowledgeSource
23
+ network_targets: set[KoiNetNode] = set()
24
+
25
+ def __repr__(self):
26
+ return f"<Knowledge Object '{self.rid}' ({self.event_type}) -> ({self.normalized_event_type})>"
27
+
28
+ @classmethod
29
+ def from_rid(
30
+ cls,
31
+ rid: RID,
32
+ event_type: KnowledgeEventType = None,
33
+ source: KnowledgeSource = KnowledgeSource.Internal
34
+ ) -> "KnowledgeObject":
35
+ return cls(
36
+ rid=rid,
37
+ event_type=event_type,
38
+ source=source
39
+ )
40
+
41
+ @classmethod
42
+ def from_manifest(
43
+ cls,
44
+ manifest: Manifest,
45
+ event_type: KnowledgeEventType = None,
46
+ source: KnowledgeSource = KnowledgeSource.Internal
47
+ ) -> "KnowledgeObject":
48
+ return cls(
49
+ rid=manifest.rid,
50
+ manifest=manifest,
51
+ event_type=event_type,
52
+ source=source
53
+ )
54
+
55
+ @classmethod
56
+ def from_bundle(
57
+ cls,
58
+ bundle: Bundle,
59
+ event_type: KnowledgeEventType = None,
60
+ source: KnowledgeSource = KnowledgeSource.Internal
61
+ ) -> "KnowledgeObject":
62
+ return cls(
63
+ rid=bundle.rid,
64
+ manifest=bundle.manifest,
65
+ contents=bundle.contents,
66
+ event_type=event_type,
67
+ source=source
68
+ )
69
+
70
+ @classmethod
71
+ def from_event(
72
+ cls,
73
+ event: Event,
74
+ source: KnowledgeSource = KnowledgeSource.Internal
75
+ ) -> "KnowledgeObject":
76
+ return cls(
77
+ rid=event.rid,
78
+ manifest=event.manifest,
79
+ contents=event.contents,
80
+ event_type=event.event_type,
81
+ source=source
82
+ )
83
+
84
+ @property
85
+ def bundle(self):
86
+ if self.manifest is None or self.contents is None:
87
+ return
88
+
89
+ return Bundle(
90
+ manifest=self.manifest,
91
+ contents=self.contents
92
+ )
93
+
94
+ @property
95
+ def normalized_event(self):
96
+ if not self.normalized_event_type:
97
+ raise ValueError("Internal event's normalized event type is None, cannot convert to Event")
98
+
99
+ return Event(
100
+ rid=self.rid,
101
+ event_type=self.normalized_event_type,
102
+ manifest=self.manifest,
103
+ contents=self.contents
104
+ )
File without changes
@@ -0,0 +1,39 @@
1
+ from pydantic import BaseModel
2
+ from rid_lib import RID, RIDType
3
+ from rid_lib.ext import Bundle, Manifest
4
+ from .event import Event
5
+
6
+
7
+ # REQUEST MODELS
8
+
9
+ class PollEvents(BaseModel):
10
+ rid: RID
11
+ limit: int = 0
12
+
13
+ class FetchRids(BaseModel):
14
+ rid_types: list[RIDType] = []
15
+
16
+ class FetchManifests(BaseModel):
17
+ rid_types: list[RIDType] = []
18
+ rids: list[RID] = []
19
+
20
+ class FetchBundles(BaseModel):
21
+ rids: list[RID]
22
+
23
+
24
+ # RESPONSE/PAYLOAD MODELS
25
+
26
+ class RidsPayload(BaseModel):
27
+ rids: list[RID]
28
+
29
+ class ManifestsPayload(BaseModel):
30
+ manifests: list[Manifest]
31
+ not_found: list[RID] = []
32
+
33
+ class BundlesPayload(BaseModel):
34
+ manifests: list[Bundle]
35
+ not_found: list[RID] = []
36
+ deferred: list[RID] = []
37
+
38
+ class EventsPayload(BaseModel):
39
+ events: list[Event]
@@ -0,0 +1,5 @@
1
+ BROADCAST_EVENTS_PATH = "/events/broadcast"
2
+ POLL_EVENTS_PATH = "/events/poll"
3
+ FETCH_RIDS_PATH = "/rids/fetch"
4
+ FETCH_MANIFESTS_PATH = "/manifests/fetch"
5
+ FETCH_BUNDLES_PATH = "/bundles/fetch"
@@ -0,0 +1,20 @@
1
+ from enum import StrEnum
2
+ from pydantic import BaseModel
3
+ from rid_lib import RIDType
4
+ from rid_lib.types import KoiNetNode
5
+
6
+
7
+ class EdgeStatus(StrEnum):
8
+ PROPOSED = "PROPOSED"
9
+ APPROVED = "APPROVED"
10
+
11
+ class EdgeType(StrEnum):
12
+ WEBHOOK = "WEBHOOK"
13
+ POLL = "POLL"
14
+
15
+ class EdgeProfile(BaseModel):
16
+ source: KoiNetNode
17
+ target: KoiNetNode
18
+ edge_type: EdgeType
19
+ status: EdgeStatus
20
+ rid_types: list[RIDType]
@@ -0,0 +1,48 @@
1
+ from enum import StrEnum
2
+ from pydantic import BaseModel
3
+ from rid_lib import RID
4
+ from rid_lib.ext import Manifest, Bundle
5
+
6
+
7
+ class EventType(StrEnum):
8
+ NEW = "NEW"
9
+ UPDATE = "UPDATE"
10
+ FORGET = "FORGET"
11
+
12
+ class Event(BaseModel):
13
+ rid: RID
14
+ event_type: EventType
15
+ manifest: Manifest | None = None
16
+ contents: dict | None = None
17
+
18
+ @classmethod
19
+ def from_bundle(cls, event_type: EventType, bundle: Bundle):
20
+ return cls(
21
+ rid=bundle.manifest.rid,
22
+ event_type=event_type,
23
+ manifest=bundle.manifest,
24
+ contents=bundle.contents
25
+ )
26
+
27
+ @classmethod
28
+ def from_manifest(cls, event_type: EventType, manifest: Manifest):
29
+ return cls(
30
+ rid=manifest.rid,
31
+ event_type=event_type,
32
+ manifest=manifest
33
+ )
34
+
35
+ @classmethod
36
+ def from_rid(cls, event_type: EventType, rid: RID):
37
+ return cls(
38
+ rid=rid,
39
+ event_type=event_type
40
+ )
41
+
42
+ @property
43
+ def bundle(self):
44
+ if self.manifest is not None and self.contents is not None:
45
+ return Bundle(
46
+ manifest=self.manifest,
47
+ contents=self.contents
48
+ )
@@ -0,0 +1,25 @@
1
+ from rid_lib.core import RIDType
2
+ from rid_lib.ext.bundle import Bundle
3
+ from rid_lib.types import KoiNetEdge
4
+ from rid_lib.types.koi_net_node import KoiNetNode
5
+ from .edge import EdgeProfile, EdgeStatus, EdgeType
6
+
7
+ def generate_edge_bundle(
8
+ source: KoiNetNode,
9
+ target: KoiNetNode,
10
+ rid_types: list[RIDType],
11
+ edge_type: EdgeType
12
+ ) -> Bundle:
13
+ edge_rid = KoiNetEdge.generate(source, target)
14
+ edge_profile = EdgeProfile(
15
+ source=source,
16
+ target=target,
17
+ rid_types=rid_types,
18
+ edge_type=edge_type,
19
+ status=EdgeStatus.PROPOSED
20
+ )
21
+ edge_bundle = Bundle.generate(
22
+ edge_rid,
23
+ edge_profile.model_dump()
24
+ )
25
+ return edge_bundle
@@ -0,0 +1,17 @@
1
+ from enum import StrEnum
2
+ from pydantic import BaseModel
3
+ from rid_lib import RIDType
4
+
5
+
6
+ class NodeType(StrEnum):
7
+ FULL = "FULL"
8
+ PARTIAL = "PARTIAL"
9
+
10
+ class NodeProvides(BaseModel):
11
+ event: list[RIDType] = []
12
+ state: list[RIDType] = []
13
+
14
+ class NodeProfile(BaseModel):
15
+ base_url: str | None = None
16
+ node_type: NodeType
17
+ provides: NodeProvides = NodeProvides()