traitlets.py
322 lines
| 6.7 KiB
| text/x-python
|
PythonLexer
Stefan van der Walt
|
r1253 | """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 | ||||