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
20
21
22 import os.path
23
24 from parameterized import Parameterized, Parameter, String, \
25 descendents, ParameterizedFunction, ParamOverrides
26
27
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
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
68
69
70
71
72
73 time_fn = None
74
75
76
77
78
79
90
91
93 """
94 Add 'last time' and 'last value' attributes to the generator.
95 """
96
97 if hasattr(obj,"_Dynamic_time_fn"):
98 gen._Dynamic_time_fn = obj._Dynamic_time_fn
99
100 gen._Dynamic_last = None
101
102
103 gen._Dynamic_time = -1
104
105 gen._saved_Dynamic_last = []
106 gen._saved_Dynamic_time = []
107
108
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
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
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
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
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):
198
199
200
201
202
203 import operator
204 is_number = operator.isNumberType
205
206
207
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):
264
265
277
278
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
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
301
302
303
304
305
306
307
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
321
322
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
340 return self.default
341
342 return val
343
344
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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
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
425
426
428
429 - def __init__(self,default=1.0,softbounds=None,**params):
431
432
433
435 __slots__ = ['bounds','allow_None']
436
437
438 - def __init__(self,default=False,bounds=(0,1),allow_None=False,**params):
442
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
462 __slots__ = ['length']
463
464 - def __init__(self,default=(0,0),length=None,**params):
478
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
493
494
496
497 - def __init__(self,default=(0.0,0.0),**params):
499
500
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 """
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
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
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
548
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
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
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
582 raise NotImplementedError("get_range() must be implemented in subclasses.")
583
584
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
604
605 - def __init__(self,default=None,objects=None,instantiate=False,
606 compute_default_fn=None,check_on_set=None,**params):
623
624
625
626
627
640
641
643 """
644 val must be None or one of the objects in self.objects.
645 """
646 if not val in self.objects:
647
648
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
657
658
659
660
665
666
667
669 """
670 Return the possible objects to which this parameter could be set.
671
672 (Returns the dictionary {object.name:object}.)
673 """
674
675
676
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
685 """
686 Parameter whose value is an instance of the specified class.
687 """
688
689 __slots__ = ['class_','allow_None']
690
691 - def __init__(self,class_,default=None,instantiate=True,allow_None=False,**params):
696
697
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
708
709
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
746
747
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
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
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
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
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 """
805
806
807
808
809
810
811
812
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
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
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
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
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):
904
910
921
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