"""Traitlets -- a light-weight meta-class free stand-in for Traits. Traitlet behaviour ================== - Automatic casting, equivalent to traits.C* classes, e.g. CFloat, CBool etc. - By default, validation is done by attempting to cast a given value to the underlying type, e.g. bool for Bool, float for Float etc. - To set or get a Traitlet value, use the ()-operator. E.g. >>> b = Bool(False) >>> b(True) >>> print b # returns a string representation of the Traitlet True >>> print b() # returns the underlying bool object True This makes it possible to change values "in-place", unlike an assigment of the form >>> c = Bool(False) >>> c = True which results in >>> print type(b), type(c) - Each Traitlet keeps track of its modification state, e.g. >>> c = Bool(False) >>> print c.modified False >>> c(False) >>> print c.modified False >>> c(True) >>> print c.modified True How to customize Traitlets ========================== The easiest way to create a new Traitlet is by wrapping an underlying Python type. This is done by setting the "_type" class attribute. For example, creating an int-like Traitlet is done as follows: >>> class MyInt(Traitlet): ... _type = int >>> i = MyInt(3) >>> i(4) >>> print i 4 >>> try: ... i('a') ... except ValidationError: ... pass # this is expected ... else: ... "This should not be reached." Furthermore, the following methods are provided for finer grained control of validation and assignment: - validate(self,value) Ensure that "value" is valid. If not, raise an exception of any kind with a suitable error message, which is reported to the user. - prepare_value(self) When using the ()-operator to query the underlying Traitlet value, that value is first passed through prepare_value. For example: >>> class Eval(Traitlet): ... _type = str ... ... def prepare_value(self): ... return eval(self._value) >>> x = Eval('1+1') >>> print x '1+1' >>> print x() 2 - __repr__(self) By default, repr(self._value) is returned. This can be customised to, for example, first call prepare_value and return the repr of the resulting object. """ import re import types class ValidationError(Exception): pass class Traitlet(object): """Traitlet which knows its modification state. """ def __init__(self, value): "Validate and store the default value. State is 'unmodified'." self._type = getattr(self,'_type',None) value = self._parse_validation(value) self._default_value = value self.reset() def reset(self): self._value = self._default_value self._changed = False def validate(self, value): "Validate the given value." if self._type is not None: self._type(value) def _parse_validation(self, value): """Run validation and return a descriptive error if needed. """ try: self.validate(value) except Exception, e: err_message = 'Cannot convert "%s" to %s' % \ (value, self.__class__.__name__.lower()) if e.message: err_message += ': %s' % e.message raise ValidationError(err_message) else: # Cast to appropriate type before storing if self._type is not None: value = self._type(value) return value def prepare_value(self): """Run on value before it is ever returned to the user. """ return self._value def __call__(self,value=None): """Query or set value depending on whether `value` is specified. """ if value is None: return self.prepare_value() self._value = self._parse_validation(value) self._changed = (self._value != self._default_value) @property def modified(self): "Whether value has changed from original definition." return self._changed def __repr__(self): """This class is represented by the underlying repr. Used when dumping value to file. """ return repr(self._value) class Float(Traitlet): """ >>> f = Float(0) >>> print f.modified False >>> f(3) >>> print f() 3.0 >>> print f.modified True >>> f(0) >>> print f() 0.0 >>> print f.modified False >>> try: ... f('a') ... except ValidationError: ... pass """ _type = float class Enum(Traitlet): """ >>> c = Enum('a','b','c') >>> print c() a >>> try: ... c('unknown') ... except ValidationError: ... pass >>> print c.modified False >>> c('b') >>> print c() b """ def __init__(self, *options): self._options = options super(Enum,self).__init__(options[0]) def validate(self, value): if not value in self._options: raise ValueError('must be one of %s' % str(self._options)) class Module(Traitlet): """ >>> m = Module('some.unknown.module') >>> print m 'some.unknown.module' >>> m = Module('re') >>> assert type(m()) is types.ModuleType """ _type = str def prepare_value(self): try: module = eval(self._value) except: module = None if type(module) is not types.ModuleType: raise ValueError("Invalid module name: %s" % self._value) else: return module class URI(Traitlet): """ >>> u = URI('http://') >>> try: ... u = URI('something.else') ... except ValidationError: ... pass >>> u = URI('http://ipython.scipy.org/') >>> print u 'http://ipython.scipy.org/' """ _regexp = re.compile(r'^[a-zA-Z]+:\/\/') _type = str def validate(self, uri): if not self._regexp.match(uri): raise ValueError() class Int(Traitlet): """ >>> i = Int(3.5) >>> print i 3 >>> print i() 3 >>> i = Int('4') >>> print i 4 >>> try: ... i = Int('a') ... except ValidationError: ... pass ... else: ... raise "Should fail" """ _type = int class Bool(Traitlet): """ >>> b = Bool(2) >>> print b True >>> print b() True >>> b = Bool('True') >>> print b True >>> b(True) >>> print b.modified False >>> print Bool(0) False """ _type = bool class Unicode(Traitlet): """ >>> u = Unicode(123) >>> print u u'123' >>> u = Unicode('123') >>> print u.modified False >>> u('hello world') >>> print u u'hello world' """ _type = unicode