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

Source Code for Module mrv.interface

  1  # -*- coding: utf-8 -*- 
  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") 
11 12 -class Interface( object ):
13 """Base for all interfaces. 14 All interfaces should derive from here.""" 15 16 # assure we can be handled efficiently - subclasses are free not to define 17 # slots, but those who do will not inherit a __dict__ from here 18 __slots__ = tuple() 19
20 - def supports( self, interface_type ):
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
29 30 -class iDagItem( Interface ):
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 # assure we can be handled efficiently - subclasses are free not to define 46 # slots, but those who do will not inherit a __dict__ from here 47 __slots__ = tuple() 48 49 #{ Configuration 50 # separator as appropriate for your class if it can be treated as string 51 # if string treatment is not possibly, override the respective method 52 _sep = None 53 #} END configuration 54 55 #{ Query Methods 56
57 - def isRoot( self ):
58 """:return: True if this path is the root of the DAG """ 59 return self == self.root()
60
61 - def root( self ):
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
68 - def basename( self ):
69 """:return: basename of this path, '/hello/world' -> 'world'""" 70 return str(self).split( self._sep )[-1]
71
72 - def parent( self ):
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: # its already root 78 return None 79 80 return self.__class__( self._sep.join( tokens[0:-1] ) )
81
82 - def parentDeep( self ):
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
94 - def childrenDeep( self , order = kOrder_BreadthFirst, predicate=lambda x: True ):
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 # END recursive search method 109 110 depthSearch( self ) 111 # END if depth first 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 # END while childstack 123 # END if breadth first 124 return out
125
126 - def isPartOf( self, other ):
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
131 - def isRootOf( self, other ):
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 #} END Query Methods 140 141 #{ Iterators
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 # END while true 156 157 158 #} END Iterators 159 160 #{ Name Generation 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
171 - def fullChildName( self, childname ):
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
181 #} END name generation 182 183 184 -class iDuplicatable( Interface ):
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 # assure we can be handled efficiently - subclasses are free not to define 191 # slots, but those who do will not inherit a __dict__ from here 192 __slots__ = tuple() 193
194 - def __copyTo( self, instance, *args, **kwargs ):
195 """Internal Method with minimal checking""" 196 # Get reversed mro, starting at lowest base 197 mrorev = instance.__class__.mro() 198 mrorev.reverse() 199 200 # APPLY COPY CONSTRUCTORS ! 201 ############################## 202 for base in mrorev: 203 if base is iDuplicatable: 204 continue 205 206 # must get the actual method directly from the base ! Getattr respects the mro ( of course ) 207 # and possibly looks at the base's superclass methods of the same name 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 # END for each base 216 217 # return the result ! 218 return instance
219 220 #{ Interface 221
222 - def createInstance( self, *args, **kwargs ):
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 #} END interface 241
242 - def duplicate( self, *args, **kwargs ):
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 #raise 254 raise AssertionError( "The subclass method %s must support *args and or **kwargs if the superclass does, error: %s" % ( createInstFunc, e ) ) 255 256 257 # Sanity Check 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
275 - def copyToOther( self, instance, *args, **kwargs ):
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 # Get reversed mro, starting at lowest base 281 mrorev = instance.__class__.mro() 282 mrorev.reverse() 283 284 own_bases = self.__class__.mro() 285 own_bases.reverse() 286 287 # APPLY COPY CONSTRUCTORS ! 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 # END for each base 301 return instance
302
303 304 -class iChoiceDialog( Interface ):
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
311 - def __init__( self, *args, **kwargs ):
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 # internally we store a choice list 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
338 - def choice( self ):
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
352 353 -class iPrompt( Interface ):
354 """Prompt a value from the user, providing a default if no input is retrieved""" 355
356 - def __init__( self, **kwargs ):
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 # remaining arguments for subclass use 373 self._kwargs = kwargs
374
375 - def prompt( self ):
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
383 384 -class iProgressIndicator( Interface ):
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 #{ Initialization
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
409 - def begin( self ):
410 """intiialize the progress indicator before calling `set` """ 411 self.__progress = self.__min # refresh
412
413 - def end( self ):
414 """indicate that you are done with the progress indicator - this must be your last 415 call to the interface""" 416 pass
417 418 #} END initialization 419 420 #{ Edit
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 # To be implemented in subclass 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
439 - def setRange( self, min, max ):
440 """set the range within we expect our progress to occour""" 441 self.__min = min 442 self.__max = max
443
444 - def setRoundRobin( self, round_robin ):
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
450 - def setRelative( self, state ):
451 """enable or disable relative progress computations""" 452 self.__relative = state
453
454 - def setAbortable( self, state ):
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 #} END edit 485 486 #{ Query
487 - def get( self ):
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 # END relative handling 501 502 # compute the percentage 503 return min( max( ( p - mn ) / float( mx - mn ), 0.0 ), 1.0 ) * 100.0
504
505 - def value( self ):
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
513 - def range( self ):
514 """:return: tuple( min, max ) value""" 515 return ( self.__min, self.__max )
516
517 - def roundRobin( self ):
518 """:return: True if roundRobin mode is enabled""" 519 return self.__rr
520
521 - def prefix( self, value ):
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
534 - def isAbortable( self ):
535 """:return: True if the process may be cancelled""" 536 return self.__may_abort
537
538 - def isRelative( self ):
539 """ 540 :return: true if internal progress computations are relative, False if 541 they are treated as absolute values""" 542 return self.__relative
543
544 - def isCancelRequested( self ):
545 """:return: true if the operation should be aborted""" 546 return False
547 #} END query 548