diff options
author | Mikhail Borisov <borisov.mikhail@gmail.com> | 2022-02-10 16:45:39 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:45:39 +0300 |
commit | a6a92afe03e02795227d2641b49819b687f088f8 (patch) | |
tree | f6984a1d27d5a7ec88a6fdd6e20cd5b7693b6ece /contrib/python/traitlets | |
parent | c6dc8b8bd530985bc4cce0137e9a5de32f1087cb (diff) | |
download | ydb-a6a92afe03e02795227d2641b49819b687f088f8.tar.gz |
Restoring authorship annotation for Mikhail Borisov <borisov.mikhail@gmail.com>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/traitlets')
28 files changed, 7896 insertions, 7896 deletions
diff --git a/contrib/python/traitlets/py2/COPYING.md b/contrib/python/traitlets/py2/COPYING.md index 39ca730a63..e314a9d376 100644 --- a/contrib/python/traitlets/py2/COPYING.md +++ b/contrib/python/traitlets/py2/COPYING.md @@ -1,62 +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. +# 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/traitlets/__init__.py b/contrib/python/traitlets/py2/traitlets/__init__.py index b609adb565..39933e5a79 100644 --- a/contrib/python/traitlets/py2/traitlets/__init__.py +++ b/contrib/python/traitlets/py2/traitlets/__init__.py @@ -1,3 +1,3 @@ -from .traitlets import * -from .utils.importstring import import_item -from ._version import version_info, __version__ +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 index ed16b3c1e1..6cc5c82a4f 100644 --- a/contrib/python/traitlets/py2/traitlets/_version.py +++ b/contrib/python/traitlets/py2/traitlets/_version.py @@ -1,2 +1,2 @@ version_info = (4, 3, 3) -__version__ = '.'.join(map(str, version_info)) +__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 index 0ae7d63171..1531ee5930 100644 --- a/contrib/python/traitlets/py2/traitlets/config/__init__.py +++ b/contrib/python/traitlets/py2/traitlets/config/__init__.py @@ -1,8 +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 +# 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 index d3a4c45e77..c0467e6c48 100644 --- a/contrib/python/traitlets/py2/traitlets/config/application.py +++ b/contrib/python/traitlets/py2/traitlets/config/application.py @@ -1,68 +1,68 @@ -# 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 - +# 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 +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 ( + +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 - +) +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 -#----------------------------------------------------------------------------- - +#----------------------------------------------------------------------------- +# 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','') @@ -74,194 +74,194 @@ 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() - +@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.""" + # 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) + new = getattr(logging, new) + self.log_level = new + self.log.setLevel(new) - _log_formatter_cls = LevelFormatter + _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 + 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""" + # 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) + 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()) - + + + # 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. """ @@ -269,10 +269,10 @@ class Application(SingletonConfigurable): _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. + 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: @@ -281,302 +281,302 @@ class Application(SingletonConfigurable): 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:') + @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 - + + @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 = [] + 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('') + 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) - + 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 + 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 = {} + # 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 = {} + 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 = {} + 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) + 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 + # 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: + """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) + jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log) loaded = [] filenames = [] - for loader in [pyloader, jsonloader]: + 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 + 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: + 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: @@ -587,16 +587,16 @@ class Application(SingletonConfigurable): 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) + + @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, @@ -607,8 +607,8 @@ class Application(SingletonConfigurable): # 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. @@ -641,71 +641,71 @@ class Application(SingletonConfigurable): 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('') + 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() + 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 index 1174fcf017..acb81cb208 100644 --- a/contrib/python/traitlets/py2/traitlets/config/configurable.py +++ b/contrib/python/traitlets/py2/traitlets/config/configurable.py @@ -1,191 +1,191 @@ -# 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. - +# 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 + +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(): +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)) + 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 + 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) + 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: + if len(matches) == 1: msg += u" Did you mean `{matches}`?".format(matches=matches[0]) - elif len(matches) >= 1: + 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() + + @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): + + 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, @@ -197,236 +197,236 @@ class Configurable(HasTraits): # load config self._load_config(config) # merge it into self.config - self.config.merge(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)) - + + @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 + + # 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 + 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)) + 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 - - - + 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 index 803b36276f..883ef695ac 100644 --- a/contrib/python/traitlets/py2/traitlets/config/loader.py +++ b/contrib/python/traitlets/py2/traitlets/config/loader.py @@ -1,392 +1,392 @@ -# 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 +# 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 = {} +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): + 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:: @@ -396,36 +396,36 @@ class JSONFileConfigLoader(FileConfigLoader): """ - 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 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. @@ -440,418 +440,418 @@ class JSONFileConfigLoader(FileConfigLoader): -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: +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: + 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""" + # 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 + 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: + 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: + 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 = [] - + 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 + 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 index 5e5ebde9af..89dc167943 100644 --- a/contrib/python/traitlets/py2/traitlets/config/manager.py +++ b/contrib/python/traitlets/py2/traitlets/config/manager.py @@ -1,88 +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 - +"""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 +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 index af86b325f5..559735bd1a 100644 --- a/contrib/python/traitlets/py2/traitlets/log.py +++ b/contrib/python/traitlets/py2/traitlets/log.py @@ -1,27 +1,27 @@ -"""Grab the global logger instance.""" +"""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. -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. + If a global Application is instantiated, grab its logger. + Otherwise, grab the root logger. + """ + global _logger -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: + 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 + return _logger diff --git a/contrib/python/traitlets/py2/traitlets/traitlets.py b/contrib/python/traitlets/py2/traitlets/traitlets.py index c07daf7400..233c047dc2 100644 --- a/contrib/python/traitlets/py2/traitlets/traitlets.py +++ b/contrib/python/traitlets/py2/traitlets/traitlets.py @@ -1,98 +1,98 @@ -# 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 +# 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 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 - +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.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 -#----------------------------------------------------------------------------- - + +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): @@ -117,331 +117,331 @@ def _should_warn(key): 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. - """ +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 + 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 + 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'). - """ + # 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) + 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] - """ + # 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] + return [names] else: - if not names or All in names: - return [All] - for n in names: + 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(): + 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(): + + 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], + + 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 - - -#----------------------------------------------------------------------------- + + 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' - +#----------------------------------------------------------------------------- + + +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 + """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 '' - + 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 + 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)) @@ -450,365 +450,365 @@ class TraitType(BaseDescriptor): "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() + if len(self.metadata) > 0: + self.metadata = self.metadata.copy() self.metadata.update(kwargs) - else: + else: self.metadata = kwargs - else: - self.metadata = self.metadata.copy() + 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 - """ + + # 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. - """ + 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: - + 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. + * 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: + 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)" + 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" + 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) - """ + 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.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: + elif self.nargs == 2: self.cb(change.name, change.new) - elif self.nargs == 3: + elif self.nargs == 3: self.cb(change.name, change.old, change.new) - elif self.nargs == 4: + 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.""" + +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): + # ---------------------------------------------------------------- + # 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. - """ + 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. - + 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. + + 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__ + 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) + clsname, change_or_name), DeprecationWarning) change = Bunch( type='change', old=old, @@ -816,182 +816,182 @@ def observe_compat(func): 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 - ----- + 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 + 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. - """ + 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. - """ + 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): + 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 - - + 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. - """ - + """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: + # 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 - + 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) - - + 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 = {} + 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. + # 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(): + with self.hold_trait_notifications(): for key, value in kwargs.items(): if self.has_trait(key): setattr(self, key, value) @@ -1017,35 +1017,35 @@ class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)): 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) - + + 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 + @contextlib.contextmanager def cross_validation_lock(self): """ A contextmanager for running a block with our cross validation lock set @@ -1065,72 +1065,72 @@ class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)): 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. - """ + 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: + 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): + 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)) + 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. + 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 + 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): + + # 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, @@ -1138,194 +1138,194 @@ class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)): owner=self, type='change', )) - - def notify_change(self, 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__ + + 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. - """ + 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 + 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. - + * ``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): + + 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 + + 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. + + 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 - + * ``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__,), @@ -1342,123 +1342,123 @@ class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)): 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. - """ + @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.""" + + @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)) + + 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) - + 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. @@ -1485,329 +1485,329 @@ class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)): 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) +#----------------------------------------------------------------------------- +# 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.""" - + 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 - + """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 - + 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.""" + + 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.""" + 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 + 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): + 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) + 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 + 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 - + 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 + """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 - + 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): + + 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): + 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' - + 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 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 + """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): + + 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) + 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]) - + 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 @@ -1818,17 +1818,17 @@ class Union(TraitType): 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' - - +#----------------------------------------------------------------------------- +# 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. @@ -1853,42 +1853,42 @@ def _validate_bounds(trait, obj, value): return value -class Int(TraitType): - """An int trait.""" - - default_value = 0 - info_text = 'an int' - +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) + 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: + + +class CInt(Int): + """A casting version of the int trait.""" + + def validate(self, obj, value): + try: value = int(value) - except: - self.error(obj, 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' - + 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) @@ -1897,36 +1897,36 @@ if six.PY2: 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) - + 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: + 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) + 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' - + + + 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) @@ -1935,674 +1935,674 @@ if six.PY2: 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) - + 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' - +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')) + 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) + 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: + + +class CFloat(Float): + """A casting version of the float trait.""" + + def validate(self, obj, value): + try: value = float(value) - except: - self.error(obj, 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): + + +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 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" - + 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 + # 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) - + + 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) - + 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.""" - + 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 + self.values = values if kwargs.get('allow_none', False) and default_value is Undefined: - default_value = None + 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 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): + 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 - + 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 - + """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): + 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)) - + 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 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, + """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, + + 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 - """ + """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,) - + + +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. - """ + """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): + # 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 + 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, + + 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``. - + + The default value is created by doing ``dict(default_value)``, + which creates a copy of the ``default_value``. + Parameters ---------- - trait : TraitType [ optional ] + 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): + + 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 - + 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): + + 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 + return value - validated = {} - for key in 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: + try: v = value[key] if not isinstance(validate_with, Any): v = validate_with._validate(obj, v) - except TraitError: + 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: + 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) + 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): diff --git a/contrib/python/traitlets/py2/traitlets/utils/getargspec.py b/contrib/python/traitlets/py2/traitlets/utils/getargspec.py index 0a047379fe..5c79b29549 100644 --- a/contrib/python/traitlets/py2/traitlets/utils/getargspec.py +++ b/contrib/python/traitlets/py2/traitlets/utils/getargspec.py @@ -1,86 +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 +# -*- 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) + +# 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 index 5b4f643f41..873ab9635c 100644 --- a/contrib/python/traitlets/py2/traitlets/utils/importstring.py +++ b/contrib/python/traitlets/py2/traitlets/utils/importstring.py @@ -1,42 +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. - +# 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]) + +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 index dc57a2591c..7af2558c1a 100644 --- a/contrib/python/traitlets/py2/traitlets/utils/sentinel.py +++ b/contrib/python/traitlets/py2/traitlets/utils/sentinel.py @@ -1,17 +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 - +"""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 index 4a60107101..ae62d588b4 100644 --- a/contrib/python/traitlets/py2/ya.make +++ b/contrib/python/traitlets/py2/ya.make @@ -1,49 +1,49 @@ # Generated by devtools/yamaker (pypi). PY2_LIBRARY() - + PROVIDES(python_traitlets) OWNER(borman nslus g:python-contrib) VERSION(4.3.3) - + LICENSE(BSD-3-Clause) -PEERDIR( +PEERDIR( contrib/python/decorator contrib/python/enum34 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 +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 -) - + 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() +END() RECURSE_FOR_TESTS( tests diff --git a/contrib/python/traitlets/py3/COPYING.md b/contrib/python/traitlets/py3/COPYING.md index 39ca730a63..e314a9d376 100644 --- a/contrib/python/traitlets/py3/COPYING.md +++ b/contrib/python/traitlets/py3/COPYING.md @@ -1,62 +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. +# 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/py3/traitlets/__init__.py b/contrib/python/traitlets/py3/traitlets/__init__.py index ad5ba73c86..1b8675a879 100644 --- a/contrib/python/traitlets/py3/traitlets/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/__init__.py @@ -1,11 +1,11 @@ from warnings import warn from . import traitlets -from .traitlets import * -from .utils.importstring import import_item +from .traitlets import * +from .utils.importstring import import_item from .utils.decorators import signature_has_traits from .utils.bunch import Bunch -from ._version import version_info, __version__ +from ._version import version_info, __version__ class Sentinel(traitlets.Sentinel): diff --git a/contrib/python/traitlets/py3/traitlets/config/__init__.py b/contrib/python/traitlets/py3/traitlets/config/__init__.py index 0ae7d63171..1531ee5930 100644 --- a/contrib/python/traitlets/py3/traitlets/config/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/config/__init__.py @@ -1,8 +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 +# 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/py3/traitlets/config/application.py b/contrib/python/traitlets/py3/traitlets/config/application.py index 99a6ef7ee0..6cdb801008 100644 --- a/contrib/python/traitlets/py3/traitlets/config/application.py +++ b/contrib/python/traitlets/py3/traitlets/config/application.py @@ -1,68 +1,68 @@ -"""A base class for a configurable application.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - +"""A base class for a configurable application.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + from collections import defaultdict, OrderedDict from copy import deepcopy import functools -import json -import logging -import os +import json +import logging +import os import pprint -import re -import sys +import re +import sys import warnings - -from traitlets.config.configurable import Configurable, SingletonConfigurable -from traitlets.config.loader import ( - KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader -) -from traitlets.traitlets import ( + +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 ..utils.importstring import import_item from ..utils import cast_unicode from traitlets.utils.text import indent, wrap_paragraphs from textwrap import dedent + - -#----------------------------------------------------------------------------- -# Descriptions for the various sections -#----------------------------------------------------------------------------- -# merge flags&aliases into options -option_description = """ +#----------------------------------------------------------------------------- +# Descriptions for the various sections +#----------------------------------------------------------------------------- +# merge flags&aliases into options +option_description = """ The options below are convenience aliases to configurable class-options, as listed in the "Equivalent to" description-line of the aliases. To see all configurable class-options for some <cmd>, use: <cmd> --help-all """.strip() # trim newlines of front and back - -keyvalue_description = """ + +keyvalue_description = """ The command-line option below sets the respective configurable class-parameter: --Class.parameter=value This line is evaluated in Python, so simple expressions are allowed. For instance, to set `C.a=[0,1,2]`, you may type this: --C.a='range(3)' -""".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 -#----------------------------------------------------------------------------- - +""".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','') @@ -75,13 +75,13 @@ else: def catch_config_error(method): - """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. + """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. - """ + For use on init methods, to prevent invoking excepthook on invalid input. + """ @functools.wraps(method) def inner(app, *args, **kwargs): try: @@ -90,116 +90,116 @@ def catch_config_error(method): app.log.fatal("Bad config encountered during initialization: %s", e) app.log.debug("Config at the time: %s", app.config) app.exit(1) - + return inner - -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 + +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('application') - - # The description of the application that is printed at the beginning - # of the help. + + # The description of the application that is printed at the beginning + # of the help. description = Unicode('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 = [] - + # 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, classes=None): - """Iterate through configurable classes, including configurable parents - + """Iterate through configurable classes, including configurable parents + :param classes: The list of classes to iterate; if not set, uses :attr:`classes`. - Children should always be after parents, and each class should only be - yielded once. - """ + Children should always be after parents, and each class should only be + yielded once. + """ if classes is None: classes = self.classes - seen = set() + seen = set() for c in 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. + # 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('0.0') - # the argv used to initialize the application - argv = List() - + # 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.""" + # 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, str): - new = getattr(logging, new) - self.log_level = new - self.log.setLevel(new) + new = getattr(logging, new) + self.log_level = new + self.log.setLevel(new) - _log_formatter_cls = LevelFormatter + _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.""" + 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._get_log_handler() if not _log_handler: warnings.warn( @@ -207,48 +207,48 @@ class Application(SingletonConfigurable): RuntimeWarning, ) return - _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 + _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 - + # 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 #: Keys might strings or tuples for additional options; single-letter alias accessed like `-v`. #: Values might be like "Class.trait" strings of two-tuples: (Class.trait, help-text). aliases = {'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 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 = { 'debug': ({ 'Application': { @@ -266,22 +266,22 @@ class Application(SingletonConfigurable): }, }, "Show the application's configuration (json format)"), } - - # 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()) - + + # 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. """ @@ -307,10 +307,10 @@ class Application(SingletonConfigurable): self._save_start = self.start self.start = self.start_show_config - 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. + 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: @@ -319,29 +319,29 @@ class Application(SingletonConfigurable): else: self.classes.insert(0, self.__class__) - @observe('config') - @observe_compat - def _config_changed(self, change): - super(Application, self)._config_changed(change) + @observe('config') + @observe_compat + def _config_changed(self, change): + super(Application, self)._config_changed(change) self.log.debug('Config changed: %r', 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() - + + @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 start_show_config(self): """start function used when show_config is True""" config = self.config.copy() @@ -379,21 +379,21 @@ class Application(SingletonConfigurable): pprint.pformat(value, **pformat_kwargs), )) - def print_alias_help(self): + def print_alias_help(self): """Print the alias parts of the help.""" print('\n'.join(self.emit_alias_help())) def emit_alias_help(self): """Yield the lines for alias part of the help.""" - if not self.aliases: - return - - 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 - + if not self.aliases: + return + + 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(): try: if isinstance(longname, tuple): @@ -403,10 +403,10 @@ class Application(SingletonConfigurable): classname, traitname = longname.split('.')[-2:] longname = classname + '.' + traitname cls = classdict[classname] - + trait = cls.class_traits(config=True)[traitname] fhelp = cls.class_get_trait_help(trait, helptext=fhelp).splitlines() - + if not isinstance(alias, tuple): alias = (alias, ) alias = sorted(alias, key=len) @@ -423,15 +423,15 @@ class Application(SingletonConfigurable): alias, ex) raise - def print_flag_help(self): - """Print the flag part of the help.""" + def print_flag_help(self): + """Print the flag part of the help.""" print('\n'.join(self.emit_flag_help())) def emit_flag_help(self): """Yield the lines for the flag part of the help.""" - if not self.flags: - return - + if not self.flags: + return + for flags, (cfg, fhelp) in self.flags.items(): try: if not isinstance(flags, tuple): @@ -451,50 +451,50 @@ class Application(SingletonConfigurable): self.log.error('Failed collecting help-message for flag %r, due to: %s', flags, ex) raise - - def print_options(self): + + def print_options(self): """Print the options part of the help.""" print('\n'.join(self.emit_options_help())) def emit_options_help(self): """Yield the lines for the options part of the help.""" - if not self.flags and not self.aliases: - return + if not self.flags and not self.aliases: + return header = 'Options' yield header yield '=' * len(header) - for p in wrap_paragraphs(self.option_description): + for p in wrap_paragraphs(self.option_description): yield p yield '' - + for l in self.emit_flag_help(): yield l for l in self.emit_alias_help(): yield l yield '' - def print_subcommands(self): - """Print the subcommand part of the help.""" + def print_subcommands(self): + """Print the subcommand part of the help.""" print('\n'.join(self.emit_subcommands_help())) def emit_subcommands_help(self): """Yield the lines for the subcommand part of the help.""" - if not self.subcommands: - return - + if not self.subcommands: + return + header = "Subcommands" yield header yield '=' * len(header) - for p in wrap_paragraphs(self.subcommand_description.format( - app=self.name)): + for p in wrap_paragraphs(self.subcommand_description.format( + app=self.name)): yield p yield '' for subc, (cls, help) in self.subcommands.items(): yield subc - if help: + if help: yield indent(dedent(help.strip())) yield '' - + def emit_help_epilogue(self, classes): """Yield the very bottom lines of the help message. @@ -504,13 +504,13 @@ class Application(SingletonConfigurable): yield "To see all available configurables, use `--help-all`." yield '' - 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. - """ + 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. + """ print('\n'.join(self.emit_help(classes=classes))) - + def emit_help(self, classes=False): """Yield the help-lines for each Configurable class in self.classes. @@ -523,71 +523,71 @@ class Application(SingletonConfigurable): for l in self.emit_options_help(): yield l - if classes: + if classes: help_classes = self._classes_with_config_traits() - if help_classes: + if help_classes: yield "Class options" yield "=============" - for p in wrap_paragraphs(self.keyvalue_description): + for p in wrap_paragraphs(self.keyvalue_description): yield p yield '' - - for cls in help_classes: + + for cls in help_classes: yield cls.class_get_help() yield '' for l in self.emit_examples(): yield l - + for l in self.emit_help_epilogue(classes): yield l - - 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.""" + + 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.""" print('\n'.join(self.emit_description())) - + def emit_description(self): """Yield lines with the application description.""" for p in wrap_paragraphs(self.description or self.__doc__): yield p yield '' - def print_examples(self): + def print_examples(self): """Print usage and examples (see `emit_examples()`). """ print('\n'.join(self.emit_examples())) - + def emit_examples(self): """Yield lines with the 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: + 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: yield "Examples" yield "--------" yield '' yield indent(dedent(self.examples.strip())) yield '' - - 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.""" + + 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, _ = self.subcommands.get(subc) - + if isinstance(subapp, str): - subapp = import_item(subapp) - + subapp = import_item(subapp) + ## Cannot issubclass() on a non-type (SOhttp://stackoverflow.com/questions/8692430) if isinstance(subapp, type) and issubclass(subapp, Application): # Clear existing instances before... @@ -601,55 +601,55 @@ class Application(SingletonConfigurable): raise AssertionError("Invalid mappings for subcommand '%s'!" % subc) # ... and finally initialize subapp. - self.subapp.initialize(argv) + self.subapp.initialize(argv) - def flatten_flags(self): + def flatten_flags(self): """Flatten flags and aliases for loaders, 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. + 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. Also, loaders expect ``(key: longname)`` and not ````key: (longname, help)`` items. - - 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 = {} + + 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, longname in self.aliases.items(): if isinstance(longname, tuple): longname, _ = longname cls, trait = longname.split('.', 1) - children = mro_tree[cls] - if len(children) == 1: - # exactly one descendent, promote alias - cls = children[0] + children = mro_tree[cls] + if len(children) == 1: + # exactly one descendent, promote alias + cls = children[0] if not isinstance(aliases, tuple): alias = (alias, ) for al in alias: aliases[al] = '.'.join([cls,trait]) - # flatten flags, which are of the form: - # { 'key' : ({'Cls' : {'trait' : value}}, 'help')} - flags = {} + # flatten flags, which are of the form: + # { 'key' : ({'Cls' : {'trait' : value}}, 'help')} + flags = {} for key, (flagdict, help) in self.flags.items(): - newflag = {} + newflag = {} for cls, subdict in flagdict.items(): - children = mro_tree[cls] - # exactly one descendent, promote flag section - if len(children) == 1: - cls = children[0] + children = mro_tree[cls] + # exactly one descendent, promote flag section + if len(children) == 1: + cls = children[0] if cls in newflag: newflag[cls].update(subdict) @@ -660,48 +660,48 @@ class Application(SingletonConfigurable): key = (key, ) for k in key: flags[k] = (newflag, help) - return flags, aliases - + return flags, aliases + def _create_loader(self, argv, aliases, flags, classes): return KVArgParseConfigLoader(argv, aliases, flags, classes=classes, log=self.log) - @catch_config_error - def parse_command_line(self, argv=None): - """Parse the command line arguments.""" + @catch_config_error + def parse_command_line(self, argv=None): + """Parse the command line arguments.""" assert not isinstance(argv, str) - argv = sys.argv[1:] if argv is None else argv + argv = sys.argv[1:] if argv is None else argv self.argv = [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: + 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() classes = tuple(self._classes_with_config_traits()) loader = self._create_loader(argv, aliases, flags, classes=classes) @@ -712,46 +712,46 @@ class Application(SingletonConfigurable): # help output is huge, and comes after the error raise self.update_config(self.cli_config) - # store unparsed args in extra_args - self.extra_args = loader.extra_args - - @classmethod + # 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: + """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) + jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log) loaded = [] filenames = [] - for loader in [pyloader, jsonloader]: + 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 + 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: + 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: @@ -762,16 +762,16 @@ class Application(SingletonConfigurable): 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) + + @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, @@ -782,7 +782,7 @@ class Application(SingletonConfigurable): # add self.cli_config to preserve CLI config priority new_config.merge(self.cli_config) self.update_config(new_config) - + def _classes_with_config_traits(self, classes=None): """ Yields only classes with configurable traits, and their subclasses. @@ -822,76 +822,76 @@ class Application(SingletonConfigurable): yield cl def generate_config_file(self, classes=None): - """generate default config file from Configurables""" - lines = ["# Configuration file for %s." % self.name] - lines.append('') + """generate default config file from Configurables""" + lines = ["# Configuration file for %s." % self.name] + lines.append('') classes = self.classes if classes is None else classes config_classes = list(self._classes_with_config_traits(classes)) for cls in config_classes: lines.append(cls.class_config_section(config_classes)) - 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 -#----------------------------------------------------------------------------- - + 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 +#----------------------------------------------------------------------------- + default_aliases = Application.aliases default_flags = Application.flags -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() +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() if __name__ == '__main__': diff --git a/contrib/python/traitlets/py3/traitlets/config/configurable.py b/contrib/python/traitlets/py3/traitlets/config/configurable.py index 3b2044a01b..d6dd93e114 100644 --- a/contrib/python/traitlets/py3/traitlets/config/configurable.py +++ b/contrib/python/traitlets/py3/traitlets/config/configurable.py @@ -1,13 +1,13 @@ -"""A base class for objects that are configurable.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -from copy import deepcopy +"""A base class for objects that are configurable.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from copy import deepcopy import logging import warnings - + from .loader import Config, LazyConfigValue, DeferredConfig, _is_section_key from traitlets.traitlets import ( Any, @@ -22,67 +22,67 @@ from traitlets.traitlets import ( ) from traitlets.utils.text import indent, wrap_paragraphs from textwrap import dedent - - - - -#----------------------------------------------------------------------------- -# 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) + + + + +#----------------------------------------------------------------------------- +# 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) # record traits set by config config_override_names = set() @@ -95,122 +95,122 @@ class Configurable(HasTraits): config_override_names.add(change.name) self.observe(notice_config_override) - # 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) + # 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) self.unobserve(notice_config_override) for name in config_override_names: setattr(self, name, kwargs[name]) + + #------------------------------------------------------------------------- + # Static trait notifiations + #------------------------------------------------------------------------- - #------------------------------------------------------------------------- - # 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) - ] + @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 + 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. + 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:: + 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(): + 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) + 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) elif isinstance(config_value, DeferredConfig): # DeferredConfig tends to come from CLI/environment variables config_value = config_value.get_value(traits[name]) - # 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)) + # 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 + 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) + matches = get_close_matches(name, traits) msg = "Config option `{option}` not recognized by `{klass}`.".format( option=name, klass=self.__class__.__name__) - if len(matches) == 1: + if len(matches) == 1: msg += " Did you mean `{matches}`?".format(matches=matches[0]) - elif len(matches) >= 1: + 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() + + @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): + + 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, @@ -222,28 +222,28 @@ class Configurable(HasTraits): # load config self._load_config(config) # merge it into self.config - self.config.merge(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 = [] + + @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 = [] base_classes = ', '.join(p.__name__ for p in cls.__bases__) final_help.append('%s(%s) options' % (cls.__name__, base_classes)) final_help.append(len(final_help[0])*'-') - 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 + 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, helptext=None): """Get the helptext string for a single trait. @@ -252,9 +252,9 @@ class Configurable(HasTraits): the class default. :param helptext: If not given, uses the `help` attribute of the current trait. - """ - assert inst is None or isinstance(inst, cls) - lines = [] + """ + assert inst is None or isinstance(inst, cls) + lines = [] header = "--%s.%s" % (cls.__name__, trait.name) if isinstance(trait, (Container, Dict)): multiplicity = trait.metadata.get('multiplicity', 'append') @@ -269,7 +269,7 @@ class Configurable(HasTraits): else: header = '%s=<%s>' % (header, trait.__class__.__name__) #header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) - lines.append(header) + lines.append(header) if helptext is None: helptext = trait.help @@ -281,26 +281,26 @@ class Configurable(HasTraits): # include Enum choices lines.append(indent('Choices: %s' % trait.info())) - if inst is not None: + if inst is not None: lines.append(indent("Current: %r" % (getattr(inst, trait.name),))) - 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: + 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)) - - 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 + + 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 _defining_class(cls, trait, classes): """Get the class that defines a trait @@ -336,13 +336,13 @@ class Configurable(HasTraits): The list of other classes in the config file. Used to reduce redundant information. """ - def c(s): - """return a commented, wrapped block.""" - s = '\n\n'.join(wrap_paragraphs(s, 78)) - + def c(s): + """return a commented, wrapped block.""" + s = '\n\n'.join(wrap_paragraphs(s, 78)) + return '## ' + s.replace('\n', '\n# ') - - # section header + + # section header breaker = '#' + '-' * 78 parent_classes = ', '.join( p.__name__ for p in cls.__bases__ @@ -351,17 +351,17 @@ class Configurable(HasTraits): 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 + # 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('') - + desc = getattr(cls, '__doc__', '') + if desc: + lines.append(c(desc)) + lines.append('') + for name, trait in sorted(cls.class_traits(config=True).items()): default_repr = trait.default_value_repr() @@ -385,60 +385,60 @@ class Configurable(HasTraits): lines.append('# See also: %s.%s' % (defining_class.__name__, name)) lines.append('# c.%s.%s = %s' % (cls.__name__, name, default_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__ + 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_traits(config=True).items()): - ttype = trait.__class__.__name__ - - termline = classname + '.' + trait.name - - # Choices or type - if 'Enum' in ttype: - # include Enum choices + ttype = trait.__class__.__name__ + + termline = classname + '.' + trait.name + + # Choices or type + if 'Enum' in ttype: + # include Enum choices termline += ' : ' + trait.info_rst() - 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 + 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(indent("Default: ``%s``" % dvr)) lines.append("") - - help = trait.help or 'No description' + + help = trait.help or 'No description' lines.append(indent(dedent(help))) - - # 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. - """ - + + # 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 = Any(help="Logger or LoggerAdapter instance") @validate("log") @@ -452,15 +452,15 @@ class LoggingConfigurable(Configurable): return proposal.value @default("log") - def _log_default(self): + def _log_default(self): if isinstance(self.parent, LoggingConfigurable): return self.parent.log - from traitlets import log - return log.get_logger() - + from traitlets import log + return log.get_logger() + def _get_log_handler(self): """Return the default Handler - + Returns None if none can be found """ logger = self.log @@ -472,89 +472,89 @@ class LoggingConfigurable(Configurable): return logger.handlers[0] -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( +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( "An incompatible sibling of '%s' is already instanciated" " as singleton: %s" % (cls.__name__, type(cls._instance).__name__) - ) - - @classmethod - def initialized(cls): - """Has an instance been created?""" - return hasattr(cls, "_instance") and cls._instance is not None - - - + ) + + @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/py3/traitlets/config/loader.py b/contrib/python/traitlets/py3/traitlets/config/loader.py index 5360f889ab..3af27bc22f 100644 --- a/contrib/python/traitlets/py3/traitlets/config/loader.py +++ b/contrib/python/traitlets/py3/traitlets/config/loader.py @@ -1,49 +1,49 @@ -"""A simple configuration system.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import argparse -import copy -import os -import re -import sys -import json +"""A simple configuration system.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import argparse +import copy +import os +import re +import sys +import json import warnings - + from ..utils import cast_unicode, filefind from traitlets.traitlets import ( HasTraits, Container, List, Dict, Any, Undefined, ) - -#----------------------------------------------------------------------------- -# 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. - + +#----------------------------------------------------------------------------- +# 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 _Sentinel: def __repr__(self): @@ -56,55 +56,55 @@ class _Sentinel: _deprecated = _Sentinel() -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 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 +#----------------------------------------------------------------------------- + def execfile(fname, glob): with open(fname, 'rb') as f: exec(compile(f.read(), fname, 'exec'), glob, glob) -class LazyConfigValue(HasTraits): - """Proxy object for exposing methods on configurable containers +class LazyConfigValue(HasTraits): + """Proxy object for exposing methods on configurable containers These methods allow appending/extending/updating to add to non-empty defaults instead of clobbering them. - Exposes: + Exposes: - - append, extend, insert on lists - - update on dicts - - update, add on sets - """ + - append, extend, insert on lists + - update on dicts + - update, add on sets + """ - _value = None + _value = None - # list methods - _extend = List() - _prepend = List() + # list methods + _extend = List() + _prepend = List() _inserts = List() - def append(self, obj): + def append(self, obj): """Append an item to a List""" - self._extend.append(obj) + self._extend.append(obj) - def extend(self, other): + def extend(self, other): """Extend a list""" - self._extend.extend(other) + self._extend.extend(other) - def prepend(self, other): - """like list.extend, but for the front""" - self._prepend[:0] = other + def prepend(self, other): + """like list.extend, but for the front""" + self._prepend[:0] = other def merge_into(self, other): @@ -140,86 +140,86 @@ class LazyConfigValue(HasTraits): # other is a container, reify now. return self.get_value(other) - def insert(self, index, other): - if not isinstance(index, int): - raise TypeError("An integer is required") - self._inserts.append((index, other)) + 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() + # dict methods + # update is used for both dict and set + _update = Any() - def update(self, other): + def update(self, other): """Update either a set or dict""" - 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): + 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): """Add an item to a set""" - 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 - + 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 __repr__(self): if self._value is not None: return "<%s value=%r>" % (self.__class__.__name__, self._value) else: return "<%s %r>" % (self.__class__.__name__, self.to_dict()) - - -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): + + +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. - + Accessing a field on a config object for the first time populates the key with either a nested Config object for keys starting with capitals or :class:`.LazyConfigValue` for lowercase keys, @@ -231,147 +231,147 @@ class Config(dict): """ - 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 = {} + 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) + 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) elif isinstance(v, LazyConfigValue): self[k] = v.merge_into(self[k]) - 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) - - + 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) + + class DeferredConfig: """Class for deferred-evaluation of config from CLI""" pass @@ -455,95 +455,95 @@ class DeferredConfigList(list, DeferredConfig): return '%s(%s)' % (self.__class__.__name__, self._super_repr()) -#----------------------------------------------------------------------------- -# 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. +#----------------------------------------------------------------------------- +# 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 logger 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): + 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:: @@ -553,36 +553,36 @@ class JSONFileConfigLoader(FileConfigLoader): """ - 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 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. @@ -597,68 +597,68 @@ class JSONFileConfigLoader(FileConfigLoader): -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, - ) +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, + ) conf_filename = self.full_filename with open(conf_filename, 'rb') as f: exec(compile(f.read(), conf_filename, 'exec'), namespace, 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. - """ - + + +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, trait=None): - """execute self.config.<lhs> = <rhs> + """execute self.config.<lhs> = <rhs> - * expands ~ with expanduser + * expands ~ with expanduser * interprets value with trait if available - """ + """ value = rhs if isinstance(value, DeferredConfig): if trait: @@ -672,41 +672,41 @@ class CommandLineConfigLoader(ConfigLoader): value = trait.from_string(value) else: value = DeferredConfigString(value) - + *path, key = lhs.split(".") section = self.config for part in path: section = section[part] section[key] = value return - - 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: + + 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) - + self.config[sec].update(c) + else: + raise TypeError("Invalid flag: %r" % cfg) + # match --Class.trait keys for argparse # matches: # --Class.trait # --x # -x - + class_trait_opt_pattern = re.compile(r'^\-?\-[A-Za-z][\w]*(\.[\w]+)*$') - + _DOT_REPLACEMENT = "__DOT__" _DASH_REPLACEMENT = "__DASH__" - - + + class _KVAction(argparse.Action): """Custom argparse action for handling --Class.trait=x - + Always - """ + """ def __call__(self, parser, namespace, values, option_string=None): if isinstance(values, str): values = [values] @@ -718,11 +718,11 @@ class _KVAction(argparse.Action): items = DeferredConfigList(items) items.extend(values) setattr(namespace, self.dest, items) - - + + class _DefaultOptionDict(dict): """Like the default options dict - + but acts as if all --Class.trait options are predefined """ def _add_kv_action(self, key): @@ -732,31 +732,31 @@ class _DefaultOptionDict(dict): # use metavar for display purposes metavar=key.lstrip("-"), ) - + def __contains__(self, key): if '=' in key: return False if super().__contains__(key): return True - + if key.startswith("-") and class_trait_opt_pattern.match(key): self._add_kv_action(key) return True return False - + def __getitem__(self, key): if key in self: return super().__getitem__(key) else: raise KeyError(key) - + def get(self, key, default=None): try: return self[key] except KeyError: return default - - + + class _KVArgParser(argparse.ArgumentParser): """subclass of ArgumentParser where any --Class.trait option is implicitly defined""" def parse_known_args(self, args=None, namespace=None): @@ -766,23 +766,23 @@ class _KVArgParser(argparse.ArgumentParser): container._option_string_actions = _DefaultOptionDict( container._option_string_actions) return super().parse_known_args(args, namespace) - - -class ArgParseConfigLoader(CommandLineConfigLoader): - """A loader that uses the argparse module to load from the command line.""" - + + +class ArgParseConfigLoader(CommandLineConfigLoader): + """A loader that uses the argparse module to load from the command line.""" + parser_class = ArgumentParser def __init__(self, argv=None, aliases=None, flags=None, log=None, classes=(), *parser_args, **parser_kw): - """Create a config loader for use with argparse. - - Parameters - ---------- + """Create a config loader for use with argparse. + + Parameters + ---------- classes : optional, list The classes to scan for *container* config-traits and decide for their "multiplicity" when adding them as *argparse* arguments. - argv : optional, list + argv : optional, list If given, used to read command-line arguments from, otherwise sys.argv[1:] is used. *parser_args : tuple @@ -797,39 +797,39 @@ class ArgParseConfigLoader(CommandLineConfigLoader): Dict of flags to full traitlests names for CLI parsing log Passed to `ConfigLoader` - - 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 {} + + 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.classes = classes - - 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 - + + 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=_deprecated, classes=None): - """Parse command line arguments and return as a Config object. - - Parameters - ---------- + """Parse command line arguments and return as a Config object. + + Parameters + ---------- argv : 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. flags Deprecated in traitlets 5.0, instanciate the config loader with the flags. - + """ if flags is not _deprecated: @@ -840,35 +840,35 @@ class ArgParseConfigLoader(CommandLineConfigLoader): stacklevel=2, ) - self.clear() - if argv is None: - argv = self.argv + self.clear() + if argv is None: + argv = self.argv if aliases is not None: self.aliases = aliases if classes is not None: self.classes = classes self._create_parser() - 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 [] - + 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): self.parser = self.parser_class(*self.parser_args, **self.parser_kw) self._add_arguments(self.aliases, self.flags, self.classes) - + def _add_arguments(self, aliases, flags, classes): - raise NotImplementedError("subclasses must implement _add_arguments") - - def _parse_args(self, args): - """self.parser->self.parsed_data""" + raise NotImplementedError("subclasses must implement _add_arguments") + + def _parse_args(self, args): + """self.parser->self.parsed_data""" uargs = [cast_unicode(a) for a in args] - + unpacked_aliases = {} if self.aliases: unpacked_aliases = {} @@ -908,15 +908,15 @@ class ArgParseConfigLoader(CommandLineConfigLoader): self.parsed_data = self.parser.parse_args(to_parse) self.extra_args = extra_args - def _convert_to_config(self): - """self.parsed_data->self.config""" + def _convert_to_config(self): + """self.parsed_data->self.config""" for k, v in vars(self.parsed_data).items(): *path, key = k.split(".") section = self.config for p in path: section = section[p] setattr(section, key, v) - + class _FlagAction(argparse.Action): """ArgParse action to handle a flag""" @@ -937,9 +937,9 @@ class _FlagAction(argparse.Action): setattr(namespace, self.alias, values) -class KVArgParseConfigLoader(ArgParseConfigLoader): - """A config loader that loads aliases and flags with argparse, - +class KVArgParseConfigLoader(ArgParseConfigLoader): + """A config loader that loads aliases and flags with argparse, + as well as arbitrary --Class.trait value """ @@ -947,10 +947,10 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): def _add_arguments(self, aliases, flags, classes): alias_flags = {} - paa = self.parser.add_argument + paa = self.parser.add_argument self.parser.set_defaults(_flags=[]) paa("extra_args", nargs="*") - + ## An index of all container traits collected:: # # { <traitname>: (<trait>, <argparse-kwds>) } @@ -1010,10 +1010,10 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): keys = ('-' + key, '--' + key) if len(key) == 1 else ('--'+ key,) paa(*keys, **argparse_kwds) - def _convert_to_config(self): - """self.parsed_data->self.config, parse unrecognized extra args via KVLoader.""" + def _convert_to_config(self): + """self.parsed_data->self.config, parse unrecognized extra args via KVLoader.""" extra_args = self.extra_args - + for lhs, rhs in vars(self.parsed_data).items(): if lhs == "extra_args": self.extra_args = ["-" if a == _DASH_REPLACEMENT else a for a in rhs] + extra_args @@ -1021,7 +1021,7 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): elif lhs == '_flags': # _flags will be handled later continue - + lhs = lhs.replace(_DOT_REPLACEMENT, ".") if '.' not in lhs: # probably a mistyped alias, but not technically illegal @@ -1048,12 +1048,12 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): raise ArgumentError(f"Error loading argument {lhs}={rhs}, {e}") for subc in self.parsed_data._flags: - self._load_flag(subc) - - + self._load_flag(subc) + + class KeyValueConfigLoader(KVArgParseConfigLoader): """Deprecated in traitlets 5.0 - + Use KVArgParseConfigLoader """ def __init__(self, *args, **kwargs): @@ -1066,25 +1066,25 @@ class KeyValueConfigLoader(KVArgParseConfigLoader): super().__init__(*args, **kwargs) -def load_pyconfig_files(config_files, path): - """Load multiple Python config files, merging each of them in turn. - - Parameters +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 + 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/py3/traitlets/config/manager.py b/contrib/python/traitlets/py3/traitlets/config/manager.py index 164053261e..041477b1b9 100644 --- a/contrib/python/traitlets/py3/traitlets/config/manager.py +++ b/contrib/python/traitlets/py3/traitlets/config/manager.py @@ -1,84 +1,84 @@ -"""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 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() - +"""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 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() + f = open(filename, 'w', encoding='utf-8') - 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 + 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/py3/traitlets/log.py b/contrib/python/traitlets/py3/traitlets/log.py index af86b325f5..559735bd1a 100644 --- a/contrib/python/traitlets/py3/traitlets/log.py +++ b/contrib/python/traitlets/py3/traitlets/log.py @@ -1,27 +1,27 @@ -"""Grab the global logger instance.""" +"""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. -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. + If a global Application is instantiated, grab its logger. + Otherwise, grab the root logger. + """ + global _logger -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: + 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 + return _logger diff --git a/contrib/python/traitlets/py3/traitlets/traitlets.py b/contrib/python/traitlets/py3/traitlets/traitlets.py index 6bdf7414d3..1a278992a3 100644 --- a/contrib/python/traitlets/py3/traitlets/traitlets.py +++ b/contrib/python/traitlets/py3/traitlets/traitlets.py @@ -1,62 +1,62 @@ -""" -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. - +""" +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. + from ast import literal_eval -import contextlib -import inspect +import contextlib +import inspect import os -import re -import sys -import types +import re +import sys +import types import enum -from warnings import warn, warn_explicit - -from .utils.getargspec import getargspec -from .utils.importstring import import_item -from .utils.sentinel import Sentinel +from warnings import warn, warn_explicit + +from .utils.getargspec import getargspec +from .utils.importstring import import_item +from .utils.sentinel import Sentinel from .utils.bunch import Bunch from .utils.descriptions import describe, class_of, add_article, repr_type - -SequenceTypes = (list, tuple, set, frozenset) - + +SequenceTypes = (list, tuple, set, frozenset) + # backward compatibility, use to differ between Python 2 and 3. ClassTypes = (type,) @@ -85,34 +85,34 @@ __all__ = [ # any TraitType subclass (that doesn't start with _) will be added automatically -#----------------------------------------------------------------------------- -# 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 -#----------------------------------------------------------------------------- - +#----------------------------------------------------------------------------- +# 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 +#----------------------------------------------------------------------------- + _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") def isidentifier(s): @@ -134,332 +134,332 @@ def _should_warn(key): 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. - """ +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 + 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 + try: + fname = inspect.getsourcefile(method) or "<unknown>" + lineno = inspect.getsourcelines(method)[1] or 0 except (OSError, 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) - + # Failed to inspect for some reason + warn(warn_msg + ('\n(inspection failed) %s' % e), DeprecationWarning) + else: + warn_explicit(warn_msg, DeprecationWarning, fname, lineno) + def _safe_literal_eval(s): """Safely evaluate an expression - + Returns original string if eval fails. - + Use only where types are ambiguous. - """ + """ try: return literal_eval(s) except (NameError, SyntaxError, ValueError): return s - -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] + +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'] + ['a'] >>> parse_notifier_name(["a", "b"]) - ['a', 'b'] - >>> parse_notifier_name(All) - [All] - """ + ['a', 'b'] + >>> parse_notifier_name(All) + [All] + """ if names is All or isinstance(names, str): - return [names] + return [names] else: - if not names or All in names: - return [All] - for n in names: + if not names or All in names: + return [All] + for n in names: if not isinstance(n, str): 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 + 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 transform: iterable with two callables (optional) Data transformation between source and target and target and source. - - Examples - -------- + + Examples + -------- >>> c = link((src, "value"), (tgt, "value")) - >>> src.value = 5 # updates other objects as well - """ - updating = False - + >>> src.value = 5 # updates other objects as well + """ + updating = False + def __init__(self, source, target, transform=None): - _validate_link(source, target) - self.source, self.target = source, target + _validate_link(source, target) + self.source, self.target = source, target self._transform, self._transform_inv = ( transform if transform else (lambda x: x,) * 2) self.link() def link(self): - try: + try: setattr(self.target[0], self.target[1], self._transform(getattr(self.source[0], self.source[1]))) - finally: + finally: self.source[0].observe(self._update_target, names=self.source[1]) self.target[0].observe(self._update_source, names=self.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(): + + @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], self._transform(change.new)) if getattr(self.source[0], self.source[1]) != change.new: raise TraitError( "Broken link {}: the source value changed while updating " "the target.".format(self)) - - def _update_source(self, change): - if self.updating: - return - with self._busy_updating(): + + def _update_source(self, change): + if self.updating: + return + with self._busy_updating(): setattr(self.source[0], self.source[1], self._transform_inv(change.new)) if getattr(self.target[0], self.target[1]) != change.new: raise TraitError( "Broken link {}: the target value changed while updating " "the source.".format(self)) - - 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]) - - -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 - -------- + + 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]) + + +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 + >>> 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 self.link() def link(self): - try: + try: setattr(self.target[0], self.target[1], self._transform(getattr(self.source[0], self.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], + 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]) - -dlink = directional_link - - -#----------------------------------------------------------------------------- + + def unlink(self): + self.source[0].unobserve(self._update, names=self.source[1]) + +dlink = directional_link + + +#----------------------------------------------------------------------------- # Base Descriptor Class -#----------------------------------------------------------------------------- - - -class BaseDescriptor(object): - """Base descriptor class - - Notes - ----- +#----------------------------------------------------------------------------- + + +class BaseDescriptor(object): + """Base descriptor class + + Notes + ----- This implements Python's descriptor protocol. - - 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 - + + 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 subclass_init(self, cls): pass - 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 = {} - allow_none = False - read_only = False - info_text = 'any value' + 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 = {} + allow_none = False + read_only = False + info_text = 'any value' default_value = Undefined - + 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. + """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. If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError. - 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 + 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 '' - + 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 + 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)) @@ -468,21 +468,21 @@ class TraitType(BaseDescriptor): "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() + if len(self.metadata) > 0: + self.metadata = self.metadata.copy() self.metadata.update(kwargs) - else: + else: self.metadata = kwargs - else: - self.metadata = self.metadata.copy() + 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 - + + # 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 from_string(self, s): """Get a value from a config string @@ -515,28 +515,28 @@ class TraitType(BaseDescriptor): # Undefined will raise in TraitType.get return self.default_value - def get_default_value(self): - """DEPRECATED: Retrieve the static default value for this trait. - Use self.default_value instead - """ + 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. - """ + 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 - + stacklevel=2) + value = self._validate(obj, self.default_value) + obj._trait_values[self.name] = value + return value + def get(self, obj, cls=None): - try: - value = obj._trait_values[self.name] - except KeyError: - # Check for a dynamic initializer. + try: + value = obj._trait_values[self.name] + except KeyError: + # Check for a dynamic initializer. default = obj.trait_defaults(self.name) if default is Undefined: warn( @@ -548,93 +548,93 @@ class TraitType(BaseDescriptor): ) with obj.cross_validation_lock: value = self._validate(obj, default) - obj._trait_values[self.name] = value + obj._trait_values[self.name] = value obj._notify_observers(Bunch( name=self.name, value=value, owner=obj, type='default', )) - 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) + 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 Exception: - # 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: + # 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 - + 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, error=None, info=None): """Raise a TraitError @@ -676,7 +676,7 @@ class TraitType(BaseDescriptor): "expected %s, not %s." % (self.name, chain, error.args[1], describe("the", error.args[0])),) raise error - else: + else: # this trait caused an error if self.name is None: # this is not the root trait @@ -690,196 +690,196 @@ class TraitType(BaseDescriptor): e = "The '%s' trait expected %s, not %s." % ( self.name, self.info(), describe("the", 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)" + + 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" + 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) - """ + 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.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: + elif self.nargs == 2: self.cb(change.name, change.new) - elif self.nargs == 3: + elif self.nargs == 3: self.cb(change.name, change.old, change.new) - elif self.nargs == 4: + 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.""" + +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): + # ---------------------------------------------------------------- + # 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. - """ + 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) - + if isinstance(v, BaseDescriptor): + v.class_init(cls, k) + for k, v in getmembers(cls): if isinstance(v, BaseDescriptor): v.subclass_init(cls) - - -class MetaHasTraits(MetaHasDescriptors): - """A metaclass for HasTraits.""" - - def setup_class(cls, classdict): - cls._trait_default_generators = {} - super(MetaHasTraits, cls).setup_class(classdict) - - + + +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, type="change"): - """A decorator which can be used to observe Traits on a class. - + """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. + + 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, str): raise TypeError("trait names to observe must be strings or All, not %r" % name) return ObserveHandler(names, type=type) - - -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__ + + +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) + clsname, change_or_name), DeprecationWarning) change = Bunch( type='change', old=old, @@ -887,151 +887,151 @@ def observe_compat(func): 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 - ---------- + 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 - ----- + 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 + 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. - """ + 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, str): 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. - """ + 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, str): 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): + 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): + 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().class_init(cls, name) - cls._trait_default_generators[self.trait_name] = self - - + cls._trait_default_generators[self.trait_name] = self + + class HasDescriptors(metaclass=MetaHasDescriptors): - """The base class for all classes that have descriptors. - """ - + """The base class for all classes that have descriptors. + """ + def __new__(*args, **kwargs): # Pass cls as args[0] to allow "cls" as keyword argument cls = args[0] args = args[1:] - # 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: + # 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 - + return inst + def setup_instance(*args, **kwargs): """ This is called **before** self.__init__ is called. @@ -1041,39 +1041,39 @@ class HasDescriptors(metaclass=MetaHasDescriptors): args = args[1:] 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) - - + 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(HasDescriptors, metaclass=MetaHasTraits): - + def setup_instance(*args, **kwargs): # Pass self as args[0] to allow "self" as keyword argument self = args[0] args = args[1:] - self._trait_values = {} - self._trait_notifiers = {} - self._trait_validators = {} + 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. + # 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(): + with self.hold_trait_notifications(): for key, value in kwargs.items(): if self.has_trait(key): setattr(self, key, value) @@ -1099,38 +1099,38 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): 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'] = {} + + 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'] = {} d['_trait_values'] = self._trait_values.copy() d['_cross_validation_lock'] = False # FIXME: raise if cloning locked! - 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) - + 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 + @contextlib.contextmanager def cross_validation_lock(self): """ A contextmanager for running a block with our cross validation lock set @@ -1150,72 +1150,72 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): 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. - """ + 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: + 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): + 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)) + 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. + 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 + 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): + + # 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, @@ -1223,8 +1223,8 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): owner=self, type='change', )) - - def notify_change(self, change): + + def notify_change(self, change): """Notify observers of a change event""" return self._notify_observers(change) @@ -1234,188 +1234,188 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): # cast to bunch if given a dict event = Bunch(event) name, type = event.name, event.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 + + 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 event.type == "change" and 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__ + 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 = getattr(self, c.name) c(event) - - 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. - """ + + 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 + 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. - + * ``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): + + 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 + + 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. + + 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): + * ``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 - + "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.""" cls = self.__class__ @@ -1437,63 +1437,63 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): 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. - """ + @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(): + + @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 not callable(meta_eval): - 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) + 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_has_value(self, name): """Returns True if the specified trait has a value. @@ -1587,65 +1587,65 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): defaults[n] = self._get_trait_default_generator(n)(self) return defaults - def trait_names(self, **metadata): - """Get a list of all the names of this class' traits.""" + 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(): + + 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 not callable(meta_eval): - 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)) + 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) - + 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. @@ -1672,288 +1672,288 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): 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) - - -class Type(ClassBasedTraitType): - """A trait whose value must be a subclass of a specified class.""" - +#----------------------------------------------------------------------------- +# 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) + + +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. + """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. **kwargs extra kwargs passed to `ClassBasedTraitType` - """ - 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 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, str)): - raise TraitError("A Type trait must specify a class.") - - self.klass = klass - + raise TraitError("A Type trait must specify a class.") + + self.klass = klass + super().__init__(new_default_value, **kwargs) - - def validate(self, obj, value): - """Validates that the value is a valid object instance.""" + + def validate(self, obj, value): + """Validates that the value is a valid object instance.""" if isinstance(value, str): - 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 + 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 Exception: - pass - - self.error(obj, value) - - def info(self): - """ Returns a description of the trait.""" + pass + + self.error(obj, value) + + def info(self): + """ Returns a description of the trait.""" if isinstance(self.klass, str): - klass = self.klass - else: + 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() + 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().instance_init(obj) - - def _resolve_classes(self): + + def _resolve_classes(self): if isinstance(self.klass, str): - self.klass = self._resolve_string(self.klass) + self.klass = self._resolve_string(self.klass) if isinstance(self.default_value, str): - self.default_value = self._resolve_string(self.default_value) - - def default_value_repr(self): - value = self.default_value + self.default_value = self._resolve_string(self.default_value) + + def default_value_repr(self): + value = self.default_value if isinstance(value, str): - return repr(value) - else: + return repr(value) + else: return repr(f'{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 - + + +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. + """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. **kwargs Extra kwargs passed to `ClassBasedTraitType` - - 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 + + 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, str)): - 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 - + 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): + + def validate(self, obj, value): + if isinstance(value, self.klass): + return value + else: + self.error(obj, value) + + def info(self): if isinstance(self.klass, str): result = add_article(self.klass) - else: + else: result = describe("a", self.klass) - if self.allow_none: + if self.allow_none: result += ' or None' - return result - - def instance_init(self, obj): - self._resolve_classes() + return result + + def instance_init(self, obj): + self._resolve_classes() super().instance_init(obj) - - def _resolve_classes(self): + + def _resolve_classes(self): if isinstance(self.klass, str): - 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()) - + 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()) + def from_string(self, s): return _safe_literal_eval(s) - - -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' - + + +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 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 - ---------- + """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. - """ + 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 = list(trait_types) self.info_text = " or ".join([tt.info() for tt in self.trait_types]) super(Union, self).__init__(**kwargs) - + def default(self, obj=None): default = super(Union, self).default(obj) for t in self.trait_types: @@ -1963,48 +1963,48 @@ class Union(TraitType): break return default - def class_init(self, cls, name): + def class_init(self, cls, name): for trait_type in reversed(self.trait_types): - trait_type.class_init(cls, None) - super(Union, self).class_init(cls, name) - - def instance_init(self, obj): + trait_type.class_init(cls, None) + super(Union, self).class_init(cls, name) + + def instance_init(self, obj): for trait_type in reversed(self.trait_types): - trait_type.instance_init(obj) - super(Union, self).instance_init(obj) - - def validate(self, obj, value): + 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) + 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]) - - -#----------------------------------------------------------------------------- -# Basic TraitTypes implementations/subclasses -#----------------------------------------------------------------------------- - - -class Any(TraitType): - """A trait which allows any value.""" - default_value = None + 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]) + + +#----------------------------------------------------------------------------- +# Basic TraitTypes implementations/subclasses +#----------------------------------------------------------------------------- + + +class Any(TraitType): + """A trait which allows any value.""" + default_value = None allow_none = True - info_text = 'any value' - - + info_text = 'any value' + + def _validate_bounds(trait, obj, value): """ Validate that a number to be applied to a trait is between bounds. @@ -2029,122 +2029,122 @@ def _validate_bounds(trait, obj, value): return value -class Int(TraitType): - """An int trait.""" - - default_value = 0 - info_text = 'an int' - +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) + 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) - + def from_string(self, s): if self.allow_none and s == 'None': return None return int(s) + - -class CInt(Int): - """A casting version of the int trait.""" - - def validate(self, obj, value): - try: +class CInt(Int): + """A casting version of the int trait.""" + + def validate(self, obj, value): + try: value = int(value) except Exception: - self.error(obj, value) + self.error(obj, value) return _validate_bounds(self, obj, value) - + Long, CLong = Int, CInt Integer = Int - - -class Float(TraitType): - """A float trait.""" - - default_value = 0.0 - info_text = 'a float' - + + +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')) + 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) + 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) - + def from_string(self, s): if self.allow_none and s == 'None': return None return float(s) + - -class CFloat(Float): - """A casting version of the float trait.""" - - def validate(self, obj, value): - try: +class CFloat(Float): + """A casting version of the float trait.""" + + def validate(self, obj, value): + try: value = float(value) except Exception: - self.error(obj, value) + 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 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) + def from_string(self, s): if self.allow_none and s == 'None': return None return complex(s) - - -class CComplex(Complex): - """A casting version of the complex number trait.""" - - def validate (self, obj, value): - try: - return complex(value) + + +class CComplex(Complex): + """A casting version of the complex number trait.""" + + def validate (self, obj, value): + try: + return complex(value) except Exception: - 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) - + 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) + def from_string(self, s): if self.allow_none and s == "None": return None @@ -2160,35 +2160,35 @@ class Bytes(TraitType): FutureWarning) break return s.encode("utf8") - - -class CBytes(Bytes): - """A casting version of the byte string trait.""" - - def validate(self, obj, value): - try: - return bytes(value) + + +class CBytes(Bytes): + """A casting version of the byte string trait.""" + + def validate(self, obj, value): + try: + return bytes(value) except Exception: - self.error(obj, value) - - -class Unicode(TraitType): - """A trait for unicode strings.""" - + self.error(obj, value) + + +class Unicode(TraitType): + """A trait for unicode strings.""" + default_value = '' - info_text = 'a unicode string' - - def validate(self, obj, value): + info_text = 'a unicode string' + + def validate(self, obj, value): if isinstance(value, str): - 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) - + 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) + def from_string(self, s): if self.allow_none and s == 'None': return None @@ -2204,66 +2204,66 @@ class Unicode(TraitType): "You can use %r instead of %r if you require traitlets >=5." % (s, old_s), FutureWarning) return s + - -class CUnicode(Unicode): - """A casting version of the unicode trait.""" - - def validate(self, obj, value): - try: +class CUnicode(Unicode): + """A casting version of the unicode trait.""" + + def validate(self, obj, value): + try: return str(value) except Exception: - 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" - + 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" + coerce_str = staticmethod(lambda _,s: s) - - def validate(self, obj, value): - value = self.coerce_str(obj, value) - + + def validate(self, obj, value): + value = self.coerce_str(obj, value) + if isinstance(value, str) and isidentifier(value): - return value - self.error(obj, value) - + return value + self.error(obj, value) + def from_string(self, s): if self.allow_none and s == 'None': return None return s -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) - +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, str) 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 + 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 elif isinstance(value, int): if value == 1: return True elif value == 0: return False - self.error(obj, value) - + self.error(obj, value) + def from_string(self, s): if self.allow_none and s == 'None': return None @@ -2274,32 +2274,32 @@ class Bool(TraitType): return False else: raise ValueError("%r is not 1, 0, true, or false") - - -class CBool(Bool): - """A casting version of the boolean trait.""" - - def validate(self, obj, value): - try: - return bool(value) + + +class CBool(Bool): + """A casting version of the boolean trait.""" + + def validate(self, obj, value): + try: + return bool(value) except Exception: - self.error(obj, value) - - -class Enum(TraitType): - """An enum whose value must be in a given sequence.""" - + 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 + self.values = values if kwargs.get('allow_none', False) and default_value is Undefined: - default_value = None + 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 validate(self, obj, value): + if value in self.values: + return value + self.error(obj, value) + def _choices_str(self, as_rst=False): """ Returns a description of the trait choices (not none).""" choices = self.values @@ -2310,12 +2310,12 @@ class Enum(TraitType): return choices def _info(self, as_rst=False): - """ Returns a description of the trait.""" + """ Returns a description of the trait.""" none = (' or %s' % ('`None`' if as_rst else 'None') if self.allow_none else '') return 'any of %s%s' % (self._choices_str(as_rst), none) - + def info(self): return self._info(as_rst=False) @@ -2329,21 +2329,21 @@ class Enum(TraitType): return _safe_literal_eval(s) -class CaselessStrEnum(Enum): - """An enum of strings where the case should be ignored.""" +class CaselessStrEnum(Enum): + """An enum of strings where the case should be ignored.""" def __init__(self, values, default_value=Undefined, **kwargs): super().__init__(values, default_value=default_value, **kwargs) - def validate(self, obj, value): + def validate(self, obj, value): if not isinstance(value, str): - self.error(obj, value) - - for v in self.values: - if v.lower() == value.lower(): - return v - self.error(obj, value) - + self.error(obj, value) + + for v in self.values: + if v.lower() == value.lower(): + return v + self.error(obj, value) + def _info(self, as_rst=False): """ Returns a description of the trait.""" none = (' or %s' % ('`None`' if as_rst else 'None') @@ -2408,52 +2408,52 @@ class FuzzyEnum(Enum): return self._info(as_rst=True) -class Container(Instance): - """An instance of a container (list, set, etc.) - - To be subclassed by overriding klass. - """ +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 + klass = None + _cast_types = () + _valid_defaults = SequenceTypes + _trait = None _literal_from_string_pairs = ("[]", "()") - + def __init__(self, trait=None, default_value=Undefined, **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 + """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) + further keys for extensions to the Trait (e.g. config) + + """ - """ - - # allow List([values]): + # allow List([values]): if trait is not None and default_value is Undefined and not is_trait(trait): - default_value = trait - trait = None - + default_value = trait + trait = None + if default_value is None and not kwargs.get("allow_none", False): # improve backward-compatibility for possible subclasses # specifying default_value=None as default, @@ -2468,68 +2468,68 @@ class Container(Instance): default_value = Undefined if default_value is Undefined: - args = () + args = () elif default_value is None: # default_value back on kwargs for super() to handle args = () kwargs["default_value"] = None - elif isinstance(default_value, self._valid_defaults): - args = (default_value,) - else: + 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): + + 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: + 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 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) + + 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 as error: self.error(obj, v, error) - 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) - + 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) + def from_string(self, s): """Load value from a single string""" if not isinstance(s, str): @@ -2539,7 +2539,7 @@ class Container(Instance): except Exception: test = None return self.validate(None, test) - + def from_string_list(self, s_list): """Return the value from a list of config strings @@ -2588,11 +2588,11 @@ class Container(Instance): return s -class List(Container): - """An instance of a Python list.""" - klass = list - _cast_types = (tuple,) - +class List(Container): + """An instance of a Python list.""" + klass = list + _cast_types = (tuple,) + def __init__( self, trait=None, @@ -2601,64 +2601,64 @@ class List(Container): 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, + """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 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 set(self, obj, value): if isinstance(value, str): return super().set(obj, [value]) else: return super().set(obj, value) + - -class Set(List): - """An instance of a Python set.""" - klass = set - _cast_types = (tuple, list) - +class Set(List): + """An instance of a Python set.""" + klass = set + _cast_types = (tuple, list) + _literal_from_string_pairs = ("[]", "()", "{}") - # Redefine __init__ just to make the docstring more accurate. + # Redefine __init__ just to make the docstring more accurate. def __init__( self, trait=None, @@ -2667,84 +2667,84 @@ class Set(List): 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 - """ + """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) - + def default_value_repr(self): # Ensure default value is sorted for a reproducible build list_repr = repr(sorted(self.make_dynamic_default())) if list_repr == '[]': return 'set()' return '{'+list_repr[1:-1]+'}' + +class Tuple(Container): + """An instance of a Python tuple.""" -class Tuple(Container): - """An instance of a Python tuple.""" - - klass = tuple - _cast_types = (list,) - + 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 - ---------- + """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. + 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. **kwargs Other kwargs passed to `Container` - """ + """ 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 = () - + # 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 None and not kwargs.get("allow_none", False): # improve backward-compatibility for possible subclasses # specifying default_value=None as default, @@ -2758,22 +2758,22 @@ class Tuple(Container): ) default_value = Undefined - if default_value is Undefined: - args = () + if default_value is Undefined: + args = () elif default_value is None: # default_value back on kwargs for super() to handle args = () kwargs["default_value"] = None - elif isinstance(default_value, self._valid_defaults): - args = (default_value,) - else: + 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): + + 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.", @@ -2782,12 +2782,12 @@ class Tuple(Container): ) trait = trait() self._traits.append(trait) - + if self._traits and (default_value is None or default_value is Undefined): - # don't allow default to be an empty container if length is specified - args = 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 item_from_string(self, s, index): """Cast a single item from a string @@ -2799,41 +2799,41 @@ class Tuple(Container): return s return self._traits[index].from_string(s) - 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) + 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 as error: self.error(obj, v, error) - 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): + 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. - + One or more traits can be passed to the constructor to validate the keys and/or values of the dict. If you need more detailed validation, @@ -2851,10 +2851,10 @@ class Dict(Instance): def __init__(self, value_trait=None, per_key_traits=None, key_trait=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``. - + + The default value is created by doing ``dict(default_value)``, + which creates a copy of the ``default_value``. + Parameters ---------- value_trait : TraitType [ optional ] @@ -2876,15 +2876,15 @@ class Dict(Instance): -------- >>> d = Dict(Unicode()) a dict whose values must be text - + >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()}) d2['n'] must be an integer d2['s'] must be text - + >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode()) d3's keys must be text d3's values must be integers - """ + """ # handle deprecated keywords trait = kwargs.pop('trait', None) @@ -2908,40 +2908,40 @@ class Dict(Instance): stacklevel=2, ) - # Handling positional arguments + # Handling positional arguments if default_value is Undefined and value_trait is not None: if not is_trait(value_trait): default_value = value_trait value_trait = None - + if key_trait is None and per_key_traits is not None: if is_trait(per_key_traits): key_trait = per_key_traits per_key_traits = 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 + # 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(value_trait): if isinstance(value_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) + DeprecationWarning, stacklevel=2) value_trait = value_trait() self._value_trait = value_trait elif value_trait is not None: raise TypeError("`value_trait` must be a Trait or None, got %s" % repr_type(value_trait)) - + if is_trait(key_trait): if isinstance(key_trait, type): warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)" @@ -2951,32 +2951,32 @@ class Dict(Instance): self._key_trait = key_trait elif key_trait is not None: raise TypeError("`key_trait` must be a Trait or None, got %s" % repr_type(key_trait)) - + self._per_key_traits = per_key_traits super(Dict, self).__init__(klass=dict, args=args, **kwargs) - + def element_error(self, obj, element, validator, side='Values'): e = side + " 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): + % (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): per_key_override = self._per_key_traits or {} key_trait = self._key_trait value_trait = self._value_trait if not (key_trait or value_trait or per_key_override): - return value + return value - validated = {} - for key in value: + validated = {} + for key in value: v = value[key] if key_trait: try: @@ -2991,28 +2991,28 @@ class Dict(Instance): self.element_error(obj, v, active_value_trait, 'Values') validated[key] = v - return self.klass(validated) - - def class_init(self, cls, name): + return self.klass(validated) + + def class_init(self, cls, name): if isinstance(self._value_trait, TraitType): self._value_trait.class_init(cls, None) if isinstance(self._key_trait, TraitType): self._key_trait.class_init(cls, None) if self._per_key_traits is not None: for trait in self._per_key_traits.values(): - trait.class_init(cls, None) - super(Dict, self).class_init(cls, name) - - def instance_init(self, obj): + trait.class_init(cls, None) + super(Dict, self).class_init(cls, name) + + def instance_init(self, obj): if isinstance(self._value_trait, TraitType): self._value_trait.instance_init(obj) if isinstance(self._key_trait, TraitType): self._key_trait.instance_init(obj) if self._per_key_traits is not None: for trait in self._per_key_traits.values(): - trait.instance_init(obj) - super(Dict, self).instance_init(obj) - + trait.instance_init(obj) + super(Dict, self).instance_init(obj) + def from_string(self, s): """Load value from a single string""" if not isinstance(s, str): @@ -3024,7 +3024,7 @@ class Dict(Instance): if isinstance(test, dict): return test raise - + def from_string_list(self, s_list): """Return a dict from a list of config strings. @@ -3086,24 +3086,24 @@ class Dict(Instance): return {key: value} -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: +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], str) and isinstance(value[1], int): - port = value[1] - if port >= 0 and port <= 65535: - return value - self.error(obj, value) - + port = value[1] + if port >= 0 and port <= 65535: + return value + self.error(obj, value) + def from_string(self, s): if self.allow_none and s == 'None': return None @@ -3114,19 +3114,19 @@ class TCPAddress(TraitType): return (ip, port) -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) +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 Exception: - self.error(obj, value) + self.error(obj, value) class UseEnum(TraitType): diff --git a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py index 22511437bd..086b21999a 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py +++ b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py @@ -1,19 +1,19 @@ -""" - 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 - -# Unmodified from sphinx below this line - +""" + 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 + +# Unmodified from sphinx below this line + from functools import partial - + def getargspec(func): """Like inspect.getargspec but supports functools.partial as well.""" if inspect.ismethod(func): @@ -29,12 +29,12 @@ def getargspec(func): args = args[len(func.args):] for arg in func.keywords or (): try: - i = args.index(arg) - len(args) - del args[i] - try: + i = args.index(arg) - len(args) + del args[i] + try: del defaults[i] - except IndexError: - pass + except IndexError: + pass except ValueError: # must be a kwonly arg i = kwoargs.index(arg) del kwoargs[i] diff --git a/contrib/python/traitlets/py3/traitlets/utils/importstring.py b/contrib/python/traitlets/py3/traitlets/utils/importstring.py index defad8f183..6d9e0986cd 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/importstring.py +++ b/contrib/python/traitlets/py3/traitlets/utils/importstring.py @@ -1,38 +1,38 @@ -""" -A simple utility to import something by its string name. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -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 +""" +A simple utility to import something by its string name. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +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 + + Returns + ------- + mod : module object The module that was imported. - """ + """ if not isinstance(name, str): - raise TypeError("import_item accepts strings, not '%s'." % type(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]) + raise TypeError("import_item accepts strings, not '%s'." % type(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/py3/traitlets/utils/sentinel.py b/contrib/python/traitlets/py3/traitlets/utils/sentinel.py index 0760bec8b5..de6b3e508f 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/sentinel.py +++ b/contrib/python/traitlets/py3/traitlets/utils/sentinel.py @@ -1,20 +1,20 @@ -"""Sentinel class for constants with useful reprs""" +"""Sentinel class for constants with useful reprs""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + -# 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): +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 - + def __copy__(self): return self diff --git a/contrib/python/traitlets/py3/ya.make b/contrib/python/traitlets/py3/ya.make index 46980f21b3..be00a1bd39 100644 --- a/contrib/python/traitlets/py3/ya.make +++ b/contrib/python/traitlets/py3/ya.make @@ -1,49 +1,49 @@ # Generated by devtools/yamaker (pypi). PY3_LIBRARY() - + PROVIDES(python_traitlets) OWNER(borman nslus g:python-contrib) - + VERSION(5.1.1) LICENSE(BSD-3-Clause) 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 + +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/config/sphinxdoc.py - traitlets/log.py + traitlets/log.py traitlets/tests/__init__.py traitlets/tests/_warnings.py traitlets/tests/utils.py - traitlets/traitlets.py - traitlets/utils/__init__.py + traitlets/traitlets.py + traitlets/utils/__init__.py traitlets/utils/bunch.py traitlets/utils/decorators.py traitlets/utils/descriptions.py - traitlets/utils/getargspec.py - traitlets/utils/importstring.py - traitlets/utils/sentinel.py + traitlets/utils/getargspec.py + traitlets/utils/importstring.py + traitlets/utils/sentinel.py traitlets/utils/text.py -) - +) + RESOURCE_FILES( PREFIX contrib/python/traitlets/py3/ .dist-info/METADATA .dist-info/top_level.txt ) -END() +END() RECURSE_FOR_TESTS( tests diff --git a/contrib/python/traitlets/ya.make b/contrib/python/traitlets/ya.make index 3156aae8c5..934eb39823 100644 --- a/contrib/python/traitlets/ya.make +++ b/contrib/python/traitlets/ya.make @@ -1,5 +1,5 @@ PY23_LIBRARY() - + LICENSE(Service-Py23-Proxy) OWNER(g:python-contrib) @@ -12,7 +12,7 @@ ENDIF() NO_LINT() -END() +END() RECURSE( py2 |