traitlets.py
1013 lines
| 31.9 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2157 | #!/usr/bin/env python | ||
# encoding: utf-8 | ||||
""" | ||||
A lightweight Traits like module. | ||||
Brian Granger
|
r2175 | This is designed to provide a lightweight, simple, pure Python version of | ||
many of the capabilities of enthought.traits. This includes: | ||||
* Validation | ||||
* Type specification with defaults | ||||
* Static and dynamic notification | ||||
* Basic predefined types | ||||
* An API that is similar to enthought.traits | ||||
We don't support: | ||||
* Delegation | ||||
* Automatic GUI generation | ||||
Brian Granger
|
r2179 | * A full set of trait types. Most importantly, we don't provide container | ||
traitlets (list, dict, tuple) that can trigger notifications if their | ||||
contents change. | ||||
Brian Granger
|
r2175 | * API compatibility with enthought.traits | ||
Brian Granger
|
r2179 | There are also some important difference in our design: | ||
* enthought.traits does not validate default values. We do. | ||||
Brian Granger
|
r2175 | We choose to create this module because we need these capabilities, but | ||
we need them to be pure Python so they work in all Python implementations, | ||||
including Jython and IronPython. | ||||
Brian Granger
|
r2157 | Authors: | ||
* Brian Granger | ||||
* Enthought, Inc. Some of the code in this file comes from enthought.traits | ||||
and is licensed under the BSD license. Also, many of the ideas also come | ||||
from enthought.traits even though our implementation is very different. | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2008-2009 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2157 | import inspect | ||
Brian Granger
|
r2177 | import sys | ||
Brian Granger
|
r2157 | import types | ||
Brian Granger
|
r2204 | from types import ( | ||
InstanceType, ClassType, FunctionType, | ||||
ListType, TupleType | ||||
) | ||||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2229 | from IPython.utils.importstring import import_item | ||
Brian Granger
|
r2177 | ClassTypes = (ClassType, type) | ||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2204 | SequenceTypes = (ListType, TupleType) | ||
Brian Granger
|
r2157 | #----------------------------------------------------------------------------- | ||
# Basic classes | ||||
#----------------------------------------------------------------------------- | ||||
class NoDefaultSpecified ( object ): pass | ||||
NoDefaultSpecified = NoDefaultSpecified() | ||||
class Undefined ( object ): pass | ||||
Undefined = Undefined() | ||||
class TraitletError(Exception): | ||||
pass | ||||
#----------------------------------------------------------------------------- | ||||
# Utilities | ||||
#----------------------------------------------------------------------------- | ||||
def class_of ( object ): | ||||
""" Returns a string containing the class name of an object with the | ||||
correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image', | ||||
'a PlotValue'). | ||||
""" | ||||
if isinstance( object, basestring ): | ||||
return add_article( object ) | ||||
return add_article( object.__class__.__name__ ) | ||||
def add_article ( name ): | ||||
""" Returns a string containing the correct indefinite article ('a' or 'an') | ||||
prefixed to the specified string. | ||||
""" | ||||
if name[:1].lower() in 'aeiou': | ||||
return 'an ' + name | ||||
return 'a ' + name | ||||
def repr_type(obj): | ||||
""" Return a string representation of a value and its type for readable | ||||
error messages. | ||||
""" | ||||
the_type = type(obj) | ||||
if the_type is InstanceType: | ||||
# Old-style class. | ||||
the_type = obj.__class__ | ||||
msg = '%r %r' % (obj, the_type) | ||||
return msg | ||||
def parse_notifier_name(name): | ||||
Brian Granger
|
r2175 | """Convert the name argument to a list of names. | ||
Examples | ||||
-------- | ||||
>>> parse_notifier_name('a') | ||||
['a'] | ||||
>>> parse_notifier_name(['a','b']) | ||||
['a', 'b'] | ||||
>>> parse_notifier_name(None) | ||||
['anytraitlet'] | ||||
""" | ||||
Brian Granger
|
r2157 | if isinstance(name, str): | ||
return [name] | ||||
elif name is None: | ||||
return ['anytraitlet'] | ||||
elif isinstance(name, (list, tuple)): | ||||
for n in name: | ||||
assert isinstance(n, str), "names must be strings" | ||||
return name | ||||
Brian Granger
|
r2184 | |||
class _SimpleTest: | ||||
def __init__ ( self, value ): self.value = value | ||||
def __call__ ( self, test ): | ||||
return test == self.value | ||||
def __repr__(self): | ||||
return "<SimpleTest(%r)" % self.value | ||||
def __str__(self): | ||||
return self.__repr__() | ||||
Brian Granger
|
r2287 | def getmembers(object, predicate=None): | ||
"""A safe version of inspect.getmembers that handles missing attributes. | ||||
This is useful when there are descriptor based attributes that for | ||||
some reason raise AttributeError even though they exist. This happens | ||||
in zope.inteface with the __provides__ attribute. | ||||
""" | ||||
results = [] | ||||
for key in dir(object): | ||||
try: | ||||
value = getattr(object, key) | ||||
except AttributeError: | ||||
pass | ||||
else: | ||||
if not predicate or predicate(value): | ||||
results.append((key, value)) | ||||
results.sort() | ||||
return results | ||||
Brian Granger
|
r2157 | #----------------------------------------------------------------------------- | ||
# Base TraitletType for all traitlets | ||||
#----------------------------------------------------------------------------- | ||||
class TraitletType(object): | ||||
Brian Granger
|
r2183 | """A base class for all traitlet descriptors. | ||
Notes | ||||
----- | ||||
Our implementation of traitlets is based on Python's descriptor | ||||
prototol. This class is the base class for all such descriptors. The | ||||
only magic we use is a custom metaclass for the main :class:`HasTraitlets` | ||||
class that does the following: | ||||
1. Sets the :attr:`name` attribute of every :class:`TraitletType` | ||||
instance in the class dict to the name of the attribute. | ||||
2. Sets the :attr:`this_class` attribute of every :class:`TraitletType` | ||||
instance in the class dict to the *class* that declared the traitlet. | ||||
This is used by the :class:`This` traitlet to allow subclasses to | ||||
accept superclasses for :class:`This` values. | ||||
""" | ||||
Brian Granger
|
r2157 | |||
metadata = {} | ||||
Brian Granger
|
r2177 | default_value = Undefined | ||
Brian Granger
|
r2157 | info_text = 'any value' | ||
def __init__(self, default_value=NoDefaultSpecified, **metadata): | ||||
Brian Granger
|
r2177 | """Create a TraitletType. | ||
""" | ||||
Brian Granger
|
r2157 | if default_value is not NoDefaultSpecified: | ||
self.default_value = default_value | ||||
Brian Granger
|
r2184 | |||
if len(metadata) > 0: | ||||
if len(self.metadata) > 0: | ||||
self._metadata = self.metadata.copy() | ||||
self._metadata.update(metadata) | ||||
else: | ||||
self._metadata = metadata | ||||
else: | ||||
self._metadata = self.metadata | ||||
Brian Granger
|
r2177 | self.init() | ||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2177 | def init(self): | ||
pass | ||||
def get_default_value(self): | ||||
"""Create a new instance of the default value.""" | ||||
dv = self.default_value | ||||
return dv | ||||
Brian Granger
|
r2229 | def instance_init(self, obj): | ||
"""This is called by :meth:`HasTraitlets.__new__` to finish init'ing. | ||||
Some stages of initialization must be delayed until the parent | ||||
:class:`HasTraitlets` instance has been created. This method is | ||||
called in :meth:`HasTraitlets.__new__` after the instance has been | ||||
created. | ||||
This method trigger the creation and validation of default values | ||||
and also things like the resolution of str given class names in | ||||
:class:`Type` and :class`Instance`. | ||||
Parameters | ||||
---------- | ||||
obj : :class:`HasTraitlets` instance | ||||
The parent :class:`HasTraitlets` instance that has just been | ||||
created. | ||||
""" | ||||
self.set_default_value(obj) | ||||
Brian Granger
|
r2182 | def set_default_value(self, obj): | ||
Brian Granger
|
r2229 | """Set the default value on a per instance basis. | ||
This method is called by :meth:`instance_init` to create and | ||||
validate the default value. The creation and validation of | ||||
default values must be delayed until the parent :class:`HasTraitlets` | ||||
class has been instantiated. | ||||
""" | ||||
Brian Granger
|
r2182 | dv = self.get_default_value() | ||
newdv = self._validate(obj, dv) | ||||
obj._traitlet_values[self.name] = newdv | ||||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2182 | def __get__(self, obj, cls=None): | ||
"""Get the value of the traitlet by self.name for the instance. | ||||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2182 | Default values are instantiated when :meth:`HasTraitlets.__new__` | ||
is called. Thus by the time this method gets called either the | ||||
default value or a user defined value (they called :meth:`__set__`) | ||||
is in the :class:`HasTraitlets` instance. | ||||
Brian Granger
|
r2177 | """ | ||
if obj is None: | ||||
Brian Granger
|
r2157 | return self | ||
else: | ||||
Brian Granger
|
r2182 | try: | ||
value = obj._traitlet_values[self.name] | ||||
except: | ||||
# HasTraitlets should call set_default_value to populate | ||||
# this. So this should never be reached. | ||||
raise TraitletError('Unexpected error in TraitletType: ' | ||||
'default value not set properly') | ||||
Brian Granger
|
r2177 | else: | ||
Brian Granger
|
r2182 | return value | ||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2182 | def __set__(self, obj, value): | ||
Brian Granger
|
r2177 | new_value = self._validate(obj, value) | ||
Brian Granger
|
r2182 | old_value = self.__get__(obj) | ||
if old_value != new_value: | ||||
Brian Granger
|
r2177 | obj._traitlet_values[self.name] = new_value | ||
Brian Granger
|
r2182 | obj._notify_traitlet(self.name, old_value, new_value) | ||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2177 | def _validate(self, obj, value): | ||
Brian Granger
|
r2157 | if hasattr(self, 'validate'): | ||
Brian Granger
|
r2177 | return self.validate(obj, value) | ||
Brian Granger
|
r2157 | elif hasattr(self, 'is_valid_for'): | ||
valid = self.is_valid_for(value) | ||||
if valid: | ||||
return value | ||||
else: | ||||
raise TraitletError('invalid value for type: %r' % value) | ||||
elif hasattr(self, 'value_for'): | ||||
return self.value_for(value) | ||||
else: | ||||
return value | ||||
def info(self): | ||||
return self.info_text | ||||
def error(self, obj, value): | ||||
if obj is not None: | ||||
e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \ | ||||
% (self.name, class_of(obj), | ||||
self.info(), repr_type(value)) | ||||
else: | ||||
e = "The '%s' traitlet must be %s, but a value of %r was specified." \ | ||||
% (self.name, self.info(), repr_type(value)) | ||||
raise TraitletError(e) | ||||
Brian Granger
|
r2184 | def get_metadata(self, key): | ||
return getattr(self, '_metadata', {}).get(key, None) | ||||
def set_metadata(self, key, value): | ||||
getattr(self, '_metadata', {})[key] = value | ||||
Brian Granger
|
r2157 | |||
#----------------------------------------------------------------------------- | ||||
# The HasTraitlets implementation | ||||
#----------------------------------------------------------------------------- | ||||
class MetaHasTraitlets(type): | ||||
"""A metaclass for HasTraitlets. | ||||
This metaclass makes sure that any TraitletType class attributes are | ||||
instantiated and sets their name attribute. | ||||
""" | ||||
def __new__(mcls, name, bases, classdict): | ||||
Brian Granger
|
r2183 | """Create the HasTraitlets class. | ||
This instantiates all TraitletTypes in the class dict and sets their | ||||
:attr:`name` attribute. | ||||
""" | ||||
Brian Granger
|
r2287 | # print "MetaHasTraitlets (mcls, name): ", mcls, name | ||
# print "MetaHasTraitlets (bases): ", bases | ||||
# print "MetaHasTraitlets (classdict): ", classdict | ||||
Brian Granger
|
r2157 | for k,v in classdict.iteritems(): | ||
if isinstance(v, TraitletType): | ||||
v.name = k | ||||
elif inspect.isclass(v): | ||||
if issubclass(v, TraitletType): | ||||
vinst = v() | ||||
vinst.name = k | ||||
classdict[k] = vinst | ||||
return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict) | ||||
Brian Granger
|
r2183 | def __init__(cls, name, bases, classdict): | ||
"""Finish initializing the HasTraitlets class. | ||||
This sets the :attr:`this_class` attribute of each TraitletType in the | ||||
class dict to the newly created class ``cls``. | ||||
""" | ||||
for k, v in classdict.iteritems(): | ||||
if isinstance(v, TraitletType): | ||||
v.this_class = cls | ||||
super(MetaHasTraitlets, cls).__init__(name, bases, classdict) | ||||
Brian Granger
|
r2157 | |||
class HasTraitlets(object): | ||||
__metaclass__ = MetaHasTraitlets | ||||
Brian Granger
|
r2182 | def __new__(cls, *args, **kw): | ||
Brian Granger
|
r2255 | # This is needed because in Python 2.6 object.__new__ only accepts | ||
# the cls argument. | ||||
new_meth = super(HasTraitlets, cls).__new__ | ||||
if new_meth is object.__new__: | ||||
inst = new_meth(cls) | ||||
else: | ||||
inst = new_meth(cls, *args, **kw) | ||||
Brian Granger
|
r2182 | inst._traitlet_values = {} | ||
inst._traitlet_notifiers = {} | ||||
# Here we tell all the TraitletType instances to set their default | ||||
# values on the instance. | ||||
for key in dir(cls): | ||||
Brian Granger
|
r2287 | # Some descriptors raise AttributeError like zope.interface's | ||
# __provides__ attributes even though they exist. This causes | ||||
# AttributeErrors even though they are listed in dir(cls). | ||||
try: | ||||
value = getattr(cls, key) | ||||
except AttributeError: | ||||
pass | ||||
else: | ||||
if isinstance(value, TraitletType): | ||||
value.instance_init(inst) | ||||
Brian Granger
|
r2182 | return inst | ||
# def __init__(self): | ||||
# self._traitlet_values = {} | ||||
# self._traitlet_notifiers = {} | ||||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2179 | def _notify_traitlet(self, name, old_value, new_value): | ||
Brian Granger
|
r2175 | |||
# First dynamic ones | ||||
callables = self._traitlet_notifiers.get(name,[]) | ||||
more_callables = self._traitlet_notifiers.get('anytraitlet',[]) | ||||
Brian Granger
|
r2157 | callables.extend(more_callables) | ||
Brian Granger
|
r2175 | |||
# Now static ones | ||||
try: | ||||
cb = getattr(self, '_%s_changed' % name) | ||||
except: | ||||
pass | ||||
else: | ||||
callables.append(cb) | ||||
# Call them all now | ||||
Brian Granger
|
r2157 | for c in callables: | ||
# Traits catches and logs errors here. I allow them to raise | ||||
Brian Granger
|
r2175 | if callable(c): | ||
argspec = inspect.getargspec(c) | ||||
nargs = len(argspec[0]) | ||||
# Bound methods have an additional 'self' argument | ||||
# I don't know how to treat unbound methods, but they | ||||
# can't really be used for callbacks. | ||||
if isinstance(c, types.MethodType): | ||||
offset = -1 | ||||
else: | ||||
offset = 0 | ||||
if nargs + offset == 0: | ||||
c() | ||||
elif nargs + offset == 1: | ||||
c(name) | ||||
elif nargs + offset == 2: | ||||
c(name, new_value) | ||||
elif nargs + offset == 3: | ||||
c(name, old_value, new_value) | ||||
else: | ||||
raise TraitletError('a traitlet changed callback ' | ||||
'must have 0-3 arguments.') | ||||
else: | ||||
raise TraitletError('a traitlet changed callback ' | ||||
'must be callable.') | ||||
Brian Granger
|
r2157 | |||
def _add_notifiers(self, handler, name): | ||||
Brian Granger
|
r2175 | if not self._traitlet_notifiers.has_key(name): | ||
Brian Granger
|
r2157 | nlist = [] | ||
Brian Granger
|
r2175 | self._traitlet_notifiers[name] = nlist | ||
Brian Granger
|
r2157 | else: | ||
Brian Granger
|
r2175 | nlist = self._traitlet_notifiers[name] | ||
Brian Granger
|
r2157 | if handler not in nlist: | ||
nlist.append(handler) | ||||
def _remove_notifiers(self, handler, name): | ||||
Brian Granger
|
r2175 | if self._traitlet_notifiers.has_key(name): | ||
nlist = self._traitlet_notifiers[name] | ||||
Brian Granger
|
r2157 | try: | ||
index = nlist.index(handler) | ||||
except ValueError: | ||||
pass | ||||
else: | ||||
del nlist[index] | ||||
def on_traitlet_change(self, handler, name=None, remove=False): | ||||
Brian Granger
|
r2175 | """Setup a handler to be called when a traitlet changes. | ||
This is used to setup dynamic notifications of traitlet changes. | ||||
Static handlers can be created by creating methods on a HasTraitlets | ||||
subclass with the naming convention '_[traitletname]_changed'. Thus, | ||||
to create static handler for the traitlet 'a', create the method | ||||
_a_changed(self, name, old, new) (fewer arguments can be used, see | ||||
below). | ||||
Parameters | ||||
---------- | ||||
Brian Granger
|
r2277 | handler : callable | ||
A callable that is called when a traitlet changes. Its | ||||
signature can be handler(), handler(name), handler(name, new) | ||||
or handler(name, old, new). | ||||
name : list, str, None | ||||
If None, the handler will apply to all traitlets. If a list | ||||
of str, handler will apply to all names in the list. If a | ||||
str, the handler will apply just to that name. | ||||
remove : bool | ||||
If False (the default), then install the handler. If True | ||||
then unintall it. | ||||
Brian Granger
|
r2175 | """ | ||
Brian Granger
|
r2157 | if remove: | ||
names = parse_notifier_name(name) | ||||
for n in names: | ||||
self._remove_notifiers(handler, n) | ||||
else: | ||||
names = parse_notifier_name(name) | ||||
for n in names: | ||||
self._add_notifiers(handler, n) | ||||
Brian Granger
|
r2184 | def traitlet_names(self, **metadata): | ||
Brian Granger
|
r2179 | """Get a list of all the names of this classes traitlets.""" | ||
Brian Granger
|
r2184 | return self.traitlets(**metadata).keys() | ||
Brian Granger
|
r2245 | def traitlets(self, **metadata): | ||
Brian Granger
|
r2184 | """Get a list of all the traitlets of this class. | ||
The TraitletTypes returned don't know anything about the values | ||||
that the various HasTraitlet's instances are holding. | ||||
Brian Granger
|
r2245 | |||
This follows the same algorithm as traits does and does not allow | ||||
for any simple way of specifying merely that a metadata name | ||||
exists, but has any value. This is because get_metadata returns | ||||
None if a metadata key doesn't exist. | ||||
Brian Granger
|
r2184 | """ | ||
Brian Granger
|
r2287 | traitlets = dict([memb for memb in getmembers(self.__class__) if \ | ||
Brian Granger
|
r2184 | isinstance(memb[1], TraitletType)]) | ||
Brian Granger
|
r2245 | if len(metadata) == 0: | ||
return traitlets | ||||
Brian Granger
|
r2200 | |||
Brian Granger
|
r2184 | for meta_name, meta_eval in metadata.items(): | ||
if type(meta_eval) is not FunctionType: | ||||
metadata[meta_name] = _SimpleTest(meta_eval) | ||||
result = {} | ||||
for name, traitlet in traitlets.items(): | ||||
for meta_name, meta_eval in metadata.items(): | ||||
if not meta_eval(traitlet.get_metadata(meta_name)): | ||||
break | ||||
else: | ||||
result[name] = traitlet | ||||
return result | ||||
Brian Granger
|
r2179 | |||
Brian Granger
|
r2184 | def traitlet_metadata(self, traitletname, key): | ||
"""Get metadata values for traitlet by key.""" | ||||
try: | ||||
traitlet = getattr(self.__class__, traitletname) | ||||
except AttributeError: | ||||
raise TraitletError("Class %s does not have a traitlet named %s" % | ||||
(self.__class__.__name__, traitletname)) | ||||
else: | ||||
return traitlet.get_metadata(key) | ||||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2157 | #----------------------------------------------------------------------------- | ||
# Actual TraitletTypes implementations/subclasses | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2177 | |||
#----------------------------------------------------------------------------- | ||||
# TraitletTypes subclasses for handling classes and instances of classes | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2182 | class ClassBasedTraitletType(TraitletType): | ||
"""A traitlet with error reporting for Type, Instance and This.""" | ||||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2182 | def error(self, obj, value): | ||
Brian Granger
|
r2177 | kind = type(value) | ||
if kind is InstanceType: | ||||
msg = 'class %s' % value.__class__.__name__ | ||||
else: | ||||
msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) | ||||
Brian Granger
|
r2182 | super(ClassBasedTraitletType, self).error(obj, msg) | ||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2182 | class Type(ClassBasedTraitletType): | ||
Brian Granger
|
r2177 | """A traitlet whose value must be a subclass of a specified class.""" | ||
def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ): | ||||
"""Construct a Type traitlet | ||||
A Type traitlet specifies that its values must be subclasses of | ||||
a particular class. | ||||
Brian Granger
|
r2229 | If only ``default_value`` is given, it is used for the ``klass`` as | ||
well. | ||||
Brian Granger
|
r2177 | Parameters | ||
---------- | ||||
Brian Granger
|
r2229 | default_value : class, str or None | ||
The default value must be a subclass of klass. If an str, | ||||
the str must be a fully specified class name, like 'foo.bar.Bah'. | ||||
The string is resolved into real class, when the parent | ||||
:class:`HasTraitlets` class is instantiated. | ||||
Brian Granger
|
r2177 | klass : class, str, None | ||
Values of this traitlet must be a subclass of klass. The klass | ||||
may be specified in a string like: 'foo.bar.MyClass'. | ||||
Brian Granger
|
r2229 | The string is resolved into real class, when the parent | ||
:class:`HasTraitlets` class is instantiated. | ||||
Brian Granger
|
r2177 | allow_none : boolean | ||
Indicates whether None is allowed as an assignable value. Even if | ||||
``False``, the default value may be ``None``. | ||||
""" | ||||
if default_value is None: | ||||
if klass is None: | ||||
klass = object | ||||
elif klass is None: | ||||
klass = default_value | ||||
Brian Granger
|
r2229 | if not (inspect.isclass(klass) or isinstance(klass, basestring)): | ||
Brian Granger
|
r2177 | raise TraitletError("A Type traitlet must specify a class.") | ||
self.klass = klass | ||||
self._allow_none = allow_none | ||||
super(Type, self).__init__(default_value, **metadata) | ||||
def validate(self, obj, value): | ||||
"""Validates that the value is a valid object instance.""" | ||||
try: | ||||
if issubclass(value, self.klass): | ||||
return value | ||||
except: | ||||
if (value is None) and (self._allow_none): | ||||
return value | ||||
self.error(obj, value) | ||||
def info(self): | ||||
""" Returns a description of the trait.""" | ||||
Brian Granger
|
r2229 | if isinstance(self.klass, basestring): | ||
klass = self.klass | ||||
else: | ||||
klass = self.klass.__name__ | ||||
Brian Granger
|
r2177 | result = 'a subclass of ' + klass | ||
if self._allow_none: | ||||
return result + ' or None' | ||||
return result | ||||
Brian Granger
|
r2229 | def instance_init(self, obj): | ||
self._resolve_classes() | ||||
super(Type, self).instance_init(obj) | ||||
def _resolve_classes(self): | ||||
if isinstance(self.klass, basestring): | ||||
self.klass = import_item(self.klass) | ||||
if isinstance(self.default_value, basestring): | ||||
self.default_value = import_item(self.default_value) | ||||
def get_default_value(self): | ||||
return self.default_value | ||||
Brian Granger
|
r2177 | |||
class DefaultValueGenerator(object): | ||||
"""A class for generating new default value instances.""" | ||||
Brian Granger
|
r2229 | def __init__(self, *args, **kw): | ||
Brian Granger
|
r2177 | self.args = args | ||
self.kw = kw | ||||
Brian Granger
|
r2229 | def generate(self, klass): | ||
return klass(*self.args, **self.kw) | ||||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2182 | |||
class Instance(ClassBasedTraitletType): | ||||
Brian Granger
|
r2177 | """A trait whose value must be an instance of a specified class. | ||
The value can also be an instance of a subclass of the specified class. | ||||
""" | ||||
Brian Granger
|
r2182 | def __init__(self, klass=None, args=None, kw=None, | ||
allow_none=True, **metadata ): | ||||
Brian Granger
|
r2177 | """Construct an Instance traitlet. | ||
Brian Granger
|
r2182 | This traitlet allows values that are instances of a particular | ||
class or its sublclasses. Our implementation is quite different | ||||
from that of enthough.traits as we don't allow instances to be used | ||||
for klass and we handle the ``args`` and ``kw`` arguments differently. | ||||
Brian Granger
|
r2177 | Parameters | ||
---------- | ||||
Brian Granger
|
r2229 | klass : class, str | ||
The class that forms the basis for the traitlet. Class names | ||||
can also be specified as strings, like 'foo.bar.Bar'. | ||||
Brian Granger
|
r2177 | args : tuple | ||
Positional arguments for generating the default value. | ||||
kw : dict | ||||
Keyword arguments for generating the default value. | ||||
allow_none : bool | ||||
Indicates whether None is allowed as a value. | ||||
Default Value | ||||
------------- | ||||
Brian Granger
|
r2182 | If both ``args`` and ``kw`` are None, then the default value is None. | ||
If ``args`` is a tuple and ``kw`` is a dict, then the default is | ||||
created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is | ||||
not (but not both), None is replace by ``()`` or ``{}``. | ||||
Brian Granger
|
r2177 | """ | ||
self._allow_none = allow_none | ||||
Brian Granger
|
r2182 | |||
Brian Granger
|
r2229 | if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))): | ||
Brian Granger
|
r2182 | raise TraitletError('The klass argument must be a class' | ||
' you gave: %r' % klass) | ||||
self.klass = klass | ||||
# self.klass is a class, so handle default_value | ||||
if args is None and kw is None: | ||||
Brian Granger
|
r2177 | default_value = None | ||
else: | ||||
if args is None: | ||||
Brian Granger
|
r2182 | # kw is not None | ||
Brian Granger
|
r2177 | args = () | ||
Brian Granger
|
r2182 | elif kw is None: | ||
# args is not None | ||||
kw = {} | ||||
Brian Granger
|
r2177 | if not isinstance(kw, dict): | ||
Brian Granger
|
r2182 | raise TraitletError("The 'kw' argument must be a dict or None.") | ||
Brian Granger
|
r2177 | if not isinstance(args, tuple): | ||
Brian Granger
|
r2182 | raise TraitletError("The 'args' argument must be a tuple or None.") | ||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2229 | default_value = DefaultValueGenerator(*args, **kw) | ||
Brian Granger
|
r2177 | |||
super(Instance, self).__init__(default_value, **metadata) | ||||
def validate(self, obj, value): | ||||
if value is None: | ||||
if self._allow_none: | ||||
return value | ||||
Brian Granger
|
r2182 | self.error(obj, value) | ||
Brian Granger
|
r2177 | |||
if isinstance(value, self.klass): | ||||
return value | ||||
else: | ||||
Brian Granger
|
r2182 | self.error(obj, value) | ||
Brian Granger
|
r2177 | |||
Brian Granger
|
r2182 | def info(self): | ||
Brian Granger
|
r2229 | if isinstance(self.klass, basestring): | ||
klass = self.klass | ||||
else: | ||||
klass = self.klass.__name__ | ||||
Brian Granger
|
r2177 | result = class_of(klass) | ||
if self._allow_none: | ||||
return result + ' or None' | ||||
return result | ||||
Brian Granger
|
r2229 | def instance_init(self, obj): | ||
self._resolve_classes() | ||||
super(Instance, self).instance_init(obj) | ||||
def _resolve_classes(self): | ||||
if isinstance(self.klass, basestring): | ||||
self.klass = import_item(self.klass) | ||||
Brian Granger
|
r2182 | def get_default_value(self): | ||
Brian Granger
|
r2177 | """Instantiate a default value instance. | ||
Brian Granger
|
r2182 | This is called when the containing HasTraitlets classes' | ||
:meth:`__new__` method is called to ensure that a unique instance | ||||
is created for each HasTraitlets instance. | ||||
Brian Granger
|
r2177 | """ | ||
dv = self.default_value | ||||
if isinstance(dv, DefaultValueGenerator): | ||||
Brian Granger
|
r2229 | return dv.generate(self.klass) | ||
Brian Granger
|
r2177 | else: | ||
return dv | ||||
Brian Granger
|
r2182 | class This(ClassBasedTraitletType): | ||
"""A traitlet for instances of the class containing this trait. | ||||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2182 | Because how how and when class bodies are executed, the ``This`` | ||
traitlet can only have a default value of None. This, and because we | ||||
always validate default values, ``allow_none`` is *always* true. | ||||
""" | ||||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2182 | info_text = 'an instance of the same type as the receiver or None' | ||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2182 | def __init__(self, **metadata): | ||
super(This, self).__init__(None, **metadata) | ||||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2182 | def validate(self, obj, value): | ||
Brian Granger
|
r2183 | # What if value is a superclass of obj.__class__? This is | ||
# complicated if it was the superclass that defined the This | ||||
# traitlet. | ||||
if isinstance(value, self.this_class) or (value is None): | ||||
Brian Granger
|
r2180 | return value | ||
else: | ||||
Brian Granger
|
r2182 | self.error(obj, value) | ||
Brian Granger
|
r2180 | |||
Brian Granger
|
r2177 | #----------------------------------------------------------------------------- | ||
# Basic TraitletTypes implementations/subclasses | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2157 | |||
class Any(TraitletType): | ||||
default_value = None | ||||
info_text = 'any value' | ||||
class Int(TraitletType): | ||||
Brian Granger
|
r2178 | """A integer traitlet.""" | ||
Brian Granger
|
r2157 | |||
evaluate = int | ||||
default_value = 0 | ||||
info_text = 'an integer' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, int): | ||||
return value | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | class CInt(Int): | ||
"""A casting version of the int traitlet.""" | ||||
def validate(self, obj, value): | ||||
try: | ||||
return int(value) | ||||
except: | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2157 | |||
class Long(TraitletType): | ||||
Brian Granger
|
r2178 | """A long integer traitlet.""" | ||
Brian Granger
|
r2157 | |||
evaluate = long | ||||
default_value = 0L | ||||
info_text = 'a long' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, long): | ||||
return value | ||||
if isinstance(value, int): | ||||
return long(value) | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | class CLong(Long): | ||
"""A casting version of the long integer traitlet.""" | ||||
def validate(self, obj, value): | ||||
try: | ||||
return long(value) | ||||
except: | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2157 | class Float(TraitletType): | ||
Brian Granger
|
r2178 | """A float traitlet.""" | ||
Brian Granger
|
r2157 | |||
evaluate = float | ||||
default_value = 0.0 | ||||
info_text = 'a float' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, float): | ||||
return value | ||||
if isinstance(value, int): | ||||
return float(value) | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | class CFloat(Float): | ||
"""A casting version of the float traitlet.""" | ||||
def validate(self, obj, value): | ||||
try: | ||||
return float(value) | ||||
except: | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2157 | class Complex(TraitletType): | ||
Brian Granger
|
r2178 | """A traitlet for complex numbers.""" | ||
Brian Granger
|
r2157 | |||
evaluate = complex | ||||
default_value = 0.0 + 0.0j | ||||
info_text = 'a complex number' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, complex): | ||||
return value | ||||
if isinstance(value, (float, int)): | ||||
return complex(value) | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | class CComplex(Complex): | ||
"""A casting version of the complex number traitlet.""" | ||||
def validate (self, obj, value): | ||||
try: | ||||
return complex(value) | ||||
except: | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2157 | class Str(TraitletType): | ||
Brian Granger
|
r2178 | """A traitlet for strings.""" | ||
Brian Granger
|
r2157 | |||
evaluate = lambda x: x | ||||
default_value = '' | ||||
info_text = 'a string' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, str): | ||||
return value | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | class CStr(Str): | ||
"""A casting version of the string traitlet.""" | ||||
def validate(self, obj, value): | ||||
try: | ||||
return str(value) | ||||
except: | ||||
try: | ||||
return unicode(value) | ||||
except: | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2157 | class Unicode(TraitletType): | ||
Brian Granger
|
r2178 | """A traitlet for unicode strings.""" | ||
Brian Granger
|
r2157 | |||
evaluate = unicode | ||||
default_value = u'' | ||||
info_text = 'a unicode string' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, unicode): | ||||
return value | ||||
if isinstance(value, str): | ||||
return unicode(value) | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | class CUnicode(Unicode): | ||
"""A casting version of the unicode traitlet.""" | ||||
def validate(self, obj, value): | ||||
try: | ||||
return unicode(value) | ||||
except: | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2178 | class Bool(TraitletType): | ||
"""A boolean (True, False) traitlet.""" | ||||
Brian Granger
|
r2157 | evaluate = bool | ||
default_value = False | ||||
info_text = 'a boolean' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, bool): | ||||
return value | ||||
self.error(obj, value) | ||||
Brian Granger
|
r2178 | |||
class CBool(Bool): | ||||
"""A casting version of the boolean traitlet.""" | ||||
def validate(self, obj, value): | ||||
try: | ||||
return bool(value) | ||||
except: | ||||
Brian Granger
|
r2203 | self.error(obj, value) | ||
Brian Granger
|
r2204 | |||
Brian Granger
|
r2203 | class Enum(TraitletType): | ||
Brian Granger
|
r2204 | """An enum that whose value must be in a given sequence.""" | ||
Brian Granger
|
r2203 | |||
def __init__(self, values, default_value=None, allow_none=True, **metadata): | ||||
self.values = values | ||||
self._allow_none = allow_none | ||||
super(Enum, self).__init__(default_value, **metadata) | ||||
def validate(self, obj, value): | ||||
if value is None: | ||||
if self._allow_none: | ||||
return value | ||||
if value in self.values: | ||||
return value | ||||
self.error(obj, value) | ||||
def info(self): | ||||
""" Returns a description of the trait.""" | ||||
result = 'any of ' + repr(self.values) | ||||
if self._allow_none: | ||||
return result + ' or None' | ||||
return result | ||||
class CaselessStrEnum(Enum): | ||||
Brian Granger
|
r2204 | """An enum of strings that are caseless in validate.""" | ||
Brian Granger
|
r2203 | |||
def validate(self, obj, value): | ||||
if value is None: | ||||
if self._allow_none: | ||||
return value | ||||
if not isinstance(value, str): | ||||
self.error(obj, value) | ||||
for v in self.values: | ||||
if v.lower() == value.lower(): | ||||
return v | ||||
Brian Granger
|
r2204 | self.error(obj, value) | ||
class List(Instance): | ||||
"""An instance of a Python list.""" | ||||
def __init__(self, default_value=None, allow_none=True, **metadata): | ||||
"""Create a list traitlet type from a list or tuple. | ||||
The default value is created by doing ``list(default_value)``, | ||||
which creates a copy of the ``default_value``. | ||||
""" | ||||
if default_value is None: | ||||
args = ((),) | ||||
elif isinstance(default_value, SequenceTypes): | ||||
args = (default_value,) | ||||
super(List,self).__init__(klass=list, args=args, | ||||
allow_none=allow_none, **metadata) | ||||