@@ -49,6 +49,17 @@ def dict_or_list_iter(obj):
49
49
else :
50
50
raise TypeError ("obj must be either dict or list" )
51
51
52
+ def dict_or_list_map (obj , func ):
53
+ if type (obj ) is list :
54
+ return [func (n ) for n in obj ]
55
+ elif type (obj ) is dict :
56
+ return {k : func (n ) for k , n in obj .items ()}
57
+ else :
58
+ raise TypeError ("obj must be either dict or list" )
59
+
60
+ class EmptyParentKey :
61
+ pass
62
+
52
63
class ColorCycle :
53
64
def __init__ (self , colors ):
54
65
self .colors = colors
@@ -111,8 +122,7 @@ class EvalNode(ABC, metaclass=InstanceCounterMeta):
111
122
:type name: string
112
123
113
124
:param parents: The parents of this *EvalNode*. Can also be understood as
114
- the data sources. If a single *EvalNode* instance is passed, expect a
115
- list containing a single item for *parent_data*, else *parent_data*
125
+ the data sources. *parent_data*
116
126
will have the same structure as this parameter with *EvalNodes*
117
127
exchanged for the return value of :py:meth:`EvalNode.do`
118
128
:type parents: list or dict or :py:class:`EvalNode`
@@ -205,15 +215,16 @@ def __init__(self, name, parents=None, plot=False, cache=None, ignore_cache=Fals
205
215
if self .parents is None :
206
216
self .parents = parents
207
217
218
+ # access _parent_data keys while iterating by using _set_parent_data and
219
+ # _get_parent_data to automatically handle the case of only a single parent
208
220
if type (self .parents ) is list :
209
221
self ._parent_data = [None ] * len (self .parents )
210
222
elif type (self .parents ) is dict :
211
223
self ._parent_data = {}
212
- elif isinstance (self .parents , EvalNode ):
213
- self .parents = [self .parents ]
214
- self ._parent_data = [None ]
224
+ elif isinstance (self .parents , EvalNode ) or self .parents is None :
225
+ self ._parent_data = None
215
226
else :
216
- raise TypeError ("Parents must be either list or dict or EvalNode" )
227
+ raise TypeError ("Parents must be either list or dict or EvalNode or NoneType " )
217
228
218
229
@property
219
230
def id (self ):
@@ -296,17 +307,27 @@ def copy(self, name_suffix, plot=None, ignore_cache=None, last_parents=None, las
296
307
new_parents = [None ] * len (self .parents )
297
308
elif type (self .parents ) is dict :
298
309
new_parents = {}
310
+ elif isinstance (self .parents , EvalNode ) or self .parents is None :
311
+ new_parents = None
312
+ else :
313
+ raise TypeError ("parents have an invalid type" )
299
314
300
315
if self in memo :
301
316
new = memo [self ]
302
317
else :
318
+ is_last_node = True
303
319
for n , p in self .parents_iter :
304
320
cpy_or_memo = p .copy (name_suffix , plot , ignore_cache , last_parents , last_kwargs , memo = memo )#, **kwargs)
305
- new_parents [n ] = cpy_or_memo
321
+ is_last_node = False
322
+ if n is EmptyParentKey :
323
+ # this can only happen once because EmptyParentKey is used only if there's no more than one parents
324
+ new_parents = cpy_or_memo
325
+ else :
326
+ new_parents [n ] = cpy_or_memo
306
327
307
328
kwargs_use = copy .deepcopy (self .ext )
308
329
309
- if len ( new_parents ) == 0 :
330
+ if is_last_node :
310
331
new_parents = last_parents
311
332
312
333
# merge items whose values are dicts
@@ -377,18 +398,25 @@ def __contains__(self, item):
377
398
def parents_iter (self ):
378
399
"""
379
400
:return: An iterator that can be handled uniformly
380
- for any type of parents (list or dict).
401
+ for any type of parents (list or dict or :py:class:`EvalNode` ).
381
402
For lists, it is enumerate(:py:attr:`parents`), and for dict
382
- it is :py:attr:`parents`.items()
403
+ it is :py:attr:`parents`.items(). For a single parent of class
404
+ :py:class:`EvalNode`, an iterator yielding
405
+ :py:class:`EmptyParentKey`: *parent* is returned so that this
406
+ property can be used uniformly as iterator.
383
407
384
408
:rtype: iterator
385
409
"""
386
410
if type (self .parents ) is list :
387
411
return enumerate (self .parents )
388
412
elif type (self .parents ) is dict :
389
413
return self .parents .items ()
414
+ elif isinstance (self .parents , EvalNode ):
415
+ return {EmptyParentKey : self .parents }.items ()
416
+ elif self .parents is None :
417
+ return {}.items ()
390
418
else :
391
- raise TypeError ("parents must be either dict or list" )
419
+ raise TypeError ("parents must be either dict or list or EvalNode or None " )
392
420
393
421
def is_parent (self , possible_parent ):
394
422
"""
@@ -399,21 +427,31 @@ def is_parent(self, possible_parent):
399
427
return possible_parent in self .parents
400
428
elif type (self .parents ) is dict :
401
429
return possible_parent in self .parents .values ()
430
+ elif isinstance (self .parents , EvalNode ):
431
+ return possible_parent is self .parents
432
+ elif self .parents is None :
433
+ return False
402
434
else :
403
- raise TypeError ("parents must be either dict or list" )
435
+ raise TypeError ("parents must be either dict or list or EvalNode instance or None " )
404
436
405
437
def parents_contains (self , key ):
406
438
"""
407
439
:return: True if the parents iterator contains an item with the given key.
408
440
For parents stored as list, this is equal to 'key < len(parents)'
409
441
and for parents stored as dict, this is eqal to 'key in parents'
442
+ If the parents are a single :py:class:`EvalNode` this always returns
443
+ False.
410
444
411
445
:rtype: bool
412
446
"""
413
447
if type (self .parents ) is list :
414
448
return key < len (self .parents )
415
449
elif type (self .parents ) is dict :
416
450
return key in self .parents
451
+ elif isinstance (self .parents , EvalNode ):
452
+ return False
453
+ elif self .parents is None :
454
+ return False
417
455
else :
418
456
raise TypeError ("parents must be either dict or list" )
419
457
@@ -428,6 +466,9 @@ def parents(self):
428
466
@parents .setter
429
467
def parents (self , parents ):
430
468
self ._parents = parents
469
+ if not (isinstance (parents , EvalNode ) or type (parents ) is list or
470
+ type (parents ) is dict or parents is None ):
471
+ raise TypeError ("invalid type for parents (got {})" .format (type (parents )))
431
472
432
473
@property
433
474
def plot_on (self ):
@@ -624,6 +665,27 @@ def _check_data_needed(self):
624
665
self ._log_debug ("Does not need data" )
625
666
return False
626
667
668
+ def _set_parent_data (self , key , data ):
669
+ """
670
+ Sets the parent data of parent with key.
671
+ Use this method to correctly handle the case of a single parent, i.e.
672
+ the key being EmptyParentKey as returned by :py:attr:`parents_iter`.
673
+ This enables concise iterating over the parents
674
+
675
+ :return: *None*
676
+ """
677
+
678
+ if key is EmptyParentKey :
679
+ self ._parent_data = data
680
+ else :
681
+ self ._parent_data [key ] = data
682
+
683
+ def _get_parent_data (self , key ):
684
+ if key is EmptyParentKey :
685
+ return self ._parent_data
686
+ else :
687
+ return self ._parent_data [key ]
688
+
627
689
def _maybe_fill_parent_data (self , this_need_data , common , kwargs ):
628
690
"""
629
691
Fills parent data but allows parents to return None if this_need_data is False.
@@ -638,7 +700,7 @@ def _maybe_fill_parent_data(self, this_need_data, common, kwargs):
638
700
for k , p in self .parents_iter :
639
701
maybe_data = p ._data (this_need_data , common , kwargs )
640
702
assert not (maybe_data is None and this_need_data ) # if we requested data, but recieved None, quit
641
- self ._parent_data [ k ] = maybe_data
703
+ self ._set_parent_data ( k , maybe_data )
642
704
must_fill = must_fill or not maybe_data is None
643
705
644
706
return must_fill
@@ -656,8 +718,8 @@ def _fill_parent_data(self, common, kwargs):
656
718
"""
657
719
self ._log_debug ("Some parents returned data, so we assume regeneration is neccessary and traverse the tree a second time to get all data" )
658
720
for k , p in self .parents_iter :
659
- if self ._parent_data [ k ] is None :
660
- self ._parent_data [ k ] = p ._data (True , common , kwargs )
721
+ if self ._get_parent_data ( k ) is None :
722
+ self ._set_parent_data ( k , p ._data (True , common , kwargs ) )
661
723
662
724
def _data (self , need_data , common , kwargs ):
663
725
# request data from parents using this info
@@ -673,7 +735,7 @@ def _data(self, need_data, common, kwargs):
673
735
674
736
self ._log_debug ("Requesting regeneration or RAM cache retrieval of data" )
675
737
v = self ._generate_v (common , kwargs )
676
- elif len ( self .parents ) == 0 and this_need_data :
738
+ elif self .parent_count == 0 and this_need_data :
677
739
# if there are no parents but data is needed
678
740
self ._log_debug ("This node has no parents but it needs data" )
679
741
self ._log_debug ("Requesting regeneration or RAM cache retrieval of data" )
@@ -697,6 +759,7 @@ def _data(self, need_data, common, kwargs):
697
759
698
760
def _plot (self , ax , plot_group , common , memo , kwargs ):
699
761
for k , p in self .parents_iter :
762
+ self ._log_debug ("[_plot] traversing tree upwards" )
700
763
p ._plot (ax , plot_group , common , memo , kwargs )
701
764
702
765
if self .in_plot_group (plot_group ) and not self in memo :
@@ -705,10 +768,10 @@ def _plot(self, ax, plot_group, common, memo, kwargs):
705
768
# modification: at this place, when v == None, v was loaded from
706
769
# and returned, so child notes regenerated.
707
770
# this has been removed but I dont know if it was actually senseful
708
- self ._log_debug ("Request loading data from cache or RAM cache anyways for plotting" )
771
+ self ._log_debug ("[_plot] Request loading data from cache or RAM cache anyways for plotting" )
709
772
v = self ._data (True , common , kwargs )
710
773
711
- self ._log_debug ("Plot the node" )
774
+ self ._log_debug ("[_plot] Plot the node" )
712
775
713
776
self ._plot_handles = self .plot (v , plot_on , common , ** (self .ext | kwargs ))
714
777
@@ -719,24 +782,30 @@ def _plot(self, ax, plot_group, common, memo, kwargs):
719
782
if not self ._plot_handles [0 ] is None and self ._color is None :
720
783
self ._color = self ._plot_handles [0 ].get_color ()
721
784
722
-
723
- def map_parents (self , callback ):
785
+ def map_parents (self , callback , call_on_none = False ):
724
786
"""
725
787
Executes callback for every parent of this instance
726
788
727
789
:param callback: The :py:class:`Callable` to be executed
728
790
:type callback: :py:class:`Callable` (:py:class:`EvalNode`) -> object
729
791
792
+ :param call_on_none: If this :py:class:`EvalNode` has no parents
793
+ call the callback with *None* as argument.
794
+ :type call_on_none: :py:class:`bool`, Default: *False*
795
+
730
796
:return: object with structure like :py:attr:`parents` but the nodes
731
797
replaced with the objects returned by *callback*
732
798
:rtype: same as :py:attr:`parents`
733
799
"""
734
- if type (self .parents ) is list :
735
- return [callback (n ) for n in self .parents ]
736
- elif type (self .parents ) is dict :
737
- return {k : callback (n ) for k , n in self .parents .items ()}
800
+ if isinstance (self .parents , EvalNode ):
801
+ return callback (self .parents )
802
+ elif self .parents is None :
803
+ if call_on_none :
804
+ return callback (None )
805
+ else :
806
+ return None
738
807
else :
739
- raise TypeError ( " parents must be either dict or list" )
808
+ return dict_or_list_map ( self . parents , callback )
740
809
741
810
def search_parent (self , starts_with ):
742
811
"""
@@ -854,6 +923,14 @@ def handles_complete_tree(self):
854
923
855
924
return l
856
925
926
+ @property
927
+ def parent_count (self ):
928
+ if self .parents is None :
929
+ return 0
930
+ elif isinstance (self .parents , EvalNode ):
931
+ return 1
932
+ else :
933
+ return len (self .parents )
857
934
858
935
def def_kwargs (self , ** kwargs ):
859
936
"""
@@ -981,7 +1058,9 @@ def get_color(self):
981
1058
return self ._color
982
1059
983
1060
for _ , p in self .parents_iter :
984
- return p .get_color ()
1061
+ p_color = p .get_color ()
1062
+ if not p_color is None :
1063
+ return p_color
985
1064
986
1065
return None
987
1066
@@ -1016,6 +1095,7 @@ def do(self, parent_data, common, **kwargs):
1016
1095
1017
1096
class NodePlotGroup (EvalNode ):
1018
1097
def subclass_init (self , parents , ** kwargs ):
1098
+ raise TypeError ("Using NodePlotGroup is deprecated because it hasn't been updated, will probably not work and I don't know what its purpose was anymore." )
1019
1099
super ().subclass_init (parents , ** kwargs )
1020
1100
self .never_cache = True
1021
1101
0 commit comments