Package param
[hide private]
[frames] | no frames]

Source Code for Package param

  1  """ 
  2  Parameters are a kind of class attribute allowing special behavior, 
  3  including dynamically generated parameter values, documentation 
  4  strings, constant and read-only parameters, and type or range checking 
  5  at assignment time. 
  6   
  7  Potentially useful for any large Python program that needs 
  8  user-modifiable object attributes; see the parameterized.Parameter and 
  9  parameterized.Parameterized classes for more information. 
 10   
 11   
 12  This file contains subclasses of Parameter, implementing specific 
 13  parameter types (e.g. Number). 
 14   
 15  $Id: __init__.py 11322 2010-07-28 16:47:38Z ceball $ 
 16  """ 
 17  __version__='$Revision$' 
 18   
 19  # CEBALERT: we need more documentation above, now that params is a 
 20  # separate directory and will be a separate package. 
 21   
 22  import os.path 
 23   
 24  from parameterized import Parameterized, Parameter, String, \ 
 25       descendents, ParameterizedFunction, ParamOverrides 
 26   
 27   
28 -def produce_value(value_obj):
29 """ 30 A helper function that produces an actual parameter from a stored 31 object: if the object is callable, call it, otherwise return the 32 object. 33 """ 34 if callable(value_obj): 35 return value_obj() 36 else: 37 return value_obj
38 39
40 -class Dynamic(Parameter):
41 """ 42 Parameter whose value can be generated dynamically by a callable 43 object. 44 45 If a Parameter is declared as Dynamic, it can be set a callable 46 object (such as a function or callable class), and getting the 47 parameter's value will call that callable. 48 49 Note that at present, the callable object must allow attributes 50 to be set on itself. 51 52 [Python 2.4 limitation: the callable object must be an instance of a 53 callable class, rather than a named function or a lambda function, 54 otherwise the object will not be picklable or deepcopyable.] 55 56 57 Setting Dynamic.time_fn allows the production of dynamic values to 58 be controlled: a new value will be produced only if the current 59 value of time_fn is greater than what it was last time the 60 parameter value was requested. 61 62 If time_fn is set to None, a new value is always produced. 63 64 If Dynamic.time_fn is set to something other than None, it must, 65 when called, produce a number. 66 """ 67 # CB: making Dynamic support iterators and generators is sf.net 68 # feature request 1864370. When working on that task, note that 69 # detection of a dynamic generator by 'callable' needs to be 70 # replaced by something that matches whatever Dynamic becomes 71 # capable of using. 72 73 time_fn = None # could add a slot for time_fn to allow instances 74 # to override 75 76 # CBENHANCEMENT: Add an 'epsilon' slot. 77 # See email 'Re: simulation-time-controlled Dynamic parameters' 78 # Dec 22, 2007 CB->JAB 79
80 - def __init__(self,**params):
81 """ 82 Call the superclass's __init__ and set instantiate=True if the 83 default is dynamic. 84 """ 85 super(Dynamic,self).__init__(**params) 86 87 if callable(self.default): 88 self._set_instantiate(True) 89 self._initialize_generator(self.default)
90 91
92 - def _initialize_generator(self,gen,obj=None):
93 """ 94 Add 'last time' and 'last value' attributes to the generator. 95 """ 96 # CEBALERT: use a dictionary to hold these things. 97 if hasattr(obj,"_Dynamic_time_fn"): 98 gen._Dynamic_time_fn = obj._Dynamic_time_fn 99 100 gen._Dynamic_last = None 101 # CEB: I'd use None for this, except can't compare a fixedpoint 102 # number with None (e.g. 1>None but FixedPoint(1)>None can't be done) 103 gen._Dynamic_time = -1 104 105 gen._saved_Dynamic_last = [] 106 gen._saved_Dynamic_time = []
107 108
109 - def __get__(self,obj,objtype):
110 """ 111 Call the superclass's __get__; if the result is not dynamic 112 return that result, otherwise ask that result to produce a 113 value and return it. 114 """ 115 gen = super(Dynamic,self).__get__(obj,objtype) 116 117 if not hasattr(gen,'_Dynamic_last'): 118 return gen 119 else: 120 return self._produce_value(gen)
121 122
123 - def __set__(self,obj,val):
124 """ 125 Call the superclass's set and keep this parameter's 126 instantiate value up to date (dynamic parameters 127 must be instantiated). 128 129 If val is dynamic, initialize it as a generator. 130 """ 131 super(Dynamic,self).__set__(obj,val) 132 133 dynamic = callable(val) 134 if dynamic: self._initialize_generator(val,obj) 135 if not obj: self._set_instantiate(dynamic)
136 137
138 - def _produce_value(self,gen,force=False):
139 """ 140 Return a value from gen. 141 142 If there is no time_fn, then a new value will be returned 143 (i.e. gen will be asked to produce a new value). 144 145 If force is True, or the value of time_fn() is greater than 146 what it was was last time produce_value was called, a 147 new value will be produced and returned. Otherwise, 148 the last value gen produced will be returned. 149 """ 150 if hasattr(gen,"_Dynamic_time_fn"): 151 time_fn = gen._Dynamic_time_fn 152 else: 153 time_fn = self.time_fn 154 155 if time_fn is None: 156 value = produce_value(gen) 157 gen._Dynamic_last = value 158 else: 159 160 time = time_fn() 161 162 if force or time>gen._Dynamic_time: 163 value = produce_value(gen) 164 gen._Dynamic_last = value 165 gen._Dynamic_time = time 166 else: 167 value = gen._Dynamic_last 168 169 return value
170 171
172 - def _value_is_dynamic(self,obj,objtype=None):
173 """ 174 Return True if the parameter is actually dynamic (i.e. the 175 value is being generated). 176 """ 177 return hasattr(super(Dynamic,self).__get__(obj,objtype),'_Dynamic_last')
178 179
180 - def _inspect(self,obj,objtype=None):
181 """Return the last generated value for this parameter.""" 182 gen=super(Dynamic,self).__get__(obj,objtype) 183 184 if hasattr(gen,'_Dynamic_last'): 185 return gen._Dynamic_last 186 else: 187 return gen
188 189
190 - def _force(self,obj,objtype=None):
191 """Force a new value to be generated, and return it.""" 192 gen=super(Dynamic,self).__get__(obj,objtype) 193 194 if hasattr(gen,'_Dynamic_last'): 195 return self._produce_value(gen,force=True) 196 else: 197 return gen
198 199 200 # CEBALERT: isinstance(x,Number) should be possible in Python 2.6 201 # (Number is a new abstract base class). 202 # http://docs.python.org/whatsnew/2.6.html 203 import operator 204 is_number = operator.isNumberType 205 206 207
208 -class Number(Dynamic):
209 """ 210 Number is a numeric parameter. Numbers have a default value and 211 optional bounds. There are two types of bounds: ``bounds`` and 212 ``softbounds``. ``bounds`` are hard bounds: the parameter must 213 have a value within the specified range. The default bounds are 214 (None,None), meaning there are actually no hard bounds. One or 215 both bounds can be set by specifying a value 216 (e.g. bounds=(None,10) means there is no lower bound, and an upper 217 bound of 10). Bounds are inclusive by default, but exclusivity 218 can be specified for each bound by setting inclusive_bounds 219 (e.g. inclusive_bounds=(True,False) specifies an exclusive upper 220 bound). 221 222 Number is also a type of Dynamic parameter, so its value 223 can be set to a callable to get a dynamically generated 224 number (see Dynamic). 225 226 When not being dynamically generated, bounds are checked when a 227 Number is created or set. Using a default value outside the hard 228 bounds, or one that is not numeric, results in an exception. When 229 being dynamically generated, bounds are checked when a the value 230 of a Number is requested. A generated value that is not numeric, 231 or is outside the hard bounds, results in an exception. 232 233 As a special case, if allow_None=True (which is true by default if 234 the parameter has a default of None when declared) then a value 235 of None is also allowed. 236 237 A separate function set_in_bounds() is provided that will 238 silently crop the given value into the legal range, for use 239 in, for instance, a GUI. 240 241 ``softbounds`` are present to indicate the typical range of 242 the parameter, but are not enforced. Setting the soft bounds 243 allows, for instance, a GUI to know what values to display on 244 sliders for the Number. 245 246 Example of creating a Number:: 247 AB = Number(default=0.5, bounds=(None,10), softbounds=(0,1), doc='Distance from A to B.') 248 """ 249 __slots__ = ['bounds','_softbounds','allow_None','inclusive_bounds'] 250
251 - def __init__(self,default=0.0,bounds=None,softbounds=None,allow_None=False,inclusive_bounds=(True,True),**params):
252 """ 253 Initialize this parameter object and store the bounds. 254 255 Non-dynamic default values are checked against the bounds. 256 """ 257 super(Number,self).__init__(default=default,**params) 258 259 self.bounds = bounds 260 self.inclusive_bounds = inclusive_bounds 261 self._softbounds = softbounds 262 self.allow_None = (default is None or allow_None) 263 if not callable(default): self._check_value(default)
264 265
266 - def __get__(self,obj,objtype):
267 """ 268 Same as the superclass's __get__, but if the value was 269 dynamically generated, check the bounds. 270 """ 271 result = super(Number,self).__get__(obj,objtype) 272 # CEBALERT: results in extra lookups (_value_is_dynamic() is 273 # also looking up 'result' - should just pass it in). Note 274 # that this method is called often. 275 if self._value_is_dynamic(obj,objtype): self._check_value(result) 276 return result
277 278
279 - def __set__(self,obj,val):
280 """ 281 Set to the given value, raising an exception if out of bounds. 282 """ 283 if not callable(val): self._check_value(val) 284 super(Number,self).__set__(obj,val)
285 286
287 - def set_in_bounds(self,obj,val):
288 """ 289 Set to the given value, but cropped to be within the legal bounds. 290 All objects are accepted, and no exceptions will be raised. See 291 crop_to_bounds for details on how cropping is done. 292 """ 293 if not callable(val): 294 bounded_val = self.crop_to_bounds(val) 295 else: 296 bounded_val = val 297 super(Number,self).__set__(obj,bounded_val)
298 299 300 # CEBERRORALERT: doesn't take account of exclusive bounds. When 301 # the gui uses set_in_bounds(), expecting to get acceptable 302 # values, it actually gets an out-of-bounds error. When fixed, 303 # should remove hack in 304 # topo.tkgui.projectionpanel.UnitsPanel.sheet_change(). 305 306 # CEBALERT: in the methods below, should be testing for identity 307 # with None, rather than equality
308 - def crop_to_bounds(self,val):
309 """ 310 Return the given value cropped to be within the hard bounds 311 for this parameter. 312 313 If a numeric value is passed in, check it is within the hard 314 bounds. If it is larger than the high bound, return the high 315 bound. If it's smaller, return the low bound. In either case, the 316 returned value could be None. If a non-numeric value is passed 317 in, set to be the default value (which could be None). In no 318 case is an exception raised; all values are accepted. 319 """ 320 # Currently, values outside the bounds are silently cropped to 321 # be inside the bounds; it may be appropriate to add a warning 322 # in such cases. 323 if (is_number(val)): 324 if self.bounds==None: 325 return val 326 vmin, vmax = self.bounds 327 if vmin != None: 328 if val < vmin: 329 return vmin 330 331 if vmax != None: 332 if val > vmax: 333 return vmax 334 335 elif self.allow_None and val==None: 336 return val 337 338 else: 339 # non-numeric value sent in: reverts to default value 340 return self.default 341 342 return val
343 344
345 - def _check_value(self,val):
346 """ 347 Checks that the value is numeric and that it is within the hard 348 bounds; if not, an exception is raised. 349 """ 350 if self.allow_None and val==None: 351 return 352 353 if not (is_number(val)): 354 raise ValueError("Parameter '%s' only takes numeric values"%(self._attrib_name)) 355 356 357 if self.bounds!=None: 358 vmin,vmax = self.bounds 359 incmin,incmax = self.inclusive_bounds 360 361 if vmax is not None: 362 if incmax is True: 363 if not val <= vmax: 364 raise ValueError("Parameter '%s' must be at most %s"%(self._attrib_name,vmax)) 365 else: 366 if not val < vmax: 367 raise ValueError("Parameter '%s' must be less than %s"%(self._attrib_name,vmax)) 368 369 if vmin is not None: 370 if incmin is True: 371 if not val >= vmin: 372 raise ValueError("Parameter '%s' must be at least %s"%(self._attrib_name,vmin)) 373 else: 374 if not val > vmin: 375 raise ValueError("Parameter '%s' must be greater than %s"%(self._attrib_name,vmin))
376 377 ## could consider simplifying the above to something like this untested code: 378 379 ## too_low = False if vmin is None else 380 ## (val < vmin if incmin else val <= vmin) and 381 ## (val > vmin if incmin else val <= vmin) 382 383 ## too_high = ... 384 385 ## if too_low or too_high: 386 ## raise ValueError("Parameter '%s' must be in the range %s" % (self._attrib_name,self.rangestr())) 387 388 ## where self.rangestr() formats the range using the usual notation for 389 ## indicating exclusivity, e.g. "[0,10)". 390 391 392
393 - def get_soft_bounds(self):
394 """ 395 For each soft bound (upper and lower), if there is a defined bound (not equal to None) 396 then it is returned, otherwise it defaults to the hard bound. The hard bound could still be None. 397 """ 398 if self.bounds==None: 399 hl,hu=(None,None) 400 else: 401 hl,hu=self.bounds 402 403 if self._softbounds==None: 404 sl,su=(None,None) 405 else: 406 sl,su=self._softbounds 407 408 409 if (sl==None): l = hl 410 else: l = sl 411 412 if (su==None): u = hu 413 else: u = su 414 415 return (l,u)
416 417 418
419 -class Integer(Number):
420
421 - def _check_value(self,val):
422 if not isinstance(val,int): 423 raise ValueError("Parameter '%s' must be an integer."%self._attrib_name) 424 super(Integer,self)._check_value(val)
425 426
427 -class Magnitude(Number):
428
429 - def __init__(self,default=1.0,softbounds=None,**params):
430 Number.__init__(self,default=default,bounds=(0.0,1.0),softbounds=softbounds,**params)
431 432 433 # JAB: Should this and other Parameters below be a Dynamic instead?
434 -class Boolean(Parameter):
435 __slots__ = ['bounds','allow_None'] 436 437 # CB: what does bounds=(0,1) mean/do for this Parameter?
438 - def __init__(self,default=False,bounds=(0,1),allow_None=False,**params):
439 self.bounds = bounds 440 self.allow_None = (default is None or allow_None) 441 Parameter.__init__(self,default=default,**params)
442
443 - def __set__(self,obj,val):
444 if self.allow_None: 445 if not isinstance(val,bool) and val is not None: 446 raise ValueError("Boolean '%s' only takes a Boolean value or None." 447 %self._attrib_name) 448 449 if val is not True and val is not False and val is not None: 450 raise ValueError("Boolean '%s' must be True, False, or None."%self._attrib_name) 451 else: 452 if not isinstance(val,bool): 453 raise ValueError("Boolean '%s' only takes a Boolean value."%self._attrib_name) 454 455 if val is not True and val is not False: 456 raise ValueError("Boolean '%s' must be True or False."%self._attrib_name) 457 458 super(Boolean,self).__set__(obj,val)
459 460
461 -class NumericTuple(Parameter):
462 __slots__ = ['length'] 463
464 - def __init__(self,default=(0,0),length=None,**params):
465 """ 466 Initialize a numeric tuple parameter with a fixed length 467 (number of elements). The length is determined by the initial 468 default value, and is not allowed to change after 469 instantiation. 470 """ 471 if length is None: 472 self.length = len(default) 473 else: 474 self.length = length 475 476 self._check(default) 477 Parameter.__init__(self,default=default,**params)
478
479 - def _check(self,val):
480 if not isinstance(val,tuple): 481 raise ValueError("NumericTuple '%s' only takes a tuple value."%self._attrib_name) 482 483 if not len(val)==self.length: 484 raise ValueError("%s: tuple is not of the correct length (%d instead of %d)." % 485 (self._attrib_name,len(val),self.length)) 486 for n in val: 487 if not is_number(n): 488 raise ValueError("%s: tuple element is not numeric: %s." % (self._attrib_name,str(n)))
489
490 - def __set__(self,obj,val):
491 self._check(val) 492 super(NumericTuple,self).__set__(obj,val)
493 494
495 -class XYCoordinates(NumericTuple):
496
497 - def __init__(self,default=(0.0,0.0),**params):
499 500
501 -class Callable(Parameter):
502 """ 503 Parameter holding a value that is a callable object, such as a function. 504 505 A keyword argument instantiate=True should be provided when a 506 function object is used that might have state. On the other hand, 507 regular standalone functions cannot be deepcopied as of Python 508 2.4, so instantiate must be False for those values. 509 """
510 - def __set__(self,obj,val):
511 if not callable(val): 512 raise ValueError("Callable '%s' only takes a callable object."%self._attrib_name) 513 super(Callable,self).__set__(obj,val)
514 515 516 517 # CEBALERT: this should be a method of ClassSelector.
518 -def concrete_descendents(parentclass):
519 """ 520 Return a dictionary containing all subclasses of the specified 521 parentclass, including the parentclass. Only classes that are 522 defined in scripts that have been run or modules that have been 523 imported are included, so the caller will usually first do ``from 524 package import *``. 525 526 If the class has an attribute ``abstract``, and it is True, the 527 class will not be included. 528 """ 529 return dict([(c.__name__,c) for c in descendents(parentclass) 530 if not c.abstract])
531 532
533 -class Composite(Parameter):
534 """ 535 A parameter that is in fact a composite of a set of other 536 parameters or attributes of the class. The constructor argumentt 537 'attribs' takes a list of attribute names. Getting the parameter 538 returns a list of the values of the constituents of the composite, 539 in the order specified. Likewise, setting the parameter takes a 540 sequence of values and sets the value of the constituent 541 attributes. 542 """ 543 __slots__=['attribs','objtype'] 544
545 - def __init__(self,attribs=[],**kw):
546 super(Composite,self).__init__(default=None,**kw) 547 self.attribs = attribs
548
549 - def __get__(self,obj,objtype):
550 """ 551 Return the values of all the attribs, as a list. 552 """ 553 if not obj: 554 return [getattr(objtype,a) for a in self.attribs] 555 else: 556 return [getattr(obj,a) for a in self.attribs]
557
558 - def __set__(self,obj,val):
559 """ 560 Set the values of all the attribs. 561 """ 562 assert len(val) == len(self.attribs),"Compound parameter '%s' got the wrong number of values (needed %d, but got %d)." % (self._attrib_name,len(self.attribs),len(val)) 563 564 if not obj: 565 for a,v in zip(self.attribs,val): 566 setattr(self.objtype,a,v) 567 else: 568 for a,v in zip(self.attribs,val): 569 setattr(obj,a,v)
570 571
572 -class Selector(Parameter):
573 """ 574 Parameter whose value is set to some form of one of the 575 possibilities in its range. 576 577 Subclasses must implement get_range(). 578 """ 579 __abstract = True 580
581 - def get_range(self):
582 raise NotImplementedError("get_range() must be implemented in subclasses.")
583 584
585 -class ObjectSelector(Selector):
586 """ 587 Parameter whose value is set to an object from its list of 588 possible objects. 589 590 check_on_set restricts the value to be among the current list of 591 objects. By default, if objects are initially supplied, 592 check_on_set is True, whereas if no objects are initially 593 supplied, check_on_set is False. This can be overridden by 594 explicitly specifying check_on_set initially. 595 596 If check_on_set is True (either because objects are supplied 597 initially, or because it is explicitly specified), the default 598 (initial) value must be among the list of objects (unless the 599 default value is None). 600 """ 601 __slots__ = ['objects','compute_default_fn','check_on_set'] 602 603 # ObjectSelector is usually used to allow selection from a list of 604 # existing objects, therefore instantiate is False by default.
605 - def __init__(self,default=None,objects=None,instantiate=False, 606 compute_default_fn=None,check_on_set=None,**params):
607 if objects is None: 608 objects = [] 609 self.objects = objects 610 self.compute_default_fn = compute_default_fn 611 612 if check_on_set is not None: 613 self.check_on_set=check_on_set 614 elif len(objects)==0: 615 self.check_on_set=False 616 else: 617 self.check_on_set=True 618 619 if default is not None and self.check_on_set is True: 620 self._check_value(default) 621 622 super(ObjectSelector,self).__init__(default=default,instantiate=instantiate,**params)
623 624 625 # CBNOTE: if the list of objects is changed, the current value for 626 # this parameter in existing POs could be out of the new range. 627
628 - def compute_default(self):
629 """ 630 If this parameter's compute_default_fn is callable, call it 631 and store the result in self.default. 632 633 Also removes None from the list of objects (if the default is 634 no longer None). 635 """ 636 if self.default is None and callable(self.compute_default_fn): 637 self.default=self.compute_default_fn() 638 if self.default not in self.objects: 639 self.objects.append(self.default)
640 641
642 - def _check_value(self,val,obj=None):
643 """ 644 val must be None or one of the objects in self.objects. 645 """ 646 if not val in self.objects: 647 # CEBALERT: can be called before __init__ has called 648 # super's __init__, i.e. before attrib_name has been set. 649 try: 650 attrib_name = self._attrib_name 651 except AttributeError: 652 attrib_name = "" 653 raise ValueError("%s not in Parameter %s's list of possible objects" \ 654 %(val,attrib_name))
655 656 # CBNOTE: I think it's not helpful to do a type check for the value of 657 # an ObjectSelector. If we did such type checking, any user 658 # of this Parameter would have to be sure to update the list of possible 659 # objects before setting the Parameter's value. As it is, only users who care about the 660 # correct list of objects being displayed need to update the list.
661 - def __set__(self,obj,val):
662 if self.check_on_set: 663 self._check_value(val,obj) 664 super(ObjectSelector,self).__set__(obj,val)
665 666 667 # CebAlert; move some bits into superclass (same for clsselector)?
668 - def get_range(self):
669 """ 670 Return the possible objects to which this parameter could be set. 671 672 (Returns the dictionary {object.name:object}.) 673 """ 674 # CEBHACKALERT: was written assuming it would only operate on 675 # Parameterized instances. Think this is an sf.net bug/feature 676 # request. Temporary fix: don't use obj.name if unavailable. 677 try: 678 d=dict([(obj.name,obj) for obj in self.objects]) 679 except AttributeError: 680 d=dict([(obj,obj) for obj in self.objects]) 681 return d
682 683
684 -class ClassSelector(Selector):
685 """ 686 Parameter whose value is an instance of the specified class. 687 """ 688 # CEBALERT: allow_None already a slot from superclass? 689 __slots__ = ['class_','allow_None'] 690
691 - def __init__(self,class_,default=None,instantiate=True,allow_None=False,**params):
696 697
698 - def _check_value(self,val,obj=None):
699 """val must be None or an instance of self.class_""" 700 if not (isinstance(val,self.class_)) and not (val is None and self.allow_None): 701 raise ValueError( 702 "Parameter '%s' value must be an instance of %s, not '%s'" % 703 (self._attrib_name, self.class_.__name__, val))
704
705 - def __set__(self,obj,val):
706 self._check_value(val,obj) 707 super(ClassSelector,self).__set__(obj,val)
708 709
710 - def get_range(self):
711 """ 712 Return the possible types for this parameter's value. 713 714 (I.e. return {name: <class>} for all classes that are 715 concrete_descendents() of self.class_.) 716 717 Only classes from modules that have been imported are added 718 (see concrete_descendents()). 719 """ 720 classes = concrete_descendents(self.class_) 721 d=dict([(name,class_) for name,class_ in classes.items()]) 722 if self.allow_None: 723 d['None']=None 724 return d
725 726
727 -class List(Parameter):
728 """ 729 Parameter whose value is a list of objects, usually of a specified type. 730 731 The bounds allow a minimum and/or maximum length of 732 list to be enforced. If the class is non-None, all 733 items in the list are checked to be of that type. 734 """ 735 __slots__ = ['class_','bounds'] 736
737 - def __init__(self,default=[],class_=None,instantiate=True, 738 bounds=(0,None),**params):
744 745 # Could add range() method from ClassSelector, to allow 746 # list to be populated in the GUI 747
748 - def __set__(self,obj,val):
749 """Set to the given value, raising an exception if out of bounds.""" 750 self._check_bounds(val) 751 super(List,self).__set__(obj,val)
752
753 - def _check_bounds(self,val):
754 """ 755 Checks that the list is of the right length and has the right contents. 756 Otherwise, an exception is raised. 757 """ 758 if not (isinstance(val,list)): 759 raise ValueError("List '%s' must be a list."%(self._attrib_name)) 760 761 if self.bounds!=None: 762 min_length,max_length = self.bounds 763 l=len(val) 764 if min_length != None and max_length != None: 765 if not (min_length <= l <= max_length): 766 raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self._attrib_name,min_length,max_length)) 767 elif min_length != None: 768 if not min_length <= l: 769 raise ValueError("%s: list length must be at least %s."%(self._attrib_name,min_length)) 770 elif max_length != None: 771 if not l <= max_length: 772 raise ValueError("%s: list length must be at most %s."%(self._attrib_name,max_length)) 773 774 self._check_type(val)
775
776 - def _check_type(self,val):
777 if self.class_!=None: 778 for v in val: 779 assert isinstance(v,self.class_),repr(v)+" is not an instance of " + repr(self.class_) + "."
780 781 782
783 -class HookList(List):
784 """ 785 Parameter whose value is a list of callable objects. 786 787 This type of List Parameter is typically used to provide a place 788 for users to register a set of commands to be called at a 789 specified place in some sequence of processing steps. 790 """ 791 __slots__ = ['class_','bounds'] 792
793 - def _check_type(self,val):
794 for v in val: 795 assert callable(v),repr(v)+" is not callable."
796 797 798
799 -class Dict(ClassSelector):
800 """ 801 Parameter whose value is a dictionary. 802 """
803 - def __init__(self,**params):
804 super(Dict,self).__init__(dict,**params)
805 806 807 808 # For portable code: 809 # - specify paths in unix (rather than Windows) style; 810 # - use resolve_path() for paths to existing files to be read, 811 # and normalize_path() for paths to new files to be written. 812
813 -class resolve_path(ParameterizedFunction):
814 """ 815 Find the path to an existing file, searching the paths specified 816 in the search_paths parameter if the filename is not absolute, and 817 converting a UNIX-style path to the current OS's format if 818 necessary. 819 820 To turn a supplied relative path into an absolute one, the path is 821 appended to paths in the search_paths parameter, in order, until 822 the file is found. 823 824 An IOError is raised if the file is not found. 825 826 Similar to Python's os.path.abspath(), except more search paths 827 than just os.getcwd() can be used, and the file must exist. 828 """ 829 830 search_paths = List(default=[os.getcwd()],pickle_default_value=False,doc=""" 831 Prepended to a non-relative path, in order, until a file is 832 found.""") 833
834 - def __call__(self,path,**params):
835 p = ParamOverrides(self,params) 836 837 path = os.path.normpath(path) 838 839 if os.path.isabs(path): 840 if os.path.isfile(path): 841 return path 842 else: 843 raise IOError('File "%s" not found.'%path) 844 else: 845 paths_tried = [] 846 for prefix in p.search_paths: 847 try_path = os.path.join(os.path.normpath(prefix),path) 848 if os.path.isfile(try_path): 849 return try_path 850 paths_tried.append(try_path) 851 852 raise IOError('File "'+os.path.split(path)[1]+'" was not found in the following place(s): '+str(paths_tried)+'.')
853 854 855
856 -class normalize_path(ParameterizedFunction):
857 """ 858 Convert a UNIX-style path to the current OS's format, 859 typically for creating a new file or directory. 860 861 If the path is not already absolute, it will be made absolute 862 (using the prefix parameter). 863 864 Should do the same as Python's os.path.abspath(), except using 865 prefix rather than os.getcwd). 866 """ 867 868 prefix = String(default=os.getcwd(),pickle_default_value=False,doc=""" 869 Prepended to the specified path, if that path is not 870 absolute.""") 871
872 - def __call__(self,path="",**params):
873 p = ParamOverrides(self,params) 874 875 if not os.path.isabs(path): 876 path = os.path.join(os.path.normpath(p.prefix),path) 877 878 return os.path.normpath(path)
879 880 881
882 -class Filename(Parameter):
883 """ 884 Parameter that can be set to a string specifying the 885 path of a file (in unix style); returns it in the format of 886 the user's operating system. 887 888 The specified path can be absolute, or relative to either: 889 890 * any of the paths specified in the search_paths attribute (if 891 search_paths is not None); 892 or 893 894 * any of the paths searched by resolve_path() (if search_paths 895 is None). 896 """ 897 __slots__ = ['search_paths'] 898
899 - def __init__(self,default=None,search_paths=None,**params):
900 if search_paths is None: 901 search_paths = [] 902 self.search_paths = search_paths 903 super(Filename,self).__init__(default,**params)
904
905 - def _resolve(self,pth):
906 if self.search_paths: 907 return resolve_path(pth,search_paths=self.search_paths) 908 else: 909 return resolve_path(pth)
910
911 - def __set__(self,obj,val):
912 """ 913 Call Parameter's __set__, but warn if the file cannot be found. 914 """ 915 try: 916 self._resolve(val) 917 except IOError, e: 918 Parameterized(name="%s.%s"%(obj.name,self._attrib_name)).warning('%s'%(e.args[0])) 919 920 super(Filename,self).__set__(obj,val)
921
922 - def __get__(self,obj,objtype):
923 """ 924 Return an absolute, normalized path (see resolve_path). 925 """ 926 raw_path = super(Filename,self).__get__(obj,objtype) 927 return self._resolve(raw_path)
928
929 - def __getstate__(self):