aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/traitlets/py2
diff options
context:
space:
mode:
authornkozlovskiy <nmk@ydb.tech>2023-09-29 12:24:06 +0300
committernkozlovskiy <nmk@ydb.tech>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/traitlets/py2
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
downloadydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz
add ydb deps
Diffstat (limited to 'contrib/python/traitlets/py2')
-rw-r--r--contrib/python/traitlets/py2/.dist-info/METADATA34
-rw-r--r--contrib/python/traitlets/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/traitlets/py2/COPYING.md62
-rw-r--r--contrib/python/traitlets/py2/README.md143
-rw-r--r--contrib/python/traitlets/py2/tests/ya.make23
-rw-r--r--contrib/python/traitlets/py2/traitlets/__init__.py3
-rw-r--r--contrib/python/traitlets/py2/traitlets/_version.py2
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/__init__.py8
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/application.py711
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/configurable.py432
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/loader.py857
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/manager.py88
-rw-r--r--contrib/python/traitlets/py2/traitlets/log.py27
-rw-r--r--contrib/python/traitlets/py2/traitlets/tests/__init__.py0
-rw-r--r--contrib/python/traitlets/py2/traitlets/tests/_warnings.py107
-rw-r--r--contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py2419
-rw-r--r--contrib/python/traitlets/py2/traitlets/tests/test_traitlets_enum.py181
-rw-r--r--contrib/python/traitlets/py2/traitlets/tests/utils.py39
-rw-r--r--contrib/python/traitlets/py2/traitlets/traitlets.py2690
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/__init__.py0
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/bunch.py25
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/getargspec.py86
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/importstring.py42
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/sentinel.py17
-rw-r--r--contrib/python/traitlets/py2/ya.make48
25 files changed, 8045 insertions, 0 deletions
diff --git a/contrib/python/traitlets/py2/.dist-info/METADATA b/contrib/python/traitlets/py2/.dist-info/METADATA
new file mode 100644
index 0000000000..bd031a5c30
--- /dev/null
+++ b/contrib/python/traitlets/py2/.dist-info/METADATA
@@ -0,0 +1,34 @@
+Metadata-Version: 2.1
+Name: traitlets
+Version: 4.3.3
+Summary: Traitlets Python config system
+Home-page: http://ipython.org
+Author: IPython Development Team
+Author-email: ipython-dev@scipy.org
+License: BSD
+Keywords: Interactive,Interpreter,Shell,Web
+Platform: Linux
+Platform: Mac OS X
+Platform: Windows
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Requires-Dist: ipython-genutils
+Requires-Dist: six
+Requires-Dist: decorator
+Requires-Dist: enum34 ; python_version=="2.7"
+Requires-Dist: enum34 ; python_version=="3.3"
+Provides-Extra: test
+Requires-Dist: pytest ; extra == 'test'
+Requires-Dist: mock ; (python_version=="2.7") and extra == 'test'
+
+A configuration system for Python applications.
+
+
diff --git a/contrib/python/traitlets/py2/.dist-info/top_level.txt b/contrib/python/traitlets/py2/.dist-info/top_level.txt
new file mode 100644
index 0000000000..adfea9c6eb
--- /dev/null
+++ b/contrib/python/traitlets/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+traitlets
diff --git a/contrib/python/traitlets/py2/COPYING.md b/contrib/python/traitlets/py2/COPYING.md
new file mode 100644
index 0000000000..39ca730a63
--- /dev/null
+++ b/contrib/python/traitlets/py2/COPYING.md
@@ -0,0 +1,62 @@
+# Licensing terms
+
+Traitlets is adapted from enthought.traits, Copyright (c) Enthought, Inc.,
+under the terms of the Modified BSD License.
+
+This project is licensed under the terms of the Modified BSD License
+(also known as New or Revised or 3-Clause BSD), as follows:
+
+- Copyright (c) 2001-, IPython Development Team
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the IPython Development Team nor the names of its
+contributors may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## About the IPython Development Team
+
+The IPython Development Team is the set of all contributors to the IPython project.
+This includes all of the IPython subprojects.
+
+The core team that coordinates development on GitHub can be found here:
+https://github.com/jupyter/.
+
+## Our Copyright Policy
+
+IPython uses a shared copyright model. Each contributor maintains copyright
+over their contributions to IPython. But, it is important to note that these
+contributions are typically only changes to the repositories. Thus, the IPython
+source code, in its entirety is not the copyright of any single person or
+institution. Instead, it is the collective copyright of the entire IPython
+Development Team. If individual contributors want to maintain a record of what
+changes/contributions they have specific copyright on, they should indicate
+their copyright in the commit message of the change, when they commit the
+change to one of the IPython repositories.
+
+With this in mind, the following banner should be used in any source code file
+to indicate the copyright and license terms:
+
+ # Copyright (c) IPython Development Team.
+ # Distributed under the terms of the Modified BSD License.
diff --git a/contrib/python/traitlets/py2/README.md b/contrib/python/traitlets/py2/README.md
new file mode 100644
index 0000000000..aa288947ef
--- /dev/null
+++ b/contrib/python/traitlets/py2/README.md
@@ -0,0 +1,143 @@
+# Traitlets
+
+[![Build Status](https://travis-ci.org/ipython/traitlets.svg?branch=master)](https://travis-ci.org/ipython/traitlets)
+[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](http://traitlets.readthedocs.org/en/latest/?badge=latest)
+
+Traitlets is a pure Python library enabling:
+
+ - the enforcement of strong typing for attributes of Python objects
+ (typed attributes are called "traits"),
+ - notifications on changes of trait attributes,
+ - automatic validation and coercion of trait attributes when attempting a
+ change.
+
+Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html)
+pattern.
+
+Traitlets powers the configuration system of IPython and Jupyter
+and the declarative API of IPython interactive widgets.
+
+## Installation
+
+For a local installation, make sure you have
+[pip installed](https://pip.pypa.io/en/stable/installing/) and run:
+
+```bash
+pip install traitlets
+```
+
+For a **development installation**, clone this repository, change into the
+`traitlets` root directory, and run pip:
+
+```bash
+git clone https://github.com/ipython/traitlets.git
+cd traitlets
+pip install -e .
+```
+
+## Running the tests
+
+```bash
+pip install "traitlets[test]"
+py.test traitlets
+```
+
+## Usage
+
+Any class with trait attributes must inherit from `HasTraits`.
+For the list of available trait types and their properties, see the
+[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html)
+section of the documentation.
+
+### Dynamic default values
+
+To calculate a default value dynamically, decorate a method of your class with
+`@default({traitname})`. This method will be called on the instance, and
+should return the default value. In this example, the `_username_default`
+method is decorated with `@default('username')`:
+
+```Python
+import getpass
+from traitlets import HasTraits, Unicode, default
+
+class Identity(HasTraits):
+ username = Unicode()
+
+ @default('username')
+ def _username_default(self):
+ return getpass.getuser()
+```
+
+### Callbacks when a trait attribute changes
+
+When a trait changes, an application can follow this trait change with
+additional actions.
+
+To do something when a trait attribute is changed, decorate a method with
+[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe).
+The method will be called with a single argument, a dictionary which contains
+an owner, new value, old value, name of the changed trait, and the event type.
+
+In this example, the `_num_changed` method is decorated with ``@observe(`num`)``:
+
+```Python
+from traitlets import HasTraits, Integer, observe
+
+class TraitletsExample(HasTraits):
+ num = Integer(5, help="a number").tag(config=True)
+
+ @observe('num')
+ def _num_changed(self, change):
+ print("{name} changed from {old} to {new}".format(**change))
+```
+
+and is passed the following dictionary when called:
+
+```Python
+{
+ 'owner': object, # The HasTraits instance
+ 'new': 6, # The new value
+ 'old': 5, # The old value
+ 'name': "foo", # The name of the changed trait
+ 'type': 'change', # The event type of the notification, usually 'change'
+}
+```
+
+### Validation and coercion
+
+Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or
+coercion logic. In addition, we can register custom cross-validators
+that may depend on the state of other attributes. For example:
+
+```Python
+from traitlets import HasTraits, TraitError, Int, Bool, validate
+
+class Parity(HasTraits):
+ value = Int()
+ parity = Int()
+
+ @validate('value')
+ def _valid_value(self, proposal):
+ if proposal['value'] % 2 != self.parity:
+ raise TraitError('value and parity should be consistent')
+ return proposal['value']
+
+ @validate('parity')
+ def _valid_parity(self, proposal):
+ parity = proposal['value']
+ if parity not in [0, 1]:
+ raise TraitError('parity should be 0 or 1')
+ if self.value % 2 != parity:
+ raise TraitError('value and parity should be consistent')
+ return proposal['value']
+
+parity_check = Parity(value=2)
+
+# Changing required parity and value together while holding cross validation
+with parity_check.hold_trait_notifications():
+ parity_check.value = 1
+ parity_check.parity = 1
+```
+
+However, we **recommend** that custom cross-validators don't modify the state
+of the HasTraits instance.
diff --git a/contrib/python/traitlets/py2/tests/ya.make b/contrib/python/traitlets/py2/tests/ya.make
new file mode 100644
index 0000000000..adcfe34ac8
--- /dev/null
+++ b/contrib/python/traitlets/py2/tests/ya.make
@@ -0,0 +1,23 @@
+PY2TEST()
+
+PEERDIR(
+ contrib/python/traitlets
+)
+
+ENV(
+ YA_PYTEST_DISABLE_DOCTEST=yes
+)
+
+SRCDIR(contrib/python/traitlets/py2/traitlets)
+
+TEST_SRCS(
+ tests/__init__.py
+ tests/_warnings.py
+ tests/test_traitlets.py
+ tests/test_traitlets_enum.py
+ tests/utils.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/traitlets/py2/traitlets/__init__.py b/contrib/python/traitlets/py2/traitlets/__init__.py
new file mode 100644
index 0000000000..b609adb565
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/__init__.py
@@ -0,0 +1,3 @@
+from .traitlets import *
+from .utils.importstring import import_item
+from ._version import version_info, __version__
diff --git a/contrib/python/traitlets/py2/traitlets/_version.py b/contrib/python/traitlets/py2/traitlets/_version.py
new file mode 100644
index 0000000000..ed16b3c1e1
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/_version.py
@@ -0,0 +1,2 @@
+version_info = (4, 3, 3)
+__version__ = '.'.join(map(str, version_info))
diff --git a/contrib/python/traitlets/py2/traitlets/config/__init__.py b/contrib/python/traitlets/py2/traitlets/config/__init__.py
new file mode 100644
index 0000000000..0ae7d63171
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/__init__.py
@@ -0,0 +1,8 @@
+# encoding: utf-8
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from .application import *
+from .configurable import *
+from .loader import Config
diff --git a/contrib/python/traitlets/py2/traitlets/config/application.py b/contrib/python/traitlets/py2/traitlets/config/application.py
new file mode 100644
index 0000000000..d3a4c45e77
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/application.py
@@ -0,0 +1,711 @@
+# encoding: utf-8
+"""A base class for a configurable application."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from __future__ import print_function
+
+from copy import deepcopy
+import json
+import logging
+import os
+import re
+import sys
+from collections import defaultdict, OrderedDict
+
+from decorator import decorator
+
+from traitlets.config.configurable import Configurable, SingletonConfigurable
+from traitlets.config.loader import (
+ KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
+)
+
+from traitlets.traitlets import (
+ Bool, Unicode, List, Enum, Dict, Instance, TraitError, observe, observe_compat, default,
+)
+from ipython_genutils.importstring import import_item
+from ipython_genutils.text import indent, wrap_paragraphs, dedent
+from ipython_genutils import py3compat
+
+import six
+
+#-----------------------------------------------------------------------------
+# Descriptions for the various sections
+#-----------------------------------------------------------------------------
+
+# merge flags&aliases into options
+option_description = """
+Arguments that take values are actually convenience aliases to full
+Configurables, whose aliases are listed on the help line. For more information
+on full configurables, see '--help-all'.
+""".strip() # trim newlines of front and back
+
+keyvalue_description = """
+Parameters are set from command-line arguments of the form:
+`--Class.trait=value`.
+This line is evaluated in Python, so simple expressions are allowed, e.g.::
+`--C.a='range(3)'` For setting C.a=[0,1,2].
+""".strip() # trim newlines of front and back
+
+# sys.argv can be missing, for example when python is embedded. See the docs
+# for details: http://docs.python.org/2/c-api/intro.html#embedding-python
+if not hasattr(sys, "argv"):
+ sys.argv = [""]
+
+subcommand_description = """
+Subcommands are launched as `{app} cmd [args]`. For information on using
+subcommand 'cmd', do: `{app} cmd -h`.
+"""
+# get running program name
+
+#-----------------------------------------------------------------------------
+# Application class
+#-----------------------------------------------------------------------------
+
+
+
+_envvar = os.environ.get('TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR','')
+if _envvar.lower() in {'1','true'}:
+ TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR = True
+elif _envvar.lower() in {'0','false',''} :
+ TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR = False
+else:
+ raise ValueError("Unsupported value for environment variable: 'TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
+
+
+@decorator
+def catch_config_error(method, app, *args, **kwargs):
+ """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
+
+ On a TraitError (generally caused by bad config), this will print the trait's
+ message, and exit the app.
+
+ For use on init methods, to prevent invoking excepthook on invalid input.
+ """
+ try:
+ return method(app, *args, **kwargs)
+ except (TraitError, ArgumentError) as e:
+ app.print_help()
+ app.log.fatal("Bad config encountered during initialization:")
+ app.log.fatal(str(e))
+ app.log.debug("Config at the time: %s", app.config)
+ app.exit(1)
+
+
+class ApplicationError(Exception):
+ pass
+
+
+class LevelFormatter(logging.Formatter):
+ """Formatter with additional `highlevel` record
+
+ This field is empty if log level is less than highlevel_limit,
+ otherwise it is formatted with self.highlevel_format.
+
+ Useful for adding 'WARNING' to warning messages,
+ without adding 'INFO' to info, etc.
+ """
+ highlevel_limit = logging.WARN
+ highlevel_format = " %(levelname)s |"
+
+ def format(self, record):
+ if record.levelno >= self.highlevel_limit:
+ record.highlevel = self.highlevel_format % record.__dict__
+ else:
+ record.highlevel = ""
+ return super(LevelFormatter, self).format(record)
+
+
+class Application(SingletonConfigurable):
+ """A singleton application with full configuration support."""
+
+ # The name of the application, will usually match the name of the command
+ # line application
+ name = Unicode(u'application')
+
+ # The description of the application that is printed at the beginning
+ # of the help.
+ description = Unicode(u'This is an application.')
+ # default section descriptions
+ option_description = Unicode(option_description)
+ keyvalue_description = Unicode(keyvalue_description)
+ subcommand_description = Unicode(subcommand_description)
+
+ python_config_loader_class = PyFileConfigLoader
+ json_config_loader_class = JSONFileConfigLoader
+
+ # The usage and example string that goes at the end of the help string.
+ examples = Unicode()
+
+ # A sequence of Configurable subclasses whose config=True attributes will
+ # be exposed at the command line.
+ classes = []
+
+ def _classes_inc_parents(self):
+ """Iterate through configurable classes, including configurable parents
+
+ Children should always be after parents, and each class should only be
+ yielded once.
+ """
+ seen = set()
+ for c in self.classes:
+ # We want to sort parents before children, so we reverse the MRO
+ for parent in reversed(c.mro()):
+ if issubclass(parent, Configurable) and (parent not in seen):
+ seen.add(parent)
+ yield parent
+
+ # The version string of this application.
+ version = Unicode(u'0.0')
+
+ # the argv used to initialize the application
+ argv = List()
+
+ # Whether failing to load config files should prevent startup
+ raise_config_file_errors = Bool(TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR)
+
+ # The log level for the application
+ log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
+ default_value=logging.WARN,
+ help="Set the log level by value or name.").tag(config=True)
+
+ @observe('log_level')
+ @observe_compat
+ def _log_level_changed(self, change):
+ """Adjust the log level when log_level is set."""
+ new = change.new
+ if isinstance(new, six.string_types):
+ new = getattr(logging, new)
+ self.log_level = new
+ self.log.setLevel(new)
+
+ _log_formatter_cls = LevelFormatter
+
+ log_datefmt = Unicode("%Y-%m-%d %H:%M:%S",
+ help="The date format used by logging formatters for %(asctime)s"
+ ).tag(config=True)
+
+ log_format = Unicode("[%(name)s]%(highlevel)s %(message)s",
+ help="The Logging format template",
+ ).tag(config=True)
+
+ @observe('log_datefmt', 'log_format')
+ @observe_compat
+ def _log_format_changed(self, change):
+ """Change the log formatter when log_format is set."""
+ _log_handler = self.log.handlers[0]
+ _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
+ _log_handler.setFormatter(_log_formatter)
+
+ @default('log')
+ def _log_default(self):
+ """Start logging for this application.
+
+ The default is to log to stderr using a StreamHandler, if no default
+ handler already exists. The log level starts at logging.WARN, but this
+ can be adjusted by setting the ``log_level`` attribute.
+ """
+ log = logging.getLogger(self.__class__.__name__)
+ log.setLevel(self.log_level)
+ log.propagate = False
+ _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
+ while _log:
+ if _log.handlers:
+ return log
+ if not _log.propagate:
+ break
+ else:
+ _log = _log.parent
+ if sys.executable and sys.executable.endswith('pythonw.exe'):
+ # this should really go to a file, but file-logging is only
+ # hooked up in parallel applications
+ _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
+ else:
+ _log_handler = logging.StreamHandler()
+ _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
+ _log_handler.setFormatter(_log_formatter)
+ log.addHandler(_log_handler)
+ return log
+
+ # the alias map for configurables
+ aliases = Dict({'log-level' : 'Application.log_level'})
+
+ # flags for loading Configurables or store_const style flags
+ # flags are loaded from this dict by '--key' flags
+ # this must be a dict of two-tuples, the first element being the Config/dict
+ # and the second being the help string for the flag
+ flags = Dict()
+ @observe('flags')
+ @observe_compat
+ def _flags_changed(self, change):
+ """ensure flags dict is valid"""
+ new = change.new
+ for key, value in new.items():
+ assert len(value) == 2, "Bad flag: %r:%s" % (key, value)
+ assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s" % (key, value)
+ assert isinstance(value[1], six.string_types), "Bad flag: %r:%s" % (key, value)
+
+
+ # subcommands for launching other applications
+ # if this is not empty, this will be a parent Application
+ # this must be a dict of two-tuples,
+ # the first element being the application class/import string
+ # and the second being the help string for the subcommand
+ subcommands = Dict()
+ # parse_command_line will initialize a subapp, if requested
+ subapp = Instance('traitlets.config.application.Application', allow_none=True)
+
+ # extra command-line arguments that don't set config values
+ extra_args = List(Unicode())
+
+ cli_config = Instance(Config, (), {},
+ help="""The subset of our configuration that came from the command-line
+
+ We re-load this configuration after loading config files,
+ to ensure that it maintains highest priority.
+ """
+ )
+
+ _loaded_config_files = List()
+
+ def __init__(self, **kwargs):
+ SingletonConfigurable.__init__(self, **kwargs)
+ # Ensure my class is in self.classes, so my attributes appear in command line
+ # options and config files.
+ cls = self.__class__
+ if cls not in self.classes:
+ if self.classes is cls.classes:
+ # class attr, assign instead of insert
+ cls.classes = [cls] + self.classes
+ else:
+ self.classes.insert(0, self.__class__)
+
+ @observe('config')
+ @observe_compat
+ def _config_changed(self, change):
+ super(Application, self)._config_changed(change)
+ self.log.debug('Config changed:')
+ self.log.debug(repr(change.new))
+
+ @catch_config_error
+ def initialize(self, argv=None):
+ """Do the basic steps to configure me.
+
+ Override in subclasses.
+ """
+ self.parse_command_line(argv)
+
+
+ def start(self):
+ """Start the app mainloop.
+
+ Override in subclasses.
+ """
+ if self.subapp is not None:
+ return self.subapp.start()
+
+ def print_alias_help(self):
+ """Print the alias part of the help."""
+ if not self.aliases:
+ return
+
+ lines = []
+ classdict = {}
+ for cls in self.classes:
+ # include all parents (up to, but excluding Configurable) in available names
+ for c in cls.mro()[:-3]:
+ classdict[c.__name__] = c
+
+ for alias, longname in self.aliases.items():
+ classname, traitname = longname.split('.',1)
+ cls = classdict[classname]
+
+ trait = cls.class_traits(config=True)[traitname]
+ help = cls.class_get_trait_help(trait).splitlines()
+ # reformat first line
+ help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
+ if len(alias) == 1:
+ help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
+ lines.extend(help)
+ # lines.append('')
+ print(os.linesep.join(lines))
+
+ def print_flag_help(self):
+ """Print the flag part of the help."""
+ if not self.flags:
+ return
+
+ lines = []
+ for m, (cfg,help) in self.flags.items():
+ prefix = '--' if len(m) > 1 else '-'
+ lines.append(prefix+m)
+ lines.append(indent(dedent(help.strip())))
+ # lines.append('')
+ print(os.linesep.join(lines))
+
+ def print_options(self):
+ if not self.flags and not self.aliases:
+ return
+ lines = ['Options']
+ lines.append('-'*len(lines[0]))
+ lines.append('')
+ for p in wrap_paragraphs(self.option_description):
+ lines.append(p)
+ lines.append('')
+ print(os.linesep.join(lines))
+ self.print_flag_help()
+ self.print_alias_help()
+ print()
+
+ def print_subcommands(self):
+ """Print the subcommand part of the help."""
+ if not self.subcommands:
+ return
+
+ lines = ["Subcommands"]
+ lines.append('-'*len(lines[0]))
+ lines.append('')
+ for p in wrap_paragraphs(self.subcommand_description.format(
+ app=self.name)):
+ lines.append(p)
+ lines.append('')
+ for subc, (cls, help) in self.subcommands.items():
+ lines.append(subc)
+ if help:
+ lines.append(indent(dedent(help.strip())))
+ lines.append('')
+ print(os.linesep.join(lines))
+
+ def print_help(self, classes=False):
+ """Print the help for each Configurable class in self.classes.
+
+ If classes=False (the default), only flags and aliases are printed.
+ """
+ self.print_description()
+ self.print_subcommands()
+ self.print_options()
+
+ if classes:
+ help_classes = self.classes
+ if help_classes:
+ print("Class parameters")
+ print("----------------")
+ print()
+ for p in wrap_paragraphs(self.keyvalue_description):
+ print(p)
+ print()
+
+ for cls in help_classes:
+ cls.class_print_help()
+ print()
+ else:
+ print("To see all available configurables, use `--help-all`")
+ print()
+
+ self.print_examples()
+
+ def document_config_options(self):
+ """Generate rST format documentation for the config options this application
+
+ Returns a multiline string.
+ """
+ return '\n'.join(c.class_config_rst_doc()
+ for c in self._classes_inc_parents())
+
+
+ def print_description(self):
+ """Print the application description."""
+ for p in wrap_paragraphs(self.description):
+ print(p)
+ print()
+
+ def print_examples(self):
+ """Print usage and examples.
+
+ This usage string goes at the end of the command line help string
+ and should contain examples of the application's usage.
+ """
+ if self.examples:
+ print("Examples")
+ print("--------")
+ print()
+ print(indent(dedent(self.examples.strip())))
+ print()
+
+ def print_version(self):
+ """Print the version string."""
+ print(self.version)
+
+ @catch_config_error
+ def initialize_subcommand(self, subc, argv=None):
+ """Initialize a subcommand with argv."""
+ subapp,help = self.subcommands.get(subc)
+
+ if isinstance(subapp, six.string_types):
+ subapp = import_item(subapp)
+
+ # clear existing instances
+ self.__class__.clear_instance()
+ # instantiate
+ self.subapp = subapp.instance(parent=self)
+ # and initialize subapp
+ self.subapp.initialize(argv)
+
+ def flatten_flags(self):
+ """flatten flags and aliases, so cl-args override as expected.
+
+ This prevents issues such as an alias pointing to InteractiveShell,
+ but a config file setting the same trait in TerminalInteraciveShell
+ getting inappropriate priority over the command-line arg.
+
+ Only aliases with exactly one descendent in the class list
+ will be promoted.
+
+ """
+ # build a tree of classes in our list that inherit from a particular
+ # it will be a dict by parent classname of classes in our list
+ # that are descendents
+ mro_tree = defaultdict(list)
+ for cls in self.classes:
+ clsname = cls.__name__
+ for parent in cls.mro()[1:-3]:
+ # exclude cls itself and Configurable,HasTraits,object
+ mro_tree[parent.__name__].append(clsname)
+ # flatten aliases, which have the form:
+ # { 'alias' : 'Class.trait' }
+ aliases = {}
+ for alias, cls_trait in self.aliases.items():
+ cls,trait = cls_trait.split('.',1)
+ children = mro_tree[cls]
+ if len(children) == 1:
+ # exactly one descendent, promote alias
+ cls = children[0]
+ aliases[alias] = '.'.join([cls,trait])
+
+ # flatten flags, which are of the form:
+ # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
+ flags = {}
+ for key, (flagdict, help) in self.flags.items():
+ newflag = {}
+ for cls, subdict in flagdict.items():
+ children = mro_tree[cls]
+ # exactly one descendent, promote flag section
+ if len(children) == 1:
+ cls = children[0]
+ newflag[cls] = subdict
+ flags[key] = (newflag, help)
+ return flags, aliases
+
+ @catch_config_error
+ def parse_command_line(self, argv=None):
+ """Parse the command line arguments."""
+ argv = sys.argv[1:] if argv is None else argv
+ self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
+
+ if argv and argv[0] == 'help':
+ # turn `ipython help notebook` into `ipython notebook -h`
+ argv = argv[1:] + ['-h']
+
+ if self.subcommands and len(argv) > 0:
+ # we have subcommands, and one may have been specified
+ subc, subargv = argv[0], argv[1:]
+ if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
+ # it's a subcommand, and *not* a flag or class parameter
+ return self.initialize_subcommand(subc, subargv)
+
+ # Arguments after a '--' argument are for the script IPython may be
+ # about to run, not IPython iteslf. For arguments parsed here (help and
+ # version), we want to only search the arguments up to the first
+ # occurrence of '--', which we're calling interpreted_argv.
+ try:
+ interpreted_argv = argv[:argv.index('--')]
+ except ValueError:
+ interpreted_argv = argv
+
+ if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
+ self.print_help('--help-all' in interpreted_argv)
+ self.exit(0)
+
+ if '--version' in interpreted_argv or '-V' in interpreted_argv:
+ self.print_version()
+ self.exit(0)
+
+ # flatten flags&aliases, so cl-args get appropriate priority:
+ flags,aliases = self.flatten_flags()
+ loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
+ flags=flags, log=self.log)
+ self.cli_config = deepcopy(loader.load_config())
+ self.update_config(self.cli_config)
+ # store unparsed args in extra_args
+ self.extra_args = loader.extra_args
+
+ @classmethod
+ def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file_errors=False):
+ """Load config files (py,json) by filename and path.
+
+ yield each config object in turn.
+ """
+
+ if not isinstance(path, list):
+ path = [path]
+ for path in path[::-1]:
+ # path list is in descending priority order, so load files backwards:
+ pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
+ if log:
+ log.debug("Looking for %s in %s", basefilename, path or os.getcwd())
+ jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
+ loaded = []
+ filenames = []
+ for loader in [pyloader, jsonloader]:
+ config = None
+ try:
+ config = loader.load_config()
+ except ConfigFileNotFound:
+ pass
+ except Exception:
+ # try to get the full filename, but it will be empty in the
+ # unlikely event that the error raised before filefind finished
+ filename = loader.full_filename or basefilename
+ # problem while running the file
+ if raise_config_file_errors:
+ raise
+ if log:
+ log.error("Exception while loading config file %s",
+ filename, exc_info=True)
+ else:
+ if log:
+ log.debug("Loaded config file: %s", loader.full_filename)
+ if config:
+ for filename, earlier_config in zip(filenames, loaded):
+ collisions = earlier_config.collisions(config)
+ if collisions and log:
+ log.warning("Collisions detected in {0} and {1} config files."
+ " {1} has higher priority: {2}".format(
+ filename, loader.full_filename, json.dumps(collisions, indent=2),
+ ))
+ yield (config, loader.full_filename)
+ loaded.append(config)
+ filenames.append(loader.full_filename)
+
+ @property
+ def loaded_config_files(self):
+ """Currently loaded configuration files"""
+ return self._loaded_config_files[:]
+
+ @catch_config_error
+ def load_config_file(self, filename, path=None):
+ """Load config files by filename and path."""
+ filename, ext = os.path.splitext(filename)
+ new_config = Config()
+ for (config, filename) in self._load_config_files(filename, path=path, log=self.log,
+ raise_config_file_errors=self.raise_config_file_errors,
+ ):
+ new_config.merge(config)
+ if filename not in self._loaded_config_files: # only add to list of loaded files if not previously loaded
+ self._loaded_config_files.append(filename)
+ # add self.cli_config to preserve CLI config priority
+ new_config.merge(self.cli_config)
+ self.update_config(new_config)
+
+
+ def _classes_in_config_sample(self):
+ """
+ Yields only classes with own traits, and their subclasses.
+
+ Thus, produced sample config-file will contain all classes
+ on which a trait-value may be overridden:
+
+ - either on the class owning the trait,
+ - or on its subclasses, even if those subclasses do not define
+ any traits themselves.
+ """
+ cls_to_config = OrderedDict( (cls, bool(cls.class_own_traits(config=True)))
+ for cls
+ in self._classes_inc_parents())
+
+ def is_any_parent_included(cls):
+ return any(b in cls_to_config and cls_to_config[b] for b in cls.__bases__)
+
+ ## Mark "empty" classes for inclusion if their parents own-traits,
+ # and loop until no more classes gets marked.
+ #
+ while True:
+ to_incl_orig = cls_to_config.copy()
+ cls_to_config = OrderedDict( (cls, inc_yes or is_any_parent_included(cls))
+ for cls, inc_yes
+ in cls_to_config.items())
+ if cls_to_config == to_incl_orig:
+ break
+ for cl, inc_yes in cls_to_config.items():
+ if inc_yes:
+ yield cl
+
+ def generate_config_file(self):
+ """generate default config file from Configurables"""
+ lines = ["# Configuration file for %s." % self.name]
+ lines.append('')
+ for cls in self._classes_in_config_sample():
+ lines.append(cls.class_config_section())
+ return '\n'.join(lines)
+
+ def exit(self, exit_status=0):
+ self.log.debug("Exiting application: %s" % self.name)
+ sys.exit(exit_status)
+
+ @classmethod
+ def launch_instance(cls, argv=None, **kwargs):
+ """Launch a global instance of this Application
+
+ If a global instance already exists, this reinitializes and starts it
+ """
+ app = cls.instance(**kwargs)
+ app.initialize(argv)
+ app.start()
+
+#-----------------------------------------------------------------------------
+# utility functions, for convenience
+#-----------------------------------------------------------------------------
+
+def boolean_flag(name, configurable, set_help='', unset_help=''):
+ """Helper for building basic --trait, --no-trait flags.
+
+ Parameters
+ ----------
+
+ name : str
+ The name of the flag.
+ configurable : str
+ The 'Class.trait' string of the trait to be set/unset with the flag
+ set_help : unicode
+ help string for --name flag
+ unset_help : unicode
+ help string for --no-name flag
+
+ Returns
+ -------
+
+ cfg : dict
+ A dict with two keys: 'name', and 'no-name', for setting and unsetting
+ the trait, respectively.
+ """
+ # default helpstrings
+ set_help = set_help or "set %s=True"%configurable
+ unset_help = unset_help or "set %s=False"%configurable
+
+ cls,trait = configurable.split('.')
+
+ setter = {cls : {trait : True}}
+ unsetter = {cls : {trait : False}}
+ return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
+
+
+def get_config():
+ """Get the config object for the global Application instance, if there is one
+
+ otherwise return an empty config object
+ """
+ if Application.initialized():
+ return Application.instance().config
+ else:
+ return Config()
diff --git a/contrib/python/traitlets/py2/traitlets/config/configurable.py b/contrib/python/traitlets/py2/traitlets/config/configurable.py
new file mode 100644
index 0000000000..1174fcf017
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/configurable.py
@@ -0,0 +1,432 @@
+# encoding: utf-8
+"""A base class for objects that are configurable."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from __future__ import print_function, absolute_import
+
+from copy import deepcopy
+import warnings
+
+from .loader import Config, LazyConfigValue, _is_section_key
+from traitlets.traitlets import HasTraits, Instance, observe, observe_compat, default
+from ipython_genutils.text import indent, dedent, wrap_paragraphs
+
+
+#-----------------------------------------------------------------------------
+# Helper classes for Configurables
+#-----------------------------------------------------------------------------
+
+
+class ConfigurableError(Exception):
+ pass
+
+
+class MultipleInstanceError(ConfigurableError):
+ pass
+
+#-----------------------------------------------------------------------------
+# Configurable implementation
+#-----------------------------------------------------------------------------
+
+class Configurable(HasTraits):
+
+ config = Instance(Config, (), {})
+ parent = Instance('traitlets.config.configurable.Configurable', allow_none=True)
+
+ def __init__(self, **kwargs):
+ """Create a configurable given a config config.
+
+ Parameters
+ ----------
+ config : Config
+ If this is empty, default values are used. If config is a
+ :class:`Config` instance, it will be used to configure the
+ instance.
+ parent : Configurable instance, optional
+ The parent Configurable instance of this object.
+
+ Notes
+ -----
+ Subclasses of Configurable must call the :meth:`__init__` method of
+ :class:`Configurable` *before* doing anything else and using
+ :func:`super`::
+
+ class MyConfigurable(Configurable):
+ def __init__(self, config=None):
+ super(MyConfigurable, self).__init__(config=config)
+ # Then any other code you need to finish initialization.
+
+ This ensures that instances will be configured properly.
+ """
+ parent = kwargs.pop('parent', None)
+ if parent is not None:
+ # config is implied from parent
+ if kwargs.get('config', None) is None:
+ kwargs['config'] = parent.config
+ self.parent = parent
+
+ config = kwargs.pop('config', None)
+
+ # load kwarg traits, other than config
+ super(Configurable, self).__init__(**kwargs)
+
+ # load config
+ if config is not None:
+ # We used to deepcopy, but for now we are trying to just save
+ # by reference. This *could* have side effects as all components
+ # will share config. In fact, I did find such a side effect in
+ # _config_changed below. If a config attribute value was a mutable type
+ # all instances of a component were getting the same copy, effectively
+ # making that a class attribute.
+ # self.config = deepcopy(config)
+ self.config = config
+ else:
+ # allow _config_default to return something
+ self._load_config(self.config)
+
+ # Ensure explicit kwargs are applied after loading config.
+ # This is usually redundant, but ensures config doesn't override
+ # explicitly assigned values.
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+ #-------------------------------------------------------------------------
+ # Static trait notifiations
+ #-------------------------------------------------------------------------
+
+ @classmethod
+ def section_names(cls):
+ """return section names as a list"""
+ return [c.__name__ for c in reversed(cls.__mro__) if
+ issubclass(c, Configurable) and issubclass(cls, c)
+ ]
+
+ def _find_my_config(self, cfg):
+ """extract my config from a global Config object
+
+ will construct a Config object of only the config values that apply to me
+ based on my mro(), as well as those of my parent(s) if they exist.
+
+ If I am Bar and my parent is Foo, and their parent is Tim,
+ this will return merge following config sections, in this order::
+
+ [Bar, Foo.bar, Tim.Foo.Bar]
+
+ With the last item being the highest priority.
+ """
+ cfgs = [cfg]
+ if self.parent:
+ cfgs.append(self.parent._find_my_config(cfg))
+ my_config = Config()
+ for c in cfgs:
+ for sname in self.section_names():
+ # Don't do a blind getattr as that would cause the config to
+ # dynamically create the section with name Class.__name__.
+ if c._has_section(sname):
+ my_config.merge(c[sname])
+ return my_config
+
+ def _load_config(self, cfg, section_names=None, traits=None):
+ """load traits from a Config object"""
+
+ if traits is None:
+ traits = self.traits(config=True)
+ if section_names is None:
+ section_names = self.section_names()
+
+ my_config = self._find_my_config(cfg)
+
+ # hold trait notifications until after all config has been loaded
+ with self.hold_trait_notifications():
+ for name, config_value in my_config.items():
+ if name in traits:
+ if isinstance(config_value, LazyConfigValue):
+ # ConfigValue is a wrapper for using append / update on containers
+ # without having to copy the initial value
+ initial = getattr(self, name)
+ config_value = config_value.get_value(initial)
+ # We have to do a deepcopy here if we don't deepcopy the entire
+ # config object. If we don't, a mutable config_value will be
+ # shared by all instances, effectively making it a class attribute.
+ setattr(self, name, deepcopy(config_value))
+ elif not _is_section_key(name) and not isinstance(config_value, Config):
+ from difflib import get_close_matches
+ if isinstance(self, LoggingConfigurable):
+ warn = self.log.warning
+ else:
+ warn = lambda msg: warnings.warn(msg, stacklevel=9)
+ matches = get_close_matches(name, traits)
+ msg = u"Config option `{option}` not recognized by `{klass}`.".format(
+ option=name, klass=self.__class__.__name__)
+
+ if len(matches) == 1:
+ msg += u" Did you mean `{matches}`?".format(matches=matches[0])
+ elif len(matches) >= 1:
+ msg +=" Did you mean one of: `{matches}`?".format(matches=', '.join(sorted(matches)))
+ warn(msg)
+
+ @observe('config')
+ @observe_compat
+ def _config_changed(self, change):
+ """Update all the class traits having ``config=True`` in metadata.
+
+ For any class trait with a ``config`` metadata attribute that is
+ ``True``, we update the trait with the value of the corresponding
+ config entry.
+ """
+ # Get all traits with a config metadata entry that is True
+ traits = self.traits(config=True)
+
+ # We auto-load config section for this class as well as any parent
+ # classes that are Configurable subclasses. This starts with Configurable
+ # and works down the mro loading the config for each section.
+ section_names = self.section_names()
+ self._load_config(change.new, traits=traits, section_names=section_names)
+
+ def update_config(self, config):
+ """Update config and load the new values"""
+ # traitlets prior to 4.2 created a copy of self.config in order to trigger change events.
+ # Some projects (IPython < 5) relied upon one side effect of this,
+ # that self.config prior to update_config was not modified in-place.
+ # For backward-compatibility, we must ensure that self.config
+ # is a new object and not modified in-place,
+ # but config consumers should not rely on this behavior.
+ self.config = deepcopy(self.config)
+ # load config
+ self._load_config(config)
+ # merge it into self.config
+ self.config.merge(config)
+ # TODO: trigger change event if/when dict-update change events take place
+ # DO NOT trigger full trait-change
+
+ @classmethod
+ def class_get_help(cls, inst=None):
+ """Get the help string for this class in ReST format.
+
+ If `inst` is given, it's current trait values will be used in place of
+ class defaults.
+ """
+ assert inst is None or isinstance(inst, cls)
+ final_help = []
+ final_help.append(u'%s options' % cls.__name__)
+ final_help.append(len(final_help[0])*u'-')
+ for k, v in sorted(cls.class_traits(config=True).items()):
+ help = cls.class_get_trait_help(v, inst)
+ final_help.append(help)
+ return '\n'.join(final_help)
+
+ @classmethod
+ def class_get_trait_help(cls, trait, inst=None):
+ """Get the help string for a single trait.
+
+ If `inst` is given, it's current trait values will be used in place of
+ the class default.
+ """
+ assert inst is None or isinstance(inst, cls)
+ lines = []
+ header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
+ lines.append(header)
+ if inst is not None:
+ lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
+ else:
+ try:
+ dvr = trait.default_value_repr()
+ except Exception:
+ dvr = None # ignore defaults we can't construct
+ if dvr is not None:
+ if len(dvr) > 64:
+ dvr = dvr[:61]+'...'
+ lines.append(indent('Default: %s' % dvr, 4))
+ if 'Enum' in trait.__class__.__name__:
+ # include Enum choices
+ lines.append(indent('Choices: %r' % (trait.values,)))
+
+ help = trait.help
+ if help != '':
+ help = '\n'.join(wrap_paragraphs(help, 76))
+ lines.append(indent(help, 4))
+ return '\n'.join(lines)
+
+ @classmethod
+ def class_print_help(cls, inst=None):
+ """Get the help string for a single trait and print it."""
+ print(cls.class_get_help(inst))
+
+ @classmethod
+ def class_config_section(cls):
+ """Get the config class config section"""
+ def c(s):
+ """return a commented, wrapped block."""
+ s = '\n\n'.join(wrap_paragraphs(s, 78))
+
+ return '## ' + s.replace('\n', '\n# ')
+
+ # section header
+ breaker = '#' + '-'*78
+ parent_classes = ','.join(p.__name__ for p in cls.__bases__)
+ s = "# %s(%s) configuration" % (cls.__name__, parent_classes)
+ lines = [breaker, s, breaker, '']
+ # get the description trait
+ desc = cls.class_traits().get('description')
+ if desc:
+ desc = desc.default_value
+ if not desc:
+ # no description from trait, use __doc__
+ desc = getattr(cls, '__doc__', '')
+ if desc:
+ lines.append(c(desc))
+ lines.append('')
+
+ for name, trait in sorted(cls.class_own_traits(config=True).items()):
+ lines.append(c(trait.help))
+ lines.append('#c.%s.%s = %s' % (cls.__name__, name, trait.default_value_repr()))
+ lines.append('')
+ return '\n'.join(lines)
+
+ @classmethod
+ def class_config_rst_doc(cls):
+ """Generate rST documentation for this class' config options.
+
+ Excludes traits defined on parent classes.
+ """
+ lines = []
+ classname = cls.__name__
+ for k, trait in sorted(cls.class_own_traits(config=True).items()):
+ ttype = trait.__class__.__name__
+
+ termline = classname + '.' + trait.name
+
+ # Choices or type
+ if 'Enum' in ttype:
+ # include Enum choices
+ termline += ' : ' + '|'.join(repr(x) for x in trait.values)
+ else:
+ termline += ' : ' + ttype
+ lines.append(termline)
+
+ # Default value
+ try:
+ dvr = trait.default_value_repr()
+ except Exception:
+ dvr = None # ignore defaults we can't construct
+ if dvr is not None:
+ if len(dvr) > 64:
+ dvr = dvr[:61]+'...'
+ # Double up backslashes, so they get to the rendered docs
+ dvr = dvr.replace('\\n', '\\\\n')
+ lines.append(' Default: ``%s``' % dvr)
+ lines.append('')
+
+ help = trait.help or 'No description'
+ lines.append(indent(dedent(help), 4))
+
+ # Blank line
+ lines.append('')
+
+ return '\n'.join(lines)
+
+
+
+class LoggingConfigurable(Configurable):
+ """A parent class for Configurables that log.
+
+ Subclasses have a log trait, and the default behavior
+ is to get the logger from the currently running Application.
+ """
+
+ log = Instance('logging.Logger')
+ @default('log')
+ def _log_default(self):
+ from traitlets import log
+ return log.get_logger()
+
+
+class SingletonConfigurable(LoggingConfigurable):
+ """A configurable that only allows one instance.
+
+ This class is for classes that should only have one instance of itself
+ or *any* subclass. To create and retrieve such a class use the
+ :meth:`SingletonConfigurable.instance` method.
+ """
+
+ _instance = None
+
+ @classmethod
+ def _walk_mro(cls):
+ """Walk the cls.mro() for parent classes that are also singletons
+
+ For use in instance()
+ """
+
+ for subclass in cls.mro():
+ if issubclass(cls, subclass) and \
+ issubclass(subclass, SingletonConfigurable) and \
+ subclass != SingletonConfigurable:
+ yield subclass
+
+ @classmethod
+ def clear_instance(cls):
+ """unset _instance for this class and singleton parents.
+ """
+ if not cls.initialized():
+ return
+ for subclass in cls._walk_mro():
+ if isinstance(subclass._instance, cls):
+ # only clear instances that are instances
+ # of the calling class
+ subclass._instance = None
+
+ @classmethod
+ def instance(cls, *args, **kwargs):
+ """Returns a global instance of this class.
+
+ This method create a new instance if none have previously been created
+ and returns a previously created instance is one already exists.
+
+ The arguments and keyword arguments passed to this method are passed
+ on to the :meth:`__init__` method of the class upon instantiation.
+
+ Examples
+ --------
+
+ Create a singleton class using instance, and retrieve it::
+
+ >>> from traitlets.config.configurable import SingletonConfigurable
+ >>> class Foo(SingletonConfigurable): pass
+ >>> foo = Foo.instance()
+ >>> foo == Foo.instance()
+ True
+
+ Create a subclass that is retrived using the base class instance::
+
+ >>> class Bar(SingletonConfigurable): pass
+ >>> class Bam(Bar): pass
+ >>> bam = Bam.instance()
+ >>> bam == Bar.instance()
+ True
+ """
+ # Create and save the instance
+ if cls._instance is None:
+ inst = cls(*args, **kwargs)
+ # Now make sure that the instance will also be returned by
+ # parent classes' _instance attribute.
+ for subclass in cls._walk_mro():
+ subclass._instance = inst
+
+ if isinstance(cls._instance, cls):
+ return cls._instance
+ else:
+ raise MultipleInstanceError(
+ 'Multiple incompatible subclass instances of '
+ '%s are being created.' % cls.__name__
+ )
+
+ @classmethod
+ def initialized(cls):
+ """Has an instance been created?"""
+ return hasattr(cls, "_instance") and cls._instance is not None
+
+
+
diff --git a/contrib/python/traitlets/py2/traitlets/config/loader.py b/contrib/python/traitlets/py2/traitlets/config/loader.py
new file mode 100644
index 0000000000..803b36276f
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/loader.py
@@ -0,0 +1,857 @@
+# encoding: utf-8
+"""A simple configuration system."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import argparse
+import copy
+import logging
+import os
+import re
+import sys
+import json
+from ast import literal_eval
+
+from ipython_genutils.path import filefind
+from ipython_genutils import py3compat
+from ipython_genutils.encoding import DEFAULT_ENCODING
+from six import text_type
+from traitlets.traitlets import HasTraits, List, Any
+
+#-----------------------------------------------------------------------------
+# Exceptions
+#-----------------------------------------------------------------------------
+
+
+class ConfigError(Exception):
+ pass
+
+class ConfigLoaderError(ConfigError):
+ pass
+
+class ConfigFileNotFound(ConfigError):
+ pass
+
+class ArgumentError(ConfigLoaderError):
+ pass
+
+#-----------------------------------------------------------------------------
+# Argparse fix
+#-----------------------------------------------------------------------------
+
+# Unfortunately argparse by default prints help messages to stderr instead of
+# stdout. This makes it annoying to capture long help screens at the command
+# line, since one must know how to pipe stderr, which many users don't know how
+# to do. So we override the print_help method with one that defaults to
+# stdout and use our class instead.
+
+class ArgumentParser(argparse.ArgumentParser):
+ """Simple argparse subclass that prints help to stdout by default."""
+
+ def print_help(self, file=None):
+ if file is None:
+ file = sys.stdout
+ return super(ArgumentParser, self).print_help(file)
+
+ print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
+
+#-----------------------------------------------------------------------------
+# Config class for holding config information
+#-----------------------------------------------------------------------------
+
+class LazyConfigValue(HasTraits):
+ """Proxy object for exposing methods on configurable containers
+
+ Exposes:
+
+ - append, extend, insert on lists
+ - update on dicts
+ - update, add on sets
+ """
+
+ _value = None
+
+ # list methods
+ _extend = List()
+ _prepend = List()
+
+ def append(self, obj):
+ self._extend.append(obj)
+
+ def extend(self, other):
+ self._extend.extend(other)
+
+ def prepend(self, other):
+ """like list.extend, but for the front"""
+ self._prepend[:0] = other
+
+ _inserts = List()
+ def insert(self, index, other):
+ if not isinstance(index, int):
+ raise TypeError("An integer is required")
+ self._inserts.append((index, other))
+
+ # dict methods
+ # update is used for both dict and set
+ _update = Any()
+ def update(self, other):
+ if self._update is None:
+ if isinstance(other, dict):
+ self._update = {}
+ else:
+ self._update = set()
+ self._update.update(other)
+
+ # set methods
+ def add(self, obj):
+ self.update({obj})
+
+ def get_value(self, initial):
+ """construct the value from the initial one
+
+ after applying any insert / extend / update changes
+ """
+ if self._value is not None:
+ return self._value
+ value = copy.deepcopy(initial)
+ if isinstance(value, list):
+ for idx, obj in self._inserts:
+ value.insert(idx, obj)
+ value[:0] = self._prepend
+ value.extend(self._extend)
+
+ elif isinstance(value, dict):
+ if self._update:
+ value.update(self._update)
+ elif isinstance(value, set):
+ if self._update:
+ value.update(self._update)
+ self._value = value
+ return value
+
+ def to_dict(self):
+ """return JSONable dict form of my data
+
+ Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
+ """
+ d = {}
+ if self._update:
+ d['update'] = self._update
+ if self._extend:
+ d['extend'] = self._extend
+ if self._prepend:
+ d['prepend'] = self._prepend
+ elif self._inserts:
+ d['inserts'] = self._inserts
+ return d
+
+
+def _is_section_key(key):
+ """Is a Config key a section name (does it start with a capital)?"""
+ if key and key[0].upper()==key[0] and not key.startswith('_'):
+ return True
+ else:
+ return False
+
+
+class Config(dict):
+ """An attribute based dict that can do smart merges."""
+
+ def __init__(self, *args, **kwds):
+ dict.__init__(self, *args, **kwds)
+ self._ensure_subconfig()
+
+ def _ensure_subconfig(self):
+ """ensure that sub-dicts that should be Config objects are
+
+ casts dicts that are under section keys to Config objects,
+ which is necessary for constructing Config objects from dict literals.
+ """
+ for key in self:
+ obj = self[key]
+ if _is_section_key(key) \
+ and isinstance(obj, dict) \
+ and not isinstance(obj, Config):
+ setattr(self, key, Config(obj))
+
+ def _merge(self, other):
+ """deprecated alias, use Config.merge()"""
+ self.merge(other)
+
+ def merge(self, other):
+ """merge another config object into this one"""
+ to_update = {}
+ for k, v in other.items():
+ if k not in self:
+ to_update[k] = v
+ else: # I have this key
+ if isinstance(v, Config) and isinstance(self[k], Config):
+ # Recursively merge common sub Configs
+ self[k].merge(v)
+ else:
+ # Plain updates for non-Configs
+ to_update[k] = v
+
+ self.update(to_update)
+
+ def collisions(self, other):
+ """Check for collisions between two config objects.
+
+ Returns a dict of the form {"Class": {"trait": "collision message"}}`,
+ indicating which values have been ignored.
+
+ An empty dict indicates no collisions.
+ """
+ collisions = {}
+ for section in self:
+ if section not in other:
+ continue
+ mine = self[section]
+ theirs = other[section]
+ for key in mine:
+ if key in theirs and mine[key] != theirs[key]:
+ collisions.setdefault(section, {})
+ collisions[section][key] = "%r ignored, using %r" % (mine[key], theirs[key])
+ return collisions
+
+ def __contains__(self, key):
+ # allow nested contains of the form `"Section.key" in config`
+ if '.' in key:
+ first, remainder = key.split('.', 1)
+ if first not in self:
+ return False
+ return remainder in self[first]
+
+ return super(Config, self).__contains__(key)
+
+ # .has_key is deprecated for dictionaries.
+ has_key = __contains__
+
+ def _has_section(self, key):
+ return _is_section_key(key) and key in self
+
+ def copy(self):
+ return type(self)(dict.copy(self))
+
+ def __copy__(self):
+ return self.copy()
+
+ def __deepcopy__(self, memo):
+ new_config = type(self)()
+ for key, value in self.items():
+ if isinstance(value, (Config, LazyConfigValue)):
+ # deep copy config objects
+ value = copy.deepcopy(value, memo)
+ elif type(value) in {dict, list, set, tuple}:
+ # shallow copy plain container traits
+ value = copy.copy(value)
+ new_config[key] = value
+ return new_config
+
+ def __getitem__(self, key):
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ if _is_section_key(key):
+ c = Config()
+ dict.__setitem__(self, key, c)
+ return c
+ elif not key.startswith('_'):
+ # undefined, create lazy value, used for container methods
+ v = LazyConfigValue()
+ dict.__setitem__(self, key, v)
+ return v
+ else:
+ raise KeyError
+
+ def __setitem__(self, key, value):
+ if _is_section_key(key):
+ if not isinstance(value, Config):
+ raise ValueError('values whose keys begin with an uppercase '
+ 'char must be Config instances: %r, %r' % (key, value))
+ dict.__setitem__(self, key, value)
+
+ def __getattr__(self, key):
+ if key.startswith('__'):
+ return dict.__getattr__(self, key)
+ try:
+ return self.__getitem__(key)
+ except KeyError as e:
+ raise AttributeError(e)
+
+ def __setattr__(self, key, value):
+ if key.startswith('__'):
+ return dict.__setattr__(self, key, value)
+ try:
+ self.__setitem__(key, value)
+ except KeyError as e:
+ raise AttributeError(e)
+
+ def __delattr__(self, key):
+ if key.startswith('__'):
+ return dict.__delattr__(self, key)
+ try:
+ dict.__delitem__(self, key)
+ except KeyError as e:
+ raise AttributeError(e)
+
+
+#-----------------------------------------------------------------------------
+# Config loading classes
+#-----------------------------------------------------------------------------
+
+
+class ConfigLoader(object):
+ """A object for loading configurations from just about anywhere.
+
+ The resulting configuration is packaged as a :class:`Config`.
+
+ Notes
+ -----
+ A :class:`ConfigLoader` does one thing: load a config from a source
+ (file, command line arguments) and returns the data as a :class:`Config` object.
+ There are lots of things that :class:`ConfigLoader` does not do. It does
+ not implement complex logic for finding config files. It does not handle
+ default values or merge multiple configs. These things need to be
+ handled elsewhere.
+ """
+
+ def _log_default(self):
+ from traitlets.log import get_logger
+ return get_logger()
+
+ def __init__(self, log=None):
+ """A base class for config loaders.
+
+ log : instance of :class:`logging.Logger` to use.
+ By default loger of :meth:`traitlets.config.application.Application.instance()`
+ will be used
+
+ Examples
+ --------
+
+ >>> cl = ConfigLoader()
+ >>> config = cl.load_config()
+ >>> config
+ {}
+ """
+ self.clear()
+ if log is None:
+ self.log = self._log_default()
+ self.log.debug('Using default logger')
+ else:
+ self.log = log
+
+ def clear(self):
+ self.config = Config()
+
+ def load_config(self):
+ """Load a config from somewhere, return a :class:`Config` instance.
+
+ Usually, this will cause self.config to be set and then returned.
+ However, in most cases, :meth:`ConfigLoader.clear` should be called
+ to erase any previous state.
+ """
+ self.clear()
+ return self.config
+
+
+class FileConfigLoader(ConfigLoader):
+ """A base class for file based configurations.
+
+ As we add more file based config loaders, the common logic should go
+ here.
+ """
+
+ def __init__(self, filename, path=None, **kw):
+ """Build a config loader for a filename and path.
+
+ Parameters
+ ----------
+ filename : str
+ The file name of the config file.
+ path : str, list, tuple
+ The path to search for the config file on, or a sequence of
+ paths to try in order.
+ """
+ super(FileConfigLoader, self).__init__(**kw)
+ self.filename = filename
+ self.path = path
+ self.full_filename = ''
+
+ def _find_file(self):
+ """Try to find the file by searching the paths."""
+ self.full_filename = filefind(self.filename, self.path)
+
+class JSONFileConfigLoader(FileConfigLoader):
+ """A JSON file loader for config
+
+ Can also act as a context manager that rewrite the configuration file to disk on exit.
+
+ Example::
+
+ with JSONFileConfigLoader('myapp.json','/home/jupyter/configurations/') as c:
+ c.MyNewConfigurable.new_value = 'Updated'
+
+ """
+
+ def load_config(self):
+ """Load the config from a file and return it as a Config object."""
+ self.clear()
+ try:
+ self._find_file()
+ except IOError as e:
+ raise ConfigFileNotFound(str(e))
+ dct = self._read_file_as_dict()
+ self.config = self._convert_to_config(dct)
+ return self.config
+
+ def _read_file_as_dict(self):
+ with open(self.full_filename) as f:
+ return json.load(f)
+
+ def _convert_to_config(self, dictionary):
+ if 'version' in dictionary:
+ version = dictionary.pop('version')
+ else:
+ version = 1
+
+ if version == 1:
+ return Config(dictionary)
+ else:
+ raise ValueError('Unknown version of JSON config file: {version}'.format(version=version))
+
+ def __enter__(self):
+ self.load_config()
+ return self.config
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """
+ Exit the context manager but do not handle any errors.
+
+ In case of any error, we do not want to write the potentially broken
+ configuration to disk.
+ """
+ self.config.version = 1
+ json_config = json.dumps(self.config, indent=2)
+ with open(self.full_filename, 'w') as f:
+ f.write(json_config)
+
+
+
+class PyFileConfigLoader(FileConfigLoader):
+ """A config loader for pure python files.
+
+ This is responsible for locating a Python config file by filename and
+ path, then executing it to construct a Config object.
+ """
+
+ def load_config(self):
+ """Load the config from a file and return it as a Config object."""
+ self.clear()
+ try:
+ self._find_file()
+ except IOError as e:
+ raise ConfigFileNotFound(str(e))
+ self._read_file_as_dict()
+ return self.config
+
+ def load_subconfig(self, fname, path=None):
+ """Injected into config file namespace as load_subconfig"""
+ if path is None:
+ path = self.path
+
+ loader = self.__class__(fname, path)
+ try:
+ sub_config = loader.load_config()
+ except ConfigFileNotFound:
+ # Pass silently if the sub config is not there,
+ # treat it as an empty config file.
+ pass
+ else:
+ self.config.merge(sub_config)
+
+ def _read_file_as_dict(self):
+ """Load the config file into self.config, with recursive loading."""
+ def get_config():
+ """Unnecessary now, but a deprecation warning is more trouble than it's worth."""
+ return self.config
+
+ namespace = dict(
+ c=self.config,
+ load_subconfig=self.load_subconfig,
+ get_config=get_config,
+ __file__=self.full_filename,
+ )
+ fs_encoding = sys.getfilesystemencoding() or 'ascii'
+ conf_filename = self.full_filename.encode(fs_encoding)
+ py3compat.execfile(conf_filename, namespace)
+
+
+class CommandLineConfigLoader(ConfigLoader):
+ """A config loader for command line arguments.
+
+ As we add more command line based loaders, the common logic should go
+ here.
+ """
+
+ def _exec_config_str(self, lhs, rhs):
+ """execute self.config.<lhs> = <rhs>
+
+ * expands ~ with expanduser
+ * tries to assign with literal_eval, otherwise assigns with just the string,
+ allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
+ equivalent are `--C.a=4` and `--C.a='4'`.
+ """
+ rhs = os.path.expanduser(rhs)
+ try:
+ # Try to see if regular Python syntax will work. This
+ # won't handle strings as the quote marks are removed
+ # by the system shell.
+ value = literal_eval(rhs)
+ except (NameError, SyntaxError, ValueError):
+ # This case happens if the rhs is a string.
+ value = rhs
+
+ exec(u'self.config.%s = value' % lhs)
+
+ def _load_flag(self, cfg):
+ """update self.config from a flag, which can be a dict or Config"""
+ if isinstance(cfg, (dict, Config)):
+ # don't clobber whole config sections, update
+ # each section from config:
+ for sec,c in cfg.items():
+ self.config[sec].update(c)
+ else:
+ raise TypeError("Invalid flag: %r" % cfg)
+
+# raw --identifier=value pattern
+# but *also* accept '-' as wordsep, for aliases
+# accepts: --foo=a
+# --Class.trait=value
+# --alias-name=value
+# rejects: -foo=value
+# --foo
+# --Class.trait
+kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
+
+# just flags, no assignments, with two *or one* leading '-'
+# accepts: --foo
+# -foo-bar-again
+# rejects: --anything=anything
+# --two.word
+
+flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
+
+class KeyValueConfigLoader(CommandLineConfigLoader):
+ """A config loader that loads key value pairs from the command line.
+
+ This allows command line options to be gives in the following form::
+
+ ipython --profile="foo" --InteractiveShell.autocall=False
+ """
+
+ def __init__(self, argv=None, aliases=None, flags=None, **kw):
+ """Create a key value pair config loader.
+
+ Parameters
+ ----------
+ argv : list
+ A list that has the form of sys.argv[1:] which has unicode
+ elements of the form u"key=value". If this is None (default),
+ then sys.argv[1:] will be used.
+ aliases : dict
+ A dict of aliases for configurable traits.
+ Keys are the short aliases, Values are the resolved trait.
+ Of the form: `{'alias' : 'Configurable.trait'}`
+ flags : dict
+ A dict of flags, keyed by str name. Vaues can be Config objects,
+ dicts, or "key=value" strings. If Config or dict, when the flag
+ is triggered, The flag is loaded as `self.config.update(m)`.
+
+ Returns
+ -------
+ config : Config
+ The resulting Config object.
+
+ Examples
+ --------
+
+ >>> from traitlets.config.loader import KeyValueConfigLoader
+ >>> cl = KeyValueConfigLoader()
+ >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
+ >>> sorted(d.items())
+ [('A', {'name': 'brian'}), ('B', {'number': 0})]
+ """
+ super(KeyValueConfigLoader, self).__init__(**kw)
+ if argv is None:
+ argv = sys.argv[1:]
+ self.argv = argv
+ self.aliases = aliases or {}
+ self.flags = flags or {}
+
+
+ def clear(self):
+ super(KeyValueConfigLoader, self).clear()
+ self.extra_args = []
+
+
+ def _decode_argv(self, argv, enc=None):
+ """decode argv if bytes, using stdin.encoding, falling back on default enc"""
+ uargv = []
+ if enc is None:
+ enc = DEFAULT_ENCODING
+ for arg in argv:
+ if not isinstance(arg, text_type):
+ # only decode if not already decoded
+ arg = arg.decode(enc)
+ uargv.append(arg)
+ return uargv
+
+
+ def load_config(self, argv=None, aliases=None, flags=None):
+ """Parse the configuration and generate the Config object.
+
+ After loading, any arguments that are not key-value or
+ flags will be stored in self.extra_args - a list of
+ unparsed command-line arguments. This is used for
+ arguments such as input files or subcommands.
+
+ Parameters
+ ----------
+ argv : list, optional
+ A list that has the form of sys.argv[1:] which has unicode
+ elements of the form u"key=value". If this is None (default),
+ then self.argv will be used.
+ aliases : dict
+ A dict of aliases for configurable traits.
+ Keys are the short aliases, Values are the resolved trait.
+ Of the form: `{'alias' : 'Configurable.trait'}`
+ flags : dict
+ A dict of flags, keyed by str name. Values can be Config objects
+ or dicts. When the flag is triggered, The config is loaded as
+ `self.config.update(cfg)`.
+ """
+ self.clear()
+ if argv is None:
+ argv = self.argv
+ if aliases is None:
+ aliases = self.aliases
+ if flags is None:
+ flags = self.flags
+
+ # ensure argv is a list of unicode strings:
+ uargv = self._decode_argv(argv)
+ for idx,raw in enumerate(uargv):
+ # strip leading '-'
+ item = raw.lstrip('-')
+
+ if raw == '--':
+ # don't parse arguments after '--'
+ # this is useful for relaying arguments to scripts, e.g.
+ # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
+ self.extra_args.extend(uargv[idx+1:])
+ break
+
+ if kv_pattern.match(raw):
+ lhs,rhs = item.split('=',1)
+ # Substitute longnames for aliases.
+ if lhs in aliases:
+ lhs = aliases[lhs]
+ if '.' not in lhs:
+ # probably a mistyped alias, but not technically illegal
+ self.log.warning("Unrecognized alias: '%s', it will probably have no effect.", raw)
+ try:
+ self._exec_config_str(lhs, rhs)
+ except Exception:
+ raise ArgumentError("Invalid argument: '%s'" % raw)
+
+ elif flag_pattern.match(raw):
+ if item in flags:
+ cfg,help = flags[item]
+ self._load_flag(cfg)
+ else:
+ raise ArgumentError("Unrecognized flag: '%s'"%raw)
+ elif raw.startswith('-'):
+ kv = '--'+item
+ if kv_pattern.match(kv):
+ raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
+ else:
+ raise ArgumentError("Invalid argument: '%s'"%raw)
+ else:
+ # keep all args that aren't valid in a list,
+ # in case our parent knows what to do with them.
+ self.extra_args.append(item)
+ return self.config
+
+class ArgParseConfigLoader(CommandLineConfigLoader):
+ """A loader that uses the argparse module to load from the command line."""
+
+ def __init__(self, argv=None, aliases=None, flags=None, log=None, *parser_args, **parser_kw):
+ """Create a config loader for use with argparse.
+
+ Parameters
+ ----------
+
+ argv : optional, list
+ If given, used to read command-line arguments from, otherwise
+ sys.argv[1:] is used.
+
+ parser_args : tuple
+ A tuple of positional arguments that will be passed to the
+ constructor of :class:`argparse.ArgumentParser`.
+
+ parser_kw : dict
+ A tuple of keyword arguments that will be passed to the
+ constructor of :class:`argparse.ArgumentParser`.
+
+ Returns
+ -------
+ config : Config
+ The resulting Config object.
+ """
+ super(CommandLineConfigLoader, self).__init__(log=log)
+ self.clear()
+ if argv is None:
+ argv = sys.argv[1:]
+ self.argv = argv
+ self.aliases = aliases or {}
+ self.flags = flags or {}
+
+ self.parser_args = parser_args
+ self.version = parser_kw.pop("version", None)
+ kwargs = dict(argument_default=argparse.SUPPRESS)
+ kwargs.update(parser_kw)
+ self.parser_kw = kwargs
+
+ def load_config(self, argv=None, aliases=None, flags=None):
+ """Parse command line arguments and return as a Config object.
+
+ Parameters
+ ----------
+
+ args : optional, list
+ If given, a list with the structure of sys.argv[1:] to parse
+ arguments from. If not given, the instance's self.argv attribute
+ (given at construction time) is used."""
+ self.clear()
+ if argv is None:
+ argv = self.argv
+ if aliases is None:
+ aliases = self.aliases
+ if flags is None:
+ flags = self.flags
+ self._create_parser(aliases, flags)
+ self._parse_args(argv)
+ self._convert_to_config()
+ return self.config
+
+ def get_extra_args(self):
+ if hasattr(self, 'extra_args'):
+ return self.extra_args
+ else:
+ return []
+
+ def _create_parser(self, aliases=None, flags=None):
+ self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
+ self._add_arguments(aliases, flags)
+
+ def _add_arguments(self, aliases=None, flags=None):
+ raise NotImplementedError("subclasses must implement _add_arguments")
+
+ def _parse_args(self, args):
+ """self.parser->self.parsed_data"""
+ # decode sys.argv to support unicode command-line options
+ enc = DEFAULT_ENCODING
+ uargs = [py3compat.cast_unicode(a, enc) for a in args]
+ self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
+
+ def _convert_to_config(self):
+ """self.parsed_data->self.config"""
+ for k, v in vars(self.parsed_data).items():
+ exec("self.config.%s = v"%k, locals(), globals())
+
+class KVArgParseConfigLoader(ArgParseConfigLoader):
+ """A config loader that loads aliases and flags with argparse,
+ but will use KVLoader for the rest. This allows better parsing
+ of common args, such as `ipython -c 'print 5'`, but still gets
+ arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
+
+ def _add_arguments(self, aliases=None, flags=None):
+ self.alias_flags = {}
+ # print aliases, flags
+ if aliases is None:
+ aliases = self.aliases
+ if flags is None:
+ flags = self.flags
+ paa = self.parser.add_argument
+ for key,value in aliases.items():
+ if key in flags:
+ # flags
+ nargs = '?'
+ else:
+ nargs = None
+ if len(key) is 1:
+ paa('-'+key, '--'+key, type=text_type, dest=value, nargs=nargs)
+ else:
+ paa('--'+key, type=text_type, dest=value, nargs=nargs)
+ for key, (value, help) in flags.items():
+ if key in self.aliases:
+ #
+ self.alias_flags[self.aliases[key]] = value
+ continue
+ if len(key) is 1:
+ paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
+ else:
+ paa('--'+key, action='append_const', dest='_flags', const=value)
+
+ def _convert_to_config(self):
+ """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
+ # remove subconfigs list from namespace before transforming the Namespace
+ if '_flags' in self.parsed_data:
+ subcs = self.parsed_data._flags
+ del self.parsed_data._flags
+ else:
+ subcs = []
+
+ for k, v in vars(self.parsed_data).items():
+ if v is None:
+ # it was a flag that shares the name of an alias
+ subcs.append(self.alias_flags[k])
+ else:
+ # eval the KV assignment
+ self._exec_config_str(k, v)
+
+ for subc in subcs:
+ self._load_flag(subc)
+
+ if self.extra_args:
+ sub_parser = KeyValueConfigLoader(log=self.log)
+ sub_parser.load_config(self.extra_args)
+ self.config.merge(sub_parser.config)
+ self.extra_args = sub_parser.extra_args
+
+
+def load_pyconfig_files(config_files, path):
+ """Load multiple Python config files, merging each of them in turn.
+
+ Parameters
+ ==========
+ config_files : list of str
+ List of config files names to load and merge into the config.
+ path : unicode
+ The full path to the location of the config files.
+ """
+ config = Config()
+ for cf in config_files:
+ loader = PyFileConfigLoader(cf, path=path)
+ try:
+ next_config = loader.load_config()
+ except ConfigFileNotFound:
+ pass
+ except:
+ raise
+ else:
+ config.merge(next_config)
+ return config
diff --git a/contrib/python/traitlets/py2/traitlets/config/manager.py b/contrib/python/traitlets/py2/traitlets/config/manager.py
new file mode 100644
index 0000000000..5e5ebde9af
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/manager.py
@@ -0,0 +1,88 @@
+"""Manager to read and modify config data in JSON files.
+"""
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+import errno
+import io
+import json
+import os
+
+from six import PY3
+from traitlets.config import LoggingConfigurable
+from traitlets.traitlets import Unicode
+
+
+def recursive_update(target, new):
+ """Recursively update one dictionary using another.
+
+ None values will delete their keys.
+ """
+ for k, v in new.items():
+ if isinstance(v, dict):
+ if k not in target:
+ target[k] = {}
+ recursive_update(target[k], v)
+ if not target[k]:
+ # Prune empty subdicts
+ del target[k]
+
+ elif v is None:
+ target.pop(k, None)
+
+ else:
+ target[k] = v
+
+
+class BaseJSONConfigManager(LoggingConfigurable):
+ """General JSON config manager
+
+ Deals with persisting/storing config in a json file
+ """
+
+ config_dir = Unicode('.')
+
+ def ensure_config_dir_exists(self):
+ try:
+ os.makedirs(self.config_dir, 0o755)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ def file_name(self, section_name):
+ return os.path.join(self.config_dir, section_name+'.json')
+
+ def get(self, section_name):
+ """Retrieve the config data for the specified section.
+
+ Returns the data as a dictionary, or an empty dictionary if the file
+ doesn't exist.
+ """
+ filename = self.file_name(section_name)
+ if os.path.isfile(filename):
+ with io.open(filename, encoding='utf-8') as f:
+ return json.load(f)
+ else:
+ return {}
+
+ def set(self, section_name, data):
+ """Store the given config data.
+ """
+ filename = self.file_name(section_name)
+ self.ensure_config_dir_exists()
+
+ if PY3:
+ f = io.open(filename, 'w', encoding='utf-8')
+ else:
+ f = open(filename, 'wb')
+ with f:
+ json.dump(data, f, indent=2)
+
+ def update(self, section_name, new_data):
+ """Modify the config section by recursively updating it with new_data.
+
+ Returns the modified config data as a dictionary.
+ """
+ data = self.get(section_name)
+ recursive_update(data, new_data)
+ self.set(section_name, data)
+ return data
diff --git a/contrib/python/traitlets/py2/traitlets/log.py b/contrib/python/traitlets/py2/traitlets/log.py
new file mode 100644
index 0000000000..af86b325f5
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/log.py
@@ -0,0 +1,27 @@
+"""Grab the global logger instance."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import logging
+
+_logger = None
+
+def get_logger():
+ """Grab the global logger instance.
+
+ If a global Application is instantiated, grab its logger.
+ Otherwise, grab the root logger.
+ """
+ global _logger
+
+ if _logger is None:
+ from .config import Application
+ if Application.initialized():
+ _logger = Application.instance().log
+ else:
+ _logger = logging.getLogger('traitlets')
+ # Add a NullHandler to silence warnings about not being
+ # initialized, per best practice for libraries.
+ _logger.addHandler(logging.NullHandler())
+ return _logger
diff --git a/contrib/python/traitlets/py2/traitlets/tests/__init__.py b/contrib/python/traitlets/py2/traitlets/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/tests/__init__.py
diff --git a/contrib/python/traitlets/py2/traitlets/tests/_warnings.py b/contrib/python/traitlets/py2/traitlets/tests/_warnings.py
new file mode 100644
index 0000000000..f135d1f67e
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/tests/_warnings.py
@@ -0,0 +1,107 @@
+# From scikit-image: https://github.com/scikit-image/scikit-image/blob/c2f8c4ab123ebe5f7b827bc495625a32bb225c10/skimage/_shared/_warnings.py
+# Licensed under modified BSD license
+
+__all__ = ['all_warnings', 'expected_warnings']
+
+from contextlib import contextmanager
+import sys
+import warnings
+import inspect
+import re
+
+
+@contextmanager
+def all_warnings():
+ """
+ Context for use in testing to ensure that all warnings are raised.
+ Examples
+ --------
+ >>> import warnings
+ >>> def foo():
+ ... warnings.warn(RuntimeWarning("bar"))
+ We raise the warning once, while the warning filter is set to "once".
+ Hereafter, the warning is invisible, even with custom filters:
+ >>> with warnings.catch_warnings():
+ ... warnings.simplefilter('once')
+ ... foo()
+ We can now run ``foo()`` without a warning being raised:
+ >>> from numpy.testing import assert_warns
+ >>> foo()
+ To catch the warning, we call in the help of ``all_warnings``:
+ >>> with all_warnings():
+ ... assert_warns(RuntimeWarning, foo)
+ """
+
+ # Whenever a warning is triggered, Python adds a __warningregistry__
+ # member to the *calling* module. The exercize here is to find
+ # and eradicate all those breadcrumbs that were left lying around.
+ #
+ # We proceed by first searching all parent calling frames and explicitly
+ # clearing their warning registries (necessary for the doctests above to
+ # pass). Then, we search for all submodules of skimage and clear theirs
+ # as well (necessary for the skimage test suite to pass).
+
+ frame = inspect.currentframe()
+ if frame:
+ for f in inspect.getouterframes(frame):
+ f[0].f_locals['__warningregistry__'] = {}
+ del frame
+
+ for mod_name, mod in list(sys.modules.items()):
+ if 'six.moves' in mod_name:
+ continue
+ try:
+ mod.__warningregistry__.clear()
+ except AttributeError:
+ pass
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ yield w
+
+
+@contextmanager
+def expected_warnings(matching):
+ """Context for use in testing to catch known warnings matching regexes
+
+ Parameters
+ ----------
+ matching : list of strings or compiled regexes
+ Regexes for the desired warning to catch
+ Examples
+ --------
+ >>> from skimage import data, img_as_ubyte, img_as_float
+ >>> with expected_warnings(['precision loss']):
+ ... d = img_as_ubyte(img_as_float(data.coins()))
+ Notes
+ -----
+ Uses `all_warnings` to ensure all warnings are raised.
+ Upon exiting, it checks the recorded warnings for the desired matching
+ pattern(s).
+ Raises a ValueError if any match was not found or an unexpected
+ warning was raised.
+ Allows for three types of behaviors: "and", "or", and "optional" matches.
+ This is done to accomodate different build enviroments or loop conditions
+ that may produce different warnings. The behaviors can be combined.
+ If you pass multiple patterns, you get an orderless "and", where all of the
+ warnings must be raised.
+ If you use the "|" operator in a pattern, you can catch one of several warnings.
+ Finally, you can use "|\A\Z" in a pattern to signify it as optional.
+ """
+ with all_warnings() as w:
+ # enter context
+ yield w
+ # exited user context, check the recorded warnings
+ remaining = [m for m in matching if not '\A\Z' in m.split('|')]
+ for warn in w:
+ found = False
+ for match in matching:
+ if re.search(match, str(warn.message)) is not None:
+ found = True
+ if match in remaining:
+ remaining.remove(match)
+ if not found:
+ raise ValueError('Unexpected warning: %s' % str(warn.message))
+ if len(remaining) > 0:
+ msg = 'No warning raised matching:\n%s' % '\n'.join(remaining)
+ raise ValueError(msg)
diff --git a/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py b/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py
new file mode 100644
index 0000000000..11b334cb60
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py
@@ -0,0 +1,2419 @@
+# encoding: utf-8
+"""Tests for traitlets.traitlets."""
+
+# 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 pickle
+import re
+import sys
+from ._warnings import expected_warnings
+
+from unittest import TestCase
+import pytest
+from pytest import mark
+
+from traitlets import (
+ HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
+ Int, CInt, Long, CLong, Integer, Float, CFloat, Complex, Bytes, Unicode,
+ TraitError, Union, All, Undefined, Type, This, Instance, TCPAddress,
+ List, Tuple, ObjectName, DottedObjectName, CRegExp, link, directional_link,
+ ForwardDeclaredType, ForwardDeclaredInstance, validate, observe, default,
+ observe_compat, BaseDescriptor, HasDescriptors,
+)
+
+import six
+
+def change_dict(*ordered_values):
+ change_names = ('name', 'old', 'new', 'owner', 'type')
+ return dict(zip(change_names, ordered_values))
+
+#-----------------------------------------------------------------------------
+# Helper classes for testing
+#-----------------------------------------------------------------------------
+
+
+class HasTraitsStub(HasTraits):
+
+ def notify_change(self, change):
+ self._notify_name = change['name']
+ self._notify_old = change['old']
+ self._notify_new = change['new']
+ self._notify_type = change['type']
+
+
+#-----------------------------------------------------------------------------
+# Test classes
+#-----------------------------------------------------------------------------
+
+
+class TestTraitType(TestCase):
+
+ def test_get_undefined(self):
+ class A(HasTraits):
+ a = TraitType
+ a = A()
+ with self.assertRaises(TraitError):
+ a.a
+
+ def test_set(self):
+ class A(HasTraitsStub):
+ a = TraitType
+
+ a = A()
+ a.a = 10
+ self.assertEqual(a.a, 10)
+ self.assertEqual(a._notify_name, 'a')
+ self.assertEqual(a._notify_old, Undefined)
+ self.assertEqual(a._notify_new, 10)
+
+ def test_validate(self):
+ class MyTT(TraitType):
+ def validate(self, inst, value):
+ return -1
+ class A(HasTraitsStub):
+ tt = MyTT
+
+ a = A()
+ a.tt = 10
+ self.assertEqual(a.tt, -1)
+
+ def test_default_validate(self):
+ class MyIntTT(TraitType):
+ def validate(self, obj, value):
+ if isinstance(value, int):
+ return value
+ self.error(obj, value)
+ class A(HasTraits):
+ tt = MyIntTT(10)
+ a = A()
+ self.assertEqual(a.tt, 10)
+
+ # Defaults are validated when the HasTraits is instantiated
+ class B(HasTraits):
+ tt = MyIntTT('bad default')
+ self.assertRaises(TraitError, B)
+
+ def test_info(self):
+ class A(HasTraits):
+ tt = TraitType
+ a = A()
+ self.assertEqual(A.tt.info(), 'any value')
+
+ def test_error(self):
+ class A(HasTraits):
+ tt = TraitType
+ a = A()
+ self.assertRaises(TraitError, A.tt.error, a, 10)
+
+ def test_deprecated_dynamic_initializer(self):
+ class A(HasTraits):
+ x = Int(10)
+ def _x_default(self):
+ return 11
+ class B(A):
+ x = Int(20)
+ class C(A):
+ def _x_default(self):
+ return 21
+
+ a = A()
+ self.assertEqual(a._trait_values, {})
+ self.assertEqual(a.x, 11)
+ self.assertEqual(a._trait_values, {'x': 11})
+ b = B()
+ self.assertEqual(b.x, 20)
+ self.assertEqual(b._trait_values, {'x': 20})
+ c = C()
+ self.assertEqual(c._trait_values, {})
+ self.assertEqual(c.x, 21)
+ self.assertEqual(c._trait_values, {'x': 21})
+ # Ensure that the base class remains unmolested when the _default
+ # initializer gets overridden in a subclass.
+ a = A()
+ c = C()
+ self.assertEqual(a._trait_values, {})
+ self.assertEqual(a.x, 11)
+ self.assertEqual(a._trait_values, {'x': 11})
+
+ def test_dynamic_initializer(self):
+
+ class A(HasTraits):
+ x = Int(10)
+
+ @default('x')
+ def _default_x(self):
+ return 11
+
+ class B(A):
+ x = Int(20)
+
+ class C(A):
+
+ @default('x')
+ def _default_x(self):
+ return 21
+
+ a = A()
+ self.assertEqual(a._trait_values, {})
+ self.assertEqual(a.x, 11)
+ self.assertEqual(a._trait_values, {'x': 11})
+ b = B()
+ self.assertEqual(b.x, 20)
+ self.assertEqual(b._trait_values, {'x': 20})
+ c = C()
+ self.assertEqual(c._trait_values, {})
+ self.assertEqual(c.x, 21)
+ self.assertEqual(c._trait_values, {'x': 21})
+ # Ensure that the base class remains unmolested when the _default
+ # initializer gets overridden in a subclass.
+ a = A()
+ c = C()
+ self.assertEqual(a._trait_values, {})
+ self.assertEqual(a.x, 11)
+ self.assertEqual(a._trait_values, {'x': 11})
+
+ def test_tag_metadata(self):
+ class MyIntTT(TraitType):
+ metadata = {'a': 1, 'b': 2}
+ a = MyIntTT(10).tag(b=3, c=4)
+ self.assertEqual(a.metadata, {'a': 1, 'b': 3, 'c': 4})
+
+ def test_metadata_localized_instance(self):
+ class MyIntTT(TraitType):
+ metadata = {'a': 1, 'b': 2}
+ a = MyIntTT(10)
+ b = MyIntTT(10)
+ a.metadata['c'] = 3
+ # make sure that changing a's metadata didn't change b's metadata
+ self.assertNotIn('c', b.metadata)
+
+ def test_union_metadata(self):
+ class Foo(HasTraits):
+ bar = (Int().tag(ta=1) | Dict().tag(ta=2, ti='b')).tag(ti='a')
+ foo = Foo()
+ # At this point, no value has been set for bar, so value-specific
+ # is not set.
+ self.assertEqual(foo.trait_metadata('bar', 'ta'), None)
+ self.assertEqual(foo.trait_metadata('bar', 'ti'), 'a')
+ foo.bar = {}
+ self.assertEqual(foo.trait_metadata('bar', 'ta'), 2)
+ self.assertEqual(foo.trait_metadata('bar', 'ti'), 'b')
+ foo.bar = 1
+ self.assertEqual(foo.trait_metadata('bar', 'ta'), 1)
+ self.assertEqual(foo.trait_metadata('bar', 'ti'), 'a')
+
+ def test_union_default_value(self):
+ class Foo(HasTraits):
+ bar = Union([Dict(), Int()], default_value=1)
+ foo = Foo()
+ self.assertEqual(foo.bar, 1)
+
+ def test_deprecated_metadata_access(self):
+ class MyIntTT(TraitType):
+ metadata = {'a': 1, 'b': 2}
+ a = MyIntTT(10)
+ with expected_warnings(["use the instance .metadata dictionary directly"]*2):
+ a.set_metadata('key', 'value')
+ v = a.get_metadata('key')
+ self.assertEqual(v, 'value')
+ with expected_warnings(["use the instance .help string directly"]*2):
+ a.set_metadata('help', 'some help')
+ v = a.get_metadata('help')
+ self.assertEqual(v, 'some help')
+
+ def test_trait_types_deprecated(self):
+ with expected_warnings(["Traits should be given as instances"]):
+ class C(HasTraits):
+ t = Int
+
+ def test_trait_types_list_deprecated(self):
+ with expected_warnings(["Traits should be given as instances"]):
+ class C(HasTraits):
+ t = List(Int)
+
+ def test_trait_types_tuple_deprecated(self):
+ with expected_warnings(["Traits should be given as instances"]):
+ class C(HasTraits):
+ t = Tuple(Int)
+
+ def test_trait_types_dict_deprecated(self):
+ with expected_warnings(["Traits should be given as instances"]):
+ class C(HasTraits):
+ t = Dict(Int)
+
+class TestHasDescriptorsMeta(TestCase):
+
+ def test_metaclass(self):
+ self.assertEqual(type(HasTraits), MetaHasTraits)
+
+ class A(HasTraits):
+ a = Int()
+
+ a = A()
+ self.assertEqual(type(a.__class__), MetaHasTraits)
+ self.assertEqual(a.a,0)
+ a.a = 10
+ self.assertEqual(a.a,10)
+
+ class B(HasTraits):
+ b = Int()
+
+ b = B()
+ self.assertEqual(b.b,0)
+ b.b = 10
+ self.assertEqual(b.b,10)
+
+ class C(HasTraits):
+ c = Int(30)
+
+ c = C()
+ self.assertEqual(c.c,30)
+ c.c = 10
+ self.assertEqual(c.c,10)
+
+ def test_this_class(self):
+ class A(HasTraits):
+ t = This()
+ tt = This()
+ class B(A):
+ tt = This()
+ ttt = This()
+ self.assertEqual(A.t.this_class, A)
+ self.assertEqual(B.t.this_class, A)
+ self.assertEqual(B.tt.this_class, B)
+ self.assertEqual(B.ttt.this_class, B)
+
+class TestHasDescriptors(TestCase):
+
+ def test_setup_instance(self):
+
+ class FooDescriptor(BaseDescriptor):
+
+ def instance_init(self, inst):
+ foo = inst.foo # instance should have the attr
+
+ class HasFooDescriptors(HasDescriptors):
+
+ fd = FooDescriptor()
+
+ def setup_instance(self, *args, **kwargs):
+ self.foo = kwargs.get('foo', None)
+ super(HasFooDescriptors, self).setup_instance(*args, **kwargs)
+
+ hfd = HasFooDescriptors(foo='bar')
+
+class TestHasTraitsNotify(TestCase):
+
+ def setUp(self):
+ self._notify1 = []
+ self._notify2 = []
+
+ def notify1(self, name, old, new):
+ self._notify1.append((name, old, new))
+
+ def notify2(self, name, old, new):
+ self._notify2.append((name, old, new))
+
+ def test_notify_all(self):
+
+ class A(HasTraits):
+ a = Int()
+ b = Float()
+
+ a = A()
+ a.on_trait_change(self.notify1)
+ a.a = 0
+ self.assertEqual(len(self._notify1),0)
+ a.b = 0.0
+ self.assertEqual(len(self._notify1),0)
+ a.a = 10
+ self.assertTrue(('a',0,10) in self._notify1)
+ a.b = 10.0
+ self.assertTrue(('b',0.0,10.0) in self._notify1)
+ self.assertRaises(TraitError,setattr,a,'a','bad string')
+ self.assertRaises(TraitError,setattr,a,'b','bad string')
+ self._notify1 = []
+ a.on_trait_change(self.notify1,remove=True)
+ a.a = 20
+ a.b = 20.0
+ self.assertEqual(len(self._notify1),0)
+
+ def test_notify_one(self):
+
+ class A(HasTraits):
+ a = Int()
+ b = Float()
+
+ a = A()
+ a.on_trait_change(self.notify1, 'a')
+ a.a = 0
+ self.assertEqual(len(self._notify1),0)
+ a.a = 10
+ self.assertTrue(('a',0,10) in self._notify1)
+ self.assertRaises(TraitError,setattr,a,'a','bad string')
+
+ def test_subclass(self):
+
+ class A(HasTraits):
+ a = Int()
+
+ class B(A):
+ b = Float()
+
+ b = B()
+ self.assertEqual(b.a,0)
+ self.assertEqual(b.b,0.0)
+ b.a = 100
+ b.b = 100.0
+ self.assertEqual(b.a,100)
+ self.assertEqual(b.b,100.0)
+
+ def test_notify_subclass(self):
+
+ class A(HasTraits):
+ a = Int()
+
+ class B(A):
+ b = Float()
+
+ b = B()
+ b.on_trait_change(self.notify1, 'a')
+ b.on_trait_change(self.notify2, 'b')
+ b.a = 0
+ b.b = 0.0
+ self.assertEqual(len(self._notify1),0)
+ self.assertEqual(len(self._notify2),0)
+ b.a = 10
+ b.b = 10.0
+ self.assertTrue(('a',0,10) in self._notify1)
+ self.assertTrue(('b',0.0,10.0) in self._notify2)
+
+ def test_static_notify(self):
+
+ class A(HasTraits):
+ a = Int()
+ _notify1 = []
+ def _a_changed(self, name, old, new):
+ self._notify1.append((name, old, new))
+
+ a = A()
+ a.a = 0
+ # This is broken!!!
+ self.assertEqual(len(a._notify1),0)
+ a.a = 10
+ self.assertTrue(('a',0,10) in a._notify1)
+
+ class B(A):
+ b = Float()
+ _notify2 = []
+ def _b_changed(self, name, old, new):
+ self._notify2.append((name, old, new))
+
+ b = B()
+ b.a = 10
+ b.b = 10.0
+ self.assertTrue(('a',0,10) in b._notify1)
+ self.assertTrue(('b',0.0,10.0) in b._notify2)
+
+ def test_notify_args(self):
+
+ def callback0():
+ self.cb = ()
+ def callback1(name):
+ self.cb = (name,)
+ def callback2(name, new):
+ self.cb = (name, new)
+ def callback3(name, old, new):
+ self.cb = (name, old, new)
+ def callback4(name, old, new, obj):
+ self.cb = (name, old, new, obj)
+
+ class A(HasTraits):
+ a = Int()
+
+ a = A()
+ a.on_trait_change(callback0, 'a')
+ a.a = 10
+ self.assertEqual(self.cb,())
+ a.on_trait_change(callback0, 'a', remove=True)
+
+ a.on_trait_change(callback1, 'a')
+ a.a = 100
+ self.assertEqual(self.cb,('a',))
+ a.on_trait_change(callback1, 'a', remove=True)
+
+ a.on_trait_change(callback2, 'a')
+ a.a = 1000
+ self.assertEqual(self.cb,('a',1000))
+ a.on_trait_change(callback2, 'a', remove=True)
+
+ a.on_trait_change(callback3, 'a')
+ a.a = 10000
+ self.assertEqual(self.cb,('a',1000,10000))
+ a.on_trait_change(callback3, 'a', remove=True)
+
+ a.on_trait_change(callback4, 'a')
+ a.a = 100000
+ self.assertEqual(self.cb,('a',10000,100000,a))
+ self.assertEqual(len(a._trait_notifiers['a']['change']), 1)
+ a.on_trait_change(callback4, 'a', remove=True)
+
+ self.assertEqual(len(a._trait_notifiers['a']['change']), 0)
+
+ def test_notify_only_once(self):
+
+ class A(HasTraits):
+ listen_to = ['a']
+
+ a = Int(0)
+ b = 0
+
+ def __init__(self, **kwargs):
+ super(A, self).__init__(**kwargs)
+ self.on_trait_change(self.listener1, ['a'])
+
+ def listener1(self, name, old, new):
+ self.b += 1
+
+ class B(A):
+
+ c = 0
+ d = 0
+
+ def __init__(self, **kwargs):
+ super(B, self).__init__(**kwargs)
+ self.on_trait_change(self.listener2)
+
+ def listener2(self, name, old, new):
+ self.c += 1
+
+ def _a_changed(self, name, old, new):
+ self.d += 1
+
+ b = B()
+ b.a += 1
+ self.assertEqual(b.b, b.c)
+ self.assertEqual(b.b, b.d)
+ b.a += 1
+ self.assertEqual(b.b, b.c)
+ self.assertEqual(b.b, b.d)
+
+class TestObserveDecorator(TestCase):
+
+ def setUp(self):
+ self._notify1 = []
+ self._notify2 = []
+
+ def notify1(self, change):
+ self._notify1.append(change)
+
+ def notify2(self, change):
+ self._notify2.append(change)
+
+ def test_notify_all(self):
+
+ class A(HasTraits):
+ a = Int()
+ b = Float()
+
+ a = A()
+ a.observe(self.notify1)
+ a.a = 0
+ self.assertEqual(len(self._notify1),0)
+ a.b = 0.0
+ self.assertEqual(len(self._notify1),0)
+ a.a = 10
+ change = change_dict('a', 0, 10, a, 'change')
+ self.assertTrue(change in self._notify1)
+ a.b = 10.0
+ change = change_dict('b', 0.0, 10.0, a, 'change')
+ self.assertTrue(change in self._notify1)
+ self.assertRaises(TraitError,setattr,a,'a','bad string')
+ self.assertRaises(TraitError,setattr,a,'b','bad string')
+ self._notify1 = []
+ a.unobserve(self.notify1)
+ a.a = 20
+ a.b = 20.0
+ self.assertEqual(len(self._notify1),0)
+
+ def test_notify_one(self):
+
+ class A(HasTraits):
+ a = Int()
+ b = Float()
+
+ a = A()
+ a.observe(self.notify1, 'a')
+ a.a = 0
+ self.assertEqual(len(self._notify1),0)
+ a.a = 10
+ change = change_dict('a', 0, 10, a, 'change')
+ self.assertTrue(change in self._notify1)
+ self.assertRaises(TraitError,setattr,a,'a','bad string')
+
+ def test_subclass(self):
+
+ class A(HasTraits):
+ a = Int()
+
+ class B(A):
+ b = Float()
+
+ b = B()
+ self.assertEqual(b.a,0)
+ self.assertEqual(b.b,0.0)
+ b.a = 100
+ b.b = 100.0
+ self.assertEqual(b.a,100)
+ self.assertEqual(b.b,100.0)
+
+ def test_notify_subclass(self):
+
+ class A(HasTraits):
+ a = Int()
+
+ class B(A):
+ b = Float()
+
+ b = B()
+ b.observe(self.notify1, 'a')
+ b.observe(self.notify2, 'b')
+ b.a = 0
+ b.b = 0.0
+ self.assertEqual(len(self._notify1),0)
+ self.assertEqual(len(self._notify2),0)
+ b.a = 10
+ b.b = 10.0
+ change = change_dict('a', 0, 10, b, 'change')
+ self.assertTrue(change in self._notify1)
+ change = change_dict('b', 0.0, 10.0, b, 'change')
+ self.assertTrue(change in self._notify2)
+
+ def test_static_notify(self):
+
+ class A(HasTraits):
+ a = Int()
+ b = Int()
+ _notify1 = []
+ _notify_any = []
+
+ @observe('a')
+ def _a_changed(self, change):
+ self._notify1.append(change)
+
+ @observe(All)
+ def _any_changed(self, change):
+ self._notify_any.append(change)
+
+ a = A()
+ a.a = 0
+ self.assertEqual(len(a._notify1),0)
+ a.a = 10
+ change = change_dict('a', 0, 10, a, 'change')
+ self.assertTrue(change in a._notify1)
+ a.b = 1
+ self.assertEqual(len(a._notify_any), 2)
+ change = change_dict('b', 0, 1, a, 'change')
+ self.assertTrue(change in a._notify_any)
+
+ class B(A):
+ b = Float()
+ _notify2 = []
+ @observe('b')
+ def _b_changed(self, change):
+ self._notify2.append(change)
+
+ b = B()
+ b.a = 10
+ b.b = 10.0
+ change = change_dict('a', 0, 10, b, 'change')
+ self.assertTrue(change in b._notify1)
+ change = change_dict('b', 0.0, 10.0, b, 'change')
+ self.assertTrue(change in b._notify2)
+
+ def test_notify_args(self):
+
+ def callback0():
+ self.cb = ()
+ def callback1(change):
+ self.cb = change
+
+ class A(HasTraits):
+ a = Int()
+
+ a = A()
+ a.on_trait_change(callback0, 'a')
+ a.a = 10
+ self.assertEqual(self.cb,())
+ a.unobserve(callback0, 'a')
+
+ a.observe(callback1, 'a')
+ a.a = 100
+ change = change_dict('a', 10, 100, a, 'change')
+ self.assertEqual(self.cb, change)
+ self.assertEqual(len(a._trait_notifiers['a']['change']), 1)
+ a.unobserve(callback1, 'a')
+
+ self.assertEqual(len(a._trait_notifiers['a']['change']), 0)
+
+ def test_notify_only_once(self):
+
+ class A(HasTraits):
+ listen_to = ['a']
+
+ a = Int(0)
+ b = 0
+
+ def __init__(self, **kwargs):
+ super(A, self).__init__(**kwargs)
+ self.observe(self.listener1, ['a'])
+
+ def listener1(self, change):
+ self.b += 1
+
+ class B(A):
+
+ c = 0
+ d = 0
+
+ def __init__(self, **kwargs):
+ super(B, self).__init__(**kwargs)
+ self.observe(self.listener2)
+
+ def listener2(self, change):
+ self.c += 1
+
+ @observe('a')
+ def _a_changed(self, change):
+ self.d += 1
+
+ b = B()
+ b.a += 1
+ self.assertEqual(b.b, b.c)
+ self.assertEqual(b.b, b.d)
+ b.a += 1
+ self.assertEqual(b.b, b.c)
+ self.assertEqual(b.b, b.d)
+
+
+class TestHasTraits(TestCase):
+
+ def test_trait_names(self):
+ class A(HasTraits):
+ i = Int()
+ f = Float()
+ a = A()
+ self.assertEqual(sorted(a.trait_names()),['f','i'])
+ self.assertEqual(sorted(A.class_trait_names()),['f','i'])
+ self.assertTrue(a.has_trait('f'))
+ self.assertFalse(a.has_trait('g'))
+
+ def test_trait_metadata_deprecated(self):
+ with expected_warnings(['metadata should be set using the \.tag\(\) method']):
+ class A(HasTraits):
+ i = Int(config_key='MY_VALUE')
+ a = A()
+ self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
+
+ def test_trait_metadata(self):
+ class A(HasTraits):
+ i = Int().tag(config_key='MY_VALUE')
+ a = A()
+ self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
+
+ def test_trait_metadata_default(self):
+ class A(HasTraits):
+ i = Int()
+ a = A()
+ self.assertEqual(a.trait_metadata('i', 'config_key'), None)
+ self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
+
+ def test_traits(self):
+ class A(HasTraits):
+ i = Int()
+ f = Float()
+ a = A()
+ self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
+ self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
+
+ def test_traits_metadata(self):
+ class A(HasTraits):
+ i = Int().tag(config_key='VALUE1', other_thing='VALUE2')
+ f = Float().tag(config_key='VALUE3', other_thing='VALUE2')
+ j = Int(0)
+ a = A()
+ self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
+ traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
+ self.assertEqual(traits, dict(i=A.i))
+
+ # This passes, but it shouldn't because I am replicating a bug in
+ # traits.
+ traits = a.traits(config_key=lambda v: True)
+ self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
+
+ def test_traits_metadata_deprecated(self):
+ with expected_warnings(['metadata should be set using the \.tag\(\) method']*2):
+ class A(HasTraits):
+ i = Int(config_key='VALUE1', other_thing='VALUE2')
+ f = Float(config_key='VALUE3', other_thing='VALUE2')
+ j = Int(0)
+ a = A()
+ self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
+ traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
+ self.assertEqual(traits, dict(i=A.i))
+
+ # This passes, but it shouldn't because I am replicating a bug in
+ # traits.
+ traits = a.traits(config_key=lambda v: True)
+ self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
+
+
+ def test_init(self):
+ class A(HasTraits):
+ i = Int()
+ x = Float()
+ a = A(i=1, x=10.0)
+ self.assertEqual(a.i, 1)
+ self.assertEqual(a.x, 10.0)
+
+ def test_positional_args(self):
+ class A(HasTraits):
+ i = Int(0)
+ def __init__(self, i):
+ super(A, self).__init__()
+ self.i = i
+
+ a = A(5)
+ self.assertEqual(a.i, 5)
+ # should raise TypeError if no positional arg given
+ self.assertRaises(TypeError, A)
+
+#-----------------------------------------------------------------------------
+# Tests for specific trait types
+#-----------------------------------------------------------------------------
+
+
+class TestType(TestCase):
+
+ def test_default(self):
+
+ class B(object): pass
+ class A(HasTraits):
+ klass = Type(allow_none=True)
+
+ a = A()
+ self.assertEqual(a.klass, object)
+
+ a.klass = B
+ self.assertEqual(a.klass, B)
+ self.assertRaises(TraitError, setattr, a, 'klass', 10)
+
+ def test_default_options(self):
+
+ class B(object): pass
+ class C(B): pass
+ class A(HasTraits):
+ # Different possible combinations of options for default_value
+ # and klass. default_value=None is only valid with allow_none=True.
+ k1 = Type()
+ k2 = Type(None, allow_none=True)
+ k3 = Type(B)
+ k4 = Type(klass=B)
+ k5 = Type(default_value=None, klass=B, allow_none=True)
+ k6 = Type(default_value=C, klass=B)
+
+ self.assertIs(A.k1.default_value, object)
+ self.assertIs(A.k1.klass, object)
+ self.assertIs(A.k2.default_value, None)
+ self.assertIs(A.k2.klass, object)
+ self.assertIs(A.k3.default_value, B)
+ self.assertIs(A.k3.klass, B)
+ self.assertIs(A.k4.default_value, B)
+ self.assertIs(A.k4.klass, B)
+ self.assertIs(A.k5.default_value, None)
+ self.assertIs(A.k5.klass, B)
+ self.assertIs(A.k6.default_value, C)
+ self.assertIs(A.k6.klass, B)
+
+ a = A()
+ self.assertIs(a.k1, object)
+ self.assertIs(a.k2, None)
+ self.assertIs(a.k3, B)
+ self.assertIs(a.k4, B)
+ self.assertIs(a.k5, None)
+ self.assertIs(a.k6, C)
+
+ def test_value(self):
+
+ class B(object): pass
+ class C(object): pass
+ class A(HasTraits):
+ klass = Type(B)
+
+ a = A()
+ self.assertEqual(a.klass, B)
+ self.assertRaises(TraitError, setattr, a, 'klass', C)
+ self.assertRaises(TraitError, setattr, a, 'klass', object)
+ a.klass = B
+
+ def test_allow_none(self):
+
+ class B(object): pass
+ class C(B): pass
+ class A(HasTraits):
+ klass = Type(B)
+
+ a = A()
+ self.assertEqual(a.klass, B)
+ self.assertRaises(TraitError, setattr, a, 'klass', None)
+ a.klass = C
+ self.assertEqual(a.klass, C)
+
+ def test_validate_klass(self):
+
+ class A(HasTraits):
+ klass = Type('no strings allowed')
+
+ self.assertRaises(ImportError, A)
+
+ class A(HasTraits):
+ klass = Type('rub.adub.Duck')
+
+ self.assertRaises(ImportError, A)
+
+ def test_validate_default(self):
+
+ class B(object): pass
+ class A(HasTraits):
+ klass = Type('bad default', B)
+
+ self.assertRaises(ImportError, A)
+
+ class C(HasTraits):
+ klass = Type(None, B)
+
+ self.assertRaises(TraitError, C)
+
+ def test_str_klass(self):
+
+ class A(HasTraits):
+ klass = Type('ipython_genutils.ipstruct.Struct')
+
+ from ipython_genutils.ipstruct import Struct
+ a = A()
+ a.klass = Struct
+ self.assertEqual(a.klass, Struct)
+
+ self.assertRaises(TraitError, setattr, a, 'klass', 10)
+
+ def test_set_str_klass(self):
+
+ class A(HasTraits):
+ klass = Type()
+
+ a = A(klass='ipython_genutils.ipstruct.Struct')
+ from ipython_genutils.ipstruct import Struct
+ self.assertEqual(a.klass, Struct)
+
+class TestInstance(TestCase):
+
+ def test_basic(self):
+ class Foo(object): pass
+ class Bar(Foo): pass
+ class Bah(object): pass
+
+ class A(HasTraits):
+ inst = Instance(Foo, allow_none=True)
+
+ a = A()
+ self.assertTrue(a.inst is None)
+ a.inst = Foo()
+ self.assertTrue(isinstance(a.inst, Foo))
+ a.inst = Bar()
+ self.assertTrue(isinstance(a.inst, Foo))
+ self.assertRaises(TraitError, setattr, a, 'inst', Foo)
+ self.assertRaises(TraitError, setattr, a, 'inst', Bar)
+ self.assertRaises(TraitError, setattr, a, 'inst', Bah())
+
+ def test_default_klass(self):
+ class Foo(object): pass
+ class Bar(Foo): pass
+ class Bah(object): pass
+
+ class FooInstance(Instance):
+ klass = Foo
+
+ class A(HasTraits):
+ inst = FooInstance(allow_none=True)
+
+ a = A()
+ self.assertTrue(a.inst is None)
+ a.inst = Foo()
+ self.assertTrue(isinstance(a.inst, Foo))
+ a.inst = Bar()
+ self.assertTrue(isinstance(a.inst, Foo))
+ self.assertRaises(TraitError, setattr, a, 'inst', Foo)
+ self.assertRaises(TraitError, setattr, a, 'inst', Bar)
+ self.assertRaises(TraitError, setattr, a, 'inst', Bah())
+
+ def test_unique_default_value(self):
+ class Foo(object): pass
+ class A(HasTraits):
+ inst = Instance(Foo,(),{})
+
+ a = A()
+ b = A()
+ self.assertTrue(a.inst is not b.inst)
+
+ def test_args_kw(self):
+ class Foo(object):
+ def __init__(self, c): self.c = c
+ class Bar(object): pass
+ class Bah(object):
+ def __init__(self, c, d):
+ self.c = c; self.d = d
+
+ class A(HasTraits):
+ inst = Instance(Foo, (10,))
+ a = A()
+ self.assertEqual(a.inst.c, 10)
+
+ class B(HasTraits):
+ inst = Instance(Bah, args=(10,), kw=dict(d=20))
+ b = B()
+ self.assertEqual(b.inst.c, 10)
+ self.assertEqual(b.inst.d, 20)
+
+ class C(HasTraits):
+ inst = Instance(Foo, allow_none=True)
+ c = C()
+ self.assertTrue(c.inst is None)
+
+ def test_bad_default(self):
+ class Foo(object): pass
+
+ class A(HasTraits):
+ inst = Instance(Foo)
+
+ a = A()
+ with self.assertRaises(TraitError):
+ a.inst
+
+ def test_instance(self):
+ class Foo(object): pass
+
+ def inner():
+ class A(HasTraits):
+ inst = Instance(Foo())
+
+ self.assertRaises(TraitError, inner)
+
+
+class TestThis(TestCase):
+
+ def test_this_class(self):
+ class Foo(HasTraits):
+ this = This()
+
+ f = Foo()
+ self.assertEqual(f.this, None)
+ g = Foo()
+ f.this = g
+ self.assertEqual(f.this, g)
+ self.assertRaises(TraitError, setattr, f, 'this', 10)
+
+ def test_this_inst(self):
+ class Foo(HasTraits):
+ this = This()
+
+ f = Foo()
+ f.this = Foo()
+ self.assertTrue(isinstance(f.this, Foo))
+
+ def test_subclass(self):
+ class Foo(HasTraits):
+ t = This()
+ class Bar(Foo):
+ pass
+ f = Foo()
+ b = Bar()
+ f.t = b
+ b.t = f
+ self.assertEqual(f.t, b)
+ self.assertEqual(b.t, f)
+
+ def test_subclass_override(self):
+ class Foo(HasTraits):
+ t = This()
+ class Bar(Foo):
+ t = This()
+ f = Foo()
+ b = Bar()
+ f.t = b
+ self.assertEqual(f.t, b)
+ self.assertRaises(TraitError, setattr, b, 't', f)
+
+ def test_this_in_container(self):
+
+ class Tree(HasTraits):
+ value = Unicode()
+ leaves = List(This())
+
+ tree = Tree(
+ value='foo',
+ leaves=[Tree(value='bar'), Tree(value='buzz')]
+ )
+
+ with self.assertRaises(TraitError):
+ tree.leaves = [1, 2]
+
+class TraitTestBase(TestCase):
+ """A best testing class for basic trait types."""
+
+ def assign(self, value):
+ self.obj.value = value
+
+ def coerce(self, value):
+ return value
+
+ def test_good_values(self):
+ if hasattr(self, '_good_values'):
+ for value in self._good_values:
+ self.assign(value)
+ self.assertEqual(self.obj.value, self.coerce(value))
+
+ def test_bad_values(self):
+ if hasattr(self, '_bad_values'):
+ for value in self._bad_values:
+ try:
+ self.assertRaises(TraitError, self.assign, value)
+ except AssertionError:
+ assert False, value
+
+ def test_default_value(self):
+ if hasattr(self, '_default_value'):
+ self.assertEqual(self._default_value, self.obj.value)
+
+ def test_allow_none(self):
+ if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
+ None in self._bad_values):
+ trait=self.obj.traits()['value']
+ try:
+ trait.allow_none = True
+ self._bad_values.remove(None)
+ #skip coerce. Allow None casts None to None.
+ self.assign(None)
+ self.assertEqual(self.obj.value,None)
+ self.test_good_values()
+ self.test_bad_values()
+ finally:
+ #tear down
+ trait.allow_none = False
+ self._bad_values.append(None)
+
+ def tearDown(self):
+ # restore default value after tests, if set
+ if hasattr(self, '_default_value'):
+ self.obj.value = self._default_value
+
+
+class AnyTrait(HasTraits):
+
+ value = Any()
+
+class AnyTraitTest(TraitTestBase):
+
+ obj = AnyTrait()
+
+ _default_value = None
+ _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
+ _bad_values = []
+
+class UnionTrait(HasTraits):
+
+ value = Union([Type(), Bool()])
+
+class UnionTraitTest(TraitTestBase):
+
+ obj = UnionTrait(value='ipython_genutils.ipstruct.Struct')
+ _good_values = [int, float, True]
+ _bad_values = [[], (0,), 1j]
+
+class OrTrait(HasTraits):
+
+ value = Bool() | Unicode()
+
+class OrTraitTest(TraitTestBase):
+
+ obj = OrTrait()
+ _good_values = [True, False, 'ten']
+ _bad_values = [[], (0,), 1j]
+
+class IntTrait(HasTraits):
+
+ value = Int(99, min=-100)
+
+class TestInt(TraitTestBase):
+
+ obj = IntTrait()
+ _default_value = 99
+ _good_values = [10, -10]
+ _bad_values = ['ten', u'ten', [10], {'ten': 10}, (10,), None, 1j,
+ 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
+ u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', -200]
+ if not six.PY3:
+ _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
+
+
+class CIntTrait(HasTraits):
+ value = CInt('5')
+
+class TestCInt(TraitTestBase):
+ obj = CIntTrait()
+
+ _default_value = 5
+ _good_values = ['10', '-10', u'10', u'-10', 10, 10.0, -10.0, 10.1]
+ _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
+ None, 1j, '10.1', u'10.1']
+
+ def coerce(self, n):
+ return int(n)
+
+
+class MinBoundCIntTrait(HasTraits):
+ value = CInt('5', min=3)
+
+class TestMinBoundCInt(TestCInt):
+ obj = MinBoundCIntTrait()
+
+ _default_value = 5
+ _good_values = [3, 3.0, '3']
+ _bad_values = [2.6, 2, -3, -3.0]
+
+
+class LongTrait(HasTraits):
+
+ value = Long(99 if six.PY3 else long(99))
+
+class TestLong(TraitTestBase):
+
+ obj = LongTrait()
+
+ _default_value = 99 if six.PY3 else long(99)
+ _good_values = [10, -10]
+ _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
+ None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
+ '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
+ u'-10.1']
+ if not six.PY3:
+ # maxint undefined on py3, because int == long
+ _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
+ _bad_values.extend([[long(10)], (long(10),)])
+
+ @mark.skipif(six.PY3, reason="not relevant on py3")
+ def test_cast_small(self):
+ """Long casts ints to long"""
+ self.obj.value = 10
+ self.assertEqual(type(self.obj.value), long)
+
+
+class MinBoundLongTrait(HasTraits):
+ value = Long(99 if six.PY3 else long(99), min=5)
+
+class TestMinBoundLong(TraitTestBase):
+ obj = MinBoundLongTrait()
+
+ _default_value = 99 if six.PY3 else long(99)
+ _good_values = [5, 10]
+ _bad_values = [4, -10]
+
+
+class MaxBoundLongTrait(HasTraits):
+ value = Long(5 if six.PY3 else long(5), max=10)
+
+class TestMaxBoundLong(TraitTestBase):
+ obj = MaxBoundLongTrait()
+
+ _default_value = 5 if six.PY3 else long(5)
+ _good_values = [10, -2]
+ _bad_values = [11, 20]
+
+
+class CLongTrait(HasTraits):
+ value = CLong('5')
+
+class TestCLong(TraitTestBase):
+ obj = CLongTrait()
+
+ _default_value = 5 if six.PY3 else long(5)
+ _good_values = ['10', '-10', u'10', u'-10', 10, 10.0, -10.0, 10.1]
+ _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
+ None, 1j, '10.1', u'10.1']
+
+ def coerce(self, n):
+ return int(n) if six.PY3 else long(n)
+
+
+class MaxBoundCLongTrait(HasTraits):
+ value = CLong('5', max=10)
+
+class TestMaxBoundCLong(TestCLong):
+ obj = MaxBoundCLongTrait()
+
+ _default_value = 5 if six.PY3 else long(5)
+ _good_values = [10, '10', 10.3]
+ _bad_values = [11.0, '11']
+
+
+class IntegerTrait(HasTraits):
+ value = Integer(1)
+
+class TestInteger(TestLong):
+ obj = IntegerTrait()
+ _default_value = 1
+
+ def coerce(self, n):
+ return int(n)
+
+ @mark.skipif(six.PY3, reason="not relevant on py3")
+ def test_cast_small(self):
+ """Integer casts small longs to int"""
+
+ self.obj.value = long(100)
+ self.assertEqual(type(self.obj.value), int)
+
+
+class MinBoundIntegerTrait(HasTraits):
+ value = Integer(5, min=3)
+
+class TestMinBoundInteger(TraitTestBase):
+ obj = MinBoundIntegerTrait()
+
+ _default_value = 5
+ _good_values = 3, 20
+ _bad_values = [2, -10]
+
+
+class MaxBoundIntegerTrait(HasTraits):
+ value = Integer(1, max=3)
+
+class TestMaxBoundInteger(TraitTestBase):
+ obj = MaxBoundIntegerTrait()
+
+ _default_value = 1
+ _good_values = 3, -2
+ _bad_values = [4, 10]
+
+
+class FloatTrait(HasTraits):
+
+ value = Float(99.0, max=200.0)
+
+class TestFloat(TraitTestBase):
+
+ obj = FloatTrait()
+
+ _default_value = 99.0
+ _good_values = [10, -10, 10.1, -10.1]
+ _bad_values = ['ten', u'ten', [10], {'ten': 10}, (10,), None,
+ 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
+ u'-10', u'10L', u'-10L', u'10.1', u'-10.1', 201.0]
+ if not six.PY3:
+ _bad_values.extend([long(10), long(-10)])
+
+
+class CFloatTrait(HasTraits):
+
+ value = CFloat('99.0', max=200.0)
+
+class TestCFloat(TraitTestBase):
+
+ obj = CFloatTrait()
+
+ _default_value = 99.0
+ _good_values = [10, 10.0, 10.5, '10.0', '10', '-10', '10.0', u'10']
+ _bad_values = ['ten', u'ten', [10], {'ten': 10}, (10,), None, 1j,
+ 200.1, '200.1']
+
+ def coerce(self, v):
+ return float(v)
+
+
+class ComplexTrait(HasTraits):
+
+ value = Complex(99.0-99.0j)
+
+class TestComplex(TraitTestBase):
+
+ obj = ComplexTrait()
+
+ _default_value = 99.0-99.0j
+ _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
+ 10.1j, 10.1+10.1j, 10.1-10.1j]
+ _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
+ if not six.PY3:
+ _bad_values.extend([long(10), long(-10)])
+
+
+class BytesTrait(HasTraits):
+
+ value = Bytes(b'string')
+
+class TestBytes(TraitTestBase):
+
+ obj = BytesTrait()
+
+ _default_value = b'string'
+ _good_values = [b'10', b'-10', b'10L',
+ b'-10L', b'10.1', b'-10.1', b'string']
+ _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
+ ['ten'],{'ten': 10},(10,), None, u'string']
+ if not six.PY3:
+ _bad_values.extend([long(10), long(-10)])
+
+
+class UnicodeTrait(HasTraits):
+
+ value = Unicode(u'unicode')
+
+class TestUnicode(TraitTestBase):
+
+ obj = UnicodeTrait()
+
+ _default_value = u'unicode'
+ _good_values = ['10', '-10', '10L', '-10L', '10.1',
+ '-10.1', '', u'', 'string', u'string', u"€"]
+ _bad_values = [10, -10, 10.1, -10.1, 1j,
+ [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
+ if not six.PY3:
+ _bad_values.extend([long(10), long(-10)])
+
+
+class ObjectNameTrait(HasTraits):
+ value = ObjectName("abc")
+
+class TestObjectName(TraitTestBase):
+ obj = ObjectNameTrait()
+
+ _default_value = "abc"
+ _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
+ _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
+ None, object(), object]
+ if sys.version_info[0] < 3:
+ _bad_values.append(u"þ")
+ else:
+ _good_values.append(u"þ") # þ=1 is valid in Python 3 (PEP 3131).
+
+
+class DottedObjectNameTrait(HasTraits):
+ value = DottedObjectName("a.b")
+
+class TestDottedObjectName(TraitTestBase):
+ obj = DottedObjectNameTrait()
+
+ _default_value = "a.b"
+ _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
+ _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
+ if sys.version_info[0] < 3:
+ _bad_values.append(u"t.þ")
+ else:
+ _good_values.append(u"t.þ")
+
+
+class TCPAddressTrait(HasTraits):
+ value = TCPAddress()
+
+class TestTCPAddress(TraitTestBase):
+
+ obj = TCPAddressTrait()
+
+ _default_value = ('127.0.0.1',0)
+ _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
+ _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
+
+class ListTrait(HasTraits):
+
+ value = List(Int())
+
+class TestList(TraitTestBase):
+
+ obj = ListTrait()
+
+ _default_value = []
+ _good_values = [[], [1], list(range(10)), (1,2)]
+ _bad_values = [10, [1,'a'], 'a']
+
+ def coerce(self, value):
+ if value is not None:
+ value = list(value)
+ return value
+
+class Foo(object):
+ pass
+
+class NoneInstanceListTrait(HasTraits):
+
+ value = List(Instance(Foo))
+
+class TestNoneInstanceList(TraitTestBase):
+
+ obj = NoneInstanceListTrait()
+
+ _default_value = []
+ _good_values = [[Foo(), Foo()], []]
+ _bad_values = [[None], [Foo(), None]]
+
+
+class InstanceListTrait(HasTraits):
+
+ value = List(Instance(__name__+'.Foo'))
+
+class TestInstanceList(TraitTestBase):
+
+ obj = InstanceListTrait()
+
+ def test_klass(self):
+ """Test that the instance klass is properly assigned."""
+ self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
+
+ _default_value = []
+ _good_values = [[Foo(), Foo()], []]
+ _bad_values = [['1', 2,], '1', [Foo], None]
+
+class UnionListTrait(HasTraits):
+
+ value = List(Int() | Bool())
+
+class TestUnionListTrait(HasTraits):
+
+ obj = UnionListTrait()
+
+ _default_value = []
+ _good_values = [[True, 1], [False, True]]
+ _bad_values = [[1, 'True'], False]
+
+
+class LenListTrait(HasTraits):
+
+ value = List(Int(), [0], minlen=1, maxlen=2)
+
+class TestLenList(TraitTestBase):
+
+ obj = LenListTrait()
+
+ _default_value = [0]
+ _good_values = [[1], [1,2], (1,2)]
+ _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
+
+ def coerce(self, value):
+ if value is not None:
+ value = list(value)
+ return value
+
+class TupleTrait(HasTraits):
+
+ value = Tuple(Int(allow_none=True), default_value=(1,))
+
+class TestTupleTrait(TraitTestBase):
+
+ obj = TupleTrait()
+
+ _default_value = (1,)
+ _good_values = [(1,), (0,), [1]]
+ _bad_values = [10, (1, 2), ('a'), (), None]
+
+ def coerce(self, value):
+ if value is not None:
+ value = tuple(value)
+ return value
+
+ def test_invalid_args(self):
+ self.assertRaises(TypeError, Tuple, 5)
+ self.assertRaises(TypeError, Tuple, default_value='hello')
+ t = Tuple(Int(), CBytes(), default_value=(1,5))
+
+class LooseTupleTrait(HasTraits):
+
+ value = Tuple((1,2,3))
+
+class TestLooseTupleTrait(TraitTestBase):
+
+ obj = LooseTupleTrait()
+
+ _default_value = (1,2,3)
+ _good_values = [(1,), [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
+ _bad_values = [10, 'hello', {}, None]
+
+ def coerce(self, value):
+ if value is not None:
+ value = tuple(value)
+ return value
+
+ def test_invalid_args(self):
+ self.assertRaises(TypeError, Tuple, 5)
+ self.assertRaises(TypeError, Tuple, default_value='hello')
+ t = Tuple(Int(), CBytes(), default_value=(1,5))
+
+
+class MultiTupleTrait(HasTraits):
+
+ value = Tuple(Int(), Bytes(), default_value=[99,b'bottles'])
+
+class TestMultiTuple(TraitTestBase):
+
+ obj = MultiTupleTrait()
+
+ _default_value = (99,b'bottles')
+ _good_values = [(1,b'a'), (2,b'b')]
+ _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
+
+class CRegExpTrait(HasTraits):
+
+ value = CRegExp(r'')
+
+class TestCRegExp(TraitTestBase):
+
+ def coerce(self, value):
+ return re.compile(value)
+
+ obj = CRegExpTrait()
+
+ _default_value = re.compile(r'')
+ _good_values = [r'\d+', re.compile(r'\d+')]
+ _bad_values = ['(', None, ()]
+
+class DictTrait(HasTraits):
+ value = Dict()
+
+def test_dict_assignment():
+ d = dict()
+ c = DictTrait()
+ c.value = d
+ d['a'] = 5
+ assert d == c.value
+ assert c.value is d
+
+
+class UniformlyValidatedDictTrait(HasTraits):
+
+ value = Dict(trait=Unicode(),
+ default_value={'foo': '1'})
+
+
+class TestInstanceUniformlyValidatedDict(TraitTestBase):
+
+ obj = UniformlyValidatedDictTrait()
+
+ _default_value = {'foo': '1'}
+ _good_values = [{'foo': '0', 'bar': '1'}]
+ _bad_values = [{'foo': 0, 'bar': '1'}]
+
+
+class KeyValidatedDictTrait(HasTraits):
+
+ value = Dict(traits={'foo': Int()},
+ default_value={'foo': 1})
+
+
+class TestInstanceKeyValidatedDict(TraitTestBase):
+
+ obj = KeyValidatedDictTrait()
+
+ _default_value = {'foo': 1}
+ _good_values = [{'foo': 0, 'bar': '1'}, {'foo': 0, 'bar': 1}]
+ _bad_values = [{'foo': '0', 'bar': '1'}]
+
+
+class FullyValidatedDictTrait(HasTraits):
+
+ value = Dict(trait=Unicode(),
+ traits={'foo': Int()},
+ default_value={'foo': 1})
+
+
+class TestInstanceFullyValidatedDict(TraitTestBase):
+
+ obj = FullyValidatedDictTrait()
+
+ _default_value = {'foo': 1}
+ _good_values = [{'foo': 0, 'bar': '1'}, {'foo': 1, 'bar': '2'}]
+ _bad_values = [{'foo': 0, 'bar': 1}, {'foo': '0', 'bar': '1'}]
+
+
+def test_dict_default_value():
+ """Check that the `{}` default value of the Dict traitlet constructor is
+ actually copied."""
+
+ class Foo(HasTraits):
+ d1 = Dict()
+ d2 = Dict()
+
+ foo = Foo()
+ assert foo.d1 == {}
+ assert foo.d2 == {}
+ assert foo.d1 is not foo.d2
+
+
+class TestValidationHook(TestCase):
+
+ def test_parity_trait(self):
+ """Verify that the early validation hook is effective"""
+
+ class Parity(HasTraits):
+
+ value = Int(0)
+ parity = Enum(['odd', 'even'], default_value='even')
+
+ @validate('value')
+ def _value_validate(self, proposal):
+ value = proposal['value']
+ if self.parity == 'even' and value % 2:
+ raise TraitError('Expected an even number')
+ if self.parity == 'odd' and (value % 2 == 0):
+ raise TraitError('Expected an odd number')
+ return value
+
+ u = Parity()
+ u.parity = 'odd'
+ u.value = 1 # OK
+ with self.assertRaises(TraitError):
+ u.value = 2 # Trait Error
+
+ u.parity = 'even'
+ u.value = 2 # OK
+
+ def test_multiple_validate(self):
+ """Verify that we can register the same validator to multiple names"""
+
+ class OddEven(HasTraits):
+
+ odd = Int(1)
+ even = Int(0)
+
+ @validate('odd', 'even')
+ def check_valid(self, proposal):
+ if proposal['trait'].name == 'odd' and not proposal['value'] % 2:
+ raise TraitError('odd should be odd')
+ if proposal['trait'].name == 'even' and proposal['value'] % 2:
+ raise TraitError('even should be even')
+
+ u = OddEven()
+ u.odd = 3 # OK
+ with self.assertRaises(TraitError):
+ u.odd = 2 # Trait Error
+
+ u.even = 2 # OK
+ with self.assertRaises(TraitError):
+ u.even = 3 # Trait Error
+
+
+
+class TestLink(TestCase):
+
+ def test_connect_same(self):
+ """Verify two traitlets of the same type can be linked together using link."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ a = A(value=9)
+ b = A(value=8)
+
+ # Conenct the two classes.
+ c = link((a, 'value'), (b, 'value'))
+
+ # Make sure the values are the same at the point of linking.
+ self.assertEqual(a.value, b.value)
+
+ # Change one of the values to make sure they stay in sync.
+ a.value = 5
+ self.assertEqual(a.value, b.value)
+ b.value = 6
+ self.assertEqual(a.value, b.value)
+
+ def test_link_different(self):
+ """Verify two traitlets of different types can be linked together using link."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ class B(HasTraits):
+ count = Int()
+ a = A(value=9)
+ b = B(count=8)
+
+ # Conenct the two classes.
+ c = link((a, 'value'), (b, 'count'))
+
+ # Make sure the values are the same at the point of linking.
+ self.assertEqual(a.value, b.count)
+
+ # Change one of the values to make sure they stay in sync.
+ a.value = 5
+ self.assertEqual(a.value, b.count)
+ b.count = 4
+ self.assertEqual(a.value, b.count)
+
+ def test_unlink(self):
+ """Verify two linked traitlets can be unlinked."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ a = A(value=9)
+ b = A(value=8)
+
+ # Connect the two classes.
+ c = link((a, 'value'), (b, 'value'))
+ a.value = 4
+ c.unlink()
+
+ # Change one of the values to make sure they don't stay in sync.
+ a.value = 5
+ self.assertNotEqual(a.value, b.value)
+
+ def test_callbacks(self):
+ """Verify two linked traitlets have their callbacks called once."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ class B(HasTraits):
+ count = Int()
+ a = A(value=9)
+ b = B(count=8)
+
+ # Register callbacks that count.
+ callback_count = []
+ def a_callback(name, old, new):
+ callback_count.append('a')
+ a.on_trait_change(a_callback, 'value')
+ def b_callback(name, old, new):
+ callback_count.append('b')
+ b.on_trait_change(b_callback, 'count')
+
+ # Connect the two classes.
+ c = link((a, 'value'), (b, 'count'))
+
+ # Make sure b's count was set to a's value once.
+ self.assertEqual(''.join(callback_count), 'b')
+ del callback_count[:]
+
+ # Make sure a's value was set to b's count once.
+ b.count = 5
+ self.assertEqual(''.join(callback_count), 'ba')
+ del callback_count[:]
+
+ # Make sure b's count was set to a's value once.
+ a.value = 4
+ self.assertEqual(''.join(callback_count), 'ab')
+ del callback_count[:]
+
+class TestDirectionalLink(TestCase):
+ def test_connect_same(self):
+ """Verify two traitlets of the same type can be linked together using directional_link."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ a = A(value=9)
+ b = A(value=8)
+
+ # Conenct the two classes.
+ c = directional_link((a, 'value'), (b, 'value'))
+
+ # Make sure the values are the same at the point of linking.
+ self.assertEqual(a.value, b.value)
+
+ # Change one the value of the source and check that it synchronizes the target.
+ a.value = 5
+ self.assertEqual(b.value, 5)
+ # Change one the value of the target and check that it has no impact on the source
+ b.value = 6
+ self.assertEqual(a.value, 5)
+
+ def test_tranform(self):
+ """Test transform link."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ a = A(value=9)
+ b = A(value=8)
+
+ # Conenct the two classes.
+ c = directional_link((a, 'value'), (b, 'value'), lambda x: 2 * x)
+
+ # Make sure the values are correct at the point of linking.
+ self.assertEqual(b.value, 2 * a.value)
+
+ # Change one the value of the source and check that it modifies the target.
+ a.value = 5
+ self.assertEqual(b.value, 10)
+ # Change one the value of the target and check that it has no impact on the source
+ b.value = 6
+ self.assertEqual(a.value, 5)
+
+ def test_link_different(self):
+ """Verify two traitlets of different types can be linked together using link."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ class B(HasTraits):
+ count = Int()
+ a = A(value=9)
+ b = B(count=8)
+
+ # Conenct the two classes.
+ c = directional_link((a, 'value'), (b, 'count'))
+
+ # Make sure the values are the same at the point of linking.
+ self.assertEqual(a.value, b.count)
+
+ # Change one the value of the source and check that it synchronizes the target.
+ a.value = 5
+ self.assertEqual(b.count, 5)
+ # Change one the value of the target and check that it has no impact on the source
+ b.value = 6
+ self.assertEqual(a.value, 5)
+
+ def test_unlink(self):
+ """Verify two linked traitlets can be unlinked."""
+
+ # Create two simple classes with Int traitlets.
+ class A(HasTraits):
+ value = Int()
+ a = A(value=9)
+ b = A(value=8)
+
+ # Connect the two classes.
+ c = directional_link((a, 'value'), (b, 'value'))
+ a.value = 4
+ c.unlink()
+
+ # Change one of the values to make sure they don't stay in sync.
+ a.value = 5
+ self.assertNotEqual(a.value, b.value)
+
+class Pickleable(HasTraits):
+
+ i = Int()
+ @observe('i')
+ def _i_changed(self, change): pass
+ @validate('i')
+ def _i_validate(self, commit):
+ return commit['value']
+
+ j = Int()
+
+ def __init__(self):
+ with self.hold_trait_notifications():
+ self.i = 1
+ self.on_trait_change(self._i_changed, 'i')
+
+def test_pickle_hastraits():
+ c = Pickleable()
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ p = pickle.dumps(c, protocol)
+ c2 = pickle.loads(p)
+ assert c2.i == c.i
+ assert c2.j == c.j
+
+ c.i = 5
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ p = pickle.dumps(c, protocol)
+ c2 = pickle.loads(p)
+ assert c2.i == c.i
+ assert c2.j == c.j
+
+
+def test_hold_trait_notifications():
+ changes = []
+
+ class Test(HasTraits):
+ a = Integer(0)
+ b = Integer(0)
+
+ def _a_changed(self, name, old, new):
+ changes.append((old, new))
+
+ def _b_validate(self, value, trait):
+ if value != 0:
+ raise TraitError('Only 0 is a valid value')
+ return value
+
+ # Test context manager and nesting
+ t = Test()
+ with t.hold_trait_notifications():
+ with t.hold_trait_notifications():
+ t.a = 1
+ assert t.a == 1
+ assert changes == []
+ t.a = 2
+ assert t.a == 2
+ with t.hold_trait_notifications():
+ t.a = 3
+ assert t.a == 3
+ assert changes == []
+ t.a = 4
+ assert t.a == 4
+ assert changes == []
+ t.a = 4
+ assert t.a == 4
+ assert changes == []
+
+ assert changes == [(0, 4)]
+ # Test roll-back
+ try:
+ with t.hold_trait_notifications():
+ t.b = 1 # raises a Trait error
+ except:
+ pass
+ assert t.b == 0
+
+
+class RollBack(HasTraits):
+ bar = Int()
+ def _bar_validate(self, value, trait):
+ if value:
+ raise TraitError('foobar')
+ return value
+
+
+class TestRollback(TestCase):
+
+ def test_roll_back(self):
+
+ def assign_rollback():
+ RollBack(bar=1)
+
+ self.assertRaises(TraitError, assign_rollback)
+
+
+class CacheModification(HasTraits):
+ foo = Int()
+ bar = Int()
+
+ def _bar_validate(self, value, trait):
+ self.foo = value
+ return value
+
+ def _foo_validate(self, value, trait):
+ self.bar = value
+ return value
+
+
+def test_cache_modification():
+ CacheModification(foo=1)
+ CacheModification(bar=1)
+
+
+class OrderTraits(HasTraits):
+ notified = Dict()
+
+ a = Unicode()
+ b = Unicode()
+ c = Unicode()
+ d = Unicode()
+ e = Unicode()
+ f = Unicode()
+ g = Unicode()
+ h = Unicode()
+ i = Unicode()
+ j = Unicode()
+ k = Unicode()
+ l = Unicode()
+
+ def _notify(self, name, old, new):
+ """check the value of all traits when each trait change is triggered
+
+ This verifies that the values are not sensitive
+ to dict ordering when loaded from kwargs
+ """
+ # check the value of the other traits
+ # when a given trait change notification fires
+ self.notified[name] = {
+ c: getattr(self, c) for c in 'abcdefghijkl'
+ }
+
+ def __init__(self, **kwargs):
+ self.on_trait_change(self._notify)
+ super(OrderTraits, self).__init__(**kwargs)
+
+def test_notification_order():
+ d = {c:c for c in 'abcdefghijkl'}
+ obj = OrderTraits()
+ assert obj.notified == {}
+ obj = OrderTraits(**d)
+ notifications = {
+ c: d for c in 'abcdefghijkl'
+ }
+ assert obj.notified == notifications
+
+
+
+###
+# Traits for Forward Declaration Tests
+###
+class ForwardDeclaredInstanceTrait(HasTraits):
+
+ value = ForwardDeclaredInstance('ForwardDeclaredBar', allow_none=True)
+
+class ForwardDeclaredTypeTrait(HasTraits):
+
+ value = ForwardDeclaredType('ForwardDeclaredBar', allow_none=True)
+
+class ForwardDeclaredInstanceListTrait(HasTraits):
+
+ value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
+
+class ForwardDeclaredTypeListTrait(HasTraits):
+
+ value = List(ForwardDeclaredType('ForwardDeclaredBar'))
+###
+# End Traits for Forward Declaration Tests
+###
+
+###
+# Classes for Forward Declaration Tests
+###
+class ForwardDeclaredBar(object):
+ pass
+
+class ForwardDeclaredBarSub(ForwardDeclaredBar):
+ pass
+###
+# End Classes for Forward Declaration Tests
+###
+
+###
+# Forward Declaration Tests
+###
+class TestForwardDeclaredInstanceTrait(TraitTestBase):
+
+ obj = ForwardDeclaredInstanceTrait()
+ _default_value = None
+ _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
+ _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
+
+class TestForwardDeclaredTypeTrait(TraitTestBase):
+
+ obj = ForwardDeclaredTypeTrait()
+ _default_value = None
+ _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
+ _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
+
+class TestForwardDeclaredInstanceList(TraitTestBase):
+
+ obj = ForwardDeclaredInstanceListTrait()
+
+ def test_klass(self):
+ """Test that the instance klass is properly assigned."""
+ self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
+
+ _default_value = []
+ _good_values = [
+ [ForwardDeclaredBar(), ForwardDeclaredBarSub()],
+ [],
+ ]
+ _bad_values = [
+ ForwardDeclaredBar(),
+ [ForwardDeclaredBar(), 3, None],
+ '1',
+ # Note that this is the type, not an instance.
+ [ForwardDeclaredBar],
+ [None],
+ None,
+ ]
+
+class TestForwardDeclaredTypeList(TraitTestBase):
+
+ obj = ForwardDeclaredTypeListTrait()
+
+ def test_klass(self):
+ """Test that the instance klass is properly assigned."""
+ self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
+
+ _default_value = []
+ _good_values = [
+ [ForwardDeclaredBar, ForwardDeclaredBarSub],
+ [],
+ ]
+ _bad_values = [
+ ForwardDeclaredBar,
+ [ForwardDeclaredBar, 3],
+ '1',
+ # Note that this is an instance, not the type.
+ [ForwardDeclaredBar()],
+ [None],
+ None,
+ ]
+###
+# End Forward Declaration Tests
+###
+
+class TestDynamicTraits(TestCase):
+
+ def setUp(self):
+ self._notify1 = []
+
+ def notify1(self, name, old, new):
+ self._notify1.append((name, old, new))
+
+ def test_notify_all(self):
+
+ class A(HasTraits):
+ pass
+
+ a = A()
+ self.assertTrue(not hasattr(a, 'x'))
+ self.assertTrue(not hasattr(a, 'y'))
+
+ # Dynamically add trait x.
+ a.add_traits(x=Int())
+ self.assertTrue(hasattr(a, 'x'))
+ self.assertTrue(isinstance(a, (A, )))
+
+ # Dynamically add trait y.
+ a.add_traits(y=Float())
+ self.assertTrue(hasattr(a, 'y'))
+ self.assertTrue(isinstance(a, (A, )))
+ self.assertEqual(a.__class__.__name__, A.__name__)
+
+ # Create a new instance and verify that x and y
+ # aren't defined.
+ b = A()
+ self.assertTrue(not hasattr(b, 'x'))
+ self.assertTrue(not hasattr(b, 'y'))
+
+ # Verify that notification works like normal.
+ a.on_trait_change(self.notify1)
+ a.x = 0
+ self.assertEqual(len(self._notify1), 0)
+ a.y = 0.0
+ self.assertEqual(len(self._notify1), 0)
+ a.x = 10
+ self.assertTrue(('x', 0, 10) in self._notify1)
+ a.y = 10.0
+ self.assertTrue(('y', 0.0, 10.0) in self._notify1)
+ self.assertRaises(TraitError, setattr, a, 'x', 'bad string')
+ self.assertRaises(TraitError, setattr, a, 'y', 'bad string')
+ self._notify1 = []
+ a.on_trait_change(self.notify1, remove=True)
+ a.x = 20
+ a.y = 20.0
+ self.assertEqual(len(self._notify1), 0)
+
+
+def test_enum_no_default():
+ class C(HasTraits):
+ t = Enum(['a', 'b'])
+
+ c = C()
+ c.t = 'a'
+ assert c.t == 'a'
+
+ c = C()
+
+ with pytest.raises(TraitError):
+ t = c.t
+
+ c = C(t='b')
+ assert c.t == 'b'
+
+
+def test_default_value_repr():
+ class C(HasTraits):
+ t = Type('traitlets.HasTraits')
+ t2 = Type(HasTraits)
+ n = Integer(0)
+ lis = List()
+ d = Dict()
+
+ assert C.t.default_value_repr() == "'traitlets.HasTraits'"
+ assert C.t2.default_value_repr() == "'traitlets.traitlets.HasTraits'"
+ assert C.n.default_value_repr() == '0'
+ assert C.lis.default_value_repr() == '[]'
+ assert C.d.default_value_repr() == '{}'
+
+
+class TransitionalClass(HasTraits):
+
+ d = Any()
+ @default('d')
+ def _d_default(self):
+ return TransitionalClass
+
+ parent_super = False
+ calls_super = Integer(0)
+
+ @default('calls_super')
+ def _calls_super_default(self):
+ return -1
+
+ @observe('calls_super')
+ @observe_compat
+ def _calls_super_changed(self, change):
+ self.parent_super = change
+
+ parent_override = False
+ overrides = Integer(0)
+
+ @observe('overrides')
+ @observe_compat
+ def _overrides_changed(self, change):
+ self.parent_override = change
+
+
+class SubClass(TransitionalClass):
+ def _d_default(self):
+ return SubClass
+
+ subclass_super = False
+ def _calls_super_changed(self, name, old, new):
+ self.subclass_super = True
+ super(SubClass, self)._calls_super_changed(name, old, new)
+
+ subclass_override = False
+ def _overrides_changed(self, name, old, new):
+ self.subclass_override = True
+
+
+def test_subclass_compat():
+ obj = SubClass()
+ obj.calls_super = 5
+ assert obj.parent_super
+ assert obj.subclass_super
+ obj.overrides = 5
+ assert obj.subclass_override
+ assert not obj.parent_override
+ assert obj.d is SubClass
+
+
+class DefinesHandler(HasTraits):
+ parent_called = False
+
+ trait = Integer()
+ @observe('trait')
+ def handler(self, change):
+ self.parent_called = True
+
+
+class OverridesHandler(DefinesHandler):
+ child_called = False
+
+ @observe('trait')
+ def handler(self, change):
+ self.child_called = True
+
+
+def test_subclass_override_observer():
+ obj = OverridesHandler()
+ obj.trait = 5
+ assert obj.child_called
+ assert not obj.parent_called
+
+
+class DoesntRegisterHandler(DefinesHandler):
+ child_called = False
+
+ def handler(self, change):
+ self.child_called = True
+
+
+def test_subclass_override_not_registered():
+ """Subclass that overrides observer and doesn't re-register unregisters both"""
+ obj = DoesntRegisterHandler()
+ obj.trait = 5
+ assert not obj.child_called
+ assert not obj.parent_called
+
+
+class AddsHandler(DefinesHandler):
+ child_called = False
+
+ @observe('trait')
+ def child_handler(self, change):
+ self.child_called = True
+
+def test_subclass_add_observer():
+ obj = AddsHandler()
+ obj.trait = 5
+ assert obj.child_called
+ assert obj.parent_called
+
+
+def test_observe_iterables():
+
+ class C(HasTraits):
+ i = Integer()
+ s = Unicode()
+
+ c = C()
+ recorded = {}
+ def record(change):
+ recorded['change'] = change
+
+ # observe with names=set
+ c.observe(record, names={'i', 's'})
+ c.i = 5
+ assert recorded['change'].name == 'i'
+ assert recorded['change'].new == 5
+ c.s = 'hi'
+ assert recorded['change'].name == 's'
+ assert recorded['change'].new == 'hi'
+
+ # observe with names=custom container with iter, contains
+ class MyContainer(object):
+ def __init__(self, container):
+ self.container = container
+
+ def __iter__(self):
+ return iter(self.container)
+
+ def __contains__(self, key):
+ return key in self.container
+
+ c.observe(record, names=MyContainer({'i', 's'}))
+ c.i = 10
+ assert recorded['change'].name == 'i'
+ assert recorded['change'].new == 10
+ c.s = 'ok'
+ assert recorded['change'].name == 's'
+ assert recorded['change'].new == 'ok'
+
+
+def test_super_args():
+ class SuperRecorder(object):
+ def __init__(self, *args, **kwargs):
+ self.super_args = args
+ self.super_kwargs = kwargs
+
+ class SuperHasTraits(HasTraits, SuperRecorder):
+ i = Integer()
+
+ obj = SuperHasTraits('a1', 'a2', b=10, i=5, c='x')
+ assert obj.i == 5
+ assert not hasattr(obj, 'b')
+ assert not hasattr(obj, 'c')
+ assert obj.super_args == ('a1' , 'a2')
+ assert obj.super_kwargs == {'b': 10 , 'c': 'x'}
+
+def test_super_bad_args():
+ class SuperHasTraits(HasTraits):
+ a = Integer()
+
+ if sys.version_info < (3,):
+ # Legacy Python, object.__init__ warns itself, instead of raising
+ w = ['object.__init__']
+ else:
+ w = ["Passing unrecoginized arguments"]
+ with expected_warnings(w):
+ obj = SuperHasTraits(a=1, b=2)
+ assert obj.a == 1
+ assert not hasattr(obj, 'b')
diff --git a/contrib/python/traitlets/py2/traitlets/tests/test_traitlets_enum.py b/contrib/python/traitlets/py2/traitlets/tests/test_traitlets_enum.py
new file mode 100644
index 0000000000..82259ae6c5
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/tests/test_traitlets_enum.py
@@ -0,0 +1,181 @@
+# -*- coding: UTF-8 -*-
+# pylint: disable=missing-docstring, too-few-public-methods
+"""
+Test the trait-type ``UseEnum``.
+"""
+
+import unittest
+import enum
+from ipython_genutils.py3compat import string_types
+from traitlets import HasTraits, TraitError, UseEnum
+
+
+# -----------------------------------------------------------------------------
+# TEST SUPPORT:
+# -----------------------------------------------------------------------------
+class Color(enum.Enum):
+ red = 1
+ green = 2
+ blue = 3
+ yellow = 4
+
+class OtherColor(enum.Enum):
+ red = 0
+ green = 1
+
+
+# -----------------------------------------------------------------------------
+# TESTSUITE:
+# -----------------------------------------------------------------------------
+class TestUseEnum(unittest.TestCase):
+ # pylint: disable=invalid-name
+
+ class Example(HasTraits):
+ color = UseEnum(Color, help="Color enum")
+
+ def test_assign_enum_value(self):
+ example = self.Example()
+ example.color = Color.green
+ self.assertEqual(example.color, Color.green)
+
+ def test_assign_all_enum_values(self):
+ # pylint: disable=no-member
+ enum_values = [value for value in Color.__members__.values()]
+ for value in enum_values:
+ self.assertIsInstance(value, Color)
+ example = self.Example()
+ example.color = value
+ self.assertEqual(example.color, value)
+ self.assertIsInstance(value, Color)
+
+ def test_assign_enum_value__with_other_enum_raises_error(self):
+ example = self.Example()
+ with self.assertRaises(TraitError):
+ example.color = OtherColor.green
+
+ def test_assign_enum_name_1(self):
+ # -- CONVERT: string => Enum value (item)
+ example = self.Example()
+ example.color = "red"
+ self.assertEqual(example.color, Color.red)
+
+ def test_assign_enum_value_name(self):
+ # -- CONVERT: string => Enum value (item)
+ # pylint: disable=no-member
+ enum_names = [enum_val.name for enum_val in Color.__members__.values()]
+ for value in enum_names:
+ self.assertIsInstance(value, string_types)
+ example = self.Example()
+ enum_value = Color.__members__.get(value)
+ example.color = value
+ self.assertIs(example.color, enum_value)
+ self.assertEqual(example.color.name, value)
+
+ def test_assign_scoped_enum_value_name(self):
+ # -- CONVERT: string => Enum value (item)
+ scoped_names = ["Color.red", "Color.green", "Color.blue", "Color.yellow"]
+ for value in scoped_names:
+ example = self.Example()
+ example.color = value
+ self.assertIsInstance(example.color, Color)
+ self.assertEqual(str(example.color), value)
+
+ def test_assign_bad_enum_value_name__raises_error(self):
+ # -- CONVERT: string => Enum value (item)
+ bad_enum_names = ["UNKNOWN_COLOR", "RED", "Green", "blue2"]
+ for value in bad_enum_names:
+ example = self.Example()
+ with self.assertRaises(TraitError):
+ example.color = value
+
+ def test_assign_enum_value_number_1(self):
+ # -- CONVERT: number => Enum value (item)
+ example = self.Example()
+ example.color = 1 # == Color.red.value
+ example.color = Color.red.value
+ self.assertEqual(example.color, Color.red)
+
+ def test_assign_enum_value_number(self):
+ # -- CONVERT: number => Enum value (item)
+ # pylint: disable=no-member
+ enum_numbers = [enum_val.value
+ for enum_val in Color.__members__.values()]
+ for value in enum_numbers:
+ self.assertIsInstance(value, int)
+ example = self.Example()
+ example.color = value
+ self.assertIsInstance(example.color, Color)
+ self.assertEqual(example.color.value, value)
+
+ def test_assign_bad_enum_value_number__raises_error(self):
+ # -- CONVERT: number => Enum value (item)
+ bad_numbers = [-1, 0, 5]
+ for value in bad_numbers:
+ self.assertIsInstance(value, int)
+ assert UseEnum(Color).select_by_number(value, None) is None
+ example = self.Example()
+ with self.assertRaises(TraitError):
+ example.color = value
+
+ def test_ctor_without_default_value(self):
+ # -- IMPLICIT: default_value = Color.red (first enum-value)
+ class Example2(HasTraits):
+ color = UseEnum(Color)
+
+ example = Example2()
+ self.assertEqual(example.color, Color.red)
+
+ def test_ctor_with_default_value_as_enum_value(self):
+ # -- CONVERT: number => Enum value (item)
+ class Example2(HasTraits):
+ color = UseEnum(Color, default_value=Color.green)
+
+ example = Example2()
+ self.assertEqual(example.color, Color.green)
+
+
+ def test_ctor_with_default_value_none_and_not_allow_none(self):
+ # -- IMPLICIT: default_value = Color.red (first enum-value)
+ class Example2(HasTraits):
+ color1 = UseEnum(Color, default_value=None, allow_none=False)
+ color2 = UseEnum(Color, default_value=None)
+ example = Example2()
+ self.assertEqual(example.color1, Color.red)
+ self.assertEqual(example.color2, Color.red)
+
+ def test_ctor_with_default_value_none_and_allow_none(self):
+ class Example2(HasTraits):
+ color1 = UseEnum(Color, default_value=None, allow_none=True)
+ color2 = UseEnum(Color, allow_none=True)
+
+ example = Example2()
+ self.assertIs(example.color1, None)
+ self.assertIs(example.color2, None)
+
+ def test_assign_none_without_allow_none_resets_to_default_value(self):
+ class Example2(HasTraits):
+ color1 = UseEnum(Color, allow_none=False)
+ color2 = UseEnum(Color)
+
+ example = Example2()
+ example.color1 = None
+ example.color2 = None
+ self.assertIs(example.color1, Color.red)
+ self.assertIs(example.color2, Color.red)
+
+ def test_assign_none_to_enum_or_none(self):
+ class Example2(HasTraits):
+ color = UseEnum(Color, allow_none=True)
+
+ example = Example2()
+ example.color = None
+ self.assertIs(example.color, None)
+
+ def test_assign_bad_value_with_to_enum_or_none(self):
+ class Example2(HasTraits):
+ color = UseEnum(Color, allow_none=True)
+
+ example = Example2()
+ with self.assertRaises(TraitError):
+ example.color = "BAD_VALUE"
+
diff --git a/contrib/python/traitlets/py2/traitlets/tests/utils.py b/contrib/python/traitlets/py2/traitlets/tests/utils.py
new file mode 100644
index 0000000000..88845d8519
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/tests/utils.py
@@ -0,0 +1,39 @@
+import sys
+
+from subprocess import Popen, PIPE
+
+def get_output_error_code(cmd):
+ """Get stdout, stderr, and exit code from running a command"""
+ p = Popen(cmd, stdout=PIPE, stderr=PIPE)
+ out, err = p.communicate()
+ out = out.decode('utf8', 'replace')
+ err = err.decode('utf8', 'replace')
+ return out, err, p.returncode
+
+
+def check_help_output(pkg, subcommand=None):
+ """test that `python -m PKG [subcommand] -h` works"""
+ cmd = [sys.executable, '-m', pkg]
+ if subcommand:
+ cmd.extend(subcommand)
+ cmd.append('-h')
+ out, err, rc = get_output_error_code(cmd)
+ assert rc == 0, err
+ assert "Traceback" not in err
+ assert "Options" in out
+ assert "--help-all" in out
+ return out, err
+
+
+def check_help_all_output(pkg, subcommand=None):
+ """test that `python -m PKG --help-all` works"""
+ cmd = [sys.executable, '-m', pkg]
+ if subcommand:
+ cmd.extend(subcommand)
+ cmd.append('--help-all')
+ out, err, rc = get_output_error_code(cmd)
+ assert rc == 0, err
+ assert "Traceback" not in err
+ assert "Options" in out
+ assert "Class parameters" in out
+ return out, err
diff --git a/contrib/python/traitlets/py2/traitlets/traitlets.py b/contrib/python/traitlets/py2/traitlets/traitlets.py
new file mode 100644
index 0000000000..c07daf7400
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/traitlets.py
@@ -0,0 +1,2690 @@
+# 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
diff --git a/contrib/python/traitlets/py2/traitlets/utils/__init__.py b/contrib/python/traitlets/py2/traitlets/utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/__init__.py
diff --git a/contrib/python/traitlets/py2/traitlets/utils/bunch.py b/contrib/python/traitlets/py2/traitlets/utils/bunch.py
new file mode 100644
index 0000000000..2edb830ad6
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/bunch.py
@@ -0,0 +1,25 @@
+"""Yet another implementation of bunch
+
+attribute-access of items on a dict.
+"""
+
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+class Bunch(dict):
+ """A dict with attribute-access"""
+ def __getattr__(self, key):
+ try:
+ return self.__getitem__(key)
+ except KeyError:
+ raise AttributeError(key)
+
+ def __setattr__(self, key, value):
+ self.__setitem__(key, value)
+
+ def __dir__(self):
+ # py2-compat: can't use super because dict doesn't have __dir__
+ names = dir({})
+ names.extend(self.keys())
+ return names
+
diff --git a/contrib/python/traitlets/py2/traitlets/utils/getargspec.py b/contrib/python/traitlets/py2/traitlets/utils/getargspec.py
new file mode 100644
index 0000000000..0a047379fe
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/getargspec.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""
+ getargspec excerpted from:
+
+ sphinx.util.inspect
+ ~~~~~~~~~~~~~~~~~~~
+ Helpers for inspecting Python modules.
+ :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import inspect
+from six import PY3
+
+# Unmodified from sphinx below this line
+
+if PY3:
+ from functools import partial
+
+ def getargspec(func):
+ """Like inspect.getargspec but supports functools.partial as well."""
+ if inspect.ismethod(func):
+ func = func.__func__
+ if type(func) is partial:
+ orig_func = func.func
+ argspec = getargspec(orig_func)
+ args = list(argspec[0])
+ defaults = list(argspec[3] or ())
+ kwoargs = list(argspec[4])
+ kwodefs = dict(argspec[5] or {})
+ if func.args:
+ args = args[len(func.args):]
+ for arg in func.keywords or ():
+ try:
+ i = args.index(arg) - len(args)
+ del args[i]
+ try:
+ del defaults[i]
+ except IndexError:
+ pass
+ except ValueError: # must be a kwonly arg
+ i = kwoargs.index(arg)
+ del kwoargs[i]
+ del kwodefs[arg]
+ return inspect.FullArgSpec(args, argspec[1], argspec[2],
+ tuple(defaults), kwoargs,
+ kwodefs, argspec[6])
+ while hasattr(func, '__wrapped__'):
+ func = func.__wrapped__
+ if not inspect.isfunction(func):
+ raise TypeError('%r is not a Python function' % func)
+ return inspect.getfullargspec(func)
+
+else: # 2.6, 2.7
+ from functools import partial
+
+ def getargspec(func):
+ """Like inspect.getargspec but supports functools.partial as well."""
+ if inspect.ismethod(func):
+ func = func.__func__
+ parts = 0, ()
+ if type(func) is partial:
+ keywords = func.keywords
+ if keywords is None:
+ keywords = {}
+ parts = len(func.args), keywords.keys()
+ func = func.func
+ if not inspect.isfunction(func):
+ raise TypeError('%r is not a Python function' % func)
+ args, varargs, varkw = inspect.getargs(func.__code__)
+ func_defaults = func.__defaults__
+ if func_defaults is None:
+ func_defaults = []
+ else:
+ func_defaults = list(func_defaults)
+ if parts[0]:
+ args = args[parts[0]:]
+ if parts[1]:
+ for arg in parts[1]:
+ i = args.index(arg) - len(args)
+ del args[i]
+ try:
+ del func_defaults[i]
+ except IndexError:
+ pass
+ return inspect.ArgSpec(args, varargs, varkw, func_defaults)
diff --git a/contrib/python/traitlets/py2/traitlets/utils/importstring.py b/contrib/python/traitlets/py2/traitlets/utils/importstring.py
new file mode 100644
index 0000000000..5b4f643f41
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/importstring.py
@@ -0,0 +1,42 @@
+# encoding: utf-8
+"""
+A simple utility to import something by its string name.
+"""
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from ipython_genutils.py3compat import cast_bytes_py2
+from six import string_types
+
+def import_item(name):
+ """Import and return ``bar`` given the string ``foo.bar``.
+
+ Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
+ executing the code ``from foo import bar``.
+
+ Parameters
+ ----------
+ name : string
+ The fully qualified name of the module/package being imported.
+
+ Returns
+ -------
+ mod : module object
+ The module that was imported.
+ """
+ if not isinstance(name, string_types):
+ raise TypeError("import_item accepts strings, not '%s'." % type(name))
+ name = cast_bytes_py2(name)
+ parts = name.rsplit('.', 1)
+ if len(parts) == 2:
+ # called with 'foo.bar....'
+ package, obj = parts
+ module = __import__(package, fromlist=[obj])
+ try:
+ pak = getattr(module, obj)
+ except AttributeError:
+ raise ImportError('No module named %s' % obj)
+ return pak
+ else:
+ # called with un-dotted string
+ return __import__(parts[0])
diff --git a/contrib/python/traitlets/py2/traitlets/utils/sentinel.py b/contrib/python/traitlets/py2/traitlets/utils/sentinel.py
new file mode 100644
index 0000000000..dc57a2591c
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/sentinel.py
@@ -0,0 +1,17 @@
+"""Sentinel class for constants with useful reprs"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+class Sentinel(object):
+
+ def __init__(self, name, module, docstring=None):
+ self.name = name
+ self.module = module
+ if docstring:
+ self.__doc__ = docstring
+
+
+ def __repr__(self):
+ return str(self.module)+'.'+self.name
+
diff --git a/contrib/python/traitlets/py2/ya.make b/contrib/python/traitlets/py2/ya.make
new file mode 100644
index 0000000000..3c26d78a20
--- /dev/null
+++ b/contrib/python/traitlets/py2/ya.make
@@ -0,0 +1,48 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+PROVIDES(python_traitlets)
+
+VERSION(4.3.3)
+
+LICENSE(BSD-3-Clause)
+
+PEERDIR(
+ contrib/deprecated/python/enum34
+ contrib/python/decorator
+ contrib/python/ipython-genutils
+ contrib/python/six
+)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ traitlets/__init__.py
+ traitlets/_version.py
+ traitlets/config/__init__.py
+ traitlets/config/application.py
+ traitlets/config/configurable.py
+ traitlets/config/loader.py
+ traitlets/config/manager.py
+ traitlets/log.py
+ traitlets/traitlets.py
+ traitlets/utils/__init__.py
+ traitlets/utils/bunch.py
+ traitlets/utils/getargspec.py
+ traitlets/utils/importstring.py
+ traitlets/utils/sentinel.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/traitlets/py2/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)