2
"""All kinds of utility methods and classes that are used in more than one modules """
4
from collections import deque as Deque
8
from interface import iDuplicatable
10
from path import make_path
14
log = logging.getLogger("mrv.maya.ui.util")
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")
25
def decodeString(valuestr):
26
""" :return: int,float or str from string valuestr - a string that encodes a
27
numeric value or a string
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)
37
val = numtype(valuestr)
40
if val != float(valuestr):
44
except (ValueError,TypeError):
46
# END for each numeric type
48
# its just a string and not a numeric type
51
def decodeStringOrList(valuestrOrList):
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]
58
return decodeString(valuestrOrList)
61
""":return: s with first letter capitalized"""
62
return s[0].upper() + s[1:]
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'
70
if preserveAcronymns and s[0:2].isupper():
72
except IndexError: pass
74
return s[0].lower() + s[1:]
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 ;)
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
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():
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
105
if name in forbiddenMembers:
108
# store original - overwritten members must still be able to access it
109
if hasattr(destcls, name):
110
if not overwritePrefix:
112
morig = getattr(destcls, name)
113
type.__setattr__(destcls, overwritePrefix+name, morig)
114
type.__setattr__(destcls, name, member)
118
# END for each memebr in sourcecls
120
def packageClasses(importBase, packageFile, predicate = lambda x: True):
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
130
packageDir = os.path.dirname(packageFile)
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('_')]
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)
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
165
:param ignore_startitem: if True, the startItem will be ignored and automatically pruned from
167
:note: this is an adjusted version of `dge.iterShells`"""
170
stack.append((0 , startItem)) # startitem is always depth level 0
172
def addToStack(stack, lst, branch_first, dpth):
174
reviter = ((dpth , lst[i]) for i in range(len(lst)-1,-1,-1))
175
stack.extendleft(reviter)
177
stack.extend((dpth,item) for item in lst)
178
# END addToStack local method
180
# adjust function to define direction
181
directionfunc = graph.successors
183
directionfunc = graph.predecessors
186
d, item = stack.pop() # depth of item, item
195
if stop(oitem, graph):
198
skipStartItem = ignore_startitem and (item == startItem)
199
if not skipStartItem and not prune(oitem, graph):
202
# only continue to next level if this is appropriate !
204
if depth > -1 and nd > depth:
207
addToStack(stack, directionfunc(item), branch_first, nd)
208
# END for each item on work stack
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")
217
def __init__(self, func, *args,**kwargs):
218
"""Initialize object with function to call once this object is called"""
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)
230
"""Advanced call class providing additional options:
233
if True, default True, incoming arguments will be prepended before the static ones
235
if True, default True, incoming kwargs will be merged into the static ones """
236
__slots__ = ("merge_args", "merge_kwargs")
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)
242
super(CallAdv, self).__init__(func, *args, **kwargs)
244
def __call__(self, *inargs, **inkwargs):
245
"""Call with merge support"""
249
args.extend(self.args)
251
if self.merge_kwargs:
252
self.kwargs.update(inkwargs)
254
return self.func(*args, **self.kwargs)
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
263
__slots__ = ("_weakinst", "_clsfunc")
265
def __init__(self, instancefunction):
266
self._weakinst = weakref.ref(instancefunction.im_self)
267
self._clsfunc = instancefunction.im_func
269
def __eq__(self, other):
270
return hash(self) == hash(other)
273
return hash((self._clsfunc, self._weakinst()))
275
def __call__(self, *args, **kwargs):
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__)
283
return self._clsfunc(inst, *args, **kwargs)
287
"""Descriptor allowing to easily setup callbacks for classes derived from
289
_inst_event_attr = '__events__' # dict with event -> set() relation
292
# if true, functions will be weak-referenced - its useful if you use instance
293
# variables as callbacks
296
# if True, callback handlers throwing an exception will immediately be
297
# removed from the callback list
298
remove_on_error = False
301
# If not None, this value overrides the corresponding value on the EventSender class
302
sender_as_argument = None
305
# internally used to keep track of the current sender. Is None if no event
309
def __init__(self, **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
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
323
def _func_to_key(self, eventfunc):
325
:return: an eventfunction suitable to be used as key in our instance
328
if inspect.ismethod(eventfunc):
329
eventfunc = WeakInstFunction(eventfunc)
331
eventfunc = weakref.ref(eventfunc)
332
# END instance function special handling
333
# END if use weak ref
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"""
340
if isinstance(eventkey, WeakInstFunction):
344
# END instance method handling
345
# END weak ref handling
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()
354
def __set__(self, inst, eventfunc):
355
"""Set a new event to our object"""
356
self._getFunctionSet(inst).add(self._func_to_key(eventfunc))
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."""
362
inst = weakref.ref(inst)
363
# END handle instance
364
self._last_inst_ref = inst
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())
373
ed = getattr(inst, self._inst_event_attr)
377
return ed.setdefault(self, set())
378
# END handle self -> set relation
381
def send(self, *args, **kwargs):
382
"""Send our event using the given args
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)
390
failed_callbacks = list()
392
sas = inst.sender_as_argument
393
if self.sender_as_argument is not None:
394
sas = self.sender_as_argument
398
Event._curSender = inst
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():
404
func = self._key_to_func(function)
406
log.warn("Listener for callback of %s was not available anymore" % self)
407
failed_callbacks.append(function)
413
func(inst, *args, **kwargs)
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:
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)
427
if inst.reraise_on_error:
431
# END for each registered event
433
# remove failed listeners
434
for function in failed_callbacks:
435
callbackset.remove(function)
437
Event._curSender = None
440
# Alias, to make event sending even easier
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)
449
self._getFunctionSet(inst).remove(eventfunc)
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
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.
470
Derive from this class and define your callbacks like:
474
>>> self.event.send([*args][,**kwargs]])
476
>>> # Users register using
477
>>> yourinstance.event = callable
479
>>> # and deregister using
480
>>> yourinstance.event.remove(callable)
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
485
:note: using weak-references to ensure one does not keep objects alive,
486
see `Event.use_weakref`"""
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
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
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))]
504
def clearAllEvents(self):
505
"""Remove all event receivers for all events registered in this instance.
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
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
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
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",)
531
im_provide_on_instance = True # if true, interfaces are available directly through the class using descriptors
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
540
def __init__(self, interfacename):
541
self.iname = interfacename # keep name of our interface
543
def __get__(self, inst, cls = None):
544
# allow our instance to be manipulated if accessed through the class
549
return inst.interface(self.iname)
551
raise AttributeError("Interface %s does not exist" % self.iname)
553
def __set__(self, value):
554
raise ValueError("Cannot set interfaces through the instance - use the setInterface method instead")
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):
562
self.__callerid = ibase._num_callers
563
ibase._num_callers += 1
565
ibase._current_caller_id = self.__callerid # assure the callback finds the right one
566
ibase.givenToCaller()
568
def __getattr__(self, attr):
569
self.__ibase._current_caller_id = self.__callerid # set our caller
570
return getattr(self.__ibase, attr)
573
self.__ibase.aboutToRemoveFromCaller()
574
self.__ibase._num_callers -= 1
575
self.__ibase._current_caller_id = -1
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.
582
:note: You can register an InterfaceBase with several InterfaceMasters and
583
share the caller count respectively"""
584
__slots__ = ("_current_caller_id", "_num_callers")
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)
589
def numCallers(self):
590
""":return: number possible callers"""
591
return self._num_callers
594
"""Return the number of the caller that called your interface method
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
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"""
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 """
610
#} END helper classes
614
"""Initialize the interface base with some tracking variables"""
615
self._idict = dict() # keep interfacename->interfaceinstance relations
617
#} END object overrides
619
def copyFrom(self, other, *args, **kwargs):
620
"""Copy all interface from other to self, use they duplciate method if
622
for ifname, ifinst in other._idict.iteritems():
624
if hasattr(ifinst, "duplicate"):
625
myinst = ifinst.duplicate()
627
self.setInterface(ifname, myinst)
628
# END for each interface in other
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
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 ?
645
del(self._idict[interfaceName])
649
# delete class descriptor
650
if self.im_provide_on_instance:
652
delattr(self.__class__, interfaceName)
653
except AttributeError:
657
# END interface deleting
659
self._idict[interfaceName] = interfaceInstance
662
if self.im_provide_on_instance:
663
setattr(self.__class__, interfaceName, self.InterfaceDescriptor(interfaceName))
665
# provide class variables ?
667
def interface(self, interfaceName):
668
""":return: an interface registered with interfaceName
669
:raise ValueError: if no such interface exists"""
671
iinst = self._idict[interfaceName]
673
# return wrapper if we can, otherwise just
674
if isinstance(iinst, self.InterfaceBase):
675
return self._InterfaceHandler(iinst)
679
raise ValueError("Interface %s does not exist" % interfaceName)
681
def listInterfaces(self):
682
""":return: list of names indicating interfaces available at our InterfaceMaster"""
683
return self._idict.keys()
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
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):
705
self.callableobj = callableobj
708
if self.callableobj is not None:
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"""
717
def children(self, n):
718
""" :return: list of children of given node n """
719
return list(self.children_iter(n))
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))
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):
732
def parent_iter(self, n):
733
""":return: iterator returning all parents of node n"""
737
raise StopIteration()
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()
750
for parent in self.parent_iter(startnode):
755
def to_hierarchy_file(self, root, output_path):
756
"""Write ourselves in hierarchy file format to the given output_path.
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
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):
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))
774
class PipeSeparatedFile(object):
775
"""Read and write simple pipe separated files.
777
The number of column must remain the same per line
784
__slots__ = ("_fileobj", "_columncount", "_formatstr")
786
def __init__(self, fileobj):
787
"""Initialize the instance
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
794
def beginReading(self):
795
"""Start reading the file"""
797
def readColumnLine(self):
798
"""Generator reading one line after another, returning the stripped columns
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()):
806
tokens = [item.strip() for item in line.split(self.kSeparator)]
807
if not self._columncount:
808
self._columncount = len(tokens)
810
if self._columncount != len(tokens):
811
raise ValueError("Columncount changed between successive lines")
816
def beginWriting(self, columnSizes):
817
"""intiialize the writing process
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"
824
def writeTokens(self, tokens):
825
"""Write the list of tokens to the file accordingly
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)
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__``
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__', [])
844
for sourcecls in vbases:
845
for name,member in sourcecls.__dict__.iteritems():
846
if name in forbiddenMembers:
849
# store original - overwritten members must still be able to access it
851
if not overwritePrefix:
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
859
return super(MetaCopyClsMembers, metacls).__new__(metacls, name, bases, clsdict)
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)
875
def __call__(self, *args, **kwargs):
876
"""Called during filter function, return true if all functions return true"""
878
for func in self.functions:
879
val = val and func(*args, **kwargs)
882
# END for each function
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
894
def __call__(self, *args, **kwargs):
895
"""Called during filter function, return true if all functions return true"""
897
for func in self.functions:
898
val = val or func(*args, **kwargs)
901
# END for each function
906
#{ Module initialization helpers
908
def list_submodules(path):
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__
916
cut_ext = lambda f: str(os.path.splitext(os.path.basename(f))[0])
918
for glob in ("*.py", "*.pyc"):
919
modules.update(set(map(cut_ext, file_package.files(glob))))
920
# END for each filetype to import
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?")]
927
#} END module initialization helpers