|
|
- # encoding: utf-8
- """
- A lightweight Traits like module.
-
- 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
- * A full set of trait types. Most importantly, we don't provide container
- traits (list, dict, tuple) that can trigger notifications if their
- contents change.
- * API compatibility with enthought.traits
-
- There are also some important difference in our design:
-
- * enthought.traits does not validate default values. We do.
-
- 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.
-
- Inheritance diagram:
-
- .. inheritance-diagram:: traitlets.traitlets
- :parts: 3
- """
-
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the Modified BSD License.
- #
- # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
- # also under the terms of the Modified BSD License.
-
- import contextlib
- import inspect
- import os
- import re
- import sys
- import types
- import enum
- try:
- from types import ClassType, InstanceType
- ClassTypes = (ClassType, type)
- except:
- ClassTypes = (type,)
- from warnings import warn, warn_explicit
-
- import six
-
- from .utils.getargspec import getargspec
- from .utils.importstring import import_item
- from .utils.sentinel import Sentinel
- from .utils.bunch import Bunch
-
- SequenceTypes = (list, tuple, set, frozenset)
-
- #-----------------------------------------------------------------------------
- # Basic classes
- #-----------------------------------------------------------------------------
-
-
- Undefined = Sentinel('Undefined', 'traitlets',
- '''
- Used in Traitlets to specify that no defaults are set in kwargs
- '''
- )
-
- All = Sentinel('All', 'traitlets',
- '''
- Used in Traitlets to listen to all types of notification or to notifications
- from all trait attributes.
- '''
- )
-
- # Deprecated alias
- NoDefaultSpecified = Undefined
-
- class TraitError(Exception):
- pass
-
- #-----------------------------------------------------------------------------
- # Utilities
- #-----------------------------------------------------------------------------
-
- from ipython_genutils.py3compat import cast_unicode_py2
-
- _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
-
- def isidentifier(s):
- if six.PY2:
- return bool(_name_re.match(s))
- else:
- return s.isidentifier()
-
- _deprecations_shown = set()
- def _should_warn(key):
- """Add our own checks for too many deprecation warnings.
-
- Limit to once per package.
- """
- env_flag = os.environ.get('TRAITLETS_ALL_DEPRECATIONS')
- if env_flag and env_flag != '0':
- return True
-
- if key not in _deprecations_shown:
- _deprecations_shown.add(key)
- return True
- else:
- return False
-
- def _deprecated_method(method, cls, method_name, msg):
- """Show deprecation warning about a magic method definition.
-
- Uses warn_explicit to bind warning to method definition instead of triggering code,
- which isn't relevant.
- """
- warn_msg = "{classname}.{method_name} is deprecated in traitlets 4.1: {msg}".format(
- classname=cls.__name__, method_name=method_name, msg=msg
- )
-
- for parent in inspect.getmro(cls):
- if method_name in parent.__dict__:
- cls = parent
- break
- # limit deprecation messages to once per package
- package_name = cls.__module__.split('.', 1)[0]
- key = (package_name, msg)
- if not _should_warn(key):
- return
- try:
- fname = inspect.getsourcefile(method) or "<unknown>"
- lineno = inspect.getsourcelines(method)[1] or 0
- except (IOError, TypeError) as e:
- # Failed to inspect for some reason
- warn(warn_msg + ('\n(inspection failed) %s' % e), DeprecationWarning)
- else:
- warn_explicit(warn_msg, DeprecationWarning, fname, lineno)
-
- 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, six.string_types ):
- 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 six.PY2 and the_type is InstanceType:
- # Old-style class.
- the_type = obj.__class__
- msg = '%r %r' % (obj, the_type)
- return msg
-
-
- 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)))
-
-
- def parse_notifier_name(names):
- """Convert the name argument to a list of names.
-
- Examples
- --------
-
- >>> parse_notifier_name([])
- [All]
- >>> parse_notifier_name('a')
- ['a']
- >>> parse_notifier_name(['a', 'b'])
- ['a', 'b']
- >>> parse_notifier_name(All)
- [All]
- """
- if names is All or isinstance(names, six.string_types):
- return [names]
- else:
- if not names or All in names:
- return [All]
- for n in names:
- if not isinstance(n, six.string_types):
- raise TypeError("names must be strings, not %r" % n)
- return names
-
-
- 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__()
-
-
- 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
-
- def _validate_link(*tuples):
- """Validate arguments for traitlet link functions"""
- for t in tuples:
- if not len(t) == 2:
- raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
- obj, trait_name = t
- if not isinstance(obj, HasTraits):
- raise TypeError("Each object must be HasTraits, not %r" % type(obj))
- if not trait_name in obj.traits():
- raise TypeError("%r has no trait %r" % (obj, trait_name))
-
- class link(object):
- """Link traits from different objects together so they remain in sync.
-
- Parameters
- ----------
- source : (object / attribute name) pair
- target : (object / attribute name) pair
-
- Examples
- --------
-
- >>> c = link((src, 'value'), (tgt, 'value'))
- >>> src.value = 5 # updates other objects as well
- """
- updating = False
-
- def __init__(self, source, target):
- _validate_link(source, target)
- self.source, self.target = source, target
- try:
- setattr(target[0], target[1], getattr(source[0], source[1]))
- finally:
- source[0].observe(self._update_target, names=source[1])
- target[0].observe(self._update_source, names=target[1])
-
- @contextlib.contextmanager
- def _busy_updating(self):
- self.updating = True
- try:
- yield
- finally:
- self.updating = False
-
- def _update_target(self, change):
- if self.updating:
- return
- with self._busy_updating():
- setattr(self.target[0], self.target[1], change.new)
-
- def _update_source(self, change):
- if self.updating:
- return
- with self._busy_updating():
- setattr(self.source[0], self.source[1], change.new)
-
- def unlink(self):
- self.source[0].unobserve(self._update_target, names=self.source[1])
- self.target[0].unobserve(self._update_source, names=self.target[1])
- self.source, self.target = None, None
-
-
- class directional_link(object):
- """Link the trait of a source object with traits of target objects.
-
- Parameters
- ----------
- source : (object, attribute name) pair
- target : (object, attribute name) pair
- transform: callable (optional)
- Data transformation between source and target.
-
- Examples
- --------
-
- >>> c = directional_link((src, 'value'), (tgt, 'value'))
- >>> src.value = 5 # updates target objects
- >>> tgt.value = 6 # does not update source object
- """
- updating = False
-
- def __init__(self, source, target, transform=None):
- self._transform = transform if transform else lambda x: x
- _validate_link(source, target)
- self.source, self.target = source, target
- try:
- setattr(target[0], target[1],
- self._transform(getattr(source[0], source[1])))
- finally:
- self.source[0].observe(self._update, names=self.source[1])
-
- @contextlib.contextmanager
- def _busy_updating(self):
- self.updating = True
- try:
- yield
- finally:
- self.updating = False
-
- def _update(self, change):
- if self.updating:
- return
- with self._busy_updating():
- setattr(self.target[0], self.target[1],
- self._transform(change.new))
-
- def unlink(self):
- self.source[0].unobserve(self._update, names=self.source[1])
- self.source, self.target = None, None
-
- dlink = directional_link
-
-
- #-----------------------------------------------------------------------------
- # Base Descriptor Class
- #-----------------------------------------------------------------------------
-
-
- class BaseDescriptor(object):
- """Base descriptor class
-
- Notes
- -----
- This implements 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:`HasTraits`
- class that does the following:
-
- 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor`
- instance in the class dict to the name of the attribute.
- 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor`
- instance in the class dict to the *class* that declared the trait.
- This is used by the :class:`This` trait to allow subclasses to
- accept superclasses for :class:`This` values.
- """
-
- name = None
- this_class = None
-
- def class_init(self, cls, name):
- """Part of the initialization which may depend on the underlying
- HasDescriptors class.
-
- It is typically overloaded for specific types.
-
- This method is called by :meth:`MetaHasDescriptors.__init__`
- passing the class (`cls`) and `name` under which the descriptor
- has been assigned.
- """
- self.this_class = cls
- self.name = name
-
- def instance_init(self, obj):
- """Part of the initialization which may depend on the underlying
- HasDescriptors instance.
-
- It is typically overloaded for specific types.
-
- This method is called by :meth:`HasTraits.__new__` and in the
- :meth:`BaseDescriptor.instance_init` method of descriptors holding
- other descriptors.
- """
- pass
-
-
- class TraitType(BaseDescriptor):
- """A base class for all trait types.
- """
-
- metadata = {}
- default_value = Undefined
- allow_none = False
- read_only = False
- info_text = 'any value'
-
- def __init__(self, default_value=Undefined, allow_none=False, read_only=None, help=None,
- config=None, **kwargs):
- """Declare a traitlet.
-
- If *allow_none* is True, None is a valid value in addition to any
- values that are normally valid. The default is up to the subclass.
- For most trait types, the default value for ``allow_none`` is False.
-
- Extra metadata can be associated with the traitlet using the .tag() convenience method
- or by using the traitlet instance's .metadata dictionary.
- """
- if default_value is not Undefined:
- self.default_value = default_value
- if allow_none:
- self.allow_none = allow_none
- if read_only is not None:
- self.read_only = read_only
- self.help = help if help is not None else ''
-
- if len(kwargs) > 0:
- stacklevel = 1
- f = inspect.currentframe()
- # count supers to determine stacklevel for warning
- while f.f_code.co_name == '__init__':
- stacklevel += 1
- f = f.f_back
- mod = f.f_globals.get('__name__') or ''
- pkg = mod.split('.', 1)[0]
- key = tuple(['metadata-tag', pkg] + sorted(kwargs))
- if _should_warn(key):
- warn("metadata %s was set from the constructor. "
- "With traitlets 4.1, metadata should be set using the .tag() method, "
- "e.g., Int().tag(key1='value1', key2='value2')" % (kwargs,),
- DeprecationWarning, stacklevel=stacklevel)
- if len(self.metadata) > 0:
- self.metadata = self.metadata.copy()
- self.metadata.update(kwargs)
- else:
- self.metadata = kwargs
- else:
- self.metadata = self.metadata.copy()
- if config is not None:
- self.metadata['config'] = config
-
- # We add help to the metadata during a deprecation period so that
- # code that looks for the help string there can find it.
- if help is not None:
- self.metadata['help'] = help
-
- def get_default_value(self):
- """DEPRECATED: Retrieve the static default value for this trait.
-
- Use self.default_value instead
- """
- warn("get_default_value is deprecated in traitlets 4.0: use the .default_value attribute", DeprecationWarning,
- stacklevel=2)
- return self.default_value
-
- def init_default_value(self, obj):
- """DEPRECATED: Set the static default value for the trait type.
- """
- warn("init_default_value is deprecated in traitlets 4.0, and may be removed in the future", DeprecationWarning,
- stacklevel=2)
- value = self._validate(obj, self.default_value)
- obj._trait_values[self.name] = value
- return value
-
- def _dynamic_default_callable(self, obj):
- """Retrieve a callable to calculate the default for this traitlet.
-
- This looks for:
-
- * default generators registered with the @default descriptor.
- * obj._{name}_default() on the class with the traitlet, or a subclass
- that obj belongs to.
- * trait.make_dynamic_default, which is defined by Instance
-
- If neither exist, it returns None
- """
- # Traitlets without a name are not on the instance, e.g. in List or Union
- if self.name:
-
- # Only look for default handlers in classes derived from self.this_class.
- mro = type(obj).mro()
- meth_name = '_%s_default' % self.name
- for cls in mro[:mro.index(self.this_class) + 1]:
- if hasattr(cls, '_trait_default_generators'):
- default_handler = cls._trait_default_generators.get(self.name)
- if default_handler is not None and default_handler.this_class == cls:
- return types.MethodType(default_handler.func, obj)
-
- if meth_name in cls.__dict__:
- method = getattr(obj, meth_name)
- return method
-
- return getattr(self, 'make_dynamic_default', None)
-
- def instance_init(self, obj):
- # If no dynamic initialiser is present, and the trait implementation or
- # use provides a static default, transfer that to obj._trait_values.
- with obj.cross_validation_lock:
- if (self._dynamic_default_callable(obj) is None) \
- and (self.default_value is not Undefined):
- v = self._validate(obj, self.default_value)
- if self.name is not None:
- obj._trait_values[self.name] = v
-
- def get(self, obj, cls=None):
- try:
- value = obj._trait_values[self.name]
- except KeyError:
- # Check for a dynamic initializer.
- dynamic_default = self._dynamic_default_callable(obj)
- if dynamic_default is None:
- raise TraitError("No default value found for %s trait of %r"
- % (self.name, obj))
- value = self._validate(obj, dynamic_default())
- obj._trait_values[self.name] = value
- return value
- except Exception:
- # This should never be reached.
- raise TraitError('Unexpected error in TraitType: '
- 'default value not set properly')
- else:
- return value
-
- def __get__(self, obj, cls=None):
- """Get the value of the trait by self.name for the instance.
-
- Default values are instantiated when :meth:`HasTraits.__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:`HasTraits` instance.
- """
- if obj is None:
- return self
- else:
- return self.get(obj, cls)
-
- def set(self, obj, value):
- new_value = self._validate(obj, value)
- try:
- old_value = obj._trait_values[self.name]
- except KeyError:
- old_value = self.default_value
-
- obj._trait_values[self.name] = new_value
- try:
- silent = bool(old_value == new_value)
- except:
- # if there is an error in comparing, default to notify
- silent = False
- if silent is not True:
- # we explicitly compare silent to True just in case the equality
- # comparison above returns something other than True/False
- obj._notify_trait(self.name, old_value, new_value)
-
- def __set__(self, obj, value):
- """Set the value of the trait by self.name for the instance.
-
- Values pass through a validation stage where errors are raised when
- impropper types, or types that cannot be coerced, are encountered.
- """
- if self.read_only:
- raise TraitError('The "%s" trait is read-only.' % self.name)
- else:
- self.set(obj, value)
-
- def _validate(self, obj, value):
- if value is None and self.allow_none:
- return value
- if hasattr(self, 'validate'):
- value = self.validate(obj, value)
- if obj._cross_validation_lock is False:
- value = self._cross_validate(obj, value)
- return value
-
- def _cross_validate(self, obj, value):
- if self.name in obj._trait_validators:
- proposal = Bunch({'trait': self, 'value': value, 'owner': obj})
- value = obj._trait_validators[self.name](obj, proposal)
- elif hasattr(obj, '_%s_validate' % self.name):
- meth_name = '_%s_validate' % self.name
- cross_validate = getattr(obj, meth_name)
- _deprecated_method(cross_validate, obj.__class__, meth_name,
- "use @validate decorator instead.")
- value = cross_validate(value, self)
- return value
-
- def __or__(self, other):
- if isinstance(other, Union):
- return Union([self] + other.trait_types)
- else:
- return Union([self, other])
-
- def info(self):
- return self.info_text
-
- def error(self, obj, value):
- 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(), repr_type(value))
- else:
- e = "The '%s' trait must be %s, but a value of %r was specified." \
- % (self.name, self.info(), repr_type(value))
- raise TraitError(e)
-
- def get_metadata(self, key, default=None):
- """DEPRECATED: Get a metadata value.
-
- Use .metadata[key] or .metadata.get(key, default) instead.
- """
- if key == 'help':
- msg = "use the instance .help string directly, like x.help"
- else:
- msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)"
- warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
- return self.metadata.get(key, default)
-
- def set_metadata(self, key, value):
- """DEPRECATED: Set a metadata key/value.
-
- Use .metadata[key] = value instead.
- """
- if key == 'help':
- msg = "use the instance .help string directly, like x.help = value"
- else:
- msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value"
- warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
- self.metadata[key] = value
-
- def tag(self, **metadata):
- """Sets metadata and returns self.
-
- This allows convenient metadata tagging when initializing the trait, such as:
-
- >>> Int(0).tag(config=True, sync=True)
- """
- maybe_constructor_keywords = set(metadata.keys()).intersection({'help','allow_none', 'read_only', 'default_value'})
- if maybe_constructor_keywords:
- warn('The following attributes are set in using `tag`, but seem to be constructor keywords arguments: %s '%
- maybe_constructor_keywords, UserWarning, stacklevel=2)
-
- self.metadata.update(metadata)
- return self
-
- def default_value_repr(self):
- return repr(self.default_value)
-
- #-----------------------------------------------------------------------------
- # The HasTraits implementation
- #-----------------------------------------------------------------------------
-
- class _CallbackWrapper(object):
- """An object adapting a on_trait_change callback into an observe callback.
-
- The comparison operator __eq__ is implemented to enable removal of wrapped
- callbacks.
- """
-
- def __init__(self, cb):
- self.cb = cb
- # Bound methods have an additional 'self' argument.
- offset = -1 if isinstance(self.cb, types.MethodType) else 0
- self.nargs = len(getargspec(cb)[0]) + offset
- if (self.nargs > 4):
- raise TraitError('a trait changed callback must have 0-4 arguments.')
-
- def __eq__(self, other):
- # The wrapper is equal to the wrapped element
- if isinstance(other, _CallbackWrapper):
- return self.cb == other.cb
- else:
- return self.cb == other
-
- def __call__(self, change):
- # The wrapper is callable
- if self.nargs == 0:
- self.cb()
- elif self.nargs == 1:
- self.cb(change.name)
- elif self.nargs == 2:
- self.cb(change.name, change.new)
- elif self.nargs == 3:
- self.cb(change.name, change.old, change.new)
- elif self.nargs == 4:
- self.cb(change.name, change.old, change.new, change.owner)
-
- def _callback_wrapper(cb):
- if isinstance(cb, _CallbackWrapper):
- return cb
- else:
- return _CallbackWrapper(cb)
-
-
- class MetaHasDescriptors(type):
- """A metaclass for HasDescriptors.
-
- This metaclass makes sure that any TraitType class attributes are
- instantiated and sets their name attribute.
- """
-
- def __new__(mcls, name, bases, classdict):
- """Create the HasDescriptors class."""
- for k, v in classdict.items():
- # ----------------------------------------------------------------
- # Support of deprecated behavior allowing for TraitType types
- # to be used instead of TraitType instances.
- if inspect.isclass(v) and issubclass(v, TraitType):
- warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning, stacklevel=2)
- classdict[k] = v()
- # ----------------------------------------------------------------
-
- return super(MetaHasDescriptors, mcls).__new__(mcls, name, bases, classdict)
-
- def __init__(cls, name, bases, classdict):
- """Finish initializing the HasDescriptors class."""
- super(MetaHasDescriptors, cls).__init__(name, bases, classdict)
- cls.setup_class(classdict)
-
- def setup_class(cls, classdict):
- """Setup descriptor instance on the class
-
- This sets the :attr:`this_class` and :attr:`name` attributes of each
- BaseDescriptor in the class dict of the newly created ``cls`` before
- calling their :attr:`class_init` method.
- """
- for k, v in classdict.items():
- if isinstance(v, BaseDescriptor):
- v.class_init(cls, k)
-
-
- class MetaHasTraits(MetaHasDescriptors):
- """A metaclass for HasTraits."""
-
- def setup_class(cls, classdict):
- cls._trait_default_generators = {}
- super(MetaHasTraits, cls).setup_class(classdict)
-
-
- def observe(*names, **kwargs):
- """A decorator which can be used to observe Traits on a class.
-
- The handler passed to the decorator will be called with one ``change``
- dict argument. The change dictionary at least holds a 'type' key and a
- 'name' key, corresponding respectively to the type of notification and the
- name of the attribute that triggered the notification.
-
- Other keys may be passed depending on the value of 'type'. In the case
- where type is 'change', we also have the following keys:
- * ``owner`` : the HasTraits instance
- * ``old`` : the old value of the modified trait attribute
- * ``new`` : the new value of the modified trait attribute
- * ``name`` : the name of the modified trait attribute.
-
- Parameters
- ----------
- *names
- The str names of the Traits to observe on the object.
- type: str, kwarg-only
- The type of event to observe (e.g. 'change')
- """
- if not names:
- raise TypeError("Please specify at least one trait name to observe.")
- for name in names:
- if name is not All and not isinstance(name, six.string_types):
- raise TypeError("trait names to observe must be strings or All, not %r" % name)
- return ObserveHandler(names, type=kwargs.get('type', 'change'))
-
-
- def observe_compat(func):
- """Backward-compatibility shim decorator for observers
-
- Use with:
-
- @observe('name')
- @observe_compat
- def _foo_changed(self, change):
- ...
-
- With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work.
- Allows adoption of new observer API without breaking subclasses that override and super.
- """
- def compatible_observer(self, change_or_name, old=Undefined, new=Undefined):
- if isinstance(change_or_name, dict):
- change = change_or_name
- else:
- clsname = self.__class__.__name__
- warn("A parent of %s._%s_changed has adopted the new (traitlets 4.1) @observe(change) API" % (
- clsname, change_or_name), DeprecationWarning)
- change = Bunch(
- type='change',
- old=old,
- new=new,
- name=change_or_name,
- owner=self,
- )
- return func(self, change)
- return compatible_observer
-
-
- def validate(*names):
- """A decorator to register cross validator of HasTraits object's state
- when a Trait is set.
-
- The handler passed to the decorator must have one ``proposal`` dict argument.
- The proposal dictionary must hold the following keys:
- * ``owner`` : the HasTraits instance
- * ``value`` : the proposed value for the modified trait attribute
- * ``trait`` : the TraitType instance associated with the attribute
-
- Parameters
- ----------
- names
- The str names of the Traits to validate.
-
- Notes
- -----
- Since the owner has access to the ``HasTraits`` instance via the 'owner' key,
- the registered cross validator could potentially make changes to attributes
- of the ``HasTraits`` instance. However, we recommend not to do so. The reason
- is that the cross-validation of attributes may run in arbitrary order when
- exiting the ``hold_trait_notifications`` context, and such changes may not
- commute.
- """
- if not names:
- raise TypeError("Please specify at least one trait name to validate.")
- for name in names:
- if name is not All and not isinstance(name, six.string_types):
- raise TypeError("trait names to validate must be strings or All, not %r" % name)
- return ValidateHandler(names)
-
-
- def default(name):
- """ A decorator which assigns a dynamic default for a Trait on a HasTraits object.
-
- Parameters
- ----------
- name
- The str name of the Trait on the object whose default should be generated.
-
- Notes
- -----
- Unlike observers and validators which are properties of the HasTraits
- instance, default value generators are class-level properties.
-
- Besides, default generators are only invoked if they are registered in
- subclasses of `this_type`.
-
- ::
-
- class A(HasTraits):
- bar = Int()
-
- @default('bar')
- def get_bar_default(self):
- return 11
-
-
- class B(A):
- bar = Float() # This trait ignores the default generator defined in
- # the base class A
-
-
- class C(B):
-
- @default('bar')
- def some_other_default(self): # This default generator should not be
- return 3.0 # ignored since it is defined in a
- # class derived from B.a.this_class.
- """
- if not isinstance(name, six.string_types):
- raise TypeError("Trait name must be a string or All, not %r" % name)
- return DefaultHandler(name)
-
-
- class EventHandler(BaseDescriptor):
-
- def _init_call(self, func):
- self.func = func
- return self
-
- def __call__(self, *args, **kwargs):
- """Pass `*args` and `**kwargs` to the handler's function if it exists."""
- if hasattr(self, 'func'):
- return self.func(*args, **kwargs)
- else:
- return self._init_call(*args, **kwargs)
-
- def __get__(self, inst, cls=None):
- if inst is None:
- return self
- return types.MethodType(self.func, inst)
-
-
- class ObserveHandler(EventHandler):
-
- def __init__(self, names, type):
- self.trait_names = names
- self.type = type
-
- def instance_init(self, inst):
- inst.observe(self, self.trait_names, type=self.type)
-
-
- class ValidateHandler(EventHandler):
-
- def __init__(self, names):
- self.trait_names = names
-
- def instance_init(self, inst):
- inst._register_validator(self, self.trait_names)
-
-
- class DefaultHandler(EventHandler):
-
- def __init__(self, name):
- self.trait_name = name
-
- def class_init(self, cls, name):
- super(DefaultHandler, self).class_init(cls, name)
- cls._trait_default_generators[self.trait_name] = self
-
-
- class HasDescriptors(six.with_metaclass(MetaHasDescriptors, object)):
- """The base class for all classes that have descriptors.
- """
-
- def __new__(cls, *args, **kwargs):
- # This is needed because object.__new__ only accepts
- # the cls argument.
- new_meth = super(HasDescriptors, cls).__new__
- if new_meth is object.__new__:
- inst = new_meth(cls)
- else:
- inst = new_meth(cls, *args, **kwargs)
- inst.setup_instance(*args, **kwargs)
- return inst
-
- def setup_instance(self, *args, **kwargs):
- """
- This is called **before** self.__init__ is called.
- """
- self._cross_validation_lock = False
- cls = self.__class__
- for key in dir(cls):
- # 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, BaseDescriptor):
- value.instance_init(self)
-
-
- class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)):
-
- def setup_instance(self, *args, **kwargs):
- self._trait_values = {}
- self._trait_notifiers = {}
- self._trait_validators = {}
- super(HasTraits, self).setup_instance(*args, **kwargs)
-
- def __init__(self, *args, **kwargs):
- # Allow trait values to be set using keyword arguments.
- # We need to use setattr for this to trigger validation and
- # notifications.
- super_args = args
- super_kwargs = {}
- with self.hold_trait_notifications():
- for key, value in kwargs.items():
- if self.has_trait(key):
- setattr(self, key, value)
- else:
- # passthrough args that don't set traits to super
- super_kwargs[key] = value
- try:
- super(HasTraits, self).__init__(*super_args, **super_kwargs)
- except TypeError as e:
- arg_s_list = [ repr(arg) for arg in super_args ]
- for k, v in super_kwargs.items():
- arg_s_list.append("%s=%r" % (k, v))
- arg_s = ', '.join(arg_s_list)
- warn(
- "Passing unrecoginized arguments to super({classname}).__init__({arg_s}).\n"
- "{error}\n"
- "This is deprecated in traitlets 4.2."
- "This error will be raised in a future release of traitlets."
- .format(
- arg_s=arg_s, classname=self.__class__.__name__,
- error=e,
- ),
- DeprecationWarning,
- stacklevel=2,
- )
-
- def __getstate__(self):
- d = self.__dict__.copy()
- # event handlers stored on an instance are
- # expected to be reinstantiated during a
- # recall of instance_init during __setstate__
- d['_trait_notifiers'] = {}
- d['_trait_validators'] = {}
- return d
-
- def __setstate__(self, state):
- self.__dict__ = state.copy()
-
- # event handlers are reassigned to self
- cls = self.__class__
- for key in dir(cls):
- # 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, EventHandler):
- value.instance_init(self)
-
- @property
- @contextlib.contextmanager
- def cross_validation_lock(self):
- """
- A contextmanager for running a block with our cross validation lock set
- to True.
-
- At the end of the block, the lock's value is restored to its value
- prior to entering the block.
- """
- if self._cross_validation_lock:
- yield
- return
- else:
- try:
- self._cross_validation_lock = True
- yield
- finally:
- self._cross_validation_lock = False
-
- @contextlib.contextmanager
- def hold_trait_notifications(self):
- """Context manager for bundling trait change notifications and cross
- validation.
-
- Use this when doing multiple trait assignments (init, config), to avoid
- race conditions in trait notifiers requesting other trait values.
- All trait notifications will fire after all values have been assigned.
- """
- if self._cross_validation_lock:
- yield
- return
- else:
- cache = {}
- notify_change = self.notify_change
-
- def compress(past_changes, change):
- """Merges the provided change with the last if possible."""
- if past_changes is None:
- return [change]
- else:
- if past_changes[-1]['type'] == 'change' and change.type == 'change':
- past_changes[-1]['new'] = change.new
- else:
- # In case of changes other than 'change', append the notification.
- past_changes.append(change)
- return past_changes
-
- def hold(change):
- name = change.name
- cache[name] = compress(cache.get(name), change)
-
- try:
- # Replace notify_change with `hold`, caching and compressing
- # notifications, disable cross validation and yield.
- self.notify_change = hold
- self._cross_validation_lock = True
- yield
- # Cross validate final values when context is released.
- for name in list(cache.keys()):
- trait = getattr(self.__class__, name)
- value = trait._cross_validate(self, getattr(self, name))
- self.set_trait(name, value)
- except TraitError as e:
- # Roll back in case of TraitError during final cross validation.
- self.notify_change = lambda x: None
- for name, changes in cache.items():
- for change in changes[::-1]:
- # TODO: Separate in a rollback function per notification type.
- if change.type == 'change':
- if change.old is not Undefined:
- self.set_trait(name, change.old)
- else:
- self._trait_values.pop(name)
- cache = {}
- raise e
- finally:
- self._cross_validation_lock = False
- # Restore method retrieval from class
- del self.notify_change
-
- # trigger delayed notifications
- for changes in cache.values():
- for change in changes:
- self.notify_change(change)
-
- def _notify_trait(self, name, old_value, new_value):
- self.notify_change(Bunch(
- name=name,
- old=old_value,
- new=new_value,
- owner=self,
- type='change',
- ))
-
- def notify_change(self, change):
- if not isinstance(change, Bunch):
- # cast to bunch if given a dict
- change = Bunch(change)
- name, type = change.name, change.type
-
- callables = []
- callables.extend(self._trait_notifiers.get(name, {}).get(type, []))
- callables.extend(self._trait_notifiers.get(name, {}).get(All, []))
- callables.extend(self._trait_notifiers.get(All, {}).get(type, []))
- callables.extend(self._trait_notifiers.get(All, {}).get(All, []))
-
- # Now static ones
- magic_name = '_%s_changed' % name
- if hasattr(self, magic_name):
- class_value = getattr(self.__class__, magic_name)
- if not isinstance(class_value, ObserveHandler):
- _deprecated_method(class_value, self.__class__, magic_name,
- "use @observe and @unobserve instead.")
- cb = getattr(self, magic_name)
- # Only append the magic method if it was not manually registered
- if cb not in callables:
- callables.append(_callback_wrapper(cb))
-
- # Call them all now
- # Traits catches and logs errors here. I allow them to raise
- for c in callables:
- # Bound methods have an additional 'self' argument.
-
- if isinstance(c, _CallbackWrapper):
- c = c.__call__
- elif isinstance(c, EventHandler) and c.name is not None:
- c = getattr(self, c.name)
-
- c(change)
-
- def _add_notifiers(self, handler, name, type):
- if name not in self._trait_notifiers:
- nlist = []
- self._trait_notifiers[name] = {type: nlist}
- else:
- if type not in self._trait_notifiers[name]:
- nlist = []
- self._trait_notifiers[name][type] = nlist
- else:
- nlist = self._trait_notifiers[name][type]
- if handler not in nlist:
- nlist.append(handler)
-
- def _remove_notifiers(self, handler, name, type):
- try:
- if handler is None:
- del self._trait_notifiers[name][type]
- else:
- self._trait_notifiers[name][type].remove(handler)
- except KeyError:
- pass
-
- def on_trait_change(self, handler=None, name=None, remove=False):
- """DEPRECATED: Setup a handler to be called when a trait changes.
-
- This is used to setup dynamic notifications of trait changes.
-
- Static handlers can be created by creating methods on a HasTraits
- subclass with the naming convention '_[traitname]_changed'. Thus,
- to create static handler for the trait 'a', create the method
- _a_changed(self, name, old, new) (fewer arguments can be used, see
- below).
-
- If `remove` is True and `handler` is not specified, all change
- handlers for the specified name are uninstalled.
-
- Parameters
- ----------
- handler : callable, None
- A callable that is called when a trait changes. Its
- signature can be handler(), handler(name), handler(name, new),
- handler(name, old, new), or handler(name, old, new, self).
- name : list, str, None
- If None, the handler will apply to all traits. 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.
- """
- warn("on_trait_change is deprecated in traitlets 4.1: use observe instead",
- DeprecationWarning, stacklevel=2)
- if name is None:
- name = All
- if remove:
- self.unobserve(_callback_wrapper(handler), names=name)
- else:
- self.observe(_callback_wrapper(handler), names=name)
-
- def observe(self, handler, names=All, type='change'):
- """Setup a handler to be called when a trait changes.
-
- This is used to setup dynamic notifications of trait changes.
-
- Parameters
- ----------
- handler : callable
- A callable that is called when a trait changes. Its
- signature should be ``handler(change)``, where ``change`` is a
- dictionary. The change dictionary at least holds a 'type' key.
- * ``type``: the type of notification.
- Other keys may be passed depending on the value of 'type'. In the
- case where type is 'change', we also have the following keys:
- * ``owner`` : the HasTraits instance
- * ``old`` : the old value of the modified trait attribute
- * ``new`` : the new value of the modified trait attribute
- * ``name`` : the name of the modified trait attribute.
- names : list, str, All
- If names is All, the handler will apply to all traits. 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.
- type : str, All (default: 'change')
- The type of notification to filter by. If equal to All, then all
- notifications are passed to the observe handler.
- """
- names = parse_notifier_name(names)
- for n in names:
- self._add_notifiers(handler, n, type)
-
- def unobserve(self, handler, names=All, type='change'):
- """Remove a trait change handler.
-
- This is used to unregister handlers to trait change notifications.
-
- Parameters
- ----------
- handler : callable
- The callable called when a trait attribute changes.
- names : list, str, All (default: All)
- The names of the traits for which the specified handler should be
- uninstalled. If names is All, the specified handler is uninstalled
- from the list of notifiers corresponding to all changes.
- type : str or All (default: 'change')
- The type of notification to filter by. If All, the specified handler
- is uninstalled from the list of notifiers corresponding to all types.
- """
- names = parse_notifier_name(names)
- for n in names:
- self._remove_notifiers(handler, n, type)
-
- def unobserve_all(self, name=All):
- """Remove trait change handlers of any type for the specified name.
- If name is not specified, removes all trait notifiers."""
- if name is All:
- self._trait_notifiers = {}
- else:
- try:
- del self._trait_notifiers[name]
- except KeyError:
- pass
-
- def _register_validator(self, handler, names):
- """Setup a handler to be called when a trait should be cross validated.
-
- This is used to setup dynamic notifications for cross-validation.
-
- If a validator is already registered for any of the provided names, a
- TraitError is raised and no new validator is registered.
-
- Parameters
- ----------
- handler : callable
- A callable that is called when the given trait is cross-validated.
- Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access)
- with the following attributes/keys:
- * ``owner`` : the HasTraits instance
- * ``value`` : the proposed value for the modified trait attribute
- * ``trait`` : the TraitType instance associated with the attribute
- names : List of strings
- The names of the traits that should be cross-validated
- """
- for name in names:
- magic_name = '_%s_validate' % name
- if hasattr(self, magic_name):
- class_value = getattr(self.__class__, magic_name)
- if not isinstance(class_value, ValidateHandler):
- _deprecated_method(class_value, self.__class, magic_name,
- "use @validate decorator instead.")
- for name in names:
- self._trait_validators[name] = handler
-
- def add_traits(self, **traits):
- """Dynamically add trait attributes to the HasTraits instance."""
- self.__class__ = type(self.__class__.__name__, (self.__class__,),
- traits)
- for trait in traits.values():
- trait.instance_init(self)
-
- def set_trait(self, name, value):
- """Forcibly sets trait attribute, including read-only attributes."""
- cls = self.__class__
- if not self.has_trait(name):
- raise TraitError("Class %s does not have a trait named %s" %
- (cls.__name__, name))
- else:
- getattr(cls, name).set(self, value)
-
- @classmethod
- def class_trait_names(cls, **metadata):
- """Get a list of all the names of this class' traits.
-
- This method is just like the :meth:`trait_names` method,
- but is unbound.
- """
- return list(cls.class_traits(**metadata))
-
- @classmethod
- def class_traits(cls, **metadata):
- """Get a ``dict`` of all the traits of this class. The dictionary
- is keyed on the name and the values are the TraitType objects.
-
- 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.
-
- The metadata kwargs allow functions to be passed in which
- filter traits based on metadata values. The functions should
- take a single value as an argument and return a boolean. If
- any function returns False, then the trait is not included in
- the output. If a metadata key doesn't exist, None will be passed
- to the function.
- """
- traits = dict([memb for memb in getmembers(cls) if
- isinstance(memb[1], TraitType)])
-
- if len(metadata) == 0:
- return traits
-
- result = {}
- for name, trait in traits.items():
- for meta_name, meta_eval in metadata.items():
- if type(meta_eval) is not types.FunctionType:
- meta_eval = _SimpleTest(meta_eval)
- if not meta_eval(trait.metadata.get(meta_name, None)):
- break
- else:
- result[name] = trait
-
- return result
-
- @classmethod
- def class_own_traits(cls, **metadata):
- """Get a dict of all the traitlets defined on this class, not a parent.
-
- Works like `class_traits`, except for excluding traits from parents.
- """
- sup = super(cls, cls)
- return {n: t for (n, t) in cls.class_traits(**metadata).items()
- if getattr(sup, n, None) is not t}
-
- def has_trait(self, name):
- """Returns True if the object has a trait with the specified name."""
- return isinstance(getattr(self.__class__, name, None), TraitType)
-
- def trait_names(self, **metadata):
- """Get a list of all the names of this class' traits."""
- return list(self.traits(**metadata))
-
- def traits(self, **metadata):
- """Get a ``dict`` of all the traits of this class. The dictionary
- is keyed on the name and the values are the TraitType objects.
-
- The TraitTypes returned don't know anything about the values
- that the various HasTrait's instances are holding.
-
- The metadata kwargs allow functions to be passed in which
- filter traits based on metadata values. The functions should
- take a single value as an argument and return a boolean. If
- any function returns False, then the trait is not included in
- the output. If a metadata key doesn't exist, None will be passed
- to the function.
- """
- traits = dict([memb for memb in getmembers(self.__class__) if
- isinstance(memb[1], TraitType)])
-
- if len(metadata) == 0:
- return traits
-
- result = {}
- for name, trait in traits.items():
- for meta_name, meta_eval in metadata.items():
- if type(meta_eval) is not types.FunctionType:
- meta_eval = _SimpleTest(meta_eval)
- if not meta_eval(trait.metadata.get(meta_name, None)):
- break
- else:
- result[name] = trait
-
- return result
-
- def trait_metadata(self, traitname, key, default=None):
- """Get metadata values for trait by key."""
- try:
- trait = getattr(self.__class__, traitname)
- except AttributeError:
- raise TraitError("Class %s does not have a trait named %s" %
- (self.__class__.__name__, traitname))
- metadata_name = '_' + traitname + '_metadata'
- if hasattr(self, metadata_name) and key in getattr(self, metadata_name):
- return getattr(self, metadata_name).get(key, default)
- else:
- return trait.metadata.get(key, default)
-
- @classmethod
- def class_own_trait_events(cls, name):
- """Get a dict of all event handlers defined on this class, not a parent.
-
- Works like ``event_handlers``, except for excluding traits from parents.
- """
- sup = super(cls, cls)
- return {n: e for (n, e) in cls.events(name).items()
- if getattr(sup, n, None) is not e}
-
- @classmethod
- def trait_events(cls, name=None):
- """Get a ``dict`` of all the event handlers of this class.
-
- Parameters
- ----------
- name: str (default: None)
- The name of a trait of this class. If name is ``None`` then all
- the event handlers of this class will be returned instead.
-
- Returns
- -------
- The event handlers associated with a trait name, or all event handlers.
- """
- events = {}
- for k, v in getmembers(cls):
- if isinstance(v, EventHandler):
- if name is None:
- events[k] = v
- elif name in v.trait_names:
- events[k] = v
- elif hasattr(v, 'tags'):
- if cls.trait_names(**v.tags):
- events[k] = v
- return events
-
- #-----------------------------------------------------------------------------
- # Actual TraitTypes implementations/subclasses
- #-----------------------------------------------------------------------------
-
- #-----------------------------------------------------------------------------
- # TraitTypes subclasses for handling classes and instances of classes
- #-----------------------------------------------------------------------------
-
-
- class ClassBasedTraitType(TraitType):
- """
- A trait with error reporting and string -> type resolution for Type,
- Instance and This.
- """
-
- def _resolve_string(self, string):
- """
- Resolve a string supplied for a type into an actual object.
- """
- return import_item(string)
-
- def error(self, obj, value):
- kind = type(value)
- if six.PY2 and kind is InstanceType:
- msg = 'class %s' % value.__class__.__name__
- else:
- msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
-
- 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)
-
-
- class Type(ClassBasedTraitType):
- """A trait whose value must be a subclass of a specified class."""
-
- def __init__ (self, default_value=Undefined, klass=None, **kwargs):
- """Construct a Type trait
-
- A Type trait specifies that its values must be subclasses of
- a particular class.
-
- If only ``default_value`` is given, it is used for the ``klass`` as
- well. If neither are given, both default to ``object``.
-
- Parameters
- ----------
- 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:`HasTraits` class is instantiated.
- klass : class, str [ default object ]
- Values of this trait must be a subclass of klass. The klass
- may be specified in a string like: 'foo.bar.MyClass'.
- The string is resolved into real class, when the parent
- :class:`HasTraits` class is instantiated.
- allow_none : bool [ default False ]
- Indicates whether None is allowed as an assignable value.
- """
- if default_value is Undefined:
- new_default_value = object if (klass is None) else klass
- else:
- new_default_value = default_value
-
- if klass is None:
- if (default_value is None) or (default_value is Undefined):
- klass = object
- else:
- klass = default_value
-
- if not (inspect.isclass(klass) or isinstance(klass, six.string_types)):
- raise TraitError("A Type trait must specify a class.")
-
- self.klass = klass
-
- super(Type, self).__init__(new_default_value, **kwargs)
-
- def validate(self, obj, value):
- """Validates that the value is a valid object instance."""
- if isinstance(value, six.string_types):
- try:
- value = self._resolve_string(value)
- except ImportError:
- raise TraitError("The '%s' trait of %s instance must be a type, but "
- "%r could not be imported" % (self.name, obj, value))
- try:
- if issubclass(value, self.klass):
- return value
- except:
- pass
-
- self.error(obj, value)
-
- def info(self):
- """ Returns a description of the trait."""
- if isinstance(self.klass, six.string_types):
- klass = self.klass
- else:
- klass = self.klass.__module__ + '.' + self.klass.__name__
- result = "a subclass of '%s'" % klass
- if self.allow_none:
- return result + ' or None'
- return result
-
- def instance_init(self, obj):
- self._resolve_classes()
- super(Type, self).instance_init(obj)
-
- def _resolve_classes(self):
- if isinstance(self.klass, six.string_types):
- self.klass = self._resolve_string(self.klass)
- if isinstance(self.default_value, six.string_types):
- self.default_value = self._resolve_string(self.default_value)
-
- def default_value_repr(self):
- value = self.default_value
- if isinstance(value, six.string_types):
- return repr(value)
- else:
- return repr('{}.{}'.format(value.__module__, value.__name__))
-
-
- class Instance(ClassBasedTraitType):
- """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.
-
- Subclasses can declare default classes by overriding the klass attribute
- """
-
- klass = None
-
- def __init__(self, klass=None, args=None, kw=None, **kwargs):
- """Construct an Instance trait.
-
- This trait allows values that are instances of a particular
- class or its subclasses. 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.
-
- Parameters
- ----------
- klass : class, str
- The class that forms the basis for the trait. Class names
- can also be specified as strings, like 'foo.bar.Bar'.
- args : tuple
- Positional arguments for generating the default value.
- kw : dict
- Keyword arguments for generating the default value.
- allow_none : bool [ default False ]
- Indicates whether None is allowed as a value.
-
- Notes
- -----
- 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 exactly one of ``args`` or ``kw`` is
- None, the None is replaced by ``()`` or ``{}``, respectively.
- """
- if klass is None:
- klass = self.klass
-
- if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, six.string_types)):
- self.klass = klass
- else:
- raise TraitError('The klass attribute must be a class'
- ' not: %r' % klass)
-
- if (kw is not None) and not isinstance(kw, dict):
- raise TraitError("The 'kw' argument must be a dict or None.")
- if (args is not None) and not isinstance(args, tuple):
- raise TraitError("The 'args' argument must be a tuple or None.")
-
- self.default_args = args
- self.default_kwargs = kw
-
- super(Instance, self).__init__(**kwargs)
-
- def validate(self, obj, value):
- if isinstance(value, self.klass):
- return value
- else:
- self.error(obj, value)
-
- def info(self):
- if isinstance(self.klass, six.string_types):
- klass = self.klass
- else:
- klass = self.klass.__name__
- result = class_of(klass)
- if self.allow_none:
- return result + ' or None'
-
- return result
-
- def instance_init(self, obj):
- self._resolve_classes()
- super(Instance, self).instance_init(obj)
-
- def _resolve_classes(self):
- if isinstance(self.klass, six.string_types):
- self.klass = self._resolve_string(self.klass)
-
- def make_dynamic_default(self):
- if (self.default_args is None) and (self.default_kwargs is None):
- return None
- return self.klass(*(self.default_args or ()),
- **(self.default_kwargs or {}))
-
- def default_value_repr(self):
- return repr(self.make_dynamic_default())
-
-
- class ForwardDeclaredMixin(object):
- """
- Mixin for forward-declared versions of Instance and Type.
- """
- def _resolve_string(self, string):
- """
- Find the specified class name by looking for it in the module in which
- our this_class attribute was defined.
- """
- modname = self.this_class.__module__
- return import_item('.'.join([modname, string]))
-
-
- class ForwardDeclaredType(ForwardDeclaredMixin, Type):
- """
- Forward-declared version of Type.
- """
- pass
-
-
- class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
- """
- Forward-declared version of Instance.
- """
- pass
-
-
- class This(ClassBasedTraitType):
- """A trait for instances of the class containing this trait.
-
- Because how how and when class bodies are executed, the ``This``
- trait can only have a default value of None. This, and because we
- always validate default values, ``allow_none`` is *always* true.
- """
-
- info_text = 'an instance of the same type as the receiver or None'
-
- def __init__(self, **kwargs):
- super(This, self).__init__(None, **kwargs)
-
- def validate(self, obj, value):
- # What if value is a superclass of obj.__class__? This is
- # complicated if it was the superclass that defined the This
- # trait.
- if isinstance(value, self.this_class) or (value is None):
- return value
- else:
- self.error(obj, value)
-
-
- class Union(TraitType):
- """A trait type representing a Union type."""
-
- def __init__(self, trait_types, **kwargs):
- """Construct a Union trait.
-
- This trait allows values that are allowed by at least one of the
- specified trait types. A Union traitlet cannot have metadata on
- its own, besides the metadata of the listed types.
-
- Parameters
- ----------
- trait_types: sequence
- The list of trait types of length at least 1.
-
- Notes
- -----
- Union([Float(), Bool(), Int()]) attempts to validate the provided values
- with the validation function of Float, then Bool, and finally Int.
- """
- self.trait_types = trait_types
- self.info_text = " or ".join([tt.info() for tt in self.trait_types])
- super(Union, self).__init__(**kwargs)
-
- def class_init(self, cls, name):
- for trait_type in self.trait_types:
- trait_type.class_init(cls, None)
- super(Union, self).class_init(cls, name)
-
- def instance_init(self, obj):
- for trait_type in self.trait_types:
- trait_type.instance_init(obj)
- super(Union, self).instance_init(obj)
-
- def validate(self, obj, value):
- with obj.cross_validation_lock:
- for trait_type in self.trait_types:
- try:
- v = trait_type._validate(obj, value)
- # In the case of an element trait, the name is None
- if self.name is not None:
- setattr(obj, '_' + self.name + '_metadata', trait_type.metadata)
- return v
- except TraitError:
- continue
- self.error(obj, value)
-
- def __or__(self, other):
- if isinstance(other, Union):
- return Union(self.trait_types + other.trait_types)
- else:
- return Union(self.trait_types + [other])
-
- def make_dynamic_default(self):
- if self.default_value is not Undefined:
- return self.default_value
- for trait_type in self.trait_types:
- if trait_type.default_value is not Undefined:
- return trait_type.default_value
- elif hasattr(trait_type, 'make_dynamic_default'):
- return trait_type.make_dynamic_default()
-
-
- #-----------------------------------------------------------------------------
- # Basic TraitTypes implementations/subclasses
- #-----------------------------------------------------------------------------
-
-
- class Any(TraitType):
- """A trait which allows any value."""
- default_value = None
- info_text = 'any value'
-
-
- def _validate_bounds(trait, obj, value):
- """
- Validate that a number to be applied to a trait is between bounds.
-
- If value is not between min_bound and max_bound, this raises a
- TraitError with an error message appropriate for this trait.
- """
- if trait.min is not None and value < trait.min:
- raise TraitError(
- "The value of the '{name}' trait of {klass} instance should "
- "not be less than {min_bound}, but a value of {value} was "
- "specified".format(
- name=trait.name, klass=class_of(obj),
- value=value, min_bound=trait.min))
- if trait.max is not None and value > trait.max:
- raise TraitError(
- "The value of the '{name}' trait of {klass} instance should "
- "not be greater than {max_bound}, but a value of {value} was "
- "specified".format(
- name=trait.name, klass=class_of(obj),
- value=value, max_bound=trait.max))
- return value
-
-
- class Int(TraitType):
- """An int trait."""
-
- default_value = 0
- info_text = 'an int'
-
- def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
- self.min = kwargs.pop('min', None)
- self.max = kwargs.pop('max', None)
- super(Int, self).__init__(default_value=default_value,
- allow_none=allow_none, **kwargs)
-
- def validate(self, obj, value):
- if not isinstance(value, int):
- self.error(obj, value)
- return _validate_bounds(self, obj, value)
-
-
- class CInt(Int):
- """A casting version of the int trait."""
-
- def validate(self, obj, value):
- try:
- value = int(value)
- except:
- self.error(obj, value)
- return _validate_bounds(self, obj, value)
-
-
- if six.PY2:
- class Long(TraitType):
- """A long integer trait."""
-
- default_value = 0
- info_text = 'a long'
-
- def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
- self.min = kwargs.pop('min', None)
- self.max = kwargs.pop('max', None)
- super(Long, self).__init__(
- default_value=default_value,
- allow_none=allow_none, **kwargs)
-
- def _validate_long(self, obj, value):
- if isinstance(value, long):
- return value
- if isinstance(value, int):
- return long(value)
- self.error(obj, value)
-
- def validate(self, obj, value):
- value = self._validate_long(obj, value)
- return _validate_bounds(self, obj, value)
-
-
- class CLong(Long):
- """A casting version of the long integer trait."""
-
- def validate(self, obj, value):
- try:
- value = long(value)
- except:
- self.error(obj, value)
- return _validate_bounds(self, obj, value)
-
-
- class Integer(TraitType):
- """An integer trait.
-
- Longs that are unnecessary (<= sys.maxint) are cast to ints."""
-
- default_value = 0
- info_text = 'an integer'
-
- def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
- self.min = kwargs.pop('min', None)
- self.max = kwargs.pop('max', None)
- super(Integer, self).__init__(
- default_value=default_value,
- allow_none=allow_none, **kwargs)
-
- def _validate_int(self, obj, value):
- if isinstance(value, int):
- return value
- if isinstance(value, long):
- # 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)
- if sys.platform == "cli":
- from System import Int64
- if isinstance(value, Int64):
- return int(value)
- self.error(obj, value)
-
- def validate(self, obj, value):
- value = self._validate_int(obj, value)
- return _validate_bounds(self, obj, value)
-
- else:
- Long, CLong = Int, CInt
- Integer = Int
-
-
- class Float(TraitType):
- """A float trait."""
-
- default_value = 0.0
- info_text = 'a float'
-
- def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
- self.min = kwargs.pop('min', -float('inf'))
- self.max = kwargs.pop('max', float('inf'))
- super(Float, self).__init__(default_value=default_value,
- allow_none=allow_none, **kwargs)
-
- def validate(self, obj, value):
- if isinstance(value, int):
- value = float(value)
- if not isinstance(value, float):
- self.error(obj, value)
- return _validate_bounds(self, obj, value)
-
-
- class CFloat(Float):
- """A casting version of the float trait."""
-
- def validate(self, obj, value):
- try:
- value = float(value)
- except:
- self.error(obj, value)
- return _validate_bounds(self, obj, value)
-
-
- class Complex(TraitType):
- """A trait for complex numbers."""
-
- 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)
-
-
- class CComplex(Complex):
- """A casting version of the complex number trait."""
-
- def validate (self, obj, value):
- try:
- return complex(value)
- except:
- self.error(obj, value)
-
- # 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.
- class Bytes(TraitType):
- """A trait for byte strings."""
-
- default_value = b''
- info_text = 'a bytes object'
-
- def validate(self, obj, value):
- if isinstance(value, bytes):
- return value
- self.error(obj, value)
-
-
- class CBytes(Bytes):
- """A casting version of the byte string trait."""
-
- def validate(self, obj, value):
- try:
- return bytes(value)
- except:
- self.error(obj, value)
-
-
- class Unicode(TraitType):
- """A trait for unicode strings."""
-
- default_value = u''
- info_text = 'a unicode string'
-
- def validate(self, obj, value):
- if isinstance(value, six.text_type):
- return value
- if isinstance(value, bytes):
- try:
- return value.decode('ascii', 'strict')
- except UnicodeDecodeError:
- msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
- raise TraitError(msg.format(value, self.name, class_of(obj)))
- self.error(obj, value)
-
-
- class CUnicode(Unicode):
- """A casting version of the unicode trait."""
-
- def validate(self, obj, value):
- try:
- return six.text_type(value)
- except:
- self.error(obj, value)
-
-
- class ObjectName(TraitType):
- """A string holding a valid object name in this version of Python.
-
- This does not check that the name exists in any scope."""
- info_text = "a valid object identifier in Python"
-
- if six.PY2:
- # 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
- else:
- coerce_str = staticmethod(lambda _,s: s)
-
- def validate(self, obj, value):
- value = self.coerce_str(obj, value)
-
- if isinstance(value, six.string_types) and isidentifier(value):
- 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)
-
- if isinstance(value, six.string_types) and all(isidentifier(a)
- for a in value.split('.')):
- return value
- self.error(obj, value)
-
-
- class Bool(TraitType):
- """A boolean (True, False) trait."""
-
- default_value = False
- info_text = 'a boolean'
-
- def validate(self, obj, value):
- if isinstance(value, bool):
- return value
- self.error(obj, value)
-
-
- class CBool(Bool):
- """A casting version of the boolean trait."""
-
- def validate(self, obj, value):
- try:
- return bool(value)
- except:
- self.error(obj, value)
-
-
- class Enum(TraitType):
- """An enum whose value must be in a given sequence."""
-
- def __init__(self, values, default_value=Undefined, **kwargs):
- self.values = values
- if kwargs.get('allow_none', False) and default_value is Undefined:
- default_value = None
- super(Enum, self).__init__(default_value, **kwargs)
-
- def validate(self, obj, 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):
- """An enum of strings where the case should be ignored."""
-
- def __init__(self, values, default_value=Undefined, **kwargs):
- values = [cast_unicode_py2(value) for value in values]
- super(CaselessStrEnum, self).__init__(values, default_value=default_value, **kwargs)
-
- def validate(self, obj, value):
- if isinstance(value, str):
- value = cast_unicode_py2(value)
- if not isinstance(value, six.string_types):
- self.error(obj, value)
-
- for v in self.values:
- if v.lower() == value.lower():
- return v
- self.error(obj, value)
-
- class Container(Instance):
- """An instance of a container (list, set, etc.)
-
- To be subclassed by overriding klass.
- """
- klass = None
- _cast_types = ()
- _valid_defaults = SequenceTypes
- _trait = None
-
- def __init__(self, trait=None, default_value=None, **kwargs):
- """Create a container trait type from a list, set, or tuple.
-
- The default value is created by doing ``List(default_value)``,
- which creates a copy of the ``default_value``.
-
- ``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 False ]
- Whether to allow the value to be None
-
- **kwargs : any
- further keys for extensions to the Trait (e.g. config)
-
- """
- # allow List([values]):
- if default_value is None and not is_trait(trait):
- default_value = trait
- trait = None
-
- if default_value is None:
- args = ()
- elif isinstance(default_value, self._valid_defaults):
- args = (default_value,)
- else:
- raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
-
- if is_trait(trait):
- if isinstance(trait, type):
- warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning, stacklevel=3)
- self._trait = trait() if isinstance(trait, type) else trait
- elif trait is not None:
- raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
-
- super(Container,self).__init__(klass=self.klass, args=args, **kwargs)
-
- 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)
-
- def validate(self, obj, value):
- if isinstance(value, self._cast_types):
- value = self.klass(value)
- value = super(Container, self).validate(obj, value)
- if value is None:
- return value
-
- 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)
-
- def class_init(self, cls, name):
- if isinstance(self._trait, TraitType):
- self._trait.class_init(cls, None)
- super(Container, self).class_init(cls, name)
-
- def instance_init(self, obj):
- if isinstance(self._trait, TraitType):
- self._trait.instance_init(obj)
- super(Container, self).instance_init(obj)
-
-
- class List(Container):
- """An instance of a Python list."""
- klass = list
- _cast_types = (tuple,)
-
- def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **kwargs):
- """Create a List trait type from a list, set, or tuple.
-
- The default value is created by doing ``list(default_value)``,
- which creates a copy of the ``default_value``.
-
- ``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
-
- maxlen : Int [ default sys.maxsize ]
- The maximum length of the input list
- """
- self._minlen = minlen
- self._maxlen = maxlen
- super(List, self).__init__(trait=trait, default_value=default_value,
- **kwargs)
-
- 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)
-
- def validate(self, obj, value):
- value = super(List, self).validate(obj, value)
- value = self.validate_elements(obj, value)
- return value
-
-
- class Set(List):
- """An instance of a Python set."""
- klass = set
- _cast_types = (tuple, list)
-
- # Redefine __init__ just to make the docstring more accurate.
- def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
- **kwargs):
- """Create a Set trait type from a list, set, or tuple.
-
- The default value is created by doing ``set(default_value)``,
- which creates a copy of the ``default_value``.
-
- ``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 = Set({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
-
- maxlen : Int [ default sys.maxsize ]
- The maximum length of the input list
- """
- super(Set, self).__init__(trait, default_value, minlen, maxlen, **kwargs)
-
-
- class Tuple(Container):
- """An instance of a Python tuple."""
- klass = tuple
- _cast_types = (list,)
-
- def __init__(self, *traits, **kwargs):
- """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 types 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,
- ``default_value`` must conform to the shape and type they specify.
- """
- default_value = kwargs.pop('default_value', Undefined)
- # allow Tuple((values,)):
- if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
- default_value = traits[0]
- traits = ()
-
- if default_value is Undefined:
- args = ()
- elif isinstance(default_value, self._valid_defaults):
- args = (default_value,)
- else:
- raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
-
- self._traits = []
- for trait in traits:
- if isinstance(trait, type):
- warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning, stacklevel=2)
- t = trait() if isinstance(trait, type) else trait
- 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, **kwargs)
-
- 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)
-
- def class_init(self, cls, name):
- for trait in self._traits:
- if isinstance(trait, TraitType):
- trait.class_init(cls, None)
- super(Container, self).class_init(cls, name)
-
- def instance_init(self, obj):
- for trait in self._traits:
- if isinstance(trait, TraitType):
- trait.instance_init(obj)
- super(Container, self).instance_init(obj)
-
-
- class Dict(Instance):
- """An instance of a Python dict."""
- _trait = None
-
- def __init__(self, trait=None, traits=None, default_value=Undefined,
- **kwargs):
- """Create a dict trait type from a Python dict.
-
- The default value is created by doing ``dict(default_value)``,
- which creates a copy of the ``default_value``.
-
- Parameters
- ----------
-
- trait : TraitType [ optional ]
- The specified trait type to check and use to restrict contents of
- the Container. If unspecified, trait types are not checked.
-
- traits : Dictionary of trait types [ optional ]
- A Python dictionary containing the types that are valid for
- restricting the content of the Dict Container for certain keys.
-
- default_value : SequenceType [ optional ]
- The default value for the Dict. Must be dict, tuple, or None, and
- will be cast to a dict if not None. If `trait` is specified, the
- `default_value` must conform to the constraints it specifies.
- """
- # Handling positional arguments
- if default_value is Undefined and trait is not None:
- if not is_trait(trait):
- default_value = trait
- trait = None
-
- # Handling default value
- if default_value is Undefined:
- default_value = {}
- if default_value is None:
- args = None
- 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)
-
- # Case where a type of TraitType is provided rather than an instance
- if is_trait(trait):
- if isinstance(trait, type):
- warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning, stacklevel=2)
- self._trait = trait() if isinstance(trait, type) else trait
- elif trait is not None:
- raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
-
- self._traits = traits
-
- super(Dict, self).__init__(klass=dict, args=args, **kwargs)
-
- 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)
-
- def validate(self, obj, value):
- value = super(Dict, self).validate(obj, value)
- if value is None:
- return value
- value = self.validate_elements(obj, value)
- return value
-
- def validate_elements(self, obj, value):
- use_dict = bool(self._traits)
- default_to = (self._trait or Any())
- if not use_dict and isinstance(default_to, Any):
- return value
-
- validated = {}
- for key in value:
- if use_dict and key in self._traits:
- validate_with = self._traits[key]
- else:
- validate_with = default_to
- try:
- v = value[key]
- if not isinstance(validate_with, Any):
- v = validate_with._validate(obj, v)
- except TraitError:
- self.element_error(obj, v, validate_with)
- else:
- validated[key] = v
-
- return self.klass(validated)
-
- def class_init(self, cls, name):
- if isinstance(self._trait, TraitType):
- self._trait.class_init(cls, None)
- if self._traits is not None:
- for trait in self._traits.values():
- trait.class_init(cls, None)
- super(Dict, self).class_init(cls, name)
-
- def instance_init(self, obj):
- if isinstance(self._trait, TraitType):
- self._trait.instance_init(obj)
- if self._traits is not None:
- for trait in self._traits.values():
- trait.instance_init(obj)
- super(Dict, self).instance_init(obj)
-
-
- 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:
- if isinstance(value[0], six.string_types) and isinstance(value[1], int):
- port = value[1]
- if port >= 0 and port <= 65535:
- return value
- self.error(obj, value)
-
- class CRegExp(TraitType):
- """A casting compiled regular expression trait.
-
- Accepts both strings and compiled regular expressions. The resulting
- attribute will be a compiled regular expression."""
-
- info_text = 'a regular expression'
-
- def validate(self, obj, value):
- try:
- return re.compile(value)
- except:
- self.error(obj, value)
-
-
- class UseEnum(TraitType):
- """Use a Enum class as model for the data type description.
- Note that if no default-value is provided, the first enum-value is used
- as default-value.
-
- .. sourcecode:: python
-
- # -- SINCE: Python 3.4 (or install backport: pip install enum34)
- import enum
- from traitlets import HasTraits, UseEnum
-
- class Color(enum.Enum):
- red = 1 # -- IMPLICIT: default_value
- blue = 2
- green = 3
-
- class MyEntity(HasTraits):
- color = UseEnum(Color, default_value=Color.blue)
-
- entity = MyEntity(color=Color.red)
- entity.color = Color.green # USE: Enum-value (preferred)
- entity.color = "green" # USE: name (as string)
- entity.color = "Color.green" # USE: scoped-name (as string)
- entity.color = 3 # USE: number (as int)
- assert entity.color is Color.green
- """
- default_value = None
- info_text = "Trait type adapter to a Enum class"
-
- def __init__(self, enum_class, default_value=None, **kwargs):
- assert issubclass(enum_class, enum.Enum), \
- "REQUIRE: enum.Enum, but was: %r" % enum_class
- allow_none = kwargs.get("allow_none", False)
- if default_value is None and not allow_none:
- default_value = list(enum_class.__members__.values())[0]
- super(UseEnum, self).__init__(default_value=default_value, **kwargs)
- self.enum_class = enum_class
- self.name_prefix = enum_class.__name__ + "."
-
- def select_by_number(self, value, default=Undefined):
- """Selects enum-value by using its number-constant."""
- assert isinstance(value, int)
- enum_members = self.enum_class.__members__
- for enum_item in enum_members.values():
- if enum_item.value == value:
- return enum_item
- # -- NOT FOUND:
- return default
-
- def select_by_name(self, value, default=Undefined):
- """Selects enum-value by using its name or scoped-name."""
- assert isinstance(value, six.string_types)
- if value.startswith(self.name_prefix):
- # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red"
- value = value.replace(self.name_prefix, "", 1)
- return self.enum_class.__members__.get(value, default)
-
- def validate(self, obj, value):
- if isinstance(value, self.enum_class):
- return value
- elif isinstance(value, int):
- # -- CONVERT: number => enum_value (item)
- value2 = self.select_by_number(value)
- if value2 is not Undefined:
- return value2
- elif isinstance(value, six.string_types):
- # -- CONVERT: name or scoped_name (as string) => enum_value (item)
- value2 = self.select_by_name(value)
- if value2 is not Undefined:
- return value2
- elif value is None:
- if self.allow_none:
- return None
- else:
- return self.default_value
- self.error(obj, value)
-
- def info(self):
- """Returns a description of this Enum trait (in case of errors)."""
- result = "Any of: %s" % ", ".join(self.enum_class.__members__.keys())
- if self.allow_none:
- return result + " or None"
- return result
|