55import abc
66import logging
77from collections import abc as cabc
8- from typing import Callable , Collection , Tuple , Union
8+ from typing import Callable , Collection , Mapping , Tuple , Union
99
1010from boltons .setutils import IndexedSet as iset
1111
@@ -41,7 +41,7 @@ def reparse_operation_data(name, needs, provides):
4141
4242# TODO: immutable `Operation` by inheriting from `namedtuple`.
4343class Operation (abc .ABC ):
44- """An abstract class representing a data transformation by :meth:`.compute()`."""
44+ """An abstract class representing an action with :meth:`.compute()`."""
4545
4646 @abc .abstractmethod
4747 def compute (self , named_inputs , outputs = None ):
@@ -75,6 +75,7 @@ def __init__(
7575 provides : Union [Collection , str ] = None ,
7676 * ,
7777 parents : Tuple = None ,
78+ node_props : Mapping = None ,
7879 returns_dict = None ,
7980 ):
8081 """
@@ -87,18 +88,21 @@ def __init__(
8788 Names of input data objects this operation requires.
8889 :param provides:
8990 Names of output data objects this provides.
90- :param parent :
91+ :param parents :
9192 a tuple wth the names of the parents, prefixing `name`,
9293 but also kept for equality/hash check.
93-
94+ :param node_props:
95+ added as-is into NetworkX graph
9496 """
9597 ## Set op-data early, for repr() to work on errors.
9698 self .fn = fn
9799 self .name = name
98100 self .needs = needs
99101 self .provides = provides
100102 self .parents = parents
103+ self .node_props = node_props = node_props if node_props else {}
101104 self .returns_dict = returns_dict
105+
102106 if not fn or not callable (fn ):
103107 raise ValueError (
104108 f"Operation was not provided with a callable: { fn } \n { self } "
@@ -107,6 +111,9 @@ def __init__(
107111 raise ValueError (
108112 f"Operation `parents` must be tuple, was { parents } \n { self } "
109113 )
114+ if node_props is not None and not isinstance (node_props , cabc .Mapping ):
115+ raise ValueError (f"`node_props` must be a mapping, got: { node_props !r} " )
116+
110117 ## Overwrite reparsed op-data.
111118 name = "." .join (str (pop ) for pop in ((parents or ()) + (name ,)))
112119 self .name , self .needs , self .provides = reparse_operation_data (
@@ -133,21 +140,20 @@ def __repr__(self):
133140 provides = aslist (self .provides , "provides" )
134141 fn_name = self .fn and getattr (self .fn , "__name__" , str (self .fn ))
135142 returns_dict_marker = self .returns_dict and "{}" or ""
143+ nprops = f", x{ len (self .node_props )} props" if self .node_props else ""
136144 return (
137145 f"FunctionalOperation(name={ self .name !r} , needs={ needs !r} , "
138- f"provides={ provides !r} , fn{ returns_dict_marker } ={ fn_name !r} )"
146+ f"provides={ provides !r} , fn{ returns_dict_marker } ={ fn_name !r} { nprops } )"
139147 )
140148
141- def _adopted_by (self , parent ):
142- """Make a clone with the given parrent set."""
143- return FunctionalOperation (
144- self .fn ,
145- self .name ,
146- self .needs ,
147- self .provides ,
148- parents = (parent ,) + (self .parents or ()),
149- returns_dict = self .returns_dict ,
150- )
149+ def withset (self , ** kw ) -> "FunctionalOperation" :
150+ """Make a clone with the some values replaced."""
151+ fn = kw ["fn" ] if "fn" in kw else self .fn
152+ name = kw ["name" ] if "name" in kw else self .name
153+ needs = kw ["needs" ] if "needs" in kw else self .needs
154+ provides = kw ["provides" ] if "provides" in kw else self .provides
155+
156+ return FunctionalOperation (fn , name , needs , provides , ** kw )
151157
152158 def _zip_results_with_provides (self , results , real_provides : iset ) -> dict :
153159 """Zip results with expected "real" (without sideffects) `provides`."""
@@ -201,7 +207,6 @@ def compute(self, named_inputs, outputs=None) -> dict:
201207 if not isinstance (n , (optional , sideffect ))
202208 ]
203209 except KeyError :
204- # FIXME:
205210 compulsory = iset (
206211 n for n in self .needs if not isinstance (n , (optional , sideffect ))
207212 )
@@ -283,7 +288,8 @@ class operation:
283288 elements must be returned
284289 :param bool returns_dict:
285290 if true, it means the `fn` returns a dictionary with all `provides`,
286- and no further processing is done on them.
291+ :param node_props:
292+ added as-is into NetworkX graph
287293
288294 :return:
289295 when called, it returns a :class:`FunctionalOperation`
@@ -316,15 +322,24 @@ def __init__(
316322 needs = None ,
317323 provides = None ,
318324 returns_dict = None ,
325+ node_props : Mapping = None ,
319326 ):
320327 self .fn = fn
321328 self .name = name
322329 self .needs = needs
323330 self .provides = provides
324331 self .returns_dict = returns_dict
332+ self .node_props = node_props
325333
326334 def withset (
327- self , * , fn = None , name = None , needs = None , provides = None , returns_dict = None
335+ self ,
336+ * ,
337+ fn = None ,
338+ name = None ,
339+ needs = None ,
340+ provides = None ,
341+ returns_dict = None ,
342+ node_props : Mapping = None ,
328343 ) -> "operation" :
329344 if fn is not None :
330345 self .fn = fn
@@ -336,11 +351,20 @@ def withset(
336351 self .provides = provides
337352 if returns_dict is not None :
338353 self .returns_dict = returns_dict
354+ if node_props is not None :
355+ self .node_props = node_props
339356
340357 return self
341358
342359 def __call__ (
343- self , fn = None , * , name = None , needs = None , provides = None , returns_dict = None
360+ self ,
361+ fn = None ,
362+ * ,
363+ name = None ,
364+ needs = None ,
365+ provides = None ,
366+ returns_dict = None ,
367+ node_props : Mapping = None ,
344368 ) -> FunctionalOperation :
345369 """
346370 This enables ``operation`` to act as a decorator or as a functional
@@ -365,7 +389,12 @@ def myadd(a, b):
365389 """
366390
367391 self .withset (
368- fn = fn , name = name , needs = needs , provides = provides , returns_dict = returns_dict
392+ fn = fn ,
393+ name = name ,
394+ needs = needs ,
395+ provides = provides ,
396+ returns_dict = returns_dict ,
397+ node_props = node_props ,
369398 )
370399
371400 return FunctionalOperation (** vars (self ))
@@ -377,4 +406,8 @@ def __repr__(self):
377406 needs = aslist (self .needs , "needs" )
378407 provides = aslist (self .provides , "provides" )
379408 fn_name = self .fn and getattr (self .fn , "__name__" , str (self .fn ))
380- return f"operation(name={ self .name !r} , needs={ needs !r} , provides={ provides !r} , fn={ fn_name !r} )"
409+ nprops = f", x{ len (self .node_props )} props" if self .node_props else ""
410+ return (
411+ f"operation(name={ self .name !r} , needs={ needs !r} , "
412+ f"provides={ provides !r} , fn={ fn_name !r} { nprops } )"
413+ )
0 commit comments