2323from  .utils  import  STYLES 
2424
2525
26- # root has no key (None), keyed node has children with str keys, unkeyed node has children with int keys 
27- Key  =  Union [None , str , int ]
28- KeyedTree  =  Tuple [Key , "Tree" ]
29- Path  =  str 
26+ # keyed node has children with str keys, unkeyed node has children with int keys 
27+ # note: root has no key (None value) 
28+ Key  =  Union [str , int ]
29+ KeyedTree  =  Tuple [Optional [Key ], "Tree" ]
30+ Path  =  Iterable [Key ]
3031
3132GenericNode  =  TypeVar ("GenericNode" , bound = Node )
32- KeyedNode  =  Tuple [Key , GenericNode ]
33+ KeyedNode  =  Tuple [Optional [ Key ] , GenericNode ]
3334
3435
3536class  Tree (Generic [GenericNode ]):
@@ -48,9 +49,7 @@ class Tree(Generic[GenericNode]):
4849
4950    """ 
5051
51-     def  __init__ (self , path_separator : str  =  "." ) ->  None :
52-         self .path_separator  =  path_separator 
53- 
52+     def  __init__ (self ) ->  None :
5453        # nodes references and hierarchy in tree 
5554        self .root : Optional [NodeId ] =  None 
5655        # node identifier -> node 
@@ -79,37 +78,40 @@ def child_id(self, nid: NodeId, key: Key) -> NodeId:
7978            if  child_id  is  None :
8079                raise  ValueError ("No child of key %s below %s"  %  (key , nid ))
8180            return  child_id 
82-         if  not  isinstance (key , (str , int )):
83-             raise  ValueError ("Expected integer 'castable' key, got %s"  %  key )
84-         return  self ._nodes_children_list [nid ][int (key )]
81+         try :
82+             return  self ._nodes_children_list [nid ][int (key )]
83+         except  (KeyError , ValueError , TypeError ):
84+             raise  ValueError ("No child of key %s below %s"  %  (key , nid ))
8585
8686    def  child (self , nid : NodeId , key : Key ) ->  KeyedNode :
8787        return  self .get (self .child_id (nid , key ))
8888
89-     def  get_node_id_by_path (self , path : Path ) ->  NodeId :
89+     def  get_node_id_by_path (self , path : Path ,  strict :  bool   =   True ) ->  NodeId :
9090        nid  =  self .root 
9191        if  nid  is  None :
9292            raise  ValueError ("Empty tree" )
9393        if  path  ==  "" :
9494            return  nid 
95-         keys  =  str (path ).split (self .path_separator )
96-         for  k  in  keys :
97-             nid  =  self .child_id (nid , k )
95+         for  k  in  path :
96+             try :
97+                 nid  =  self .child_id (nid , k )
98+             except  ValueError :
99+                 if  strict  or  not  isinstance (k , str ) or  not  k .isdigit ():
100+                     raise 
101+                 nid  =  self .child_id (nid , int (k ))
98102        if  nid  is  None :
99-             raise  ValueError ("Empty tree " )
103+             raise  ValueError ("Not found " )
100104        return  nid 
101105
102106    def  get_path (self , nid : NodeId ) ->  Path :
103-         return  self .path_separator .join (
104-             [
105-                 str (k )
106-                 for  k , _  in  self .ancestors (nid , from_root = True , include_current = True )[
107-                     1 :
108-                 ]
109-             ]
110-         )
107+         return  [
108+             # ignore typing warning of potential None value, since None only applies at root node which is excluded 
109+             # [1:] -> exclude root node key 
110+             k   # type: ignore 
111+             for  k , _  in  self .ancestors (nid , from_root = True , include_current = True )[1 :]
112+         ]
111113
112-     def  get_key (self , nid : NodeId ) ->  Key :
114+     def  get_key (self , nid : NodeId ) ->  Optional [ Key ] :
113115        """Get a node's key. 
114116        :param nid: str, identifier of node 
115117
@@ -334,7 +336,7 @@ def insert(
334336        parent_id : Optional [NodeId ] =  None ,
335337        child_id : Optional [NodeId ] =  None ,
336338        child_id_below : Optional [NodeId ] =  None ,
337-         key : Key  =  None ,
339+         key : Optional [ Key ]  =  None ,
338340    ) ->  "Tree" :
339341        if  isinstance (item , Tree ):
340342            self .insert_tree (
@@ -363,15 +365,8 @@ def insert_node(
363365        node : GenericNode ,
364366        parent_id : Optional [NodeId ] =  None ,
365367        child_id : Optional [NodeId ] =  None ,
366-         key : Key  =  None ,
367-     ) ->  Key :
368-         """Insert node, return key 
369-         :param node: 
370-         :param parent_id: 
371-         :param child_id: 
372-         :param key: 
373-         :return: 
374-         """ 
368+         key : Optional [Key ] =  None ,
369+     ) ->  Optional [Key ]:
375370        self ._validate_node_insertion (node )
376371        if  parent_id  is  not   None  and  child_id  is  not   None :
377372            raise  ValueError ('Can declare at most "parent_id" or "child_id"' )
@@ -385,7 +380,7 @@ def _insert_node_below(
385380        self ,
386381        node : GenericNode ,
387382        parent_id : Optional [NodeId ],
388-         key : Key ,
383+         key : Optional [ Key ] ,
389384    ) ->  None :
390385        # insertion at root 
391386        if  parent_id  is  None :
@@ -430,7 +425,9 @@ def _insert_node_below(
430425        self ._nodes_map [node_id ] =  node 
431426        self ._nodes_parent [node_id ] =  parent_id 
432427
433-     def  _insert_node_above (self , node : GenericNode , child_id : NodeId , key : Key ) ->  None :
428+     def  _insert_node_above (
429+         self , node : GenericNode , child_id : NodeId , key : Optional [Key ]
430+     ) ->  None :
434431        self ._ensure_present (child_id )
435432        # get parent_id before dropping subtree 
436433        try :
@@ -454,9 +451,8 @@ def insert_tree(
454451        parent_id : Optional [NodeId ] =  None ,
455452        child_id : Optional [NodeId ] =  None ,
456453        child_id_below : Optional [NodeId ] =  None ,
457-         key : Key  =  None ,
458-     ) ->  Key :
459-         """Return new key""" 
454+         key : Optional [Key ] =  None ,
455+     ) ->  Optional [Key ]:
460456        self ._validate_tree_insertion (new_tree )
461457        if  new_tree .root  is  None :
462458            raise  ValueError ("Empty inserted tree" )
@@ -480,7 +476,7 @@ def _insert_tree_below(
480476        self ,
481477        new_tree : "Tree" ,
482478        parent_id : Optional [NodeId ],
483-         key : Key ,
479+         key : Optional [ Key ] ,
484480    ) ->  "Tree" :
485481        if  parent_id  is  None :
486482            # insertion at root requires tree to be empty 
@@ -506,7 +502,7 @@ def _insert_tree_above(
506502        new_tree : "Tree" ,
507503        child_id : NodeId ,
508504        child_id_below : Optional [NodeId ],
509-         key : Key ,
505+         key : Optional [ Key ] ,
510506    ) ->  None :
511507        # make all checks before modifying tree 
512508        self ._ensure_present (child_id )
@@ -711,7 +707,7 @@ def _iter_nodes_with_location(
711707        filter_ : Optional [Callable [[GenericNode ], bool ]],
712708        reverse : bool ,
713709        is_last_list : Optional [List [bool ]] =  None ,
714-     ) ->  Iterable [Tuple [Tuple [bool , ...], Key , GenericNode ]]:
710+     ) ->  Iterable [Tuple [Tuple [bool , ...], Optional [ Key ] , GenericNode ]]:
715711        """Yield nodes with information on how they are placed. 
716712        :param nid: starting node identifier 
717713        :param filter_: filter function applied on nodes 
0 commit comments