@@ -194,10 +194,10 @@ def discover_systems(self):
194194 print (system_objs )
195195 for system_json in system_objs :
196196 print (system_json )
197- system = SystemResource .model_validate (system_json )
197+ system = SystemResource .model_validate (system_json , by_alias = True )
198198 sys_obj = System (label = system .properties ['name' ],
199199 name = to_lower_camel (system .properties ['name' ].replace (" " , "_" )),
200- urn = system .properties ['uid' ], parent_node = self )
200+ urn = system .properties ['uid' ], parent_node = self , resource_id = system . system_id )
201201
202202 self ._systems .append (sys_obj )
203203 new_systems .append (sys_obj )
@@ -247,6 +247,64 @@ def register_streamable(self, streamable: StreamableResource):
247247 def get_session (self ) -> OSHClientSession :
248248 return self ._client_session
249249
250+ def serialize (self ) -> dict :
251+ data = {
252+ "_id" : self ._id ,
253+ "protocol" : self .protocol ,
254+ "address" : self .address ,
255+ "port" : self .port ,
256+ "server_root" : self .server_root ,
257+ "is_secure" : self .is_secure ,
258+ "username" : getattr (self ._api_helper , "username" , None ),
259+ "password" : getattr (self ._api_helper , "password" , None ),
260+ "_systems" : [system .serialize () for system in self ._systems ] if self ._systems is not None else None ,
261+ }
262+ data ["name" ] = getattr (self , "name" , None )
263+ data ["label" ] = getattr (self , "label" , None )
264+ data ["urn" ] = getattr (self , "urn" , None )
265+ data ["description" ] = getattr (self , "description" , None )
266+ datastreams = getattr (self , "datastreams" , None )
267+ if datastreams is not None :
268+ data ["datastreams" ] = [ds .serialize () for ds in datastreams ]
269+ else :
270+ data ["datastreams" ] = None
271+ control_channels = getattr (self , "control_channels" , None )
272+ if control_channels is not None :
273+ data ["control_channels" ] = [cc .serialize () for cc in control_channels ]
274+ else :
275+ data ["control_channels" ] = None
276+ underlying = getattr (self , "_underlying_resource" , None )
277+ if underlying is not None :
278+ dump = getattr (underlying , 'model_dump' , None )
279+ if callable (dump ):
280+ data ["underlying_resource" ] = underlying .model_dump (by_alias = True , exclude_none = True )
281+ elif hasattr (underlying , 'to_dict' ):
282+ data ["underlying_resource" ] = underlying .to_dict ()
283+ else :
284+ data ["underlying_resource" ] = str (underlying )
285+ else :
286+ data ["underlying_resource" ] = None
287+ # Remove any 'resource' key if present
288+ data .pop ("resource" , None )
289+ return data
290+
291+ @classmethod
292+ def deserialize (cls , data : dict , session_manager : 'SessionManager' = None ) -> 'Node' :
293+ node = cls (
294+ protocol = data ["protocol" ],
295+ address = data ["address" ],
296+ port = data ["port" ],
297+ username = data .get ("username" ),
298+ password = data .get ("password" ),
299+ server_root = data .get ("server_root" , "sensorhub" ),
300+ session_manager = session_manager
301+ )
302+ node ._id = data ["_id" ]
303+ node .is_secure = data .get ("is_secure" , False )
304+ node ._systems = [System .deserialize (sys , node ) for sys in data .get ("_systems" , [])] if data .get (
305+ "_systems" ) is not None else []
306+ return node
307+
250308
251309class Status (Enum ):
252310 INITIALIZING = "initializing"
@@ -269,7 +327,7 @@ class StreamableModes(Enum):
269327class StreamableResource (Generic [T ], ABC ):
270328 _id : UUID
271329 _resource_id : str
272- _canonical_link : str
330+ # _canonical_link: str
273331 _topic : str
274332 _status : str = Status .STOPPED .value
275333 ws_url : str
@@ -536,6 +594,39 @@ def get_inbound_deque(self):
536594 def get_outbound_deque (self ):
537595 return self ._outbound_deque
538596
597+ def serialize (self ) -> dict :
598+ """Serializes common attributes of StreamableResource, safely handling missing/None attributes."""
599+ topic = getattr (self , "_topic" , None )
600+ status = getattr (self , "_status" , None )
601+ parent_resource_id = getattr (self , "_parent_resource_id" , None )
602+ connection_mode = getattr (self , "_connection_mode" , None )
603+ resource_id = getattr (self , "_resource_id" , None )
604+ if isinstance (connection_mode , Enum ):
605+ connection_mode = connection_mode .value
606+
607+ return {
608+ "id" : str (getattr (self , "_id" , None )),
609+ "resource_id" : resource_id ,
610+ # "canonical_link": getattr(self, "_canonical_link", None),
611+ "topic" : topic ,
612+ "status" : status ,
613+ "parent_resource_id" : parent_resource_id ,
614+ "connection_mode" : connection_mode ,
615+ }
616+
617+ @classmethod
618+ def deserialize (cls , data : dict , node : 'Node' ) -> 'StreamableResource' :
619+ """Deserializes common attributes. Subclasses should override and call super()."""
620+ obj = cls (node = node )
621+ obj ._id = uuid .UUID (data ["id" ])
622+ obj ._resource_id = data .get ("resource_id" )
623+ # obj._canonical_link = data.get("canonical_link")
624+ obj ._topic = data .get ("topic" )
625+ obj ._status = data .get ("status" )
626+ obj ._parent_resource_id = data .get ("parent_resource_id" )
627+ obj ._connection_mode = StreamableModes (data .get ("connection_mode" , StreamableModes .PUSH .value )),
628+ return obj
629+
539630
540631class System (StreamableResource [SystemResource ]):
541632 name : str
@@ -567,29 +658,37 @@ def __init__(self, name: str, label: str, urn: str, parent_node: Node, **kwargs)
567658
568659 self ._underlying_resource = self .to_system_resource ()
569660
570- def discover_datastreams (self ) -> list [DatastreamResource ]:
661+ def discover_datastreams (self ) -> list [Datastream ]:
571662 res = self ._parent_node .get_api_helper ().get_resource (APIResourceTypes .SYSTEM , self ._resource_id ,
572663 APIResourceTypes .DATASTREAM )
573664 datastream_json = res .json ()['items' ]
574- ds_resources = []
665+ datastreams = []
575666
576667 for ds in datastream_json :
577- datastream_objs = DatastreamResource .model_validate (ds )
578- ds_resources .append (datastream_objs )
668+ datastream_objs = DatastreamResource .model_validate (ds , by_alias = True )
669+ new_ds = Datastream (self ._parent_node , datastream_objs )
670+ datastreams .append (new_ds )
579671
580- return ds_resources
672+ if not [ds .get_underlying_resource () != datastream_objs for ds in self .datastreams ]:
673+ self .datastreams .append (new_ds )
581674
582- def discover_controlstreams (self ) -> list [ControlStreamResource ]:
675+ return datastreams
676+
677+ def discover_controlstreams (self ) -> list [ControlStream ]:
583678 res = self ._parent_node .get_api_helper ().get_resource (APIResourceTypes .SYSTEM , self ._resource_id ,
584679 APIResourceTypes .CONTROL_CHANNEL )
585680 controlstream_json = res .json ()['items' ]
586- cs_resources = []
681+ controlstreams = []
682+
683+ for cs_json in controlstream_json :
684+ controlstream_objs = ControlStreamResource .model_validate (cs_json )
685+ new_cs = ControlStream (self ._parent_node , controlstream_objs )
686+ controlstreams .append (new_cs )
587687
588- for cs in controlstream_json :
589- controlstream_objs = ControlStreamResource .model_validate (cs )
590- cs_resources .append (controlstream_objs )
688+ if not [cs .get_underlying_resource () != controlstream_objs for cs in self .control_channels ]:
689+ self .control_channels .append (new_cs )
591690
592- return cs_resources
691+ return controlstreams
593692
594693 @staticmethod
595694 def from_system_resource (system_resource : SystemResource , parent_node : Node ) -> System :
@@ -737,6 +836,53 @@ def retrieve_resource(self):
737836 self ._underlying_resource = system_resource
738837 return None
739838
839+ def serialize (self ) -> dict :
840+ data = super ().serialize ()
841+ data ["name" ] = getattr (self , "name" , None )
842+ data ["label" ] = getattr (self , "label" , None )
843+ data ["urn" ] = getattr (self , "urn" , None )
844+ data ["description" ] = getattr (self , "description" , None )
845+ datastreams = getattr (self , "datastreams" , None )
846+ if datastreams is not None :
847+ data ["datastreams" ] = [ds .serialize () for ds in datastreams ]
848+ else :
849+ data ["datastreams" ] = None
850+ control_channels = getattr (self , "control_channels" , None )
851+ if control_channels is not None :
852+ data ["control_channels" ] = [cc .serialize () for cc in control_channels ]
853+ else :
854+ data ["control_channels" ] = None
855+ underlying = getattr (self , "_underlying_resource" , None )
856+ if underlying is not None :
857+ dump = getattr (underlying , 'model_dump' , None )
858+ if callable (dump ):
859+ data ["underlying_resource" ] = underlying .model_dump (by_alias = True , exclude_none = True )
860+ elif hasattr (underlying , 'to_dict' ):
861+ data ["underlying_resource" ] = underlying .to_dict ()
862+ else :
863+ data ["underlying_resource" ] = str (underlying )
864+ else :
865+ data ["underlying_resource" ] = None
866+ # Remove any 'resource' key if present
867+ data .pop ("resource" , None )
868+ return data
869+
870+ @classmethod
871+ def deserialize (cls , data : dict , node : 'Node' ) -> 'System' :
872+ obj = cls (
873+ name = data ["name" ],
874+ label = data ["label" ],
875+ urn = data ["urn" ],
876+ parent_node = node ,
877+ description = data .get ("description" ),
878+ resource_id = data .get ("resource_id" )
879+ )
880+ obj ._id = uuid .UUID (data ["id" ])
881+ obj .datastreams = [Datastream .deserialize (ds , node ) for ds in data .get ("datastreams" , [])]
882+ obj .control_channels = [ControlStream .deserialize (cc , node ) for cc in data .get ("control_channels" , [])]
883+ obj ._underlying_resource = SystemResource .model_validate (data .get ("_underlying_resource" ))
884+ return obj
885+
740886
741887class Datastream (StreamableResource [DatastreamResource ]):
742888 should_poll : bool
@@ -813,6 +959,32 @@ def insert(self, data: dict):
813959 encoded = json .dumps (data ).encode ('utf-8' )
814960 self ._publish_mqtt (self ._topic , encoded )
815961
962+ def serialize (self ) -> dict :
963+ data = super ().serialize ()
964+ data ["should_poll" ] = getattr (self , "should_poll" , None )
965+ underlying = getattr (self , "_underlying_resource" , None )
966+ if underlying is not None :
967+ dump = getattr (underlying , 'model_dump' , None )
968+ if callable (dump ):
969+ data ["underlying_resource" ] = underlying .model_dump (by_alias = True , exclude_none = True )
970+ elif hasattr (underlying , 'to_dict' ):
971+ data ["underlying_resource" ] = underlying .to_dict ()
972+ else :
973+ data ["underlying_resource" ] = str (underlying )
974+ else :
975+ data ["underlying_resource" ] = None
976+
977+ return data
978+
979+ @classmethod
980+ def deserialize (cls , data : dict , node : 'Node' ) -> 'Datastream' :
981+ ds_resource = DatastreamResource .model_validate (data ["resource" ]) if data .get ("resource" ) else None
982+ obj = cls (parent_node = node , datastream_resource = ds_resource )
983+ obj ._id = uuid .UUID (data ["id" ])
984+ obj .should_poll = data .get ("should_poll" , False )
985+ obj ._underlying_resource = DatastreamResource .model_validate (data ["_underlying_resource" ])
986+ return obj
987+
816988
817989class ControlStream (StreamableResource [ControlStreamResource ]):
818990 _status_topic : str
@@ -905,3 +1077,29 @@ def subscribe(self, topic=None, callback=None, qos=0):
9051077 self ._mqtt_client .subscribe (t , qos = qos , msg_callback = self ._mqtt_sub_callback )
9061078 else :
9071079 self ._mqtt_client .subscribe (t , qos = qos , msg_callback = callback )
1080+
1081+ def serialize (self ) -> dict :
1082+ data = super ().serialize ()
1083+ data ["status_topic" ] = getattr (self , "_status_topic" , None )
1084+ underlying = getattr (self , "_underlying_resource" , None )
1085+ if underlying is not None :
1086+ dump = getattr (underlying , 'model_dump' , None )
1087+ if callable (dump ):
1088+ data ["underlying_resource" ] = underlying .model_dump (by_alias = True , exclude_none = True )
1089+ elif hasattr (underlying , 'to_dict' ):
1090+ data ["underlying_resource" ] = underlying .to_dict ()
1091+ else :
1092+ data ["underlying_resource" ] = str (underlying )
1093+ else :
1094+ data ["underlying_resource" ] = None
1095+
1096+ return data
1097+
1098+ @classmethod
1099+ def deserialize (cls , data : dict , node : 'Node' ) -> 'ControlStream' :
1100+ cs_resource = ControlStreamResource .model_validate (data ["resource" ]) if data .get ("resource" ) else None
1101+ obj = cls (node = node , controlstream_resource = cs_resource )
1102+ obj ._id = uuid .UUID (data ["id" ])
1103+ obj ._status_topic = data .get ("status_topic" )
1104+ obj ._underlying_resource = ControlStreamResource .model_validate (data ["underlying_resource" ])
1105+ return obj
0 commit comments