2
"""Contains nodes supporting facading within a dependency graph - this can be used
3
for container tyoes or nodes containing their own subgraph even
5
__docformat__ = "restructuredtext"
7
from networkx import DiGraph, NetworkXError
8
from collections import deque
11
from util import iDuplicatable
13
from dge import NodeBase
14
from dge import _PlugShell
16
from dge import Attribute
18
__all__ = ("FacadeNodeBase", "GraphNodeBase", "OIFacadePlug")
23
class _OIShellMeta( type ):
24
"""Metaclass building the method wrappers for the _FacadeShell class - not
25
all methods should be overridden, just the ones important to use"""
28
def createUnfacadeMethod( cls, funcname ):
29
def unfacadeMethod( self, *args, **kwargs ):
30
return getattr( self._toIShell(), funcname )( *args, **kwargs )
34
def createFacadeMethod( cls, funcname ):
35
"""in our case, connections just are handled by our own OI plug, staying
40
def createMethod( cls,funcname, facadetype ):
42
if facadetype == "unfacade":
43
method = cls.createUnfacadeMethod( funcname )
45
method = cls.createFacadeMethod( funcname )
47
if method: # could be none if we do not overwrite the method
48
method.__name__ = funcname
53
def __new__( metacls, name, bases, clsdict ):
54
unfacadelist = clsdict.get( '__unfacade__' )
55
facadelist = clsdict.get( '__facade__' )
57
# create the wrapper functions for the methods that should wire to the
58
# original shell, thus we unfacade them
59
for funcnamelist, functype in ( ( unfacadelist, "unfacade" ), ( facadelist, "facade" ) ):
60
for funcname in funcnamelist:
61
method = metacls.createMethod( funcname, functype )
63
clsdict[ funcname ] = method
64
# END for each funcname in funcnamelist
65
# END for each type of functions
67
return type.__new__( metacls, name, bases, clsdict )
70
class _IOShellMeta( _OIShellMeta ):
71
"""Metaclass wrapping all unfacade attributes on the plugshell trying
72
to get an input connection """
75
def createUnfacadeMethod( cls,funcname ):
76
""":return: wrapper method for funcname """
78
if funcname == "get": # drection to input
79
def unfacadeMethod( self, *args, **kwargs ):
80
"""apply to the input shell"""
81
# behave like the base implementation and check the internal shell
82
# for caches first - if it exists, we use it.
83
# It would have been cleared if it is affecfted by another plug being set,
84
# thus its either still cached or somenone set the cache.
85
# if there is no cache, just trace the connections upwards.
86
# This means for get we specifiaclly override the normal "original last"
87
# behaviour to allow greater flexibility
88
oshell = self._getOriginalShell( )
92
return getattr( self._getShells( "input" )[0], funcname )( *args, **kwargs )
93
method = unfacadeMethod
94
else: # direction to output
95
def unfacadeMethod( self, *args, **kwargs ):
96
"""Clear caches of all output plugs as well"""
97
for shell in self._getShells( "output" ):
98
getattr( shell, funcname )( *args, **kwargs )
100
method = unfacadeMethod
101
# END funk type handling
105
def createFacadeMethod( cls, funcname ):
106
"""Call the main shell's function"""
107
def facadeMethod( self, *args, **kwargs ):
108
return getattr( self._getOriginalShell( ), funcname )( *args, **kwargs )
112
class _OIShell( _PlugShell ):
113
"""All connections from and to the FacadeNode must actually start and end there.
114
Iteration over internal plugShells is not allowed.
115
Thus we override only the methods that matter and assure that the call is handed
116
to the acutal internal plugshell.
117
We know everything we require as we have been fed with an oiplug
119
* node = facacde node
120
* plug = oiplug containing inode and iplug ( internal node and internal plug )
121
* The internal node allows us to hand in calls to the native internal shell
123
# list all methods that should not be a facade to our facade node
124
__unfacade__ = [ 'set', 'get', 'clearCache', 'hasCache','setCache', 'cache' ]
126
# keep this list uptodate - otherwise a default shell will be used for the missing
128
# TODO: parse the plugshell class itself to get the functions automatically
129
__facade__ = [ 'connect','disconnect','input', 'outputs','connections',
132
__metaclass__ = _OIShellMeta
134
def __init__( self, *args ):
135
"""Sanity checking"""
136
if not isinstance( args[1], OIFacadePlug ):
137
raise AssertionError( "Invalid PlugType: Need %r, got %r (%s)" % ( OIFacadePlug, args[1].__class__ , args[1]) )
139
# NOTE deprecated in python 2.6 and without effect in our case
140
super( _OIShell, self ).__init__( *args )
143
def __repr__ ( self ):
144
"""Cut away our name in the possible oiplug ( printing an unnecessary long name then )"""
145
plugname = str( self.plug )
146
nodename = str( self.node )
147
plugname = plugname.replace( nodename+'.', "" )
148
return "%s.%s" % ( nodename, plugname )
150
def _toIShell( self ):
151
""":return: convert ourselves to the real shell actually behind this facade plug"""
152
# must return original shell, otherwise call would be handed out again
153
return self.plug.inode.shellcls.origshellcls( self.plug.inode, self.plug.iplug )
156
class _IOShell( _PlugShell ):
157
"""This callable class, when called, will create a IOShell using the
158
actual facade node, not the one given as input. This allows it to have the
159
facade system handle the plugshell, or simply satisfy the original request"""
161
__unfacade__ = [ 'get', 'clearCache' ]
163
# keep this list uptodate - otherwise a default shell will be used for the missing
165
# TODO: parse the plugshell class itself to get the functions automatically
166
__facade__ = [ 'set','hasCache','setCache', 'cache',
167
'connect','disconnect','input','connections','outputs',
170
__metaclass__ = _IOShellMeta
172
def __init__( self, *args ):
173
"""Initialize this instance - we can be in creator mode or in shell mode.
174
ShellMode: we behave like a shell but apply customizations, true if 3 args ( node, plug, origshellcls )
175
CreatorMode: we only create shells of our type in ShellMode, true if 2 args
178
* origshellcls[0] = the shell class used on the manipulated node before we , must always be set as last arg
179
* facadenode[1] = the facadenode we are connected to
181
:todo: optimize by creating the unfacade methods exactly as we need them and bind the respective instance
182
methods - currently this is solved with a simple if conditiion.
184
# find whether we are in shell mode or in class mode - depending on the
187
if hasattr( args[0], '__call__' ) or isinstance( args[0], type ):
188
self.origshellcls = args[0]
189
self.facadenode = args[1]
190
self.iomap = dict() # plugname -> oiplug
191
super( _IOShell, self ).__init__( ) # initialize empty
194
# NOTE: This is deprecated in python 2.6 and doesnt do anything in our case
195
# we do not do anything special in shell mode ( at least value-wise
196
# super( _IOShell, self ).__init__( *args ) # init base
197
# END INSTANCE ( SHELL ) MODE
199
def __call__( self, *args ):
200
"""This equals a constructor call to the shell class on the wrapped node.
201
Simply return an ordinary shell at its base, but we catch some callbacks
202
This applies to everything but connection handling
204
:note: the shells we create are default ones with some extra handlers
206
return self.__class__( *args )
210
def _getoiplug( self ):
211
""":return: oiplug suitable for this shell or None"""
213
# cannot use weak references, don't want to use strong references
214
return self.node.shellcls.iomap[ self.plug.name() ]
216
# plug not on facadenode - this is fine as we get always called
218
#except AttributeError:
219
# TODO: take that back in once we use weak references or proper ids again ... lets see
220
# # facade node does not know an io plug - assure we do not try again
221
# del( self.node.shellcls[ self.plug.name() ] )
225
def _getOriginalShell( self ):
226
""":return: instance of the original shell class that was replaced by our instance"""
227
return self.node.shellcls.origshellcls( self.node, self.plug )
229
def _getTopFacadeNodeShell( self ):
230
"""Recursive method to find the first facade parent having an OI shell
232
:return: topmost facade node shell or None if we are not a managed plug"""
234
# otherwise we have found the topmost parent
235
return facadeNodeShell
238
def _getShells( self, shelltype ):
239
""":return: list of ( outside ) shells, depending on the shelltype and availability.
240
If no outside shell is avaiable, return the actual shell only
241
As facade nodes can be nested, we have to check each level of nesting
242
for connections into the outside world - if available, we use these, otherwise
245
:param shelltype: "input" - outside input shell
246
"output" - output shells, and the default shell"""
247
if not isinstance( self.node.shellcls, _IOShell ):
248
raise AssertionError( "Shellclass of %s must be _IOShell, but is %s" % ( self.node, type( self.node.shellcls ) ) )
252
# get the oiplug on our node
253
oiplug = self._getoiplug( )
255
# plug not on facadenode, just ignore and return the original shell
256
return [ self._getOriginalShell( ) ]
257
# END if there is no cached oiplug
260
# Use the facade node shell type - we need to try to get connections now,
261
# either inputs or outputs on our facade node. In case it is facaded
262
# as well, we just use a default shell that will definetly handle connections
263
# the way we expect it
264
facadeNodeShell = self.node.shellcls.facadenode.toShell( oiplug )
267
# NESTED FACADE NODES SPECIAL CASE !
268
######################################
269
# If a facade node is nested inside of another facade node, it will put
270
# it's IO shell above our OI shell.
271
# IOShells do not return connections - get a normal shell then
272
connectionShell = facadeNodeShell
273
if facadeNodeShell.__class__ is _IOShell:
274
connectionShell = _PlugShell( facadeNodeShell.node, facadeNodeShell.plug )
275
# END nested facade node special handling
279
if shelltype == "input":
281
# HIGHER LEVEL INPUT SHELLS
282
############################
283
# if we are nested, use an imput connection of our parent as they
284
# override lower level connections
285
if not connectionShell is facadeNodeShell:
286
aboveLevelInputShells = facadeNodeShell._getShells( shelltype )
288
# this is either the real input shell, or the original shell of the toplevel
289
# By convention, we return the facadeshell that is connected to the input
291
# The method that calls us only uses array index [0], which is the shell it needs !
292
# We just use the length as internal flag !
293
if len( aboveLevelInputShells ) == 2: # top level orverride !
294
return aboveLevelInputShells
296
# END aquire TL Input
298
# still here means no toplevel override
299
# TRY OUR LEVEL INPUT
300
inputShell = connectionShell.input( )
303
# FLAGGED RETURN VALUE : this indicates to our callers that
304
# we have found a good input on our level and want to use it.
305
# if the caller is the metaclass wrapper, it will only use the outshell[0]
306
# anyways and not bother
307
outShells.append( inputShell )
308
outShells.append( self )
310
outShells.append( self._getOriginalShell( ) )
312
# END outside INPUT shell handling
314
outShells.extend( connectionShell.outputs( ) )
316
# ADD 'INSIDE' ORIGINAL SHELL
317
# always allow our 'inside' level to get informed as well
318
outShells.append( self._getOriginalShell( ) )
320
# NESTED SHELL SPECIAL CASE
321
##############################
322
# query the IO Parent Shell for the shells on its level and add them
323
if not connectionShell is facadeNodeShell:
324
outShells.extend( facadeNodeShell._getShells( shelltype ) )
325
# END outside OUTPUT shell handling
337
class FacadeNodeBase( NodeBase ):
338
"""Node having no own plugs, but retrieves them by querying other other nodes
339
and claiming its his own ones.
341
Using a non-default shell it is possibly to guide all calls through to the
344
Derived classes must override _plugshells which will be queried when
345
plugs or plugshells are requested. This node will cache the result and do
346
everything required to integrate itself.
348
It lies in the nature of this class that the plugs are dependent on a specific instance
349
of this node, thus classmethods of NodeBase have been overridden with instance versions
352
The facade node keeps a plug map allowing it to map plug-shells it got from
353
you back to the original shell respectively. If the map has been missed,
354
your node will be asked for information.
356
:note: facades are intrusive for the nodes they are facading - thus the nodes
357
returned by `_getNodePlugs` will be altered. Namely the instance will get a
358
shellcls and plug override to allow us to hook into the callchain. Thus you should have
359
your own instance of the node - otherwise things might behave differently for
360
others using your nodes from another angle
362
:note: this class could also be used for facades Container nodes that provide
363
an interface to their internal nodes"""
364
shellcls = _OIShell # overriden from NodeBase
367
caching_enabled = True # if true, the facade may cache plugs once queried
370
def __init__( self, *args, **kwargs ):
371
""" Initialize the instance"""
372
self._cachedOIPlugs = list() # simple list of names
373
NodeBase.__init__( self, *args, **kwargs )
376
def __getattr__( self, attr ):
377
""":return: shell on attr made from our plugs - we do not have real ones, so we
378
need to call plugs and find it by name
380
:note: to make this work, you should always name the plug names equal to their
382
check_ambigious = not attr.startswith( OIFacadePlug._fp_prefix ) # non long names are not garantueed to be unique
385
for plug in self.plugs( ):
386
if plug.name() == attr or plug.iplug.name() == attr:
387
shell = self.toShell( plug )
388
if not check_ambigious:
390
candidates.append( shell )
391
# END if plugname matches
392
# END for each of our plugs
395
raise AttributeError( "Attribute %s does not exist on %s" % (attr,self) )
397
if len( candidates ) == 1:
401
raise AttributeError( "More than one plug with the local name %s exist on %s - use the long name, i.e. %snode_attr" % ( attr, self, OIFacadePlug._fp_prefix ) )
405
def copyFrom( self, other, **kwargs ):
406
"""Actually, it does nothing because our plugs are linked to the internal
407
nodes in a quite complex way. The good thing is that this is just a cache that
408
will be updated once someone queries connections again.
409
Basically it comes down to the graph duplicating itself using node and plug
410
methods instead of just doing his 'internal' magic"""
414
#{ To be Subclass-Implemented
416
def _getNodePlugs( self ):
417
"""Implement this as if it was your plugs method - it will be called by the
418
base - your result needs processing before it can be returned
420
:return: list( tuple( node, plug ) )
421
if you have an existing node that the plug or shell you gave is from,
422
return it in the tuple, otherwise set it to a node with a shell that allows you
423
to handle it - the only time the node is required is when it is used in and with
424
the shells of the node's own shell class.
426
The node will be altered slightly to allow input of your facade to be reached
429
:note: a predicate is not supported as it must be applied on the converted
430
plugs, not on the ones you hand out"""
431
raise NotImplementedError( "Needs to be implemented in SubClass" )
433
#} END to be subclass implemented
436
def plugs( self, **kwargs ):
437
"""Calls `_getNodePlugs` method to ask you to actuallly return your
438
actual nodes and plugs or shells.
439
We prepare the returned value to assure we are being called in certain occasion,
440
which actually glues outside and inside worlds together """
441
# check args - currently only predicate is supported
442
predicate = kwargs.pop( 'predicate', lambda x: True )
444
if kwargs: # still args that we do not know ?
445
raise AssertionError( "Unhandled arguments found - update this method: %s" % kwargs.keys() )
450
if self._cachedOIPlugs:
452
for oiplug in self._cachedOIPlugs:
453
if predicate( oiplug ):
454
outresult.append( oiplug )
455
# END for each cached plug
457
# END for each cached plug
460
# GATHER PLUGS FROM SUBCLASS
461
##############################
462
yourResult = self._getNodePlugs( )
465
def toFacadePlug( node, plug ):
466
if isinstance( plug, OIFacadePlug )\
467
and self is plug.inode.shellcls.facadenode: # we can wrap other facade nodes as well
469
return OIFacadePlug( node, plug )
470
# END to facade plug helper
472
# PROCESS RETURNED PLUGS
474
for orignode, plug in yourResult:
475
oiplug = toFacadePlug( orignode, plug )
478
# Cache all plugs, ignoring the predicate
479
if self.caching_enabled:
480
self._cachedOIPlugs.append( oiplug )
484
# MODIFY NODE INSTANCE
485
##################################################
486
# Allowing us to get callbacks once the node is used inside of the internal
489
# ADD FACADE SHELL CLASS
490
############################
491
# This can also handle facaded facade nodes, as they have the type
492
# of _IOShell as shellcls, but no instance
493
if not isinstance( orignode.shellcls, _IOShell ):
494
classShellCls = orignode.shellcls
495
orignode.shellcls = _IOShell( classShellCls, self )
496
# END for each shell to reconnect
497
# END if we have to swap in our facadeIOShell
500
# update facade shell class ( inst ) cache so that it can map our internal
501
# plug to the io plug on the outside node
502
# cannot create weakref to tuple type unfortunately - use name instead
503
orignode.shellcls.iomap[ oiplug.iplug.name() ] = oiplug
506
# UPDATE CONNECTIONS ( per plug, not per node )
507
##########################
508
# update all connections with the new shells - they are required when
509
# walking the affects tree, as existing ones will be taken instead of
510
# our new shell then.
511
internalshell = orignode.toShell( oiplug.iplug )
512
all_shell_cons = internalshell.connections( 1, 1 ) # now we get old shells
514
# disconnect and reconnect with new
515
for edge in all_shell_cons:
516
nedge = list( ( None, None ) )
517
created_shell = False
519
for i,shell in enumerate( edge ):
521
# its enough to just have an io shell here, it just assures
523
# edges are always ordered start->end - we could be any of these
524
# thus we have to check before
525
if shell == internalshell and not isinstance( shell, _IOShell ) :
526
nedge[ i ] = shell.node.toShell( shell.plug )
528
# END for each shell in edge
531
edge[0].disconnect( edge[1] )
532
nedge[0].connect( nedge[1] )
533
# END new shell needs connection
534
# END for each edge to update
537
# ONLY AFTER EVERYTHING HAS BEEN UPDATED, WE MAY DROP IT
538
##########################################################
539
if not predicate( oiplug ):
542
finalres.append( oiplug )
544
# END for each orignode,plug in result
547
# the final result has everything nicely put back together, but
548
# it has been altered as well
551
def clearPlugCache( self ):
552
"""if a cache has been build as caching is enabled, this method clears
553
the cache forcing it to be updated on the next demand
555
:note: this could be more efficient by just deleting plugs that are
556
not required anymore, but probably this method can expect the whole
557
cache to be deleted right away ... so its fine"""
558
self._cachedOIPlugs = list()
561
class GraphNodeBase( FacadeNodeBase ):
562
"""A node wrapping a graph, allowing it to be nested within the node
563
All inputs and outputs on this node are purely virtual, thus they internally connect
564
to the wrapped graph.
566
:todo: tests deletion of graphnodes and see whether they are being garbage collected.
567
It should work with the new collector as it can handle cyclic references - these
568
strong cycles we have a lot in this structure. Weakrefs will not work for nested
569
facade nodes as they are tuples not allowing weak refs.
572
duplicate_wrapped_graph = True # an independent copy of the wrapped graph usually is required - duplication assures that ( or the caller )
573
allow_auto_plugs = True # if True, plugs can be found automatically by iterating nodes on the graph and using their plugs
574
ignore_failed_includes = False # if True, node will not raise if a plug to be included cannot be found
576
# list of node.plug strings ( like "node.inName" ) and/or node names ( like "node" )
577
# defining the plugs you would like to specifically include on the facade
578
# If just a name is given, the node name is assumed and all plugs on that node will be included
581
# same as include, but matching nodes/plugs will be excluded
585
def __init__( self, wrappedGraph, *args, **kwargs ):
586
""" Initialize the instance
587
:param wrappedGraph: graph we are wrapping"""
588
self.wgraph = wrappedGraph
589
if self.duplicate_wrapped_graph:
590
self.wgraph = self.wgraph.duplicate( )
592
FacadeNodeBase.__init__( self, *args, **kwargs )
594
def createInstance( self , **kwargs ):
595
"""Create a copy of self and return it"""
596
return self.__class__( self.wgraph ) # graph will be duplicated in the constructor
601
def _iterNodes( self ):
602
""":return: generator for nodes in our graph
603
:note: derived classes could override this to just return a filtered view on
605
return self.wgraph.iterNodes( )
610
def _addIncludeNodePlugs( self, outset ):
611
"""Add the plugs defined in include to the given output list"""
612
missingplugs = list()
613
nodes = self.wgraph.nodes()
614
nodenames = [ str( node ) for node in nodes ]
616
for nodeplugname in self.include:
617
nodename = plugname = None
619
# INCLUDE WHOLE NODE HANDLING
620
##############################
621
if nodeplugname.find( '.' ) == -1 :
622
nodename = nodeplugname
624
nodename, plugname = tuple( nodeplugname.split( "." ) )
625
# END wholenode check
628
######################
630
index = nodenames.index( nodename )
631
node = nodes[ index ]
633
missingplugs.append( nodeplugname )
640
outset.update( ( (node,plug) for plug in node.plugs() ) )
642
# find matching plugs
644
plug = getattr( node, plugname ).plug
645
except AttributeError:
646
missingplugs.append( nodeplugname )
648
# finally append the located plug
649
outset.add( ( node , plug ) )
651
# END whole node handling
652
# END for each nodeplug name
654
if not self.ignore_failed_includes and missingplugs:
655
msg = "%s: Could not find following include plugs: %s" % ( self, ",".join( missingplugs ) )
656
raise AssertionError( msg )
658
def _removeExcludedPlugs( self, outset ):
659
"""remove the plugs from our exclude list and modify the outset"""
664
excludeNameTuples = [ tuple( plugname.split( "." ) ) for plugname in self.exclude ]
665
for node,plug in outset:
666
for nodeplugname in self.exclude:
668
nodename = plugname = None
669
if nodeplugname.find( '.' ) == -1: # node mode
670
nodename = nodeplugname
672
nodename,plugname = nodeplugname.split( '.' ) # node plug mode
674
if nodename == str( node ) and ( not plugname or plugname == plug.name() ):
675
excludepairs.add( ( node,plug ) )
676
# END for each nodename.plugname to exclude
677
# END for each node,plug pair
679
# substract our pairs accordingly to modify the set
680
outset -= excludepairs
682
def _getNodePlugs( self ):
683
""":return: all plugs on nodes we wrap ( as node,plug tuple )"""
686
# get the included plugs
687
self._addIncludeNodePlugs( outset )
689
if self.allow_auto_plugs:
690
for node in self._iterNodes():
691
plugresult = node.plugs( )
692
outset.update( set( ( (node,plug) for plug in plugresult ) ) )
694
# END for node in nodes
695
# END allow auto plugs
697
# remove excluded plugs
698
self._removeExcludedPlugs( outset )
700
# the rest of the nitty gritty details, the base class will deal
707
class OIFacadePlug( tuple , iPlug ):
708
"""Facade Plugs are meant to be stored on instance level overriding the respective
709
class level plug descriptor.
710
If used directly, it will facade the internal affects relationships and just return
711
what really is affected on the facade node
713
Additionally they are associated to a node instance, and can thus be used to
714
find the original node once the plug is used in an OI facacde shell
716
Its a tuple as it will be more memory efficient that way. Additionally one
717
automatically has a proper hash and comparison if the same objects come together
721
#{ Object Overridden Methods
723
def __new__( cls, *args ):
724
"""Store only weakrefs, throw if we do not get 3 inputs
727
* arg[0] = internal node
728
* arg[1] = internal plug"""
730
if len( args ) != count:
731
raise AssertionError( "Invalid Argument count, should be %i, was %i" % ( count, len( args ) ) )
733
#return tuple.__new__( cls, ( weakref.ref( arg ) for arg in args ) )
734
return tuple.__new__( cls, args ) # NOTE: have to use string refs for recursive facade plugs
737
def __getattr__( self, attr ):
738
""" Allow easy attribute access
739
inode: the internal node
740
iplug: the internal plug
743
- Act as IOFacade returning additional information
745
- Act as original plug for attribute access
747
This will work as long as the method names are unique
754
# still here ? try to return a value on the original plug
755
return getattr( self.iplug, attr )
757
#} END object overridden methods
761
""" Get name of facade plug
763
:return: name of (internal) plug - must be a unique key, unique enough
764
to allow connections to several nodes of the same type"""
765
return "%s%s_%s" % ( self._fp_prefix, self.inode, self.iplug )
768
def _affectedList( self, direction ):
769
""" Get affected shells into the given direction
771
:return: list of all oiplugs looking in direction, if
772
plugtestfunc says: False, do not prune the given shell"""
773
these = lambda shell: shell.plug is self.iplug or not isinstance( shell, _IOShell ) or shell._getoiplug() is None
775
iterShells = self.inode.toShell( self.iplug ).iterShells( direction=direction, prune = these, visit_once=True )
776
outlist = [ shell._getoiplug() for shell in iterShells ]
780
def affects( self, otherplug ):
781
"""Affects relationships will be set on the original plug only"""
782
return self.iplug.affects( otherplug )
784
def affected( self ):
785
"""Walk the internal affects using an internal plugshell
787
:note: only output plugs can be affected - this is a rule followed throughout the system
788
:return: tuple containing affected plugs ( plugs that are affected by our value )"""
789
return self._affectedList( "down" )
791
def affectedBy( self ):
792
"""Walk the graph upwards and return all input plugs that are being facaded
793
:return: tuple containing plugs that affect us ( plugs affecting our value )"""
794
return self._affectedList( "up" )
796
def providesOutput( self ):
797
""":return: True if this is an output plug that can trigger computations """
798
return self.iplug.providesOutput( )
800
def providesInput( self ):
801
""":return: True if this is an input plug that will never cause computations"""
802
return self.iplug.providesInput( )