traitlets.py
1438 lines
| 46.2 KiB
| text/x-python
|
PythonLexer
|
r2157 | # encoding: utf-8 | ||
""" | ||||
A lightweight Traits like module. | ||||
|
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 | ||||
|
r2179 | * A full set of trait types. Most importantly, we don't provide container | ||
|
r2385 | traits (list, dict, tuple) that can trigger notifications if their | ||
|
r2179 | contents change. | ||
|
r2175 | * API compatibility with enthought.traits | ||
|
r2179 | There are also some important difference in our design: | ||
* enthought.traits does not validate default values. We do. | ||||
|
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. | ||||
|
r8795 | Inheritance diagram: | ||
.. inheritance-diagram:: IPython.utils.traitlets | ||||
:parts: 3 | ||||
|
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. | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
|
r5390 | # Copyright (C) 2008-2011 The IPython Development Team | ||
|
r2157 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
|
r2177 | |||
|
r2157 | import inspect | ||
|
r4047 | import re | ||
|
r2177 | import sys | ||
|
r2157 | import types | ||
|
r4732 | from types import FunctionType | ||
try: | ||||
from types import ClassType, InstanceType | ||||
ClassTypes = (ClassType, type) | ||||
except: | ||||
ClassTypes = (type,) | ||||
|
r4872 | |||
|
r3108 | from .importstring import import_item | ||
|
r4732 | from IPython.utils import py3compat | ||
|
r13361 | from IPython.utils.py3compat import iteritems | ||
|
r2229 | |||
|
r4732 | SequenceTypes = (list, tuple, set, frozenset) | ||
|
r2204 | |||
|
r2157 | #----------------------------------------------------------------------------- | ||
# Basic classes | ||||
#----------------------------------------------------------------------------- | ||||
class NoDefaultSpecified ( object ): pass | ||||
NoDefaultSpecified = NoDefaultSpecified() | ||||
class Undefined ( object ): pass | ||||
Undefined = Undefined() | ||||
|
r2384 | class TraitError(Exception): | ||
pass | ||||
|
r2157 | |||
#----------------------------------------------------------------------------- | ||||
# 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'). | ||||
""" | ||||
|
r13353 | if isinstance( object, py3compat.string_types ): | ||
|
r2157 | 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) | ||||
|
r4732 | if (not py3compat.PY3) and the_type is InstanceType: | ||
|
r2157 | # Old-style class. | ||
the_type = obj.__class__ | ||||
msg = '%r %r' % (obj, the_type) | ||||
return msg | ||||
|
r8407 | def is_trait(t): | ||
""" Returns whether the given value is an instance or subclass of TraitType. | ||||
""" | ||||
return (isinstance(t, TraitType) or | ||||
(isinstance(t, type) and issubclass(t, TraitType))) | ||||
|
r2157 | def parse_notifier_name(name): | ||
|
r2175 | """Convert the name argument to a list of names. | ||
|
r4872 | |||
|
r2175 | Examples | ||
-------- | ||||
|
r4872 | |||
|
r2175 | >>> parse_notifier_name('a') | ||
['a'] | ||||
>>> parse_notifier_name(['a','b']) | ||||
['a', 'b'] | ||||
>>> parse_notifier_name(None) | ||||
|
r2384 | ['anytrait'] | ||
|
r2175 | """ | ||
|
r2157 | if isinstance(name, str): | ||
return [name] | ||||
elif name is None: | ||||
|
r2384 | return ['anytrait'] | ||
|
r2157 | elif isinstance(name, (list, tuple)): | ||
for n in name: | ||||
assert isinstance(n, str), "names must be strings" | ||||
return name | ||||
|
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__() | ||||
|
r2287 | def getmembers(object, predicate=None): | ||
"""A safe version of inspect.getmembers that handles missing attributes. | ||||
|
r4872 | This is useful when there are descriptor based attributes that for | ||
|
r2287 | 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 | ||||
|
r2157 | #----------------------------------------------------------------------------- | ||
|
r2385 | # Base TraitType for all traits | ||
|
r2157 | #----------------------------------------------------------------------------- | ||
|
r2385 | class TraitType(object): | ||
"""A base class for all trait descriptors. | ||||
|
r2183 | |||
Notes | ||||
----- | ||||
|
r2385 | Our implementation of traits is based on Python's descriptor | ||
|
r2183 | prototol. This class is the base class for all such descriptors. The | ||
|
r2384 | only magic we use is a custom metaclass for the main :class:`HasTraits` | ||
|
r2183 | class that does the following: | ||
|
r2385 | 1. Sets the :attr:`name` attribute of every :class:`TraitType` | ||
|
r2183 | instance in the class dict to the name of the attribute. | ||
|
r2385 | 2. Sets the :attr:`this_class` attribute of every :class:`TraitType` | ||
instance in the class dict to the *class* that declared the trait. | ||||
This is used by the :class:`This` trait to allow subclasses to | ||||
|
r2183 | accept superclasses for :class:`This` values. | ||
""" | ||||
|
r4872 | |||
|
r2157 | |||
metadata = {} | ||||
|
r2177 | default_value = Undefined | ||
|
r2157 | info_text = 'any value' | ||
def __init__(self, default_value=NoDefaultSpecified, **metadata): | ||||
|
r2385 | """Create a TraitType. | ||
|
r2177 | """ | ||
|
r2157 | if default_value is not NoDefaultSpecified: | ||
self.default_value = default_value | ||||
|
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 | ||||
|
r2177 | self.init() | ||
|
r2157 | |||
|
r2177 | def init(self): | ||
pass | ||||
def get_default_value(self): | ||||
"""Create a new instance of the default value.""" | ||||
|
r2742 | return self.default_value | ||
|
r2177 | |||
|
r2229 | def instance_init(self, obj): | ||
|
r2384 | """This is called by :meth:`HasTraits.__new__` to finish init'ing. | ||
|
r2229 | |||
Some stages of initialization must be delayed until the parent | ||||
|
r2384 | :class:`HasTraits` instance has been created. This method is | ||
called in :meth:`HasTraits.__new__` after the instance has been | ||||
|
r2229 | created. | ||
This method trigger the creation and validation of default values | ||||
|
r4872 | and also things like the resolution of str given class names in | ||
|
r2229 | :class:`Type` and :class`Instance`. | ||
Parameters | ||||
---------- | ||||
|
r2384 | obj : :class:`HasTraits` instance | ||
The parent :class:`HasTraits` instance that has just been | ||||
|
r2229 | created. | ||
""" | ||||
self.set_default_value(obj) | ||||
|
r2182 | def set_default_value(self, obj): | ||
|
r2229 | """Set the default value on a per instance basis. | ||
This method is called by :meth:`instance_init` to create and | ||||
|
r4872 | validate the default value. The creation and validation of | ||
|
r2384 | default values must be delayed until the parent :class:`HasTraits` | ||
|
r2229 | class has been instantiated. | ||
""" | ||||
|
r3207 | # Check for a deferred initializer defined in the same class as the | ||
# trait declaration or above. | ||||
mro = type(obj).mro() | ||||
meth_name = '_%s_default' % self.name | ||||
for cls in mro[:mro.index(self.this_class)+1]: | ||||
if meth_name in cls.__dict__: | ||||
break | ||||
else: | ||||
# We didn't find one. Do static initialization. | ||||
dv = self.get_default_value() | ||||
newdv = self._validate(obj, dv) | ||||
obj._trait_values[self.name] = newdv | ||||
return | ||||
# Complete the dynamic initialization. | ||||
|
r3336 | obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name] | ||
|
r2180 | |||
|
r2182 | def __get__(self, obj, cls=None): | ||
|
r2385 | """Get the value of the trait by self.name for the instance. | ||
|
r2180 | |||
|
r2384 | Default values are instantiated when :meth:`HasTraits.__new__` | ||
|
r4872 | is called. Thus by the time this method gets called either the | ||
|
r2182 | default value or a user defined value (they called :meth:`__set__`) | ||
|
r2384 | is in the :class:`HasTraits` instance. | ||
|
r2177 | """ | ||
if obj is None: | ||||
|
r2157 | return self | ||
else: | ||||
|
r2182 | try: | ||
|
r2385 | value = obj._trait_values[self.name] | ||
|
r3207 | except KeyError: | ||
# Check for a dynamic initializer. | ||||
|
r3336 | if self.name in obj._trait_dyn_inits: | ||
value = obj._trait_dyn_inits[self.name](obj) | ||||
|
r3207 | # FIXME: Do we really validate here? | ||
value = self._validate(obj, value) | ||||
obj._trait_values[self.name] = value | ||||
return value | ||||
else: | ||||
raise TraitError('Unexpected error in TraitType: ' | ||||
'both default value and dynamic initializer are ' | ||||
'absent.') | ||||
except Exception: | ||||
|
r2384 | # HasTraits should call set_default_value to populate | ||
|
r2182 | # this. So this should never be reached. | ||
|
r2385 | raise TraitError('Unexpected error in TraitType: ' | ||
|
r2182 | 'default value not set properly') | ||
|
r2177 | else: | ||
|
r2182 | return value | ||
|
r2177 | |||
|
r2182 | def __set__(self, obj, value): | ||
|
r2177 | new_value = self._validate(obj, value) | ||
|
r2182 | old_value = self.__get__(obj) | ||
|
r8230 | obj._trait_values[self.name] = new_value | ||
|
r2182 | if old_value != new_value: | ||
|
r2385 | obj._notify_trait(self.name, old_value, new_value) | ||
|
r2157 | |||
|
r2177 | def _validate(self, obj, value): | ||
|
r2157 | if hasattr(self, 'validate'): | ||
|
r2177 | return self.validate(obj, value) | ||
|
r2157 | elif hasattr(self, 'is_valid_for'): | ||
valid = self.is_valid_for(value) | ||||
if valid: | ||||
return value | ||||
else: | ||||
|
r2385 | raise TraitError('invalid value for type: %r' % value) | ||
|
r2157 | 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: | ||||
|
r2385 | e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ | ||
|
r2157 | % (self.name, class_of(obj), | ||
self.info(), repr_type(value)) | ||||
else: | ||||
|
r2385 | e = "The '%s' trait must be %s, but a value of %r was specified." \ | ||
|
r3870 | % (self.name, self.info(), repr_type(value)) | ||
|
r2385 | raise TraitError(e) | ||
|
r2157 | |||
|
r2184 | def get_metadata(self, key): | ||
return getattr(self, '_metadata', {}).get(key, None) | ||||
def set_metadata(self, key, value): | ||||
getattr(self, '_metadata', {})[key] = value | ||||
|
r2157 | |||
#----------------------------------------------------------------------------- | ||||
|
r2384 | # The HasTraits implementation | ||
|
r2157 | #----------------------------------------------------------------------------- | ||
|
r2384 | class MetaHasTraits(type): | ||
"""A metaclass for HasTraits. | ||||
|
r4872 | |||
|
r2385 | This metaclass makes sure that any TraitType class attributes are | ||
|
r2157 | instantiated and sets their name attribute. | ||
""" | ||||
|
r4872 | |||
|
r2157 | def __new__(mcls, name, bases, classdict): | ||
|
r2384 | """Create the HasTraits class. | ||
|
r4872 | |||
|
r2385 | This instantiates all TraitTypes in the class dict and sets their | ||
|
r2183 | :attr:`name` attribute. | ||
""" | ||||
|
r2287 | # print "MetaHasTraitlets (mcls, name): ", mcls, name | ||
# print "MetaHasTraitlets (bases): ", bases | ||||
# print "MetaHasTraitlets (classdict): ", classdict | ||||
|
r13361 | for k,v in iteritems(classdict): | ||
|
r2385 | if isinstance(v, TraitType): | ||
|
r2157 | v.name = k | ||
elif inspect.isclass(v): | ||||
|
r2385 | if issubclass(v, TraitType): | ||
|
r2157 | vinst = v() | ||
vinst.name = k | ||||
classdict[k] = vinst | ||||
|
r2384 | return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict) | ||
|
r2157 | |||
|
r2183 | def __init__(cls, name, bases, classdict): | ||
|
r2384 | """Finish initializing the HasTraits class. | ||
|
r4872 | |||
|
r2385 | This sets the :attr:`this_class` attribute of each TraitType in the | ||
|
r2183 | class dict to the newly created class ``cls``. | ||
""" | ||||
|
r13361 | for k, v in iteritems(classdict): | ||
|
r2385 | if isinstance(v, TraitType): | ||
|
r2183 | v.this_class = cls | ||
|
r2384 | super(MetaHasTraits, cls).__init__(name, bases, classdict) | ||
|
r2157 | |||
|
r13359 | class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): | ||
|
r2157 | |||
|
r10257 | def __new__(cls, *args, **kw): | ||
|
r2255 | # This is needed because in Python 2.6 object.__new__ only accepts | ||
# the cls argument. | ||||
|
r2384 | new_meth = super(HasTraits, cls).__new__ | ||
|
r2255 | if new_meth is object.__new__: | ||
inst = new_meth(cls) | ||||
else: | ||||
|
r2739 | inst = new_meth(cls, **kw) | ||
|
r2385 | inst._trait_values = {} | ||
inst._trait_notifiers = {} | ||||
|
r3336 | inst._trait_dyn_inits = {} | ||
|
r2385 | # Here we tell all the TraitType instances to set their default | ||
|
r4872 | # values on the instance. | ||
|
r2182 | for key in dir(cls): | ||
|
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: | ||||
|
r2469 | if isinstance(value, TraitType): | ||
|
r2287 | value.instance_init(inst) | ||
|
r2469 | |||
|
r2182 | return inst | ||
|
r10257 | def __init__(self, *args, **kw): | ||
|
r2740 | # Allow trait values to be set using keyword arguments. | ||
|
r2745 | # We need to use setattr for this to trigger validation and | ||
# notifications. | ||||
|
r13361 | for key, value in iteritems(kw): | ||
|
r2739 | setattr(self, key, value) | ||
|
r2157 | |||
|
r2385 | def _notify_trait(self, name, old_value, new_value): | ||
|
r2175 | |||
# First dynamic ones | ||||
|
r13148 | callables = [] | ||
callables.extend(self._trait_notifiers.get(name,[])) | ||||
callables.extend(self._trait_notifiers.get('anytrait',[])) | ||||
|
r2175 | |||
# Now static ones | ||||
try: | ||||
cb = getattr(self, '_%s_changed' % name) | ||||
except: | ||||
pass | ||||
else: | ||||
callables.append(cb) | ||||
# Call them all now | ||||
|
r2157 | for c in callables: | ||
# Traits catches and logs errors here. I allow them to raise | ||||
|
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: | ||||
|
r2385 | raise TraitError('a trait changed callback ' | ||
|
r2175 | 'must have 0-3 arguments.') | ||
else: | ||||
|
r2385 | raise TraitError('a trait changed callback ' | ||
|
r2175 | 'must be callable.') | ||
|
r4872 | |||
|
r2157 | |||
def _add_notifiers(self, handler, name): | ||||
|
r7859 | if name not in self._trait_notifiers: | ||
|
r2157 | nlist = [] | ||
|
r2385 | self._trait_notifiers[name] = nlist | ||
|
r2157 | else: | ||
|
r2385 | nlist = self._trait_notifiers[name] | ||
|
r2157 | if handler not in nlist: | ||
nlist.append(handler) | ||||
def _remove_notifiers(self, handler, name): | ||||
|
r7859 | if name in self._trait_notifiers: | ||
|
r2385 | nlist = self._trait_notifiers[name] | ||
|
r2157 | try: | ||
index = nlist.index(handler) | ||||
except ValueError: | ||||
pass | ||||
else: | ||||
del nlist[index] | ||||
|
r2384 | def on_trait_change(self, handler, name=None, remove=False): | ||
|
r2385 | """Setup a handler to be called when a trait changes. | ||
|
r2175 | |||
|
r2385 | This is used to setup dynamic notifications of trait changes. | ||
|
r4872 | |||
|
r2384 | Static handlers can be created by creating methods on a HasTraits | ||
|
r2385 | subclass with the naming convention '_[traitname]_changed'. Thus, | ||
to create static handler for the trait 'a', create the method | ||||
|
r2175 | _a_changed(self, name, old, new) (fewer arguments can be used, see | ||
below). | ||||
|
r4872 | |||
|
r2175 | Parameters | ||
---------- | ||||
|
r2277 | handler : callable | ||
|
r4872 | A callable that is called when a trait changes. Its | ||
|
r2277 | signature can be handler(), handler(name), handler(name, new) | ||
or handler(name, old, new). | ||||
name : list, str, None | ||||
|
r2385 | If None, the handler will apply to all traits. If a list | ||
|
r2277 | 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. | ||||
|
r2175 | """ | ||
|
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) | ||||
|
r3789 | @classmethod | ||
def class_trait_names(cls, **metadata): | ||||
"""Get a list of all the names of this classes traits. | ||||
This method is just like the :meth:`trait_names` method, but is unbound. | ||||
""" | ||||
return cls.class_traits(**metadata).keys() | ||||
@classmethod | ||||
def class_traits(cls, **metadata): | ||||
"""Get a list of all the traits of this class. | ||||
This method is just like the :meth:`traits` method, but is unbound. | ||||
The TraitTypes returned don't know anything about the values | ||||
that the various HasTrait's instances are holding. | ||||
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. | ||||
""" | ||||
traits = dict([memb for memb in getmembers(cls) if \ | ||||
isinstance(memb[1], TraitType)]) | ||||
if len(metadata) == 0: | ||||
return traits | ||||
for meta_name, meta_eval in metadata.items(): | ||||
if type(meta_eval) is not FunctionType: | ||||
metadata[meta_name] = _SimpleTest(meta_eval) | ||||
result = {} | ||||
for name, trait in traits.items(): | ||||
for meta_name, meta_eval in metadata.items(): | ||||
if not meta_eval(trait.get_metadata(meta_name)): | ||||
break | ||||
else: | ||||
result[name] = trait | ||||
return result | ||||
|
r2384 | def trait_names(self, **metadata): | ||
|
r2385 | """Get a list of all the names of this classes traits.""" | ||
|
r2384 | return self.traits(**metadata).keys() | ||
|
r2184 | |||
|
r2384 | def traits(self, **metadata): | ||
|
r2385 | """Get a list of all the traits of this class. | ||
|
r2184 | |||
|
r2385 | The TraitTypes returned don't know anything about the values | ||
that the various HasTrait's instances are holding. | ||||
|
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. | ||||
|
r2184 | """ | ||
|
r2469 | traits = dict([memb for memb in getmembers(self.__class__) if \ | ||
|
r2385 | isinstance(memb[1], TraitType)]) | ||
|
r2184 | |||
|
r2245 | if len(metadata) == 0: | ||
|
r2385 | return traits | ||
|
r2200 | |||
|
r2184 | for meta_name, meta_eval in metadata.items(): | ||
if type(meta_eval) is not FunctionType: | ||||
metadata[meta_name] = _SimpleTest(meta_eval) | ||||
result = {} | ||||
|
r2385 | for name, trait in traits.items(): | ||
|
r2184 | for meta_name, meta_eval in metadata.items(): | ||
|
r2385 | if not meta_eval(trait.get_metadata(meta_name)): | ||
|
r2184 | break | ||
else: | ||||
|
r2385 | result[name] = trait | ||
|
r2184 | |||
return result | ||||
|
r2179 | |||
|
r2385 | def trait_metadata(self, traitname, key): | ||
"""Get metadata values for trait by key.""" | ||||
|
r2184 | try: | ||
|
r2385 | trait = getattr(self.__class__, traitname) | ||
|
r2184 | except AttributeError: | ||
|
r2385 | raise TraitError("Class %s does not have a trait named %s" % | ||
(self.__class__.__name__, traitname)) | ||||
|
r2184 | else: | ||
|
r2385 | return trait.get_metadata(key) | ||
|
r2177 | |||
|
r2157 | #----------------------------------------------------------------------------- | ||
|
r2385 | # Actual TraitTypes implementations/subclasses | ||
|
r2157 | #----------------------------------------------------------------------------- | ||
|
r2177 | |||
#----------------------------------------------------------------------------- | ||||
|
r2385 | # TraitTypes subclasses for handling classes and instances of classes | ||
|
r2177 | #----------------------------------------------------------------------------- | ||
|
r2385 | class ClassBasedTraitType(TraitType): | ||
"""A trait with error reporting for Type, Instance and This.""" | ||||
|
r2177 | |||
|
r2182 | def error(self, obj, value): | ||
|
r2177 | kind = type(value) | ||
|
r4732 | if (not py3compat.PY3) and kind is InstanceType: | ||
|
r2177 | msg = 'class %s' % value.__class__.__name__ | ||
else: | ||||
msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) | ||||
|
r3870 | if obj is not None: | ||
e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ | ||||
% (self.name, class_of(obj), | ||||
self.info(), msg) | ||||
else: | ||||
e = "The '%s' trait must be %s, but a value of %r was specified." \ | ||||
% (self.name, self.info(), msg) | ||||
raise TraitError(e) | ||||
|
r2177 | |||
|
r2385 | class Type(ClassBasedTraitType): | ||
"""A trait whose value must be a subclass of a specified class.""" | ||||
|
r2177 | |||
def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ): | ||||
|
r2385 | """Construct a Type trait | ||
|
r2177 | |||
|
r2385 | A Type trait specifies that its values must be subclasses of | ||
|
r2177 | a particular class. | ||
|
r2229 | If only ``default_value`` is given, it is used for the ``klass`` as | ||
well. | ||||
|
r2177 | Parameters | ||
---------- | ||||
|
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'. | ||||
|
r4872 | The string is resolved into real class, when the parent | ||
|
r2384 | :class:`HasTraits` class is instantiated. | ||
|
r2177 | klass : class, str, None | ||
|
r2385 | Values of this trait must be a subclass of klass. The klass | ||
|
r2177 | may be specified in a string like: 'foo.bar.MyClass'. | ||
|
r4872 | The string is resolved into real class, when the parent | ||
|
r2384 | :class:`HasTraits` class is instantiated. | ||
|
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 | ||||
|
r13353 | if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)): | ||
|
r2385 | raise TraitError("A Type trait must specify a class.") | ||
|
r2177 | |||
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.""" | ||||
|
r13353 | if isinstance(self.klass, py3compat.string_types): | ||
|
r2229 | klass = self.klass | ||
else: | ||||
klass = self.klass.__name__ | ||||
|
r2177 | result = 'a subclass of ' + klass | ||
if self._allow_none: | ||||
return result + ' or None' | ||||
return result | ||||
|
r2229 | def instance_init(self, obj): | ||
self._resolve_classes() | ||||
super(Type, self).instance_init(obj) | ||||
def _resolve_classes(self): | ||||
|
r13353 | if isinstance(self.klass, py3compat.string_types): | ||
|
r2229 | self.klass = import_item(self.klass) | ||
|
r13353 | if isinstance(self.default_value, py3compat.string_types): | ||
|
r2229 | self.default_value = import_item(self.default_value) | ||
def get_default_value(self): | ||||
return self.default_value | ||||
|
r2177 | |||
class DefaultValueGenerator(object): | ||||
"""A class for generating new default value instances.""" | ||||
|
r2229 | def __init__(self, *args, **kw): | ||
|
r2177 | self.args = args | ||
self.kw = kw | ||||
|
r2229 | def generate(self, klass): | ||
return klass(*self.args, **self.kw) | ||||
|
r2177 | |||
|
r2182 | |||
|
r2385 | class Instance(ClassBasedTraitType): | ||
|
r2177 | """A trait whose value must be an instance of a specified class. | ||
|
r4872 | |||
|
r2177 | The value can also be an instance of a subclass of the specified class. | ||
""" | ||||
|
r4872 | def __init__(self, klass=None, args=None, kw=None, | ||
|
r2182 | allow_none=True, **metadata ): | ||
|
r2385 | """Construct an Instance trait. | ||
|
r2177 | |||
|
r2385 | This trait allows values that are instances of a particular | ||
|
r2182 | 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. | ||||
|
r2177 | Parameters | ||
---------- | ||||
|
r2229 | klass : class, str | ||
|
r2385 | The class that forms the basis for the trait. Class names | ||
|
r2229 | can also be specified as strings, like 'foo.bar.Bar'. | ||
|
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. | ||||
|
r13587 | Notes | ||
----- | ||||
|
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 | ||||
|
r4872 | created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is | ||
|
r2182 | not (but not both), None is replace by ``()`` or ``{}``. | ||
|
r2177 | """ | ||
self._allow_none = allow_none | ||||
|
r2182 | |||
|
r13353 | if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))): | ||
|
r2385 | raise TraitError('The klass argument must be a class' | ||
|
r2182 | ' you gave: %r' % klass) | ||
self.klass = klass | ||||
|
r4872 | |||
|
r2182 | # self.klass is a class, so handle default_value | ||
if args is None and kw is None: | ||||
|
r2177 | default_value = None | ||
else: | ||||
if args is None: | ||||
|
r2182 | # kw is not None | ||
|
r2177 | args = () | ||
|
r2182 | elif kw is None: | ||
# args is not None | ||||
kw = {} | ||||
|
r4872 | |||
|
r2177 | if not isinstance(kw, dict): | ||
|
r2385 | raise TraitError("The 'kw' argument must be a dict or None.") | ||
|
r2177 | if not isinstance(args, tuple): | ||
|
r2385 | raise TraitError("The 'args' argument must be a tuple or None.") | ||
|
r4872 | |||
|
r2229 | default_value = DefaultValueGenerator(*args, **kw) | ||
|
r2177 | |||
super(Instance, self).__init__(default_value, **metadata) | ||||
def validate(self, obj, value): | ||||
if value is None: | ||||
if self._allow_none: | ||||
return value | ||||
|
r2182 | self.error(obj, value) | ||
|
r2177 | |||
if isinstance(value, self.klass): | ||||
return value | ||||
else: | ||||
|
r2182 | self.error(obj, value) | ||
|
r2177 | |||
|
r2182 | def info(self): | ||
|
r13353 | if isinstance(self.klass, py3compat.string_types): | ||
|
r2229 | klass = self.klass | ||
else: | ||||
klass = self.klass.__name__ | ||||
|
r2177 | result = class_of(klass) | ||
if self._allow_none: | ||||
return result + ' or None' | ||||
return result | ||||
|
r2229 | def instance_init(self, obj): | ||
self._resolve_classes() | ||||
super(Instance, self).instance_init(obj) | ||||
def _resolve_classes(self): | ||||
|
r13353 | if isinstance(self.klass, py3compat.string_types): | ||
|
r2229 | self.klass = import_item(self.klass) | ||
|
r2182 | def get_default_value(self): | ||
|
r2177 | """Instantiate a default value instance. | ||
|
r4872 | |||
|
r2384 | This is called when the containing HasTraits classes' | ||
|
r2182 | :meth:`__new__` method is called to ensure that a unique instance | ||
|
r2384 | is created for each HasTraits instance. | ||
|
r2177 | """ | ||
dv = self.default_value | ||||
if isinstance(dv, DefaultValueGenerator): | ||||
|
r2229 | return dv.generate(self.klass) | ||
|
r2177 | else: | ||
return dv | ||||
|
r2385 | class This(ClassBasedTraitType): | ||
"""A trait for instances of the class containing this trait. | ||||
|
r2180 | |||
|
r2182 | Because how how and when class bodies are executed, the ``This`` | ||
|
r4872 | trait can only have a default value of None. This, and because we | ||
|
r2182 | always validate default values, ``allow_none`` is *always* true. | ||
""" | ||||
|
r2180 | |||
|
r2182 | info_text = 'an instance of the same type as the receiver or None' | ||
|
r2180 | |||
|
r2182 | def __init__(self, **metadata): | ||
super(This, self).__init__(None, **metadata) | ||||
|
r2180 | |||
|
r2182 | def validate(self, obj, value): | ||
|
r2183 | # What if value is a superclass of obj.__class__? This is | ||
# complicated if it was the superclass that defined the This | ||||
|
r2385 | # trait. | ||
|
r2183 | if isinstance(value, self.this_class) or (value is None): | ||
|
r2180 | return value | ||
else: | ||||
|
r2182 | self.error(obj, value) | ||
|
r2180 | |||
|
r2177 | #----------------------------------------------------------------------------- | ||
|
r2385 | # Basic TraitTypes implementations/subclasses | ||
|
r2177 | #----------------------------------------------------------------------------- | ||
|
r2157 | |||
|
r2385 | class Any(TraitType): | ||
|
r2157 | default_value = None | ||
info_text = 'any value' | ||||
|
r2385 | class Int(TraitType): | ||
|
r5344 | """An int trait.""" | ||
|
r2157 | |||
default_value = 0 | ||||
|
r5344 | info_text = 'an int' | ||
|
r2157 | |||
def validate(self, obj, value): | ||||
if isinstance(value, int): | ||||
return value | ||||
self.error(obj, value) | ||||
|
r2178 | class CInt(Int): | ||
|
r2385 | """A casting version of the int trait.""" | ||
|
r2178 | |||
def validate(self, obj, value): | ||||
try: | ||||
return int(value) | ||||
except: | ||||
self.error(obj, value) | ||||
|
r4764 | if py3compat.PY3: | ||
Long, CLong = Int, CInt | ||||
|
r5344 | Integer = Int | ||
|
r4764 | else: | ||
|
r4732 | class Long(TraitType): | ||
"""A long integer trait.""" | ||||
|
r2157 | |||
|
r13352 | default_value = 0 | ||
|
r4732 | info_text = 'a long' | ||
|
r2157 | |||
|
r4732 | def validate(self, obj, value): | ||
if isinstance(value, long): | ||||
return value | ||||
if isinstance(value, int): | ||||
return long(value) | ||||
self.error(obj, value) | ||||
|
r2157 | |||
|
r4732 | class CLong(Long): | ||
"""A casting version of the long integer trait.""" | ||||
|
r2178 | |||
|
r4732 | def validate(self, obj, value): | ||
try: | ||||
return long(value) | ||||
except: | ||||
self.error(obj, value) | ||||
|
r2178 | |||
|
r5344 | class Integer(TraitType): | ||
"""An integer trait. | ||||
Longs that are unnecessary (<= sys.maxint) are cast to ints.""" | ||||
default_value = 0 | ||||
info_text = 'an integer' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, int): | ||||
return value | ||||
|
r8500 | if isinstance(value, long): | ||
|
r5344 | # downcast longs that fit in int: | ||
# note that int(n > sys.maxint) returns a long, so | ||||
# we don't need a condition on this cast | ||||
return int(value) | ||||
|
r8500 | if sys.platform == "cli": | ||
from System import Int64 | ||||
if isinstance(value, Int64): | ||||
return int(value) | ||||
|
r5344 | self.error(obj, value) | ||
|
r2178 | |||
|
r2385 | class Float(TraitType): | ||
"""A float trait.""" | ||||
|
r2157 | |||
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) | ||||
|
r2178 | class CFloat(Float): | ||
|
r2385 | """A casting version of the float trait.""" | ||
|
r2178 | |||
def validate(self, obj, value): | ||||
try: | ||||
return float(value) | ||||
except: | ||||
self.error(obj, value) | ||||
|
r2385 | class Complex(TraitType): | ||
"""A trait for complex numbers.""" | ||||
|
r2157 | |||
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) | ||||
|
r2178 | class CComplex(Complex): | ||
|
r2385 | """A casting version of the complex number trait.""" | ||
|
r2178 | |||
def validate (self, obj, value): | ||||
try: | ||||
return complex(value) | ||||
except: | ||||
self.error(obj, value) | ||||
|
r4046 | # We should always be explicit about whether we're using bytes or unicode, both | ||
# for Python 3 conversion and for reliable unicode behaviour on Python 2. So | ||||
# we don't have a Str type. | ||||
|
r4044 | class Bytes(TraitType): | ||
|
r4732 | """A trait for byte strings.""" | ||
|
r2157 | |||
|
r5287 | default_value = b'' | ||
|
r13429 | info_text = 'a bytes object' | ||
|
r2157 | |||
def validate(self, obj, value): | ||||
|
r4044 | if isinstance(value, bytes): | ||
|
r2157 | return value | ||
self.error(obj, value) | ||||
|
r4044 | class CBytes(Bytes): | ||
|
r4732 | """A casting version of the byte string trait.""" | ||
|
r2178 | |||
def validate(self, obj, value): | ||||
try: | ||||
|
r4044 | return bytes(value) | ||
|
r2178 | except: | ||
|
r4045 | self.error(obj, value) | ||
|
r2178 | |||
|
r2385 | class Unicode(TraitType): | ||
"""A trait for unicode strings.""" | ||||
|
r2157 | |||
default_value = u'' | ||||
info_text = 'a unicode string' | ||||
def validate(self, obj, value): | ||||
|
r13353 | if isinstance(value, py3compat.unicode_type): | ||
|
r2157 | return value | ||
|
r4044 | if isinstance(value, bytes): | ||
|
r13353 | return py3compat.unicode_type(value) | ||
|
r2157 | self.error(obj, value) | ||
|
r2178 | class CUnicode(Unicode): | ||
|
r2385 | """A casting version of the unicode trait.""" | ||
|
r2178 | |||
def validate(self, obj, value): | ||||
try: | ||||
|
r13353 | return py3compat.unicode_type(value) | ||
|
r2178 | except: | ||
self.error(obj, value) | ||||
|
r4872 | |||
|
r4047 | class ObjectName(TraitType): | ||
"""A string holding a valid object name in this version of Python. | ||||
|
r4872 | |||
|
r4047 | This does not check that the name exists in any scope.""" | ||
info_text = "a valid object identifier in Python" | ||||
|
r4740 | if py3compat.PY3: | ||
# Python 3: | ||||
coerce_str = staticmethod(lambda _,s: s) | ||||
|
r4872 | |||
|
r4740 | else: | ||
|
r4047 | # Python 2: | ||
def coerce_str(self, obj, value): | ||||
"In Python 2, coerce ascii-only unicode to str" | ||||
if isinstance(value, unicode): | ||||
try: | ||||
return str(value) | ||||
except UnicodeEncodeError: | ||||
self.error(obj, value) | ||||
return value | ||||
|
r4872 | |||
|
r4047 | def validate(self, obj, value): | ||
value = self.coerce_str(obj, value) | ||||
|
r4872 | |||
|
r4740 | if isinstance(value, str) and py3compat.isidentifier(value): | ||
|
r4047 | return value | ||
self.error(obj, value) | ||||
class DottedObjectName(ObjectName): | ||||
"""A string holding a valid dotted object name in Python, such as A.b3._c""" | ||||
def validate(self, obj, value): | ||||
value = self.coerce_str(obj, value) | ||||
|
r4872 | |||
|
r4740 | if isinstance(value, str) and py3compat.isidentifier(value, dotted=True): | ||
|
r4047 | return value | ||
self.error(obj, value) | ||||
|
r2178 | |||
|
r2157 | |||
|
r2385 | class Bool(TraitType): | ||
"""A boolean (True, False) trait.""" | ||||
|
r2742 | |||
|
r2157 | default_value = False | ||
info_text = 'a boolean' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, bool): | ||||
return value | ||||
self.error(obj, value) | ||||
|
r2178 | |||
class CBool(Bool): | ||||
|
r2385 | """A casting version of the boolean trait.""" | ||
|
r2178 | |||
def validate(self, obj, value): | ||||
try: | ||||
return bool(value) | ||||
except: | ||||
|
r2203 | self.error(obj, value) | ||
|
r2204 | |||
|
r2385 | class Enum(TraitType): | ||
|
r2204 | """An enum that whose value must be in a given sequence.""" | ||
|
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): | ||||
|
r2204 | """An enum of strings that are caseless in validate.""" | ||
|
r2203 | |||
def validate(self, obj, value): | ||||
if value is None: | ||||
if self._allow_none: | ||||
return value | ||||
|
r13353 | if not isinstance(value, py3compat.string_types): | ||
|
r2203 | self.error(obj, value) | ||
for v in self.values: | ||||
if v.lower() == value.lower(): | ||||
return v | ||||
|
r2204 | self.error(obj, value) | ||
|
r3870 | class Container(Instance): | ||
"""An instance of a container (list, set, etc.) | ||||
|
r2204 | |||
|
r3870 | To be subclassed by overriding klass. | ||
""" | ||||
klass = None | ||||
_valid_defaults = SequenceTypes | ||||
_trait = None | ||||
|
r2204 | |||
|
r3870 | def __init__(self, trait=None, default_value=None, allow_none=True, | ||
**metadata): | ||||
"""Create a container trait type from a list, set, or tuple. | ||||
|
r2204 | |||
|
r3870 | The default value is created by doing ``List(default_value)``, | ||
|
r2204 | which creates a copy of the ``default_value``. | ||
|
r3870 | |||
``trait`` can be specified, which restricts the type of elements | ||||
in the container to that TraitType. | ||||
If only one arg is given and it is not a Trait, it is taken as | ||||
``default_value``: | ||||
``c = List([1,2,3])`` | ||||
Parameters | ||||
---------- | ||||
trait : TraitType [ optional ] | ||||
the type for restricting the contents of the Container. If unspecified, | ||||
types are not checked. | ||||
default_value : SequenceType [ optional ] | ||||
The default value for the Trait. Must be list/tuple/set, and | ||||
will be cast to the container type. | ||||
allow_none : Bool [ default True ] | ||||
Whether to allow the value to be None | ||||
**metadata : any | ||||
further keys for extensions to the Trait (e.g. config) | ||||
|
r2204 | """ | ||
|
r3870 | # allow List([values]): | ||
|
r8407 | if default_value is None and not is_trait(trait): | ||
|
r3870 | default_value = trait | ||
trait = None | ||||
|
r2204 | if default_value is None: | ||
|
r3870 | args = () | ||
elif isinstance(default_value, self._valid_defaults): | ||||
|
r2204 | args = (default_value,) | ||
|
r2380 | else: | ||
|
r3870 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) | ||
|
r8407 | if is_trait(trait): | ||
self._trait = trait() if isinstance(trait, type) else trait | ||||
|
r3870 | self._trait.name = 'element' | ||
elif trait is not None: | ||||
raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait)) | ||||
|
r2204 | |||
|
r3870 | super(Container,self).__init__(klass=self.klass, args=args, | ||
|
r2204 | allow_none=allow_none, **metadata) | ||
|
r2742 | |||
|
r3870 | def element_error(self, obj, element, validator): | ||
e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \ | ||||
% (self.name, class_of(obj), validator.info(), repr_type(element)) | ||||
raise TraitError(e) | ||||
|
r2742 | |||
|
r3870 | def validate(self, obj, value): | ||
value = super(Container, self).validate(obj, value) | ||||
if value is None: | ||||
return value | ||||
|
r3604 | |||
|
r3870 | value = self.validate_elements(obj, value) | ||
return value | ||||
def validate_elements(self, obj, value): | ||||
validated = [] | ||||
if self._trait is None or isinstance(self._trait, Any): | ||||
return value | ||||
for v in value: | ||||
try: | ||||
v = self._trait.validate(obj, v) | ||||
except TraitError: | ||||
self.element_error(obj, v, self._trait) | ||||
else: | ||||
validated.append(v) | ||||
return self.klass(validated) | ||||
class List(Container): | ||||
"""An instance of a Python list.""" | ||||
klass = list | ||||
|
r3604 | |||
|
r8494 | def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, | ||
|
r3870 | allow_none=True, **metadata): | ||
"""Create a List trait type from a list, set, or tuple. | ||||
The default value is created by doing ``List(default_value)``, | ||||
|
r3604 | which creates a copy of the ``default_value``. | ||
|
r3870 | |||
``trait`` can be specified, which restricts the type of elements | ||||
in the container to that TraitType. | ||||
If only one arg is given and it is not a Trait, it is taken as | ||||
``default_value``: | ||||
``c = List([1,2,3])`` | ||||
Parameters | ||||
---------- | ||||
trait : TraitType [ optional ] | ||||
the type for restricting the contents of the Container. If unspecified, | ||||
types are not checked. | ||||
default_value : SequenceType [ optional ] | ||||
The default value for the Trait. Must be list/tuple/set, and | ||||
will be cast to the container type. | ||||
minlen : Int [ default 0 ] | ||||
The minimum length of the input list | ||||
|
r8494 | maxlen : Int [ default sys.maxsize ] | ||
|
r3870 | The maximum length of the input list | ||
allow_none : Bool [ default True ] | ||||
Whether to allow the value to be None | ||||
**metadata : any | ||||
further keys for extensions to the Trait (e.g. config) | ||||
|
r3604 | """ | ||
|
r3870 | self._minlen = minlen | ||
self._maxlen = maxlen | ||||
super(List, self).__init__(trait=trait, default_value=default_value, | ||||
allow_none=allow_none, **metadata) | ||||
def length_error(self, obj, value): | ||||
e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \ | ||||
% (self.name, class_of(obj), self._minlen, self._maxlen, value) | ||||
raise TraitError(e) | ||||
def validate_elements(self, obj, value): | ||||
length = len(value) | ||||
if length < self._minlen or length > self._maxlen: | ||||
self.length_error(obj, value) | ||||
return super(List, self).validate_elements(obj, value) | ||||
class Set(Container): | ||||
"""An instance of a Python set.""" | ||||
klass = set | ||||
class Tuple(Container): | ||||
"""An instance of a Python tuple.""" | ||||
klass = tuple | ||||
def __init__(self, *traits, **metadata): | ||||
"""Tuple(*traits, default_value=None, allow_none=True, **medatata) | ||||
Create a tuple from a list, set, or tuple. | ||||
Create a fixed-type tuple with Traits: | ||||
``t = Tuple(Int, Str, CStr)`` | ||||
would be length 3, with Int,Str,CStr for each element. | ||||
If only one arg is given and it is not a Trait, it is taken as | ||||
default_value: | ||||
``t = Tuple((1,2,3))`` | ||||
Otherwise, ``default_value`` *must* be specified by keyword. | ||||
Parameters | ||||
---------- | ||||
*traits : TraitTypes [ optional ] | ||||
the tsype for restricting the contents of the Tuple. If unspecified, | ||||
types are not checked. If specified, then each positional argument | ||||
corresponds to an element of the tuple. Tuples defined with traits | ||||
are of fixed length. | ||||
default_value : SequenceType [ optional ] | ||||
The default value for the Tuple. Must be list/tuple/set, and | ||||
will be cast to a tuple. If `traits` are specified, the | ||||
`default_value` must conform to the shape and type they specify. | ||||
allow_none : Bool [ default True ] | ||||
Whether to allow the value to be None | ||||
**metadata : any | ||||
further keys for extensions to the Trait (e.g. config) | ||||
""" | ||||
default_value = metadata.pop('default_value', None) | ||||
allow_none = metadata.pop('allow_none', True) | ||||
# allow Tuple((values,)): | ||||
|
r8407 | if len(traits) == 1 and default_value is None and not is_trait(traits[0]): | ||
|
r3870 | default_value = traits[0] | ||
traits = () | ||||
|
r3604 | if default_value is None: | ||
|
r3870 | args = () | ||
elif isinstance(default_value, self._valid_defaults): | ||||
|
r3604 | args = (default_value,) | ||
else: | ||||
|
r3870 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) | ||
self._traits = [] | ||||
for trait in traits: | ||||
|
r8407 | t = trait() if isinstance(trait, type) else trait | ||
|
r3870 | t.name = 'element' | ||
self._traits.append(t) | ||||
if self._traits and default_value is None: | ||||
# don't allow default to be an empty container if length is specified | ||||
args = None | ||||
super(Container,self).__init__(klass=self.klass, args=args, | ||||
|
r3604 | allow_none=allow_none, **metadata) | ||
|
r3870 | def validate_elements(self, obj, value): | ||
if not self._traits: | ||||
# nothing to validate | ||||
return value | ||||
if len(value) != len(self._traits): | ||||
e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \ | ||||
% (self.name, class_of(obj), len(self._traits), repr_type(value)) | ||||
raise TraitError(e) | ||||
validated = [] | ||||
for t,v in zip(self._traits, value): | ||||
try: | ||||
v = t.validate(obj, v) | ||||
except TraitError: | ||||
self.element_error(obj, v, t) | ||||
else: | ||||
validated.append(v) | ||||
return tuple(validated) | ||||
|
r3604 | |||
|
r2738 | class Dict(Instance): | ||
"""An instance of a Python dict.""" | ||||
def __init__(self, default_value=None, allow_none=True, **metadata): | ||||
"""Create a dict trait type from a dict. | ||||
|
r4872 | The default value is created by doing ``dict(default_value)``, | ||
|
r2738 | which creates a copy of the ``default_value``. | ||
""" | ||||
if default_value is None: | ||||
args = ((),) | ||||
elif isinstance(default_value, dict): | ||||
args = (default_value,) | ||||
elif isinstance(default_value, SequenceTypes): | ||||
args = (default_value,) | ||||
else: | ||||
raise TypeError('default value of Dict was %s' % default_value) | ||||
|
r4872 | super(Dict,self).__init__(klass=dict, args=args, | ||
|
r2738 | allow_none=allow_none, **metadata) | ||
|
r2752 | |||
|
r2742 | class TCPAddress(TraitType): | ||
"""A trait for an (ip, port) tuple. | ||||
This allows for both IPv4 IP addresses as well as hostnames. | ||||
""" | ||||
default_value = ('127.0.0.1', 0) | ||||
info_text = 'an (ip, port) tuple' | ||||
def validate(self, obj, value): | ||||
if isinstance(value, tuple): | ||||
if len(value) == 2: | ||||
|
r13353 | if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int): | ||
|
r2742 | port = value[1] | ||
if port >= 0 and port <= 65535: | ||||
return value | ||||
self.error(obj, value) | ||||
|
r6732 | |||
class CRegExp(TraitType): | ||||
|
r6743 | """A casting compiled regular expression trait. | ||
Accepts both strings and compiled regular expressions. The resulting | ||||
attribute will be a compiled regular expression.""" | ||||
|
r6732 | |||
info_text = 'a regular expression' | ||||
def validate(self, obj, value): | ||||
try: | ||||
return re.compile(value) | ||||
except: | ||||
self.error(obj, value) | ||||