Package mrv :: Module dgfe
[hide private]
[frames] | no frames]

Source Code for Module mrv.dgfe

  1  # -*- coding: utf-8 -*- 
  2  """Contains nodes supporting facading within a dependency graph  - this can be used 
  3  for container tyoes or nodes containing their own subgraph even 
  4  """ 
  5  __docformat__ = "restructuredtext" 
  6   
  7  from networkx import DiGraph, NetworkXError 
  8  from collections import deque 
  9  import inspect 
 10  import weakref 
 11  from util import iDuplicatable 
 12   
 13  from dge import NodeBase 
 14  from dge import _PlugShell 
 15  from dge import iPlug 
 16  from dge import Attribute 
 17   
 18  __all__ = ("FacadeNodeBase", "GraphNodeBase", "OIFacadePlug") 
19 20 #{ Shells 21 22 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""" 26 27 @classmethod
28 - def createUnfacadeMethod( cls, funcname ):
29 def unfacadeMethod( self, *args, **kwargs ): 30 return getattr( self._toIShell(), funcname )( *args, **kwargs )
31 return unfacadeMethod
32 33 @classmethod
34 - def createFacadeMethod( cls, funcname ):
35 """in our case, connections just are handled by our own OI plug, staying 36 in the main graph""" 37 return list()
38 39 @classmethod
40 - def createMethod( cls,funcname, facadetype ):
41 method = None 42 if facadetype == "unfacade": 43 method = cls.createUnfacadeMethod( funcname ) 44 else: 45 method = cls.createFacadeMethod( funcname ) 46 47 if method: # could be none if we do not overwrite the method 48 method.__name__ = funcname 49 50 return method
51 52
53 - def __new__( metacls, name, bases, clsdict ):
54 unfacadelist = clsdict.get( '__unfacade__' ) 55 facadelist = clsdict.get( '__facade__' ) 56 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 ) 62 if method: 63 clsdict[ funcname ] = method 64 # END for each funcname in funcnamelist 65 # END for each type of functions 66 67 return type.__new__( metacls, name, bases, clsdict )
68
69 70 -class _IOShellMeta( _OIShellMeta ):
71 """Metaclass wrapping all unfacade attributes on the plugshell trying 72 to get an input connection """ 73 74 @classmethod
75 - def createUnfacadeMethod( cls,funcname ):
76 """:return: wrapper method for funcname """ 77 method = None 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( ) 89 if oshell.hasCache(): 90 return oshell.cache() 91 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 )
99 # END unfacade method 100 method = unfacadeMethod 101 # END funk type handling 102 return method 103 104 @classmethod
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 )
109 return facadeMethod 110
111 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 118 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 122 """ 123 # list all methods that should not be a facade to our facade node 124 __unfacade__ = [ 'set', 'get', 'clearCache', 'hasCache','setCache', 'cache' ] 125 126 # keep this list uptodate - otherwise a default shell will be used for the missing 127 # function 128 # TODO: parse the plugshell class itself to get the functions automatically 129 __facade__ = [ 'connect','disconnect','input', 'outputs','connections', 130 'iterShells' ] 131 132 __metaclass__ = _OIShellMeta 133
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]) ) 138 139 # NOTE deprecated in python 2.6 and without effect in our case 140 super( _OIShell, self ).__init__( *args )
141 142
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 )
149
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 )
154
155 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""" 160 161 __unfacade__ = [ 'get', 'clearCache' ] 162 163 # keep this list uptodate - otherwise a default shell will be used for the missing 164 # function 165 # TODO: parse the plugshell class itself to get the functions automatically 166 __facade__ = [ 'set','hasCache','setCache', 'cache', 167 'connect','disconnect','input','connections','outputs', 168 'iterShells' ] 169 170 __metaclass__ = _IOShellMeta 171
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 176 177 :param 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 180 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. 183 """ 184 # find whether we are in shell mode or in class mode - depending on the 185 # types of the args 186 # CLASS MODE 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
192 # END class mode 193 #else: 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 198
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 203 204 :note: the shells we create are default ones with some extra handlers 205 for exceptions""" 206 return self.__class__( *args )
207 208 #{ Helpers 209
210 - def _getoiplug( self ):
211 """:return: oiplug suitable for this shell or None""" 212 try: 213 # cannot use weak references, don't want to use strong references 214 return self.node.shellcls.iomap[ self.plug.name() ] 215 except KeyError: 216 # plug not on facadenode - this is fine as we get always called 217 pass 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() ] ) 222 223 return None
224
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 )
228
229 - def _getTopFacadeNodeShell( self ):
230 """Recursive method to find the first facade parent having an OI shell 231 232 :return: topmost facade node shell or None if we are not a managed plug""" 233 234 # otherwise we have found the topmost parent 235 return facadeNodeShell
236 237
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 243 we stay 'inside' 244 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 ) ) ) 249 250 # GET FACADE SHELL 251 #################### 252 # get the oiplug on our node 253 oiplug = self._getoiplug( ) 254 if not oiplug: 255 # plug not on facadenode, just ignore and return the original shell 256 return [ self._getOriginalShell( ) ] 257 # END if there is no cached oiplug 258 259 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 ) 265 266 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 276 277 278 outShells = list() 279 if shelltype == "input": 280 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 ) 287 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 290 # in rval[1] 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 295 296 # END aquire TL Input 297 298 # still here means no toplevel override 299 # TRY OUR LEVEL INPUT 300 inputShell = connectionShell.input( ) 301 302 if inputShell: 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 ) 309 else: 310 outShells.append( self._getOriginalShell( ) ) 311 312 # END outside INPUT shell handling 313 else: 314 outShells.extend( connectionShell.outputs( ) ) 315 316 # ADD 'INSIDE' ORIGINAL SHELL 317 # always allow our 'inside' level to get informed as well 318 outShells.append( self._getOriginalShell( ) ) 319 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 326 327 return outShells
328
329 # } END helpers 330 331 332 # END shells 333 334 335 #{ Nodes 336 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. 340 341 Using a non-default shell it is possibly to guide all calls through to the 342 virtual PlugShell. 343 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. 347 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 350 of it. 351 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. 355 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 361 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 365 366 #{ Configuration 367 caching_enabled = True # if true, the facade may cache plugs once queried 368 #} END configuration 369
370 - def __init__( self, *args, **kwargs ):
371 """ Initialize the instance""" 372 self._cachedOIPlugs = list() # simple list of names 373 NodeBase.__init__( self, *args, **kwargs )
374 375
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 379 380 :note: to make this work, you should always name the plug names equal to their 381 class attribute""" 382 check_ambigious = not attr.startswith( OIFacadePlug._fp_prefix ) # non long names are not garantueed to be unique 383 384 candidates = list() 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: 389 return shell 390 candidates.append( shell ) 391 # END if plugname matches 392 # END for each of our plugs 393 394 if not candidates: 395 raise AttributeError( "Attribute %s does not exist on %s" % (attr,self) ) 396 397 if len( candidates ) == 1: 398 return candidates[0] 399 400 # must be more ... 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 ) )
402 403 404
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""" 411 pass
412 413 414 #{ To be Subclass-Implemented 415
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 419 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. 425 426 The node will be altered slightly to allow input of your facade to be reached 427 from the inside 428 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" )
432 433 #} END to be subclass implemented 434 435
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 ) 443 444 if kwargs: # still args that we do not know ? 445 raise AssertionError( "Unhandled arguments found - update this method: %s" % kwargs.keys() ) 446 447 448 # HAND OUT CACHE 449 ################# 450 if self._cachedOIPlugs: 451 outresult = list() 452 for oiplug in self._cachedOIPlugs: 453 if predicate( oiplug ): 454 outresult.append( oiplug ) 455 # END for each cached plug 456 return outresult 457 # END for each cached plug 458 459 460 # GATHER PLUGS FROM SUBCLASS 461 ############################## 462 yourResult = self._getNodePlugs( ) 463 464 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 468 return plug 469 return OIFacadePlug( node, plug )
470 # END to facade plug helper 471 472 # PROCESS RETURNED PLUGS 473 finalres = list() 474 for orignode, plug in yourResult: 475 oiplug = toFacadePlug( orignode, plug ) 476 477 478 # Cache all plugs, ignoring the predicate 479 if self.caching_enabled: 480 self._cachedOIPlugs.append( oiplug ) 481 # END cache update 482 483 484 # MODIFY NODE INSTANCE 485 ################################################## 486 # Allowing us to get callbacks once the node is used inside of the internal 487 # structures 488 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 498 499 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 504 505 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 513 514 # disconnect and reconnect with new 515 for edge in all_shell_cons: 516 nedge = list( ( None, None ) ) 517 created_shell = False 518 519 for i,shell in enumerate( edge ): 520 nedge[ i ] = shell 521 # its enough to just have an io shell here, it just assures 522 # our callbacks 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 ) 527 created_shell = True 528 # END for each shell in edge 529 530 if created_shell: 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 535 536 537 # ONLY AFTER EVERYTHING HAS BEEN UPDATED, WE MAY DROP IT 538 ########################################################## 539 if not predicate( oiplug ): 540 continue 541 542 finalres.append( oiplug ) 543 544 # END for each orignode,plug in result 545 546 547 # the final result has everything nicely put back together, but 548 # it has been altered as well 549 return finalres
550
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 554 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()
559
560 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. 565 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. 570 """ 571 #{ Configuration 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 575 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 579 include = list() 580 581 # same as include, but matching nodes/plugs will be excluded 582 exclude = list() 583 #}END configuration 584
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( ) 591 592 FacadeNodeBase.__init__( self, *args, **kwargs )
593
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
597 598 599 #{ Base Methods 600
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 604 their nodes""" 605 return self.wgraph.iterNodes( )
606 607 #} END base 608 609
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 ] 615 616 for nodeplugname in self.include: 617 nodename = plugname = None 618 619 # INCLUDE WHOLE NODE HANDLING 620 ############################## 621 if nodeplugname.find( '.' ) == -1 : 622 nodename = nodeplugname 623 else: 624 nodename, plugname = tuple( nodeplugname.split( "." ) ) 625 # END wholenode check 626 627 # FIND NODE INSTANCE 628 ###################### 629 try: 630 index = nodenames.index( nodename ) 631 node = nodes[ index ] 632 except ValueError: 633 missingplugs.append( nodeplugname ) 634 continue 635 636 637 # ADD INCLUDE PLUGS 638 ################### 639 if not plugname: 640 outset.update( ( (node,plug) for plug in node.plugs() ) ) 641 else: 642 # find matching plugs 643 try: 644 plug = getattr( node, plugname ).plug 645 except AttributeError: 646 missingplugs.append( nodeplugname ) 647 else: 648 # finally append the located plug 649 outset.add( ( node , plug ) ) 650 continue 651 # END whole node handling 652 # END for each nodeplug name 653 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 )
657
658 - def _removeExcludedPlugs( self, outset ):
659 """remove the plugs from our exclude list and modify the outset""" 660 if not self.exclude: 661 return 662 663 excludepairs = set() 664 excludeNameTuples = [ tuple( plugname.split( "." ) ) for plugname in self.exclude ] 665 for node,plug in outset: 666 for nodeplugname in self.exclude: 667 668 nodename = plugname = None 669 if nodeplugname.find( '.' ) == -1: # node mode 670 nodename = nodeplugname 671 else: 672 nodename,plugname = nodeplugname.split( '.' ) # node plug mode 673 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 678 679 # substract our pairs accordingly to modify the set 680 outset -= excludepairs
681
682 - def _getNodePlugs( self ):
683 """:return: all plugs on nodes we wrap ( as node,plug tuple )""" 684 outset = set() 685 686 # get the included plugs 687 self._addIncludeNodePlugs( outset ) 688 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 ) ) ) 693 # END update lut map 694 # END for node in nodes 695 # END allow auto plugs 696 697 # remove excluded plugs 698 self._removeExcludedPlugs( outset ) 699 700 # the rest of the nitty gritty details, the base class will deal 701 return outset
702
703 #} END nodes 704 705 706 #{ Plugs 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 712 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 715 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 718 """ 719 _fp_prefix = "_FP_" 720 721 #{ Object Overridden Methods 722
723 - def __new__( cls, *args ):
724 """Store only weakrefs, throw if we do not get 3 inputs 725 726 :param args: 727 * arg[0] = internal node 728 * arg[1] = internal plug""" 729 count = 2 730 if len( args ) != count: 731 raise AssertionError( "Invalid Argument count, should be %i, was %i" % ( count, len( args ) ) ) 732 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
735 736
737 - def __getattr__( self, attr ):
738 """ Allow easy attribute access 739 inode: the internal node 740 iplug: the internal plug 741 742 Thus we must: 743 - Act as IOFacade returning additional information 744 745 - Act as original plug for attribute access 746 747 This will work as long as the method names are unique 748 """ 749 if attr == 'inode': 750 return self[0] 751 if attr == 'iplug': 752 return self[1] 753 754 # still here ? try to return a value on the original plug 755 return getattr( self.iplug, attr )
756 757 #} END object overridden methods 758 759
760 - def name( self ):
761 """ Get name of facade plug 762 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 )
766 767
768 - def _affectedList( self, direction ):
769 """ Get affected shells into the given direction 770 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 774 775 iterShells = self.inode.toShell( self.iplug ).iterShells( direction=direction, prune = these, visit_once=True ) 776 outlist = [ shell._getoiplug() for shell in iterShells ] 777 778 return outlist
779
780 - def affects( self, otherplug ):
781 """Affects relationships will be set on the original plug only""" 782 return self.iplug.affects( otherplug )
783
784 - def affected( self ):
785 """Walk the internal affects using an internal plugshell 786 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" )
790
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" )
795
796 - def providesOutput( self ):
797 """:return: True if this is an output plug that can trigger computations """ 798 return self.iplug.providesOutput( )
799
800 - def providesInput( self ):
801 """:return: True if this is an input plug that will never cause computations""" 802 return self.iplug.providesInput( )
803 804 805 #} END plugs 806