1
2 """Contains interface definitions """
3 __docformat__ = "restructuredtext"
4
5 from collections import deque as Deque
6 import logging
7 log = logging.getLogger('mrv.interface')
8
9 __all__ = ("Interface", "iDagItem", "iDuplicatable", "iChoiceDialog", "iPrompt",
10 "iProgressIndicator")
13 """Base for all interfaces.
14 All interfaces should derive from here."""
15
16
17
18 __slots__ = tuple()
19
21 """:return: True if this instance supports the interface of the given type
22 :param interface_type: Type of the interface you require this instance
23 to support
24 :note: Must be used in case you only have a weak reference of your interface
25 instance or proxy which is a case where the ordinary isinstance( obj, iInterface )
26 will not work"""
27 return isinstance( self, interface_type )
28
31 """ Describes interface for a DAG item.
32 Its used to unify interfaces allowing to access objects in a dag like graph
33 Of the underlying object has a string representation, the defatult implementation
34 will work natively.
35 Otherwise the getParent and getChildren methods should be overwritten
36
37 :note: a few methods of this class are abstract and need to be overwritten
38 :note: this class expects the attribute '_sep' to exist containing the
39 separator at which your object should be split ( for default implementations ).
40 This works as the passed in pointer will belong to derived classes that can
41 define that attribute on instance or on class level"""
42
43 kOrder_DepthFirst, kOrder_BreadthFirst = range(2)
44
45
46
47 __slots__ = tuple()
48
49
50
51
52 _sep = None
53
54
55
56
58 """:return: True if this path is the root of the DAG """
59 return self == self.root()
60
62 """:return: the root of the DAG - it has no further parents"""
63 parents = self.parentDeep( )
64 if not parents:
65 return self
66 return parents[-1]
67
69 """:return: basename of this path, '/hello/world' -> 'world'"""
70 return str(self).split( self._sep )[-1]
71
73 """
74 :return: parent of this path, '/hello/world' -> '/hello' or None if this path
75 is the dag's root"""
76 tokens = str(self).split( self._sep )
77 if len( tokens ) <= 2:
78 return None
79
80 return self.__class__( self._sep.join( tokens[0:-1] ) )
81
83 """:return: all parents of this path, '/hello/my/world' -> [ '/hello/my','/hello' ]"""
84 return list( self.iterParents( ) )
85
86 return out
87
88 - def children( self , predicate = lambda x: True):
89 """:return: list of intermediate children of path, [ child1 , child2 ]
90 :param predicate: return True to include x in result
91 :note: the child objects returned are supposed to be valid paths, not just relative paths"""
92 raise NotImplementedError( )
93
95 """:return: list of all children of path, [ child1 , child2 ]
96 :param order: order enumeration
97 :param predicate: returns true if x may be returned
98 :note: the child objects returned are supposed to be valid paths, not just relative paths"""
99 out = []
100 if order == self.kOrder_DepthFirst:
101 def depthSearch( child ):
102 if not predicate( c ):
103 return
104 children = child.children( predicate = predicate )
105 for c in children:
106 depthSearch( c )
107 out.append( child )
108
109
110 depthSearch( self )
111
112 elif order == self.kOrder_BreadthFirst:
113 childstack = Deque( [ self ] )
114 while childstack:
115 item = childstack.pop( )
116 if not predicate( item ):
117 continue
118 children = item.children( predicate = predicate )
119
120 childstack.extendleft( children )
121 out.extend( children )
122
123
124 return out
125
127 """:return: True if self is a part of other, and thus can be found in other
128 :note: operates on strings only"""
129 return str( other ).find( str( self ) ) != -1
130
132 """:return: True other starts with self
133 :note: operates on strings
134 :note: we assume other has the same type as self, thus the same separator"""
135 selfstr = self.addSep( str( self ), self._sep )
136 other = self.addSep( str( other ), self._sep )
137 return other.startswith( selfstr )
138
139
140
141
142 - def iterParents( self , predicate = lambda x : True ):
143 """:return: generator retrieving all parents up to the root
144 :param predicate: returns True for all x that you want to be returned"""
145 curpath = self
146 while True:
147 parent = curpath.parent( )
148 if not parent:
149 raise StopIteration
150
151 if predicate( parent ):
152 yield parent
153
154 curpath = parent
155
156
157
158
159
160
161 @classmethod
162 - def addSep( cls, item, sep ):
163 """:return: item with separator added to it ( just once )
164 :note: operates best on strings
165 :param item: item to add separator to
166 :param sep: the separator"""
167 if not item.endswith( sep ):
168 item += sep
169 return item
170
172 """Add the given name to the string version of our instance
173 :return: string with childname added like name _sep childname"""
174 sname = self.addSep( str( self ), self._sep )
175
176 if childname.startswith( self._sep ):
177 childname = childname[1:]
178
179 return sname + childname
180
185 """Simple interface allowing any class to be properly duplicated
186
187 :note: to implement this interface, implement `createInstance` and
188 `copyFrom` in your class """
189
190
191
192 __slots__ = tuple()
193
194 - def __copyTo( self, instance, *args, **kwargs ):
195 """Internal Method with minimal checking"""
196
197 mrorev = instance.__class__.mro()
198 mrorev.reverse()
199
200
201
202 for base in mrorev:
203 if base is iDuplicatable:
204 continue
205
206
207
208 try:
209 copyFromFunc = base.__dict__[ 'copyFrom' ]
210 copyFromFunc( instance, self, *args, **kwargs )
211 except KeyError:
212 pass
213 except TypeError,e:
214 raise
215
216
217
218 return instance
219
220
221
223 """Create and Initialize an instance of self.__class__( ... ) based on your own data
224
225 :return: new instance of self
226 :note: using self.__class__ instead of an explicit class allows derived
227 classes that do not have anything to duplicate just to use your implementeation
228
229 :note: you must support ``args`` and ``kwargs`` if one of your iDuplicate bases does"""
230 return self.__class__(*args, **kwargs)
231
232 - def copyFrom( self, other, *args, **kwargs ):
233 """Copy the data from other into self as good as possible
234 Only copy the data that is unique to your specific class - the data of other
235 classes will be taken care of by them !
236
237 :note: you must support ``args`` and ``kwargs`` if one of your iDuplicate bases does"""
238 raise NotImplementedError( "Copy all data you know from other into self" )
239
240
241
243 """Implements a c-style copy constructor by creating a new instance of self
244 and applying the `copyFrom` methods from base to all classes implementing the copyfrom
245 method. Thus we will call the method directly on the class
246
247 :param args: passed to `copyFrom` and `createInstance` method to give additional directions
248 :param kwargs: see param args"""
249 try:
250 createInstFunc = getattr( self, 'createInstance' )
251 instance = createInstFunc( *args, **kwargs )
252 except TypeError,e:
253
254 raise AssertionError( "The subclass method %s must support *args and or **kwargs if the superclass does, error: %s" % ( createInstFunc, e ) )
255
256
257
258 if not ( instance.__class__ is self.__class__ ):
259 msg = "Duplicate must have same class as self, was %s, should be %s" % ( instance.__class__, self.__class__ )
260 raise AssertionError( msg )
261
262 return self.__copyTo( instance, *args, **kwargs )
263
264 - def copyTo( self, instance, *args, **kwargs ):
265 """Copy the values of ourselves onto the given instance which must be an
266 instance of our class to be compatible.
267 Only the common classes will be copied to instance
268
269 :return: altered instance
270 :note: instance will be altered during the process"""
271 if type( instance ) != type( self ):
272 raise TypeError( "copyTo: Instance must be of type %s but was type %s" % ( type( self ), type( instance ) ) )
273 return self.__copyTo( instance, *args, **kwargs )
274
276 """As `copyTo`, but does only require the objects to have a common base.
277 It will match the actually compatible base classes and call `copyFrom`
278 if possible.
279 As more checking is performed, this method performs worse than `copyTo`"""
280
281 mrorev = instance.__class__.mro()
282 mrorev.reverse()
283
284 own_bases = self.__class__.mro()
285 own_bases.reverse()
286
287
288
289 for base in mrorev:
290 if base is iDuplicatable or base not in own_bases:
291 continue
292
293 try:
294 copyFromFunc = base.__dict__[ 'copyFrom' ]
295 copyFromFunc( instance, self, *args, **kwargs )
296 except KeyError:
297 pass
298 except TypeError,e:
299 raise AssertionError( "The subclass method %s.%s must support *args and or **kwargs if the superclass does, error: %s" % (base, copyFromFunc,e) )
300
301 return instance
302
305 """Interface allowing access to a simple confirm dialog allowing the user
306 to pick between a selection of choices, one of which he has to confirm
307
308 :note: for convenience, this interface contains a brief implementation as a
309 basis for subclasses, using standard input and standard ouput for communication"""
310
312 """Allow the user to pick a choice
313
314 :note: all paramaters exist in a short and a long version for convenience, given
315 in the form short/long
316 :param kwargs:
317 * t/title: optional title of the choice box, quickly saying what this choice is about
318 * m/message: message to be shown, informing the user in detail what the choice is about
319 * c/choices: single item or list of items identifying the choices if used as string
320 * dc/defaultChoice: choice in set of choices to be used as default choice, default is first choice
321 * cc/cancelChoice: choice in set of choices to be used if the dialog is cancelled using esc,
322 default is last choice"""
323 self.title = kwargs.get( "t", kwargs.get( "title", "Choice Dialog" ) )
324 self.message = kwargs.get( "m", kwargs.get( "message", None ) )
325 assert self.message
326
327 self.choices = kwargs.get( "c", kwargs.get( "choices", None ) )
328 assert self.choices
329
330
331 if not isinstance( self.choices, ( list, tuple ) ):
332 self.choices = [ self.choices ]
333
334 self.default_choice = kwargs.get( "dc", kwargs.get( "defaultChoice", self.choices[0] ) )
335 self.cancel_choice = kwargs.get( "cc", kwargs.get( "cancelChoice", self.choices[-1] ) )
336
337
339 """Make the choice
340
341 :return: name of the choice made by the user, the type shall equal the type given
342 as button names
343 :note: this implementation always returns the default choice"""
344 log.debug(self.title)
345 log.debug("-"*len( self.title ))
346 log.debug(self.message)
347 log.debug(" | ".join(( str( c ) for c in self.choices )))
348 log.debug("answer: %s" % self.default_choice)
349
350 return self.default_choice
351
354 """Prompt a value from the user, providing a default if no input is retrieved"""
355
357 """Configure the prompt, most parameters allow short and long names
358
359 :param kwargs:
360 * m/message: Message to be presented, like "Enter your name", must be set
361 * d/default: default value to return in case there is no input
362 * cd/cancelDefault: default value if prompt is cancelled
363 * confirmToken: token to enter/hit/press to finish the prompt
364 * cancelToken: token to cancel and abort the prompt"""
365 self.msg = kwargs.pop( "m", kwargs.pop( "message", None ) )
366 assert self.msg is not None, "No Message given"
367 self.confirmDefault = kwargs.pop( "d", kwargs.pop( "default", None ) )
368 self.cancelDefault = kwargs.pop( "cd", kwargs.pop( "cancelDefault", None ) )
369 self.confirmToken = kwargs.pop( "t", kwargs.pop( "confirmToken", None ) )
370 self.cancelToken = kwargs.pop( "ct", kwargs.pop( "cancelToken", None ) )
371
372
373 self._kwargs = kwargs
374
376 """activate our prompt
377 :return: the prompted value
378 :note: base implementation just prints a sample text and returns the default"""
379 log.debug("%s [ %s ]:" % ( self.msg, self.confirmDefault ))
380 log.debug("Hit %s to confirm or %s to cancel" % ( self.confirmToken, self.cancelToken ))
381 return self.confirmDefault
382
385 """Interface allowing to submit progress information
386 The default implementation just prints the respective messages
387 Additionally you may query whether the computation has been cancelled by the user
388
389 :note: this interface is a simple progress indicator itself, and can do some computations
390 for you if you use the get() method yourself"""
391
392
393
394 - def __init__( self, min = 0, max = 100, is_relative = True, may_abort = False, round_robin=False, **kwargs ):
395 """:param min: the minimum progress value
396 :param max: the maximum progress value
397 :param is_relative: if True, the values given will be scaled to a range of 0-100,
398 if False no adjustments will be done
399 :param round_robin: see `setRoundRobin`
400 :param kwargs: additional arguments being ignored"""
401 self.setRange( min, max )
402 self.setRelative( is_relative )
403 self.setAbortable( may_abort )
404 self.setRoundRobin( round_robin )
405 self.__progress = min
406
407
408
410 """intiialize the progress indicator before calling `set` """
411 self.__progress = self.__min
412
414 """indicate that you are done with the progress indicator - this must be your last
415 call to the interface"""
416 pass
417
418
419
420
421 - def refresh( self, message = None ):
422 """Refresh the progress indicator so that it represents its values on screen.
423
424 :param message: message passed along by the user"""
425
426
427 - def set( self, value, message = None , omit_refresh=False ):
428 """Set the progress of the progress indicator to the given value
429
430 :param value: progress value ( min<=value<=max )
431 :param message: optional message you would like to give to the user
432 :param omit_refresh: by default, the progress indicator refreshes on set,
433 if False, you have to call refresh manually after you set the value"""
434 self.__progress = value
435
436 if not omit_refresh:
437 self.refresh( message = message )
438
440 """set the range within we expect our progress to occour"""
441 self.__min = min
442 self.__max = max
443
445 """Set if round-robin mode should be used.
446 If True, values exceeding the maximum range will be wrapped and
447 start at the minimum range"""
448 self.__rr = round_robin
449
451 """enable or disable relative progress computations"""
452 self.__relative = state
453
455 """If state is True, the progress may be interrupted, if false it cannot
456 be interrupted"""
457 self.__may_abort = state
458
459 - def setup( self, range=None, relative=None, abortable=None, begin=True, round_robin=None ):
460 """Multifunctional, all in one convenience method setting all important attributes
461 at once. This allows setting up the progress indicator with one call instead of many
462
463 :note: If a kw argument is None, it will not be set
464 :param range: Tuple( min, max ) - start ane end of progress indicator range
465 :param relative: equivalent to `setRelative`
466 :param abortable: equivalent to `setAbortable`
467 :param round_robin: equivalent to `setRoundRobin`
468 :param begin: if True, `begin` will be called as well"""
469 if range is not None:
470 self.setRange( range[0], range[1] )
471
472 if relative is not None:
473 self.setRelative( relative )
474
475 if abortable is not None:
476 self.setAbortable( abortable )
477
478 if round_robin is not None:
479 self.setRoundRobin(round_robin)
480
481 if begin:
482 self.begin()
483
484
485
486
488 """:return: the current progress value
489
490 :note: if set to relative mode, values will range
491 from 0.0 to 100.0.
492 Values will always be within the ones returned by `range`"""
493 p = self.value()
494 mn,mx = self.range()
495 if self.roundRobin():
496 p = p % mx
497
498 if not self.isRelative():
499 return min( max( p, mn ), mx )
500
501
502
503 return min( max( ( p - mn ) / float( mx - mn ), 0.0 ), 1.0 ) * 100.0
504
506 """:return: current progress as it is stored internally, without regarding
507 the range or round-robin options.
508
509 :note: This allows you to use this instance as a counter without concern to
510 the range and round-robin settings"""
511 return self.__progress
512
514 """:return: tuple( min, max ) value"""
515 return ( self.__min, self.__max )
516
518 """:return: True if roundRobin mode is enabled"""
519 return self.__rr
520
522 """
523 :return: a prefix indicating the progress according to the current range
524 and given value """
525 prefix = ""
526 if self.isRelative():
527 prefix = "%i%%" % value
528 else:
529 mn,mx = self.range()
530 prefix = "%i/%i" % ( value, mx )
531
532 return prefix
533
535 """:return: True if the process may be cancelled"""
536 return self.__may_abort
537
539 """
540 :return: true if internal progress computations are relative, False if
541 they are treated as absolute values"""
542 return self.__relative
543
545 """:return: true if the operation should be aborted"""
546 return False
547
548