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

Source Code for Module mrv.util

  1  # -*- coding: utf-8 -*- 
  2  """All kinds of utility methods and classes that are used in more than one modules """ 
  3  import networkx as nx 
  4  from collections import deque as Deque 
  5  import weakref 
  6  import inspect 
  7  import itertools 
  8  from interface import iDuplicatable 
  9   
 10  from path import make_path 
 11   
 12  import os 
 13  import logging 
 14  log = logging.getLogger("mrv.maya.ui.util") 
 15   
 16  __docformat__ = "restructuredtext" 
 17  __all__ = ("decodeString", "decodeStringOrList", "capitalize", "uncapitalize",  
 18          "pythonIndex", "copyClsMembers", "packageClasses", "iterNetworkxGraph",  
 19             "Call", "CallAdv", "WeakInstFunction", "Event", "EventSender",  
 20             "InterfaceMaster", "Singleton", "CallOnDeletion",  
 21             "DAGTree", "PipeSeparatedFile", "MetaCopyClsMembers", "And", "Or",  
 22             "list_submodules", "list_subpackages")  
23 24 25 -def decodeString(valuestr):
26 """ :return: int,float or str from string valuestr - a string that encodes a 27 numeric value or a string 28 29 :raise TypeError: if the type could not be determined""" 30 # put this check here intentionally - want to allow 31 if not isinstance(valuestr, basestring): 32 raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr) 33 34 types = (long, float) 35 for numtype in types: 36 try: 37 val = numtype(valuestr) 38 39 # truncated value ? 40 if val != float(valuestr): 41 continue 42 43 return val 44 except (ValueError,TypeError): 45 continue 46 # END for each numeric type 47 48 # its just a string and not a numeric type 49 return valuestr
50
51 -def decodeStringOrList(valuestrOrList):
52 """ 53 :return: as `decodeString`, but returns a list of appropriate values if 54 the input argument is a list or tuple type""" 55 if isinstance(valuestrOrList , (list, tuple)): 56 return [decodeString(valuestr) for valuestr in valuestrOrList] 57 58 return decodeString(valuestrOrList)
59
60 -def capitalize(s):
61 """:return: s with first letter capitalized""" 62 return s[0].upper() + s[1:]
63
64 -def uncapitalize(s, preserveAcronymns=False):
65 """:return: ``s`` with first letter lower case 66 :param preserveAcronymns: enabled ensures that 'NTSC' does not become 'nTSC' 67 :note: from pymel 68 """ 69 try: 70 if preserveAcronymns and s[0:2].isupper(): 71 return s 72 except IndexError: pass 73 74 return s[0].lower() + s[1:]
75
76 -def pythonIndex(index, length):
77 """Compute the actual index based on the given index and array length, thus 78 -1 will result in the last array element's index""" 79 if index > -1: return index 80 return length + index # yes, length be better 1 or more ;)
81
82 -def copyClsMembers(sourcecls, destcls, overwritePrefix = None, forbiddenMembers = list(), copyNamespaceGlobally=None):
83 """Copy the members or sourcecls to destcls while ignoring member names in forbiddenMembers 84 It will only copy mebers of this class, not its base classes 85 86 :param sourcecls: class whose members should be copied 87 :param destcls: class to receive members from sourcecls 88 :param overwritePrefix: if None, existing members on destcls will not be overwritten, if string, 89 the original method will be stored in a name like prefix+originalname (allowing you to access the 90 original method lateron) 91 :param copyNamespaceGlobally: if not None, the variable contains the name of the namespace as string 92 whose methods should also be copied into the global namespace, possibly overwriting existing ones. 93 For instance, 'nsmethod' will be available as obj.nsmethod and as obj.method if the namespace value was 'ns. 94 The forbiddenMembers list is applied to the original as well as the global name 95 :note: this can be useful if you cannot inherit from a class directly because you would get 96 method resolution order problems 97 :note: see also the `MetaCopyClsMembers` meta class""" 98 for orig_name,member in sourcecls.__dict__.iteritems(): 99 names = [orig_name] 100 if copyNamespaceGlobally is not None and orig_name.startswith(copyNamespaceGlobally): 101 names.append(orig_name[len(copyNamespaceGlobally):]) # truncate namespace 102 # END handle namespace removal 103 104 for name in names: 105 if name in forbiddenMembers: 106 continue 107 try: 108 # store original - overwritten members must still be able to access it 109 if hasattr(destcls, name): 110 if not overwritePrefix: 111 continue 112 morig = getattr(destcls, name) 113 type.__setattr__(destcls, overwritePrefix+name, morig) 114 type.__setattr__(destcls, name, member) 115 except TypeError: 116 pass
117 # END for each name
118 # END for each memebr in sourcecls 119 120 -def packageClasses(importBase, packageFile, predicate = lambda x: True):
121 """ 122 :return: all classes of modules of the given package file that additionally 123 match given predicate 124 :param importBase: longest import base path whose submodules contain the classes to import 125 :param packageFile: the filepath to the package, as given in your __file__ variables 126 :param predicate: receives the class and returns True if it is a class you are looking for""" 127 from glob import glob 128 import os 129 130 packageDir = os.path.dirname(packageFile) 131 132 # get all submodules 133 basenameNoExt = lambda n: os.path.splitext(os.path.split(n)[1])[0] 134 pymodules = itertools.chain(glob(os.path.join(packageDir, "*.py")), glob(os.path.join(packageDir, "*.pyc"))) 135 pymodules = [basenameNoExt(m) for m in pymodules 136 if not os.path.basename(m).startswith('_')] 137 138 outclasses = [] 139 classAndCustom = lambda x: inspect.isclass(x) and predicate(x) 140 for modulename in pymodules: 141 modobj = __import__("%s.%s" % (importBase, modulename), globals(), locals(), ['']) 142 for name,obj in inspect.getmembers(modobj, predicate = classAndCustom): 143 outclasses.append(obj) 144 # import the modules 145 return outclasses
146
147 -def iterNetworkxGraph(graph, startItem, direction = 0, prune = lambda i,g: False, 148 stop = lambda i,g: False, depth = -1, branch_first=True, 149 visit_once = True, ignore_startitem=1):
150 """:return: iterator yielding pairs of depth, item 151 :param direction: specifies search direction, either : 152 0 = items being successors of startItem 153 1 = items being predecessors of startItem 154 :param prune: return True if item d,i in graph g should be pruned from result. 155 d is the depth of item i 156 :param stop: return True if item d,i in graph g, d is the depth of item i 157 stop the search in that direction. It will not be returned. 158 :param depth: define at which level the iteration should not go deeper 159 if -1, there is no limit 160 if 0, you would only get startitem. 161 i.e. if 1, you would only get the startitem and the first level of predessessors/successors 162 :param branch_first: if True, items will be returned branch first, otherwise depth first 163 :param visit_once: if True, items will only be returned once, although they might be encountered 164 several times 165 :param ignore_startitem: if True, the startItem will be ignored and automatically pruned from 166 the result 167 :note: this is an adjusted version of `dge.iterShells`""" 168 visited = set() 169 stack = Deque() 170 stack.append((0 , startItem)) # startitem is always depth level 0 171 172 def addToStack(stack, lst, branch_first, dpth): 173 if branch_first: 174 reviter = ((dpth , lst[i]) for i in range(len(lst)-1,-1,-1)) 175 stack.extendleft(reviter) 176 else: 177 stack.extend((dpth,item) for item in lst)
178 # END addToStack local method 179 180 # adjust function to define direction 181 directionfunc = graph.successors 182 if direction == 1: 183 directionfunc = graph.predecessors 184 185 while stack: 186 d, item = stack.pop() # depth of item, item 187 188 if item in visited: 189 continue 190 191 if visit_once: 192 visited.add(item) 193 194 oitem = (d, item) 195 if stop(oitem, graph): 196 continue 197 198 skipStartItem = ignore_startitem and (item == startItem) 199 if not skipStartItem and not prune(oitem, graph): 200 yield oitem 201 202 # only continue to next level if this is appropriate ! 203 nd = d + 1 204 if depth > -1 and nd > depth: 205 continue 206 207 addToStack(stack, directionfunc(item), branch_first, nd) 208 # END for each item on work stack
209 210 211 212 -class Call(object):
213 """Call object encapsulating any code, thus providing a simple facade for it 214 :note: derive from it if a more complex call is required""" 215 __slots__ = ("func", "args", "kwargs") 216
217 - def __init__(self, func, *args,**kwargs):
218 """Initialize object with function to call once this object is called""" 219 self.func = func 220 self.args = args 221 self.kwargs = kwargs
222
223 - def __call__(self, *args, **kwargs):
224 """Execute the stored function on call 225 :note: having ``args`` and ``kwargs`` set makes it more versatile""" 226 return self.func(*self.args, **self.kwargs)
227
228 229 -class CallAdv(Call):
230 """Advanced call class providing additional options: 231 232 merge_args: 233 if True, default True, incoming arguments will be prepended before the static ones 234 merge_kwargs: 235 if True, default True, incoming kwargs will be merged into the static ones """ 236 __slots__ = ("merge_args", "merge_kwargs") 237
238 - def __init__(self, func, *args, **kwargs):
239 self.merge_args = kwargs.pop("merge_args", True) 240 self.merge_kwargs = kwargs.pop("merge_kwargs", True) 241 242 super(CallAdv, self).__init__(func, *args, **kwargs)
243
244 - def __call__(self, *inargs, **inkwargs):
245 """Call with merge support""" 246 args = self.args 247 if self.merge_args: 248 args = list(inargs) 249 args.extend(self.args) 250 251 if self.merge_kwargs: 252 self.kwargs.update(inkwargs) 253 254 return self.func(*args, **self.kwargs)
255
256 257 -class WeakInstFunction(object):
258 """Create a proper weak instance to an instance function by weakly binding 259 the instance, not the bound function object. 260 When called, the weakreferenced instance pointer will be retrieved, if possible, 261 to finally make the call. If it could not be retrieved, the call 262 will do nothing.""" 263 __slots__ = ("_weakinst", "_clsfunc") 264
265 - def __init__(self, instancefunction):
266 self._weakinst = weakref.ref(instancefunction.im_self) 267 self._clsfunc = instancefunction.im_func
268
269 - def __eq__(self, other):
270 return hash(self) == hash(other)
271
272 - def __hash__(self):
273 return hash((self._clsfunc, self._weakinst()))
274
275 - def __call__(self, *args, **kwargs):
276 """ 277 :raise LookupError: if the instance referred to by the instance method 278 does not exist anymore""" 279 inst = self._weakinst() 280 if inst is None: # went out of scope 281 raise LookupError("Instance for call to %s has been deleted as it is weakly bound" % self._clsfunc.__name__) 282 283 return self._clsfunc(inst, *args, **kwargs)
284
285 286 -class Event(object):
287 """Descriptor allowing to easily setup callbacks for classes derived from 288 EventSender""" 289 _inst_event_attr = '__events__' # dict with event -> set() relation 290 291 #{ Configuration 292 # if true, functions will be weak-referenced - its useful if you use instance 293 # variables as callbacks 294 use_weakref = True 295 296 # if True, callback handlers throwing an exception will immediately be 297 # removed from the callback list 298 remove_on_error = False 299 300 301 # If not None, this value overrides the corresponding value on the EventSender class 302 sender_as_argument = None 303 #} END configuration 304 305 # internally used to keep track of the current sender. Is None if no event 306 # is being fired 307 _curSender = None 308
309 - def __init__(self, **kwargs):
310 """ 311 :param kwargs: 312 * weak: if True, default class configuration use_weakref, weak 313 references will be created for event handlers, if False it will be strong 314 references 315 * remove_failed: if True, defailt False, failed callback handlers 316 will be removed silently 317 * sender_as_argument - see class member""" 318 self.use_weakref = kwargs.get("weak", self.__class__.use_weakref) 319 self.remove_on_error = kwargs.get("remove_failed", self.__class__.remove_on_error) 320 self.sender_as_argument = kwargs.get("sender_as_argument", self.__class__.sender_as_argument) 321 self._last_inst_ref = None
322
323 - def _func_to_key(self, eventfunc):
324 """ 325 :return: an eventfunction suitable to be used as key in our instance 326 event set""" 327 if self.use_weakref: 328 if inspect.ismethod(eventfunc): 329 eventfunc = WeakInstFunction(eventfunc) 330 else: 331 eventfunc = weakref.ref(eventfunc) 332 # END instance function special handling 333 # END if use weak ref 334 return eventfunc
335
336 - def _key_to_func(self, eventkey):
337 """:return: event function from the given eventkey as stored in our events set. 338 :note: this is required as the event might be weakreffed or not""" 339 if self.use_weakref: 340 if isinstance(eventkey, WeakInstFunction): 341 return eventkey 342 else: 343 return eventkey() 344 # END instance method handling 345 # END weak ref handling 346 return eventkey
347
348 - def _get_last_instance(self):
349 """:return: The last instance that retrieved us""" 350 if self._last_inst_ref is None or self._last_inst_ref() is None: 351 raise TypeError("Cannot send events through class descriptor") 352 return self._last_inst_ref()
353
354 - def __set__(self, inst, eventfunc):
355 """Set a new event to our object""" 356 self._getFunctionSet(inst).add(self._func_to_key(eventfunc))
357
358 - def __get__(self, inst, cls = None):
359 """Always return self, but keep the instance in case 360 we someone wants to send an event.""" 361 if inst is not None: 362 inst = weakref.ref(inst) 363 # END handle instance 364 self._last_inst_ref = inst 365 return self
366
367 - def _getFunctionSet(self, inst):
368 """:return: function set of the given instance containing functions of our event""" 369 if not hasattr(inst, self._inst_event_attr): 370 setattr(inst, self._inst_event_attr, dict()) 371 # END initialize set 372 373 ed = getattr(inst, self._inst_event_attr) 374 try: 375 return ed[self] 376 except KeyError: 377 return ed.setdefault(self, set())
378 # END handle self -> set relation 379 #{ Interface 380
381 - def send(self, *args, **kwargs):
382 """Send our event using the given args 383 384 :note: if an event listener is weak referenced and goes out of scope 385 :note: will catch all event exceptions trown by the methods called 386 :return: False if at least one event call threw an exception, true otherwise""" 387 inst = self._get_last_instance() 388 callbackset = self._getFunctionSet(inst) 389 success = True 390 failed_callbacks = list() 391 392 sas = inst.sender_as_argument 393 if self.sender_as_argument is not None: 394 sas = self.sender_as_argument 395 # END event override 396 397 # keep the sender 398 Event._curSender = inst 399 400 # copy the set, allowing callbacks to remove themselves during the callback. 401 # Otherwise we have a size change during the iteration 402 for function in callbackset.copy(): 403 try: 404 func = self._key_to_func(function) 405 if func is None: 406 log.warn("Listener for callback of %s was not available anymore" % self) 407 failed_callbacks.append(function) 408 continue 409 # END handle no-func 410 411 try: 412 if sas: 413 func(inst, *args, **kwargs) 414 else: 415 func(*args, **kwargs) 416 except LookupError, e: 417 # thrown if self in instance methods went out of scope 418 if inst.reraise_on_error: 419 raise 420 log.error(str(e)) 421 failed_callbacks.append(function) 422 # END sendder as argument 423 except Exception, e : 424 if self.remove_on_error: 425 failed_callbacks.append(function) 426 427 if inst.reraise_on_error: 428 raise 429 log.error(str(e)) 430 success = False 431 # END for each registered event 432 433 # remove failed listeners 434 for function in failed_callbacks: 435 callbackset.remove(function) 436 437 Event._curSender = None 438 return success
439 440 # Alias, to make event sending even easier 441 __call__ = send 442
443 - def remove(self, eventfunc):
444 """remove the given function from this event 445 :note: will not raise if eventfunc does not exist""" 446 inst = self._get_last_instance() 447 eventfunc = self._func_to_key(eventfunc) 448 try: 449 self._getFunctionSet(inst).remove(eventfunc) 450 except KeyError: 451 pass
452
453 - def duplicate(self):
454 inst = self.__class__("") 455 inst._name = self._name 456 inst.use_weakref = self.use_weakref 457 inst.remove_on_error = self.remove_on_error 458 inst.sender_as_argument = self.sender_as_argument 459 return inst
460
461 #} END interface 462 463 # END event class 464 465 -class EventSender(object):
466 """Base class for all classes that want to provide a common callback interface 467 to supply event information to clients. 468 469 **Usage**: 470 Derive from this class and define your callbacks like: 471 472 >>> event = Event() 473 >>> # Call it using 474 >>> self.event.send([*args][,**kwargs]]) 475 476 >>> # Users register using 477 >>> yourinstance.event = callable 478 479 >>> # and deregister using 480 >>> yourinstance.event.remove(callable) 481 482 :note: if use_weakref is True, we will weakref the eventfunction, and deal 483 properly with instance methods which would go out of scope immediatly otherwise 484 485 :note: using weak-references to ensure one does not keep objects alive, 486 see `Event.use_weakref`""" 487 __slots__ = tuple() 488 489 #{ Configuration 490 # if True, the sender, thus self of an instance of this class, will be put 491 # as first arguments to functions when called for a specific event 492 sender_as_argument = False 493 494 # if True, exceptions thrown when sending events will be reraised immediately 495 # and may stop execution of the event sender as well 496 reraise_on_error = False 497 #} END configuration 498 499 @classmethod
500 - def listEventNames(cls):
501 """:return: list of event ids that exist on our class""" 502 return [name for name,member in inspect.getmembers(cls, lambda m: isinstance(m, Event))]
503
504 - def clearAllEvents(self):
505 """Remove all event receivers for all events registered in this instance. 506 507 :note: This usually doesn't need to be called directly, but might be useful 508 in conjunction with other system that do not release your strongly bound 509 instance""" 510 # call remove once for each registered method to properly deregister them 511 for en in self.listEventNames(): 512 event = getattr(self, en) 513 for key in event._getFunctionSet(self).copy(): 514 event.remove(event._key_to_func(key))
515 # END for each function key 516 # END for each event whose methods are to clear 517
518 - def sender(self):
519 """:return: instance which sent the event you are currently processing 520 :raise ValueError: if no event is currently in progress""" 521 if Event._curSender is None: 522 raise ValueError("Cannot return sender as no event is being sent") 523 return Event._curSender
524
525 526 -class InterfaceMaster(iDuplicatable):
527 """Base class making the derived class an interface provider, allowing interfaces 528 to be set, queried and used including build-in use""" 529 __slots__ = ("_idict",) 530 #{ Configuration 531 im_provide_on_instance = True # if true, interfaces are available directly through the class using descriptors 532 #} END configuration 533 534 #{ Helper Classes
535 - class InterfaceDescriptor(object):
536 """Descriptor handing out interfaces from our interface dict 537 They allow access to interfaces directly through the InterfaceMaster without calling 538 extra functions""" 539
540 - def __init__(self, interfacename):
541 self.iname = interfacename # keep name of our interface
542
543 - def __get__(self, inst, cls = None):
544 # allow our instance to be manipulated if accessed through the class 545 if inst is None: 546 return self 547 548 try: 549 return inst.interface(self.iname) 550 except KeyError: 551 raise AttributeError("Interface %s does not exist" % self.iname)
552
553 - def __set__(self, value):
554 raise ValueError("Cannot set interfaces through the instance - use the setInterface method instead")
555 556
557 - class _InterfaceHandler(object):
558 """Utility class passing all calls to the stored InterfaceBase, updating the 559 internal caller-id"""
560 - def __init__(self, ibase):
561 self.__ibase = ibase 562 self.__callerid = ibase._num_callers 563 ibase._num_callers += 1 564 565 ibase._current_caller_id = self.__callerid # assure the callback finds the right one 566 ibase.givenToCaller()
567
568 - def __getattr__(self, attr):
569 self.__ibase._current_caller_id = self.__callerid # set our caller 570 return getattr(self.__ibase, attr)
571
572 - def __del__(self):
573 self.__ibase.aboutToRemoveFromCaller() 574 self.__ibase._num_callers -= 1 575 self.__ibase._current_caller_id = -1
576 577
578 - class InterfaceBase(object):
579 """If your interface class is derived from this base, you get access to 580 access to call to the number of your current caller. 581 582 :note: You can register an InterfaceBase with several InterfaceMasters and 583 share the caller count respectively""" 584 __slots__ = ("_current_caller_id", "_num_callers")
585 - def __init__(self):
586 self._current_caller_id = -1 # id of the caller currently operating on us 587 self._num_callers = 0 # the amount of possible callers, ids range from 0 to (num_callers-1)
588
589 - def numCallers(self):
590 """:return: number possible callers""" 591 return self._num_callers
592
593 - def callerId(self):
594 """Return the number of the caller that called your interface method 595 596 :note: the return value of this method is undefined if called if the 597 method has been called by someone not being an official caller (like yourself)""" 598 return self._current_caller_id
599
600 - def givenToCaller(self):
601 """Called once our interface has been given to a new caller. 602 The caller has not made a call yet, but its id can be queried""" 603 pass
604
605 - def aboutToRemoveFromCaller(self):
606 """Called once our interface is about to be removed from the current 607 caller - you will not receive a call from it anymore """ 608 pass
609 610 #} END helper classes 611 612 #{ Object Overrides
613 - def __init__(self):
614 """Initialize the interface base with some tracking variables""" 615 self._idict = dict() # keep interfacename->interfaceinstance relations
616 617 #} END object overrides 618
619 - def copyFrom(self, other, *args, **kwargs):
620 """Copy all interface from other to self, use they duplciate method if 621 possibly """ 622 for ifname, ifinst in other._idict.iteritems(): 623 myinst = ifinst 624 if hasattr(ifinst, "duplicate"): 625 myinst = ifinst.duplicate() 626 627 self.setInterface(ifname, myinst)
628 # END for each interface in other 629 630 631 #{ Interface
632 - def setInterface(self, interfaceName, interfaceInstance):
633 """Set the given interfaceInstance to be handed out once an interface 634 with interfaceName is requested from the provider base 635 636 :param interfaceName: should start with i..., i.e. names would be iInterface 637 The name can be used to refer to the interface later on 638 :param interfaceInstance: instance to be handed out once an interface with the 639 given name is requested by the InterfaceMaster or None 640 if None, the interface will effectively be deleted 641 :raise ValueError: if given InterfaceBase has a master already """ 642 if interfaceInstance is None: # delete interface ? 643 # delete from dict 644 try: 645 del(self._idict[interfaceName]) 646 except KeyError: 647 pass 648 649 # delete class descriptor 650 if self.im_provide_on_instance: 651 try: 652 delattr(self.__class__, interfaceName) 653 except AttributeError: 654 pass 655 # END del on class 656 657 # END interface deleting 658 else: 659 self._idict[interfaceName] = interfaceInstance 660 661 # set on class ? 662 if self.im_provide_on_instance: 663 setattr(self.__class__, interfaceName, self.InterfaceDescriptor(interfaceName))
664 665 # provide class variables ? 666
667 - def interface(self, interfaceName):
668 """:return: an interface registered with interfaceName 669 :raise ValueError: if no such interface exists""" 670 try: 671 iinst = self._idict[interfaceName] 672 673 # return wrapper if we can, otherwise just 674 if isinstance(iinst, self.InterfaceBase): 675 return self._InterfaceHandler(iinst) 676 else: 677 return iinst 678 except KeyError: 679 raise ValueError("Interface %s does not exist" % interfaceName)
680
681 - def listInterfaces(self):
682 """:return: list of names indicating interfaces available at our InterfaceMaster""" 683 return self._idict.keys()
684
685 #} END interface 686 687 688 -class Singleton(object) :
689 """ Singleton classes can be derived from this class, 690 you can derive from other classes as long as Singleton comes first (and class doesn't override __new__) """
691 - def __new__(cls, *p, **k):
692 # explicitly query the classes dict to allow subclassing of singleton types. 693 # Querying with hasattr would follow the inheritance graph 694 if '_the_instance' not in cls.__dict__: 695 cls._the_instance = super(Singleton, cls).__new__(cls) 696 return cls._the_instance
697
698 699 -class CallOnDeletion(object):
700 """Call the given callable object once this object is being deleted 701 Its usefull if you want to assure certain code to run once the parent scope 702 of this object looses focus""" 703 __slots__ = "callableobj"
704 - def __init__(self, callableobj):
706
707 - def __del__(self):
708 if self.callableobj is not None: 709 self.callableobj()
710
711 712 -class DAGTree(nx.DiGraph):
713 """Adds utility functions to DirectedTree allowing to handle a directed tree like a dag 714 :note: currently this tree does not support instancing 715 :todo: add instancing support""" 716
717 - def children(self, n):
718 """ :return: list of children of given node n """ 719 return list(self.children_iter(n))
720
721 - def children_iter(self, n):
722 """ :return: iterator with children of given node n""" 723 return (e[1] for e in self.out_edges_iter(n))
724
725 - def parent(self, n):
726 """:return: parent of node n 727 :note: currently there is only one parent, as instancing is not supported yet""" 728 for parent in self.predecessors_iter(n): 729 return parent 730 return None
731
732 - def parent_iter(self, n):
733 """:return: iterator returning all parents of node n""" 734 while True: 735 p = self.parent(n) 736 if p is None: 737 raise StopIteration() 738 yield p 739 n = p
740
741 - def get_root(self, startnode = None):
742 """:return: the root node of this dag tree 743 :param startnode: if None, the first node will be used to get the root from 744 (good for single rooted dags), otherwise this node will be used to get the root from 745 - thus it must exist in the dag tree""" 746 if startnode is None: 747 startnode = self.nodes_iter().next() 748 749 root = None 750 for parent in self.parent_iter(startnode): 751 root = parent 752 753 return root
754
755 - def to_hierarchy_file(self, root, output_path):
756 """Write ourselves in hierarchy file format to the given output_path. 757 758 :param root: The root of the written file, nodes above it will not be serialized. 759 :note: Directories are expected to exist 760 :raise ValueError: If an node's string representation contains a newline or 761 starts with a tab 762 :note: works best with strings as nodes, which may not contain newlines""" 763 fp = open(output_path, "wb") 764 for depth, item in iterNetworkxGraph(self, root, branch_first=False, ignore_startitem=False): 765 itemstr = str(item) 766 if itemstr.startswith("\t") or "\n" in itemstr: 767 raise ValueError("Item %r contained characters unsupported by the hierarchy file format") 768 # END handle serialization 769 fp.write("%s%s\n" % ("\t"*depth, itemstr)) 770 # END for each item 771 fp.close()
772
773 774 -class PipeSeparatedFile(object):
775 """Read and write simple pipe separated files. 776 777 The number of column must remain the same per line 778 **Format**: 779 780 val11 | val2 | valn 781 ... 782 """ 783 kSeparator = '|' 784 __slots__ = ("_fileobj", "_columncount", "_formatstr") 785
786 - def __init__(self, fileobj):
787 """Initialize the instance 788 789 :param fileobj: fileobject where new lines will be written to or read from 790 It must already be opened for reading and/or writing respectively""" 791 self._fileobj = fileobj 792 self._columncount = None
793
794 - def beginReading(self):
795 """Start reading the file"""
796
797 - def readColumnLine(self):
798 """Generator reading one line after another, returning the stripped columns 799 800 :return: tuple of stripped column strings 801 :raise ValueError: if the column count changes between the lines""" 802 for line in self._fileobj: 803 if not len(line.strip()): 804 continue 805 806 tokens = [item.strip() for item in line.split(self.kSeparator)] 807 if not self._columncount: 808 self._columncount = len(tokens) 809 810 if self._columncount != len(tokens): 811 raise ValueError("Columncount changed between successive lines") 812 813 yield tuple(tokens)
814 # END for each line 815
816 - def beginWriting(self, columnSizes):
817 """intiialize the writing process 818 819 :param columnSizes: list of ints defining the size in characters for each column you plan to feed 820 :note: When done writing, you have to close the file object yourself (there is no endWriting method here)""" 821 columnTokens = ["%%-%is" % csize for csize in columnSizes] 822 self._formatstr = ((self.kSeparator + " ").join(columnTokens)) + "\n"
823
824 - def writeTokens(self, tokens):
825 """Write the list of tokens to the file accordingly 826 827 :param tokens: one token per column that you want to write 828 :raise TypeError: If column count changed between successive calls""" 829 self._fileobj.write(self._formatstr % tokens)
830
831 832 -class MetaCopyClsMembers(type):
833 """Meta class copying members from given classes onto the type to be created 834 it will read the following attributes from the class dict: 835 ``forbiddenMembers``, ``overwritePrefix``, ``__virtual_bases__`` 836 837 The virtual bases are a tuple of base classes whose members you whish to receive 838 For information on these members, check the docs of `copyClsMembers`"""
839 - def __new__(metacls, name, bases, clsdict):
840 forbiddenMembers = clsdict.get('forbiddenMembers', []) 841 overwritePrefix = clsdict.get('overwritePrefix', None) 842 vbases = clsdict.get('__virtual_bases__', []) 843 844 for sourcecls in vbases: 845 for name,member in sourcecls.__dict__.iteritems(): 846 if name in forbiddenMembers: 847 continue 848 849 # store original - overwritten members must still be able to access it 850 if name in clsdict: 851 if not overwritePrefix: 852 continue 853 morig = clsdict[name] 854 clsdict[overwritePrefix+name] = morig 855 clsdict[name] = member 856 # END for each sourcecls member 857 # END for each sourcecls in bases 858 859 return super(MetaCopyClsMembers, metacls).__new__(metacls, name, bases, clsdict)
860
861 862 #{ Predicates 863 864 # general boolean 865 -class And(object):
866 """For use with python's filter method, simulates logical AND 867 Usage: filter(And(f1,f2,fn), sequence)""" 868 __slots__ = "functions"
869 - def __init__(self, *args):
870 """args must contain the filter methods to be AND'ed 871 To append functions after creation, simply access the 'functions' attribute 872 directly as a list""" 873 self.functions = list(args)
874
875 - def __call__(self, *args, **kwargs):
876 """Called during filter function, return true if all functions return true""" 877 val = True 878 for func in self.functions: 879 val = val and func(*args, **kwargs) 880 if not val: 881 return val 882 # END for each function 883 return val
884
885 886 -class Or(object):
887 """For use with python's filter method, simulates logical OR 888 Usage: filter(Or(f1,f2,fn), sequence) """ 889 __slots__ = "functions"
890 - def __init__(self, *args):
891 """args must contain the filter methods to be AND'ed""" 892 self.functions = args
893
894 - def __call__(self, *args, **kwargs):
895 """Called during filter function, return true if all functions return true""" 896 val = False 897 for func in self.functions: 898 val = val or func(*args, **kwargs) 899 if val: 900 return val 901 # END for each function 902 return val
903
904 #} END predicates 905 906 #{ Module initialization helpers 907 908 -def list_submodules(path):
909 """ 910 :return: set(submodule_name, ...) list of submodule names that could 911 be imported using __import__ 912 :param path: module path containing the submodules""" 913 file_package = make_path(path).dirname() 914 # convert it to a string, unicode ( on windows ) is not handled by the __import__ 915 # statement 916 cut_ext = lambda f: str(os.path.splitext(os.path.basename(f))[0]) 917 modules = set() 918 for glob in ("*.py", "*.pyc"): 919 modules.update(set(map(cut_ext, file_package.files(glob)))) 920 # END for each filetype to import 921 return modules
922
923 -def list_subpackages(path):
924 """:return: list of sub-package names""" 925 return [str(p.basename()) for p in make_path(path).dirname().dirs() if p.files("__init__.py?")]
926 927 #} END module initialization helpers 928