33import networkx as nx
44from boltons .setutils import IndexedSet as iset
55
6- from .base import jetsam , NetworkOperation , Operation
6+ from .base import NetworkOperation , Operation , aslist , jetsam
77from .modifiers import optional , sideffect
88from .network import Network
99
1010
1111class FunctionalOperation (Operation ):
12+ """Use operation() to build instances of this class instead"""
1213 def __init__ (self , fn = None , ** kwargs ):
1314 self .fn = fn
1415 Operation .__init__ (self , ** kwargs )
16+ self ._validate ()
17+
18+ def __repr__ (self ):
19+ """
20+ Display more informative names for the Operation class
21+ """
22+ needs = aslist (self .needs )
23+ provides = aslist (self .provides )
24+ fn_name = self .fn and getattr (self .fn , "__name__" , str (self .fn ))
25+ return f"FunctionalOperation(name={ self .name !r} , needs={ needs !r} , provides={ provides !r} , fn={ fn_name !r} )"
26+
27+ def _validate (self ):
28+ super ()._validate ()
29+ if not self .fn or not callable (self .fn ):
30+ raise ValueError (f"Operation was not provided with a callable: { self .fn } " )
1531
1632 def compute (self , named_inputs , outputs = None ):
1733 try :
@@ -65,57 +81,63 @@ def __call__(self, *args, **kwargs):
6581 return self .fn (* args , ** kwargs )
6682
6783
68- class operation ( Operation ) :
84+ class operation :
6985 """
70- This object represents an operation in a computation graph. Its
71- relationship to other operations in the graph is specified via its
72- ``needs`` and ``provides`` arguments.
86+ A builder for graph-operations wrapping functions.
7387
7488 :param function fn:
7589 The function used by this operation. This does not need to be
7690 specified when the operation object is instantiated and can instead
7791 be set via ``__call__`` later.
78-
7992 :param str name:
8093 The name of the operation in the computation graph.
81-
8294 :param list needs:
8395 Names of input data objects this operation requires. These should
8496 correspond to the ``args`` of ``fn``.
85-
8697 :param list provides:
8798 Names of output data objects this operation provides.
8899
89- """
100+ :return:
101+ when called, it returns a :class:`FunctionalOperation`
90102
91- def __init__ (self , fn = None , ** kwargs ):
92- self .fn = fn
93- Operation .__init__ (self , ** kwargs )
103+ **Example:**
104+
105+ Here it is an example of it use with the "builder pattern"::
106+
107+ >>> from graphtik import operation
108+
109+ >>> opb = operation(name='add_op')
110+ >>> opb.withset(needs=['a', 'b'])
111+ operation(name='add_op', needs=['a', 'b'], provides=None, fn=None)
112+ >>> opb.withset(provides='SUM', fn=sum)
113+ operation(name='add_op', needs=['a', 'b'], provides='SUM', fn='sum')
114+
115+ You may keep calling ``withset()`` till you invoke ``__call__()`` on the builder;
116+ then you get te actual :class:`Operation` instance::
94117
95- def _normalize_kwargs (self , kwargs ):
118+ >>> # Create `Operation` and overwrite function at the last moment.
119+ >>> opb(sum)
120+ FunctionalOperation(name='add_op', needs=['a', 'b'], provides=['SUM'], fn='sum')
121+ """
96122
97- # Allow single value for needs parameter
98- needs = kwargs ["needs" ]
99- if isinstance (needs , str ) and not isinstance (needs , optional ):
100- assert needs , "empty string provided for `needs` parameters"
101- kwargs ["needs" ] = [needs ]
123+ fn = name = needs = provides = None
102124
103- # Allow single value for provides parameter
104- provides = kwargs .get ("provides" )
105- if isinstance (provides , str ):
106- assert provides , "empty string provided for `needs` parameters"
107- kwargs ["provides" ] = [provides ]
125+ def __init__ (self , fn = None , * , name = None , needs = None , provides = None ):
126+ self .withset (fn = fn , name = name , needs = needs , provides = provides )
108127
109- assert kwargs ["name" ], "operation needs a name"
110- assert isinstance (kwargs ["needs" ], list ), "no `needs` parameter provided"
111- assert isinstance (kwargs ["provides" ], list ), "no `provides` parameter provided"
112- assert hasattr (
113- kwargs ["fn" ], "__call__"
114- ), "operation was not provided with a callable"
128+ def withset (self , * , fn = None , name = None , needs = None , provides = None ):
129+ if fn is not None :
130+ self .fn = fn
131+ if name is not None :
132+ self .name = name
133+ if needs is not None :
134+ self .needs = needs
135+ if provides is not None :
136+ self .provides = provides
115137
116- return kwargs
138+ return self
117139
118- def __call__ (self , fn = None , ** kwargs ):
140+ def __call__ (self , fn = None , * , name = None , needs = None , provides = None ):
119141 """
120142 This enables ``operation`` to act as a decorator or as a functional
121143 operation, for example::
@@ -138,35 +160,18 @@ def myadd(a, b):
138160 composed into a computation graph.
139161 """
140162
141- if fn is not None :
142- self .fn = fn
143-
144- total_kwargs = {}
145- total_kwargs .update (vars (self ))
146- total_kwargs .update (kwargs )
147- total_kwargs = self ._normalize_kwargs (total_kwargs )
163+ self .withset (fn = fn , name = name , needs = needs , provides = provides )
148164
149- return FunctionalOperation (** total_kwargs )
165+ return FunctionalOperation (** vars ( self ) )
150166
151167 def __repr__ (self ):
152168 """
153169 Display more informative names for the Operation class
154170 """
155-
156- def aslist (i ):
157- if i and not isinstance (i , str ):
158- return list (i )
159- return i
160-
161- func_name = getattr (self , "fn" )
162- func_name = func_name and getattr (func_name , "__name__" , None )
163- return u"%s(name='%s', needs=%s, provides=%s, fn=%s)" % (
164- self .__class__ .__name__ ,
165- getattr (self , "name" , None ),
166- aslist (getattr (self , "needs" , None )),
167- aslist (getattr (self , "provides" , None )),
168- func_name ,
169- )
171+ needs = aslist (self .needs )
172+ provides = aslist (self .provides )
173+ fn_name = self .fn and getattr (self .fn , "__name__" , str (self .fn ))
174+ return f"operation(name={ self .name !r} , needs={ needs !r} , provides={ provides !r} , fn={ fn_name !r} )"
170175
171176
172177class compose (object ):
0 commit comments