@@ -279,22 +279,43 @@ class DNode:
279279
280280 __slots__ = ("context" , "cdata" , "attributes" , "free_func" , "__dict__" )
281281
282- def __init__ (self , context : "libyang.Context" , cdata ):
282+ def __init__ (self , context : "libyang.Context" , cdata , refcnt_parent ):
283283 """
284284 :arg context:
285285 The libyang.Context python object.
286286 :arg cdata:
287287 The pointer to the C structure allocated by libyang.so.
288+ :arg refcnt_parent:
289+ New nodes may be created with internal references to the internal
290+ cdata tree. By holding a reference to the parent node that created
291+ us, we can force Python to keep proper reference counts so the
292+ destructor doesn't get called. The only time this may be set to
293+ None is if we can guarantee we are the initial creator of the
294+ cdata object.
288295 """
289296 self .context = context
290297 self .cdata = cdata # C type: "struct lyd_node *"
291298 self .attributes = None
292299 self .free_func = None # type: Callable[DNode]
300+ self .refcnt_parent = refcnt_parent
293301
294302 def __del__ (self ):
295- # Delete only the root node when it goes out of scope, this will delete
296- # all children.
297- if node .parent () is None :
303+ # Don't auto-destroy the node unless we know we're allowed. We can
304+ # determine this if refcnt_parent is set or not. If we have a parent
305+ # they own the registered cdata.
306+ # Functions that may create a DNode without a parent:
307+ # * Context.create_data_path() if parent is None
308+ # * Context.parse_op{_mem}() if parent is None
309+ # * Context.parse_data{_mem,_file}() if parent is None
310+ # * DNode.diff()
311+ # * DNode.duplicate() if parent is None
312+ # * dict_to_dnode() if parent is None
313+ # * DNode.merge_data_dict()
314+ # * DNode.iter_tree()
315+ # * DNode.leafref_nodes()
316+ # Functions that might unset the refcnt_parent:
317+ # * DNode.merge{_module}()
318+ if not self .refcnt_parent :
298319 self .free ()
299320
300321 def meta (self ):
@@ -458,17 +479,17 @@ def schema(self) -> SNode:
458479 def parent (self ) -> Optional ["DNode" ]:
459480 if not self .cdata .parent :
460481 return None
461- return self .new (self .context , self .cdata .parent )
482+ return self .new (self .context , self .cdata .parent , self )
462483
463484 def next (self ) -> Optional ["DNode" ]:
464485 if not self .cdata .next :
465486 return None
466- return self .new (self .context , self .cdata .next )
487+ return self .new (self .context , self .cdata .next , self )
467488
468489 def prev (self ) -> Optional ["DNode" ]:
469490 if not self .cdata .prev :
470491 return None
471- return self .new (self .context , self .cdata .prev )
492+ return self .new (self .context , self .cdata .prev , self )
472493
473494 def root (self ) -> "DNode" :
474495 node = self
@@ -480,7 +501,7 @@ def first_sibling(self) -> "DNode":
480501 n = lib .lyd_first_sibling (self .cdata )
481502 if n == self .cdata :
482503 return self
483- return self .new (self .context , n )
504+ return self .new (self .context , n , self )
484505
485506 def siblings (self , include_self : bool = True ) -> Iterator ["DNode" ]:
486507 n = lib .lyd_first_sibling (self .cdata )
@@ -489,14 +510,14 @@ def siblings(self, include_self: bool = True) -> Iterator["DNode"]:
489510 if include_self :
490511 yield self
491512 else :
492- yield self .new (self .context , n )
513+ yield self .new (self .context , n , self )
493514 n = n .next
494515
495516 def find_path (self , path : str , output : bool = False ):
496517 node = ffi .new ("struct lyd_node **" )
497518 ret = lib .lyd_find_path (self .cdata , str2c (path ), output , node )
498519 if ret == lib .LY_SUCCESS :
499- return DNode .new (self .context , node [0 ])
520+ return DNode .new (self .context , node [0 ], self )
500521 return None
501522
502523 def find_one (self , xpath : str ) -> Optional ["DNode" ]:
@@ -514,7 +535,7 @@ def find_all(self, xpath: str) -> Iterator["DNode"]:
514535 node_set = node_set [0 ]
515536 try :
516537 for i in range (node_set .count ):
517- n = DNode .new (self .context , node_set .dnodes [i ])
538+ n = DNode .new (self .context , node_set .dnodes [i ], self )
518539 yield n
519540 finally :
520541 lib .ly_set_free (node_set , ffi .NULL )
@@ -599,7 +620,7 @@ def diff(
599620 if node_p [0 ] == ffi .NULL :
600621 return None
601622
602- return self .new (self .context , node_p [0 ])
623+ return self .new (self .context , node_p [0 ], None ''' New allocation ''' )
603624
604625 def diff_apply (self , diff_node : "DNode" ) -> None :
605626 node_p = ffi .new ("struct lyd_node **" )
@@ -636,7 +657,7 @@ def duplicate(
636657 else :
637658 lib .lyd_dup_single (self .cdata , parent , flags , node )
638659
639- return DNode .new (self .context , node [0 ])
660+ return DNode .new (self .context , node [0 ], parent )
640661
641662 def merge_module (
642663 self ,
@@ -654,6 +675,10 @@ def merge_module(
654675 if ret != lib .LY_SUCCESS :
655676 raise self .context .error ("merge failed" )
656677
678+ if destruct :
679+ source .cdata = None
680+ source .refcnt_parent = None
681+
657682 def merge (
658683 self ,
659684 source : "DNode" ,
@@ -675,10 +700,14 @@ def merge(
675700
676701 self .cdata = node_p [0 ]
677702
703+ if destruct :
704+ source .cdata = None
705+ source .refcnt_parent = None
706+
678707 def iter_tree (self ) -> Iterator ["DNode" ]:
679708 n = next_n = self .cdata
680709 while n != ffi .NULL :
681- yield self .new (self .context , n )
710+ yield self .new (self .context , n , self )
682711
683712 next_n = lib .lyd_child (n )
684713 if next_n == ffi .NULL :
@@ -1031,7 +1060,7 @@ def leafref_nodes(self) -> Iterator["DNode"]:
10311060 if lib .lyd_leafref_get_links (term_node , out ) != lib .LY_SUCCESS :
10321061 return
10331062 for n in ly_array_iter (out [0 ].leafref_nodes ):
1034- yield DNode .new (self .context , n )
1063+ yield DNode .new (self .context , n , self )
10351064
10361065 def __repr__ (self ):
10371066 cls = self .__class__
@@ -1052,7 +1081,7 @@ def _decorator(nodeclass):
10521081 return _decorator
10531082
10541083 @classmethod
1055- def new (cls , context : "libyang.Context" , cdata ) -> "DNode" :
1084+ def new (cls , context : "libyang.Context" , cdata , refcnt_parent ) -> "DNode" :
10561085 cdata = ffi .cast ("struct lyd_node *" , cdata )
10571086 if not cdata .schema :
10581087 schemas = list (context .find_path (cls ._get_path (cdata )))
@@ -1063,7 +1092,7 @@ def new(cls, context: "libyang.Context", cdata) -> "DNode":
10631092 nodecls = cls .NODETYPE_CLASS .get (cdata .schema .nodetype , None )
10641093 if nodecls is None :
10651094 raise TypeError ("node type %s not implemented" % cdata .schema .nodetype )
1066- return nodecls (context , cdata )
1095+ return nodecls (context , cdata , allow_destroy )
10671096
10681097 @staticmethod
10691098 def _get_path (cdata ) -> str :
@@ -1096,7 +1125,7 @@ def children(self, no_keys=False) -> Iterator[DNode]:
10961125
10971126 while child :
10981127 if child .schema != ffi .NULL :
1099- yield DNode .new (self .context , child )
1128+ yield DNode .new (self .context , child , self )
11001129 child = child .next
11011130
11021131 def __iter__ (self ):
@@ -1243,7 +1272,7 @@ def dict_to_dnode(
12431272
12441273 created = []
12451274
1246- def _create_leaf (_parent , module , name , value , in_rpc_output = False ):
1275+ def _create_leaf (_parent_cdata , module , name , value , in_rpc_output = False ):
12471276 if value is not None :
12481277 if isinstance (value , bool ):
12491278 value = str (value ).lower ()
@@ -1253,7 +1282,7 @@ def _create_leaf(_parent, module, name, value, in_rpc_output=False):
12531282 n = ffi .new ("struct lyd_node **" )
12541283 flags = newval_flags (rpc_output = in_rpc_output , store_only = store_only )
12551284 ret = lib .lyd_new_term (
1256- _parent ,
1285+ _parent_cdata ,
12571286 module .cdata ,
12581287 str2c (name ),
12591288 str2c (value ),
@@ -1262,21 +1291,21 @@ def _create_leaf(_parent, module, name, value, in_rpc_output=False):
12621291 )
12631292
12641293 if ret != lib .LY_SUCCESS :
1265- if _parent :
1266- parent_path = repr (DNode .new (module .context , _parent ).path ())
1294+ if _parent_cdata :
1295+ parent_path = repr (DNode .new (module .context , _parent_cdata , parent ).path ())
12671296 else :
12681297 parent_path = "module %r" % module .name ()
12691298 raise module .context .error (
12701299 "failed to create leaf %r as a child of %s" , name , parent_path
12711300 )
12721301 created .append (n [0 ])
12731302
1274- def _create_container (_parent , module , name , in_rpc_output = False ):
1303+ def _create_container (_parent_cdata , module , name , in_rpc_output = False ):
12751304 n = ffi .new ("struct lyd_node **" )
1276- ret = lib .lyd_new_inner (_parent , module .cdata , str2c (name ), in_rpc_output , n )
1305+ ret = lib .lyd_new_inner (_parent_cdata , module .cdata , str2c (name ), in_rpc_output , n )
12771306 if ret != lib .LY_SUCCESS :
1278- if _parent :
1279- parent_path = repr (DNode .new (module .context , _parent ).path ())
1307+ if _parent_cdata :
1308+ parent_path = repr (DNode .new (module .context , _parent_cdata , parent ).path ())
12801309 else :
12811310 parent_path = "module %r" % module .name ()
12821311 raise module .context .error (
@@ -1287,20 +1316,20 @@ def _create_container(_parent, module, name, in_rpc_output=False):
12871316 created .append (n [0 ])
12881317 return n [0 ]
12891318
1290- def _create_list (_parent , module , name , key_values , in_rpc_output = False ):
1319+ def _create_list (_parent_cdata , module , name , key_values , in_rpc_output = False ):
12911320 n = ffi .new ("struct lyd_node **" )
12921321 flags = newval_flags (rpc_output = in_rpc_output , store_only = store_only )
12931322 ret = lib .lyd_new_list (
1294- _parent ,
1323+ _parent_cdata ,
12951324 module .cdata ,
12961325 str2c (name ),
12971326 flags ,
12981327 n ,
12991328 * [str2c (str (i )) for i in key_values ],
13001329 )
13011330 if ret != lib .LY_SUCCESS :
1302- if _parent :
1303- parent_path = repr (DNode .new (module .context , _parent ).path ())
1331+ if _parent_cdata :
1332+ parent_path = repr (DNode .new (module .context , _parent_cdata , parent ).path ())
13041333 else :
13051334 parent_path = "module %r" % module .name ()
13061335 raise module .context .error (
@@ -1356,7 +1385,7 @@ def _dic_keys(_dic, _schema):
13561385 return keys
13571386 return _dic .keys ()
13581387
1359- def _to_dnode (_dic , _schema , _parent = ffi .NULL , in_rpc_output = False ):
1388+ def _to_dnode (_dic , _schema , _parent_cdata = ffi .NULL , in_rpc_output = False ):
13601389 for key in _dic_keys (_dic , _schema ):
13611390 if ":" in key :
13621391 prefix , name = key .split (":" )
@@ -1379,7 +1408,7 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
13791408 value = _dic [key ]
13801409
13811410 if isinstance (s , SLeaf ):
1382- _create_leaf (_parent , module , name , value , in_rpc_output )
1411+ _create_leaf (_parent_cdata , module , name , value , in_rpc_output )
13831412
13841413 elif isinstance (s , SLeafList ):
13851414 if not isinstance (value , (list , tuple )):
@@ -1388,14 +1417,14 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
13881417 % (s .schema_path (), value )
13891418 )
13901419 for v in value :
1391- _create_leaf (_parent , module , name , v , in_rpc_output )
1420+ _create_leaf (_parent_cdata , module , name , v , in_rpc_output )
13921421
13931422 elif isinstance (s , SRpc ):
1394- n = _create_container (_parent , module , name , in_rpc_output )
1423+ n = _create_container (_parent_cdata , module , name , in_rpc_output )
13951424 _to_dnode (value , s , n , rpcreply )
13961425
13971426 elif isinstance (s , SContainer ):
1398- n = _create_container (_parent , module , name , in_rpc_output )
1427+ n = _create_container (_parent_cdata , module , name , in_rpc_output )
13991428 _to_dnode (value , s , n , in_rpc_output )
14001429
14011430 elif isinstance (s , SList ):
@@ -1420,31 +1449,31 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
14201449 except KeyError as e :
14211450 raise ValueError ("Missing key %s in the list" % (k )) from e
14221451
1423- n = _create_list (_parent , module , name , key_values , in_rpc_output )
1452+ n = _create_list (_parent_cdata , module , name , key_values , in_rpc_output )
14241453 _to_dnode (val , s , n , in_rpc_output )
14251454
14261455 elif isinstance (s , SNotif ):
1427- n = _create_container (_parent , module , name , in_rpc_output )
1456+ n = _create_container (_parent_cdata , module , name , in_rpc_output )
14281457 _to_dnode (value , s , n , in_rpc_output )
14291458
14301459 result = None
14311460
14321461 try :
14331462 if parent is not None :
1434- _parent = parent .cdata
1463+ _parent_cdata = parent .cdata
14351464 _schema_parent = parent .schema ()
14361465 else :
1437- _parent = ffi .NULL
1466+ _parent_cdata = ffi .NULL
14381467 _schema_parent = module
14391468
14401469 _to_dnode (
14411470 dic ,
14421471 _schema_parent ,
1443- _parent ,
1472+ _parent_cdata ,
14441473 in_rpc_output = rpcreply and isinstance (parent , DRpc ),
14451474 )
14461475 if created :
1447- result = DNode .new (module .context , created [0 ])
1476+ result = DNode .new (module .context , created [0 ], parent )
14481477 if validate :
14491478 result .root ().validate (
14501479 no_state = no_state ,
0 commit comments