|
|
"""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)
|
|
|
<class 'IPython.config.traitlets.Bool'> <type 'bool'>
|
|
|
|
|
|
- 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
|
|
|
|