diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
commit | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (patch) | |
tree | 64175d5cadab313b3e7039ebaa06c5bc3295e274 /contrib/python/traitlets | |
parent | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (diff) | |
download | ydb-e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/traitlets')
47 files changed, 8049 insertions, 8049 deletions
diff --git a/contrib/python/traitlets/py2/.dist-info/METADATA b/contrib/python/traitlets/py2/.dist-info/METADATA index d0c2057662..bd031a5c30 100644 --- a/contrib/python/traitlets/py2/.dist-info/METADATA +++ b/contrib/python/traitlets/py2/.dist-info/METADATA @@ -1,34 +1,34 @@ -Metadata-Version: 2.1 -Name: traitlets -Version: 4.3.3 -Summary: Traitlets Python config system -Home-page: http://ipython.org -Author: IPython Development Team -Author-email: ipython-dev@scipy.org -License: BSD -Keywords: Interactive,Interpreter,Shell,Web -Platform: Linux -Platform: Mac OS X -Platform: Windows -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: System Administrators -Classifier: Intended Audience :: Science/Research -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Requires-Dist: ipython-genutils -Requires-Dist: six -Requires-Dist: decorator -Requires-Dist: enum34 ; python_version=="2.7" -Requires-Dist: enum34 ; python_version=="3.3" -Provides-Extra: test -Requires-Dist: pytest ; extra == 'test' -Requires-Dist: mock ; (python_version=="2.7") and extra == 'test' - -A configuration system for Python applications. - - +Metadata-Version: 2.1 +Name: traitlets +Version: 4.3.3 +Summary: Traitlets Python config system +Home-page: http://ipython.org +Author: IPython Development Team +Author-email: ipython-dev@scipy.org +License: BSD +Keywords: Interactive,Interpreter,Shell,Web +Platform: Linux +Platform: Mac OS X +Platform: Windows +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Requires-Dist: ipython-genutils +Requires-Dist: six +Requires-Dist: decorator +Requires-Dist: enum34 ; python_version=="2.7" +Requires-Dist: enum34 ; python_version=="3.3" +Provides-Extra: test +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: mock ; (python_version=="2.7") and extra == 'test' + +A configuration system for Python applications. + + diff --git a/contrib/python/traitlets/py2/.dist-info/top_level.txt b/contrib/python/traitlets/py2/.dist-info/top_level.txt index 3d938864d2..adfea9c6eb 100644 --- a/contrib/python/traitlets/py2/.dist-info/top_level.txt +++ b/contrib/python/traitlets/py2/.dist-info/top_level.txt @@ -1 +1 @@ -traitlets +traitlets diff --git a/contrib/python/traitlets/py2/README.md b/contrib/python/traitlets/py2/README.md index 436b97c19b..aa288947ef 100644 --- a/contrib/python/traitlets/py2/README.md +++ b/contrib/python/traitlets/py2/README.md @@ -1,143 +1,143 @@ -# Traitlets - -[![Build Status](https://travis-ci.org/ipython/traitlets.svg?branch=master)](https://travis-ci.org/ipython/traitlets) -[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](http://traitlets.readthedocs.org/en/latest/?badge=latest) - -Traitlets is a pure Python library enabling: - - - the enforcement of strong typing for attributes of Python objects - (typed attributes are called "traits"), - - notifications on changes of trait attributes, - - automatic validation and coercion of trait attributes when attempting a - change. - -Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html) -pattern. - -Traitlets powers the configuration system of IPython and Jupyter -and the declarative API of IPython interactive widgets. - -## Installation - -For a local installation, make sure you have -[pip installed](https://pip.pypa.io/en/stable/installing/) and run: - -```bash -pip install traitlets -``` - -For a **development installation**, clone this repository, change into the -`traitlets` root directory, and run pip: - -```bash -git clone https://github.com/ipython/traitlets.git -cd traitlets -pip install -e . -``` - -## Running the tests - -```bash -pip install "traitlets[test]" -py.test traitlets -``` - -## Usage - -Any class with trait attributes must inherit from `HasTraits`. -For the list of available trait types and their properties, see the -[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html) -section of the documentation. - -### Dynamic default values - -To calculate a default value dynamically, decorate a method of your class with -`@default({traitname})`. This method will be called on the instance, and -should return the default value. In this example, the `_username_default` -method is decorated with `@default('username')`: - -```Python -import getpass -from traitlets import HasTraits, Unicode, default - -class Identity(HasTraits): - username = Unicode() - - @default('username') - def _username_default(self): - return getpass.getuser() -``` - -### Callbacks when a trait attribute changes - -When a trait changes, an application can follow this trait change with -additional actions. - -To do something when a trait attribute is changed, decorate a method with -[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe). -The method will be called with a single argument, a dictionary which contains -an owner, new value, old value, name of the changed trait, and the event type. - -In this example, the `_num_changed` method is decorated with ``@observe(`num`)``: - -```Python -from traitlets import HasTraits, Integer, observe - -class TraitletsExample(HasTraits): - num = Integer(5, help="a number").tag(config=True) - - @observe('num') - def _num_changed(self, change): - print("{name} changed from {old} to {new}".format(**change)) -``` - -and is passed the following dictionary when called: - -```Python -{ - 'owner': object, # The HasTraits instance - 'new': 6, # The new value - 'old': 5, # The old value - 'name': "foo", # The name of the changed trait - 'type': 'change', # The event type of the notification, usually 'change' -} -``` - -### Validation and coercion - -Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or -coercion logic. In addition, we can register custom cross-validators -that may depend on the state of other attributes. For example: - -```Python -from traitlets import HasTraits, TraitError, Int, Bool, validate - -class Parity(HasTraits): - value = Int() - parity = Int() - - @validate('value') - def _valid_value(self, proposal): - if proposal['value'] % 2 != self.parity: - raise TraitError('value and parity should be consistent') - return proposal['value'] - - @validate('parity') - def _valid_parity(self, proposal): - parity = proposal['value'] - if parity not in [0, 1]: - raise TraitError('parity should be 0 or 1') - if self.value % 2 != parity: - raise TraitError('value and parity should be consistent') - return proposal['value'] - -parity_check = Parity(value=2) - -# Changing required parity and value together while holding cross validation -with parity_check.hold_trait_notifications(): - parity_check.value = 1 - parity_check.parity = 1 -``` - -However, we **recommend** that custom cross-validators don't modify the state -of the HasTraits instance. +# Traitlets + +[![Build Status](https://travis-ci.org/ipython/traitlets.svg?branch=master)](https://travis-ci.org/ipython/traitlets) +[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](http://traitlets.readthedocs.org/en/latest/?badge=latest) + +Traitlets is a pure Python library enabling: + + - the enforcement of strong typing for attributes of Python objects + (typed attributes are called "traits"), + - notifications on changes of trait attributes, + - automatic validation and coercion of trait attributes when attempting a + change. + +Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html) +pattern. + +Traitlets powers the configuration system of IPython and Jupyter +and the declarative API of IPython interactive widgets. + +## Installation + +For a local installation, make sure you have +[pip installed](https://pip.pypa.io/en/stable/installing/) and run: + +```bash +pip install traitlets +``` + +For a **development installation**, clone this repository, change into the +`traitlets` root directory, and run pip: + +```bash +git clone https://github.com/ipython/traitlets.git +cd traitlets +pip install -e . +``` + +## Running the tests + +```bash +pip install "traitlets[test]" +py.test traitlets +``` + +## Usage + +Any class with trait attributes must inherit from `HasTraits`. +For the list of available trait types and their properties, see the +[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html) +section of the documentation. + +### Dynamic default values + +To calculate a default value dynamically, decorate a method of your class with +`@default({traitname})`. This method will be called on the instance, and +should return the default value. In this example, the `_username_default` +method is decorated with `@default('username')`: + +```Python +import getpass +from traitlets import HasTraits, Unicode, default + +class Identity(HasTraits): + username = Unicode() + + @default('username') + def _username_default(self): + return getpass.getuser() +``` + +### Callbacks when a trait attribute changes + +When a trait changes, an application can follow this trait change with +additional actions. + +To do something when a trait attribute is changed, decorate a method with +[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe). +The method will be called with a single argument, a dictionary which contains +an owner, new value, old value, name of the changed trait, and the event type. + +In this example, the `_num_changed` method is decorated with ``@observe(`num`)``: + +```Python +from traitlets import HasTraits, Integer, observe + +class TraitletsExample(HasTraits): + num = Integer(5, help="a number").tag(config=True) + + @observe('num') + def _num_changed(self, change): + print("{name} changed from {old} to {new}".format(**change)) +``` + +and is passed the following dictionary when called: + +```Python +{ + 'owner': object, # The HasTraits instance + 'new': 6, # The new value + 'old': 5, # The old value + 'name': "foo", # The name of the changed trait + 'type': 'change', # The event type of the notification, usually 'change' +} +``` + +### Validation and coercion + +Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or +coercion logic. In addition, we can register custom cross-validators +that may depend on the state of other attributes. For example: + +```Python +from traitlets import HasTraits, TraitError, Int, Bool, validate + +class Parity(HasTraits): + value = Int() + parity = Int() + + @validate('value') + def _valid_value(self, proposal): + if proposal['value'] % 2 != self.parity: + raise TraitError('value and parity should be consistent') + return proposal['value'] + + @validate('parity') + def _valid_parity(self, proposal): + parity = proposal['value'] + if parity not in [0, 1]: + raise TraitError('parity should be 0 or 1') + if self.value % 2 != parity: + raise TraitError('value and parity should be consistent') + return proposal['value'] + +parity_check = Parity(value=2) + +# Changing required parity and value together while holding cross validation +with parity_check.hold_trait_notifications(): + parity_check.value = 1 + parity_check.parity = 1 +``` + +However, we **recommend** that custom cross-validators don't modify the state +of the HasTraits instance. diff --git a/contrib/python/traitlets/py2/tests/ya.make b/contrib/python/traitlets/py2/tests/ya.make index 087a9ab183..d2d3e3b9bf 100644 --- a/contrib/python/traitlets/py2/tests/ya.make +++ b/contrib/python/traitlets/py2/tests/ya.make @@ -6,18 +6,18 @@ PEERDIR( contrib/python/traitlets ) -ENV( - YA_PYTEST_DISABLE_DOCTEST=yes -) +ENV( + YA_PYTEST_DISABLE_DOCTEST=yes +) + +SRCDIR(contrib/python/traitlets/py2/traitlets) -SRCDIR(contrib/python/traitlets/py2/traitlets) - -TEST_SRCS( - tests/__init__.py - tests/_warnings.py - tests/test_traitlets.py - tests/test_traitlets_enum.py - tests/utils.py +TEST_SRCS( + tests/__init__.py + tests/_warnings.py + tests/test_traitlets.py + tests/test_traitlets_enum.py + tests/utils.py ) NO_LINT() diff --git a/contrib/python/traitlets/py2/traitlets/_version.py b/contrib/python/traitlets/py2/traitlets/_version.py index e9ec9e6da6..ed16b3c1e1 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_info = (4, 3, 3) __version__ = '.'.join(map(str, version_info)) diff --git a/contrib/python/traitlets/py2/traitlets/config/application.py b/contrib/python/traitlets/py2/traitlets/config/application.py index 147dc9f068..d3a4c45e77 100644 --- a/contrib/python/traitlets/py2/traitlets/config/application.py +++ b/contrib/python/traitlets/py2/traitlets/config/application.py @@ -267,7 +267,7 @@ class Application(SingletonConfigurable): """ ) - _loaded_config_files = List() + _loaded_config_files = List() def __init__(self, **kwargs): SingletonConfigurable.__init__(self, **kwargs) @@ -558,7 +558,7 @@ class Application(SingletonConfigurable): loaded = [] filenames = [] for loader in [pyloader, jsonloader]: - config = None + config = None try: config = loader.load_config() except ConfigFileNotFound: @@ -584,26 +584,26 @@ class Application(SingletonConfigurable): " {1} has higher priority: {2}".format( filename, loader.full_filename, json.dumps(collisions, indent=2), )) - yield (config, loader.full_filename) + 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[:] + @property + def loaded_config_files(self): + """Currently loaded configuration files""" + return self._loaded_config_files[:] @catch_config_error def load_config_file(self, filename, path=None): """Load config files by filename and path.""" filename, ext = os.path.splitext(filename) new_config = Config() - for (config, filename) in self._load_config_files(filename, path=path, log=self.log, + for (config, filename) in self._load_config_files(filename, path=path, log=self.log, raise_config_file_errors=self.raise_config_file_errors, ): new_config.merge(config) - if filename not in self._loaded_config_files: # only add to list of loaded files if not previously loaded - self._loaded_config_files.append(filename) + if filename not in self._loaded_config_files: # only add to list of loaded files if not previously loaded + self._loaded_config_files.append(filename) # add self.cli_config to preserve CLI config priority new_config.merge(self.cli_config) self.update_config(new_config) diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py b/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py index ede020bc9c..39bd9b786e 100644 --- a/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py +++ b/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py @@ -1,421 +1,421 @@ -# coding: utf-8 -""" -Tests for traitlets.config.application.Application -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import json -import logging -import os -from io import StringIO -from unittest import TestCase - -try: - from unittest import mock -except ImportError: - import mock - -pjoin = os.path.join - -from pytest import mark - -from traitlets.config.configurable import Configurable -from traitlets.config.loader import Config -from traitlets.tests.utils import check_help_output, check_help_all_output - -from traitlets.config.application import ( - Application -) - -from ipython_genutils.tempdir import TemporaryDirectory -from traitlets.traitlets import ( - Bool, Unicode, Integer, List, Dict -) - - -class Foo(Configurable): - - i = Integer(0, help="The integer i.").tag(config=True) - j = Integer(1, help="The integer j.").tag(config=True) - name = Unicode(u'Brian', help="First name.").tag(config=True) - - -class Bar(Configurable): - - b = Integer(0, help="The integer b.").tag(config=True) - enabled = Bool(True, help="Enable bar.").tag(config=True) - - -class MyApp(Application): - - name = Unicode(u'myapp') - running = Bool(False, help="Is the app running?").tag(config=True) - classes = List([Bar, Foo]) - config_file = Unicode(u'', help="Load this config file").tag(config=True) - - warn_tpyo = Unicode(u"yes the name is wrong on purpose", config=True, - help="Should print a warning if `MyApp.warn-typo=...` command is passed") - - aliases = Dict({ - 'i' : 'Foo.i', - 'j' : 'Foo.j', - 'name' : 'Foo.name', - 'enabled' : 'Bar.enabled', - 'log-level' : 'Application.log_level', - }) - - flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"), - disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False"), - crit=({'Application' : {'log_level' : logging.CRITICAL}}, - "set level=CRITICAL"), - )) - - def init_foo(self): - self.foo = Foo(parent=self) - - def init_bar(self): - self.bar = Bar(parent=self) - - -class TestApplication(TestCase): - - def test_log(self): - stream = StringIO() - app = MyApp(log_level=logging.INFO) - handler = logging.StreamHandler(stream) - # trigger reconstruction of the log formatter - app.log.handlers = [handler] - app.log_format = "%(message)s" - app.log_datefmt = "%Y-%m-%d %H:%M" - app.log.info("hello") - assert "hello" in stream.getvalue() - - def test_basic(self): - app = MyApp() - self.assertEqual(app.name, u'myapp') - self.assertEqual(app.running, False) - self.assertEqual(app.classes, [MyApp,Bar,Foo]) - self.assertEqual(app.config_file, u'') - - def test_config(self): - app = MyApp() - app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"]) - config = app.config - self.assertEqual(config.Foo.i, 10) - self.assertEqual(config.Foo.j, 10) - self.assertEqual(config.Bar.enabled, False) - self.assertEqual(config.MyApp.log_level,50) - - def test_config_propagation(self): - app = MyApp() - app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"]) - app.init_foo() - app.init_bar() - self.assertEqual(app.foo.i, 10) - self.assertEqual(app.foo.j, 10) - self.assertEqual(app.bar.enabled, False) - - def test_cli_priority(self): - """Test that loading config files does not override CLI options""" - name = 'config.py' - class TestApp(Application): - value = Unicode().tag(config=True) - config_file_loaded = Bool().tag(config=True) - aliases = {'v': 'TestApp.value'} - app = TestApp() - with TemporaryDirectory() as td: - config_file = pjoin(td, name) - with open(config_file, 'w') as f: - f.writelines([ - "c.TestApp.value = 'config file'\n", - "c.TestApp.config_file_loaded = True\n" - ]) - - app.parse_command_line(['--v=cli']) - assert 'value' in app.config.TestApp - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - - app.load_config_file(name, path=[td]) - assert app.config_file_loaded - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - - def test_ipython_cli_priority(self): - # this test is almost entirely redundant with above, - # but we can keep it around in case of subtle issues creeping into - # the exact sequence IPython follows. - name = 'config.py' - class TestApp(Application): - value = Unicode().tag(config=True) - config_file_loaded = Bool().tag(config=True) - aliases = {'v': 'TestApp.value'} - app = TestApp() - with TemporaryDirectory() as td: - config_file = pjoin(td, name) - with open(config_file, 'w') as f: - f.writelines([ - "c.TestApp.value = 'config file'\n", - "c.TestApp.config_file_loaded = True\n" - ]) - # follow IPython's config-loading sequence to ensure CLI priority is preserved - app.parse_command_line(['--v=cli']) - # this is where IPython makes a mistake: - # it assumes app.config will not be modified, - # and storing a reference is storing a copy - cli_config = app.config - assert 'value' in app.config.TestApp - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - app.load_config_file(name, path=[td]) - assert app.config_file_loaded - # enforce cl-opts override config file opts: - # this is where IPython makes a mistake: it assumes - # that cl_config is a different object, but it isn't. - app.update_config(cli_config) - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - - def test_flags(self): - app = MyApp() - app.parse_command_line(["--disable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - app.parse_command_line(["--enable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - - def test_aliases(self): - app = MyApp() - app.parse_command_line(["--i=5", "--j=10"]) - app.init_foo() - self.assertEqual(app.foo.i, 5) - app.init_foo() - self.assertEqual(app.foo.j, 10) - - def test_flag_clobber(self): - """test that setting flags doesn't clobber existing settings""" - app = MyApp() - app.parse_command_line(["--Bar.b=5", "--disable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - self.assertEqual(app.bar.b, 5) - app.parse_command_line(["--enable", "--Bar.b=10"]) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - self.assertEqual(app.bar.b, 10) - - def test_warn_autocorrect(self): - stream = StringIO() - app = MyApp(log_level=logging.INFO) - app.log.handlers = [logging.StreamHandler(stream)] - - cfg = Config() - cfg.MyApp.warn_typo = "WOOOO" - app.config = cfg - - self.assertIn("warn_typo", stream.getvalue()) - self.assertIn("warn_tpyo", stream.getvalue()) - - - def test_flatten_flags(self): - cfg = Config() - cfg.MyApp.log_level = logging.WARN - app = MyApp() - app.update_config(cfg) - self.assertEqual(app.log_level, logging.WARN) - self.assertEqual(app.config.MyApp.log_level, logging.WARN) - app.initialize(["--crit"]) - self.assertEqual(app.log_level, logging.CRITICAL) - # this would be app.config.Application.log_level if it failed: - self.assertEqual(app.config.MyApp.log_level, logging.CRITICAL) - - def test_flatten_aliases(self): - cfg = Config() - cfg.MyApp.log_level = logging.WARN - app = MyApp() - app.update_config(cfg) - self.assertEqual(app.log_level, logging.WARN) - self.assertEqual(app.config.MyApp.log_level, logging.WARN) - app.initialize(["--log-level", "CRITICAL"]) - self.assertEqual(app.log_level, logging.CRITICAL) - # this would be app.config.Application.log_level if it failed: - self.assertEqual(app.config.MyApp.log_level, "CRITICAL") - - def test_extra_args(self): - app = MyApp() - app.parse_command_line(["--Bar.b=5", 'extra', "--disable", 'args']) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - self.assertEqual(app.bar.b, 5) - self.assertEqual(app.extra_args, ['extra', 'args']) - app = MyApp() - app.parse_command_line(["--Bar.b=5", '--', 'extra', "--disable", 'args']) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - self.assertEqual(app.bar.b, 5) - self.assertEqual(app.extra_args, ['extra', '--disable', 'args']) - - def test_unicode_argv(self): - app = MyApp() - app.parse_command_line(['ünîcødé']) - - def test_document_config_option(self): - app = MyApp() - app.document_config_options() - - def test_generate_config_file(self): - app = MyApp() - assert 'The integer b.' in app.generate_config_file() - - def test_generate_config_file_classes_to_include(self): - class NoTraits(Foo, Bar): - pass - - app = MyApp() - app.classes.append(NoTraits) - conf_txt = app.generate_config_file() - self.assertIn('The integer b.', conf_txt) - self.assertIn('# Bar(Configurable)', conf_txt) - self.assertIn('# Foo(Configurable)', conf_txt) - self.assertNotIn('# Configurable', conf_txt) - self.assertIn('# NoTraits(Foo,Bar)', conf_txt) - - def test_multi_file(self): - app = MyApp() - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory('_1') as td1: - with open(pjoin(td1, name), 'w') as f1: - f1.write("get_config().MyApp.Bar.b = 1") - with TemporaryDirectory('_2') as td2: - with open(pjoin(td2, name), 'w') as f2: - f2.write("get_config().MyApp.Bar.b = 2") - app.load_config_file(name, path=[td2, td1]) - app.init_bar() - self.assertEqual(app.bar.b, 2) - app.load_config_file(name, path=[td1, td2]) - app.init_bar() - self.assertEqual(app.bar.b, 1) - - @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') - def test_log_collisions(self): - app = MyApp() - app.log = logging.getLogger() - app.log.setLevel(logging.INFO) - name = 'config' - with TemporaryDirectory('_1') as td: - with open(pjoin(td, name + '.py'), 'w') as f: - f.write("get_config().Bar.b = 1") - with open(pjoin(td, name + '.json'), 'w') as f: - json.dump({ - 'Bar': { - 'b': 2 - } - }, f) - with self.assertLogs(app.log, logging.WARNING) as captured: - app.load_config_file(name, path=[td]) - app.init_bar() - assert app.bar.b == 2 - output = '\n'.join(captured.output) - assert 'Collision' in output - assert '1 ignored, using 2' in output - assert pjoin(td, name + '.py') in output - assert pjoin(td, name + '.json') in output - - @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') - def test_log_bad_config(self): - app = MyApp() - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory() as td: - with open(pjoin(td, name), 'w') as f: - f.write("syntax error()") - with self.assertLogs(app.log, logging.ERROR) as captured: - app.load_config_file(name, path=[td]) - output = '\n'.join(captured.output) - self.assertIn('SyntaxError', output) - - def test_raise_on_bad_config(self): - app = MyApp() - app.raise_config_file_errors = True - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory() as td: - with open(pjoin(td, name), 'w') as f: - f.write("syntax error()") - with self.assertRaises(SyntaxError): - app.load_config_file(name, path=[td]) - - def test_loaded_config_files(self): - app = MyApp() - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory('_1') as td1: - config_file = pjoin(td1, name) - with open(config_file, 'w') as f: - f.writelines([ - "c.MyApp.running = True\n" - ]) - - app.load_config_file(name, path=[td1]) - self.assertEqual(len(app.loaded_config_files), 1) - self.assertEquals(app.loaded_config_files[0], config_file) - - app.start() - self.assertEqual(app.running, True) - - # emulate an app that allows dynamic updates and update config file - with open(config_file, 'w') as f: - f.writelines([ - "c.MyApp.running = False\n" - ]) - - # reload and verify update, and that loaded_configs was not increased - app.load_config_file(name, path=[td1]) - self.assertEqual(len(app.loaded_config_files), 1) - self.assertEqual(app.running, False) - - # Attempt to update, ensure error... - with self.assertRaises(AttributeError): - app.loaded_config_files = "/foo" - - # ensure it can't be udpated via append - app.loaded_config_files.append("/bar") - self.assertEqual(len(app.loaded_config_files), 1) - - # repeat to ensure no unexpected changes occurred - app.load_config_file(name, path=[td1]) - self.assertEqual(len(app.loaded_config_files), 1) - self.assertEqual(app.running, False) - - -class DeprecatedApp(Application): - override_called = False - parent_called = False - def _config_changed(self, name, old, new): - self.override_called = True - def _capture(*args): - self.parent_called = True - with mock.patch.object(self.log, 'debug', _capture): - super(DeprecatedApp, self)._config_changed(name, old, new) - - -def test_deprecated_notifier(): - app = DeprecatedApp() - assert not app.override_called - assert not app.parent_called - app.config = Config({'A': {'b': 'c'}}) - assert app.override_called - assert app.parent_called - - -def test_help_output(): - check_help_output(__name__) - check_help_all_output(__name__) - -if __name__ == '__main__': - # for test_help_output: - MyApp.launch_instance()
\ No newline at end of file +# coding: utf-8 +""" +Tests for traitlets.config.application.Application +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import logging +import os +from io import StringIO +from unittest import TestCase + +try: + from unittest import mock +except ImportError: + import mock + +pjoin = os.path.join + +from pytest import mark + +from traitlets.config.configurable import Configurable +from traitlets.config.loader import Config +from traitlets.tests.utils import check_help_output, check_help_all_output + +from traitlets.config.application import ( + Application +) + +from ipython_genutils.tempdir import TemporaryDirectory +from traitlets.traitlets import ( + Bool, Unicode, Integer, List, Dict +) + + +class Foo(Configurable): + + i = Integer(0, help="The integer i.").tag(config=True) + j = Integer(1, help="The integer j.").tag(config=True) + name = Unicode(u'Brian', help="First name.").tag(config=True) + + +class Bar(Configurable): + + b = Integer(0, help="The integer b.").tag(config=True) + enabled = Bool(True, help="Enable bar.").tag(config=True) + + +class MyApp(Application): + + name = Unicode(u'myapp') + running = Bool(False, help="Is the app running?").tag(config=True) + classes = List([Bar, Foo]) + config_file = Unicode(u'', help="Load this config file").tag(config=True) + + warn_tpyo = Unicode(u"yes the name is wrong on purpose", config=True, + help="Should print a warning if `MyApp.warn-typo=...` command is passed") + + aliases = Dict({ + 'i' : 'Foo.i', + 'j' : 'Foo.j', + 'name' : 'Foo.name', + 'enabled' : 'Bar.enabled', + 'log-level' : 'Application.log_level', + }) + + flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"), + disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False"), + crit=({'Application' : {'log_level' : logging.CRITICAL}}, + "set level=CRITICAL"), + )) + + def init_foo(self): + self.foo = Foo(parent=self) + + def init_bar(self): + self.bar = Bar(parent=self) + + +class TestApplication(TestCase): + + def test_log(self): + stream = StringIO() + app = MyApp(log_level=logging.INFO) + handler = logging.StreamHandler(stream) + # trigger reconstruction of the log formatter + app.log.handlers = [handler] + app.log_format = "%(message)s" + app.log_datefmt = "%Y-%m-%d %H:%M" + app.log.info("hello") + assert "hello" in stream.getvalue() + + def test_basic(self): + app = MyApp() + self.assertEqual(app.name, u'myapp') + self.assertEqual(app.running, False) + self.assertEqual(app.classes, [MyApp,Bar,Foo]) + self.assertEqual(app.config_file, u'') + + def test_config(self): + app = MyApp() + app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"]) + config = app.config + self.assertEqual(config.Foo.i, 10) + self.assertEqual(config.Foo.j, 10) + self.assertEqual(config.Bar.enabled, False) + self.assertEqual(config.MyApp.log_level,50) + + def test_config_propagation(self): + app = MyApp() + app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"]) + app.init_foo() + app.init_bar() + self.assertEqual(app.foo.i, 10) + self.assertEqual(app.foo.j, 10) + self.assertEqual(app.bar.enabled, False) + + def test_cli_priority(self): + """Test that loading config files does not override CLI options""" + name = 'config.py' + class TestApp(Application): + value = Unicode().tag(config=True) + config_file_loaded = Bool().tag(config=True) + aliases = {'v': 'TestApp.value'} + app = TestApp() + with TemporaryDirectory() as td: + config_file = pjoin(td, name) + with open(config_file, 'w') as f: + f.writelines([ + "c.TestApp.value = 'config file'\n", + "c.TestApp.config_file_loaded = True\n" + ]) + + app.parse_command_line(['--v=cli']) + assert 'value' in app.config.TestApp + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + + app.load_config_file(name, path=[td]) + assert app.config_file_loaded + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + + def test_ipython_cli_priority(self): + # this test is almost entirely redundant with above, + # but we can keep it around in case of subtle issues creeping into + # the exact sequence IPython follows. + name = 'config.py' + class TestApp(Application): + value = Unicode().tag(config=True) + config_file_loaded = Bool().tag(config=True) + aliases = {'v': 'TestApp.value'} + app = TestApp() + with TemporaryDirectory() as td: + config_file = pjoin(td, name) + with open(config_file, 'w') as f: + f.writelines([ + "c.TestApp.value = 'config file'\n", + "c.TestApp.config_file_loaded = True\n" + ]) + # follow IPython's config-loading sequence to ensure CLI priority is preserved + app.parse_command_line(['--v=cli']) + # this is where IPython makes a mistake: + # it assumes app.config will not be modified, + # and storing a reference is storing a copy + cli_config = app.config + assert 'value' in app.config.TestApp + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + app.load_config_file(name, path=[td]) + assert app.config_file_loaded + # enforce cl-opts override config file opts: + # this is where IPython makes a mistake: it assumes + # that cl_config is a different object, but it isn't. + app.update_config(cli_config) + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + + def test_flags(self): + app = MyApp() + app.parse_command_line(["--disable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + app.parse_command_line(["--enable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + + def test_aliases(self): + app = MyApp() + app.parse_command_line(["--i=5", "--j=10"]) + app.init_foo() + self.assertEqual(app.foo.i, 5) + app.init_foo() + self.assertEqual(app.foo.j, 10) + + def test_flag_clobber(self): + """test that setting flags doesn't clobber existing settings""" + app = MyApp() + app.parse_command_line(["--Bar.b=5", "--disable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + self.assertEqual(app.bar.b, 5) + app.parse_command_line(["--enable", "--Bar.b=10"]) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + self.assertEqual(app.bar.b, 10) + + def test_warn_autocorrect(self): + stream = StringIO() + app = MyApp(log_level=logging.INFO) + app.log.handlers = [logging.StreamHandler(stream)] + + cfg = Config() + cfg.MyApp.warn_typo = "WOOOO" + app.config = cfg + + self.assertIn("warn_typo", stream.getvalue()) + self.assertIn("warn_tpyo", stream.getvalue()) + + + def test_flatten_flags(self): + cfg = Config() + cfg.MyApp.log_level = logging.WARN + app = MyApp() + app.update_config(cfg) + self.assertEqual(app.log_level, logging.WARN) + self.assertEqual(app.config.MyApp.log_level, logging.WARN) + app.initialize(["--crit"]) + self.assertEqual(app.log_level, logging.CRITICAL) + # this would be app.config.Application.log_level if it failed: + self.assertEqual(app.config.MyApp.log_level, logging.CRITICAL) + + def test_flatten_aliases(self): + cfg = Config() + cfg.MyApp.log_level = logging.WARN + app = MyApp() + app.update_config(cfg) + self.assertEqual(app.log_level, logging.WARN) + self.assertEqual(app.config.MyApp.log_level, logging.WARN) + app.initialize(["--log-level", "CRITICAL"]) + self.assertEqual(app.log_level, logging.CRITICAL) + # this would be app.config.Application.log_level if it failed: + self.assertEqual(app.config.MyApp.log_level, "CRITICAL") + + def test_extra_args(self): + app = MyApp() + app.parse_command_line(["--Bar.b=5", 'extra', "--disable", 'args']) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + self.assertEqual(app.bar.b, 5) + self.assertEqual(app.extra_args, ['extra', 'args']) + app = MyApp() + app.parse_command_line(["--Bar.b=5", '--', 'extra', "--disable", 'args']) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + self.assertEqual(app.bar.b, 5) + self.assertEqual(app.extra_args, ['extra', '--disable', 'args']) + + def test_unicode_argv(self): + app = MyApp() + app.parse_command_line(['ünîcødé']) + + def test_document_config_option(self): + app = MyApp() + app.document_config_options() + + def test_generate_config_file(self): + app = MyApp() + assert 'The integer b.' in app.generate_config_file() + + def test_generate_config_file_classes_to_include(self): + class NoTraits(Foo, Bar): + pass + + app = MyApp() + app.classes.append(NoTraits) + conf_txt = app.generate_config_file() + self.assertIn('The integer b.', conf_txt) + self.assertIn('# Bar(Configurable)', conf_txt) + self.assertIn('# Foo(Configurable)', conf_txt) + self.assertNotIn('# Configurable', conf_txt) + self.assertIn('# NoTraits(Foo,Bar)', conf_txt) + + def test_multi_file(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory('_1') as td1: + with open(pjoin(td1, name), 'w') as f1: + f1.write("get_config().MyApp.Bar.b = 1") + with TemporaryDirectory('_2') as td2: + with open(pjoin(td2, name), 'w') as f2: + f2.write("get_config().MyApp.Bar.b = 2") + app.load_config_file(name, path=[td2, td1]) + app.init_bar() + self.assertEqual(app.bar.b, 2) + app.load_config_file(name, path=[td1, td2]) + app.init_bar() + self.assertEqual(app.bar.b, 1) + + @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') + def test_log_collisions(self): + app = MyApp() + app.log = logging.getLogger() + app.log.setLevel(logging.INFO) + name = 'config' + with TemporaryDirectory('_1') as td: + with open(pjoin(td, name + '.py'), 'w') as f: + f.write("get_config().Bar.b = 1") + with open(pjoin(td, name + '.json'), 'w') as f: + json.dump({ + 'Bar': { + 'b': 2 + } + }, f) + with self.assertLogs(app.log, logging.WARNING) as captured: + app.load_config_file(name, path=[td]) + app.init_bar() + assert app.bar.b == 2 + output = '\n'.join(captured.output) + assert 'Collision' in output + assert '1 ignored, using 2' in output + assert pjoin(td, name + '.py') in output + assert pjoin(td, name + '.json') in output + + @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') + def test_log_bad_config(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory() as td: + with open(pjoin(td, name), 'w') as f: + f.write("syntax error()") + with self.assertLogs(app.log, logging.ERROR) as captured: + app.load_config_file(name, path=[td]) + output = '\n'.join(captured.output) + self.assertIn('SyntaxError', output) + + def test_raise_on_bad_config(self): + app = MyApp() + app.raise_config_file_errors = True + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory() as td: + with open(pjoin(td, name), 'w') as f: + f.write("syntax error()") + with self.assertRaises(SyntaxError): + app.load_config_file(name, path=[td]) + + def test_loaded_config_files(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory('_1') as td1: + config_file = pjoin(td1, name) + with open(config_file, 'w') as f: + f.writelines([ + "c.MyApp.running = True\n" + ]) + + app.load_config_file(name, path=[td1]) + self.assertEqual(len(app.loaded_config_files), 1) + self.assertEquals(app.loaded_config_files[0], config_file) + + app.start() + self.assertEqual(app.running, True) + + # emulate an app that allows dynamic updates and update config file + with open(config_file, 'w') as f: + f.writelines([ + "c.MyApp.running = False\n" + ]) + + # reload and verify update, and that loaded_configs was not increased + app.load_config_file(name, path=[td1]) + self.assertEqual(len(app.loaded_config_files), 1) + self.assertEqual(app.running, False) + + # Attempt to update, ensure error... + with self.assertRaises(AttributeError): + app.loaded_config_files = "/foo" + + # ensure it can't be udpated via append + app.loaded_config_files.append("/bar") + self.assertEqual(len(app.loaded_config_files), 1) + + # repeat to ensure no unexpected changes occurred + app.load_config_file(name, path=[td1]) + self.assertEqual(len(app.loaded_config_files), 1) + self.assertEqual(app.running, False) + + +class DeprecatedApp(Application): + override_called = False + parent_called = False + def _config_changed(self, name, old, new): + self.override_called = True + def _capture(*args): + self.parent_called = True + with mock.patch.object(self.log, 'debug', _capture): + super(DeprecatedApp, self)._config_changed(name, old, new) + + +def test_deprecated_notifier(): + app = DeprecatedApp() + assert not app.override_called + assert not app.parent_called + app.config = Config({'A': {'b': 'c'}}) + assert app.override_called + assert app.parent_called + + +def test_help_output(): + check_help_output(__name__) + check_help_all_output(__name__) + +if __name__ == '__main__': + # for test_help_output: + MyApp.launch_instance()
\ No newline at end of file diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py b/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py index 639b65f729..9fbdb7209d 100644 --- a/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py +++ b/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py @@ -1,459 +1,459 @@ -# encoding: utf-8 -"""Tests for traitlets.config.configurable""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import logging -from unittest import TestCase - -from pytest import mark - -from traitlets.config.configurable import ( - Configurable, - LoggingConfigurable, - SingletonConfigurable, -) - -from traitlets.traitlets import ( - Integer, Float, Unicode, List, Dict, Set, - _deprecations_shown, -) - -from traitlets.config.loader import Config -from six import PY3 - -from ...tests._warnings import expected_warnings - -class MyConfigurable(Configurable): - a = Integer(1, help="The integer a.").tag(config=True) - b = Float(1.0, help="The integer b.").tag(config=True) - c = Unicode('no config') - - -mc_help=u"""MyConfigurable options ----------------------- ---MyConfigurable.a=<Integer> - Default: 1 - The integer a. ---MyConfigurable.b=<Float> - Default: 1.0 - The integer b.""" - -mc_help_inst=u"""MyConfigurable options ----------------------- ---MyConfigurable.a=<Integer> - Current: 5 - The integer a. ---MyConfigurable.b=<Float> - Current: 4.0 - The integer b.""" - -# On Python 3, the Integer trait is a synonym for Int -if PY3: - mc_help = mc_help.replace(u"<Integer>", u"<Int>") - mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>") - -class Foo(Configurable): - a = Integer(0, help="The integer a.").tag(config=True) - b = Unicode('nope').tag(config=True) - - -class Bar(Foo): - b = Unicode('gotit', help="The string b.").tag(config=False) - c = Float(help="The string c.").tag(config=True) - - -class TestConfigurable(TestCase): - - def test_default(self): - c1 = Configurable() - c2 = Configurable(config=c1.config) - c3 = Configurable(config=c2.config) - self.assertEqual(c1.config, c2.config) - self.assertEqual(c2.config, c3.config) - - def test_custom(self): - config = Config() - config.foo = 'foo' - config.bar = 'bar' - c1 = Configurable(config=config) - c2 = Configurable(config=c1.config) - c3 = Configurable(config=c2.config) - self.assertEqual(c1.config, config) - self.assertEqual(c2.config, config) - self.assertEqual(c3.config, config) - # Test that copies are not made - self.assertTrue(c1.config is config) - self.assertTrue(c2.config is config) - self.assertTrue(c3.config is config) - self.assertTrue(c1.config is c2.config) - self.assertTrue(c2.config is c3.config) - - def test_inheritance(self): - config = Config() - config.MyConfigurable.a = 2 - config.MyConfigurable.b = 2.0 - c1 = MyConfigurable(config=config) - c2 = MyConfigurable(config=c1.config) - self.assertEqual(c1.a, config.MyConfigurable.a) - self.assertEqual(c1.b, config.MyConfigurable.b) - self.assertEqual(c2.a, config.MyConfigurable.a) - self.assertEqual(c2.b, config.MyConfigurable.b) - - def test_parent(self): - config = Config() - config.Foo.a = 10 - config.Foo.b = "wow" - config.Bar.b = 'later' - config.Bar.c = 100.0 - f = Foo(config=config) - with expected_warnings(['`b` not recognized']): - b = Bar(config=f.config) - self.assertEqual(f.a, 10) - self.assertEqual(f.b, 'wow') - self.assertEqual(b.b, 'gotit') - self.assertEqual(b.c, 100.0) - - def test_override1(self): - config = Config() - config.MyConfigurable.a = 2 - config.MyConfigurable.b = 2.0 - c = MyConfigurable(a=3, config=config) - self.assertEqual(c.a, 3) - self.assertEqual(c.b, config.MyConfigurable.b) - self.assertEqual(c.c, 'no config') - - def test_override2(self): - config = Config() - config.Foo.a = 1 - config.Bar.b = 'or' # Up above b is config=False, so this won't do it. - config.Bar.c = 10.0 - with expected_warnings(['`b` not recognized']): - c = Bar(config=config) - self.assertEqual(c.a, config.Foo.a) - self.assertEqual(c.b, 'gotit') - self.assertEqual(c.c, config.Bar.c) - with expected_warnings(['`b` not recognized']): - c = Bar(a=2, b='and', c=20.0, config=config) - self.assertEqual(c.a, 2) - self.assertEqual(c.b, 'and') - self.assertEqual(c.c, 20.0) - - def test_help(self): - self.assertEqual(MyConfigurable.class_get_help(), mc_help) - - def test_help_inst(self): - inst = MyConfigurable(a=5, b=4) - self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst) - - -class TestSingletonConfigurable(TestCase): - - def test_instance(self): - class Foo(SingletonConfigurable): pass - self.assertEqual(Foo.initialized(), False) - foo = Foo.instance() - self.assertEqual(Foo.initialized(), True) - self.assertEqual(foo, Foo.instance()) - self.assertEqual(SingletonConfigurable._instance, None) - - def test_inheritance(self): - class Bar(SingletonConfigurable): pass - class Bam(Bar): pass - self.assertEqual(Bar.initialized(), False) - self.assertEqual(Bam.initialized(), False) - bam = Bam.instance() - bam == Bar.instance() - self.assertEqual(Bar.initialized(), True) - self.assertEqual(Bam.initialized(), True) - self.assertEqual(bam, Bam._instance) - self.assertEqual(bam, Bar._instance) - self.assertEqual(SingletonConfigurable._instance, None) - - -class MyParent(Configurable): - pass - -class MyParent2(MyParent): - pass - -class TestParentConfigurable(TestCase): - - def test_parent_config(self): - cfg = Config({ - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 2.0, - } - } - }) - parent = MyParent(config=cfg) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) - - def test_parent_inheritance(self): - cfg = Config({ - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 2.0, - } - } - }) - parent = MyParent2(config=cfg) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) - - def test_multi_parent(self): - cfg = Config({ - 'MyParent2' : { - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 2.0, - } - }, - # this one shouldn't count - 'MyConfigurable' : { - 'b' : 3.0, - }, - } - }) - parent2 = MyParent2(config=cfg) - parent = MyParent(parent=parent2) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) - - def test_parent_priority(self): - cfg = Config({ - 'MyConfigurable' : { - 'b' : 2.0, - }, - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 3.0, - } - }, - 'MyParent2' : { - 'MyConfigurable' : { - 'b' : 4.0, - } - } - }) - parent = MyParent2(config=cfg) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b) - - def test_multi_parent_priority(self): - cfg = Config({ - 'MyConfigurable' : { - 'b' : 2.0, - }, - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 3.0, - } - }, - 'MyParent2' : { - 'MyConfigurable' : { - 'b' : 4.0, - } - }, - 'MyParent2' : { - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 5.0, - } - } - } - }) - parent2 = MyParent2(config=cfg) - parent = MyParent2(parent=parent2) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) - -class Containers(Configurable): - lis = List().tag(config=True) - def _lis_default(self): - return [-1] - - s = Set().tag(config=True) - def _s_default(self): - return {'a'} - - d = Dict().tag(config=True) - def _d_default(self): - return {'a' : 'b'} - -class TestConfigContainers(TestCase): - def test_extend(self): - c = Config() - c.Containers.lis.extend(list(range(5))) - obj = Containers(config=c) - self.assertEqual(obj.lis, list(range(-1,5))) - - def test_insert(self): - c = Config() - c.Containers.lis.insert(0, 'a') - c.Containers.lis.insert(1, 'b') - obj = Containers(config=c) - self.assertEqual(obj.lis, ['a', 'b', -1]) - - def test_prepend(self): - c = Config() - c.Containers.lis.prepend([1,2]) - c.Containers.lis.prepend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [2,3,1,2,-1]) - - def test_prepend_extend(self): - c = Config() - c.Containers.lis.prepend([1,2]) - c.Containers.lis.extend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [1,2,-1,2,3]) - - def test_append_extend(self): - c = Config() - c.Containers.lis.append([1,2]) - c.Containers.lis.extend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [-1,[1,2],2,3]) - - def test_extend_append(self): - c = Config() - c.Containers.lis.extend([2,3]) - c.Containers.lis.append([1,2]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [-1,2,3,[1,2]]) - - def test_insert_extend(self): - c = Config() - c.Containers.lis.insert(0, 1) - c.Containers.lis.extend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [1,-1,2,3]) - - def test_set_update(self): - c = Config() - c.Containers.s.update({0,1,2}) - c.Containers.s.update({3}) - obj = Containers(config=c) - self.assertEqual(obj.s, {'a', 0, 1, 2, 3}) - - def test_dict_update(self): - c = Config() - c.Containers.d.update({'c' : 'd'}) - c.Containers.d.update({'e' : 'f'}) - obj = Containers(config=c) - self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'}) - - def test_update_twice(self): - c = Config() - c.MyConfigurable.a = 5 - m = MyConfigurable(config=c) - self.assertEqual(m.a, 5) - - c2 = Config() - c2.MyConfigurable.a = 10 - m.update_config(c2) - self.assertEqual(m.a, 10) - - c2.MyConfigurable.a = 15 - m.update_config(c2) - self.assertEqual(m.a, 15) - - def test_update_self(self): - """update_config with same config object still triggers config_changed""" - c = Config() - c.MyConfigurable.a = 5 - m = MyConfigurable(config=c) - self.assertEqual(m.a, 5) - c.MyConfigurable.a = 10 - m.update_config(c) - self.assertEqual(m.a, 10) - - def test_config_default(self): - class SomeSingleton(SingletonConfigurable): - pass - - class DefaultConfigurable(Configurable): - a = Integer().tag(config=True) - def _config_default(self): - if SomeSingleton.initialized(): - return SomeSingleton.instance().config - return Config() - - c = Config() - c.DefaultConfigurable.a = 5 - - d1 = DefaultConfigurable() - self.assertEqual(d1.a, 0) - - single = SomeSingleton.instance(config=c) - - d2 = DefaultConfigurable() - self.assertIs(d2.config, single.config) - self.assertEqual(d2.a, 5) - - def test_config_default_deprecated(self): - """Make sure configurables work even with the deprecations in traitlets""" - class SomeSingleton(SingletonConfigurable): - pass - - # reset deprecation limiter - _deprecations_shown.clear() - with expected_warnings([]): - class DefaultConfigurable(Configurable): - a = Integer(config=True) - def _config_default(self): - if SomeSingleton.initialized(): - return SomeSingleton.instance().config - return Config() - - c = Config() - c.DefaultConfigurable.a = 5 - - d1 = DefaultConfigurable() - self.assertEqual(d1.a, 0) - - single = SomeSingleton.instance(config=c) - - d2 = DefaultConfigurable() - self.assertIs(d2.config, single.config) - self.assertEqual(d2.a, 5) - - -class TestLogger(TestCase): - - class A(LoggingConfigurable): - foo = Integer(config=True) - bar = Integer(config=True) - baz = Integer(config=True) - - @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') - def test_warn_match(self): - logger = logging.getLogger('test_warn_match') - cfg = Config({'A': {'bat': 5}}) - with self.assertLogs(logger, logging.WARNING) as captured: - a = TestLogger.A(config=cfg, log=logger) - - output = '\n'.join(captured.output) - self.assertIn('Did you mean one of: `bar, baz`?', output) - self.assertIn('Config option `bat` not recognized by `A`.', output) - - cfg = Config({'A': {'fool': 5}}) - with self.assertLogs(logger, logging.WARNING) as captured: - a = TestLogger.A(config=cfg, log=logger) - - output = '\n'.join(captured.output) - self.assertIn('Config option `fool` not recognized by `A`.', output) - self.assertIn('Did you mean `foo`?', output) - - cfg = Config({'A': {'totally_wrong': 5}}) - with self.assertLogs(logger, logging.WARNING) as captured: - a = TestLogger.A(config=cfg, log=logger) - - output = '\n'.join(captured.output) - self.assertIn('Config option `totally_wrong` not recognized by `A`.', output) - self.assertNotIn('Did you mean', output) - +# encoding: utf-8 +"""Tests for traitlets.config.configurable""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import logging +from unittest import TestCase + +from pytest import mark + +from traitlets.config.configurable import ( + Configurable, + LoggingConfigurable, + SingletonConfigurable, +) + +from traitlets.traitlets import ( + Integer, Float, Unicode, List, Dict, Set, + _deprecations_shown, +) + +from traitlets.config.loader import Config +from six import PY3 + +from ...tests._warnings import expected_warnings + +class MyConfigurable(Configurable): + a = Integer(1, help="The integer a.").tag(config=True) + b = Float(1.0, help="The integer b.").tag(config=True) + c = Unicode('no config') + + +mc_help=u"""MyConfigurable options +---------------------- +--MyConfigurable.a=<Integer> + Default: 1 + The integer a. +--MyConfigurable.b=<Float> + Default: 1.0 + The integer b.""" + +mc_help_inst=u"""MyConfigurable options +---------------------- +--MyConfigurable.a=<Integer> + Current: 5 + The integer a. +--MyConfigurable.b=<Float> + Current: 4.0 + The integer b.""" + +# On Python 3, the Integer trait is a synonym for Int +if PY3: + mc_help = mc_help.replace(u"<Integer>", u"<Int>") + mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>") + +class Foo(Configurable): + a = Integer(0, help="The integer a.").tag(config=True) + b = Unicode('nope').tag(config=True) + + +class Bar(Foo): + b = Unicode('gotit', help="The string b.").tag(config=False) + c = Float(help="The string c.").tag(config=True) + + +class TestConfigurable(TestCase): + + def test_default(self): + c1 = Configurable() + c2 = Configurable(config=c1.config) + c3 = Configurable(config=c2.config) + self.assertEqual(c1.config, c2.config) + self.assertEqual(c2.config, c3.config) + + def test_custom(self): + config = Config() + config.foo = 'foo' + config.bar = 'bar' + c1 = Configurable(config=config) + c2 = Configurable(config=c1.config) + c3 = Configurable(config=c2.config) + self.assertEqual(c1.config, config) + self.assertEqual(c2.config, config) + self.assertEqual(c3.config, config) + # Test that copies are not made + self.assertTrue(c1.config is config) + self.assertTrue(c2.config is config) + self.assertTrue(c3.config is config) + self.assertTrue(c1.config is c2.config) + self.assertTrue(c2.config is c3.config) + + def test_inheritance(self): + config = Config() + config.MyConfigurable.a = 2 + config.MyConfigurable.b = 2.0 + c1 = MyConfigurable(config=config) + c2 = MyConfigurable(config=c1.config) + self.assertEqual(c1.a, config.MyConfigurable.a) + self.assertEqual(c1.b, config.MyConfigurable.b) + self.assertEqual(c2.a, config.MyConfigurable.a) + self.assertEqual(c2.b, config.MyConfigurable.b) + + def test_parent(self): + config = Config() + config.Foo.a = 10 + config.Foo.b = "wow" + config.Bar.b = 'later' + config.Bar.c = 100.0 + f = Foo(config=config) + with expected_warnings(['`b` not recognized']): + b = Bar(config=f.config) + self.assertEqual(f.a, 10) + self.assertEqual(f.b, 'wow') + self.assertEqual(b.b, 'gotit') + self.assertEqual(b.c, 100.0) + + def test_override1(self): + config = Config() + config.MyConfigurable.a = 2 + config.MyConfigurable.b = 2.0 + c = MyConfigurable(a=3, config=config) + self.assertEqual(c.a, 3) + self.assertEqual(c.b, config.MyConfigurable.b) + self.assertEqual(c.c, 'no config') + + def test_override2(self): + config = Config() + config.Foo.a = 1 + config.Bar.b = 'or' # Up above b is config=False, so this won't do it. + config.Bar.c = 10.0 + with expected_warnings(['`b` not recognized']): + c = Bar(config=config) + self.assertEqual(c.a, config.Foo.a) + self.assertEqual(c.b, 'gotit') + self.assertEqual(c.c, config.Bar.c) + with expected_warnings(['`b` not recognized']): + c = Bar(a=2, b='and', c=20.0, config=config) + self.assertEqual(c.a, 2) + self.assertEqual(c.b, 'and') + self.assertEqual(c.c, 20.0) + + def test_help(self): + self.assertEqual(MyConfigurable.class_get_help(), mc_help) + + def test_help_inst(self): + inst = MyConfigurable(a=5, b=4) + self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst) + + +class TestSingletonConfigurable(TestCase): + + def test_instance(self): + class Foo(SingletonConfigurable): pass + self.assertEqual(Foo.initialized(), False) + foo = Foo.instance() + self.assertEqual(Foo.initialized(), True) + self.assertEqual(foo, Foo.instance()) + self.assertEqual(SingletonConfigurable._instance, None) + + def test_inheritance(self): + class Bar(SingletonConfigurable): pass + class Bam(Bar): pass + self.assertEqual(Bar.initialized(), False) + self.assertEqual(Bam.initialized(), False) + bam = Bam.instance() + bam == Bar.instance() + self.assertEqual(Bar.initialized(), True) + self.assertEqual(Bam.initialized(), True) + self.assertEqual(bam, Bam._instance) + self.assertEqual(bam, Bar._instance) + self.assertEqual(SingletonConfigurable._instance, None) + + +class MyParent(Configurable): + pass + +class MyParent2(MyParent): + pass + +class TestParentConfigurable(TestCase): + + def test_parent_config(self): + cfg = Config({ + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + } + }) + parent = MyParent(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) + + def test_parent_inheritance(self): + cfg = Config({ + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + } + }) + parent = MyParent2(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) + + def test_multi_parent(self): + cfg = Config({ + 'MyParent2' : { + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + }, + # this one shouldn't count + 'MyConfigurable' : { + 'b' : 3.0, + }, + } + }) + parent2 = MyParent2(config=cfg) + parent = MyParent(parent=parent2) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) + + def test_parent_priority(self): + cfg = Config({ + 'MyConfigurable' : { + 'b' : 2.0, + }, + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 3.0, + } + }, + 'MyParent2' : { + 'MyConfigurable' : { + 'b' : 4.0, + } + } + }) + parent = MyParent2(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b) + + def test_multi_parent_priority(self): + cfg = Config({ + 'MyConfigurable' : { + 'b' : 2.0, + }, + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 3.0, + } + }, + 'MyParent2' : { + 'MyConfigurable' : { + 'b' : 4.0, + } + }, + 'MyParent2' : { + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 5.0, + } + } + } + }) + parent2 = MyParent2(config=cfg) + parent = MyParent2(parent=parent2) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) + +class Containers(Configurable): + lis = List().tag(config=True) + def _lis_default(self): + return [-1] + + s = Set().tag(config=True) + def _s_default(self): + return {'a'} + + d = Dict().tag(config=True) + def _d_default(self): + return {'a' : 'b'} + +class TestConfigContainers(TestCase): + def test_extend(self): + c = Config() + c.Containers.lis.extend(list(range(5))) + obj = Containers(config=c) + self.assertEqual(obj.lis, list(range(-1,5))) + + def test_insert(self): + c = Config() + c.Containers.lis.insert(0, 'a') + c.Containers.lis.insert(1, 'b') + obj = Containers(config=c) + self.assertEqual(obj.lis, ['a', 'b', -1]) + + def test_prepend(self): + c = Config() + c.Containers.lis.prepend([1,2]) + c.Containers.lis.prepend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [2,3,1,2,-1]) + + def test_prepend_extend(self): + c = Config() + c.Containers.lis.prepend([1,2]) + c.Containers.lis.extend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [1,2,-1,2,3]) + + def test_append_extend(self): + c = Config() + c.Containers.lis.append([1,2]) + c.Containers.lis.extend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [-1,[1,2],2,3]) + + def test_extend_append(self): + c = Config() + c.Containers.lis.extend([2,3]) + c.Containers.lis.append([1,2]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [-1,2,3,[1,2]]) + + def test_insert_extend(self): + c = Config() + c.Containers.lis.insert(0, 1) + c.Containers.lis.extend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [1,-1,2,3]) + + def test_set_update(self): + c = Config() + c.Containers.s.update({0,1,2}) + c.Containers.s.update({3}) + obj = Containers(config=c) + self.assertEqual(obj.s, {'a', 0, 1, 2, 3}) + + def test_dict_update(self): + c = Config() + c.Containers.d.update({'c' : 'd'}) + c.Containers.d.update({'e' : 'f'}) + obj = Containers(config=c) + self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'}) + + def test_update_twice(self): + c = Config() + c.MyConfigurable.a = 5 + m = MyConfigurable(config=c) + self.assertEqual(m.a, 5) + + c2 = Config() + c2.MyConfigurable.a = 10 + m.update_config(c2) + self.assertEqual(m.a, 10) + + c2.MyConfigurable.a = 15 + m.update_config(c2) + self.assertEqual(m.a, 15) + + def test_update_self(self): + """update_config with same config object still triggers config_changed""" + c = Config() + c.MyConfigurable.a = 5 + m = MyConfigurable(config=c) + self.assertEqual(m.a, 5) + c.MyConfigurable.a = 10 + m.update_config(c) + self.assertEqual(m.a, 10) + + def test_config_default(self): + class SomeSingleton(SingletonConfigurable): + pass + + class DefaultConfigurable(Configurable): + a = Integer().tag(config=True) + def _config_default(self): + if SomeSingleton.initialized(): + return SomeSingleton.instance().config + return Config() + + c = Config() + c.DefaultConfigurable.a = 5 + + d1 = DefaultConfigurable() + self.assertEqual(d1.a, 0) + + single = SomeSingleton.instance(config=c) + + d2 = DefaultConfigurable() + self.assertIs(d2.config, single.config) + self.assertEqual(d2.a, 5) + + def test_config_default_deprecated(self): + """Make sure configurables work even with the deprecations in traitlets""" + class SomeSingleton(SingletonConfigurable): + pass + + # reset deprecation limiter + _deprecations_shown.clear() + with expected_warnings([]): + class DefaultConfigurable(Configurable): + a = Integer(config=True) + def _config_default(self): + if SomeSingleton.initialized(): + return SomeSingleton.instance().config + return Config() + + c = Config() + c.DefaultConfigurable.a = 5 + + d1 = DefaultConfigurable() + self.assertEqual(d1.a, 0) + + single = SomeSingleton.instance(config=c) + + d2 = DefaultConfigurable() + self.assertIs(d2.config, single.config) + self.assertEqual(d2.a, 5) + + +class TestLogger(TestCase): + + class A(LoggingConfigurable): + foo = Integer(config=True) + bar = Integer(config=True) + baz = Integer(config=True) + + @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') + def test_warn_match(self): + logger = logging.getLogger('test_warn_match') + cfg = Config({'A': {'bat': 5}}) + with self.assertLogs(logger, logging.WARNING) as captured: + a = TestLogger.A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + self.assertIn('Did you mean one of: `bar, baz`?', output) + self.assertIn('Config option `bat` not recognized by `A`.', output) + + cfg = Config({'A': {'fool': 5}}) + with self.assertLogs(logger, logging.WARNING) as captured: + a = TestLogger.A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + self.assertIn('Config option `fool` not recognized by `A`.', output) + self.assertIn('Did you mean `foo`?', output) + + cfg = Config({'A': {'totally_wrong': 5}}) + with self.assertLogs(logger, logging.WARNING) as captured: + a = TestLogger.A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + self.assertIn('Config option `totally_wrong` not recognized by `A`.', output) + self.assertNotIn('Did you mean', output) + diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py b/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py index fc543a535a..50c8659f3c 100644 --- a/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py +++ b/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py @@ -1,453 +1,453 @@ -# encoding: utf-8 -"""Tests for traitlets.config.loader""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import copy -import logging -import os -import pickle -import sys -from tempfile import mkstemp -from unittest import TestCase - -from pytest import skip - -from traitlets.config.loader import ( - Config, - LazyConfigValue, - PyFileConfigLoader, - JSONFileConfigLoader, - KeyValueConfigLoader, - ArgParseConfigLoader, - KVArgParseConfigLoader, - ConfigError, -) - - -pyfile = """ -c = get_config() -c.a=10 -c.b=20 -c.Foo.Bar.value=10 -c.Foo.Bam.value=list(range(10)) -c.D.C.value='hi there' -""" - -json1file = """ -{ - "version": 1, - "a": 10, - "b": 20, - "Foo": { - "Bam": { - "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] - }, - "Bar": { - "value": 10 - } - }, - "D": { - "C": { - "value": "hi there" - } - } -} -""" - -# should not load -json2file = """ -{ - "version": 2 -} -""" - -import logging -log = logging.getLogger('devnull') -log.setLevel(0) - -class TestFileCL(TestCase): - - def _check_conf(self, config): - self.assertEqual(config.a, 10) - self.assertEqual(config.b, 20) - self.assertEqual(config.Foo.Bar.value, 10) - self.assertEqual(config.Foo.Bam.value, list(range(10))) - self.assertEqual(config.D.C.value, 'hi there') - - def test_python(self): - fd, fname = mkstemp('.py') - f = os.fdopen(fd, 'w') - f.write(pyfile) - f.close() - # Unlink the file - cl = PyFileConfigLoader(fname, log=log) - config = cl.load_config() - self._check_conf(config) - - def test_json(self): - fd, fname = mkstemp('.json') - f = os.fdopen(fd, 'w') - f.write(json1file) - f.close() - # Unlink the file - cl = JSONFileConfigLoader(fname, log=log) - config = cl.load_config() - self._check_conf(config) - - def test_context_manager(self): - - fd, fname = mkstemp('.json') - f = os.fdopen(fd, 'w') - f.write('{}') - f.close() - - cl = JSONFileConfigLoader(fname, log=log) - - value = 'context_manager' - - with cl as c: - c.MyAttr.value = value - - self.assertEqual(cl.config.MyAttr.value, value) - - # check that another loader does see the change - cl2 = JSONFileConfigLoader(fname, log=log) - self.assertEqual(cl.config.MyAttr.value, value) - - def test_json_context_bad_write(self): - fd, fname = mkstemp('.json') - f = os.fdopen(fd, 'w') - f.write('{}') - f.close() - - with JSONFileConfigLoader(fname, log=log) as config: - config.A.b = 1 - - with self.assertRaises(TypeError): - with JSONFileConfigLoader(fname, log=log) as config: - config.A.cant_json = lambda x: x - - loader = JSONFileConfigLoader(fname, log=log) - cfg = loader.load_config() - assert cfg.A.b == 1 - assert 'cant_json' not in cfg.A - - def test_collision(self): - a = Config() - b = Config() - self.assertEqual(a.collisions(b), {}) - a.A.trait1 = 1 - b.A.trait2 = 2 - self.assertEqual(a.collisions(b), {}) - b.A.trait1 = 1 - self.assertEqual(a.collisions(b), {}) - b.A.trait1 = 0 - self.assertEqual(a.collisions(b), { - 'A': { - 'trait1': "1 ignored, using 0", - } - }) - self.assertEqual(b.collisions(a), { - 'A': { - 'trait1': "0 ignored, using 1", - } - }) - a.A.trait2 = 3 - self.assertEqual(b.collisions(a), { - 'A': { - 'trait1': "0 ignored, using 1", - 'trait2': "2 ignored, using 3", - } - }) - - def test_v2raise(self): - fd, fname = mkstemp('.json') - f = os.fdopen(fd, 'w') - f.write(json2file) - f.close() - # Unlink the file - cl = JSONFileConfigLoader(fname, log=log) - with self.assertRaises(ValueError): - cl.load_config() - - -class MyLoader1(ArgParseConfigLoader): - def _add_arguments(self, aliases=None, flags=None): - p = self.parser - p.add_argument('-f', '--foo', dest='Global.foo', type=str) - p.add_argument('-b', dest='MyClass.bar', type=int) - p.add_argument('-n', dest='n', action='store_true') - p.add_argument('Global.bam', type=str) - -class MyLoader2(ArgParseConfigLoader): - def _add_arguments(self, aliases=None, flags=None): - subparsers = self.parser.add_subparsers(dest='subparser_name') - subparser1 = subparsers.add_parser('1') - subparser1.add_argument('-x',dest='Global.x') - subparser2 = subparsers.add_parser('2') - subparser2.add_argument('y') - -class TestArgParseCL(TestCase): - - def test_basic(self): - cl = MyLoader1() - config = cl.load_config('-f hi -b 10 -n wow'.split()) - self.assertEqual(config.Global.foo, 'hi') - self.assertEqual(config.MyClass.bar, 10) - self.assertEqual(config.n, True) - self.assertEqual(config.Global.bam, 'wow') - config = cl.load_config(['wow']) - self.assertEqual(list(config.keys()), ['Global']) - self.assertEqual(list(config.Global.keys()), ['bam']) - self.assertEqual(config.Global.bam, 'wow') - - def test_add_arguments(self): - cl = MyLoader2() - config = cl.load_config('2 frobble'.split()) - self.assertEqual(config.subparser_name, '2') - self.assertEqual(config.y, 'frobble') - config = cl.load_config('1 -x frobble'.split()) - self.assertEqual(config.subparser_name, '1') - self.assertEqual(config.Global.x, 'frobble') - - def test_argv(self): - cl = MyLoader1(argv='-f hi -b 10 -n wow'.split()) - config = cl.load_config() - self.assertEqual(config.Global.foo, 'hi') - self.assertEqual(config.MyClass.bar, 10) - self.assertEqual(config.n, True) - self.assertEqual(config.Global.bam, 'wow') - - -class TestKeyValueCL(TestCase): - klass = KeyValueConfigLoader - - def test_eval(self): - cl = self.klass(log=log) - config = cl.load_config('--Class.str_trait=all --Class.int_trait=5 --Class.list_trait=["hello",5]'.split()) - self.assertEqual(config.Class.str_trait, 'all') - self.assertEqual(config.Class.int_trait, 5) - self.assertEqual(config.Class.list_trait, ["hello", 5]) - - def test_basic(self): - cl = self.klass(log=log) - argv = [ '--' + s[2:] for s in pyfile.split('\n') if s.startswith('c.') ] - print(argv) - config = cl.load_config(argv) - self.assertEqual(config.a, 10) - self.assertEqual(config.b, 20) - self.assertEqual(config.Foo.Bar.value, 10) - # non-literal expressions are not evaluated - self.assertEqual(config.Foo.Bam.value, 'list(range(10))') - self.assertEqual(config.D.C.value, 'hi there') - - def test_expanduser(self): - cl = self.klass(log=log) - argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"'] - config = cl.load_config(argv) - self.assertEqual(config.a, os.path.expanduser('~/1/2/3')) - self.assertEqual(config.b, os.path.expanduser('~')) - self.assertEqual(config.c, os.path.expanduser('~/')) - self.assertEqual(config.d, '~/') - - def test_extra_args(self): - cl = self.klass(log=log) - config = cl.load_config(['--a=5', 'b', '--c=10', 'd']) - self.assertEqual(cl.extra_args, ['b', 'd']) - self.assertEqual(config.a, 5) - self.assertEqual(config.c, 10) - config = cl.load_config(['--', '--a=5', '--c=10']) - self.assertEqual(cl.extra_args, ['--a=5', '--c=10']) - - def test_unicode_args(self): - cl = self.klass(log=log) - argv = [u'--a=épsîlön'] - config = cl.load_config(argv) - self.assertEqual(config.a, u'épsîlön') - - def test_unicode_bytes_args(self): - uarg = u'--a=é' - try: - barg = uarg.encode(sys.stdin.encoding) - except (TypeError, UnicodeEncodeError): - raise skip("sys.stdin.encoding can't handle 'é'") - - cl = self.klass(log=log) - config = cl.load_config([barg]) - self.assertEqual(config.a, u'é') - - def test_unicode_alias(self): - cl = self.klass(log=log) - argv = [u'--a=épsîlön'] - config = cl.load_config(argv, aliases=dict(a='A.a')) - self.assertEqual(config.A.a, u'épsîlön') - - -class TestArgParseKVCL(TestKeyValueCL): - klass = KVArgParseConfigLoader - - def test_expanduser2(self): - cl = self.klass(log=log) - argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"] - config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b')) - self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3')) - self.assertEqual(config.A.b, '~/1/2/3') - - def test_eval(self): - cl = self.klass(log=log) - argv = ['-c', 'a=5'] - config = cl.load_config(argv, aliases=dict(c='A.c')) - self.assertEqual(config.A.c, u"a=5") - - -class TestConfig(TestCase): - - def test_setget(self): - c = Config() - c.a = 10 - self.assertEqual(c.a, 10) - self.assertEqual('b' in c, False) - - def test_auto_section(self): - c = Config() - self.assertNotIn('A', c) - assert not c._has_section('A') - A = c.A - A.foo = 'hi there' - self.assertIn('A', c) - assert c._has_section('A') - self.assertEqual(c.A.foo, 'hi there') - del c.A - self.assertEqual(c.A, Config()) - - def test_merge_doesnt_exist(self): - c1 = Config() - c2 = Config() - c2.bar = 10 - c2.Foo.bar = 10 - c1.merge(c2) - self.assertEqual(c1.Foo.bar, 10) - self.assertEqual(c1.bar, 10) - c2.Bar.bar = 10 - c1.merge(c2) - self.assertEqual(c1.Bar.bar, 10) - - def test_merge_exists(self): - c1 = Config() - c2 = Config() - c1.Foo.bar = 10 - c1.Foo.bam = 30 - c2.Foo.bar = 20 - c2.Foo.wow = 40 - c1.merge(c2) - self.assertEqual(c1.Foo.bam, 30) - self.assertEqual(c1.Foo.bar, 20) - self.assertEqual(c1.Foo.wow, 40) - c2.Foo.Bam.bam = 10 - c1.merge(c2) - self.assertEqual(c1.Foo.Bam.bam, 10) - - def test_deepcopy(self): - c1 = Config() - c1.Foo.bar = 10 - c1.Foo.bam = 30 - c1.a = 'asdf' - c1.b = range(10) - c1.Test.logger = logging.Logger('test') - c1.Test.get_logger = logging.getLogger('test') - c2 = copy.deepcopy(c1) - self.assertEqual(c1, c2) - self.assertTrue(c1 is not c2) - self.assertTrue(c1.Foo is not c2.Foo) - self.assertTrue(c1.Test is not c2.Test) - self.assertTrue(c1.Test.logger is c2.Test.logger) - self.assertTrue(c1.Test.get_logger is c2.Test.get_logger) - - def test_builtin(self): - c1 = Config() - c1.format = "json" - - def test_fromdict(self): - c1 = Config({'Foo' : {'bar' : 1}}) - self.assertEqual(c1.Foo.__class__, Config) - self.assertEqual(c1.Foo.bar, 1) - - def test_fromdictmerge(self): - c1 = Config() - c2 = Config({'Foo' : {'bar' : 1}}) - c1.merge(c2) - self.assertEqual(c1.Foo.__class__, Config) - self.assertEqual(c1.Foo.bar, 1) - - def test_fromdictmerge2(self): - c1 = Config({'Foo' : {'baz' : 2}}) - c2 = Config({'Foo' : {'bar' : 1}}) - c1.merge(c2) - self.assertEqual(c1.Foo.__class__, Config) - self.assertEqual(c1.Foo.bar, 1) - self.assertEqual(c1.Foo.baz, 2) - self.assertNotIn('baz', c2.Foo) - - def test_contains(self): - c1 = Config({'Foo' : {'baz' : 2}}) - c2 = Config({'Foo' : {'bar' : 1}}) - self.assertIn('Foo', c1) - self.assertIn('Foo.baz', c1) - self.assertIn('Foo.bar', c2) - self.assertNotIn('Foo.bar', c1) - - def test_pickle_config(self): - cfg = Config() - cfg.Foo.bar = 1 - pcfg = pickle.dumps(cfg) - cfg2 = pickle.loads(pcfg) - self.assertEqual(cfg2, cfg) - - def test_getattr_section(self): - cfg = Config() - self.assertNotIn('Foo', cfg) - Foo = cfg.Foo - assert isinstance(Foo, Config) - self.assertIn('Foo', cfg) - - def test_getitem_section(self): - cfg = Config() - self.assertNotIn('Foo', cfg) - Foo = cfg['Foo'] - assert isinstance(Foo, Config) - self.assertIn('Foo', cfg) - - def test_getattr_not_section(self): - cfg = Config() - self.assertNotIn('foo', cfg) - foo = cfg.foo - assert isinstance(foo, LazyConfigValue) - self.assertIn('foo', cfg) - - def test_getattr_private_missing(self): - cfg = Config() - self.assertNotIn('_repr_html_', cfg) - with self.assertRaises(AttributeError): - _ = cfg._repr_html_ - self.assertNotIn('_repr_html_', cfg) - self.assertEqual(len(cfg), 0) - - def test_getitem_not_section(self): - cfg = Config() - self.assertNotIn('foo', cfg) - foo = cfg['foo'] - assert isinstance(foo, LazyConfigValue) - self.assertIn('foo', cfg) - - def test_merge_no_copies(self): - c = Config() - c2 = Config() - c2.Foo.trait = [] - c.merge(c2) - c2.Foo.trait.append(1) - self.assertIs(c.Foo, c2.Foo) - self.assertEqual(c.Foo.trait, [1]) - self.assertEqual(c2.Foo.trait, [1]) - +# encoding: utf-8 +"""Tests for traitlets.config.loader""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import copy +import logging +import os +import pickle +import sys +from tempfile import mkstemp +from unittest import TestCase + +from pytest import skip + +from traitlets.config.loader import ( + Config, + LazyConfigValue, + PyFileConfigLoader, + JSONFileConfigLoader, + KeyValueConfigLoader, + ArgParseConfigLoader, + KVArgParseConfigLoader, + ConfigError, +) + + +pyfile = """ +c = get_config() +c.a=10 +c.b=20 +c.Foo.Bar.value=10 +c.Foo.Bam.value=list(range(10)) +c.D.C.value='hi there' +""" + +json1file = """ +{ + "version": 1, + "a": 10, + "b": 20, + "Foo": { + "Bam": { + "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + }, + "Bar": { + "value": 10 + } + }, + "D": { + "C": { + "value": "hi there" + } + } +} +""" + +# should not load +json2file = """ +{ + "version": 2 +} +""" + +import logging +log = logging.getLogger('devnull') +log.setLevel(0) + +class TestFileCL(TestCase): + + def _check_conf(self, config): + self.assertEqual(config.a, 10) + self.assertEqual(config.b, 20) + self.assertEqual(config.Foo.Bar.value, 10) + self.assertEqual(config.Foo.Bam.value, list(range(10))) + self.assertEqual(config.D.C.value, 'hi there') + + def test_python(self): + fd, fname = mkstemp('.py') + f = os.fdopen(fd, 'w') + f.write(pyfile) + f.close() + # Unlink the file + cl = PyFileConfigLoader(fname, log=log) + config = cl.load_config() + self._check_conf(config) + + def test_json(self): + fd, fname = mkstemp('.json') + f = os.fdopen(fd, 'w') + f.write(json1file) + f.close() + # Unlink the file + cl = JSONFileConfigLoader(fname, log=log) + config = cl.load_config() + self._check_conf(config) + + def test_context_manager(self): + + fd, fname = mkstemp('.json') + f = os.fdopen(fd, 'w') + f.write('{}') + f.close() + + cl = JSONFileConfigLoader(fname, log=log) + + value = 'context_manager' + + with cl as c: + c.MyAttr.value = value + + self.assertEqual(cl.config.MyAttr.value, value) + + # check that another loader does see the change + cl2 = JSONFileConfigLoader(fname, log=log) + self.assertEqual(cl.config.MyAttr.value, value) + + def test_json_context_bad_write(self): + fd, fname = mkstemp('.json') + f = os.fdopen(fd, 'w') + f.write('{}') + f.close() + + with JSONFileConfigLoader(fname, log=log) as config: + config.A.b = 1 + + with self.assertRaises(TypeError): + with JSONFileConfigLoader(fname, log=log) as config: + config.A.cant_json = lambda x: x + + loader = JSONFileConfigLoader(fname, log=log) + cfg = loader.load_config() + assert cfg.A.b == 1 + assert 'cant_json' not in cfg.A + + def test_collision(self): + a = Config() + b = Config() + self.assertEqual(a.collisions(b), {}) + a.A.trait1 = 1 + b.A.trait2 = 2 + self.assertEqual(a.collisions(b), {}) + b.A.trait1 = 1 + self.assertEqual(a.collisions(b), {}) + b.A.trait1 = 0 + self.assertEqual(a.collisions(b), { + 'A': { + 'trait1': "1 ignored, using 0", + } + }) + self.assertEqual(b.collisions(a), { + 'A': { + 'trait1': "0 ignored, using 1", + } + }) + a.A.trait2 = 3 + self.assertEqual(b.collisions(a), { + 'A': { + 'trait1': "0 ignored, using 1", + 'trait2': "2 ignored, using 3", + } + }) + + def test_v2raise(self): + fd, fname = mkstemp('.json') + f = os.fdopen(fd, 'w') + f.write(json2file) + f.close() + # Unlink the file + cl = JSONFileConfigLoader(fname, log=log) + with self.assertRaises(ValueError): + cl.load_config() + + +class MyLoader1(ArgParseConfigLoader): + def _add_arguments(self, aliases=None, flags=None): + p = self.parser + p.add_argument('-f', '--foo', dest='Global.foo', type=str) + p.add_argument('-b', dest='MyClass.bar', type=int) + p.add_argument('-n', dest='n', action='store_true') + p.add_argument('Global.bam', type=str) + +class MyLoader2(ArgParseConfigLoader): + def _add_arguments(self, aliases=None, flags=None): + subparsers = self.parser.add_subparsers(dest='subparser_name') + subparser1 = subparsers.add_parser('1') + subparser1.add_argument('-x',dest='Global.x') + subparser2 = subparsers.add_parser('2') + subparser2.add_argument('y') + +class TestArgParseCL(TestCase): + + def test_basic(self): + cl = MyLoader1() + config = cl.load_config('-f hi -b 10 -n wow'.split()) + self.assertEqual(config.Global.foo, 'hi') + self.assertEqual(config.MyClass.bar, 10) + self.assertEqual(config.n, True) + self.assertEqual(config.Global.bam, 'wow') + config = cl.load_config(['wow']) + self.assertEqual(list(config.keys()), ['Global']) + self.assertEqual(list(config.Global.keys()), ['bam']) + self.assertEqual(config.Global.bam, 'wow') + + def test_add_arguments(self): + cl = MyLoader2() + config = cl.load_config('2 frobble'.split()) + self.assertEqual(config.subparser_name, '2') + self.assertEqual(config.y, 'frobble') + config = cl.load_config('1 -x frobble'.split()) + self.assertEqual(config.subparser_name, '1') + self.assertEqual(config.Global.x, 'frobble') + + def test_argv(self): + cl = MyLoader1(argv='-f hi -b 10 -n wow'.split()) + config = cl.load_config() + self.assertEqual(config.Global.foo, 'hi') + self.assertEqual(config.MyClass.bar, 10) + self.assertEqual(config.n, True) + self.assertEqual(config.Global.bam, 'wow') + + +class TestKeyValueCL(TestCase): + klass = KeyValueConfigLoader + + def test_eval(self): + cl = self.klass(log=log) + config = cl.load_config('--Class.str_trait=all --Class.int_trait=5 --Class.list_trait=["hello",5]'.split()) + self.assertEqual(config.Class.str_trait, 'all') + self.assertEqual(config.Class.int_trait, 5) + self.assertEqual(config.Class.list_trait, ["hello", 5]) + + def test_basic(self): + cl = self.klass(log=log) + argv = [ '--' + s[2:] for s in pyfile.split('\n') if s.startswith('c.') ] + print(argv) + config = cl.load_config(argv) + self.assertEqual(config.a, 10) + self.assertEqual(config.b, 20) + self.assertEqual(config.Foo.Bar.value, 10) + # non-literal expressions are not evaluated + self.assertEqual(config.Foo.Bam.value, 'list(range(10))') + self.assertEqual(config.D.C.value, 'hi there') + + def test_expanduser(self): + cl = self.klass(log=log) + argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"'] + config = cl.load_config(argv) + self.assertEqual(config.a, os.path.expanduser('~/1/2/3')) + self.assertEqual(config.b, os.path.expanduser('~')) + self.assertEqual(config.c, os.path.expanduser('~/')) + self.assertEqual(config.d, '~/') + + def test_extra_args(self): + cl = self.klass(log=log) + config = cl.load_config(['--a=5', 'b', '--c=10', 'd']) + self.assertEqual(cl.extra_args, ['b', 'd']) + self.assertEqual(config.a, 5) + self.assertEqual(config.c, 10) + config = cl.load_config(['--', '--a=5', '--c=10']) + self.assertEqual(cl.extra_args, ['--a=5', '--c=10']) + + def test_unicode_args(self): + cl = self.klass(log=log) + argv = [u'--a=épsîlön'] + config = cl.load_config(argv) + self.assertEqual(config.a, u'épsîlön') + + def test_unicode_bytes_args(self): + uarg = u'--a=é' + try: + barg = uarg.encode(sys.stdin.encoding) + except (TypeError, UnicodeEncodeError): + raise skip("sys.stdin.encoding can't handle 'é'") + + cl = self.klass(log=log) + config = cl.load_config([barg]) + self.assertEqual(config.a, u'é') + + def test_unicode_alias(self): + cl = self.klass(log=log) + argv = [u'--a=épsîlön'] + config = cl.load_config(argv, aliases=dict(a='A.a')) + self.assertEqual(config.A.a, u'épsîlön') + + +class TestArgParseKVCL(TestKeyValueCL): + klass = KVArgParseConfigLoader + + def test_expanduser2(self): + cl = self.klass(log=log) + argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"] + config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b')) + self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3')) + self.assertEqual(config.A.b, '~/1/2/3') + + def test_eval(self): + cl = self.klass(log=log) + argv = ['-c', 'a=5'] + config = cl.load_config(argv, aliases=dict(c='A.c')) + self.assertEqual(config.A.c, u"a=5") + + +class TestConfig(TestCase): + + def test_setget(self): + c = Config() + c.a = 10 + self.assertEqual(c.a, 10) + self.assertEqual('b' in c, False) + + def test_auto_section(self): + c = Config() + self.assertNotIn('A', c) + assert not c._has_section('A') + A = c.A + A.foo = 'hi there' + self.assertIn('A', c) + assert c._has_section('A') + self.assertEqual(c.A.foo, 'hi there') + del c.A + self.assertEqual(c.A, Config()) + + def test_merge_doesnt_exist(self): + c1 = Config() + c2 = Config() + c2.bar = 10 + c2.Foo.bar = 10 + c1.merge(c2) + self.assertEqual(c1.Foo.bar, 10) + self.assertEqual(c1.bar, 10) + c2.Bar.bar = 10 + c1.merge(c2) + self.assertEqual(c1.Bar.bar, 10) + + def test_merge_exists(self): + c1 = Config() + c2 = Config() + c1.Foo.bar = 10 + c1.Foo.bam = 30 + c2.Foo.bar = 20 + c2.Foo.wow = 40 + c1.merge(c2) + self.assertEqual(c1.Foo.bam, 30) + self.assertEqual(c1.Foo.bar, 20) + self.assertEqual(c1.Foo.wow, 40) + c2.Foo.Bam.bam = 10 + c1.merge(c2) + self.assertEqual(c1.Foo.Bam.bam, 10) + + def test_deepcopy(self): + c1 = Config() + c1.Foo.bar = 10 + c1.Foo.bam = 30 + c1.a = 'asdf' + c1.b = range(10) + c1.Test.logger = logging.Logger('test') + c1.Test.get_logger = logging.getLogger('test') + c2 = copy.deepcopy(c1) + self.assertEqual(c1, c2) + self.assertTrue(c1 is not c2) + self.assertTrue(c1.Foo is not c2.Foo) + self.assertTrue(c1.Test is not c2.Test) + self.assertTrue(c1.Test.logger is c2.Test.logger) + self.assertTrue(c1.Test.get_logger is c2.Test.get_logger) + + def test_builtin(self): + c1 = Config() + c1.format = "json" + + def test_fromdict(self): + c1 = Config({'Foo' : {'bar' : 1}}) + self.assertEqual(c1.Foo.__class__, Config) + self.assertEqual(c1.Foo.bar, 1) + + def test_fromdictmerge(self): + c1 = Config() + c2 = Config({'Foo' : {'bar' : 1}}) + c1.merge(c2) + self.assertEqual(c1.Foo.__class__, Config) + self.assertEqual(c1.Foo.bar, 1) + + def test_fromdictmerge2(self): + c1 = Config({'Foo' : {'baz' : 2}}) + c2 = Config({'Foo' : {'bar' : 1}}) + c1.merge(c2) + self.assertEqual(c1.Foo.__class__, Config) + self.assertEqual(c1.Foo.bar, 1) + self.assertEqual(c1.Foo.baz, 2) + self.assertNotIn('baz', c2.Foo) + + def test_contains(self): + c1 = Config({'Foo' : {'baz' : 2}}) + c2 = Config({'Foo' : {'bar' : 1}}) + self.assertIn('Foo', c1) + self.assertIn('Foo.baz', c1) + self.assertIn('Foo.bar', c2) + self.assertNotIn('Foo.bar', c1) + + def test_pickle_config(self): + cfg = Config() + cfg.Foo.bar = 1 + pcfg = pickle.dumps(cfg) + cfg2 = pickle.loads(pcfg) + self.assertEqual(cfg2, cfg) + + def test_getattr_section(self): + cfg = Config() + self.assertNotIn('Foo', cfg) + Foo = cfg.Foo + assert isinstance(Foo, Config) + self.assertIn('Foo', cfg) + + def test_getitem_section(self): + cfg = Config() + self.assertNotIn('Foo', cfg) + Foo = cfg['Foo'] + assert isinstance(Foo, Config) + self.assertIn('Foo', cfg) + + def test_getattr_not_section(self): + cfg = Config() + self.assertNotIn('foo', cfg) + foo = cfg.foo + assert isinstance(foo, LazyConfigValue) + self.assertIn('foo', cfg) + + def test_getattr_private_missing(self): + cfg = Config() + self.assertNotIn('_repr_html_', cfg) + with self.assertRaises(AttributeError): + _ = cfg._repr_html_ + self.assertNotIn('_repr_html_', cfg) + self.assertEqual(len(cfg), 0) + + def test_getitem_not_section(self): + cfg = Config() + self.assertNotIn('foo', cfg) + foo = cfg['foo'] + assert isinstance(foo, LazyConfigValue) + self.assertIn('foo', cfg) + + def test_merge_no_copies(self): + c = Config() + c2 = Config() + c2.Foo.trait = [] + c.merge(c2) + c2.Foo.trait.append(1) + self.assertIs(c.Foo, c2.Foo) + self.assertEqual(c.Foo.trait, [1]) + self.assertEqual(c2.Foo.trait, [1]) + diff --git a/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py b/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py index 7ec6e37bfb..11b334cb60 100644 --- a/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py +++ b/contrib/python/traitlets/py2/traitlets/tests/test_traitlets.py @@ -10,7 +10,7 @@ import pickle import re import sys -from ._warnings import expected_warnings +from ._warnings import expected_warnings from unittest import TestCase import pytest diff --git a/contrib/python/traitlets/py2/traitlets/tests/utils.py b/contrib/python/traitlets/py2/traitlets/tests/utils.py index 25f59b74a0..88845d8519 100644 --- a/contrib/python/traitlets/py2/traitlets/tests/utils.py +++ b/contrib/python/traitlets/py2/traitlets/tests/utils.py @@ -4,7 +4,7 @@ from subprocess import Popen, PIPE def get_output_error_code(cmd): """Get stdout, stderr, and exit code from running a command""" - p = Popen(cmd, stdout=PIPE, stderr=PIPE) + p = Popen(cmd, stdout=PIPE, stderr=PIPE) out, err = p.communicate() out = out.decode('utf8', 'replace') err = err.decode('utf8', 'replace') diff --git a/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py b/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py index ca4357c674..ef5c3c1384 100644 --- a/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py +++ b/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py @@ -1,14 +1,14 @@ -from ..bunch import Bunch - -def test_bunch(): - b = Bunch(x=5, y=10) - assert 'y' in b - assert 'x' in b - assert b.x == 5 - b['a'] = 'hi' - assert b.a == 'hi' - -def test_bunch_dir(): - b = Bunch(x=5, y=10) - assert 'x' in dir(b) - assert 'keys' in dir(b) +from ..bunch import Bunch + +def test_bunch(): + b = Bunch(x=5, y=10) + assert 'y' in b + assert 'x' in b + assert b.x == 5 + b['a'] = 'hi' + assert b.a == 'hi' + +def test_bunch_dir(): + b = Bunch(x=5, y=10) + assert 'x' in dir(b) + assert 'keys' in dir(b) diff --git a/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py b/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py index 13cf43639b..c86459ff42 100644 --- a/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py +++ b/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py @@ -1,30 +1,30 @@ -# encoding: utf-8 -# 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. -"""Tests for traitlets.utils.importstring.""" - -import os -from unittest import TestCase - -from ..importstring import import_item - - -class TestImportItem(TestCase): - - def test_import_unicode(self): - self.assertIs(os, import_item(u'os')) - self.assertIs(os.path, import_item(u'os.path')) - self.assertIs(os.path.join, import_item(u'os.path.join')) - - def test_bad_input(self): - class NotAString(object): - pass - msg = ( - "import_item accepts strings, " - "not '%s'." % NotAString - ) - with self.assertRaisesRegexp(TypeError, msg): - import_item(NotAString()) +# encoding: utf-8 +# 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. +"""Tests for traitlets.utils.importstring.""" + +import os +from unittest import TestCase + +from ..importstring import import_item + + +class TestImportItem(TestCase): + + def test_import_unicode(self): + self.assertIs(os, import_item(u'os')) + self.assertIs(os.path, import_item(u'os.path')) + self.assertIs(os.path.join, import_item(u'os.path.join')) + + def test_bad_input(self): + class NotAString(object): + pass + msg = ( + "import_item accepts strings, " + "not '%s'." % NotAString + ) + with self.assertRaisesRegexp(TypeError, msg): + import_item(NotAString()) diff --git a/contrib/python/traitlets/py2/ya.make b/contrib/python/traitlets/py2/ya.make index c65ddc5b70..4a60107101 100644 --- a/contrib/python/traitlets/py2/ya.make +++ b/contrib/python/traitlets/py2/ya.make @@ -1,24 +1,24 @@ -# Generated by devtools/yamaker (pypi). - -PY2_LIBRARY() +# Generated by devtools/yamaker (pypi). -PROVIDES(python_traitlets) +PY2_LIBRARY() -OWNER(borman nslus g:python-contrib) - -VERSION(4.3.3) +PROVIDES(python_traitlets) + +OWNER(borman nslus g:python-contrib) + +VERSION(4.3.3) + +LICENSE(BSD-3-Clause) -LICENSE(BSD-3-Clause) - PEERDIR( - contrib/python/decorator - contrib/python/enum34 - contrib/python/ipython-genutils + contrib/python/decorator + contrib/python/enum34 + contrib/python/ipython-genutils contrib/python/six ) -NO_LINT() - +NO_LINT() + PY_SRCS( TOP_LEVEL traitlets/__init__.py @@ -37,14 +37,14 @@ PY_SRCS( traitlets/utils/sentinel.py ) -RESOURCE_FILES( - PREFIX contrib/python/traitlets/py2/ - .dist-info/METADATA - .dist-info/top_level.txt -) - +RESOURCE_FILES( + PREFIX contrib/python/traitlets/py2/ + .dist-info/METADATA + .dist-info/top_level.txt +) + END() RECURSE_FOR_TESTS( - tests + tests ) diff --git a/contrib/python/traitlets/py3/.dist-info/METADATA b/contrib/python/traitlets/py3/.dist-info/METADATA index 58c280914c..7387396b72 100644 --- a/contrib/python/traitlets/py3/.dist-info/METADATA +++ b/contrib/python/traitlets/py3/.dist-info/METADATA @@ -1,195 +1,195 @@ -Metadata-Version: 2.1 -Name: traitlets +Metadata-Version: 2.1 +Name: traitlets Version: 5.1.1 -Summary: Traitlets Python configuration system -Home-page: https://github.com/ipython/traitlets -Author: IPython Development Team -Author-email: ipython-dev@python.org -License: BSD -Project-URL: Documentation, https://traitlets.readthedocs.io/ -Project-URL: Funding, https://numfocus.org/ -Project-URL: Source, https://github.com/ipython/traitlets -Project-URL: Tracker, https://github.com/ipython/traitlets/issues -Keywords: Interactive,Interpreter,Shell,Web -Platform: Linux -Platform: Mac OS X -Platform: Windows -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: System Administrators -Classifier: Intended Audience :: Science/Research -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Requires-Python: >=3.7 -Description-Content-Type: text/markdown +Summary: Traitlets Python configuration system +Home-page: https://github.com/ipython/traitlets +Author: IPython Development Team +Author-email: ipython-dev@python.org +License: BSD +Project-URL: Documentation, https://traitlets.readthedocs.io/ +Project-URL: Funding, https://numfocus.org/ +Project-URL: Source, https://github.com/ipython/traitlets +Project-URL: Tracker, https://github.com/ipython/traitlets/issues +Keywords: Interactive,Interpreter,Shell,Web +Platform: Linux +Platform: Mac OS X +Platform: Windows +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Requires-Python: >=3.7 +Description-Content-Type: text/markdown License-File: COPYING.md -Provides-Extra: test -Requires-Dist: pytest ; extra == 'test' - -# Traitlets - -[![Tests](https://github.com/ipython/traitlets/actions/workflows/tests.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/tests.yml) -[![Test downstream projects](https://github.com/ipython/traitlets/actions/workflows/downstream.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/downstream.yml) -[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](https://traitlets.readthedocs.io/en/latest/?badge=latest) - -| | | -|---------------|----------------------------------------| -| **home** | https://github.com/ipython/traitlets | -| **pypi-repo** | https://pypi.org/project/traitlets/ | -| **docs** | https://traitlets.readthedocs.io/ | -| **license** | Modified BSD License | - -Traitlets is a pure Python library enabling: - -- the enforcement of strong typing for attributes of Python objects - (typed attributes are called *"traits"*); -- dynamically calculated default values; -- automatic validation and coercion of trait attributes when attempting a - change; -- registering for receiving notifications when trait values change; -- reading configuring values from files or from command line - arguments - a distinct layer on top of traitlets, so you may use - traitlets without the configuration machinery. - -Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html) -pattern, and it is a lightweight pure-python alternative of the -[*traits* library](https://docs.enthought.com/traits/). - -Traitlets powers the configuration system of IPython and Jupyter -and the declarative API of IPython interactive widgets. - -## Installation - -For a local installation, make sure you have -[pip installed](https://pip.pypa.io/en/stable/installing/) and run: - -```bash -pip install traitlets -``` - -For a **development installation**, clone this repository, change into the -`traitlets` root directory, and run pip: - -```bash -git clone https://github.com/ipython/traitlets.git -cd traitlets -pip install -e . -``` - -## Running the tests - -```bash -pip install "traitlets[test]" -py.test traitlets -``` - -## Usage - -Any class with trait attributes must inherit from `HasTraits`. -For the list of available trait types and their properties, see the -[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html) -section of the documentation. - -### Dynamic default values - -To calculate a default value dynamically, decorate a method of your class with -`@default({traitname})`. This method will be called on the instance, and -should return the default value. In this example, the `_username_default` -method is decorated with `@default('username')`: - -```Python -import getpass -from traitlets import HasTraits, Unicode, default - -class Identity(HasTraits): - username = Unicode() - - @default('username') - def _username_default(self): - return getpass.getuser() -``` - -### Callbacks when a trait attribute changes - -When a trait changes, an application can follow this trait change with -additional actions. - -To do something when a trait attribute is changed, decorate a method with -[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe). -The method will be called with a single argument, a dictionary which contains -an owner, new value, old value, name of the changed trait, and the event type. - -In this example, the `_num_changed` method is decorated with ``@observe(`num`)``: - -```Python -from traitlets import HasTraits, Integer, observe - -class TraitletsExample(HasTraits): - num = Integer(5, help="a number").tag(config=True) - - @observe('num') - def _num_changed(self, change): - print("{name} changed from {old} to {new}".format(**change)) -``` - -and is passed the following dictionary when called: - -```Python -{ - 'owner': object, # The HasTraits instance - 'new': 6, # The new value - 'old': 5, # The old value - 'name': "foo", # The name of the changed trait - 'type': 'change', # The event type of the notification, usually 'change' -} -``` - -### Validation and coercion - -Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or -coercion logic. In addition, we can register custom cross-validators -that may depend on the state of other attributes. For example: - -```Python -from traitlets import HasTraits, TraitError, Int, Bool, validate - -class Parity(HasTraits): - value = Int() - parity = Int() - - @validate('value') - def _valid_value(self, proposal): - if proposal['value'] % 2 != self.parity: - raise TraitError('value and parity should be consistent') - return proposal['value'] - - @validate('parity') - def _valid_parity(self, proposal): - parity = proposal['value'] - if parity not in [0, 1]: - raise TraitError('parity should be 0 or 1') - if self.value % 2 != parity: - raise TraitError('value and parity should be consistent') - return proposal['value'] - -parity_check = Parity(value=2) - -# Changing required parity and value together while holding cross validation -with parity_check.hold_trait_notifications(): - parity_check.value = 1 - parity_check.parity = 1 -``` - -However, we **recommend** that custom cross-validators don't modify the state -of the HasTraits instance. - -### Release build: - +Provides-Extra: test +Requires-Dist: pytest ; extra == 'test' + +# Traitlets + +[![Tests](https://github.com/ipython/traitlets/actions/workflows/tests.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/tests.yml) +[![Test downstream projects](https://github.com/ipython/traitlets/actions/workflows/downstream.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/downstream.yml) +[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](https://traitlets.readthedocs.io/en/latest/?badge=latest) + +| | | +|---------------|----------------------------------------| +| **home** | https://github.com/ipython/traitlets | +| **pypi-repo** | https://pypi.org/project/traitlets/ | +| **docs** | https://traitlets.readthedocs.io/ | +| **license** | Modified BSD License | + +Traitlets is a pure Python library enabling: + +- the enforcement of strong typing for attributes of Python objects + (typed attributes are called *"traits"*); +- dynamically calculated default values; +- automatic validation and coercion of trait attributes when attempting a + change; +- registering for receiving notifications when trait values change; +- reading configuring values from files or from command line + arguments - a distinct layer on top of traitlets, so you may use + traitlets without the configuration machinery. + +Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html) +pattern, and it is a lightweight pure-python alternative of the +[*traits* library](https://docs.enthought.com/traits/). + +Traitlets powers the configuration system of IPython and Jupyter +and the declarative API of IPython interactive widgets. + +## Installation + +For a local installation, make sure you have +[pip installed](https://pip.pypa.io/en/stable/installing/) and run: + +```bash +pip install traitlets +``` + +For a **development installation**, clone this repository, change into the +`traitlets` root directory, and run pip: + +```bash +git clone https://github.com/ipython/traitlets.git +cd traitlets +pip install -e . +``` + +## Running the tests + +```bash +pip install "traitlets[test]" +py.test traitlets +``` + +## Usage + +Any class with trait attributes must inherit from `HasTraits`. +For the list of available trait types and their properties, see the +[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html) +section of the documentation. + +### Dynamic default values + +To calculate a default value dynamically, decorate a method of your class with +`@default({traitname})`. This method will be called on the instance, and +should return the default value. In this example, the `_username_default` +method is decorated with `@default('username')`: + +```Python +import getpass +from traitlets import HasTraits, Unicode, default + +class Identity(HasTraits): + username = Unicode() + + @default('username') + def _username_default(self): + return getpass.getuser() +``` + +### Callbacks when a trait attribute changes + +When a trait changes, an application can follow this trait change with +additional actions. + +To do something when a trait attribute is changed, decorate a method with +[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe). +The method will be called with a single argument, a dictionary which contains +an owner, new value, old value, name of the changed trait, and the event type. + +In this example, the `_num_changed` method is decorated with ``@observe(`num`)``: + +```Python +from traitlets import HasTraits, Integer, observe + +class TraitletsExample(HasTraits): + num = Integer(5, help="a number").tag(config=True) + + @observe('num') + def _num_changed(self, change): + print("{name} changed from {old} to {new}".format(**change)) +``` + +and is passed the following dictionary when called: + +```Python +{ + 'owner': object, # The HasTraits instance + 'new': 6, # The new value + 'old': 5, # The old value + 'name': "foo", # The name of the changed trait + 'type': 'change', # The event type of the notification, usually 'change' +} +``` + +### Validation and coercion + +Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or +coercion logic. In addition, we can register custom cross-validators +that may depend on the state of other attributes. For example: + +```Python +from traitlets import HasTraits, TraitError, Int, Bool, validate + +class Parity(HasTraits): + value = Int() + parity = Int() + + @validate('value') + def _valid_value(self, proposal): + if proposal['value'] % 2 != self.parity: + raise TraitError('value and parity should be consistent') + return proposal['value'] + + @validate('parity') + def _valid_parity(self, proposal): + parity = proposal['value'] + if parity not in [0, 1]: + raise TraitError('parity should be 0 or 1') + if self.value % 2 != parity: + raise TraitError('value and parity should be consistent') + return proposal['value'] + +parity_check = Parity(value=2) + +# Changing required parity and value together while holding cross validation +with parity_check.hold_trait_notifications(): + parity_check.value = 1 + parity_check.parity = 1 +``` + +However, we **recommend** that custom cross-validators don't modify the state +of the HasTraits instance. + +### Release build: + Releases should be automatically build and pushed to Pypi when a tag is marked and pushed to GitHub. -```bash -$ pip install build -$ python -m build . -``` - - +```bash +$ pip install build +$ python -m build . +``` + + diff --git a/contrib/python/traitlets/py3/.dist-info/top_level.txt b/contrib/python/traitlets/py3/.dist-info/top_level.txt index 3d938864d2..adfea9c6eb 100644 --- a/contrib/python/traitlets/py3/.dist-info/top_level.txt +++ b/contrib/python/traitlets/py3/.dist-info/top_level.txt @@ -1 +1 @@ -traitlets +traitlets diff --git a/contrib/python/traitlets/py3/.yandex_meta/yamaker.yaml b/contrib/python/traitlets/py3/.yandex_meta/yamaker.yaml index f606cd815c..e85fb94a55 100644 --- a/contrib/python/traitlets/py3/.yandex_meta/yamaker.yaml +++ b/contrib/python/traitlets/py3/.yandex_meta/yamaker.yaml @@ -1,4 +1,4 @@ -mark_as_sources: -- traitlets/tests/__init__.py -- traitlets/tests/_warnings.py -- traitlets/tests/utils.py +mark_as_sources: +- traitlets/tests/__init__.py +- traitlets/tests/_warnings.py +- traitlets/tests/utils.py diff --git a/contrib/python/traitlets/py3/README.md b/contrib/python/traitlets/py3/README.md index 5922dae51b..b0c1b2e67c 100644 --- a/contrib/python/traitlets/py3/README.md +++ b/contrib/python/traitlets/py3/README.md @@ -1,165 +1,165 @@ -# Traitlets - -[![Tests](https://github.com/ipython/traitlets/actions/workflows/tests.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/tests.yml) -[![Test downstream projects](https://github.com/ipython/traitlets/actions/workflows/downstream.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/downstream.yml) -[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](https://traitlets.readthedocs.io/en/latest/?badge=latest) - -| | | -|---------------|----------------------------------------| -| **home** | https://github.com/ipython/traitlets | -| **pypi-repo** | https://pypi.org/project/traitlets/ | -| **docs** | https://traitlets.readthedocs.io/ | -| **license** | Modified BSD License | - -Traitlets is a pure Python library enabling: - -- the enforcement of strong typing for attributes of Python objects - (typed attributes are called *"traits"*); -- dynamically calculated default values; -- automatic validation and coercion of trait attributes when attempting a - change; -- registering for receiving notifications when trait values change; -- reading configuring values from files or from command line - arguments - a distinct layer on top of traitlets, so you may use - traitlets without the configuration machinery. - -Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html) -pattern, and it is a lightweight pure-python alternative of the -[*traits* library](https://docs.enthought.com/traits/). - -Traitlets powers the configuration system of IPython and Jupyter -and the declarative API of IPython interactive widgets. - -## Installation - -For a local installation, make sure you have -[pip installed](https://pip.pypa.io/en/stable/installing/) and run: - -```bash -pip install traitlets -``` - -For a **development installation**, clone this repository, change into the -`traitlets` root directory, and run pip: - -```bash -git clone https://github.com/ipython/traitlets.git -cd traitlets -pip install -e . -``` - -## Running the tests - -```bash -pip install "traitlets[test]" -py.test traitlets -``` - -## Usage - -Any class with trait attributes must inherit from `HasTraits`. -For the list of available trait types and their properties, see the -[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html) -section of the documentation. - -### Dynamic default values - -To calculate a default value dynamically, decorate a method of your class with -`@default({traitname})`. This method will be called on the instance, and -should return the default value. In this example, the `_username_default` -method is decorated with `@default('username')`: - -```Python -import getpass -from traitlets import HasTraits, Unicode, default - -class Identity(HasTraits): - username = Unicode() - - @default('username') - def _username_default(self): - return getpass.getuser() -``` - -### Callbacks when a trait attribute changes - -When a trait changes, an application can follow this trait change with -additional actions. - -To do something when a trait attribute is changed, decorate a method with -[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe). -The method will be called with a single argument, a dictionary which contains -an owner, new value, old value, name of the changed trait, and the event type. - -In this example, the `_num_changed` method is decorated with ``@observe(`num`)``: - -```Python -from traitlets import HasTraits, Integer, observe - -class TraitletsExample(HasTraits): - num = Integer(5, help="a number").tag(config=True) - - @observe('num') - def _num_changed(self, change): - print("{name} changed from {old} to {new}".format(**change)) -``` - -and is passed the following dictionary when called: - -```Python -{ - 'owner': object, # The HasTraits instance - 'new': 6, # The new value - 'old': 5, # The old value - 'name': "foo", # The name of the changed trait - 'type': 'change', # The event type of the notification, usually 'change' -} -``` - -### Validation and coercion - -Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or -coercion logic. In addition, we can register custom cross-validators -that may depend on the state of other attributes. For example: - -```Python -from traitlets import HasTraits, TraitError, Int, Bool, validate - -class Parity(HasTraits): - value = Int() - parity = Int() - - @validate('value') - def _valid_value(self, proposal): - if proposal['value'] % 2 != self.parity: - raise TraitError('value and parity should be consistent') - return proposal['value'] - - @validate('parity') - def _valid_parity(self, proposal): - parity = proposal['value'] - if parity not in [0, 1]: - raise TraitError('parity should be 0 or 1') - if self.value % 2 != parity: - raise TraitError('value and parity should be consistent') - return proposal['value'] - -parity_check = Parity(value=2) - -# Changing required parity and value together while holding cross validation -with parity_check.hold_trait_notifications(): - parity_check.value = 1 - parity_check.parity = 1 -``` - -However, we **recommend** that custom cross-validators don't modify the state -of the HasTraits instance. - -### Release build: - +# Traitlets + +[![Tests](https://github.com/ipython/traitlets/actions/workflows/tests.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/tests.yml) +[![Test downstream projects](https://github.com/ipython/traitlets/actions/workflows/downstream.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/downstream.yml) +[![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](https://traitlets.readthedocs.io/en/latest/?badge=latest) + +| | | +|---------------|----------------------------------------| +| **home** | https://github.com/ipython/traitlets | +| **pypi-repo** | https://pypi.org/project/traitlets/ | +| **docs** | https://traitlets.readthedocs.io/ | +| **license** | Modified BSD License | + +Traitlets is a pure Python library enabling: + +- the enforcement of strong typing for attributes of Python objects + (typed attributes are called *"traits"*); +- dynamically calculated default values; +- automatic validation and coercion of trait attributes when attempting a + change; +- registering for receiving notifications when trait values change; +- reading configuring values from files or from command line + arguments - a distinct layer on top of traitlets, so you may use + traitlets without the configuration machinery. + +Its implementation relies on the [descriptor](https://docs.python.org/howto/descriptor.html) +pattern, and it is a lightweight pure-python alternative of the +[*traits* library](https://docs.enthought.com/traits/). + +Traitlets powers the configuration system of IPython and Jupyter +and the declarative API of IPython interactive widgets. + +## Installation + +For a local installation, make sure you have +[pip installed](https://pip.pypa.io/en/stable/installing/) and run: + +```bash +pip install traitlets +``` + +For a **development installation**, clone this repository, change into the +`traitlets` root directory, and run pip: + +```bash +git clone https://github.com/ipython/traitlets.git +cd traitlets +pip install -e . +``` + +## Running the tests + +```bash +pip install "traitlets[test]" +py.test traitlets +``` + +## Usage + +Any class with trait attributes must inherit from `HasTraits`. +For the list of available trait types and their properties, see the +[Trait Types](https://traitlets.readthedocs.io/en/latest/trait_types.html) +section of the documentation. + +### Dynamic default values + +To calculate a default value dynamically, decorate a method of your class with +`@default({traitname})`. This method will be called on the instance, and +should return the default value. In this example, the `_username_default` +method is decorated with `@default('username')`: + +```Python +import getpass +from traitlets import HasTraits, Unicode, default + +class Identity(HasTraits): + username = Unicode() + + @default('username') + def _username_default(self): + return getpass.getuser() +``` + +### Callbacks when a trait attribute changes + +When a trait changes, an application can follow this trait change with +additional actions. + +To do something when a trait attribute is changed, decorate a method with +[`traitlets.observe()`](https://traitlets.readthedocs.io/en/latest/api.html?highlight=observe#traitlets.observe). +The method will be called with a single argument, a dictionary which contains +an owner, new value, old value, name of the changed trait, and the event type. + +In this example, the `_num_changed` method is decorated with ``@observe(`num`)``: + +```Python +from traitlets import HasTraits, Integer, observe + +class TraitletsExample(HasTraits): + num = Integer(5, help="a number").tag(config=True) + + @observe('num') + def _num_changed(self, change): + print("{name} changed from {old} to {new}".format(**change)) +``` + +and is passed the following dictionary when called: + +```Python +{ + 'owner': object, # The HasTraits instance + 'new': 6, # The new value + 'old': 5, # The old value + 'name': "foo", # The name of the changed trait + 'type': 'change', # The event type of the notification, usually 'change' +} +``` + +### Validation and coercion + +Each trait type (`Int`, `Unicode`, `Dict` etc.) may have its own validation or +coercion logic. In addition, we can register custom cross-validators +that may depend on the state of other attributes. For example: + +```Python +from traitlets import HasTraits, TraitError, Int, Bool, validate + +class Parity(HasTraits): + value = Int() + parity = Int() + + @validate('value') + def _valid_value(self, proposal): + if proposal['value'] % 2 != self.parity: + raise TraitError('value and parity should be consistent') + return proposal['value'] + + @validate('parity') + def _valid_parity(self, proposal): + parity = proposal['value'] + if parity not in [0, 1]: + raise TraitError('parity should be 0 or 1') + if self.value % 2 != parity: + raise TraitError('value and parity should be consistent') + return proposal['value'] + +parity_check = Parity(value=2) + +# Changing required parity and value together while holding cross validation +with parity_check.hold_trait_notifications(): + parity_check.value = 1 + parity_check.parity = 1 +``` + +However, we **recommend** that custom cross-validators don't modify the state +of the HasTraits instance. + +### Release build: + Releases should be automatically build and pushed to Pypi when a tag is marked and pushed to GitHub. -```bash -$ pip install build -$ python -m build . -``` +```bash +$ pip install build +$ python -m build . +``` diff --git a/contrib/python/traitlets/py3/patches/01-fix-tests.patch b/contrib/python/traitlets/py3/patches/01-fix-tests.patch index 43e1562619..a4b6de274d 100644 --- a/contrib/python/traitlets/py3/patches/01-fix-tests.patch +++ b/contrib/python/traitlets/py3/patches/01-fix-tests.patch @@ -1,92 +1,92 @@ ---- contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py (index) -+++ contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py (working tree) -@@ -22,7 +22,7 @@ from traitlets.traitlets import ( - - from traitlets.config.loader import Config - --from ...tests._warnings import expected_warnings -+from traitlets.tests._warnings import expected_warnings - - class MyConfigurable(Configurable): - a = Integer(1, help="The integer a.").tag(config=True) ---- contrib/python/traitlets/py3/traitlets/config/tests/test_application.py (index) -+++ contrib/python/traitlets/py3/traitlets/config/tests/test_application.py (working tree) -@@ -629,6 +629,8 @@ class TestApplication(TestCase): - self.assertEqual(app.running, False) - - -+ -+@mark.skip - def test_cli_multi_scalar(caplog): - class App(Application): - aliases = {"opt": "App.opt"} -@@ -648,7 +650,7 @@ def test_cli_multi_scalar(caplog): - - class Root(Application): - subcommands = { -- 'sub1': ('traitlets.config.tests.test_application.Sub1', 'import string'), -+ 'sub1': ('__tests__.config.tests.test_application.Sub1', 'import string'), - } - - ---- contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py (index) -+++ contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py (working tree) -@@ -13,7 +13,7 @@ from unittest import TestCase - - import pytest - --from ._warnings import expected_warnings -+from traitlets.tests._warnings import expected_warnings - from traitlets import ( - HasTraits, - MetaHasTraits, ---- contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py (index) -+++ contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py (working tree) -@@ -1,4 +1,4 @@ --from ..bunch import Bunch -+from traitlets.utils.bunch import Bunch - - def test_bunch(): - b = Bunch(x=5, y=10) ---- contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py (index) -+++ contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py (working tree) -@@ -2,9 +2,9 @@ from unittest import TestCase - - from inspect import Signature, Parameter, signature - --from ...traitlets import HasTraits, Int, Unicode -+from traitlets.traitlets import HasTraits, Int, Unicode - --from ..decorators import signature_has_traits -+from traitlets.utils.decorators import signature_has_traits - - - class TestExpandSignature(TestCase): ---- contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py (index) -+++ contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py (working tree) -@@ -8,7 +8,7 @@ - import os - from unittest import TestCase - --from ..importstring import import_item -+from traitlets.utils.importstring import import_item - - - class TestImportItem(TestCase): ---- contrib/python/traitlets/py3/traitlets/tests/utils.py (index) -+++ contrib/python/traitlets/py3/traitlets/tests/utils.py (working tree) -@@ -1,10 +1,13 @@ - from subprocess import Popen, PIPE - import sys -+import os - - - def get_output_error_code(cmd): - """Get stdout, stderr, and exit code from running a command""" -- p = Popen(cmd, stdout=PIPE, stderr=PIPE) -+ env = os.environ.copy() -+ env['Y_PYTHON_ENTRY_POINT'] = ':main' -+ p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) - out, err = p.communicate() - out = out.decode('utf8', 'replace') - err = err.decode('utf8', 'replace') +--- contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py (index) ++++ contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py (working tree) +@@ -22,7 +22,7 @@ from traitlets.traitlets import ( + + from traitlets.config.loader import Config + +-from ...tests._warnings import expected_warnings ++from traitlets.tests._warnings import expected_warnings + + class MyConfigurable(Configurable): + a = Integer(1, help="The integer a.").tag(config=True) +--- contrib/python/traitlets/py3/traitlets/config/tests/test_application.py (index) ++++ contrib/python/traitlets/py3/traitlets/config/tests/test_application.py (working tree) +@@ -629,6 +629,8 @@ class TestApplication(TestCase): + self.assertEqual(app.running, False) + + ++ ++@mark.skip + def test_cli_multi_scalar(caplog): + class App(Application): + aliases = {"opt": "App.opt"} +@@ -648,7 +650,7 @@ def test_cli_multi_scalar(caplog): + + class Root(Application): + subcommands = { +- 'sub1': ('traitlets.config.tests.test_application.Sub1', 'import string'), ++ 'sub1': ('__tests__.config.tests.test_application.Sub1', 'import string'), + } + + +--- contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py (index) ++++ contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py (working tree) +@@ -13,7 +13,7 @@ from unittest import TestCase + + import pytest + +-from ._warnings import expected_warnings ++from traitlets.tests._warnings import expected_warnings + from traitlets import ( + HasTraits, + MetaHasTraits, +--- contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py (index) ++++ contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py (working tree) +@@ -1,4 +1,4 @@ +-from ..bunch import Bunch ++from traitlets.utils.bunch import Bunch + + def test_bunch(): + b = Bunch(x=5, y=10) +--- contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py (index) ++++ contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py (working tree) +@@ -2,9 +2,9 @@ from unittest import TestCase + + from inspect import Signature, Parameter, signature + +-from ...traitlets import HasTraits, Int, Unicode ++from traitlets.traitlets import HasTraits, Int, Unicode + +-from ..decorators import signature_has_traits ++from traitlets.utils.decorators import signature_has_traits + + + class TestExpandSignature(TestCase): +--- contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py (index) ++++ contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py (working tree) +@@ -8,7 +8,7 @@ + import os + from unittest import TestCase + +-from ..importstring import import_item ++from traitlets.utils.importstring import import_item + + + class TestImportItem(TestCase): +--- contrib/python/traitlets/py3/traitlets/tests/utils.py (index) ++++ contrib/python/traitlets/py3/traitlets/tests/utils.py (working tree) +@@ -1,10 +1,13 @@ + from subprocess import Popen, PIPE + import sys ++import os + + + def get_output_error_code(cmd): + """Get stdout, stderr, and exit code from running a command""" +- p = Popen(cmd, stdout=PIPE, stderr=PIPE) ++ env = os.environ.copy() ++ env['Y_PYTHON_ENTRY_POINT'] = ':main' ++ p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) + out, err = p.communicate() + out = out.decode('utf8', 'replace') + err = err.decode('utf8', 'replace') diff --git a/contrib/python/traitlets/py3/tests/ya.make b/contrib/python/traitlets/py3/tests/ya.make index 16a1115365..e085c70153 100644 --- a/contrib/python/traitlets/py3/tests/ya.make +++ b/contrib/python/traitlets/py3/tests/ya.make @@ -1,24 +1,24 @@ -PY3TEST() - -OWNER(g:python-contrib borman nslus) - -PEERDIR( - contrib/python/traitlets -) - -SRCDIR(contrib/python/traitlets/py3/traitlets) - -TEST_SRCS( - config/tests/test_application.py - config/tests/test_configurable.py - config/tests/test_loader.py - tests/test_traitlets.py - tests/test_traitlets_enum.py - utils/tests/test_bunch.py - utils/tests/test_decorators.py - utils/tests/test_importstring.py -) - -NO_LINT() - -END() +PY3TEST() + +OWNER(g:python-contrib borman nslus) + +PEERDIR( + contrib/python/traitlets +) + +SRCDIR(contrib/python/traitlets/py3/traitlets) + +TEST_SRCS( + config/tests/test_application.py + config/tests/test_configurable.py + config/tests/test_loader.py + tests/test_traitlets.py + tests/test_traitlets_enum.py + utils/tests/test_bunch.py + utils/tests/test_decorators.py + utils/tests/test_importstring.py +) + +NO_LINT() + +END() diff --git a/contrib/python/traitlets/py3/traitlets/__init__.py b/contrib/python/traitlets/py3/traitlets/__init__.py index d9d3560f32..ad5ba73c86 100644 --- a/contrib/python/traitlets/py3/traitlets/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/__init__.py @@ -1,21 +1,21 @@ -from warnings import warn - -from . import traitlets +from warnings import warn + +from . import traitlets from .traitlets import * from .utils.importstring import import_item -from .utils.decorators import signature_has_traits -from .utils.bunch import Bunch +from .utils.decorators import signature_has_traits +from .utils.bunch import Bunch from ._version import version_info, __version__ - - -class Sentinel(traitlets.Sentinel): - def __init__(self, *args, **kwargs): - super(Sentinel, self).__init__(*args, **kwargs) - warn( - """ - Sentinel is not a public part of the traitlets API. - It was published by mistake, and may be removed in the future. - """, - DeprecationWarning, - stacklevel=2, - ) + + +class Sentinel(traitlets.Sentinel): + def __init__(self, *args, **kwargs): + super(Sentinel, self).__init__(*args, **kwargs) + warn( + """ + Sentinel is not a public part of the traitlets API. + It was published by mistake, and may be removed in the future. + """, + DeprecationWarning, + stacklevel=2, + ) diff --git a/contrib/python/traitlets/py3/traitlets/_version.py b/contrib/python/traitlets/py3/traitlets/_version.py index bdace7011d..5f05912b66 100644 --- a/contrib/python/traitlets/py3/traitlets/_version.py +++ b/contrib/python/traitlets/py3/traitlets/_version.py @@ -1,14 +1,14 @@ version_info = (5, 1, 1) - -# unlike `.dev`, alpha, beta and rc _must not_ have dots, -# or the wheel and tgz won't look to pip like the same version. - -__version__ = ( - ".".join(map(str, version_info)) - .replace(".b", "b") - .replace(".a", "a") - .replace(".rc", "rc") -) -assert ".b" not in __version__ -assert ".a" not in __version__ -assert ".rc" not in __version__ + +# unlike `.dev`, alpha, beta and rc _must not_ have dots, +# or the wheel and tgz won't look to pip like the same version. + +__version__ = ( + ".".join(map(str, version_info)) + .replace(".b", "b") + .replace(".a", "a") + .replace(".rc", "rc") +) +assert ".b" not in __version__ +assert ".a" not in __version__ +assert ".rc" not in __version__ diff --git a/contrib/python/traitlets/py3/traitlets/config/application.py b/contrib/python/traitlets/py3/traitlets/config/application.py index 5af4c229c7..99a6ef7ee0 100644 --- a/contrib/python/traitlets/py3/traitlets/config/application.py +++ b/contrib/python/traitlets/py3/traitlets/config/application.py @@ -4,16 +4,16 @@ # Distributed under the terms of the Modified BSD License. -from collections import defaultdict, OrderedDict +from collections import defaultdict, OrderedDict from copy import deepcopy -import functools +import functools import json import logging import os -import pprint +import pprint import re import sys -import warnings +import warnings from traitlets.config.configurable import Configurable, SingletonConfigurable from traitlets.config.loader import ( @@ -22,11 +22,11 @@ from traitlets.config.loader import ( 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 + +from ..utils.importstring import import_item +from ..utils import cast_unicode +from traitlets.utils.text import indent, wrap_paragraphs +from textwrap import dedent #----------------------------------------------------------------------------- @@ -34,18 +34,18 @@ from textwrap import dedent #----------------------------------------------------------------------------- # 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 +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 = """ -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)' +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 @@ -74,7 +74,7 @@ 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 ) -def catch_config_error(method): +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 @@ -82,16 +82,16 @@ def catch_config_error(method): For use on init methods, to prevent invoking excepthook on invalid input. """ - @functools.wraps(method) - def inner(app, *args, **kwargs): - try: - return method(app, *args, **kwargs) - except (TraitError, ArgumentError) as e: - app.log.fatal("Bad config encountered during initialization: %s", e) - app.log.debug("Config at the time: %s", app.config) - app.exit(1) + @functools.wraps(method) + def inner(app, *args, **kwargs): + try: + return method(app, *args, **kwargs) + except (TraitError, ArgumentError) as e: + 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 + return inner class ApplicationError(Exception): pass @@ -122,11 +122,11 @@ class Application(SingletonConfigurable): # The name of the application, will usually match the name of the command # line application - name = Unicode('application') + name = Unicode('application') # The description of the application that is printed at the beginning # of the help. - description = Unicode('This is an application.') + description = Unicode('This is an application.') # default section descriptions option_description = Unicode(option_description) keyvalue_description = Unicode(keyvalue_description) @@ -142,20 +142,20 @@ class Application(SingletonConfigurable): # be exposed at the command line. classes = [] - def _classes_inc_parents(self, classes=None): + def _classes_inc_parents(self, classes=None): """Iterate through configurable classes, including configurable parents - :param classes: - The list of classes to iterate; if not set, uses :attr:`classes`. - + :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. """ - if classes is None: - classes = self.classes - + if classes is None: + classes = self.classes + seen = set() - for c in classes: + 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): @@ -163,7 +163,7 @@ class Application(SingletonConfigurable): yield parent # The version string of this application. - version = Unicode('0.0') + version = Unicode('0.0') # the argv used to initialize the application argv = List() @@ -181,7 +181,7 @@ class Application(SingletonConfigurable): def _log_level_changed(self, change): """Adjust the log level when log_level is set.""" new = change.new - if isinstance(new, str): + if isinstance(new, str): new = getattr(logging, new) self.log_level = new self.log.setLevel(new) @@ -200,13 +200,13 @@ class Application(SingletonConfigurable): @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( - f"No Handler found on {self.log}, setting log_format will have no effect", - RuntimeWarning, - ) - return + _log_handler = self._get_log_handler() + if not _log_handler: + warnings.warn( + f"No Handler found on {self.log}, setting log_format will have no effect", + RuntimeWarning, + ) + return _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt) _log_handler.setFormatter(_log_formatter) @@ -240,32 +240,32 @@ class Application(SingletonConfigurable): 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'} + #: 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 = { - 'debug': ({ - 'Application': { - 'log_level': logging.DEBUG, - }, - }, "Set log-level to debug, for the most verbose logging."), - 'show-config': ({ - 'Application': { - 'show_config': True, - }, - }, "Show the application's configuration (human-readable format)"), - 'show-config-json': ({ - 'Application': { - 'show_config_json': True, - }, - }, "Show the application's configuration (json format)"), - } + flags = { + 'debug': ({ + 'Application': { + 'log_level': logging.DEBUG, + }, + }, "Set log-level to debug, for the most verbose logging."), + 'show-config': ({ + 'Application': { + 'show_config': True, + }, + }, "Show the application's configuration (human-readable format)"), + 'show-config-json': ({ + 'Application': { + 'show_config_json': True, + }, + }, "Show the application's configuration (json format)"), + } # subcommands for launching other applications # if this is not empty, this will be a parent Application @@ -287,26 +287,26 @@ class Application(SingletonConfigurable): """ ) - _loaded_config_files = List() - - show_config = Bool( - help="Instead of starting the Application, dump configuration to stdout" - ).tag(config=True) - - show_config_json = Bool( - help="Instead of starting the Application, dump configuration to stdout (as JSON)" - ).tag(config=True) - - @observe('show_config_json') - def _show_config_json_changed(self, change): - self.show_config = change.new - - @observe('show_config') - def _show_config_changed(self, change): - if change.new: - self._save_start = self.start - self.start = self.start_show_config - + _loaded_config_files = List() + + show_config = Bool( + help="Instead of starting the Application, dump configuration to stdout" + ).tag(config=True) + + show_config_json = Bool( + help="Instead of starting the Application, dump configuration to stdout (as JSON)" + ).tag(config=True) + + @observe('show_config_json') + def _show_config_json_changed(self, change): + self.show_config = change.new + + @observe('show_config') + def _show_config_changed(self, change): + if change.new: + 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 @@ -315,7 +315,7 @@ class Application(SingletonConfigurable): if cls not in self.classes: if self.classes is cls.classes: # class attr, assign instead of insert - self.classes = [cls] + self.classes + self.classes = [cls] + self.classes else: self.classes.insert(0, self.__class__) @@ -323,7 +323,7 @@ class Application(SingletonConfigurable): @observe_compat def _config_changed(self, change): super(Application, self)._config_changed(change) - self.log.debug('Config changed: %r', change.new) + self.log.debug('Config changed: %r', change.new) @catch_config_error def initialize(self, argv=None): @@ -342,49 +342,49 @@ class Application(SingletonConfigurable): 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() - # exclude show_config flags from displayed config - for cls in self.__class__.mro(): - if cls.__name__ in config: - cls_config = config[cls.__name__] - cls_config.pop('show_config', None) - cls_config.pop('show_config_json', None) - - if self.show_config_json: - json.dump(config, sys.stdout, - indent=1, sort_keys=True, default=repr) - # add trailing newline - sys.stdout.write('\n') - return - - if self._loaded_config_files: - print("Loaded config files:") - for f in self._loaded_config_files: - print(' ' + f) - print() - - for classname in sorted(config): - class_config = config[classname] - if not class_config: - continue - print(classname) - pformat_kwargs = dict(indent=4, compact=True) - - for traitname in sorted(class_config): - value = class_config[traitname] - print(' .{} = {}'.format( - traitname, - pprint.pformat(value, **pformat_kwargs), - )) - + def start_show_config(self): + """start function used when show_config is True""" + config = self.config.copy() + # exclude show_config flags from displayed config + for cls in self.__class__.mro(): + if cls.__name__ in config: + cls_config = config[cls.__name__] + cls_config.pop('show_config', None) + cls_config.pop('show_config_json', None) + + if self.show_config_json: + json.dump(config, sys.stdout, + indent=1, sort_keys=True, default=repr) + # add trailing newline + sys.stdout.write('\n') + return + + if self._loaded_config_files: + print("Loaded config files:") + for f in self._loaded_config_files: + print(' ' + f) + print() + + for classname in sorted(config): + class_config = config[classname] + if not class_config: + continue + print(classname) + pformat_kwargs = dict(indent=4, compact=True) + + for traitname in sorted(class_config): + value = class_config[traitname] + print(' .{} = {}'.format( + traitname, + pprint.pformat(value, **pformat_kwargs), + )) + 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.""" + """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 @@ -395,151 +395,151 @@ class Application(SingletonConfigurable): classdict[c.__name__] = c for alias, longname in self.aliases.items(): - try: - if isinstance(longname, tuple): - longname, fhelp = longname - else: - fhelp = None - 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) - alias = ', '.join(('--%s' if len(m) > 1 else '-%s') % m - for m in alias) - - # reformat first line - fhelp[0] = fhelp[0].replace('--' + longname, alias) - for l in fhelp: - yield l - yield indent("Equivalent to: [--%s]" % longname) - except Exception as ex: - self.log.error('Failed collecting help-message for alias %r, due to: %s', - alias, ex) - raise - + try: + if isinstance(longname, tuple): + longname, fhelp = longname + else: + fhelp = None + 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) + alias = ', '.join(('--%s' if len(m) > 1 else '-%s') % m + for m in alias) + + # reformat first line + fhelp[0] = fhelp[0].replace('--' + longname, alias) + for l in fhelp: + yield l + yield indent("Equivalent to: [--%s]" % longname) + except Exception as ex: + self.log.error('Failed collecting help-message for alias %r, due to: %s', + alias, ex) + raise + 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.""" + 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 - for flags, (cfg, fhelp) in self.flags.items(): - try: - if not isinstance(flags, tuple): - flags = (flags, ) - flags = sorted(flags, key=len) - flags = ', '.join(('--%s' if len(m) > 1 else '-%s') % m - for m in flags) - yield flags - yield indent(dedent(fhelp.strip())) - cfg_list = ' '.join('--%s.%s=%s' %(clname, prop, val) - for clname, props_dict - in cfg.items() - for prop, val in props_dict.items()) - cfg_txt = "Equivalent to: [%s]" % cfg_list - yield indent(dedent(cfg_txt)) - except Exception as ex: - self.log.error('Failed collecting help-message for flag %r, due to: %s', - flags, ex) - raise + for flags, (cfg, fhelp) in self.flags.items(): + try: + if not isinstance(flags, tuple): + flags = (flags, ) + flags = sorted(flags, key=len) + flags = ', '.join(('--%s' if len(m) > 1 else '-%s') % m + for m in flags) + yield flags + yield indent(dedent(fhelp.strip())) + cfg_list = ' '.join('--%s.%s=%s' %(clname, prop, val) + for clname, props_dict + in cfg.items() + for prop, val in props_dict.items()) + cfg_txt = "Equivalent to: [%s]" % cfg_list + yield indent(dedent(cfg_txt)) + except Exception as ex: + self.log.error('Failed collecting help-message for flag %r, due to: %s', + flags, ex) + raise 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.""" + """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 - header = 'Options' - yield header - yield '=' * len(header) + header = 'Options' + yield header + yield '=' * len(header) 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 '' - + 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.""" - print('\n'.join(self.emit_subcommands_help())) - - def emit_subcommands_help(self): - """Yield the lines for 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 - header = "Subcommands" - yield header - yield '=' * len(header) + header = "Subcommands" + yield header + yield '=' * len(header) for p in wrap_paragraphs(self.subcommand_description.format( app=self.name)): - yield p - yield '' + yield p + yield '' for subc, (cls, help) in self.subcommands.items(): - yield subc + yield subc if help: - yield indent(dedent(help.strip())) - yield '' - - def emit_help_epilogue(self, classes): - """Yield the very bottom lines of the help message. - - If classes=False (the default), print `--help-all` msg. - """ - if not classes: - yield "To see all available configurables, use `--help-all`." - yield '' - + yield indent(dedent(help.strip())) + yield '' + + def emit_help_epilogue(self, classes): + """Yield the very bottom lines of the help message. + + If classes=False (the default), print `--help-all` msg. + """ + if not classes: + 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. """ - 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. - - If classes=False (the default), only flags and aliases are printed. - """ - for l in self.emit_description(): - yield l - for l in self.emit_subcommands_help(): - yield l - for l in self.emit_options_help(): - yield l - + 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. + + If classes=False (the default), only flags and aliases are printed. + """ + for l in self.emit_description(): + yield l + for l in self.emit_subcommands_help(): + yield l + for l in self.emit_options_help(): + yield l + if classes: - help_classes = self._classes_with_config_traits() + help_classes = self._classes_with_config_traits() if help_classes: - yield "Class options" - yield "=============" + yield "Class options" + yield "=============" for p in wrap_paragraphs(self.keyvalue_description): - yield p - yield '' + yield p + yield '' for cls in help_classes: - yield cls.class_get_help() - yield '' - for l in self.emit_examples(): - yield l + yield cls.class_get_help() + yield '' + for l in self.emit_examples(): + yield l - for l in self.emit_help_epilogue(classes): - 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 @@ -551,30 +551,30 @@ class Application(SingletonConfigurable): 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 '' - + 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): - """Print usage and examples (see `emit_examples()`). """ - print('\n'.join(self.emit_examples())) + """Print usage and examples (see `emit_examples()`). """ + print('\n'.join(self.emit_examples())) + + def emit_examples(self): + """Yield lines with the usage and 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: - yield "Examples" - yield "--------" - yield '' - yield indent(dedent(self.examples.strip())) - yield '' + yield "Examples" + yield "--------" + yield '' + yield indent(dedent(self.examples.strip())) + yield '' def print_version(self): """Print the version string.""" @@ -583,33 +583,33 @@ class Application(SingletonConfigurable): @catch_config_error def initialize_subcommand(self, subc, argv=None): """Initialize a subcommand with argv.""" - subapp, _ = self.subcommands.get(subc) + subapp, _ = self.subcommands.get(subc) - if isinstance(subapp, str): + if isinstance(subapp, str): 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... - self.__class__.clear_instance() - # instantiating subapp... - self.subapp = subapp.instance(parent=self) - elif callable(subapp): - # or ask factory to create it... - self.subapp = subapp(self) - else: - raise AssertionError("Invalid mappings for subcommand '%s'!" % subc) - - # ... and finally initialize subapp. + ## Cannot issubclass() on a non-type (SOhttp://stackoverflow.com/questions/8692430) + if isinstance(subapp, type) and issubclass(subapp, Application): + # Clear existing instances before... + self.__class__.clear_instance() + # instantiating subapp... + self.subapp = subapp.instance(parent=self) + elif callable(subapp): + # or ask factory to create it... + self.subapp = subapp(self) + else: + raise AssertionError("Invalid mappings for subcommand '%s'!" % subc) + + # ... and finally initialize subapp. self.subapp.initialize(argv) def flatten_flags(self): - """Flatten flags and aliases for loaders, so cl-args override as expected. + """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. - Also, loaders expect ``(key: longname)`` and not ````key: (longname, help)`` items. + Also, loaders expect ``(key: longname)`` and not ````key: (longname, help)`` items. Only aliases with exactly one descendent in the class list will be promoted. @@ -627,18 +627,18 @@ class Application(SingletonConfigurable): # 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) + 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] - if not isinstance(aliases, tuple): - alias = (alias, ) - for al in alias: - aliases[al] = '.'.join([cls,trait]) + 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')} @@ -650,28 +650,28 @@ class Application(SingletonConfigurable): # exactly one descendent, promote flag section if len(children) == 1: cls = children[0] - - if cls in newflag: - newflag[cls].update(subdict) - else: - newflag[cls] = subdict - - if not isinstance(key, tuple): - key = (key, ) - for k in key: - flags[k] = (newflag, help) + + if cls in newflag: + newflag[cls].update(subdict) + else: + newflag[cls] = subdict + + if not isinstance(key, tuple): + key = (key, ) + for k in key: + flags[k] = (newflag, help) return flags, aliases - def _create_loader(self, argv, aliases, flags, classes): - return KVArgParseConfigLoader(argv, aliases, flags, classes=classes, - log=self.log) - + 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.""" - assert not isinstance(argv, str) + assert not isinstance(argv, str) argv = sys.argv[1:] if argv is None else argv - self.argv = [cast_unicode(arg) for arg in argv ] + self.argv = [cast_unicode(arg) for arg in argv ] if argv and argv[0] == 'help': # turn `ipython help notebook` into `ipython notebook -h` @@ -702,15 +702,15 @@ class Application(SingletonConfigurable): 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) - try: - self.cli_config = deepcopy(loader.load_config()) - except SystemExit: - # traitlets 5: no longer print help output on error - # help output is huge, and comes after the error - raise + flags, aliases = self.flatten_flags() + classes = tuple(self._classes_with_config_traits()) + loader = self._create_loader(argv, aliases, flags, classes=classes) + try: + self.cli_config = deepcopy(loader.load_config()) + except SystemExit: + # traitlets 5: no longer print help output on error + # 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 @@ -733,7 +733,7 @@ class Application(SingletonConfigurable): loaded = [] filenames = [] for loader in [pyloader, jsonloader]: - config = None + config = None try: config = loader.load_config() except ConfigFileNotFound: @@ -759,37 +759,37 @@ class Application(SingletonConfigurable): " {1} has higher priority: {2}".format( filename, loader.full_filename, json.dumps(collisions, indent=2), )) - yield (config, loader.full_filename) + 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[:] + @property + def loaded_config_files(self): + """Currently loaded configuration files""" + return self._loaded_config_files[:] @catch_config_error def load_config_file(self, filename, path=None): """Load config files by filename and path.""" filename, ext = os.path.splitext(filename) new_config = Config() - for (config, filename) in self._load_config_files(filename, path=path, log=self.log, + for (config, filename) in self._load_config_files(filename, path=path, log=self.log, raise_config_file_errors=self.raise_config_file_errors, ): new_config.merge(config) - if filename not in self._loaded_config_files: # only add to list of loaded files if not previously loaded - self._loaded_config_files.append(filename) + if filename not in self._loaded_config_files: # only add to list of loaded files if not previously loaded + self._loaded_config_files.append(filename) # add self.cli_config to preserve CLI config priority new_config.merge(self.cli_config) self.update_config(new_config) - def _classes_with_config_traits(self, classes=None): + def _classes_with_config_traits(self, classes=None): """ - Yields only classes with configurable traits, and their subclasses. + Yields only classes with configurable traits, and their subclasses. + + :param classes: + The list of classes to iterate; if not set, uses :attr:`classes`. - :param classes: - The list of classes to iterate; if not set, uses :attr:`classes`. - Thus, produced sample config-file will contain all classes on which a trait-value may be overridden: @@ -797,12 +797,12 @@ class Application(SingletonConfigurable): - or on its subclasses, even if those subclasses do not define any traits themselves. """ - if classes is None: - classes = self.classes - + if classes is None: + classes = self.classes + cls_to_config = OrderedDict( (cls, bool(cls.class_own_traits(config=True))) for cls - in self._classes_inc_parents(classes)) + in self._classes_inc_parents(classes)) def is_any_parent_included(cls): return any(b in cls_to_config and cls_to_config[b] for b in cls.__bases__) @@ -821,14 +821,14 @@ class Application(SingletonConfigurable): if inc_yes: yield cl - def generate_config_file(self, classes=None): + def generate_config_file(self, classes=None): """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)) + 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): @@ -849,9 +849,9 @@ class Application(SingletonConfigurable): # utility functions, for convenience #----------------------------------------------------------------------------- -default_aliases = Application.aliases -default_flags = Application.flags - +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. @@ -892,7 +892,7 @@ def get_config(): return Application.instance().config else: return Config() - - -if __name__ == '__main__': - Application.launch_instance() + + +if __name__ == '__main__': + Application.launch_instance() diff --git a/contrib/python/traitlets/py3/traitlets/config/configurable.py b/contrib/python/traitlets/py3/traitlets/config/configurable.py index 1059ed0ed7..3b2044a01b 100644 --- a/contrib/python/traitlets/py3/traitlets/config/configurable.py +++ b/contrib/python/traitlets/py3/traitlets/config/configurable.py @@ -5,27 +5,27 @@ from copy import deepcopy -import logging +import logging import warnings -from .loader import Config, LazyConfigValue, DeferredConfig, _is_section_key -from traitlets.traitlets import ( - Any, - HasTraits, - Instance, - Container, - Dict, - observe, - observe_compat, - default, - validate, -) -from traitlets.utils.text import indent, wrap_paragraphs -from textwrap import dedent - - - - +from .loader import Config, LazyConfigValue, DeferredConfig, _is_section_key +from traitlets.traitlets import ( + Any, + HasTraits, + Instance, + Container, + Dict, + observe, + observe_compat, + default, + validate, +) +from traitlets.utils.text import indent, wrap_paragraphs +from textwrap import dedent + + + + #----------------------------------------------------------------------------- # Helper classes for Configurables #----------------------------------------------------------------------------- @@ -84,17 +84,17 @@ class Configurable(HasTraits): # load kwarg traits, other than config super(Configurable, self).__init__(**kwargs) - # record traits set by config - config_override_names = set() - def notice_config_override(change): - """Record traits set by both config and kwargs. - - They will need to be overridden again after loading config. - """ - if change.name in kwargs: - config_override_names.add(change.name) - self.observe(notice_config_override) - + # record traits set by config + config_override_names = set() + def notice_config_override(change): + """Record traits set by both config and kwargs. + + They will need to be overridden again after loading config. + """ + if change.name in kwargs: + 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 @@ -108,12 +108,12 @@ class Configurable(HasTraits): else: # allow _config_default to return something self._load_config(self.config) - self.unobserve(notice_config_override) + self.unobserve(notice_config_override) + + for name in config_override_names: + setattr(self, name, kwargs[name]) - for name in config_override_names: - setattr(self, name, kwargs[name]) - #------------------------------------------------------------------------- # Static trait notifiations #------------------------------------------------------------------------- @@ -134,7 +134,7 @@ class Configurable(HasTraits): 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] + [Bar, Foo.Bar, Tim.Foo.Bar] With the last item being the highest priority. """ @@ -169,9 +169,9 @@ class Configurable(HasTraits): # 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]) + 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. @@ -183,11 +183,11 @@ class Configurable(HasTraits): else: warn = lambda msg: warnings.warn(msg, stacklevel=9) matches = get_close_matches(name, traits) - msg = "Config option `{option}` not recognized by `{klass}`.".format( + msg = "Config option `{option}` not recognized by `{klass}`.".format( option=name, klass=self.__class__.__name__) if len(matches) == 1: - msg += " Did you mean `{matches}`?".format(matches=matches[0]) + msg += " Did you mean `{matches}`?".format(matches=matches[0]) elif len(matches) >= 1: msg +=" Did you mean one of: `{matches}`?".format(matches=', '.join(sorted(matches))) warn(msg) @@ -235,54 +235,54 @@ class Configurable(HasTraits): """ 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])*'-') + 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 - def class_get_trait_help(cls, trait, inst=None, helptext=None): - """Get the helptext string for a single trait. - - :param inst: - If given, it's current trait values will be used in place of - the class default. - :param helptext: - If not given, uses the `help` attribute of the current trait. + def class_get_trait_help(cls, trait, inst=None, helptext=None): + """Get the helptext string for a single trait. + + :param inst: + If given, it's current trait values will be used in place of + 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 = [] - header = "--%s.%s" % (cls.__name__, trait.name) - if isinstance(trait, (Container, Dict)): - multiplicity = trait.metadata.get('multiplicity', 'append') - if isinstance(trait, Dict): - sample_value = '<key-1>=<value-1>' - else: - sample_value = '<%s-item-1>' % trait.__class__.__name__.lower() - if multiplicity == 'append': - header = "%s=%s..." % (header, sample_value) - else: - header = "%s %s..." % (header, sample_value) - else: - header = '%s=<%s>' % (header, trait.__class__.__name__) - #header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) + header = "--%s.%s" % (cls.__name__, trait.name) + if isinstance(trait, (Container, Dict)): + multiplicity = trait.metadata.get('multiplicity', 'append') + if isinstance(trait, Dict): + sample_value = '<key-1>=<value-1>' + else: + sample_value = '<%s-item-1>' % trait.__class__.__name__.lower() + if multiplicity == 'append': + header = "%s=%s..." % (header, sample_value) + else: + header = "%s %s..." % (header, sample_value) + else: + header = '%s=<%s>' % (header, trait.__class__.__name__) + #header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) lines.append(header) - - if helptext is None: - helptext = trait.help - if helptext != '': - helptext = '\n'.join(wrap_paragraphs(helptext, 76)) - lines.append(indent(helptext)) - - if 'Enum' in trait.__class__.__name__: - # include Enum choices - lines.append(indent('Choices: %s' % trait.info())) - + + if helptext is None: + helptext = trait.help + if helptext != '': + helptext = '\n'.join(wrap_paragraphs(helptext, 76)) + lines.append(indent(helptext)) + + if 'Enum' in trait.__class__.__name__: + # include Enum choices + lines.append(indent('Choices: %s' % trait.info())) + if inst is not None: - lines.append(indent("Current: %r" % (getattr(inst, trait.name),))) + lines.append(indent("Current: %r" % (getattr(inst, trait.name),))) else: try: dvr = trait.default_value_repr() @@ -290,8 +290,8 @@ class Configurable(HasTraits): 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)) + dvr = dvr[:61] + "..." + lines.append(indent("Default: %s" % dvr)) return '\n'.join(lines) @@ -301,41 +301,41 @@ class Configurable(HasTraits): print(cls.class_get_help(inst)) @classmethod - def _defining_class(cls, trait, classes): - """Get the class that defines a trait - - For reducing redundant help output in config files. - Returns the current class if: - - the trait is defined on this class, or - - the class where it is defined would not be in the config file - - Parameters - ---------- - trait : Trait - The trait to look for - classes : list - The list of other classes to consider for redundancy. - Will return `cls` even if it is not defined on `cls` - if the defining class is not in `classes`. - """ - defining_cls = cls - for parent in cls.mro(): - if issubclass(parent, Configurable) and \ - parent in classes and \ - parent.class_own_traits(config=True).get(trait.name, None) is trait: - defining_cls = parent - return defining_cls - - @classmethod - def class_config_section(cls, classes=None): - """Get the config section for this class. - - Parameters - ---------- - classes : list, optional - The list of other classes in the config file. - Used to reduce redundant information. - """ + def _defining_class(cls, trait, classes): + """Get the class that defines a trait + + For reducing redundant help output in config files. + Returns the current class if: + - the trait is defined on this class, or + - the class where it is defined would not be in the config file + + Parameters + ---------- + trait : Trait + The trait to look for + classes : list + The list of other classes to consider for redundancy. + Will return `cls` even if it is not defined on `cls` + if the defining class is not in `classes`. + """ + defining_cls = cls + for parent in cls.mro(): + if issubclass(parent, Configurable) and \ + parent in classes and \ + parent.class_own_traits(config=True).get(trait.name, None) is trait: + defining_cls = parent + return defining_cls + + @classmethod + def class_config_section(cls, classes=None): + """Get the config section for this class. + + Parameters + ---------- + classes : list, optional + 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)) @@ -343,14 +343,14 @@ class Configurable(HasTraits): return '## ' + s.replace('\n', '\n# ') # section header - breaker = '#' + '-' * 78 - parent_classes = ', '.join( - p.__name__ for p in cls.__bases__ - if issubclass(p, Configurable) - ) - + breaker = '#' + '-' * 78 + parent_classes = ', '.join( + p.__name__ for p in cls.__bases__ + if issubclass(p, Configurable) + ) + s = "# %s(%s) configuration" % (cls.__name__, parent_classes) - lines = [breaker, s, breaker] + lines = [breaker, s, breaker] # get the description trait desc = cls.class_traits().get('description') if desc: @@ -362,29 +362,29 @@ class Configurable(HasTraits): lines.append(c(desc)) lines.append('') - for name, trait in sorted(cls.class_traits(config=True).items()): - default_repr = trait.default_value_repr() - - if classes: - defining_class = cls._defining_class(trait, classes) - else: - defining_class = cls - if defining_class is cls: - # cls owns the trait, show full help - if trait.help: - lines.append(c(trait.help)) - if 'Enum' in type(trait).__name__: - # include Enum choices - lines.append('# Choices: %s' % trait.info()) - lines.append('# Default: %s' % default_repr) - else: - # Trait appears multiple times and isn't defined here. - # Truncate help to first line + "See also Original.trait" - if trait.help: - lines.append(c(trait.help.split('\n', 1)[0])) - lines.append('# See also: %s.%s' % (defining_class.__name__, name)) - - lines.append('# c.%s.%s = %s' % (cls.__name__, name, default_repr)) + for name, trait in sorted(cls.class_traits(config=True).items()): + default_repr = trait.default_value_repr() + + if classes: + defining_class = cls._defining_class(trait, classes) + else: + defining_class = cls + if defining_class is cls: + # cls owns the trait, show full help + if trait.help: + lines.append(c(trait.help)) + if 'Enum' in type(trait).__name__: + # include Enum choices + lines.append('# Choices: %s' % trait.info()) + lines.append('# Default: %s' % default_repr) + else: + # Trait appears multiple times and isn't defined here. + # Truncate help to first line + "See also Original.trait" + if trait.help: + lines.append(c(trait.help.split('\n', 1)[0])) + 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) @@ -396,7 +396,7 @@ class Configurable(HasTraits): """ lines = [] classname = cls.__name__ - for k, trait in sorted(cls.class_traits(config=True).items()): + for k, trait in sorted(cls.class_traits(config=True).items()): ttype = trait.__class__.__name__ termline = classname + '.' + trait.name @@ -404,7 +404,7 @@ class Configurable(HasTraits): # Choices or type if 'Enum' in ttype: # include Enum choices - termline += ' : ' + trait.info_rst() + termline += ' : ' + trait.info_rst() else: termline += ' : ' + ttype lines.append(termline) @@ -418,12 +418,12 @@ class Configurable(HasTraits): 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("") + dvr = dvr.replace("\\n", "\\\\n") + lines.append(indent("Default: ``%s``" % dvr)) + lines.append("") help = trait.help or 'No description' - lines.append(indent(dedent(help))) + lines.append(indent(dedent(help))) # Blank line lines.append('') @@ -439,39 +439,39 @@ class LoggingConfigurable(Configurable): is to get the logger from the currently running Application. """ - log = Any(help="Logger or LoggerAdapter instance") - - @validate("log") - def _validate_log(self, proposal): - if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)): - # warn about unsupported type, but be lenient to allow for duck typing - warnings.warn( - f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter," - f" got {proposal.value}." - ) - return proposal.value - - @default("log") + log = Any(help="Logger or LoggerAdapter instance") + + @validate("log") + def _validate_log(self, proposal): + if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)): + # warn about unsupported type, but be lenient to allow for duck typing + warnings.warn( + f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter," + f" got {proposal.value}." + ) + return proposal.value + + @default("log") def _log_default(self): - if isinstance(self.parent, LoggingConfigurable): - return self.parent.log + if isinstance(self.parent, LoggingConfigurable): + return self.parent.log 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 - if isinstance(logger, logging.LoggerAdapter): - logger = logger.logger - if not getattr(logger, "handlers", None): - # no handlers attribute or empty handlers list - return None - return logger.handlers[0] - - + def _get_log_handler(self): + """Return the default Handler + + Returns None if none can be found + """ + logger = self.log + if isinstance(logger, logging.LoggerAdapter): + logger = logger.logger + if not getattr(logger, "handlers", None): + # no handlers attribute or empty handlers list + return None + return logger.handlers[0] + + class SingletonConfigurable(LoggingConfigurable): """A configurable that only allows one instance. @@ -547,8 +547,8 @@ class SingletonConfigurable(LoggingConfigurable): return cls._instance else: raise MultipleInstanceError( - "An incompatible sibling of '%s' is already instanciated" - " as singleton: %s" % (cls.__name__, type(cls._instance).__name__) + "An incompatible sibling of '%s' is already instanciated" + " as singleton: %s" % (cls.__name__, type(cls._instance).__name__) ) @classmethod diff --git a/contrib/python/traitlets/py3/traitlets/config/loader.py b/contrib/python/traitlets/py3/traitlets/config/loader.py index edd4fa0317..5360f889ab 100644 --- a/contrib/python/traitlets/py3/traitlets/config/loader.py +++ b/contrib/python/traitlets/py3/traitlets/config/loader.py @@ -9,13 +9,13 @@ import os import re import sys import json -import warnings +import warnings -from ..utils import cast_unicode, filefind - -from traitlets.traitlets import ( - HasTraits, Container, List, Dict, Any, Undefined, -) +from ..utils import cast_unicode, filefind + +from traitlets.traitlets import ( + HasTraits, Container, List, Dict, Any, Undefined, +) #----------------------------------------------------------------------------- # Exceptions @@ -44,18 +44,18 @@ class ArgumentError(ConfigLoaderError): # 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): - return "<Sentinel deprecated>" - - def __str__(self): - return "<deprecated>" - - -_deprecated = _Sentinel() - - + +class _Sentinel: + def __repr__(self): + return "<Sentinel deprecated>" + + def __str__(self): + return "<deprecated>" + + +_deprecated = _Sentinel() + + class ArgumentParser(argparse.ArgumentParser): """Simple argparse subclass that prints help to stdout by default.""" @@ -70,102 +70,102 @@ class ArgumentParser(argparse.ArgumentParser): # Config class for holding config information #----------------------------------------------------------------------------- -def execfile(fname, glob): - with open(fname, 'rb') as f: - exec(compile(f.read(), fname, 'exec'), glob, glob) - +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 - - These methods allow appending/extending/updating - to add to non-empty defaults instead of clobbering them. - + + These methods allow appending/extending/updating + to add to non-empty defaults instead of clobbering them. + Exposes: - + - append, extend, insert on lists - update on dicts - update, add on sets """ - + _value = None - + # list methods _extend = List() _prepend = List() - _inserts = List() - + _inserts = List() + def append(self, obj): - """Append an item to a List""" + """Append an item to a List""" self._extend.append(obj) - + def extend(self, other): - """Extend a list""" + """Extend a list""" self._extend.extend(other) - + def prepend(self, other): """like list.extend, but for the front""" self._prepend[:0] = other - - - def merge_into(self, other): - """ - Merge with another earlier LazyConfigValue or an earlier container. - This is useful when having global system-wide configuration files. - - Self is expected to have higher precedence. - - Parameters - ---------- - other : LazyConfigValue or container - - Returns - ------- - LazyConfigValue - if ``other`` is also lazy, a reified container otherwise. - """ - if isinstance(other, LazyConfigValue): - other._extend.extend(self._extend) - self._extend = other._extend - - self._prepend.extend(other._prepend) - - other._inserts.extend(self._inserts) - self._inserts = other._inserts - - if self._update: - other.update(self._update) - self._update = other._update - return self - else: - # other is a container, reify now. - return self.get_value(other) - + + + def merge_into(self, other): + """ + Merge with another earlier LazyConfigValue or an earlier container. + This is useful when having global system-wide configuration files. + + Self is expected to have higher precedence. + + Parameters + ---------- + other : LazyConfigValue or container + + Returns + ------- + LazyConfigValue + if ``other`` is also lazy, a reified container otherwise. + """ + if isinstance(other, LazyConfigValue): + other._extend.extend(self._extend) + self._extend = other._extend + + self._prepend.extend(other._prepend) + + other._inserts.extend(self._inserts) + self._inserts = other._inserts + + if self._update: + other.update(self._update) + self._update = other._update + return self + else: + # 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)) - + # dict methods # update is used for both dict and set _update = Any() - + def update(self, other): - """Update either a set or dict""" + """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): - """Add an item to a set""" + """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: @@ -176,7 +176,7 @@ class LazyConfigValue(HasTraits): value.insert(idx, obj) value[:0] = self._prepend value.extend(self._extend) - + elif isinstance(value, dict): if self._update: value.update(self._update) @@ -185,10 +185,10 @@ class LazyConfigValue(HasTraits): 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 = {} @@ -202,13 +202,13 @@ class LazyConfigValue(HasTraits): 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 __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('_'): @@ -218,26 +218,26 @@ def _is_section_key(key): 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, - allowing quick assignments such as:: - - c = Config() - c.Class.int_trait = 5 - c.Class.list_trait.append("x") - - """ - + """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, + allowing quick assignments such as:: + + c = Config() + c.Class.int_trait = 5 + c.Class.list_trait.append("x") + + """ + 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. """ @@ -247,11 +247,11 @@ class Config(dict): 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 = {} @@ -262,20 +262,20 @@ class Config(dict): 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]) + 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 = {} @@ -289,7 +289,7 @@ class Config(dict): 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: @@ -297,15 +297,15 @@ class Config(dict): 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)) @@ -323,7 +323,7 @@ class Config(dict): value = copy.copy(value) new_config[key] = value return new_config - + def __getitem__(self, key): try: return dict.__getitem__(self, key) @@ -372,89 +372,89 @@ class Config(dict): raise AttributeError(e) -class DeferredConfig: - """Class for deferred-evaluation of config from CLI""" - pass - - def get_value(self, trait): - raise NotImplementedError("Implement in subclasses") - - def _super_repr(self): - # explicitly call super on direct parent - return super(self.__class__, self).__repr__() - - -class DeferredConfigString(str, DeferredConfig): - """Config value for loading config from a string - - Interpretation is deferred until it is loaded into the trait. - - Subclass of str for backward compatibility. - - This class is only used for values that are not listed - in the configurable classes. - - When config is loaded, `trait.from_string` will be used. - - If an error is raised in `.from_string`, - the original string is returned. - - .. versionadded:: 5.0 - """ - def get_value(self, trait): - """Get the value stored in this string""" - s = str(self) - try: - return trait.from_string(s) - except Exception: - # exception casting from string, - # let the original string lie. - # this will raise a more informative error when config is loaded. - return s - - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, self._super_repr()) - - -class DeferredConfigList(list, DeferredConfig): - """Config value for loading config from a list of strings - - Interpretation is deferred until it is loaded into the trait. - - This class is only used for values that are not listed - in the configurable classes. - - When config is loaded, `trait.from_string_list` will be used. - - If an error is raised in `.from_string_list`, - the original string list is returned. - - .. versionadded:: 5.0 - """ - def get_value(self, trait): - """Get the value stored in this string""" - if hasattr(trait, "from_string_list"): - src = list(self) - cast = trait.from_string_list - else: - # only allow one item - if len(self) > 1: - raise ValueError(f"{trait.name} only accepts one value, got {len(self)}: {list(self)}") - src = self[0] - cast = trait.from_string - - try: - return cast(src) - except Exception: - # exception casting from string, - # let the original value lie. - # this will raise a more informative error when config is loaded. - return src - - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, self._super_repr()) - - +class DeferredConfig: + """Class for deferred-evaluation of config from CLI""" + pass + + def get_value(self, trait): + raise NotImplementedError("Implement in subclasses") + + def _super_repr(self): + # explicitly call super on direct parent + return super(self.__class__, self).__repr__() + + +class DeferredConfigString(str, DeferredConfig): + """Config value for loading config from a string + + Interpretation is deferred until it is loaded into the trait. + + Subclass of str for backward compatibility. + + This class is only used for values that are not listed + in the configurable classes. + + When config is loaded, `trait.from_string` will be used. + + If an error is raised in `.from_string`, + the original string is returned. + + .. versionadded:: 5.0 + """ + def get_value(self, trait): + """Get the value stored in this string""" + s = str(self) + try: + return trait.from_string(s) + except Exception: + # exception casting from string, + # let the original string lie. + # this will raise a more informative error when config is loaded. + return s + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self._super_repr()) + + +class DeferredConfigList(list, DeferredConfig): + """Config value for loading config from a list of strings + + Interpretation is deferred until it is loaded into the trait. + + This class is only used for values that are not listed + in the configurable classes. + + When config is loaded, `trait.from_string_list` will be used. + + If an error is raised in `.from_string_list`, + the original string list is returned. + + .. versionadded:: 5.0 + """ + def get_value(self, trait): + """Get the value stored in this string""" + if hasattr(trait, "from_string_list"): + src = list(self) + cast = trait.from_string_list + else: + # only allow one item + if len(self) > 1: + raise ValueError(f"{trait.name} only accepts one value, got {len(self)}: {list(self)}") + src = self[0] + cast = trait.from_string + + try: + return cast(src) + except Exception: + # exception casting from string, + # let the original value lie. + # this will raise a more informative error when config is loaded. + return src + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self._super_repr()) + + #----------------------------------------------------------------------------- # Config loading classes #----------------------------------------------------------------------------- @@ -483,7 +483,7 @@ class ConfigLoader(object): """A base class for config loaders. log : instance of :class:`logging.Logger` to use. - By default logger of :meth:`traitlets.config.application.Application.instance()` + By default logger of :meth:`traitlets.config.application.Application.instance()` will be used Examples @@ -613,12 +613,12 @@ class PyFileConfigLoader(FileConfigLoader): 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() @@ -628,22 +628,22 @@ class PyFileConfigLoader(FileConfigLoader): 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) + conf_filename = self.full_filename + with open(conf_filename, 'rb') as f: + exec(compile(f.read(), conf_filename, 'exec'), namespace, namespace) class CommandLineConfigLoader(ConfigLoader): @@ -653,150 +653,150 @@ class CommandLineConfigLoader(ConfigLoader): here. """ - def _exec_config_str(self, lhs, rhs, trait=None): + def _exec_config_str(self, lhs, rhs, trait=None): """execute self.config.<lhs> = <rhs> - + * expands ~ with expanduser - * interprets value with trait if available + * interprets value with trait if available """ - value = rhs - if isinstance(value, DeferredConfig): - if trait: - # trait available, reify config immediately - value = value.get_value(trait) - elif isinstance(rhs, DeferredConfigList) and len(rhs) == 1: - # single item, make it a deferred str - value = DeferredConfigString(os.path.expanduser(rhs[0])) - else: - if trait: - 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 + value = rhs + if isinstance(value, DeferredConfig): + if trait: + # trait available, reify config immediately + value = value.get_value(trait) + elif isinstance(rhs, DeferredConfigList) and len(rhs) == 1: + # single item, make it a deferred str + value = DeferredConfigString(os.path.expanduser(rhs[0])) + else: + if trait: + 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: - for sec, c in cfg.items(): + for sec, c in cfg.items(): self.config[sec].update(c) else: raise TypeError("Invalid flag: %r" % cfg) -# match --Class.trait keys for argparse -# matches: -# --Class.trait -# --x -# -x +# match --Class.trait keys for argparse +# matches: +# --Class.trait +# --x +# -x -class_trait_opt_pattern = re.compile(r'^\-?\-[A-Za-z][\w]*(\.[\w]+)*$') +class_trait_opt_pattern = re.compile(r'^\-?\-[A-Za-z][\w]*(\.[\w]+)*$') -_DOT_REPLACEMENT = "__DOT__" -_DASH_REPLACEMENT = "__DASH__" +_DOT_REPLACEMENT = "__DOT__" +_DASH_REPLACEMENT = "__DASH__" -class _KVAction(argparse.Action): - """Custom argparse action for handling --Class.trait=x +class _KVAction(argparse.Action): + """Custom argparse action for handling --Class.trait=x - Always + Always """ - def __call__(self, parser, namespace, values, option_string=None): - if isinstance(values, str): - values = [values] - values = ["-" if v is _DASH_REPLACEMENT else v for v in values] - items = getattr(namespace, self.dest, None) - if items is None: - items = DeferredConfigList() - else: - 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): - self[key] = _KVAction( - option_strings=[key], - dest=key.lstrip("-").replace(".", _DOT_REPLACEMENT), - # 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): - # must be done immediately prior to parsing because if we do it in init, - # registration of explicit actions via parser.add_option will fail during setup - for container in (self, self._optionals): - container._option_string_actions = _DefaultOptionDict( - container._option_string_actions) - return super().parse_known_args(args, namespace) + def __call__(self, parser, namespace, values, option_string=None): + if isinstance(values, str): + values = [values] + values = ["-" if v is _DASH_REPLACEMENT else v for v in values] + items = getattr(namespace, self.dest, None) + if items is None: + items = DeferredConfigList() + else: + 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): + self[key] = _KVAction( + option_strings=[key], + dest=key.lstrip("-").replace(".", _DOT_REPLACEMENT), + # 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): + # must be done immediately prior to parsing because if we do it in init, + # registration of explicit actions via parser.add_option will fail during setup + for container in (self, self._optionals): + 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.""" - parser_class = ArgumentParser - - def __init__(self, argv=None, aliases=None, flags=None, log=None, classes=(), - *parser_args, **parser_kw): + 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 ---------- - classes : optional, list - The classes to scan for *container* config-traits and decide - for their "multiplicity" when adding them as *argparse* arguments. + 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 - 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`. - aliases : dict of str to str - Dict of aliases to full traitlests names for CLI parsing - flags : dict of str to str - Dict of flags to full traitlests names for CLI parsing - log - Passed to `ConfigLoader` + 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`. + aliases : dict of str to str + Dict of aliases to full traitlests names for CLI parsing + flags : dict of str to str + Dict of flags to full traitlests names for CLI parsing + log + Passed to `ConfigLoader` Returns ------- @@ -810,7 +810,7 @@ class ArgParseConfigLoader(CommandLineConfigLoader): self.argv = argv self.aliases = aliases or {} self.flags = flags or {} - self.classes = classes + self.classes = classes self.parser_args = parser_args self.version = parser_kw.pop("version", None) @@ -818,36 +818,36 @@ class ArgParseConfigLoader(CommandLineConfigLoader): kwargs.update(parser_kw) self.parser_kw = kwargs - def load_config(self, argv=None, aliases=None, flags=_deprecated, classes=None): + def load_config(self, argv=None, aliases=None, flags=_deprecated, classes=None): """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: - warnings.warn( - "The `flag` argument to load_config is deprecated since Traitlets " - f"5.0 and will be ignored, pass flags the `{type(self)}` constructor.", - DeprecationWarning, - stacklevel=2, - ) - + 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: + warnings.warn( + "The `flag` argument to load_config is deprecated since Traitlets " + f"5.0 and will be ignored, pass flags the `{type(self)}` constructor.", + DeprecationWarning, + stacklevel=2, + ) + 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() + 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 @@ -858,219 +858,219 @@ class ArgParseConfigLoader(CommandLineConfigLoader): 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 _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): + def _add_arguments(self, aliases, flags, classes): 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 = {} - for alias, alias_target in self.aliases.items(): - if alias in self.flags: - continue - if not isinstance(alias, tuple): - short_alias, alias = alias, None - else: - short_alias, alias = alias - for al in (short_alias, alias): - if al is None: - continue - if len(al) == 1: - unpacked_aliases["-" + al] = "--" + alias_target - unpacked_aliases["--" + al] = "--" + alias_target - - def _replace(arg): - if arg == "-": - return _DASH_REPLACEMENT - for k, v in unpacked_aliases.items(): - if arg == k: - return v - if arg.startswith(k + "="): - return v + "=" + arg[len(k) + 1:] - return arg - - if '--' in uargs: - idx = uargs.index('--') - extra_args = uargs[idx+1:] - to_parse = uargs[:idx] - else: - extra_args = [] - to_parse = uargs - to_parse = [_replace(a) for a in to_parse] - - self.parsed_data = self.parser.parse_args(to_parse) - self.extra_args = extra_args - + uargs = [cast_unicode(a) for a in args] + + unpacked_aliases = {} + if self.aliases: + unpacked_aliases = {} + for alias, alias_target in self.aliases.items(): + if alias in self.flags: + continue + if not isinstance(alias, tuple): + short_alias, alias = alias, None + else: + short_alias, alias = alias + for al in (short_alias, alias): + if al is None: + continue + if len(al) == 1: + unpacked_aliases["-" + al] = "--" + alias_target + unpacked_aliases["--" + al] = "--" + alias_target + + def _replace(arg): + if arg == "-": + return _DASH_REPLACEMENT + for k, v in unpacked_aliases.items(): + if arg == k: + return v + if arg.startswith(k + "="): + return v + "=" + arg[len(k) + 1:] + return arg + + if '--' in uargs: + idx = uargs.index('--') + extra_args = uargs[idx+1:] + to_parse = uargs[:idx] + else: + extra_args = [] + to_parse = uargs + to_parse = [_replace(a) for a in to_parse] + + self.parsed_data = self.parser.parse_args(to_parse) + self.extra_args = extra_args + 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""" - def __init__(self, *args, **kwargs): - self.flag = kwargs.pop('flag') - self.alias = kwargs.pop('alias', None) - kwargs['const'] = Undefined - if not self.alias: - kwargs['nargs'] = 0 - super(_FlagAction, self).__init__(*args, **kwargs) - - def __call__(self, parser, namespace, values, option_string=None): - if self.nargs == 0 or values is Undefined: + *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""" + def __init__(self, *args, **kwargs): + self.flag = kwargs.pop('flag') + self.alias = kwargs.pop('alias', None) + kwargs['const'] = Undefined + if not self.alias: + kwargs['nargs'] = 0 + super(_FlagAction, self).__init__(*args, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + if self.nargs == 0 or values is Undefined: if not hasattr(namespace, '_flags'): namespace._flags = [] - namespace._flags.append(self.flag) - else: - setattr(namespace, self.alias, values) - - + namespace._flags.append(self.flag) + else: + setattr(namespace, self.alias, values) + + class KVArgParseConfigLoader(ArgParseConfigLoader): """A config loader that loads aliases and flags with argparse, - as well as arbitrary --Class.trait value - """ - - parser_class = _KVArgParser - - def _add_arguments(self, aliases, flags, classes): - alias_flags = {} + as well as arbitrary --Class.trait value + """ + + parser_class = _KVArgParser + + def _add_arguments(self, aliases, flags, classes): + alias_flags = {} 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>) } - # - # Used to add the correct type into the `config` tree. - # Used also for aliases, not to re-collect them. - self.argparse_traits = argparse_traits = {} - for cls in classes: - for traitname, trait in cls.class_traits(config=True).items(): - argname = '%s.%s' % (cls.__name__, traitname) - argparse_kwds = {'type': str} - if isinstance(trait, (Container, Dict)): - multiplicity = trait.metadata.get('multiplicity', 'append') - if multiplicity == 'append': - argparse_kwds['action'] = multiplicity - else: - argparse_kwds['nargs'] = multiplicity - argparse_traits[argname] = (trait, argparse_kwds) - - for keys, (value, _) in flags.items(): - if not isinstance(keys, tuple): - keys = (keys,) - for key in keys: - if key in aliases: - alias_flags[aliases[key]] = value - continue - keys = ('-' + key, '--' + key) if len(key) == 1 else ('--' + key,) - paa(*keys, action=_FlagAction, flag=value) - - for keys, traitname in aliases.items(): - if not isinstance(keys, tuple): - keys = (keys,) - - for key in keys: - argparse_kwds = { - 'type': str, - 'dest': traitname.replace(".", _DOT_REPLACEMENT), - 'metavar': traitname, - } - if traitname in argparse_traits: - argparse_kwds.update(argparse_traits[traitname][1]) - if 'action' in argparse_kwds and traitname in alias_flags: - # flag sets 'action', so can't have flag & alias with custom action - # on the same name - raise ArgumentError( - "The alias `%s` for the 'append' sequence " - "config-trait `%s` cannot be also a flag!'" - % (key, traitname)) - if traitname in alias_flags: - # alias and flag. - # when called with 0 args: flag - # when called with >= 1: alias - argparse_kwds.setdefault('nargs', '?') - argparse_kwds['action'] = _FlagAction - argparse_kwds['flag'] = alias_flags[traitname] - argparse_kwds['alias'] = traitname - keys = ('-' + key, '--' + key) if len(key) == 1 else ('--'+ key,) - paa(*keys, **argparse_kwds) - + self.parser.set_defaults(_flags=[]) + paa("extra_args", nargs="*") + + ## An index of all container traits collected:: + # + # { <traitname>: (<trait>, <argparse-kwds>) } + # + # Used to add the correct type into the `config` tree. + # Used also for aliases, not to re-collect them. + self.argparse_traits = argparse_traits = {} + for cls in classes: + for traitname, trait in cls.class_traits(config=True).items(): + argname = '%s.%s' % (cls.__name__, traitname) + argparse_kwds = {'type': str} + if isinstance(trait, (Container, Dict)): + multiplicity = trait.metadata.get('multiplicity', 'append') + if multiplicity == 'append': + argparse_kwds['action'] = multiplicity + else: + argparse_kwds['nargs'] = multiplicity + argparse_traits[argname] = (trait, argparse_kwds) + + for keys, (value, _) in flags.items(): + if not isinstance(keys, tuple): + keys = (keys,) + for key in keys: + if key in aliases: + alias_flags[aliases[key]] = value + continue + keys = ('-' + key, '--' + key) if len(key) == 1 else ('--' + key,) + paa(*keys, action=_FlagAction, flag=value) + + for keys, traitname in aliases.items(): + if not isinstance(keys, tuple): + keys = (keys,) + + for key in keys: + argparse_kwds = { + 'type': str, + 'dest': traitname.replace(".", _DOT_REPLACEMENT), + 'metavar': traitname, + } + if traitname in argparse_traits: + argparse_kwds.update(argparse_traits[traitname][1]) + if 'action' in argparse_kwds and traitname in alias_flags: + # flag sets 'action', so can't have flag & alias with custom action + # on the same name + raise ArgumentError( + "The alias `%s` for the 'append' sequence " + "config-trait `%s` cannot be also a flag!'" + % (key, traitname)) + if traitname in alias_flags: + # alias and flag. + # when called with 0 args: flag + # when called with >= 1: alias + argparse_kwds.setdefault('nargs', '?') + argparse_kwds['action'] = _FlagAction + argparse_kwds['flag'] = alias_flags[traitname] + argparse_kwds['alias'] = traitname + 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.""" - 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 - continue - 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 - self.log.warning("Unrecognized alias: '%s', it will have no effect.", lhs) - trait = None - - if isinstance(rhs, list): - rhs = DeferredConfigList(rhs) - elif isinstance(rhs, str): - rhs = DeferredConfigString(rhs) - - trait = self.argparse_traits.get(lhs) - if trait: - trait = trait[0] - - # eval the KV assignment - try: - self._exec_config_str(lhs, rhs, trait) - except Exception as e: - # cast deferred to nicer repr for the error - # DeferredList->list, etc - if isinstance(rhs, DeferredConfig): - rhs = rhs._super_repr() - raise ArgumentError(f"Error loading argument {lhs}={rhs}, {e}") - - for subc in self.parsed_data._flags: + 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 + continue + 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 + self.log.warning("Unrecognized alias: '%s', it will have no effect.", lhs) + trait = None + + if isinstance(rhs, list): + rhs = DeferredConfigList(rhs) + elif isinstance(rhs, str): + rhs = DeferredConfigString(rhs) + + trait = self.argparse_traits.get(lhs) + if trait: + trait = trait[0] + + # eval the KV assignment + try: + self._exec_config_str(lhs, rhs, trait) + except Exception as e: + # cast deferred to nicer repr for the error + # DeferredList->list, etc + if isinstance(rhs, DeferredConfig): + rhs = rhs._super_repr() + raise ArgumentError(f"Error loading argument {lhs}={rhs}, {e}") + + for subc in self.parsed_data._flags: self._load_flag(subc) -class KeyValueConfigLoader(KVArgParseConfigLoader): - """Deprecated in traitlets 5.0 - - Use KVArgParseConfigLoader - """ - def __init__(self, *args, **kwargs): - warnings.warn( - "KeyValueConfigLoader is deprecated since Traitlets 5.0." - " Use KVArgParseConfigLoader instead.", - DeprecationWarning, - stacklevel=2, - ) - super().__init__(*args, **kwargs) - - +class KeyValueConfigLoader(KVArgParseConfigLoader): + """Deprecated in traitlets 5.0 + + Use KVArgParseConfigLoader + """ + def __init__(self, *args, **kwargs): + warnings.warn( + "KeyValueConfigLoader is deprecated since Traitlets 5.0." + " Use KVArgParseConfigLoader instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) + + 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 diff --git a/contrib/python/traitlets/py3/traitlets/config/manager.py b/contrib/python/traitlets/py3/traitlets/config/manager.py index d458257c9c..164053261e 100644 --- a/contrib/python/traitlets/py3/traitlets/config/manager.py +++ b/contrib/python/traitlets/py3/traitlets/config/manager.py @@ -34,7 +34,7 @@ def recursive_update(target, new): class BaseJSONConfigManager(LoggingConfigurable): """General JSON config manager - + Deals with persisting/storing config in a json file """ @@ -69,7 +69,7 @@ class BaseJSONConfigManager(LoggingConfigurable): filename = self.file_name(section_name) self.ensure_config_dir_exists() - f = open(filename, 'w', encoding='utf-8') + f = open(filename, 'w', encoding='utf-8') with f: json.dump(data, f, indent=2) diff --git a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py index 3f2b53c634..ce22e4a674 100644 --- a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py +++ b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py @@ -1,161 +1,161 @@ -"""Machinery for documenting traitlets config options with Sphinx. - -This includes: - -- A Sphinx extension defining directives and roles for config options. -- A function to generate an rst file given an Application instance. - -To make this documentation, first set this module as an extension in Sphinx's -conf.py:: - - extensions = [ - # ... - 'traitlets.config.sphinxdoc', - ] - -Autogenerate the config documentation by running code like this before -Sphinx builds:: - - from traitlets.config.sphinxdoc import write_doc - from myapp import MyApplication - - writedoc('config/options.rst', # File to write - 'MyApp config options', # Title - MyApplication() - ) - -The generated rST syntax looks like this:: - - .. configtrait:: Application.log_datefmt - - Description goes here. - - Cross reference like this: :configtrait:`Application.log_datefmt`. -""" -from traitlets import Undefined -from traitlets.utils.text import indent -from collections import defaultdict - -from textwrap import indent as _indent, dedent - - -def setup(app): - """Registers the Sphinx extension. - - You shouldn't need to call this directly; configure Sphinx to use this - module instead. - """ - app.add_object_type('configtrait', 'configtrait', objname='Config option') - metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} - return metadata - -def interesting_default_value(dv): - if (dv is None) or (dv is Undefined): - return False - if isinstance(dv, (str, list, tuple, dict, set)): - return bool(dv) - return True - -def format_aliases(aliases): - fmted = [] - for a in aliases: - dashes = '-' if len(a) == 1 else '--' - fmted.append('``%s%s``' % (dashes, a)) - return ', '.join(fmted) - -def class_config_rst_doc(cls, trait_aliases): - """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__ - - fullname = classname + '.' + trait.name - lines += ['.. configtrait:: ' + fullname, - '' - ] - - help = trait.help.rstrip() or "No description" - lines.append(indent(dedent(help)) + "\n") - - # Choices or type - if 'Enum' in ttype: - # include Enum choices - lines.append( - indent(":options: " + ", ".join("``%r``" % x for x in trait.values)) - ) - else: - lines.append(indent(":trait type: " + ttype)) - - # Default value - # Ignore boring default values like None, [] or '' - if interesting_default_value(trait.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)) - - # Command line aliases - if trait_aliases[fullname]: - fmt_aliases = format_aliases(trait_aliases[fullname]) - lines.append(indent(":CLI option: " + fmt_aliases)) - - # Blank line - lines.append('') - - return '\n'.join(lines) - -def reverse_aliases(app): - """Produce a mapping of trait names to lists of command line aliases. - """ - res = defaultdict(list) - for alias, trait in app.aliases.items(): - res[trait].append(alias) - - # Flags also often act as aliases for a boolean trait. - # Treat flags which set one trait to True as aliases. - for flag, (cfg, _) in app.flags.items(): - if len(cfg) == 1: - classname = list(cfg)[0] - cls_cfg = cfg[classname] - if len(cls_cfg) == 1: - traitname = list(cls_cfg)[0] - if cls_cfg[traitname] is True: - res[classname+'.'+traitname].append(flag) - - return res - -def write_doc(path, title, app, preamble=None): - """Write a rst file documenting config options for a traitlets application. - - Parameters - ---------- - path : str - The file to be written - title : str - The human-readable title of the document - app : traitlets.config.Application - An instance of the application class to be documented - preamble : str - Extra text to add just after the title (optional) - """ - trait_aliases = reverse_aliases(app) - with open(path, 'w') as f: - f.write(title + '\n') - f.write(('=' * len(title)) + '\n') - f.write('\n') - if preamble is not None: - f.write(preamble + '\n\n') - - for c in app._classes_inc_parents(): - f.write(class_config_rst_doc(c, trait_aliases)) - f.write('\n') +"""Machinery for documenting traitlets config options with Sphinx. + +This includes: + +- A Sphinx extension defining directives and roles for config options. +- A function to generate an rst file given an Application instance. + +To make this documentation, first set this module as an extension in Sphinx's +conf.py:: + + extensions = [ + # ... + 'traitlets.config.sphinxdoc', + ] + +Autogenerate the config documentation by running code like this before +Sphinx builds:: + + from traitlets.config.sphinxdoc import write_doc + from myapp import MyApplication + + writedoc('config/options.rst', # File to write + 'MyApp config options', # Title + MyApplication() + ) + +The generated rST syntax looks like this:: + + .. configtrait:: Application.log_datefmt + + Description goes here. + + Cross reference like this: :configtrait:`Application.log_datefmt`. +""" +from traitlets import Undefined +from traitlets.utils.text import indent +from collections import defaultdict + +from textwrap import indent as _indent, dedent + + +def setup(app): + """Registers the Sphinx extension. + + You shouldn't need to call this directly; configure Sphinx to use this + module instead. + """ + app.add_object_type('configtrait', 'configtrait', objname='Config option') + metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} + return metadata + +def interesting_default_value(dv): + if (dv is None) or (dv is Undefined): + return False + if isinstance(dv, (str, list, tuple, dict, set)): + return bool(dv) + return True + +def format_aliases(aliases): + fmted = [] + for a in aliases: + dashes = '-' if len(a) == 1 else '--' + fmted.append('``%s%s``' % (dashes, a)) + return ', '.join(fmted) + +def class_config_rst_doc(cls, trait_aliases): + """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__ + + fullname = classname + '.' + trait.name + lines += ['.. configtrait:: ' + fullname, + '' + ] + + help = trait.help.rstrip() or "No description" + lines.append(indent(dedent(help)) + "\n") + + # Choices or type + if 'Enum' in ttype: + # include Enum choices + lines.append( + indent(":options: " + ", ".join("``%r``" % x for x in trait.values)) + ) + else: + lines.append(indent(":trait type: " + ttype)) + + # Default value + # Ignore boring default values like None, [] or '' + if interesting_default_value(trait.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)) + + # Command line aliases + if trait_aliases[fullname]: + fmt_aliases = format_aliases(trait_aliases[fullname]) + lines.append(indent(":CLI option: " + fmt_aliases)) + + # Blank line + lines.append('') + + return '\n'.join(lines) + +def reverse_aliases(app): + """Produce a mapping of trait names to lists of command line aliases. + """ + res = defaultdict(list) + for alias, trait in app.aliases.items(): + res[trait].append(alias) + + # Flags also often act as aliases for a boolean trait. + # Treat flags which set one trait to True as aliases. + for flag, (cfg, _) in app.flags.items(): + if len(cfg) == 1: + classname = list(cfg)[0] + cls_cfg = cfg[classname] + if len(cls_cfg) == 1: + traitname = list(cls_cfg)[0] + if cls_cfg[traitname] is True: + res[classname+'.'+traitname].append(flag) + + return res + +def write_doc(path, title, app, preamble=None): + """Write a rst file documenting config options for a traitlets application. + + Parameters + ---------- + path : str + The file to be written + title : str + The human-readable title of the document + app : traitlets.config.Application + An instance of the application class to be documented + preamble : str + Extra text to add just after the title (optional) + """ + trait_aliases = reverse_aliases(app) + with open(path, 'w') as f: + f.write(title + '\n') + f.write(('=' * len(title)) + '\n') + f.write('\n') + if preamble is not None: + f.write(preamble + '\n\n') + + for c in app._classes_inc_parents(): + f.write(class_config_rst_doc(c, trait_aliases)) + f.write('\n') diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py b/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py index 8ad4073d12..b3188bd7d1 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py +++ b/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py @@ -1,769 +1,769 @@ -# coding: utf-8 -""" -Tests for traitlets.config.application.Application -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import contextlib -import io -import json -import logging -import os -import sys -from io import StringIO -from tempfile import TemporaryDirectory -from unittest import TestCase - -import pytest -from pytest import mark - -from traitlets import ( - Bool, - Bytes, - Dict, - HasTraits, - Integer, - List, - Set, - Tuple, - Unicode, -) -from traitlets.config.application import Application -from traitlets.config.configurable import Configurable -from traitlets.config.loader import Config -from traitlets.tests.utils import ( - check_help_all_output, - check_help_output, - get_output_error_code, -) - -try: - from unittest import mock -except ImportError: - import mock - -pjoin = os.path.join - - -class Foo(Configurable): - - i = Integer(0, help=""" - The integer i. - - Details about i. - """).tag(config=True) - j = Integer(1, help="The integer j.").tag(config=True) - name = Unicode('Brian', help="First name.").tag(config=True) - la = List([]).tag(config=True) - li = List(Integer()).tag(config=True) - fdict = Dict().tag(config=True, multiplicity='+') - - -class Bar(Configurable): - - b = Integer(0, help="The integer b.").tag(config=True) - enabled = Bool(True, help="Enable bar.").tag(config=True) - tb = Tuple(()).tag(config=True, multiplicity='*') - aset = Set().tag(config=True, multiplicity='+') - bdict = Dict().tag(config=True) - idict = Dict(value_trait=Integer()).tag(config=True) - key_dict = Dict(per_key_traits={'i': Integer(), 'b': Bytes()}).tag(config=True) - - -class MyApp(Application): - - name = Unicode('myapp') - running = Bool(False, help="Is the app running?").tag(config=True) - classes = List([Bar, Foo]) - config_file = Unicode('', help="Load this config file").tag(config=True) - - warn_tpyo = Unicode("yes the name is wrong on purpose", config=True, - help="Should print a warning if `MyApp.warn-typo=...` command is passed") - - aliases = {} - aliases.update(Application.aliases) - aliases.update({ - ('fooi', 'i') : 'Foo.i', - ('j', 'fooj') : ('Foo.j', "`j` terse help msg"), - 'name' : 'Foo.name', - 'la': 'Foo.la', - 'li': 'Foo.li', - 'tb': 'Bar.tb', - 'D': 'Bar.bdict', - 'enabled' : 'Bar.enabled', - 'enable' : 'Bar.enabled', - 'log-level' : 'Application.log_level', - }) - - flags = {} - flags.update(Application.flags) - flags.update({('enable', 'e'): - ({'Bar': {'enabled' : True}}, - "Set Bar.enabled to True"), - ('d', 'disable'): - ({'Bar': {'enabled' : False}}, - "Set Bar.enabled to False"), - 'crit': - ({'Application' : {'log_level' : logging.CRITICAL}}, - "set level=CRITICAL"), - }) - - def init_foo(self): - self.foo = Foo(parent=self) - - def init_bar(self): - self.bar = Bar(parent=self) - - -def class_to_names(classes): - return [klass.__name__ for klass in classes] - - -class TestApplication(TestCase): - def test_log(self): - stream = StringIO() - app = MyApp(log_level=logging.INFO) - handler = logging.StreamHandler(stream) - # trigger reconstruction of the log formatter - app.log.handlers = [handler] - app.log_format = "%(message)s" - app.log_datefmt = "%Y-%m-%d %H:%M" - app.log.info("hello") - assert "hello" in stream.getvalue() - - def test_no_eval_cli_text(self): - app = MyApp() - app.initialize(['--Foo.name=1']) - app.init_foo() - assert app.foo.name == '1' - - def test_basic(self): - app = MyApp() - self.assertEqual(app.name, 'myapp') - self.assertEqual(app.running, False) - self.assertEqual(app.classes, [MyApp, Bar, Foo]) - self.assertEqual(app.config_file, '') - - def test_mro_discovery(self): - app = MyApp() - - self.assertSequenceEqual(class_to_names(app._classes_with_config_traits()), - ['Application', 'MyApp', 'Bar', 'Foo']) - self.assertSequenceEqual(class_to_names(app._classes_inc_parents()), - ['Configurable', 'LoggingConfigurable', 'SingletonConfigurable', - 'Application', 'MyApp', 'Bar', 'Foo']) - - self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Application])), - ['Application']) - self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Application])), - ['Configurable', 'LoggingConfigurable', 'SingletonConfigurable', - 'Application']) - - self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])), - ['Foo']) - self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Bar])), - ['Configurable', 'Bar']) - - class MyApp2(Application): # no defined `classes` attr - pass - - self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])), - ['Foo']) - self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Bar])), - ['Configurable', 'Bar']) - - - def test_config(self): - app = MyApp() - app.parse_command_line([ - "--i=10", - "--Foo.j=10", - "--enable=False", - "--log-level=50", - ]) - config = app.config - print(config) - self.assertEqual(config.Foo.i, 10) - self.assertEqual(config.Foo.j, 10) - self.assertEqual(config.Bar.enabled, False) - self.assertEqual(config.MyApp.log_level, 50) - - def test_config_seq_args(self): - app = MyApp() - app.parse_command_line("--li 1 --li 3 --la 1 --tb AB 2 --Foo.la=ab --Bar.aset S1 --Bar.aset S2 --Bar.aset S1".split()) - assert app.extra_args == ["2"] - config = app.config - assert config.Foo.li == [1, 3] - assert config.Foo.la == ["1", "ab"] - assert config.Bar.tb == ("AB",) - self.assertEqual(config.Bar.aset, {"S1", "S2"}) - app.init_foo() - assert app.foo.li == [1, 3] - assert app.foo.la == ['1', 'ab'] - app.init_bar() - self.assertEqual(app.bar.aset, {'S1', 'S2'}) - assert app.bar.tb == ('AB',) - - def test_config_dict_args(self): - app = MyApp() - app.parse_command_line( - "--Foo.fdict a=1 --Foo.fdict b=b --Foo.fdict c=3 " - "--Bar.bdict k=1 -D=a=b -D 22=33 " - "--Bar.idict k=1 --Bar.idict b=2 --Bar.idict c=3 " - .split()) - fdict = {'a': '1', 'b': 'b', 'c': '3'} - bdict = {'k': '1', 'a': 'b', '22': '33'} - idict = {'k': 1, 'b': 2, 'c': 3} - config = app.config - assert config.Bar.idict == idict - self.assertDictEqual(config.Foo.fdict, fdict) - self.assertDictEqual(config.Bar.bdict, bdict) - app.init_foo() - self.assertEqual(app.foo.fdict, fdict) - app.init_bar() - assert app.bar.idict == idict - self.assertEqual(app.bar.bdict, bdict) - - def test_config_propagation(self): - app = MyApp() - app.parse_command_line(["--i=10","--Foo.j=10","--enable=False","--log-level=50"]) - app.init_foo() - app.init_bar() - self.assertEqual(app.foo.i, 10) - self.assertEqual(app.foo.j, 10) - self.assertEqual(app.bar.enabled, False) - - def test_cli_priority(self): - """Test that loading config files does not override CLI options""" - name = 'config.py' - class TestApp(Application): - value = Unicode().tag(config=True) - config_file_loaded = Bool().tag(config=True) - aliases = {'v': 'TestApp.value'} - app = TestApp() - with TemporaryDirectory() as td: - config_file = pjoin(td, name) - with open(config_file, 'w') as f: - f.writelines([ - "c.TestApp.value = 'config file'\n", - "c.TestApp.config_file_loaded = True\n" - ]) - - app.parse_command_line(['--v=cli']) - assert 'value' in app.config.TestApp - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - - app.load_config_file(name, path=[td]) - assert app.config_file_loaded - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - - def test_ipython_cli_priority(self): - # this test is almost entirely redundant with above, - # but we can keep it around in case of subtle issues creeping into - # the exact sequence IPython follows. - name = 'config.py' - class TestApp(Application): - value = Unicode().tag(config=True) - config_file_loaded = Bool().tag(config=True) - aliases = {'v': ('TestApp.value', 'some help')} - app = TestApp() - with TemporaryDirectory() as td: - config_file = pjoin(td, name) - with open(config_file, 'w') as f: - f.writelines([ - "c.TestApp.value = 'config file'\n", - "c.TestApp.config_file_loaded = True\n" - ]) - # follow IPython's config-loading sequence to ensure CLI priority is preserved - app.parse_command_line(['--v=cli']) - # this is where IPython makes a mistake: - # it assumes app.config will not be modified, - # and storing a reference is storing a copy - cli_config = app.config - assert 'value' in app.config.TestApp - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - app.load_config_file(name, path=[td]) - assert app.config_file_loaded - # enforce cl-opts override config file opts: - # this is where IPython makes a mistake: it assumes - # that cl_config is a different object, but it isn't. - app.update_config(cli_config) - assert app.config.TestApp.value == 'cli' - assert app.value == 'cli' - - def test_cli_allow_none(self): - class App(Application): - aliases = {"opt": "App.opt"} - opt = Unicode(allow_none=True, config=True) - - app = App() - app.parse_command_line(["--opt=None"]) - assert app.opt is None - - def test_flags(self): - app = MyApp() - app.parse_command_line(["--disable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - - app = MyApp() - app.parse_command_line(["-d"]) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - - app = MyApp() - app.parse_command_line(["--enable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - - app = MyApp() - app.parse_command_line(["-e"]) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - - def test_flags_help_msg(self): - app = MyApp() - stdout = io.StringIO() - with contextlib.redirect_stdout(stdout): - app.print_flag_help() - hmsg = stdout.getvalue() - self.assertRegex(hmsg, "(?<!-)-e, --enable\\b") - self.assertRegex(hmsg, "(?<!-)-d, --disable\\b") - self.assertIn("Equivalent to: [--Bar.enabled=True]", hmsg) - self.assertIn("Equivalent to: [--Bar.enabled=False]", hmsg) - - def test_aliases(self): - app = MyApp() - app.parse_command_line(["--i=5", "--j=10"]) - app.init_foo() - self.assertEqual(app.foo.i, 5) - app.init_foo() - self.assertEqual(app.foo.j, 10) - - app = MyApp() - app.parse_command_line(["-i=5", "-j=10"]) - app.init_foo() - self.assertEqual(app.foo.i, 5) - app.init_foo() - self.assertEqual(app.foo.j, 10) - - app = MyApp() - app.parse_command_line(["--fooi=5", "--fooj=10"]) - app.init_foo() - self.assertEqual(app.foo.i, 5) - app.init_foo() - self.assertEqual(app.foo.j, 10) - - def test_aliases_help_msg(self): - app = MyApp() - stdout = io.StringIO() - with contextlib.redirect_stdout(stdout): - app.print_alias_help() - hmsg = stdout.getvalue() - self.assertRegex(hmsg, "(?<!-)-i, --fooi\\b") - self.assertRegex(hmsg, "(?<!-)-j, --fooj\\b") - self.assertIn("Equivalent to: [--Foo.i]", hmsg) - self.assertIn("Equivalent to: [--Foo.j]", hmsg) - self.assertIn("Equivalent to: [--Foo.name]", hmsg) - - def test_flag_clobber(self): - """test that setting flags doesn't clobber existing settings""" - app = MyApp() - app.parse_command_line(["--Bar.b=5", "--disable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - self.assertEqual(app.bar.b, 5) - app.parse_command_line(["--enable", "--Bar.b=10"]) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - self.assertEqual(app.bar.b, 10) - - def test_warn_autocorrect(self): - stream = StringIO() - app = MyApp(log_level=logging.INFO) - app.log.handlers = [logging.StreamHandler(stream)] - - cfg = Config() - cfg.MyApp.warn_typo = "WOOOO" - app.config = cfg - - self.assertIn("warn_typo", stream.getvalue()) - self.assertIn("warn_tpyo", stream.getvalue()) - - - def test_flatten_flags(self): - cfg = Config() - cfg.MyApp.log_level = logging.WARN - app = MyApp() - app.update_config(cfg) - self.assertEqual(app.log_level, logging.WARN) - self.assertEqual(app.config.MyApp.log_level, logging.WARN) - app.initialize(["--crit"]) - self.assertEqual(app.log_level, logging.CRITICAL) - # this would be app.config.Application.log_level if it failed: - self.assertEqual(app.config.MyApp.log_level, logging.CRITICAL) - - def test_flatten_aliases(self): - cfg = Config() - cfg.MyApp.log_level = logging.WARN - app = MyApp() - app.update_config(cfg) - self.assertEqual(app.log_level, logging.WARN) - self.assertEqual(app.config.MyApp.log_level, logging.WARN) - app.initialize(["--log-level", "CRITICAL"]) - self.assertEqual(app.log_level, logging.CRITICAL) - # this would be app.config.Application.log_level if it failed: - self.assertEqual(app.config.MyApp.log_level, "CRITICAL") - - def test_extra_args(self): - - app = MyApp() - app.parse_command_line(["--Bar.b=5", 'extra', 'args', "--disable"]) - app.init_bar() - self.assertEqual(app.bar.enabled, False) - self.assertEqual(app.bar.b, 5) - self.assertEqual(app.extra_args, ['extra', 'args']) - - app = MyApp() - app.parse_command_line(["--Bar.b=5", '--', 'extra', "--disable", 'args']) - app.init_bar() - self.assertEqual(app.bar.enabled, True) - self.assertEqual(app.bar.b, 5) - self.assertEqual(app.extra_args, ['extra', '--disable', 'args']) - - app = MyApp() - app.parse_command_line( - ["--disable", "--la", "-", "-", "--Bar.b=1", "--", "-", "extra"] - ) - self.assertEqual(app.extra_args, ["-", "-", "extra"]) - - def test_unicode_argv(self): - app = MyApp() - app.parse_command_line(['ünîcødé']) - - def test_document_config_option(self): - app = MyApp() - app.document_config_options() - - def test_generate_config_file(self): - app = MyApp() - assert 'The integer b.' in app.generate_config_file() - - def test_generate_config_file_classes_to_include(self): - class NotInConfig(HasTraits): - from_hidden = Unicode('x', help="""From hidden class - - Details about from_hidden. - """).tag(config=True) - - class NoTraits(Foo, Bar, NotInConfig): - pass - - app = MyApp() - app.classes.append(NoTraits) - - conf_txt = app.generate_config_file() - print(conf_txt) - self.assertIn('The integer b.', conf_txt) - self.assertIn('# Foo(Configurable)', conf_txt) - self.assertNotIn('# Configurable', conf_txt) - self.assertIn('# NoTraits(Foo, Bar)', conf_txt) - - # inherited traits, parent in class list: - self.assertIn('# c.NoTraits.i', conf_txt) - self.assertIn('# c.NoTraits.j', conf_txt) - self.assertIn('# c.NoTraits.n', conf_txt) - self.assertIn('# See also: Foo.j', conf_txt) - self.assertIn('# See also: Bar.b', conf_txt) - self.assertEqual(conf_txt.count('Details about i.'), 1) - - # inherited traits, parent not in class list: - self.assertIn("# c.NoTraits.from_hidden", conf_txt) - self.assertNotIn('# See also: NotInConfig.', conf_txt) - self.assertEqual(conf_txt.count('Details about from_hidden.'), 1) - self.assertNotIn("NotInConfig", conf_txt) - - def test_multi_file(self): - app = MyApp() - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory('_1') as td1: - with open(pjoin(td1, name), 'w') as f1: - f1.write("get_config().MyApp.Bar.b = 1") - with TemporaryDirectory('_2') as td2: - with open(pjoin(td2, name), 'w') as f2: - f2.write("get_config().MyApp.Bar.b = 2") - app.load_config_file(name, path=[td2, td1]) - app.init_bar() - self.assertEqual(app.bar.b, 2) - app.load_config_file(name, path=[td1, td2]) - app.init_bar() - self.assertEqual(app.bar.b, 1) - - @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') - def test_log_collisions(self): - app = MyApp() - app.log = logging.getLogger() - app.log.setLevel(logging.INFO) - name = 'config' - with TemporaryDirectory('_1') as td: - with open(pjoin(td, name + '.py'), 'w') as f: - f.write("get_config().Bar.b = 1") - with open(pjoin(td, name + '.json'), 'w') as f: - json.dump({ - 'Bar': { - 'b': 2 - } - }, f) - with self.assertLogs(app.log, logging.WARNING) as captured: - app.load_config_file(name, path=[td]) - app.init_bar() - assert app.bar.b == 2 - output = '\n'.join(captured.output) - assert 'Collision' in output - assert '1 ignored, using 2' in output - assert pjoin(td, name + '.py') in output - assert pjoin(td, name + '.json') in output - - @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') - def test_log_bad_config(self): - app = MyApp() - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory() as td: - with open(pjoin(td, name), 'w') as f: - f.write("syntax error()") - with self.assertLogs(app.log, logging.ERROR) as captured: - app.load_config_file(name, path=[td]) - output = '\n'.join(captured.output) - self.assertIn('SyntaxError', output) - - def test_raise_on_bad_config(self): - app = MyApp() - app.raise_config_file_errors = True - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory() as td: - with open(pjoin(td, name), 'w') as f: - f.write("syntax error()") - with self.assertRaises(SyntaxError): - app.load_config_file(name, path=[td]) - - def test_subcommands_instanciation(self): - """Try all ways to specify how to create sub-apps.""" - app = Root.instance() - app.parse_command_line(['sub1']) - - self.assertIsInstance(app.subapp, Sub1) - ## Check parent hierarchy. - self.assertIs(app.subapp.parent, app) - - Root.clear_instance() - Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails. - app = Root.instance() - - app.parse_command_line(['sub1', 'sub2']) - self.assertIsInstance(app.subapp, Sub1) - self.assertIsInstance(app.subapp.subapp, Sub2) - ## Check parent hierarchy. - self.assertIs(app.subapp.parent, app) - self.assertIs(app.subapp.subapp.parent, app.subapp) - - Root.clear_instance() - Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails. - app = Root.instance() - - app.parse_command_line(['sub1', 'sub3']) - self.assertIsInstance(app.subapp, Sub1) - self.assertIsInstance(app.subapp.subapp, Sub3) - self.assertTrue(app.subapp.subapp.flag) # Set by factory. - ## Check parent hierarchy. - self.assertIs(app.subapp.parent, app) - self.assertIs(app.subapp.subapp.parent, app.subapp) # Set by factory. - - def test_loaded_config_files(self): - app = MyApp() - app.log = logging.getLogger() - name = 'config.py' - with TemporaryDirectory('_1') as td1: - config_file = pjoin(td1, name) - with open(config_file, 'w') as f: - f.writelines([ - "c.MyApp.running = True\n" - ]) - - app.load_config_file(name, path=[td1]) - self.assertEqual(len(app.loaded_config_files), 1) - self.assertEqual(app.loaded_config_files[0], config_file) - - app.start() - self.assertEqual(app.running, True) - - # emulate an app that allows dynamic updates and update config file - with open(config_file, 'w') as f: - f.writelines([ - "c.MyApp.running = False\n" - ]) - - # reload and verify update, and that loaded_configs was not increased - app.load_config_file(name, path=[td1]) - self.assertEqual(len(app.loaded_config_files), 1) - self.assertEqual(app.running, False) - - # Attempt to update, ensure error... - with self.assertRaises(AttributeError): - app.loaded_config_files = "/foo" - - # ensure it can't be udpated via append - app.loaded_config_files.append("/bar") - self.assertEqual(len(app.loaded_config_files), 1) - - # repeat to ensure no unexpected changes occurred - app.load_config_file(name, path=[td1]) - self.assertEqual(len(app.loaded_config_files), 1) - self.assertEqual(app.running, False) - - - -@mark.skip -def test_cli_multi_scalar(caplog): - class App(Application): - aliases = {"opt": "App.opt"} - opt = Unicode(config=True) - - app = App(log=logging.getLogger()) - with pytest.raises(SystemExit): - app.parse_command_line(["--opt", "1", "--opt", "2"]) - record = caplog.get_records("call")[-1] - message = record.message - - assert "Error loading argument" in message - assert "App.opt=['1', '2']" in message - assert "opt only accepts one value" in message - assert record.levelno == logging.CRITICAL - - -class Root(Application): - subcommands = { - 'sub1': ('__tests__.config.tests.test_application.Sub1', 'import string'), - } - - -class Sub3(Application): - flag = Bool(False) - - -class Sub2(Application): - pass - - -class Sub1(Application): - subcommands = { - 'sub2': (Sub2, 'Application class'), - 'sub3': (lambda root: Sub3(parent=root, flag=True), 'factory'), - } - - -class DeprecatedApp(Application): - override_called = False - parent_called = False - def _config_changed(self, name, old, new): - self.override_called = True - def _capture(*args): - self.parent_called = True - with mock.patch.object(self.log, 'debug', _capture): - super(DeprecatedApp, self)._config_changed(name, old, new) - - -def test_deprecated_notifier(): - app = DeprecatedApp() - assert not app.override_called - assert not app.parent_called - app.config = Config({'A': {'b': 'c'}}) - assert app.override_called - assert app.parent_called - - -def test_help_output(): - check_help_output(__name__) - - -def test_help_all_output(): - check_help_all_output(__name__) - - -def test_show_config_cli(): - out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config']) - assert ec == 0 - assert 'show_config' not in out - - -def test_show_config_json_cli(): - out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config-json']) - assert ec == 0 - assert 'show_config' not in out - - -def test_show_config(capsys): - cfg = Config() - cfg.MyApp.i = 5 - # don't show empty - cfg.OtherApp - - app = MyApp(config=cfg, show_config=True) - app.start() - out, err = capsys.readouterr() - assert 'MyApp' in out - assert 'i = 5' in out - assert 'OtherApp' not in out - - -def test_show_config_json(capsys): - cfg = Config() - cfg.MyApp.i = 5 - cfg.OtherApp - - app = MyApp(config=cfg, show_config_json=True) - app.start() - out, err = capsys.readouterr() - displayed = json.loads(out) - assert Config(displayed) == cfg - - -def test_deep_alias(): - from traitlets.config import Application, Configurable - from traitlets import Int - - class Foo(Configurable): - val = Int(default_value=5).tag(config=True) - - class Bar(Configurable): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.foo = Foo(parent=self) - - class TestApp(Application): - name = 'test' - - aliases = {'val': 'Bar.Foo.val'} - classes = [Foo, Bar] - - def initialize(self, *args, **kwargs): - super().initialize(*args, **kwargs) - self.bar = Bar(parent=self) - - app = TestApp() - app.initialize(['--val=10']) - assert app.bar.foo.val == 10 - assert len(list(app.emit_alias_help())) > 0 - - -if __name__ == '__main__': - # for test_help_output: - MyApp.launch_instance() +# coding: utf-8 +""" +Tests for traitlets.config.application.Application +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import contextlib +import io +import json +import logging +import os +import sys +from io import StringIO +from tempfile import TemporaryDirectory +from unittest import TestCase + +import pytest +from pytest import mark + +from traitlets import ( + Bool, + Bytes, + Dict, + HasTraits, + Integer, + List, + Set, + Tuple, + Unicode, +) +from traitlets.config.application import Application +from traitlets.config.configurable import Configurable +from traitlets.config.loader import Config +from traitlets.tests.utils import ( + check_help_all_output, + check_help_output, + get_output_error_code, +) + +try: + from unittest import mock +except ImportError: + import mock + +pjoin = os.path.join + + +class Foo(Configurable): + + i = Integer(0, help=""" + The integer i. + + Details about i. + """).tag(config=True) + j = Integer(1, help="The integer j.").tag(config=True) + name = Unicode('Brian', help="First name.").tag(config=True) + la = List([]).tag(config=True) + li = List(Integer()).tag(config=True) + fdict = Dict().tag(config=True, multiplicity='+') + + +class Bar(Configurable): + + b = Integer(0, help="The integer b.").tag(config=True) + enabled = Bool(True, help="Enable bar.").tag(config=True) + tb = Tuple(()).tag(config=True, multiplicity='*') + aset = Set().tag(config=True, multiplicity='+') + bdict = Dict().tag(config=True) + idict = Dict(value_trait=Integer()).tag(config=True) + key_dict = Dict(per_key_traits={'i': Integer(), 'b': Bytes()}).tag(config=True) + + +class MyApp(Application): + + name = Unicode('myapp') + running = Bool(False, help="Is the app running?").tag(config=True) + classes = List([Bar, Foo]) + config_file = Unicode('', help="Load this config file").tag(config=True) + + warn_tpyo = Unicode("yes the name is wrong on purpose", config=True, + help="Should print a warning if `MyApp.warn-typo=...` command is passed") + + aliases = {} + aliases.update(Application.aliases) + aliases.update({ + ('fooi', 'i') : 'Foo.i', + ('j', 'fooj') : ('Foo.j', "`j` terse help msg"), + 'name' : 'Foo.name', + 'la': 'Foo.la', + 'li': 'Foo.li', + 'tb': 'Bar.tb', + 'D': 'Bar.bdict', + 'enabled' : 'Bar.enabled', + 'enable' : 'Bar.enabled', + 'log-level' : 'Application.log_level', + }) + + flags = {} + flags.update(Application.flags) + flags.update({('enable', 'e'): + ({'Bar': {'enabled' : True}}, + "Set Bar.enabled to True"), + ('d', 'disable'): + ({'Bar': {'enabled' : False}}, + "Set Bar.enabled to False"), + 'crit': + ({'Application' : {'log_level' : logging.CRITICAL}}, + "set level=CRITICAL"), + }) + + def init_foo(self): + self.foo = Foo(parent=self) + + def init_bar(self): + self.bar = Bar(parent=self) + + +def class_to_names(classes): + return [klass.__name__ for klass in classes] + + +class TestApplication(TestCase): + def test_log(self): + stream = StringIO() + app = MyApp(log_level=logging.INFO) + handler = logging.StreamHandler(stream) + # trigger reconstruction of the log formatter + app.log.handlers = [handler] + app.log_format = "%(message)s" + app.log_datefmt = "%Y-%m-%d %H:%M" + app.log.info("hello") + assert "hello" in stream.getvalue() + + def test_no_eval_cli_text(self): + app = MyApp() + app.initialize(['--Foo.name=1']) + app.init_foo() + assert app.foo.name == '1' + + def test_basic(self): + app = MyApp() + self.assertEqual(app.name, 'myapp') + self.assertEqual(app.running, False) + self.assertEqual(app.classes, [MyApp, Bar, Foo]) + self.assertEqual(app.config_file, '') + + def test_mro_discovery(self): + app = MyApp() + + self.assertSequenceEqual(class_to_names(app._classes_with_config_traits()), + ['Application', 'MyApp', 'Bar', 'Foo']) + self.assertSequenceEqual(class_to_names(app._classes_inc_parents()), + ['Configurable', 'LoggingConfigurable', 'SingletonConfigurable', + 'Application', 'MyApp', 'Bar', 'Foo']) + + self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Application])), + ['Application']) + self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Application])), + ['Configurable', 'LoggingConfigurable', 'SingletonConfigurable', + 'Application']) + + self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])), + ['Foo']) + self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Bar])), + ['Configurable', 'Bar']) + + class MyApp2(Application): # no defined `classes` attr + pass + + self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])), + ['Foo']) + self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Bar])), + ['Configurable', 'Bar']) + + + def test_config(self): + app = MyApp() + app.parse_command_line([ + "--i=10", + "--Foo.j=10", + "--enable=False", + "--log-level=50", + ]) + config = app.config + print(config) + self.assertEqual(config.Foo.i, 10) + self.assertEqual(config.Foo.j, 10) + self.assertEqual(config.Bar.enabled, False) + self.assertEqual(config.MyApp.log_level, 50) + + def test_config_seq_args(self): + app = MyApp() + app.parse_command_line("--li 1 --li 3 --la 1 --tb AB 2 --Foo.la=ab --Bar.aset S1 --Bar.aset S2 --Bar.aset S1".split()) + assert app.extra_args == ["2"] + config = app.config + assert config.Foo.li == [1, 3] + assert config.Foo.la == ["1", "ab"] + assert config.Bar.tb == ("AB",) + self.assertEqual(config.Bar.aset, {"S1", "S2"}) + app.init_foo() + assert app.foo.li == [1, 3] + assert app.foo.la == ['1', 'ab'] + app.init_bar() + self.assertEqual(app.bar.aset, {'S1', 'S2'}) + assert app.bar.tb == ('AB',) + + def test_config_dict_args(self): + app = MyApp() + app.parse_command_line( + "--Foo.fdict a=1 --Foo.fdict b=b --Foo.fdict c=3 " + "--Bar.bdict k=1 -D=a=b -D 22=33 " + "--Bar.idict k=1 --Bar.idict b=2 --Bar.idict c=3 " + .split()) + fdict = {'a': '1', 'b': 'b', 'c': '3'} + bdict = {'k': '1', 'a': 'b', '22': '33'} + idict = {'k': 1, 'b': 2, 'c': 3} + config = app.config + assert config.Bar.idict == idict + self.assertDictEqual(config.Foo.fdict, fdict) + self.assertDictEqual(config.Bar.bdict, bdict) + app.init_foo() + self.assertEqual(app.foo.fdict, fdict) + app.init_bar() + assert app.bar.idict == idict + self.assertEqual(app.bar.bdict, bdict) + + def test_config_propagation(self): + app = MyApp() + app.parse_command_line(["--i=10","--Foo.j=10","--enable=False","--log-level=50"]) + app.init_foo() + app.init_bar() + self.assertEqual(app.foo.i, 10) + self.assertEqual(app.foo.j, 10) + self.assertEqual(app.bar.enabled, False) + + def test_cli_priority(self): + """Test that loading config files does not override CLI options""" + name = 'config.py' + class TestApp(Application): + value = Unicode().tag(config=True) + config_file_loaded = Bool().tag(config=True) + aliases = {'v': 'TestApp.value'} + app = TestApp() + with TemporaryDirectory() as td: + config_file = pjoin(td, name) + with open(config_file, 'w') as f: + f.writelines([ + "c.TestApp.value = 'config file'\n", + "c.TestApp.config_file_loaded = True\n" + ]) + + app.parse_command_line(['--v=cli']) + assert 'value' in app.config.TestApp + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + + app.load_config_file(name, path=[td]) + assert app.config_file_loaded + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + + def test_ipython_cli_priority(self): + # this test is almost entirely redundant with above, + # but we can keep it around in case of subtle issues creeping into + # the exact sequence IPython follows. + name = 'config.py' + class TestApp(Application): + value = Unicode().tag(config=True) + config_file_loaded = Bool().tag(config=True) + aliases = {'v': ('TestApp.value', 'some help')} + app = TestApp() + with TemporaryDirectory() as td: + config_file = pjoin(td, name) + with open(config_file, 'w') as f: + f.writelines([ + "c.TestApp.value = 'config file'\n", + "c.TestApp.config_file_loaded = True\n" + ]) + # follow IPython's config-loading sequence to ensure CLI priority is preserved + app.parse_command_line(['--v=cli']) + # this is where IPython makes a mistake: + # it assumes app.config will not be modified, + # and storing a reference is storing a copy + cli_config = app.config + assert 'value' in app.config.TestApp + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + app.load_config_file(name, path=[td]) + assert app.config_file_loaded + # enforce cl-opts override config file opts: + # this is where IPython makes a mistake: it assumes + # that cl_config is a different object, but it isn't. + app.update_config(cli_config) + assert app.config.TestApp.value == 'cli' + assert app.value == 'cli' + + def test_cli_allow_none(self): + class App(Application): + aliases = {"opt": "App.opt"} + opt = Unicode(allow_none=True, config=True) + + app = App() + app.parse_command_line(["--opt=None"]) + assert app.opt is None + + def test_flags(self): + app = MyApp() + app.parse_command_line(["--disable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + + app = MyApp() + app.parse_command_line(["-d"]) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + + app = MyApp() + app.parse_command_line(["--enable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + + app = MyApp() + app.parse_command_line(["-e"]) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + + def test_flags_help_msg(self): + app = MyApp() + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + app.print_flag_help() + hmsg = stdout.getvalue() + self.assertRegex(hmsg, "(?<!-)-e, --enable\\b") + self.assertRegex(hmsg, "(?<!-)-d, --disable\\b") + self.assertIn("Equivalent to: [--Bar.enabled=True]", hmsg) + self.assertIn("Equivalent to: [--Bar.enabled=False]", hmsg) + + def test_aliases(self): + app = MyApp() + app.parse_command_line(["--i=5", "--j=10"]) + app.init_foo() + self.assertEqual(app.foo.i, 5) + app.init_foo() + self.assertEqual(app.foo.j, 10) + + app = MyApp() + app.parse_command_line(["-i=5", "-j=10"]) + app.init_foo() + self.assertEqual(app.foo.i, 5) + app.init_foo() + self.assertEqual(app.foo.j, 10) + + app = MyApp() + app.parse_command_line(["--fooi=5", "--fooj=10"]) + app.init_foo() + self.assertEqual(app.foo.i, 5) + app.init_foo() + self.assertEqual(app.foo.j, 10) + + def test_aliases_help_msg(self): + app = MyApp() + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + app.print_alias_help() + hmsg = stdout.getvalue() + self.assertRegex(hmsg, "(?<!-)-i, --fooi\\b") + self.assertRegex(hmsg, "(?<!-)-j, --fooj\\b") + self.assertIn("Equivalent to: [--Foo.i]", hmsg) + self.assertIn("Equivalent to: [--Foo.j]", hmsg) + self.assertIn("Equivalent to: [--Foo.name]", hmsg) + + def test_flag_clobber(self): + """test that setting flags doesn't clobber existing settings""" + app = MyApp() + app.parse_command_line(["--Bar.b=5", "--disable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + self.assertEqual(app.bar.b, 5) + app.parse_command_line(["--enable", "--Bar.b=10"]) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + self.assertEqual(app.bar.b, 10) + + def test_warn_autocorrect(self): + stream = StringIO() + app = MyApp(log_level=logging.INFO) + app.log.handlers = [logging.StreamHandler(stream)] + + cfg = Config() + cfg.MyApp.warn_typo = "WOOOO" + app.config = cfg + + self.assertIn("warn_typo", stream.getvalue()) + self.assertIn("warn_tpyo", stream.getvalue()) + + + def test_flatten_flags(self): + cfg = Config() + cfg.MyApp.log_level = logging.WARN + app = MyApp() + app.update_config(cfg) + self.assertEqual(app.log_level, logging.WARN) + self.assertEqual(app.config.MyApp.log_level, logging.WARN) + app.initialize(["--crit"]) + self.assertEqual(app.log_level, logging.CRITICAL) + # this would be app.config.Application.log_level if it failed: + self.assertEqual(app.config.MyApp.log_level, logging.CRITICAL) + + def test_flatten_aliases(self): + cfg = Config() + cfg.MyApp.log_level = logging.WARN + app = MyApp() + app.update_config(cfg) + self.assertEqual(app.log_level, logging.WARN) + self.assertEqual(app.config.MyApp.log_level, logging.WARN) + app.initialize(["--log-level", "CRITICAL"]) + self.assertEqual(app.log_level, logging.CRITICAL) + # this would be app.config.Application.log_level if it failed: + self.assertEqual(app.config.MyApp.log_level, "CRITICAL") + + def test_extra_args(self): + + app = MyApp() + app.parse_command_line(["--Bar.b=5", 'extra', 'args', "--disable"]) + app.init_bar() + self.assertEqual(app.bar.enabled, False) + self.assertEqual(app.bar.b, 5) + self.assertEqual(app.extra_args, ['extra', 'args']) + + app = MyApp() + app.parse_command_line(["--Bar.b=5", '--', 'extra', "--disable", 'args']) + app.init_bar() + self.assertEqual(app.bar.enabled, True) + self.assertEqual(app.bar.b, 5) + self.assertEqual(app.extra_args, ['extra', '--disable', 'args']) + + app = MyApp() + app.parse_command_line( + ["--disable", "--la", "-", "-", "--Bar.b=1", "--", "-", "extra"] + ) + self.assertEqual(app.extra_args, ["-", "-", "extra"]) + + def test_unicode_argv(self): + app = MyApp() + app.parse_command_line(['ünîcødé']) + + def test_document_config_option(self): + app = MyApp() + app.document_config_options() + + def test_generate_config_file(self): + app = MyApp() + assert 'The integer b.' in app.generate_config_file() + + def test_generate_config_file_classes_to_include(self): + class NotInConfig(HasTraits): + from_hidden = Unicode('x', help="""From hidden class + + Details about from_hidden. + """).tag(config=True) + + class NoTraits(Foo, Bar, NotInConfig): + pass + + app = MyApp() + app.classes.append(NoTraits) + + conf_txt = app.generate_config_file() + print(conf_txt) + self.assertIn('The integer b.', conf_txt) + self.assertIn('# Foo(Configurable)', conf_txt) + self.assertNotIn('# Configurable', conf_txt) + self.assertIn('# NoTraits(Foo, Bar)', conf_txt) + + # inherited traits, parent in class list: + self.assertIn('# c.NoTraits.i', conf_txt) + self.assertIn('# c.NoTraits.j', conf_txt) + self.assertIn('# c.NoTraits.n', conf_txt) + self.assertIn('# See also: Foo.j', conf_txt) + self.assertIn('# See also: Bar.b', conf_txt) + self.assertEqual(conf_txt.count('Details about i.'), 1) + + # inherited traits, parent not in class list: + self.assertIn("# c.NoTraits.from_hidden", conf_txt) + self.assertNotIn('# See also: NotInConfig.', conf_txt) + self.assertEqual(conf_txt.count('Details about from_hidden.'), 1) + self.assertNotIn("NotInConfig", conf_txt) + + def test_multi_file(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory('_1') as td1: + with open(pjoin(td1, name), 'w') as f1: + f1.write("get_config().MyApp.Bar.b = 1") + with TemporaryDirectory('_2') as td2: + with open(pjoin(td2, name), 'w') as f2: + f2.write("get_config().MyApp.Bar.b = 2") + app.load_config_file(name, path=[td2, td1]) + app.init_bar() + self.assertEqual(app.bar.b, 2) + app.load_config_file(name, path=[td1, td2]) + app.init_bar() + self.assertEqual(app.bar.b, 1) + + @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') + def test_log_collisions(self): + app = MyApp() + app.log = logging.getLogger() + app.log.setLevel(logging.INFO) + name = 'config' + with TemporaryDirectory('_1') as td: + with open(pjoin(td, name + '.py'), 'w') as f: + f.write("get_config().Bar.b = 1") + with open(pjoin(td, name + '.json'), 'w') as f: + json.dump({ + 'Bar': { + 'b': 2 + } + }, f) + with self.assertLogs(app.log, logging.WARNING) as captured: + app.load_config_file(name, path=[td]) + app.init_bar() + assert app.bar.b == 2 + output = '\n'.join(captured.output) + assert 'Collision' in output + assert '1 ignored, using 2' in output + assert pjoin(td, name + '.py') in output + assert pjoin(td, name + '.json') in output + + @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') + def test_log_bad_config(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory() as td: + with open(pjoin(td, name), 'w') as f: + f.write("syntax error()") + with self.assertLogs(app.log, logging.ERROR) as captured: + app.load_config_file(name, path=[td]) + output = '\n'.join(captured.output) + self.assertIn('SyntaxError', output) + + def test_raise_on_bad_config(self): + app = MyApp() + app.raise_config_file_errors = True + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory() as td: + with open(pjoin(td, name), 'w') as f: + f.write("syntax error()") + with self.assertRaises(SyntaxError): + app.load_config_file(name, path=[td]) + + def test_subcommands_instanciation(self): + """Try all ways to specify how to create sub-apps.""" + app = Root.instance() + app.parse_command_line(['sub1']) + + self.assertIsInstance(app.subapp, Sub1) + ## Check parent hierarchy. + self.assertIs(app.subapp.parent, app) + + Root.clear_instance() + Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails. + app = Root.instance() + + app.parse_command_line(['sub1', 'sub2']) + self.assertIsInstance(app.subapp, Sub1) + self.assertIsInstance(app.subapp.subapp, Sub2) + ## Check parent hierarchy. + self.assertIs(app.subapp.parent, app) + self.assertIs(app.subapp.subapp.parent, app.subapp) + + Root.clear_instance() + Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails. + app = Root.instance() + + app.parse_command_line(['sub1', 'sub3']) + self.assertIsInstance(app.subapp, Sub1) + self.assertIsInstance(app.subapp.subapp, Sub3) + self.assertTrue(app.subapp.subapp.flag) # Set by factory. + ## Check parent hierarchy. + self.assertIs(app.subapp.parent, app) + self.assertIs(app.subapp.subapp.parent, app.subapp) # Set by factory. + + def test_loaded_config_files(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory('_1') as td1: + config_file = pjoin(td1, name) + with open(config_file, 'w') as f: + f.writelines([ + "c.MyApp.running = True\n" + ]) + + app.load_config_file(name, path=[td1]) + self.assertEqual(len(app.loaded_config_files), 1) + self.assertEqual(app.loaded_config_files[0], config_file) + + app.start() + self.assertEqual(app.running, True) + + # emulate an app that allows dynamic updates and update config file + with open(config_file, 'w') as f: + f.writelines([ + "c.MyApp.running = False\n" + ]) + + # reload and verify update, and that loaded_configs was not increased + app.load_config_file(name, path=[td1]) + self.assertEqual(len(app.loaded_config_files), 1) + self.assertEqual(app.running, False) + + # Attempt to update, ensure error... + with self.assertRaises(AttributeError): + app.loaded_config_files = "/foo" + + # ensure it can't be udpated via append + app.loaded_config_files.append("/bar") + self.assertEqual(len(app.loaded_config_files), 1) + + # repeat to ensure no unexpected changes occurred + app.load_config_file(name, path=[td1]) + self.assertEqual(len(app.loaded_config_files), 1) + self.assertEqual(app.running, False) + + + +@mark.skip +def test_cli_multi_scalar(caplog): + class App(Application): + aliases = {"opt": "App.opt"} + opt = Unicode(config=True) + + app = App(log=logging.getLogger()) + with pytest.raises(SystemExit): + app.parse_command_line(["--opt", "1", "--opt", "2"]) + record = caplog.get_records("call")[-1] + message = record.message + + assert "Error loading argument" in message + assert "App.opt=['1', '2']" in message + assert "opt only accepts one value" in message + assert record.levelno == logging.CRITICAL + + +class Root(Application): + subcommands = { + 'sub1': ('__tests__.config.tests.test_application.Sub1', 'import string'), + } + + +class Sub3(Application): + flag = Bool(False) + + +class Sub2(Application): + pass + + +class Sub1(Application): + subcommands = { + 'sub2': (Sub2, 'Application class'), + 'sub3': (lambda root: Sub3(parent=root, flag=True), 'factory'), + } + + +class DeprecatedApp(Application): + override_called = False + parent_called = False + def _config_changed(self, name, old, new): + self.override_called = True + def _capture(*args): + self.parent_called = True + with mock.patch.object(self.log, 'debug', _capture): + super(DeprecatedApp, self)._config_changed(name, old, new) + + +def test_deprecated_notifier(): + app = DeprecatedApp() + assert not app.override_called + assert not app.parent_called + app.config = Config({'A': {'b': 'c'}}) + assert app.override_called + assert app.parent_called + + +def test_help_output(): + check_help_output(__name__) + + +def test_help_all_output(): + check_help_all_output(__name__) + + +def test_show_config_cli(): + out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config']) + assert ec == 0 + assert 'show_config' not in out + + +def test_show_config_json_cli(): + out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config-json']) + assert ec == 0 + assert 'show_config' not in out + + +def test_show_config(capsys): + cfg = Config() + cfg.MyApp.i = 5 + # don't show empty + cfg.OtherApp + + app = MyApp(config=cfg, show_config=True) + app.start() + out, err = capsys.readouterr() + assert 'MyApp' in out + assert 'i = 5' in out + assert 'OtherApp' not in out + + +def test_show_config_json(capsys): + cfg = Config() + cfg.MyApp.i = 5 + cfg.OtherApp + + app = MyApp(config=cfg, show_config_json=True) + app.start() + out, err = capsys.readouterr() + displayed = json.loads(out) + assert Config(displayed) == cfg + + +def test_deep_alias(): + from traitlets.config import Application, Configurable + from traitlets import Int + + class Foo(Configurable): + val = Int(default_value=5).tag(config=True) + + class Bar(Configurable): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.foo = Foo(parent=self) + + class TestApp(Application): + name = 'test' + + aliases = {'val': 'Bar.Foo.val'} + classes = [Foo, Bar] + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + self.bar = Bar(parent=self) + + app = TestApp() + app.initialize(['--val=10']) + assert app.bar.foo.val == 10 + assert len(list(app.emit_alias_help())) > 0 + + +if __name__ == '__main__': + # for test_help_output: + MyApp.launch_instance() diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py b/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py index 4f08c83198..b8d153b53f 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py +++ b/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py @@ -1,680 +1,680 @@ -"""Tests for traitlets.config.configurable""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import logging -from unittest import TestCase - -from pytest import mark - -from traitlets.config.application import Application -from traitlets.config.configurable import ( - Configurable, - LoggingConfigurable, - SingletonConfigurable, -) -from traitlets.log import get_logger -from traitlets.traitlets import ( - Integer, Float, Unicode, List, Dict, Set, Enum, FuzzyEnum, - CaselessStrEnum, _deprecations_shown, validate, -) - -from traitlets.config.loader import Config - -from traitlets.tests._warnings import expected_warnings - -class MyConfigurable(Configurable): - a = Integer(1, help="The integer a.").tag(config=True) - b = Float(1.0, help="The integer b.").tag(config=True) - c = Unicode('no config') - - -mc_help = """MyConfigurable(Configurable) options ------------------------------------- ---MyConfigurable.a=<Integer> - The integer a. - Default: 1 ---MyConfigurable.b=<Float> - The integer b. - Default: 1.0""" - -mc_help_inst="""MyConfigurable(Configurable) options ------------------------------------- ---MyConfigurable.a=<Integer> - The integer a. - Current: 5 ---MyConfigurable.b=<Float> - The integer b. - Current: 4.0""" - -# On Python 3, the Integer trait is a synonym for Int -mc_help = mc_help.replace("<Integer>", "<Int>") -mc_help_inst = mc_help_inst.replace("<Integer>", "<Int>") - -class Foo(Configurable): - a = Integer(0, help="The integer a.").tag(config=True) - b = Unicode('nope').tag(config=True) - flist = List([]).tag(config=True) - fdict = Dict().tag(config=True) - - -class Bar(Foo): - b = Unicode('gotit', help="The string b.").tag(config=False) - c = Float(help="The string c.").tag(config=True) - bset = Set([]).tag(config=True, multiplicity='+') - bset_values = Set([2,1,5]).tag(config=True, multiplicity='+') - bdict = Dict().tag(config=True, multiplicity='+') - bdict_values = Dict({1:'a','0':'b',5:'c'}).tag(config=True, multiplicity='+') - -foo_help="""Foo(Configurable) options -------------------------- ---Foo.a=<Int> - The integer a. - Default: 0 ---Foo.b=<Unicode> - Default: 'nope' ---Foo.fdict=<key-1>=<value-1>... - Default: {} ---Foo.flist=<list-item-1>... - Default: []""" - -bar_help="""Bar(Foo) options ----------------- ---Bar.a=<Int> - The integer a. - Default: 0 ---Bar.bdict <key-1>=<value-1>... - Default: {} ---Bar.bdict_values <key-1>=<value-1>... - Default: {1: 'a', '0': 'b', 5: 'c'} ---Bar.bset <set-item-1>... - Default: set() ---Bar.bset_values <set-item-1>... - Default: {1, 2, 5} ---Bar.c=<Float> - The string c. - Default: 0.0 ---Bar.fdict=<key-1>=<value-1>... - Default: {} ---Bar.flist=<list-item-1>... - Default: []""" - - -class TestConfigurable(TestCase): - - def test_default(self): - c1 = Configurable() - c2 = Configurable(config=c1.config) - c3 = Configurable(config=c2.config) - self.assertEqual(c1.config, c2.config) - self.assertEqual(c2.config, c3.config) - - def test_custom(self): - config = Config() - config.foo = 'foo' - config.bar = 'bar' - c1 = Configurable(config=config) - c2 = Configurable(config=c1.config) - c3 = Configurable(config=c2.config) - self.assertEqual(c1.config, config) - self.assertEqual(c2.config, config) - self.assertEqual(c3.config, config) - # Test that copies are not made - self.assertTrue(c1.config is config) - self.assertTrue(c2.config is config) - self.assertTrue(c3.config is config) - self.assertTrue(c1.config is c2.config) - self.assertTrue(c2.config is c3.config) - - def test_inheritance(self): - config = Config() - config.MyConfigurable.a = 2 - config.MyConfigurable.b = 2.0 - c1 = MyConfigurable(config=config) - c2 = MyConfigurable(config=c1.config) - self.assertEqual(c1.a, config.MyConfigurable.a) - self.assertEqual(c1.b, config.MyConfigurable.b) - self.assertEqual(c2.a, config.MyConfigurable.a) - self.assertEqual(c2.b, config.MyConfigurable.b) - - def test_parent(self): - config = Config() - config.Foo.a = 10 - config.Foo.b = "wow" - config.Bar.b = 'later' - config.Bar.c = 100.0 - f = Foo(config=config) - with expected_warnings(['`b` not recognized']): - b = Bar(config=f.config) - self.assertEqual(f.a, 10) - self.assertEqual(f.b, 'wow') - self.assertEqual(b.b, 'gotit') - self.assertEqual(b.c, 100.0) - - def test_override1(self): - config = Config() - config.MyConfigurable.a = 2 - config.MyConfigurable.b = 2.0 - c = MyConfigurable(a=3, config=config) - self.assertEqual(c.a, 3) - self.assertEqual(c.b, config.MyConfigurable.b) - self.assertEqual(c.c, 'no config') - - def test_override2(self): - config = Config() - config.Foo.a = 1 - config.Bar.b = 'or' # Up above b is config=False, so this won't do it. - config.Bar.c = 10.0 - with expected_warnings(['`b` not recognized']): - c = Bar(config=config) - self.assertEqual(c.a, config.Foo.a) - self.assertEqual(c.b, 'gotit') - self.assertEqual(c.c, config.Bar.c) - with expected_warnings(['`b` not recognized']): - c = Bar(a=2, b='and', c=20.0, config=config) - self.assertEqual(c.a, 2) - self.assertEqual(c.b, 'and') - self.assertEqual(c.c, 20.0) - - def test_help(self): - self.assertEqual(MyConfigurable.class_get_help(), mc_help) - self.assertEqual(Foo.class_get_help(), foo_help) - self.assertEqual(Bar.class_get_help(), bar_help) - - def test_help_inst(self): - inst = MyConfigurable(a=5, b=4) - self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst) - - def test_generated_config_enum_comments(self): - class MyConf(Configurable): - an_enum = Enum('Choice1 choice2'.split(), - help="Many choices.").tag(config=True) - - help_str = "Many choices." - enum_choices_str = "Choices: any of ['Choice1', 'choice2']" - rst_choices_str = "MyConf.an_enum : any of ``'Choice1'``|``'choice2'``" - or_none_str = "or None" - - cls_help = MyConf.class_get_help() - - self.assertIn(help_str, cls_help) - self.assertIn(enum_choices_str, cls_help) - self.assertNotIn(or_none_str, cls_help) - - cls_cfg = MyConf.class_config_section() - - self.assertIn(help_str, cls_cfg) - self.assertIn(enum_choices_str, cls_cfg) - self.assertNotIn(or_none_str, cls_help) - ## Check order of Help-msg <--> Choices sections - self.assertGreater(cls_cfg.index(enum_choices_str), - cls_cfg.index(help_str)) - - rst_help = MyConf.class_config_rst_doc() - - self.assertIn(help_str, rst_help) - self.assertIn(rst_choices_str, rst_help) - self.assertNotIn(or_none_str, rst_help) - - class MyConf2(Configurable): - an_enum = Enum('Choice1 choice2'.split(), - allow_none=True, - default_value='choice2', - help="Many choices.").tag(config=True) - - defaults_str = "Default: 'choice2'" - - cls2_msg = MyConf2.class_get_help() - - self.assertIn(help_str, cls2_msg) - self.assertIn(enum_choices_str, cls2_msg) - self.assertIn(or_none_str, cls2_msg) - self.assertIn(defaults_str, cls2_msg) - ## Check order of Default <--> Choices sections - self.assertGreater(cls2_msg.index(defaults_str), - cls2_msg.index(enum_choices_str)) - - cls2_cfg = MyConf2.class_config_section() - - self.assertIn(help_str, cls2_cfg) - self.assertIn(enum_choices_str, cls2_cfg) - self.assertIn(or_none_str, cls2_cfg) - self.assertIn(defaults_str, cls2_cfg) - ## Check order of Default <--> Choices sections - self.assertGreater(cls2_cfg.index(defaults_str), - cls2_cfg.index(enum_choices_str)) - - def test_generated_config_strenum_comments(self): - help_str = "Many choices." - defaults_str = "Default: 'choice2'" - or_none_str = "or None" - - class MyConf3(Configurable): - an_enum = CaselessStrEnum('Choice1 choice2'.split(), - allow_none=True, - default_value='choice2', - help="Many choices.").tag(config=True) - - enum_choices_str = ("Choices: any of ['Choice1', 'choice2'] " - "(case-insensitive)") - - cls3_msg = MyConf3.class_get_help() - - self.assertIn(help_str, cls3_msg) - self.assertIn(enum_choices_str, cls3_msg) - self.assertIn(or_none_str, cls3_msg) - self.assertIn(defaults_str, cls3_msg) - ## Check order of Default <--> Choices sections - self.assertGreater(cls3_msg.index(defaults_str), - cls3_msg.index(enum_choices_str)) - - cls3_cfg = MyConf3.class_config_section() - - self.assertIn(help_str, cls3_cfg) - self.assertIn(enum_choices_str, cls3_cfg) - self.assertIn(or_none_str, cls3_cfg) - self.assertIn(defaults_str, cls3_cfg) - ## Check order of Default <--> Choices sections - self.assertGreater(cls3_cfg.index(defaults_str), - cls3_cfg.index(enum_choices_str)) - - class MyConf4(Configurable): - an_enum = FuzzyEnum('Choice1 choice2'.split(), - allow_none=True, - default_value='choice2', - help="Many choices.").tag(config=True) - - enum_choices_str = ("Choices: any case-insensitive prefix " - "of ['Choice1', 'choice2']") - - cls4_msg = MyConf4.class_get_help() - - self.assertIn(help_str, cls4_msg) - self.assertIn(enum_choices_str, cls4_msg) - self.assertIn(or_none_str, cls4_msg) - self.assertIn(defaults_str, cls4_msg) - ## Check order of Default <--> Choices sections - self.assertGreater(cls4_msg.index(defaults_str), - cls4_msg.index(enum_choices_str)) - - cls4_cfg = MyConf4.class_config_section() - - self.assertIn(help_str, cls4_cfg) - self.assertIn(enum_choices_str, cls4_cfg) - self.assertIn(or_none_str, cls4_cfg) - self.assertIn(defaults_str, cls4_cfg) - ## Check order of Default <--> Choices sections - self.assertGreater(cls4_cfg.index(defaults_str), - cls4_cfg.index(enum_choices_str)) - - - -class TestSingletonConfigurable(TestCase): - - def test_instance(self): - class Foo(SingletonConfigurable): pass - self.assertEqual(Foo.initialized(), False) - foo = Foo.instance() - self.assertEqual(Foo.initialized(), True) - self.assertEqual(foo, Foo.instance()) - self.assertEqual(SingletonConfigurable._instance, None) - - def test_inheritance(self): - class Bar(SingletonConfigurable): pass - class Bam(Bar): pass - self.assertEqual(Bar.initialized(), False) - self.assertEqual(Bam.initialized(), False) - bam = Bam.instance() - self.assertEqual(Bar.initialized(), True) - self.assertEqual(Bam.initialized(), True) - self.assertEqual(bam, Bam._instance) - self.assertEqual(bam, Bar._instance) - self.assertEqual(SingletonConfigurable._instance, None) - - -class TestLoggingConfigurable(TestCase): - - def test_parent_logger(self): - class Parent(LoggingConfigurable): pass - class Child(LoggingConfigurable): pass - log = get_logger().getChild("TestLoggingConfigurable") - - parent = Parent(log=log) - child = Child(parent=parent) - self.assertEqual(parent.log, log) - self.assertEqual(child.log, log) - - parent = Parent() - child = Child(parent=parent, log=log) - self.assertEqual(parent.log, get_logger()) - self.assertEqual(child.log, log) - - def test_parent_not_logging_configurable(self): - class Parent(Configurable): pass - class Child(LoggingConfigurable): pass - parent = Parent() - child = Child(parent=parent) - self.assertEqual(child.log, get_logger()) - - -class MyParent(Configurable): - pass - -class MyParent2(MyParent): - pass - -class TestParentConfigurable(TestCase): - - def test_parent_config(self): - cfg = Config({ - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 2.0, - } - } - }) - parent = MyParent(config=cfg) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) - - def test_parent_inheritance(self): - cfg = Config({ - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 2.0, - } - } - }) - parent = MyParent2(config=cfg) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) - - def test_multi_parent(self): - cfg = Config({ - 'MyParent2' : { - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 2.0, - } - }, - # this one shouldn't count - 'MyConfigurable' : { - 'b' : 3.0, - }, - } - }) - parent2 = MyParent2(config=cfg) - parent = MyParent(parent=parent2) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) - - def test_parent_priority(self): - cfg = Config({ - 'MyConfigurable' : { - 'b' : 2.0, - }, - 'MyParent' : { - 'MyConfigurable' : { - 'b' : 3.0, - } - }, - 'MyParent2' : { - 'MyConfigurable' : { - 'b' : 4.0, - } - } - }) - parent = MyParent2(config=cfg) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b) - - def test_multi_parent_priority(self): - cfg = Config({ - 'MyConfigurable': { - 'b': 2.0, - }, - 'MyParent': { - 'MyConfigurable': { - 'b': 3.0, - }, - }, - 'MyParent2': { - 'MyConfigurable': { - 'b': 4.0, - }, - 'MyParent': { - 'MyConfigurable': { - 'b': 5.0, - }, - }, - }, - }) - parent2 = MyParent2(config=cfg) - parent = MyParent2(parent=parent2) - myc = MyConfigurable(parent=parent) - self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) - -class Containers(Configurable): - lis = List().tag(config=True) - def _lis_default(self): - return [-1] - - s = Set().tag(config=True) - def _s_default(self): - return {'a'} - - d = Dict().tag(config=True) - def _d_default(self): - return {'a' : 'b'} - -class TestConfigContainers(TestCase): - def test_extend(self): - c = Config() - c.Containers.lis.extend(list(range(5))) - obj = Containers(config=c) - self.assertEqual(obj.lis, list(range(-1,5))) - - def test_insert(self): - c = Config() - c.Containers.lis.insert(0, 'a') - c.Containers.lis.insert(1, 'b') - obj = Containers(config=c) - self.assertEqual(obj.lis, ['a', 'b', -1]) - - def test_prepend(self): - c = Config() - c.Containers.lis.prepend([1,2]) - c.Containers.lis.prepend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [2,3,1,2,-1]) - - def test_prepend_extend(self): - c = Config() - c.Containers.lis.prepend([1,2]) - c.Containers.lis.extend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [1,2,-1,2,3]) - - def test_append_extend(self): - c = Config() - c.Containers.lis.append([1,2]) - c.Containers.lis.extend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [-1,[1,2],2,3]) - - def test_extend_append(self): - c = Config() - c.Containers.lis.extend([2,3]) - c.Containers.lis.append([1,2]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [-1,2,3,[1,2]]) - - def test_insert_extend(self): - c = Config() - c.Containers.lis.insert(0, 1) - c.Containers.lis.extend([2,3]) - obj = Containers(config=c) - self.assertEqual(obj.lis, [1,-1,2,3]) - - def test_set_update(self): - c = Config() - c.Containers.s.update({0,1,2}) - c.Containers.s.update({3}) - obj = Containers(config=c) - self.assertEqual(obj.s, {'a', 0, 1, 2, 3}) - - def test_dict_update(self): - c = Config() - c.Containers.d.update({'c' : 'd'}) - c.Containers.d.update({'e' : 'f'}) - obj = Containers(config=c) - self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'}) - - def test_update_twice(self): - c = Config() - c.MyConfigurable.a = 5 - m = MyConfigurable(config=c) - self.assertEqual(m.a, 5) - - c2 = Config() - c2.MyConfigurable.a = 10 - m.update_config(c2) - self.assertEqual(m.a, 10) - - c2.MyConfigurable.a = 15 - m.update_config(c2) - self.assertEqual(m.a, 15) - - def test_update_self(self): - """update_config with same config object still triggers config_changed""" - c = Config() - c.MyConfigurable.a = 5 - m = MyConfigurable(config=c) - self.assertEqual(m.a, 5) - c.MyConfigurable.a = 10 - m.update_config(c) - self.assertEqual(m.a, 10) - - def test_config_default(self): - class SomeSingleton(SingletonConfigurable): - pass - - class DefaultConfigurable(Configurable): - a = Integer().tag(config=True) - def _config_default(self): - if SomeSingleton.initialized(): - return SomeSingleton.instance().config - return Config() - - c = Config() - c.DefaultConfigurable.a = 5 - - d1 = DefaultConfigurable() - self.assertEqual(d1.a, 0) - - single = SomeSingleton.instance(config=c) - - d2 = DefaultConfigurable() - self.assertIs(d2.config, single.config) - self.assertEqual(d2.a, 5) - - def test_config_default_deprecated(self): - """Make sure configurables work even with the deprecations in traitlets""" - class SomeSingleton(SingletonConfigurable): - pass - - # reset deprecation limiter - _deprecations_shown.clear() - with expected_warnings([]): - class DefaultConfigurable(Configurable): - a = Integer(config=True) - def _config_default(self): - if SomeSingleton.initialized(): - return SomeSingleton.instance().config - return Config() - - c = Config() - c.DefaultConfigurable.a = 5 - - d1 = DefaultConfigurable() - self.assertEqual(d1.a, 0) - - single = SomeSingleton.instance(config=c) - - d2 = DefaultConfigurable() - self.assertIs(d2.config, single.config) - self.assertEqual(d2.a, 5) - - def test_kwarg_config_priority(self): - # a, c set in kwargs - # a, b set in config - # verify that: - # - kwargs are set before config - # - kwargs have priority over config - class A(Configurable): - a = Unicode('default', config=True) - b = Unicode('default', config=True) - c = Unicode('default', config=True) - c_during_config = Unicode('never') - @validate('b') - def _record_c(self, proposal): - # setting b from config records c's value at the time - self.c_during_config = self.c - return proposal.value - - cfg = Config() - cfg.A.a = 'a-config' - cfg.A.b = 'b-config' - obj = A(a='a-kwarg', c='c-kwarg', config=cfg) - assert obj.a == 'a-kwarg' - assert obj.b == 'b-config' - assert obj.c == 'c-kwarg' - assert obj.c_during_config == 'c-kwarg' - - -class TestLogger(TestCase): - - class A(LoggingConfigurable): - foo = Integer(config=True) - bar = Integer(config=True) - baz = Integer(config=True) - - @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') - def test_warn_match(self): - logger = logging.getLogger('test_warn_match') - cfg = Config({'A': {'bat': 5}}) - with self.assertLogs(logger, logging.WARNING) as captured: - TestLogger.A(config=cfg, log=logger) - - output = '\n'.join(captured.output) - self.assertIn('Did you mean one of: `bar, baz`?', output) - self.assertIn('Config option `bat` not recognized by `A`.', output) - - cfg = Config({'A': {'fool': 5}}) - with self.assertLogs(logger, logging.WARNING) as captured: - TestLogger.A(config=cfg, log=logger) - - output = '\n'.join(captured.output) - self.assertIn('Config option `fool` not recognized by `A`.', output) - self.assertIn('Did you mean `foo`?', output) - - cfg = Config({'A': {'totally_wrong': 5}}) - with self.assertLogs(logger, logging.WARNING) as captured: - TestLogger.A(config=cfg, log=logger) - - output = '\n'.join(captured.output) - self.assertIn('Config option `totally_wrong` not recognized by `A`.', output) - self.assertNotIn('Did you mean', output) - - def test_logger_adapter(self): - logger = logging.getLogger("test_logger_adapter") - adapter = logging.LoggerAdapter(logger, {"key": "adapted"}) - - with self.assertLogs(logger, logging.INFO) as captured: - app = Application(log=adapter, log_level=logging.INFO) - app.log_format = "%(key)s %(message)s" - app.log.info("test message") - - output = "\n".join(captured.output) - assert "adapted test message" in output +"""Tests for traitlets.config.configurable""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import logging +from unittest import TestCase + +from pytest import mark + +from traitlets.config.application import Application +from traitlets.config.configurable import ( + Configurable, + LoggingConfigurable, + SingletonConfigurable, +) +from traitlets.log import get_logger +from traitlets.traitlets import ( + Integer, Float, Unicode, List, Dict, Set, Enum, FuzzyEnum, + CaselessStrEnum, _deprecations_shown, validate, +) + +from traitlets.config.loader import Config + +from traitlets.tests._warnings import expected_warnings + +class MyConfigurable(Configurable): + a = Integer(1, help="The integer a.").tag(config=True) + b = Float(1.0, help="The integer b.").tag(config=True) + c = Unicode('no config') + + +mc_help = """MyConfigurable(Configurable) options +------------------------------------ +--MyConfigurable.a=<Integer> + The integer a. + Default: 1 +--MyConfigurable.b=<Float> + The integer b. + Default: 1.0""" + +mc_help_inst="""MyConfigurable(Configurable) options +------------------------------------ +--MyConfigurable.a=<Integer> + The integer a. + Current: 5 +--MyConfigurable.b=<Float> + The integer b. + Current: 4.0""" + +# On Python 3, the Integer trait is a synonym for Int +mc_help = mc_help.replace("<Integer>", "<Int>") +mc_help_inst = mc_help_inst.replace("<Integer>", "<Int>") + +class Foo(Configurable): + a = Integer(0, help="The integer a.").tag(config=True) + b = Unicode('nope').tag(config=True) + flist = List([]).tag(config=True) + fdict = Dict().tag(config=True) + + +class Bar(Foo): + b = Unicode('gotit', help="The string b.").tag(config=False) + c = Float(help="The string c.").tag(config=True) + bset = Set([]).tag(config=True, multiplicity='+') + bset_values = Set([2,1,5]).tag(config=True, multiplicity='+') + bdict = Dict().tag(config=True, multiplicity='+') + bdict_values = Dict({1:'a','0':'b',5:'c'}).tag(config=True, multiplicity='+') + +foo_help="""Foo(Configurable) options +------------------------- +--Foo.a=<Int> + The integer a. + Default: 0 +--Foo.b=<Unicode> + Default: 'nope' +--Foo.fdict=<key-1>=<value-1>... + Default: {} +--Foo.flist=<list-item-1>... + Default: []""" + +bar_help="""Bar(Foo) options +---------------- +--Bar.a=<Int> + The integer a. + Default: 0 +--Bar.bdict <key-1>=<value-1>... + Default: {} +--Bar.bdict_values <key-1>=<value-1>... + Default: {1: 'a', '0': 'b', 5: 'c'} +--Bar.bset <set-item-1>... + Default: set() +--Bar.bset_values <set-item-1>... + Default: {1, 2, 5} +--Bar.c=<Float> + The string c. + Default: 0.0 +--Bar.fdict=<key-1>=<value-1>... + Default: {} +--Bar.flist=<list-item-1>... + Default: []""" + + +class TestConfigurable(TestCase): + + def test_default(self): + c1 = Configurable() + c2 = Configurable(config=c1.config) + c3 = Configurable(config=c2.config) + self.assertEqual(c1.config, c2.config) + self.assertEqual(c2.config, c3.config) + + def test_custom(self): + config = Config() + config.foo = 'foo' + config.bar = 'bar' + c1 = Configurable(config=config) + c2 = Configurable(config=c1.config) + c3 = Configurable(config=c2.config) + self.assertEqual(c1.config, config) + self.assertEqual(c2.config, config) + self.assertEqual(c3.config, config) + # Test that copies are not made + self.assertTrue(c1.config is config) + self.assertTrue(c2.config is config) + self.assertTrue(c3.config is config) + self.assertTrue(c1.config is c2.config) + self.assertTrue(c2.config is c3.config) + + def test_inheritance(self): + config = Config() + config.MyConfigurable.a = 2 + config.MyConfigurable.b = 2.0 + c1 = MyConfigurable(config=config) + c2 = MyConfigurable(config=c1.config) + self.assertEqual(c1.a, config.MyConfigurable.a) + self.assertEqual(c1.b, config.MyConfigurable.b) + self.assertEqual(c2.a, config.MyConfigurable.a) + self.assertEqual(c2.b, config.MyConfigurable.b) + + def test_parent(self): + config = Config() + config.Foo.a = 10 + config.Foo.b = "wow" + config.Bar.b = 'later' + config.Bar.c = 100.0 + f = Foo(config=config) + with expected_warnings(['`b` not recognized']): + b = Bar(config=f.config) + self.assertEqual(f.a, 10) + self.assertEqual(f.b, 'wow') + self.assertEqual(b.b, 'gotit') + self.assertEqual(b.c, 100.0) + + def test_override1(self): + config = Config() + config.MyConfigurable.a = 2 + config.MyConfigurable.b = 2.0 + c = MyConfigurable(a=3, config=config) + self.assertEqual(c.a, 3) + self.assertEqual(c.b, config.MyConfigurable.b) + self.assertEqual(c.c, 'no config') + + def test_override2(self): + config = Config() + config.Foo.a = 1 + config.Bar.b = 'or' # Up above b is config=False, so this won't do it. + config.Bar.c = 10.0 + with expected_warnings(['`b` not recognized']): + c = Bar(config=config) + self.assertEqual(c.a, config.Foo.a) + self.assertEqual(c.b, 'gotit') + self.assertEqual(c.c, config.Bar.c) + with expected_warnings(['`b` not recognized']): + c = Bar(a=2, b='and', c=20.0, config=config) + self.assertEqual(c.a, 2) + self.assertEqual(c.b, 'and') + self.assertEqual(c.c, 20.0) + + def test_help(self): + self.assertEqual(MyConfigurable.class_get_help(), mc_help) + self.assertEqual(Foo.class_get_help(), foo_help) + self.assertEqual(Bar.class_get_help(), bar_help) + + def test_help_inst(self): + inst = MyConfigurable(a=5, b=4) + self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst) + + def test_generated_config_enum_comments(self): + class MyConf(Configurable): + an_enum = Enum('Choice1 choice2'.split(), + help="Many choices.").tag(config=True) + + help_str = "Many choices." + enum_choices_str = "Choices: any of ['Choice1', 'choice2']" + rst_choices_str = "MyConf.an_enum : any of ``'Choice1'``|``'choice2'``" + or_none_str = "or None" + + cls_help = MyConf.class_get_help() + + self.assertIn(help_str, cls_help) + self.assertIn(enum_choices_str, cls_help) + self.assertNotIn(or_none_str, cls_help) + + cls_cfg = MyConf.class_config_section() + + self.assertIn(help_str, cls_cfg) + self.assertIn(enum_choices_str, cls_cfg) + self.assertNotIn(or_none_str, cls_help) + ## Check order of Help-msg <--> Choices sections + self.assertGreater(cls_cfg.index(enum_choices_str), + cls_cfg.index(help_str)) + + rst_help = MyConf.class_config_rst_doc() + + self.assertIn(help_str, rst_help) + self.assertIn(rst_choices_str, rst_help) + self.assertNotIn(or_none_str, rst_help) + + class MyConf2(Configurable): + an_enum = Enum('Choice1 choice2'.split(), + allow_none=True, + default_value='choice2', + help="Many choices.").tag(config=True) + + defaults_str = "Default: 'choice2'" + + cls2_msg = MyConf2.class_get_help() + + self.assertIn(help_str, cls2_msg) + self.assertIn(enum_choices_str, cls2_msg) + self.assertIn(or_none_str, cls2_msg) + self.assertIn(defaults_str, cls2_msg) + ## Check order of Default <--> Choices sections + self.assertGreater(cls2_msg.index(defaults_str), + cls2_msg.index(enum_choices_str)) + + cls2_cfg = MyConf2.class_config_section() + + self.assertIn(help_str, cls2_cfg) + self.assertIn(enum_choices_str, cls2_cfg) + self.assertIn(or_none_str, cls2_cfg) + self.assertIn(defaults_str, cls2_cfg) + ## Check order of Default <--> Choices sections + self.assertGreater(cls2_cfg.index(defaults_str), + cls2_cfg.index(enum_choices_str)) + + def test_generated_config_strenum_comments(self): + help_str = "Many choices." + defaults_str = "Default: 'choice2'" + or_none_str = "or None" + + class MyConf3(Configurable): + an_enum = CaselessStrEnum('Choice1 choice2'.split(), + allow_none=True, + default_value='choice2', + help="Many choices.").tag(config=True) + + enum_choices_str = ("Choices: any of ['Choice1', 'choice2'] " + "(case-insensitive)") + + cls3_msg = MyConf3.class_get_help() + + self.assertIn(help_str, cls3_msg) + self.assertIn(enum_choices_str, cls3_msg) + self.assertIn(or_none_str, cls3_msg) + self.assertIn(defaults_str, cls3_msg) + ## Check order of Default <--> Choices sections + self.assertGreater(cls3_msg.index(defaults_str), + cls3_msg.index(enum_choices_str)) + + cls3_cfg = MyConf3.class_config_section() + + self.assertIn(help_str, cls3_cfg) + self.assertIn(enum_choices_str, cls3_cfg) + self.assertIn(or_none_str, cls3_cfg) + self.assertIn(defaults_str, cls3_cfg) + ## Check order of Default <--> Choices sections + self.assertGreater(cls3_cfg.index(defaults_str), + cls3_cfg.index(enum_choices_str)) + + class MyConf4(Configurable): + an_enum = FuzzyEnum('Choice1 choice2'.split(), + allow_none=True, + default_value='choice2', + help="Many choices.").tag(config=True) + + enum_choices_str = ("Choices: any case-insensitive prefix " + "of ['Choice1', 'choice2']") + + cls4_msg = MyConf4.class_get_help() + + self.assertIn(help_str, cls4_msg) + self.assertIn(enum_choices_str, cls4_msg) + self.assertIn(or_none_str, cls4_msg) + self.assertIn(defaults_str, cls4_msg) + ## Check order of Default <--> Choices sections + self.assertGreater(cls4_msg.index(defaults_str), + cls4_msg.index(enum_choices_str)) + + cls4_cfg = MyConf4.class_config_section() + + self.assertIn(help_str, cls4_cfg) + self.assertIn(enum_choices_str, cls4_cfg) + self.assertIn(or_none_str, cls4_cfg) + self.assertIn(defaults_str, cls4_cfg) + ## Check order of Default <--> Choices sections + self.assertGreater(cls4_cfg.index(defaults_str), + cls4_cfg.index(enum_choices_str)) + + + +class TestSingletonConfigurable(TestCase): + + def test_instance(self): + class Foo(SingletonConfigurable): pass + self.assertEqual(Foo.initialized(), False) + foo = Foo.instance() + self.assertEqual(Foo.initialized(), True) + self.assertEqual(foo, Foo.instance()) + self.assertEqual(SingletonConfigurable._instance, None) + + def test_inheritance(self): + class Bar(SingletonConfigurable): pass + class Bam(Bar): pass + self.assertEqual(Bar.initialized(), False) + self.assertEqual(Bam.initialized(), False) + bam = Bam.instance() + self.assertEqual(Bar.initialized(), True) + self.assertEqual(Bam.initialized(), True) + self.assertEqual(bam, Bam._instance) + self.assertEqual(bam, Bar._instance) + self.assertEqual(SingletonConfigurable._instance, None) + + +class TestLoggingConfigurable(TestCase): + + def test_parent_logger(self): + class Parent(LoggingConfigurable): pass + class Child(LoggingConfigurable): pass + log = get_logger().getChild("TestLoggingConfigurable") + + parent = Parent(log=log) + child = Child(parent=parent) + self.assertEqual(parent.log, log) + self.assertEqual(child.log, log) + + parent = Parent() + child = Child(parent=parent, log=log) + self.assertEqual(parent.log, get_logger()) + self.assertEqual(child.log, log) + + def test_parent_not_logging_configurable(self): + class Parent(Configurable): pass + class Child(LoggingConfigurable): pass + parent = Parent() + child = Child(parent=parent) + self.assertEqual(child.log, get_logger()) + + +class MyParent(Configurable): + pass + +class MyParent2(MyParent): + pass + +class TestParentConfigurable(TestCase): + + def test_parent_config(self): + cfg = Config({ + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + } + }) + parent = MyParent(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) + + def test_parent_inheritance(self): + cfg = Config({ + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + } + }) + parent = MyParent2(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) + + def test_multi_parent(self): + cfg = Config({ + 'MyParent2' : { + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + }, + # this one shouldn't count + 'MyConfigurable' : { + 'b' : 3.0, + }, + } + }) + parent2 = MyParent2(config=cfg) + parent = MyParent(parent=parent2) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) + + def test_parent_priority(self): + cfg = Config({ + 'MyConfigurable' : { + 'b' : 2.0, + }, + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 3.0, + } + }, + 'MyParent2' : { + 'MyConfigurable' : { + 'b' : 4.0, + } + } + }) + parent = MyParent2(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b) + + def test_multi_parent_priority(self): + cfg = Config({ + 'MyConfigurable': { + 'b': 2.0, + }, + 'MyParent': { + 'MyConfigurable': { + 'b': 3.0, + }, + }, + 'MyParent2': { + 'MyConfigurable': { + 'b': 4.0, + }, + 'MyParent': { + 'MyConfigurable': { + 'b': 5.0, + }, + }, + }, + }) + parent2 = MyParent2(config=cfg) + parent = MyParent2(parent=parent2) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b) + +class Containers(Configurable): + lis = List().tag(config=True) + def _lis_default(self): + return [-1] + + s = Set().tag(config=True) + def _s_default(self): + return {'a'} + + d = Dict().tag(config=True) + def _d_default(self): + return {'a' : 'b'} + +class TestConfigContainers(TestCase): + def test_extend(self): + c = Config() + c.Containers.lis.extend(list(range(5))) + obj = Containers(config=c) + self.assertEqual(obj.lis, list(range(-1,5))) + + def test_insert(self): + c = Config() + c.Containers.lis.insert(0, 'a') + c.Containers.lis.insert(1, 'b') + obj = Containers(config=c) + self.assertEqual(obj.lis, ['a', 'b', -1]) + + def test_prepend(self): + c = Config() + c.Containers.lis.prepend([1,2]) + c.Containers.lis.prepend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [2,3,1,2,-1]) + + def test_prepend_extend(self): + c = Config() + c.Containers.lis.prepend([1,2]) + c.Containers.lis.extend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [1,2,-1,2,3]) + + def test_append_extend(self): + c = Config() + c.Containers.lis.append([1,2]) + c.Containers.lis.extend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [-1,[1,2],2,3]) + + def test_extend_append(self): + c = Config() + c.Containers.lis.extend([2,3]) + c.Containers.lis.append([1,2]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [-1,2,3,[1,2]]) + + def test_insert_extend(self): + c = Config() + c.Containers.lis.insert(0, 1) + c.Containers.lis.extend([2,3]) + obj = Containers(config=c) + self.assertEqual(obj.lis, [1,-1,2,3]) + + def test_set_update(self): + c = Config() + c.Containers.s.update({0,1,2}) + c.Containers.s.update({3}) + obj = Containers(config=c) + self.assertEqual(obj.s, {'a', 0, 1, 2, 3}) + + def test_dict_update(self): + c = Config() + c.Containers.d.update({'c' : 'd'}) + c.Containers.d.update({'e' : 'f'}) + obj = Containers(config=c) + self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'}) + + def test_update_twice(self): + c = Config() + c.MyConfigurable.a = 5 + m = MyConfigurable(config=c) + self.assertEqual(m.a, 5) + + c2 = Config() + c2.MyConfigurable.a = 10 + m.update_config(c2) + self.assertEqual(m.a, 10) + + c2.MyConfigurable.a = 15 + m.update_config(c2) + self.assertEqual(m.a, 15) + + def test_update_self(self): + """update_config with same config object still triggers config_changed""" + c = Config() + c.MyConfigurable.a = 5 + m = MyConfigurable(config=c) + self.assertEqual(m.a, 5) + c.MyConfigurable.a = 10 + m.update_config(c) + self.assertEqual(m.a, 10) + + def test_config_default(self): + class SomeSingleton(SingletonConfigurable): + pass + + class DefaultConfigurable(Configurable): + a = Integer().tag(config=True) + def _config_default(self): + if SomeSingleton.initialized(): + return SomeSingleton.instance().config + return Config() + + c = Config() + c.DefaultConfigurable.a = 5 + + d1 = DefaultConfigurable() + self.assertEqual(d1.a, 0) + + single = SomeSingleton.instance(config=c) + + d2 = DefaultConfigurable() + self.assertIs(d2.config, single.config) + self.assertEqual(d2.a, 5) + + def test_config_default_deprecated(self): + """Make sure configurables work even with the deprecations in traitlets""" + class SomeSingleton(SingletonConfigurable): + pass + + # reset deprecation limiter + _deprecations_shown.clear() + with expected_warnings([]): + class DefaultConfigurable(Configurable): + a = Integer(config=True) + def _config_default(self): + if SomeSingleton.initialized(): + return SomeSingleton.instance().config + return Config() + + c = Config() + c.DefaultConfigurable.a = 5 + + d1 = DefaultConfigurable() + self.assertEqual(d1.a, 0) + + single = SomeSingleton.instance(config=c) + + d2 = DefaultConfigurable() + self.assertIs(d2.config, single.config) + self.assertEqual(d2.a, 5) + + def test_kwarg_config_priority(self): + # a, c set in kwargs + # a, b set in config + # verify that: + # - kwargs are set before config + # - kwargs have priority over config + class A(Configurable): + a = Unicode('default', config=True) + b = Unicode('default', config=True) + c = Unicode('default', config=True) + c_during_config = Unicode('never') + @validate('b') + def _record_c(self, proposal): + # setting b from config records c's value at the time + self.c_during_config = self.c + return proposal.value + + cfg = Config() + cfg.A.a = 'a-config' + cfg.A.b = 'b-config' + obj = A(a='a-kwarg', c='c-kwarg', config=cfg) + assert obj.a == 'a-kwarg' + assert obj.b == 'b-config' + assert obj.c == 'c-kwarg' + assert obj.c_during_config == 'c-kwarg' + + +class TestLogger(TestCase): + + class A(LoggingConfigurable): + foo = Integer(config=True) + bar = Integer(config=True) + baz = Integer(config=True) + + @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs') + def test_warn_match(self): + logger = logging.getLogger('test_warn_match') + cfg = Config({'A': {'bat': 5}}) + with self.assertLogs(logger, logging.WARNING) as captured: + TestLogger.A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + self.assertIn('Did you mean one of: `bar, baz`?', output) + self.assertIn('Config option `bat` not recognized by `A`.', output) + + cfg = Config({'A': {'fool': 5}}) + with self.assertLogs(logger, logging.WARNING) as captured: + TestLogger.A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + self.assertIn('Config option `fool` not recognized by `A`.', output) + self.assertIn('Did you mean `foo`?', output) + + cfg = Config({'A': {'totally_wrong': 5}}) + with self.assertLogs(logger, logging.WARNING) as captured: + TestLogger.A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + self.assertIn('Config option `totally_wrong` not recognized by `A`.', output) + self.assertNotIn('Did you mean', output) + + def test_logger_adapter(self): + logger = logging.getLogger("test_logger_adapter") + adapter = logging.LoggerAdapter(logger, {"key": "adapted"}) + + with self.assertLogs(logger, logging.INFO) as captured: + app = Application(log=adapter, log_level=logging.INFO) + app.log_format = "%(key)s %(message)s" + app.log.info("test message") + + output = "\n".join(captured.output) + assert "adapted test message" in output diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py b/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py index b3fc580223..103eeeff6d 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py +++ b/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py @@ -1,754 +1,754 @@ -# encoding: utf-8 -"""Tests for traitlets.config.loader""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import copy -import os -import pickle -from itertools import chain -from tempfile import mkstemp -from unittest import TestCase - -import pytest - -from traitlets.config.loader import ( - Config, - LazyConfigValue, - PyFileConfigLoader, - JSONFileConfigLoader, - KeyValueConfigLoader, - ArgParseConfigLoader, - KVArgParseConfigLoader, -) -from traitlets import ( - List, - Tuple, - Dict, - Unicode, - Integer, -) -from traitlets.config import Configurable - - -pyfile = """ -c = get_config() -c.a=10 -c.b=20 -c.Foo.Bar.value=10 -c.Foo.Bam.value=list(range(10)) -c.D.C.value='hi there' -""" - -json1file = """ -{ - "version": 1, - "a": 10, - "b": 20, - "Foo": { - "Bam": { - "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] - }, - "Bar": { - "value": 10 - } - }, - "D": { - "C": { - "value": "hi there" - } - } -} -""" - -# should not load -json2file = """ -{ - "version": 2 -} -""" - -import logging -log = logging.getLogger('devnull') -log.setLevel(0) - - -class TestFileCL(TestCase): - def _check_conf(self, config): - self.assertEqual(config.a, 10) - self.assertEqual(config.b, 20) - self.assertEqual(config.Foo.Bar.value, 10) - self.assertEqual(config.Foo.Bam.value, list(range(10))) - self.assertEqual(config.D.C.value, 'hi there') - - def test_python(self): - fd, fname = mkstemp('.py', prefix='μnïcø∂e') - f = os.fdopen(fd, 'w') - f.write(pyfile) - f.close() - # Unlink the file - cl = PyFileConfigLoader(fname, log=log) - config = cl.load_config() - self._check_conf(config) - - def test_json(self): - fd, fname = mkstemp('.json', prefix='μnïcø∂e') - f = os.fdopen(fd, 'w') - f.write(json1file) - f.close() - # Unlink the file - cl = JSONFileConfigLoader(fname, log=log) - config = cl.load_config() - self._check_conf(config) - - def test_context_manager(self): - - fd, fname = mkstemp('.json', prefix='μnïcø∂e') - f = os.fdopen(fd, 'w') - f.write('{}') - f.close() - - cl = JSONFileConfigLoader(fname, log=log) - - value = 'context_manager' - - with cl as c: - c.MyAttr.value = value - - self.assertEqual(cl.config.MyAttr.value, value) - - # check that another loader does see the change - cl2 = JSONFileConfigLoader(fname, log=log) - self.assertEqual(cl.config.MyAttr.value, value) - - def test_json_context_bad_write(self): - fd, fname = mkstemp('.json', prefix='μnïcø∂e') - f = os.fdopen(fd, 'w') - f.write('{}') - f.close() - - with JSONFileConfigLoader(fname, log=log) as config: - config.A.b = 1 - - with self.assertRaises(TypeError): - with JSONFileConfigLoader(fname, log=log) as config: - config.A.cant_json = lambda x: x - - loader = JSONFileConfigLoader(fname, log=log) - cfg = loader.load_config() - assert cfg.A.b == 1 - assert 'cant_json' not in cfg.A - - def test_collision(self): - a = Config() - b = Config() - self.assertEqual(a.collisions(b), {}) - a.A.trait1 = 1 - b.A.trait2 = 2 - self.assertEqual(a.collisions(b), {}) - b.A.trait1 = 1 - self.assertEqual(a.collisions(b), {}) - b.A.trait1 = 0 - self.assertEqual(a.collisions(b), { - 'A': { - 'trait1': "1 ignored, using 0", - } - }) - self.assertEqual(b.collisions(a), { - 'A': { - 'trait1': "0 ignored, using 1", - } - }) - a.A.trait2 = 3 - self.assertEqual(b.collisions(a), { - 'A': { - 'trait1': "0 ignored, using 1", - 'trait2': "2 ignored, using 3", - } - }) - - def test_v2raise(self): - fd, fname = mkstemp('.json', prefix='μnïcø∂e') - f = os.fdopen(fd, 'w') - f.write(json2file) - f.close() - # Unlink the file - cl = JSONFileConfigLoader(fname, log=log) - with self.assertRaises(ValueError): - cl.load_config() - - -def _parse_int_or_str(v): - try: - return int(v) - except: - return str(v) - - -class MyLoader1(ArgParseConfigLoader): - def _add_arguments(self, aliases=None, flags=None, classes=None): - p = self.parser - p.add_argument('-f', '--foo', dest='Global.foo', type=str) - p.add_argument('-b', dest='MyClass.bar', type=int) - p.add_argument('-n', dest='n', action='store_true') - p.add_argument('Global.bam', type=str) - p.add_argument('--list1', action='append', type=_parse_int_or_str) - p.add_argument('--list2', nargs='+', type=int) - - -class MyLoader2(ArgParseConfigLoader): - def _add_arguments(self, aliases=None, flags=None, classes=None): - subparsers = self.parser.add_subparsers(dest='subparser_name') - subparser1 = subparsers.add_parser('1') - subparser1.add_argument('-x', dest='Global.x') - subparser2 = subparsers.add_parser('2') - subparser2.add_argument('y') - - -class TestArgParseCL(TestCase): - - def test_basic(self): - cl = MyLoader1() - config = cl.load_config('-f hi -b 10 -n wow'.split()) - self.assertEqual(config.Global.foo, 'hi') - self.assertEqual(config.MyClass.bar, 10) - self.assertEqual(config.n, True) - self.assertEqual(config.Global.bam, 'wow') - config = cl.load_config(['wow']) - self.assertEqual(list(config.keys()), ['Global']) - self.assertEqual(list(config.Global.keys()), ['bam']) - self.assertEqual(config.Global.bam, 'wow') - - def test_add_arguments(self): - cl = MyLoader2() - config = cl.load_config('2 frobble'.split()) - self.assertEqual(config.subparser_name, '2') - self.assertEqual(config.y, 'frobble') - config = cl.load_config('1 -x frobble'.split()) - self.assertEqual(config.subparser_name, '1') - self.assertEqual(config.Global.x, 'frobble') - - def test_argv(self): - cl = MyLoader1(argv='-f hi -b 10 -n wow'.split()) - config = cl.load_config() - self.assertEqual(config.Global.foo, 'hi') - self.assertEqual(config.MyClass.bar, 10) - self.assertEqual(config.n, True) - self.assertEqual(config.Global.bam, 'wow') - - def test_list_args(self): - cl = MyLoader1() - config = cl.load_config('--list1 1 wow --list2 1 2 3 --list1 B'.split()) - self.assertEqual(list(config.Global.keys()), ['bam']) - self.assertEqual(config.Global.bam, 'wow') - self.assertEqual(config.list1, [1, 'B']) - self.assertEqual(config.list2, [1, 2, 3]) - - -class C(Configurable): - str_trait = Unicode(config=True) - int_trait = Integer(config=True) - list_trait = List(config=True) - list_of_ints = List(Integer(), config=True) - dict_trait = Dict(config=True) - dict_of_ints = Dict( - key_trait=Integer(), - value_trait=Integer(), - config=True, - ) - dict_multi = Dict( - key_trait=Unicode(), - per_key_traits={ - "int": Integer(), - "str": Unicode(), - }, - config=True, - ) - - -class TestKeyValueCL(TestCase): - klass = KeyValueConfigLoader - - def test_eval(self): - cl = self.klass(log=log) - config = cl.load_config('--C.str_trait=all --C.int_trait=5 --C.list_trait=["hello",5]'.split()) - c = C(config=config) - assert c.str_trait == 'all' - assert c.int_trait == 5 - assert c.list_trait == ["hello", 5] - - def test_basic(self): - cl = self.klass(log=log) - argv = [ '--' + s[2:] for s in pyfile.split('\n') if s.startswith('c.') ] - config = cl.load_config(argv) - assert config.a == '10' - assert config.b == '20' - assert config.Foo.Bar.value == '10' - # non-literal expressions are not evaluated - self.assertEqual(config.Foo.Bam.value, 'list(range(10))') - self.assertEqual(Unicode().from_string(config.D.C.value), 'hi there') - - def test_expanduser(self): - cl = self.klass(log=log) - argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"'] - config = cl.load_config(argv) - u = Unicode() - self.assertEqual(u.from_string(config.a), os.path.expanduser('~/1/2/3')) - self.assertEqual(u.from_string(config.b), os.path.expanduser('~')) - self.assertEqual(u.from_string(config.c), os.path.expanduser('~/')) - self.assertEqual(u.from_string(config.d), '~/') - - def test_extra_args(self): - cl = self.klass(log=log) - config = cl.load_config(['--a=5', 'b', 'd', '--c=10']) - self.assertEqual(cl.extra_args, ['b', 'd']) - assert config.a == '5' - assert config.c == '10' - config = cl.load_config(['--', '--a=5', '--c=10']) - self.assertEqual(cl.extra_args, ['--a=5', '--c=10']) - - cl = self.klass(log=log) - config = cl.load_config(['extra', '--a=2', '--c=1', '--', '-']) - self.assertEqual(cl.extra_args, ['extra', '-']) - - def test_unicode_args(self): - cl = self.klass(log=log) - argv = ['--a=épsîlön'] - config = cl.load_config(argv) - print(config, cl.extra_args) - self.assertEqual(config.a, 'épsîlön') - - def test_list_append(self): - cl = self.klass(log=log) - argv = ["--C.list_trait", "x", "--C.list_trait", "y"] - config = cl.load_config(argv) - assert config.C.list_trait == ["x", "y"] - c = C(config=config) - assert c.list_trait == ["x", "y"] - - def test_list_single_item(self): - cl = self.klass(log=log) - argv = ["--C.list_trait", "x"] - config = cl.load_config(argv) - c = C(config=config) - assert c.list_trait == ["x"] - - def test_dict(self): - cl = self.klass(log=log) - argv = ["--C.dict_trait", "x=5", "--C.dict_trait", "y=10"] - config = cl.load_config(argv) - c = C(config=config) - assert c.dict_trait == {"x": "5", "y": "10"} - - def test_dict_key_traits(self): - cl = self.klass(log=log) - argv = ["--C.dict_of_ints", "1=2", "--C.dict_of_ints", "3=4"] - config = cl.load_config(argv) - c = C(config=config) - assert c.dict_of_ints == {1: 2, 3: 4} - - -class CBase(Configurable): - a = List().tag(config=True) - b = List(Integer()).tag(config=True, multiplicity='*') - c = List().tag(config=True, multiplicity='append') - adict = Dict().tag(config=True) - - -class CSub(CBase): - d = Tuple().tag(config=True) - e = Tuple().tag(config=True, multiplicity='+') - bdict = Dict().tag(config=True, multiplicity='*') - - -class TestArgParseKVCL(TestKeyValueCL): - klass = KVArgParseConfigLoader - - def test_no_cast_literals(self): - cl = self.klass(log=log) - # test ipython -c 1 doesn't cast to int - argv = ["-c", "1"] - config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run")) - assert config.IPython.command_to_run == "1" - - def test_int_literals(self): - cl = self.klass(log=log) - # test ipython -c 1 doesn't cast to int - argv = ["-c", "1"] - config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run")) - assert config.IPython.command_to_run == "1" - - def test_unicode_alias(self): - cl = self.klass(log=log) - argv = ['--a=épsîlön'] - config = cl.load_config(argv, aliases=dict(a='A.a')) - print(dict(config)) - print(cl.extra_args) - print(cl.aliases) - self.assertEqual(config.A.a, 'épsîlön') - - def test_expanduser2(self): - cl = self.klass(log=log) - argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"] - config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b')) - - class A(Configurable): - a = Unicode(config=True) - b = Unicode(config=True) - - a = A(config=config) - self.assertEqual(a.a, os.path.expanduser('~/1/2/3')) - self.assertEqual(a.b, '~/1/2/3') - - def test_eval(self): - cl = self.klass(log=log) - argv = ['-c', 'a=5'] - config = cl.load_config(argv, aliases=dict(c='A.c')) - self.assertEqual(config.A.c, "a=5") - - def test_seq_traits(self): - cl = self.klass(log=log, classes=(CBase, CSub)) - aliases = {'a3': 'CBase.c', 'a5': 'CSub.e'} - argv = ("--CBase.a A --CBase.a 2 --CBase.b 1 --CBase.b 3 --a3 AA --CBase.c BB " - "--CSub.d 1 --CSub.d BBB --CSub.e 1 --CSub.e=bcd a b c ").split() - config = cl.load_config(argv, aliases=aliases) - assert cl.extra_args == ["a", "b", "c"] - assert config.CBase.a == ['A', '2'] - assert config.CBase.b == [1, 3] - self.assertEqual(config.CBase.c, ['AA', 'BB']) - - assert config.CSub.d == ('1', 'BBB') - assert config.CSub.e == ('1', 'bcd') - - def test_seq_traits_single_empty_string(self): - cl = self.klass(log=log, classes=(CBase, )) - aliases = {'seqopt': 'CBase.c'} - argv = ['--seqopt', ''] - config = cl.load_config(argv, aliases=aliases) - self.assertEqual(config.CBase.c, ['']) - - def test_dict_traits(self): - cl = self.klass(log=log, classes=(CBase, CSub)) - aliases = {'D': 'CBase.adict', 'E': 'CSub.bdict'} - argv = ["-D", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "-E", "22=222"] - config = cl.load_config(argv, aliases=aliases) - c = CSub(config=config) - assert c.adict == {'k1': 'v1', 'k2': '2', 'k3': 'v 3'} - assert c.bdict == {'k': 'v', '22': '222'} - - def test_mixed_seq_positional(self): - aliases = {"c": "Class.trait"} - cl = self.klass(log=log, aliases=aliases) - assignments = [("-c", "1"), ("--Class.trait=2",), ("--c=3",), ("--Class.trait", "4")] - positionals = ["a", "b", "c"] - # test with positionals at any index - for idx in range(len(assignments) + 1): - argv_parts = assignments[:] - argv_parts[idx:idx] = (positionals,) - argv = list(chain(*argv_parts)) - - config = cl.load_config(argv) - assert config.Class.trait == ["1", "2", "3", "4"] - assert cl.extra_args == ["a", "b", "c"] - - def test_split_positional(self): - """Splitting positionals across flags is no longer allowed in traitlets 5""" - cl = self.klass(log=log) - argv = ["a", "--Class.trait=5", "b"] - with pytest.raises(SystemExit): - cl.load_config(argv) - - -class TestConfig(TestCase): - - def test_setget(self): - c = Config() - c.a = 10 - self.assertEqual(c.a, 10) - self.assertEqual('b' in c, False) - - def test_auto_section(self): - c = Config() - self.assertNotIn('A', c) - assert not c._has_section('A') - A = c.A - A.foo = 'hi there' - self.assertIn('A', c) - assert c._has_section('A') - self.assertEqual(c.A.foo, 'hi there') - del c.A - self.assertEqual(c.A, Config()) - - def test_merge_doesnt_exist(self): - c1 = Config() - c2 = Config() - c2.bar = 10 - c2.Foo.bar = 10 - c1.merge(c2) - self.assertEqual(c1.Foo.bar, 10) - self.assertEqual(c1.bar, 10) - c2.Bar.bar = 10 - c1.merge(c2) - self.assertEqual(c1.Bar.bar, 10) - - def test_merge_exists(self): - c1 = Config() - c2 = Config() - c1.Foo.bar = 10 - c1.Foo.bam = 30 - c2.Foo.bar = 20 - c2.Foo.wow = 40 - c1.merge(c2) - self.assertEqual(c1.Foo.bam, 30) - self.assertEqual(c1.Foo.bar, 20) - self.assertEqual(c1.Foo.wow, 40) - c2.Foo.Bam.bam = 10 - c1.merge(c2) - self.assertEqual(c1.Foo.Bam.bam, 10) - - def test_deepcopy(self): - c1 = Config() - c1.Foo.bar = 10 - c1.Foo.bam = 30 - c1.a = 'asdf' - c1.b = range(10) - c1.Test.logger = logging.Logger('test') - c1.Test.get_logger = logging.getLogger('test') - c2 = copy.deepcopy(c1) - self.assertEqual(c1, c2) - self.assertTrue(c1 is not c2) - self.assertTrue(c1.Foo is not c2.Foo) - self.assertTrue(c1.Test is not c2.Test) - self.assertTrue(c1.Test.logger is c2.Test.logger) - self.assertTrue(c1.Test.get_logger is c2.Test.get_logger) - - def test_builtin(self): - c1 = Config() - c1.format = "json" - - def test_fromdict(self): - c1 = Config({'Foo' : {'bar' : 1}}) - self.assertEqual(c1.Foo.__class__, Config) - self.assertEqual(c1.Foo.bar, 1) - - def test_fromdictmerge(self): - c1 = Config() - c2 = Config({'Foo' : {'bar' : 1}}) - c1.merge(c2) - self.assertEqual(c1.Foo.__class__, Config) - self.assertEqual(c1.Foo.bar, 1) - - def test_fromdictmerge2(self): - c1 = Config({'Foo' : {'baz' : 2}}) - c2 = Config({'Foo' : {'bar' : 1}}) - c1.merge(c2) - self.assertEqual(c1.Foo.__class__, Config) - self.assertEqual(c1.Foo.bar, 1) - self.assertEqual(c1.Foo.baz, 2) - self.assertNotIn('baz', c2.Foo) - - def test_contains(self): - c1 = Config({'Foo' : {'baz' : 2}}) - c2 = Config({'Foo' : {'bar' : 1}}) - self.assertIn('Foo', c1) - self.assertIn('Foo.baz', c1) - self.assertIn('Foo.bar', c2) - self.assertNotIn('Foo.bar', c1) - - def test_pickle_config(self): - cfg = Config() - cfg.Foo.bar = 1 - pcfg = pickle.dumps(cfg) - cfg2 = pickle.loads(pcfg) - self.assertEqual(cfg2, cfg) - - def test_getattr_section(self): - cfg = Config() - self.assertNotIn('Foo', cfg) - Foo = cfg.Foo - assert isinstance(Foo, Config) - self.assertIn('Foo', cfg) - - def test_getitem_section(self): - cfg = Config() - self.assertNotIn('Foo', cfg) - Foo = cfg['Foo'] - assert isinstance(Foo, Config) - self.assertIn('Foo', cfg) - - def test_getattr_not_section(self): - cfg = Config() - self.assertNotIn('foo', cfg) - foo = cfg.foo - assert isinstance(foo, LazyConfigValue) - self.assertIn('foo', cfg) - - def test_getattr_private_missing(self): - cfg = Config() - self.assertNotIn('_repr_html_', cfg) - with self.assertRaises(AttributeError): - _ = cfg._repr_html_ - self.assertNotIn('_repr_html_', cfg) - self.assertEqual(len(cfg), 0) - - def test_lazy_config_repr(self): - cfg = Config() - cfg.Class.lazy.append(1) - cfg_repr = repr(cfg) - assert '<LazyConfigValue' in cfg_repr - assert "extend" in cfg_repr - assert " [1]}>" in cfg_repr - assert 'value=' not in cfg_repr - cfg.Class.lazy.get_value([0]) - repr2 = repr(cfg) - assert repr([0,1]) in repr2 - assert 'value=' in repr2 - - - def test_getitem_not_section(self): - cfg = Config() - self.assertNotIn('foo', cfg) - foo = cfg['foo'] - assert isinstance(foo, LazyConfigValue) - self.assertIn('foo', cfg) - - def test_merge_no_copies(self): - c = Config() - c2 = Config() - c2.Foo.trait = [] - c.merge(c2) - c2.Foo.trait.append(1) - self.assertIs(c.Foo, c2.Foo) - self.assertEqual(c.Foo.trait, [1]) - self.assertEqual(c2.Foo.trait, [1]) - - - def test_merge_multi_lazy(self): - """ - With multiple config files (systemwide and users), we want compounding. - - If systemwide overwirte and user append, we want both in the right - order. - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait = [1] - c2.Foo.trait.append(2) - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait, [1,2] ) - - - - def test_merge_multi_lazyII(self): - """ - With multiple config files (systemwide and users), we want compounding. - - If both are lazy we still want a lazy config. - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait.append(1) - c2.Foo.trait.append(2) - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait._extend, [1,2] ) - - def test_merge_multi_lazy_III(self): - """ - With multiple config files (systemwide and users), we want compounding. - - Prepend should prepend in the right order. - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait = [1] - c2.Foo.trait.prepend([0]) - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait, [0, 1] ) - - def test_merge_multi_lazy_IV(self): - """ - With multiple config files (systemwide and users), we want compounding. - - Both prepending should be lazy - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait.prepend([1]) - c2.Foo.trait.prepend([0]) - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait._prepend, [0, 1]) - - def test_merge_multi_lazy_update_I(self): - """ - With multiple config files (systemwide and users), we want compounding. - - dict update shoudl be in the right order. - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait = {"a": 1, "z": 26} - c2.Foo.trait.update({"a": 0, "b": 1}) - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait, {"a": 0, "b": 1, "z": 26}) - - def test_merge_multi_lazy_update_II(self): - """ - With multiple config files (systemwide and users), we want compounding. - - Later dict overwrite lazyness - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait.update({"a": 0, "b": 1}) - c2.Foo.trait = {"a": 1, "z": 26} - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait, {"a": 1, "z": 26}) - - def test_merge_multi_lazy_update_III(self): - """ - With multiple config files (systemwide and users), we want compounding. - - Later dict overwrite lazyness - """ - c1 = Config() - c2 = Config() - - c1.Foo.trait.update({"a": 0, "b": 1}) - c2.Foo.trait.update({"a": 1, "z": 26}) - - c = Config() - c.merge(c1) - c.merge(c2) - - self.assertEqual(c.Foo.trait._update, {"a": 1, "z": 26, "b": 1}) +# encoding: utf-8 +"""Tests for traitlets.config.loader""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import copy +import os +import pickle +from itertools import chain +from tempfile import mkstemp +from unittest import TestCase + +import pytest + +from traitlets.config.loader import ( + Config, + LazyConfigValue, + PyFileConfigLoader, + JSONFileConfigLoader, + KeyValueConfigLoader, + ArgParseConfigLoader, + KVArgParseConfigLoader, +) +from traitlets import ( + List, + Tuple, + Dict, + Unicode, + Integer, +) +from traitlets.config import Configurable + + +pyfile = """ +c = get_config() +c.a=10 +c.b=20 +c.Foo.Bar.value=10 +c.Foo.Bam.value=list(range(10)) +c.D.C.value='hi there' +""" + +json1file = """ +{ + "version": 1, + "a": 10, + "b": 20, + "Foo": { + "Bam": { + "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + }, + "Bar": { + "value": 10 + } + }, + "D": { + "C": { + "value": "hi there" + } + } +} +""" + +# should not load +json2file = """ +{ + "version": 2 +} +""" + +import logging +log = logging.getLogger('devnull') +log.setLevel(0) + + +class TestFileCL(TestCase): + def _check_conf(self, config): + self.assertEqual(config.a, 10) + self.assertEqual(config.b, 20) + self.assertEqual(config.Foo.Bar.value, 10) + self.assertEqual(config.Foo.Bam.value, list(range(10))) + self.assertEqual(config.D.C.value, 'hi there') + + def test_python(self): + fd, fname = mkstemp('.py', prefix='μnïcø∂e') + f = os.fdopen(fd, 'w') + f.write(pyfile) + f.close() + # Unlink the file + cl = PyFileConfigLoader(fname, log=log) + config = cl.load_config() + self._check_conf(config) + + def test_json(self): + fd, fname = mkstemp('.json', prefix='μnïcø∂e') + f = os.fdopen(fd, 'w') + f.write(json1file) + f.close() + # Unlink the file + cl = JSONFileConfigLoader(fname, log=log) + config = cl.load_config() + self._check_conf(config) + + def test_context_manager(self): + + fd, fname = mkstemp('.json', prefix='μnïcø∂e') + f = os.fdopen(fd, 'w') + f.write('{}') + f.close() + + cl = JSONFileConfigLoader(fname, log=log) + + value = 'context_manager' + + with cl as c: + c.MyAttr.value = value + + self.assertEqual(cl.config.MyAttr.value, value) + + # check that another loader does see the change + cl2 = JSONFileConfigLoader(fname, log=log) + self.assertEqual(cl.config.MyAttr.value, value) + + def test_json_context_bad_write(self): + fd, fname = mkstemp('.json', prefix='μnïcø∂e') + f = os.fdopen(fd, 'w') + f.write('{}') + f.close() + + with JSONFileConfigLoader(fname, log=log) as config: + config.A.b = 1 + + with self.assertRaises(TypeError): + with JSONFileConfigLoader(fname, log=log) as config: + config.A.cant_json = lambda x: x + + loader = JSONFileConfigLoader(fname, log=log) + cfg = loader.load_config() + assert cfg.A.b == 1 + assert 'cant_json' not in cfg.A + + def test_collision(self): + a = Config() + b = Config() + self.assertEqual(a.collisions(b), {}) + a.A.trait1 = 1 + b.A.trait2 = 2 + self.assertEqual(a.collisions(b), {}) + b.A.trait1 = 1 + self.assertEqual(a.collisions(b), {}) + b.A.trait1 = 0 + self.assertEqual(a.collisions(b), { + 'A': { + 'trait1': "1 ignored, using 0", + } + }) + self.assertEqual(b.collisions(a), { + 'A': { + 'trait1': "0 ignored, using 1", + } + }) + a.A.trait2 = 3 + self.assertEqual(b.collisions(a), { + 'A': { + 'trait1': "0 ignored, using 1", + 'trait2': "2 ignored, using 3", + } + }) + + def test_v2raise(self): + fd, fname = mkstemp('.json', prefix='μnïcø∂e') + f = os.fdopen(fd, 'w') + f.write(json2file) + f.close() + # Unlink the file + cl = JSONFileConfigLoader(fname, log=log) + with self.assertRaises(ValueError): + cl.load_config() + + +def _parse_int_or_str(v): + try: + return int(v) + except: + return str(v) + + +class MyLoader1(ArgParseConfigLoader): + def _add_arguments(self, aliases=None, flags=None, classes=None): + p = self.parser + p.add_argument('-f', '--foo', dest='Global.foo', type=str) + p.add_argument('-b', dest='MyClass.bar', type=int) + p.add_argument('-n', dest='n', action='store_true') + p.add_argument('Global.bam', type=str) + p.add_argument('--list1', action='append', type=_parse_int_or_str) + p.add_argument('--list2', nargs='+', type=int) + + +class MyLoader2(ArgParseConfigLoader): + def _add_arguments(self, aliases=None, flags=None, classes=None): + subparsers = self.parser.add_subparsers(dest='subparser_name') + subparser1 = subparsers.add_parser('1') + subparser1.add_argument('-x', dest='Global.x') + subparser2 = subparsers.add_parser('2') + subparser2.add_argument('y') + + +class TestArgParseCL(TestCase): + + def test_basic(self): + cl = MyLoader1() + config = cl.load_config('-f hi -b 10 -n wow'.split()) + self.assertEqual(config.Global.foo, 'hi') + self.assertEqual(config.MyClass.bar, 10) + self.assertEqual(config.n, True) + self.assertEqual(config.Global.bam, 'wow') + config = cl.load_config(['wow']) + self.assertEqual(list(config.keys()), ['Global']) + self.assertEqual(list(config.Global.keys()), ['bam']) + self.assertEqual(config.Global.bam, 'wow') + + def test_add_arguments(self): + cl = MyLoader2() + config = cl.load_config('2 frobble'.split()) + self.assertEqual(config.subparser_name, '2') + self.assertEqual(config.y, 'frobble') + config = cl.load_config('1 -x frobble'.split()) + self.assertEqual(config.subparser_name, '1') + self.assertEqual(config.Global.x, 'frobble') + + def test_argv(self): + cl = MyLoader1(argv='-f hi -b 10 -n wow'.split()) + config = cl.load_config() + self.assertEqual(config.Global.foo, 'hi') + self.assertEqual(config.MyClass.bar, 10) + self.assertEqual(config.n, True) + self.assertEqual(config.Global.bam, 'wow') + + def test_list_args(self): + cl = MyLoader1() + config = cl.load_config('--list1 1 wow --list2 1 2 3 --list1 B'.split()) + self.assertEqual(list(config.Global.keys()), ['bam']) + self.assertEqual(config.Global.bam, 'wow') + self.assertEqual(config.list1, [1, 'B']) + self.assertEqual(config.list2, [1, 2, 3]) + + +class C(Configurable): + str_trait = Unicode(config=True) + int_trait = Integer(config=True) + list_trait = List(config=True) + list_of_ints = List(Integer(), config=True) + dict_trait = Dict(config=True) + dict_of_ints = Dict( + key_trait=Integer(), + value_trait=Integer(), + config=True, + ) + dict_multi = Dict( + key_trait=Unicode(), + per_key_traits={ + "int": Integer(), + "str": Unicode(), + }, + config=True, + ) + + +class TestKeyValueCL(TestCase): + klass = KeyValueConfigLoader + + def test_eval(self): + cl = self.klass(log=log) + config = cl.load_config('--C.str_trait=all --C.int_trait=5 --C.list_trait=["hello",5]'.split()) + c = C(config=config) + assert c.str_trait == 'all' + assert c.int_trait == 5 + assert c.list_trait == ["hello", 5] + + def test_basic(self): + cl = self.klass(log=log) + argv = [ '--' + s[2:] for s in pyfile.split('\n') if s.startswith('c.') ] + config = cl.load_config(argv) + assert config.a == '10' + assert config.b == '20' + assert config.Foo.Bar.value == '10' + # non-literal expressions are not evaluated + self.assertEqual(config.Foo.Bam.value, 'list(range(10))') + self.assertEqual(Unicode().from_string(config.D.C.value), 'hi there') + + def test_expanduser(self): + cl = self.klass(log=log) + argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"'] + config = cl.load_config(argv) + u = Unicode() + self.assertEqual(u.from_string(config.a), os.path.expanduser('~/1/2/3')) + self.assertEqual(u.from_string(config.b), os.path.expanduser('~')) + self.assertEqual(u.from_string(config.c), os.path.expanduser('~/')) + self.assertEqual(u.from_string(config.d), '~/') + + def test_extra_args(self): + cl = self.klass(log=log) + config = cl.load_config(['--a=5', 'b', 'd', '--c=10']) + self.assertEqual(cl.extra_args, ['b', 'd']) + assert config.a == '5' + assert config.c == '10' + config = cl.load_config(['--', '--a=5', '--c=10']) + self.assertEqual(cl.extra_args, ['--a=5', '--c=10']) + + cl = self.klass(log=log) + config = cl.load_config(['extra', '--a=2', '--c=1', '--', '-']) + self.assertEqual(cl.extra_args, ['extra', '-']) + + def test_unicode_args(self): + cl = self.klass(log=log) + argv = ['--a=épsîlön'] + config = cl.load_config(argv) + print(config, cl.extra_args) + self.assertEqual(config.a, 'épsîlön') + + def test_list_append(self): + cl = self.klass(log=log) + argv = ["--C.list_trait", "x", "--C.list_trait", "y"] + config = cl.load_config(argv) + assert config.C.list_trait == ["x", "y"] + c = C(config=config) + assert c.list_trait == ["x", "y"] + + def test_list_single_item(self): + cl = self.klass(log=log) + argv = ["--C.list_trait", "x"] + config = cl.load_config(argv) + c = C(config=config) + assert c.list_trait == ["x"] + + def test_dict(self): + cl = self.klass(log=log) + argv = ["--C.dict_trait", "x=5", "--C.dict_trait", "y=10"] + config = cl.load_config(argv) + c = C(config=config) + assert c.dict_trait == {"x": "5", "y": "10"} + + def test_dict_key_traits(self): + cl = self.klass(log=log) + argv = ["--C.dict_of_ints", "1=2", "--C.dict_of_ints", "3=4"] + config = cl.load_config(argv) + c = C(config=config) + assert c.dict_of_ints == {1: 2, 3: 4} + + +class CBase(Configurable): + a = List().tag(config=True) + b = List(Integer()).tag(config=True, multiplicity='*') + c = List().tag(config=True, multiplicity='append') + adict = Dict().tag(config=True) + + +class CSub(CBase): + d = Tuple().tag(config=True) + e = Tuple().tag(config=True, multiplicity='+') + bdict = Dict().tag(config=True, multiplicity='*') + + +class TestArgParseKVCL(TestKeyValueCL): + klass = KVArgParseConfigLoader + + def test_no_cast_literals(self): + cl = self.klass(log=log) + # test ipython -c 1 doesn't cast to int + argv = ["-c", "1"] + config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run")) + assert config.IPython.command_to_run == "1" + + def test_int_literals(self): + cl = self.klass(log=log) + # test ipython -c 1 doesn't cast to int + argv = ["-c", "1"] + config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run")) + assert config.IPython.command_to_run == "1" + + def test_unicode_alias(self): + cl = self.klass(log=log) + argv = ['--a=épsîlön'] + config = cl.load_config(argv, aliases=dict(a='A.a')) + print(dict(config)) + print(cl.extra_args) + print(cl.aliases) + self.assertEqual(config.A.a, 'épsîlön') + + def test_expanduser2(self): + cl = self.klass(log=log) + argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"] + config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b')) + + class A(Configurable): + a = Unicode(config=True) + b = Unicode(config=True) + + a = A(config=config) + self.assertEqual(a.a, os.path.expanduser('~/1/2/3')) + self.assertEqual(a.b, '~/1/2/3') + + def test_eval(self): + cl = self.klass(log=log) + argv = ['-c', 'a=5'] + config = cl.load_config(argv, aliases=dict(c='A.c')) + self.assertEqual(config.A.c, "a=5") + + def test_seq_traits(self): + cl = self.klass(log=log, classes=(CBase, CSub)) + aliases = {'a3': 'CBase.c', 'a5': 'CSub.e'} + argv = ("--CBase.a A --CBase.a 2 --CBase.b 1 --CBase.b 3 --a3 AA --CBase.c BB " + "--CSub.d 1 --CSub.d BBB --CSub.e 1 --CSub.e=bcd a b c ").split() + config = cl.load_config(argv, aliases=aliases) + assert cl.extra_args == ["a", "b", "c"] + assert config.CBase.a == ['A', '2'] + assert config.CBase.b == [1, 3] + self.assertEqual(config.CBase.c, ['AA', 'BB']) + + assert config.CSub.d == ('1', 'BBB') + assert config.CSub.e == ('1', 'bcd') + + def test_seq_traits_single_empty_string(self): + cl = self.klass(log=log, classes=(CBase, )) + aliases = {'seqopt': 'CBase.c'} + argv = ['--seqopt', ''] + config = cl.load_config(argv, aliases=aliases) + self.assertEqual(config.CBase.c, ['']) + + def test_dict_traits(self): + cl = self.klass(log=log, classes=(CBase, CSub)) + aliases = {'D': 'CBase.adict', 'E': 'CSub.bdict'} + argv = ["-D", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "-E", "22=222"] + config = cl.load_config(argv, aliases=aliases) + c = CSub(config=config) + assert c.adict == {'k1': 'v1', 'k2': '2', 'k3': 'v 3'} + assert c.bdict == {'k': 'v', '22': '222'} + + def test_mixed_seq_positional(self): + aliases = {"c": "Class.trait"} + cl = self.klass(log=log, aliases=aliases) + assignments = [("-c", "1"), ("--Class.trait=2",), ("--c=3",), ("--Class.trait", "4")] + positionals = ["a", "b", "c"] + # test with positionals at any index + for idx in range(len(assignments) + 1): + argv_parts = assignments[:] + argv_parts[idx:idx] = (positionals,) + argv = list(chain(*argv_parts)) + + config = cl.load_config(argv) + assert config.Class.trait == ["1", "2", "3", "4"] + assert cl.extra_args == ["a", "b", "c"] + + def test_split_positional(self): + """Splitting positionals across flags is no longer allowed in traitlets 5""" + cl = self.klass(log=log) + argv = ["a", "--Class.trait=5", "b"] + with pytest.raises(SystemExit): + cl.load_config(argv) + + +class TestConfig(TestCase): + + def test_setget(self): + c = Config() + c.a = 10 + self.assertEqual(c.a, 10) + self.assertEqual('b' in c, False) + + def test_auto_section(self): + c = Config() + self.assertNotIn('A', c) + assert not c._has_section('A') + A = c.A + A.foo = 'hi there' + self.assertIn('A', c) + assert c._has_section('A') + self.assertEqual(c.A.foo, 'hi there') + del c.A + self.assertEqual(c.A, Config()) + + def test_merge_doesnt_exist(self): + c1 = Config() + c2 = Config() + c2.bar = 10 + c2.Foo.bar = 10 + c1.merge(c2) + self.assertEqual(c1.Foo.bar, 10) + self.assertEqual(c1.bar, 10) + c2.Bar.bar = 10 + c1.merge(c2) + self.assertEqual(c1.Bar.bar, 10) + + def test_merge_exists(self): + c1 = Config() + c2 = Config() + c1.Foo.bar = 10 + c1.Foo.bam = 30 + c2.Foo.bar = 20 + c2.Foo.wow = 40 + c1.merge(c2) + self.assertEqual(c1.Foo.bam, 30) + self.assertEqual(c1.Foo.bar, 20) + self.assertEqual(c1.Foo.wow, 40) + c2.Foo.Bam.bam = 10 + c1.merge(c2) + self.assertEqual(c1.Foo.Bam.bam, 10) + + def test_deepcopy(self): + c1 = Config() + c1.Foo.bar = 10 + c1.Foo.bam = 30 + c1.a = 'asdf' + c1.b = range(10) + c1.Test.logger = logging.Logger('test') + c1.Test.get_logger = logging.getLogger('test') + c2 = copy.deepcopy(c1) + self.assertEqual(c1, c2) + self.assertTrue(c1 is not c2) + self.assertTrue(c1.Foo is not c2.Foo) + self.assertTrue(c1.Test is not c2.Test) + self.assertTrue(c1.Test.logger is c2.Test.logger) + self.assertTrue(c1.Test.get_logger is c2.Test.get_logger) + + def test_builtin(self): + c1 = Config() + c1.format = "json" + + def test_fromdict(self): + c1 = Config({'Foo' : {'bar' : 1}}) + self.assertEqual(c1.Foo.__class__, Config) + self.assertEqual(c1.Foo.bar, 1) + + def test_fromdictmerge(self): + c1 = Config() + c2 = Config({'Foo' : {'bar' : 1}}) + c1.merge(c2) + self.assertEqual(c1.Foo.__class__, Config) + self.assertEqual(c1.Foo.bar, 1) + + def test_fromdictmerge2(self): + c1 = Config({'Foo' : {'baz' : 2}}) + c2 = Config({'Foo' : {'bar' : 1}}) + c1.merge(c2) + self.assertEqual(c1.Foo.__class__, Config) + self.assertEqual(c1.Foo.bar, 1) + self.assertEqual(c1.Foo.baz, 2) + self.assertNotIn('baz', c2.Foo) + + def test_contains(self): + c1 = Config({'Foo' : {'baz' : 2}}) + c2 = Config({'Foo' : {'bar' : 1}}) + self.assertIn('Foo', c1) + self.assertIn('Foo.baz', c1) + self.assertIn('Foo.bar', c2) + self.assertNotIn('Foo.bar', c1) + + def test_pickle_config(self): + cfg = Config() + cfg.Foo.bar = 1 + pcfg = pickle.dumps(cfg) + cfg2 = pickle.loads(pcfg) + self.assertEqual(cfg2, cfg) + + def test_getattr_section(self): + cfg = Config() + self.assertNotIn('Foo', cfg) + Foo = cfg.Foo + assert isinstance(Foo, Config) + self.assertIn('Foo', cfg) + + def test_getitem_section(self): + cfg = Config() + self.assertNotIn('Foo', cfg) + Foo = cfg['Foo'] + assert isinstance(Foo, Config) + self.assertIn('Foo', cfg) + + def test_getattr_not_section(self): + cfg = Config() + self.assertNotIn('foo', cfg) + foo = cfg.foo + assert isinstance(foo, LazyConfigValue) + self.assertIn('foo', cfg) + + def test_getattr_private_missing(self): + cfg = Config() + self.assertNotIn('_repr_html_', cfg) + with self.assertRaises(AttributeError): + _ = cfg._repr_html_ + self.assertNotIn('_repr_html_', cfg) + self.assertEqual(len(cfg), 0) + + def test_lazy_config_repr(self): + cfg = Config() + cfg.Class.lazy.append(1) + cfg_repr = repr(cfg) + assert '<LazyConfigValue' in cfg_repr + assert "extend" in cfg_repr + assert " [1]}>" in cfg_repr + assert 'value=' not in cfg_repr + cfg.Class.lazy.get_value([0]) + repr2 = repr(cfg) + assert repr([0,1]) in repr2 + assert 'value=' in repr2 + + + def test_getitem_not_section(self): + cfg = Config() + self.assertNotIn('foo', cfg) + foo = cfg['foo'] + assert isinstance(foo, LazyConfigValue) + self.assertIn('foo', cfg) + + def test_merge_no_copies(self): + c = Config() + c2 = Config() + c2.Foo.trait = [] + c.merge(c2) + c2.Foo.trait.append(1) + self.assertIs(c.Foo, c2.Foo) + self.assertEqual(c.Foo.trait, [1]) + self.assertEqual(c2.Foo.trait, [1]) + + + def test_merge_multi_lazy(self): + """ + With multiple config files (systemwide and users), we want compounding. + + If systemwide overwirte and user append, we want both in the right + order. + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait = [1] + c2.Foo.trait.append(2) + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait, [1,2] ) + + + + def test_merge_multi_lazyII(self): + """ + With multiple config files (systemwide and users), we want compounding. + + If both are lazy we still want a lazy config. + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait.append(1) + c2.Foo.trait.append(2) + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait._extend, [1,2] ) + + def test_merge_multi_lazy_III(self): + """ + With multiple config files (systemwide and users), we want compounding. + + Prepend should prepend in the right order. + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait = [1] + c2.Foo.trait.prepend([0]) + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait, [0, 1] ) + + def test_merge_multi_lazy_IV(self): + """ + With multiple config files (systemwide and users), we want compounding. + + Both prepending should be lazy + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait.prepend([1]) + c2.Foo.trait.prepend([0]) + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait._prepend, [0, 1]) + + def test_merge_multi_lazy_update_I(self): + """ + With multiple config files (systemwide and users), we want compounding. + + dict update shoudl be in the right order. + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait = {"a": 1, "z": 26} + c2.Foo.trait.update({"a": 0, "b": 1}) + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait, {"a": 0, "b": 1, "z": 26}) + + def test_merge_multi_lazy_update_II(self): + """ + With multiple config files (systemwide and users), we want compounding. + + Later dict overwrite lazyness + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait.update({"a": 0, "b": 1}) + c2.Foo.trait = {"a": 1, "z": 26} + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait, {"a": 1, "z": 26}) + + def test_merge_multi_lazy_update_III(self): + """ + With multiple config files (systemwide and users), we want compounding. + + Later dict overwrite lazyness + """ + c1 = Config() + c2 = Config() + + c1.Foo.trait.update({"a": 0, "b": 1}) + c2.Foo.trait.update({"a": 1, "z": 26}) + + c = Config() + c.merge(c1) + c.merge(c2) + + self.assertEqual(c.Foo.trait._update, {"a": 1, "z": 26, "b": 1}) diff --git a/contrib/python/traitlets/py3/traitlets/tests/_warnings.py b/contrib/python/traitlets/py3/traitlets/tests/_warnings.py index d71c86893c..05e916806f 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/_warnings.py +++ b/contrib/python/traitlets/py3/traitlets/tests/_warnings.py @@ -3,13 +3,13 @@ __all__ = ['all_warnings', 'expected_warnings'] -import inspect -import os -import re +import inspect +import os +import re import sys import warnings -from contextlib import contextmanager -from unittest import mock +from contextlib import contextmanager +from unittest import mock @contextmanager @@ -55,27 +55,27 @@ def all_warnings(): except AttributeError: pass - with warnings.catch_warnings(record=True) as w, \ - mock.patch.dict(os.environ, {'TRAITLETS_ALL_DEPRECATIONS': '1'}): + with warnings.catch_warnings(record=True) as w, \ + mock.patch.dict(os.environ, {'TRAITLETS_ALL_DEPRECATIONS': '1'}): warnings.simplefilter("always") yield w @contextmanager def expected_warnings(matching): - r"""Context for use in testing to catch known warnings matching regexes - + r"""Context for use in testing to catch known warnings matching regexes + Parameters ---------- matching : list of strings or compiled regexes Regexes for the desired warning to catch - + Examples -------- >>> from skimage import data, img_as_ubyte, img_as_float - >>> with expected_warnings(["precision loss"]): + >>> with expected_warnings(["precision loss"]): ... d = img_as_ubyte(img_as_float(data.coins())) - + Notes ----- Uses `all_warnings` to ensure all warnings are raised. @@ -95,7 +95,7 @@ def expected_warnings(matching): # enter context yield w # exited user context, check the recorded warnings - remaining = [m for m in matching if not r'\A\Z' in m.split('|')] + remaining = [m for m in matching if not r'\A\Z' in m.split('|')] for warn in w: found = False for match in matching: diff --git a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py b/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py index 43a253ea31..e42dbc01d0 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py +++ b/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py @@ -3,65 +3,65 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -# +# # Adapted from enthought.traits, Copyright (c) Enthought, Inc., # also under the terms of the Modified BSD License. import pickle import re -from unittest import TestCase +from unittest import TestCase import pytest -from traitlets.tests._warnings import expected_warnings +from traitlets.tests._warnings import expected_warnings from traitlets import ( - HasTraits, - MetaHasTraits, - TraitType, - Any, - Bool, - CBytes, - Dict, - Enum, - Int, - CInt, - Long, - CLong, - Integer, - Float, - CFloat, - Complex, - Bytes, - Unicode, - TraitError, - Union, - Callable, - All, - Undefined, - Set, - Type, - This, - Instance, - TCPAddress, - List, - Set, - Tuple, - ObjectName, - DottedObjectName, - CRegExp, - link, - directional_link, - ForwardDeclaredType, - ForwardDeclaredInstance, - validate, - observe, - default, - observe_compat, - BaseDescriptor, - HasDescriptors, - CUnicode, + HasTraits, + MetaHasTraits, + TraitType, + Any, + Bool, + CBytes, + Dict, + Enum, + Int, + CInt, + Long, + CLong, + Integer, + Float, + CFloat, + Complex, + Bytes, + Unicode, + TraitError, + Union, + Callable, + All, + Undefined, + Set, + Type, + This, + Instance, + TCPAddress, + List, + Set, + Tuple, + ObjectName, + DottedObjectName, + CRegExp, + link, + directional_link, + ForwardDeclaredType, + ForwardDeclaredInstance, + validate, + observe, + default, + observe_compat, + BaseDescriptor, + HasDescriptors, + CUnicode, ) -from traitlets.utils import cast_unicode +from traitlets.utils import cast_unicode def change_dict(*ordered_values): @@ -93,7 +93,7 @@ class TestTraitType(TestCase): class A(HasTraits): a = TraitType a = A() - assert a.a is Undefined + assert a.a is Undefined def test_set(self): class A(HasTraitsStub): @@ -131,7 +131,7 @@ class TestTraitType(TestCase): # Defaults are validated when the HasTraits is instantiated class B(HasTraits): tt = MyIntTT('bad default') - self.assertRaises(TraitError, getattr, B(), 'tt') + self.assertRaises(TraitError, getattr, B(), 'tt') def test_info(self): class A(HasTraits): @@ -141,7 +141,7 @@ class TestTraitType(TestCase): def test_error(self): class A(HasTraits): - tt = TraitType() + tt = TraitType() a = A() self.assertRaises(TraitError, A.tt.error, a, 10) @@ -175,46 +175,46 @@ class TestTraitType(TestCase): self.assertEqual(a.x, 11) self.assertEqual(a._trait_values, {'x': 11}) - def test_deprecated_method_warnings(self): - - with expected_warnings([]): - class ShouldntWarn(HasTraits): - x = Integer() - @default('x') - def _x_default(self): - return 10 - - @validate('x') - def _x_validate(self, proposal): - return proposal.value - - @observe('x') - def _x_changed(self, change): - pass - - obj = ShouldntWarn() - obj.x = 5 - - assert obj.x == 5 - - with expected_warnings(['@validate', '@observe']) as w: - class ShouldWarn(HasTraits): - x = Integer() - - def _x_default(self): - return 10 - - def _x_validate(self, value, _): - return value - - def _x_changed(self): - pass - - obj = ShouldWarn() - obj.x = 5 - - assert obj.x == 5 - + def test_deprecated_method_warnings(self): + + with expected_warnings([]): + class ShouldntWarn(HasTraits): + x = Integer() + @default('x') + def _x_default(self): + return 10 + + @validate('x') + def _x_validate(self, proposal): + return proposal.value + + @observe('x') + def _x_changed(self, change): + pass + + obj = ShouldntWarn() + obj.x = 5 + + assert obj.x == 5 + + with expected_warnings(['@validate', '@observe']) as w: + class ShouldWarn(HasTraits): + x = Integer() + + def _x_default(self): + return 10 + + def _x_validate(self, value, _): + return value + + def _x_changed(self): + pass + + obj = ShouldWarn() + obj.x = 5 + + assert obj.x == 5 + def test_dynamic_initializer(self): class A(HasTraits): @@ -288,19 +288,19 @@ class TestTraitType(TestCase): foo = Foo() self.assertEqual(foo.bar, 1) - def test_union_validation_priority(self): - class Foo(HasTraits): - bar = Union([CInt(), Unicode()]) - foo = Foo() - foo.bar = '1' - # validation in order of the TraitTypes given - self.assertEqual(foo.bar, 1) - - def test_union_trait_default_value(self): - class Foo(HasTraits): - bar = Union([Dict(), Int()]) - self.assertEqual(Foo().bar, {}) - + def test_union_validation_priority(self): + class Foo(HasTraits): + bar = Union([CInt(), Unicode()]) + foo = Foo() + foo.bar = '1' + # validation in order of the TraitTypes given + self.assertEqual(foo.bar, 1) + + def test_union_trait_default_value(self): + class Foo(HasTraits): + bar = Union([Dict(), Int()]) + self.assertEqual(Foo().bar, {}) + def test_deprecated_metadata_access(self): class MyIntTT(TraitType): metadata = {'a': 1, 'b': 2} @@ -557,29 +557,29 @@ class TestHasTraitsNotify(TestCase): class A(HasTraits): listen_to = ['a'] - + a = Int(0) b = 0 - + def __init__(self, **kwargs): super(A, self).__init__(**kwargs) self.on_trait_change(self.listener1, ['a']) - + def listener1(self, name, old, new): self.b += 1 class B(A): - + c = 0 d = 0 - + def __init__(self, **kwargs): super(B, self).__init__(**kwargs) self.on_trait_change(self.listener2) - + def listener2(self, name, old, new): self.c += 1 - + def _a_changed(self, name, old, new): self.d += 1 @@ -689,7 +689,7 @@ class TestObserveDecorator(TestCase): b = Int() _notify1 = [] _notify_any = [] - + @observe('a') def _a_changed(self, change): self._notify1.append(change) @@ -753,29 +753,29 @@ class TestObserveDecorator(TestCase): class A(HasTraits): listen_to = ['a'] - + a = Int(0) b = 0 - + def __init__(self, **kwargs): super(A, self).__init__(**kwargs) self.observe(self.listener1, ['a']) - + def listener1(self, change): self.b += 1 class B(A): - + c = 0 d = 0 - + def __init__(self, **kwargs): super(B, self).__init__(**kwargs) self.observe(self.listener2) - + def listener2(self, change): self.c += 1 - + @observe('a') def _a_changed(self, change): self.d += 1 @@ -801,20 +801,20 @@ class TestHasTraits(TestCase): self.assertTrue(a.has_trait('f')) self.assertFalse(a.has_trait('g')) - def test_trait_has_value(self): - class A(HasTraits): - i = Int() - f = Float() - a = A() - self.assertFalse(a.trait_has_value('f')) - self.assertFalse(a.trait_has_value('g')) - a.i = 1 - a.f - self.assertTrue(a.trait_has_value('i')) - self.assertTrue(a.trait_has_value('f')) - + def test_trait_has_value(self): + class A(HasTraits): + i = Int() + f = Float() + a = A() + self.assertFalse(a.trait_has_value('f')) + self.assertFalse(a.trait_has_value('g')) + a.i = 1 + a.f + self.assertTrue(a.trait_has_value('i')) + self.assertTrue(a.trait_has_value('f')) + def test_trait_metadata_deprecated(self): - with expected_warnings([r'metadata should be set using the \.tag\(\) method']): + with expected_warnings([r'metadata should be set using the \.tag\(\) method']): class A(HasTraits): i = Int(config_key='MY_VALUE') a = A() @@ -857,7 +857,7 @@ class TestHasTraits(TestCase): self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j)) def test_traits_metadata_deprecated(self): - with expected_warnings([r'metadata should be set using the \.tag\(\) method']*2): + with expected_warnings([r'metadata should be set using the \.tag\(\) method']*2): class A(HasTraits): i = Int(config_key='VALUE1', other_thing='VALUE2') f = Float(config_key='VALUE3', other_thing='VALUE2') @@ -887,7 +887,7 @@ class TestHasTraits(TestCase): def __init__(self, i): super(A, self).__init__() self.i = i - + a = A(5) self.assertEqual(a.i, 5) # should raise TypeError if no positional arg given @@ -997,17 +997,17 @@ class TestType(TestCase): class C(HasTraits): klass = Type(None, B) - self.assertRaises(TraitError, getattr, C(), 'klass') + self.assertRaises(TraitError, getattr, C(), 'klass') def test_str_klass(self): class A(HasTraits): - klass = Type("traitlets.config.Config") + klass = Type("traitlets.config.Config") - from traitlets.config import Config + from traitlets.config import Config a = A() - a.klass = Config - self.assertEqual(a.klass, Config) + a.klass = Config + self.assertEqual(a.klass, Config) self.assertRaises(TraitError, setattr, a, 'klass', 10) @@ -1016,11 +1016,11 @@ class TestType(TestCase): class A(HasTraits): klass = Type() - a = A(klass="traitlets.config.Config") - from traitlets.config import Config + a = A(klass="traitlets.config.Config") + from traitlets.config import Config + + self.assertEqual(a.klass, Config) - self.assertEqual(a.klass, Config) - class TestInstance(TestCase): def test_basic(self): @@ -1232,7 +1232,7 @@ class AnyTraitTest(TraitTestBase): obj = AnyTrait() _default_value = None - _good_values = [10.0, 'ten', [10], {'ten': 10},(10,), None, 1j] + _good_values = [10.0, 'ten', [10], {'ten': 10},(10,), None, 1j] _bad_values = [] class UnionTrait(HasTraits): @@ -1241,20 +1241,20 @@ class UnionTrait(HasTraits): class UnionTraitTest(TraitTestBase): - obj = UnionTrait(value="traitlets.config.Config") + obj = UnionTrait(value="traitlets.config.Config") _good_values = [int, float, True] _bad_values = [[], (0,), 1j] -class CallableTrait(HasTraits): - - value = Callable() - -class CallableTraitTest(TraitTestBase): - - obj = CallableTrait(value=lambda x: type(x)) - _good_values = [int, sorted, lambda x: print(x)] - _bad_values = [[], 1, ''] - +class CallableTrait(HasTraits): + + value = Callable() + +class CallableTraitTest(TraitTestBase): + + obj = CallableTrait(value=lambda x: type(x)) + _good_values = [int, sorted, lambda x: print(x)] + _bad_values = [[], 1, ''] + class OrTrait(HasTraits): value = Bool() | Unicode() @@ -1274,9 +1274,9 @@ class TestInt(TraitTestBase): obj = IntTrait() _default_value = 99 _good_values = [10, -10] - _bad_values = ['ten', [10], {'ten': 10}, (10,), None, 1j, - 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', - '10', '-10', -200] + _bad_values = ['ten', [10], {'ten': 10}, (10,), None, 1j, + 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', + '10', '-10', -200] class CIntTrait(HasTraits): @@ -1286,9 +1286,9 @@ class TestCInt(TraitTestBase): obj = CIntTrait() _default_value = 5 - _good_values = ['10', '-10', 10, 10.0, -10.0, 10.1] - _bad_values = ['ten', [10], {'ten': 10},(10,), - None, 1j, '10.1'] + _good_values = ['10', '-10', 10, 10.0, -10.0, 10.1] + _bad_values = ['ten', [10], {'ten': 10},(10,), + None, 1j, '10.1'] def coerce(self, n): return int(n) @@ -1307,37 +1307,37 @@ class TestMinBoundCInt(TestCInt): class LongTrait(HasTraits): - value = Long(99) + value = Long(99) class TestLong(TraitTestBase): obj = LongTrait() - _default_value = 99 + _default_value = 99 _good_values = [10, -10] - _bad_values = ['ten', [10], {'ten': 10},(10,), + _bad_values = ['ten', [10], {'ten': 10},(10,), None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1', - '-10.1'] + '-10.1'] class MinBoundLongTrait(HasTraits): - value = Long(99, min=5) + value = Long(99, min=5) class TestMinBoundLong(TraitTestBase): obj = MinBoundLongTrait() - _default_value = 99 + _default_value = 99 _good_values = [5, 10] _bad_values = [4, -10] class MaxBoundLongTrait(HasTraits): - value = Long(5, max=10) + value = Long(5, max=10) class TestMaxBoundLong(TraitTestBase): obj = MaxBoundLongTrait() - _default_value = 5 + _default_value = 5 _good_values = [10, -2] _bad_values = [11, 20] @@ -1348,13 +1348,13 @@ class CLongTrait(HasTraits): class TestCLong(TraitTestBase): obj = CLongTrait() - _default_value = 5 - _good_values = ['10', '-10', 10, 10.0, -10.0, 10.1] - _bad_values = ['ten', [10], {'ten': 10},(10,), - None, 1j, '10.1'] + _default_value = 5 + _good_values = ['10', '-10', 10, 10.0, -10.0, 10.1] + _bad_values = ['ten', [10], {'ten': 10},(10,), + None, 1j, '10.1'] def coerce(self, n): - return int(n) + return int(n) class MaxBoundCLongTrait(HasTraits): @@ -1363,7 +1363,7 @@ class MaxBoundCLongTrait(HasTraits): class TestMaxBoundCLong(TestCLong): obj = MaxBoundCLongTrait() - _default_value = 5 + _default_value = 5 _good_values = [10, '10', 10.3] _bad_values = [11.0, '11'] @@ -1411,8 +1411,8 @@ class TestFloat(TraitTestBase): _default_value = 99.0 _good_values = [10, -10, 10.1, -10.1] - _bad_values = ['ten', [10], {'ten': 10}, (10,), None, - 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', 201.0] + _bad_values = ['ten', [10], {'ten': 10}, (10,), None, + 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', 201.0] class CFloatTrait(HasTraits): @@ -1424,8 +1424,8 @@ class TestCFloat(TraitTestBase): obj = CFloatTrait() _default_value = 99.0 - _good_values = [10, 10.0, 10.5, '10.0', '10', '-10'] - _bad_values = ['ten', [10], {'ten': 10}, (10,), None, 1j, + _good_values = [10, 10.0, 10.5, '10.0', '10', '-10'] + _bad_values = ['ten', [10], {'ten': 10}, (10,), None, 1j, 200.1, '200.1'] def coerce(self, v): @@ -1443,7 +1443,7 @@ class TestComplex(TraitTestBase): _default_value = 99.0-99.0j _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j, 10.1j, 10.1+10.1j, 10.1-10.1j] - _bad_values = ['10L', '-10L', 'ten', [10], {'ten': 10},(10,), None] + _bad_values = ['10L', '-10L', 'ten', [10], {'ten': 10},(10,), None] class BytesTrait(HasTraits): @@ -1458,28 +1458,28 @@ class TestBytes(TraitTestBase): _good_values = [b'10', b'-10', b'10L', b'-10L', b'10.1', b'-10.1', b'string'] _bad_values = [10, -10, 10.1, -10.1, 1j, [10], - ['ten'],{'ten': 10},(10,), None, 'string'] + ['ten'],{'ten': 10},(10,), None, 'string'] class UnicodeTrait(HasTraits): - value = Unicode('unicode') + value = Unicode('unicode') + - class TestUnicode(TraitTestBase): obj = UnicodeTrait() - _default_value = 'unicode' + _default_value = 'unicode' _good_values = ['10', '-10', '10L', '-10L', '10.1', - '-10.1', '', 'string', "€", b"bytestring"] + '-10.1', '', 'string', "€", b"bytestring"] _bad_values = [10, -10, 10.1, -10.1, 1j, - [10], ['ten'], {'ten': 10},(10,), None] + [10], ['ten'], {'ten': 10},(10,), None] + + def coerce(self, v): + return cast_unicode(v) - def coerce(self, v): - return cast_unicode(v) - class ObjectNameTrait(HasTraits): value = ObjectName("abc") @@ -1487,10 +1487,10 @@ class TestObjectName(TraitTestBase): obj = ObjectNameTrait() _default_value = "abc" - _good_values = ["a", "gh", "g9", "g_", "_G", "a345_"] - _bad_values = [1, "", "€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]", + _good_values = ["a", "gh", "g9", "g_", "_G", "a345_"] + _bad_values = [1, "", "€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]", None, object(), object] - _good_values.append("þ") # þ=1 is valid in Python 3 (PEP 3131). + _good_values.append("þ") # þ=1 is valid in Python 3 (PEP 3131). class DottedObjectNameTrait(HasTraits): @@ -1500,12 +1500,12 @@ class TestDottedObjectName(TraitTestBase): obj = DottedObjectNameTrait() _default_value = "a.b" - _good_values = ["A", "y.t", "y765.__repr__", "os.path.join"] - _bad_values = [1, "abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None] + _good_values = ["A", "y.t", "y765.__repr__", "os.path.join"] + _bad_values = [1, "abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None] + + _good_values.append("t.þ") - _good_values.append("t.þ") - class TCPAddressTrait(HasTraits): value = TCPAddress() @@ -1517,12 +1517,12 @@ class TestTCPAddress(TraitTestBase): _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)] _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None] - + class ListTrait(HasTraits): value = List(Int()) - + class TestList(TraitTestBase): obj = ListTrait() @@ -1530,18 +1530,18 @@ class TestList(TraitTestBase): _default_value = [] _good_values = [[], [1], list(range(10)), (1,2)] _bad_values = [10, [1,'a'], 'a'] - + def coerce(self, value): if value is not None: value = list(value) return value - + class Foo(object): pass class NoneInstanceListTrait(HasTraits): - + value = List(Instance(Foo)) class TestNoneInstanceList(TraitTestBase): @@ -1573,7 +1573,7 @@ class UnionListTrait(HasTraits): value = List(Int() | Bool()) -class TestUnionListTrait(TraitTestBase): +class TestUnionListTrait(TraitTestBase): obj = UnionListTrait() @@ -1607,7 +1607,7 @@ class TestTupleTrait(TraitTestBase): obj = TupleTrait() - _default_value = (1,) + _default_value = (1,) _good_values = [(1,), (0,), [1]] _bad_values = [10, (1, 2), ('a'), (), None] @@ -1654,64 +1654,64 @@ class TestMultiTuple(TraitTestBase): _default_value = (99,b'bottles') _good_values = [(1,b'a'), (2,b'b')] - _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, 'a')) - - -@pytest.mark.parametrize( - "Trait", (List, Tuple, Set, Dict, Integer, Unicode,), -) -def test_allow_none_default_value(Trait): - class C(HasTraits): - t = Trait(default_value=None, allow_none=True) - - # test default value - c = C() - assert c.t is None - - # and in constructor - c = C(t=None) - assert c.t is None - - -@pytest.mark.parametrize( - "Trait, default_value", - ((List, []), (Tuple, ()), (Set, set()), (Dict, {}), (Integer, 0), (Unicode, "")), -) -def test_default_value(Trait, default_value): - class C(HasTraits): - t = Trait() - - # test default value - c = C() - assert type(c.t) is type(default_value) - assert c.t == default_value - - -@pytest.mark.parametrize( - "Trait, default_value", - ((List, []), (Tuple, ()), (Set, set())), -) -def test_subclass_default_value(Trait, default_value): - """Test deprecated default_value=None behavior for Container subclass traits""" - - class SubclassTrait(Trait): - def __init__(self, default_value=None): - super().__init__(default_value=default_value) - - class C(HasTraits): - t = SubclassTrait() - - # test default value - c = C() - assert type(c.t) is type(default_value) - assert c.t == default_value - - + _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, 'a')) + + +@pytest.mark.parametrize( + "Trait", (List, Tuple, Set, Dict, Integer, Unicode,), +) +def test_allow_none_default_value(Trait): + class C(HasTraits): + t = Trait(default_value=None, allow_none=True) + + # test default value + c = C() + assert c.t is None + + # and in constructor + c = C(t=None) + assert c.t is None + + +@pytest.mark.parametrize( + "Trait, default_value", + ((List, []), (Tuple, ()), (Set, set()), (Dict, {}), (Integer, 0), (Unicode, "")), +) +def test_default_value(Trait, default_value): + class C(HasTraits): + t = Trait() + + # test default value + c = C() + assert type(c.t) is type(default_value) + assert c.t == default_value + + +@pytest.mark.parametrize( + "Trait, default_value", + ((List, []), (Tuple, ()), (Set, set())), +) +def test_subclass_default_value(Trait, default_value): + """Test deprecated default_value=None behavior for Container subclass traits""" + + class SubclassTrait(Trait): + def __init__(self, default_value=None): + super().__init__(default_value=default_value) + + class C(HasTraits): + t = SubclassTrait() + + # test default value + c = C() + assert type(c.t) is type(default_value) + assert c.t == default_value + + class CRegExpTrait(HasTraits): value = CRegExp(r'') - + class TestCRegExp(TraitTestBase): def coerce(self, value): @@ -1731,59 +1731,59 @@ def test_dict_assignment(): c = DictTrait() c.value = d d['a'] = 5 - assert d == c.value + assert d == c.value assert c.value is d -class UniformlyValueValidatedDictTrait(HasTraits): +class UniformlyValueValidatedDictTrait(HasTraits): value = Dict(trait=Unicode(), default_value={'foo': '1'}) -class TestInstanceUniformlyValueValidatedDict(TraitTestBase): +class TestInstanceUniformlyValueValidatedDict(TraitTestBase): - obj = UniformlyValueValidatedDictTrait() + obj = UniformlyValueValidatedDictTrait() _default_value = {'foo': '1'} _good_values = [{'foo': '0', 'bar': '1'}] _bad_values = [{'foo': 0, 'bar': '1'}] -class NonuniformlyValueValidatedDictTrait(HasTraits): +class NonuniformlyValueValidatedDictTrait(HasTraits): value = Dict(traits={'foo': Int()}, default_value={'foo': 1}) -class TestInstanceNonuniformlyValueValidatedDict(TraitTestBase): +class TestInstanceNonuniformlyValueValidatedDict(TraitTestBase): - obj = NonuniformlyValueValidatedDictTrait() + obj = NonuniformlyValueValidatedDictTrait() _default_value = {'foo': 1} _good_values = [{'foo': 0, 'bar': '1'}, {'foo': 0, 'bar': 1}] _bad_values = [{'foo': '0', 'bar': '1'}] -class KeyValidatedDictTrait(HasTraits): - - value = Dict(key_trait=Unicode(), - default_value={'foo': '1'}) - - -class TestInstanceKeyValidatedDict(TraitTestBase): - - obj = KeyValidatedDictTrait() - - _default_value = {'foo': '1'} - _good_values = [{'foo': '0', 'bar': '1'}] - _bad_values = [{'foo': '0', 0: '1'}] - - +class KeyValidatedDictTrait(HasTraits): + + value = Dict(key_trait=Unicode(), + default_value={'foo': '1'}) + + +class TestInstanceKeyValidatedDict(TraitTestBase): + + obj = KeyValidatedDictTrait() + + _default_value = {'foo': '1'} + _good_values = [{'foo': '0', 'bar': '1'}] + _bad_values = [{'foo': '0', 0: '1'}] + + class FullyValidatedDictTrait(HasTraits): value = Dict(trait=Unicode(), - key_trait=Unicode(), + key_trait=Unicode(), traits={'foo': Int()}, default_value={'foo': 1}) @@ -1794,7 +1794,7 @@ class TestInstanceFullyValidatedDict(TraitTestBase): _default_value = {'foo': 1} _good_values = [{'foo': 0, 'bar': '1'}, {'foo': 1, 'bar': '2'}] - _bad_values = [{'foo': 0, 'bar': 1}, {'foo': '0', 'bar': '1'}, {'foo': 0, 0: '1'}] + _bad_values = [{'foo': 0, 'bar': 1}, {'foo': '0', 'bar': '1'}, {'foo': 0, 0: '1'}] def test_dict_default_value(): @@ -1829,7 +1829,7 @@ class TestValidationHook(TestCase): if self.parity == 'odd' and (value % 2 == 0): raise TraitError('Expected an odd number') return value - + u = Parity() u.parity = 'odd' u.value = 1 # OK @@ -1911,8 +1911,8 @@ class TestLink(TestCase): b.count = 4 self.assertEqual(a.value, b.count) - def test_unlink_link(self): - """Verify two linked traitlets can be unlinked and relinked.""" + def test_unlink_link(self): + """Verify two linked traitlets can be unlinked and relinked.""" # Create two simple classes with Int traitlets. class A(HasTraits): @@ -1928,10 +1928,10 @@ class TestLink(TestCase): # Change one of the values to make sure they don't stay in sync. a.value = 5 self.assertNotEqual(a.value, b.value) - c.link() - self.assertEqual(a.value, b.value) - a.value += 1 - self.assertEqual(a.value, b.value) + c.link() + self.assertEqual(a.value, b.value) + a.value += 1 + self.assertEqual(a.value, b.value) def test_callbacks(self): """Verify two linked traitlets have their callbacks called once.""" @@ -1943,7 +1943,7 @@ class TestLink(TestCase): count = Int() a = A(value=9) b = B(count=8) - + # Register callbacks that count. callback_count = [] def a_callback(name, old, new): @@ -1970,56 +1970,56 @@ class TestLink(TestCase): self.assertEqual(''.join(callback_count), 'ab') del callback_count[:] - def test_tranform(self): - """Test transform link.""" - - # Create two simple classes with Int traitlets. - class A(HasTraits): - value = Int() - a = A(value=9) - b = A(value=8) - - # Conenct the two classes. - c = link((a, 'value'), (b, 'value'), - transform=(lambda x: 2 * x, lambda x: int(x / 2.))) - - # Make sure the values are correct at the point of linking. - self.assertEqual(b.value, 2 * a.value) - - # Change one the value of the source and check that it modifies the target. - a.value = 5 - self.assertEqual(b.value, 10) - # Change one the value of the target and check that it modifies the - # source. - b.value = 6 - self.assertEqual(a.value, 3) - - def test_link_broken_at_source(self): - class MyClass(HasTraits): - i = Int() - j = Int() - - @observe("j") - def another_update(self, change): - self.i = change.new * 2 - - mc = MyClass() - l = link((mc, "i"), (mc, "j")) - self.assertRaises(TraitError, setattr, mc, 'i', 2) - - def test_link_broken_at_target(self): - class MyClass(HasTraits): - i =Int() - j = Int() - - @observe("i") - def another_update(self, change): - self.j = change.new * 2 - - mc = MyClass() - l = link((mc, "i"), (mc, "j")) - self.assertRaises(TraitError, setattr, mc, 'j', 2) - + def test_tranform(self): + """Test transform link.""" + + # Create two simple classes with Int traitlets. + class A(HasTraits): + value = Int() + a = A(value=9) + b = A(value=8) + + # Conenct the two classes. + c = link((a, 'value'), (b, 'value'), + transform=(lambda x: 2 * x, lambda x: int(x / 2.))) + + # Make sure the values are correct at the point of linking. + self.assertEqual(b.value, 2 * a.value) + + # Change one the value of the source and check that it modifies the target. + a.value = 5 + self.assertEqual(b.value, 10) + # Change one the value of the target and check that it modifies the + # source. + b.value = 6 + self.assertEqual(a.value, 3) + + def test_link_broken_at_source(self): + class MyClass(HasTraits): + i = Int() + j = Int() + + @observe("j") + def another_update(self, change): + self.i = change.new * 2 + + mc = MyClass() + l = link((mc, "i"), (mc, "j")) + self.assertRaises(TraitError, setattr, mc, 'i', 2) + + def test_link_broken_at_target(self): + class MyClass(HasTraits): + i =Int() + j = Int() + + @observe("i") + def another_update(self, change): + self.j = change.new * 2 + + mc = MyClass() + l = link((mc, "i"), (mc, "j")) + self.assertRaises(TraitError, setattr, mc, 'j', 2) + class TestDirectionalLink(TestCase): def test_connect_same(self): """Verify two traitlets of the same type can be linked together using directional_link.""" @@ -2089,8 +2089,8 @@ class TestDirectionalLink(TestCase): b.value = 6 self.assertEqual(a.value, 5) - def test_unlink_link(self): - """Verify two linked traitlets can be unlinked and relinked.""" + def test_unlink_link(self): + """Verify two linked traitlets can be unlinked and relinked.""" # Create two simple classes with Int traitlets. class A(HasTraits): @@ -2106,10 +2106,10 @@ class TestDirectionalLink(TestCase): # Change one of the values to make sure they don't stay in sync. a.value = 5 self.assertNotEqual(a.value, b.value) - c.link() - self.assertEqual(a.value, b.value) - a.value += 1 - self.assertEqual(a.value, b.value) + c.link() + self.assertEqual(a.value, b.value) + a.value += 1 + self.assertEqual(a.value, b.value) class Pickleable(HasTraits): @@ -2121,7 +2121,7 @@ class Pickleable(HasTraits): return commit['value'] j = Int() - + def __init__(self): with self.hold_trait_notifications(): self.i = 1 @@ -2226,7 +2226,7 @@ def test_cache_modification(): class OrderTraits(HasTraits): notified = Dict() - + a = Unicode() b = Unicode() c = Unicode() @@ -2239,10 +2239,10 @@ class OrderTraits(HasTraits): j = Unicode() k = Unicode() l = Unicode() - + def _notify(self, name, old, new): """check the value of all traits when each trait change is triggered - + This verifies that the values are not sensitive to dict ordering when loaded from kwargs """ @@ -2251,7 +2251,7 @@ class OrderTraits(HasTraits): self.notified[name] = { c: getattr(self, c) for c in 'abcdefghijkl' } - + def __init__(self, **kwargs): self.on_trait_change(self._notify) super(OrderTraits, self).__init__(**kwargs) @@ -2445,7 +2445,7 @@ def test_default_value_repr(): n = Integer(0) lis = List() d = Dict() - + assert C.t.default_value_repr() == "'traitlets.HasTraits'" assert C.t2.default_value_repr() == "'traitlets.traitlets.HasTraits'" assert C.n.default_value_repr() == '0' @@ -2454,7 +2454,7 @@ def test_default_value_repr(): class TransitionalClass(HasTraits): - + d = Any() @default('d') def _d_default(self): @@ -2462,19 +2462,19 @@ class TransitionalClass(HasTraits): parent_super = False calls_super = Integer(0) - + @default('calls_super') def _calls_super_default(self): return -1 - + @observe('calls_super') @observe_compat def _calls_super_changed(self, change): self.parent_super = change - + parent_override = False overrides = Integer(0) - + @observe('overrides') @observe_compat def _overrides_changed(self, change): @@ -2484,12 +2484,12 @@ class TransitionalClass(HasTraits): class SubClass(TransitionalClass): def _d_default(self): return SubClass - + subclass_super = False def _calls_super_changed(self, name, old, new): self.subclass_super = True super(SubClass, self)._calls_super_changed(name, old, new) - + subclass_override = False def _overrides_changed(self, name, old, new): self.subclass_override = True @@ -2508,7 +2508,7 @@ def test_subclass_compat(): class DefinesHandler(HasTraits): parent_called = False - + trait = Integer() @observe('trait') def handler(self, change): @@ -2517,7 +2517,7 @@ class DefinesHandler(HasTraits): class OverridesHandler(DefinesHandler): child_called = False - + @observe('trait') def handler(self, change): self.child_called = True @@ -2532,7 +2532,7 @@ def test_subclass_override_observer(): class DoesntRegisterHandler(DefinesHandler): child_called = False - + def handler(self, change): self.child_called = True @@ -2547,7 +2547,7 @@ def test_subclass_override_not_registered(): class AddsHandler(DefinesHandler): child_called = False - + @observe('trait') def child_handler(self, change): self.child_called = True @@ -2604,10 +2604,10 @@ def test_super_args(): def __init__(self, *args, **kwargs): self.super_args = args self.super_kwargs = kwargs - + class SuperHasTraits(HasTraits, SuperRecorder): i = Integer() - + obj = SuperHasTraits('a1', 'a2', b=10, i=5, c='x') assert obj.i == 5 assert not hasattr(obj, 'b') @@ -2618,317 +2618,317 @@ def test_super_args(): def test_super_bad_args(): class SuperHasTraits(HasTraits): a = Integer() - - w = ["Passing unrecognized arguments"] + + w = ["Passing unrecognized arguments"] with expected_warnings(w): obj = SuperHasTraits(a=1, b=2) - assert obj.a == 1 + assert obj.a == 1 assert not hasattr(obj, 'b') - - -def test_default_mro(): - """Verify that default values follow mro""" - class Base(HasTraits): - trait = Unicode('base') - attr = 'base' - - class A(Base): - pass - - class B(Base): - trait = Unicode('B') - attr = 'B' - - class AB(A, B): - pass - - class BA(B, A): - pass - - assert A().trait == 'base' - assert A().attr == 'base' - assert BA().trait == 'B' - assert BA().attr == 'B' - assert AB().trait == 'B' - assert AB().attr == 'B' - - -def test_cls_self_argument(): - class X(HasTraits): - def __init__(__self, cls, self): - pass - - x = X(cls=None, self=None) - - -def test_override_default(): - class C(HasTraits): - a = Unicode('hard default') - def _a_default(self): - return 'default method' - - C._a_default = lambda self: 'overridden' - c = C() - assert c.a == 'overridden' - -def test_override_default_decorator(): - class C(HasTraits): - a = Unicode('hard default') - @default('a') - def _a_default(self): - return 'default method' - - C._a_default = lambda self: 'overridden' - c = C() - assert c.a == 'overridden' - -def test_override_default_instance(): - class C(HasTraits): - a = Unicode('hard default') - @default('a') - def _a_default(self): - return 'default method' - - c = C() - c._a_default = lambda self: 'overridden' - assert c.a == 'overridden' - - -def test_copy_HasTraits(): - from copy import copy - - class C(HasTraits): - a = Int() - - c = C(a=1) - assert c.a == 1 - - cc = copy(c) - cc.a = 2 - assert cc.a == 2 - assert c.a == 1 - - -def _from_string_test(traittype, s, expected): - """Run a test of trait.from_string""" - if isinstance(traittype, TraitType): - trait = traittype - else: - trait = traittype(allow_none=True) - if isinstance(s, list): - cast = trait.from_string_list - else: - cast = trait.from_string - if type(expected) is type and issubclass(expected, Exception): - with pytest.raises(expected): - value = cast(s) - trait.validate(None, value) - else: - value = cast(s) - assert value == expected - - -@pytest.mark.parametrize( - "s, expected", - [("xyz", "xyz"), ("1", "1"), ('"xx"', "xx"), ("'abc'", "abc"), ("None", None)], -) -def test_unicode_from_string(s, expected): - _from_string_test(Unicode, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [("xyz", "xyz"), ("1", "1"), ('"xx"', "xx"), ("'abc'", "abc"), ("None", None)], -) -def test_cunicode_from_string(s, expected): - _from_string_test(CUnicode, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [("xyz", b"xyz"), ("1", b"1"), ('b"xx"', b"xx"), ("b'abc'", b"abc"), ("None", None)], -) -def test_bytes_from_string(s, expected): - _from_string_test(Bytes, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [("xyz", b"xyz"), ("1", b"1"), ('b"xx"', b"xx"), ("b'abc'", b"abc"), ("None", None)], -) -def test_cbytes_from_string(s, expected): - _from_string_test(CBytes, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [("x", ValueError), ("1", 1), ("123", 123), ("2.0", ValueError), ("None", None)], -) -def test_int_from_string(s, expected): - _from_string_test(Integer, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [("x", ValueError), ("1", 1.0), ("123.5", 123.5), ("2.5", 2.5), ("None", None)], -) -def test_float_from_string(s, expected): - _from_string_test(Float, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("x", ValueError), - ("1", 1.0), - ("123.5", 123.5), - ("2.5", 2.5), - ("1+2j", 1 + 2j), - ("None", None), - ], -) -def test_complex_from_string(s, expected): - _from_string_test(Complex, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("true", True), - ("TRUE", True), - ("1", True), - ("0", False), - ("False", False), - ("false", False), - ("1.0", ValueError), - ("None", None), - ], -) -def test_bool_from_string(s, expected): - _from_string_test(Bool, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("{}", {}), - ("1", TraitError), - ("{1: 2}", {1: 2}), - ('{"key": "value"}', {"key": "value"}), - ("x", TraitError), - ("None", None), - ], -) -def test_dict_from_string(s, expected): - _from_string_test(Dict, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("[]", []), - ('[1, 2, "x"]', [1, 2, "x"]), - (["1", "x"], ["1", "x"]), - (["None"], None), - ], -) -def test_list_from_string(s, expected): - _from_string_test(List, s, expected) - - -@pytest.mark.parametrize( - "s, expected, value_trait", - [ - (["1", "2", "3"], [1, 2, 3], Integer()), - (["x"], ValueError, Integer()), - (["1", "x"], ["1", "x"], Unicode()), - (["None"], [None], Unicode(allow_none=True)), - (["None"], ["None"], Unicode(allow_none=False)), - ], -) -def test_list_items_from_string(s, expected, value_trait): - _from_string_test(List(value_trait), s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("[]", set()), - ('[1, 2, "x"]', {1, 2, "x"}), - ('{1, 2, "x"}', {1, 2, "x"}), - (["1", "x"], {"1", "x"}), - (["None"], None), - ], -) -def test_set_from_string(s, expected): - _from_string_test(Set, s, expected) - - -@pytest.mark.parametrize( - "s, expected, value_trait", - [ - (["1", "2", "3"], {1, 2, 3}, Integer()), - (["x"], ValueError, Integer()), - (["1", "x"], {"1", "x"}, Unicode()), - (["None"], {None}, Unicode(allow_none=True)), - ], -) -def test_set_items_from_string(s, expected, value_trait): - _from_string_test(Set(value_trait), s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("[]", ()), - ("()", ()), - ('[1, 2, "x"]', (1, 2, "x")), - ('(1, 2, "x")', (1, 2, "x")), - (["1", "x"], ("1", "x")), - (["None"], None), - ], -) -def test_tuple_from_string(s, expected): - _from_string_test(Tuple, s, expected) - - -@pytest.mark.parametrize( - "s, expected, value_traits", - [ - (["1", "2", "3"], (1, 2, 3), [Integer(), Integer(), Integer()]), - (["x"], ValueError, [Integer()]), - (["1", "x"], ("1", "x"), [Unicode()]), - (["None"], ("None",), [Unicode(allow_none=False)]), - (["None"], (None,), [Unicode(allow_none=True)]), - ], -) -def test_tuple_items_from_string(s, expected, value_traits): - _from_string_test(Tuple(*value_traits), s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("x", "x"), - ("mod.submod", "mod.submod"), - ("not an identifier", TraitError), - ("1", "1"), - ("None", None), - ], -) -def test_object_from_string(s, expected): - _from_string_test(DottedObjectName, s, expected) - - -@pytest.mark.parametrize( - "s, expected", - [ - ("127.0.0.1:8000", ("127.0.0.1", 8000)), - ("host.tld:80", ("host.tld", 80)), - ("host:notaport", ValueError), - ("127.0.0.1", ValueError), - ("None", None), - ], -) -def test_tcp_from_string(s, expected): - _from_string_test(TCPAddress, s, expected) + + +def test_default_mro(): + """Verify that default values follow mro""" + class Base(HasTraits): + trait = Unicode('base') + attr = 'base' + + class A(Base): + pass + + class B(Base): + trait = Unicode('B') + attr = 'B' + + class AB(A, B): + pass + + class BA(B, A): + pass + + assert A().trait == 'base' + assert A().attr == 'base' + assert BA().trait == 'B' + assert BA().attr == 'B' + assert AB().trait == 'B' + assert AB().attr == 'B' + + +def test_cls_self_argument(): + class X(HasTraits): + def __init__(__self, cls, self): + pass + + x = X(cls=None, self=None) + + +def test_override_default(): + class C(HasTraits): + a = Unicode('hard default') + def _a_default(self): + return 'default method' + + C._a_default = lambda self: 'overridden' + c = C() + assert c.a == 'overridden' + +def test_override_default_decorator(): + class C(HasTraits): + a = Unicode('hard default') + @default('a') + def _a_default(self): + return 'default method' + + C._a_default = lambda self: 'overridden' + c = C() + assert c.a == 'overridden' + +def test_override_default_instance(): + class C(HasTraits): + a = Unicode('hard default') + @default('a') + def _a_default(self): + return 'default method' + + c = C() + c._a_default = lambda self: 'overridden' + assert c.a == 'overridden' + + +def test_copy_HasTraits(): + from copy import copy + + class C(HasTraits): + a = Int() + + c = C(a=1) + assert c.a == 1 + + cc = copy(c) + cc.a = 2 + assert cc.a == 2 + assert c.a == 1 + + +def _from_string_test(traittype, s, expected): + """Run a test of trait.from_string""" + if isinstance(traittype, TraitType): + trait = traittype + else: + trait = traittype(allow_none=True) + if isinstance(s, list): + cast = trait.from_string_list + else: + cast = trait.from_string + if type(expected) is type and issubclass(expected, Exception): + with pytest.raises(expected): + value = cast(s) + trait.validate(None, value) + else: + value = cast(s) + assert value == expected + + +@pytest.mark.parametrize( + "s, expected", + [("xyz", "xyz"), ("1", "1"), ('"xx"', "xx"), ("'abc'", "abc"), ("None", None)], +) +def test_unicode_from_string(s, expected): + _from_string_test(Unicode, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [("xyz", "xyz"), ("1", "1"), ('"xx"', "xx"), ("'abc'", "abc"), ("None", None)], +) +def test_cunicode_from_string(s, expected): + _from_string_test(CUnicode, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [("xyz", b"xyz"), ("1", b"1"), ('b"xx"', b"xx"), ("b'abc'", b"abc"), ("None", None)], +) +def test_bytes_from_string(s, expected): + _from_string_test(Bytes, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [("xyz", b"xyz"), ("1", b"1"), ('b"xx"', b"xx"), ("b'abc'", b"abc"), ("None", None)], +) +def test_cbytes_from_string(s, expected): + _from_string_test(CBytes, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [("x", ValueError), ("1", 1), ("123", 123), ("2.0", ValueError), ("None", None)], +) +def test_int_from_string(s, expected): + _from_string_test(Integer, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [("x", ValueError), ("1", 1.0), ("123.5", 123.5), ("2.5", 2.5), ("None", None)], +) +def test_float_from_string(s, expected): + _from_string_test(Float, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("x", ValueError), + ("1", 1.0), + ("123.5", 123.5), + ("2.5", 2.5), + ("1+2j", 1 + 2j), + ("None", None), + ], +) +def test_complex_from_string(s, expected): + _from_string_test(Complex, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("true", True), + ("TRUE", True), + ("1", True), + ("0", False), + ("False", False), + ("false", False), + ("1.0", ValueError), + ("None", None), + ], +) +def test_bool_from_string(s, expected): + _from_string_test(Bool, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("{}", {}), + ("1", TraitError), + ("{1: 2}", {1: 2}), + ('{"key": "value"}', {"key": "value"}), + ("x", TraitError), + ("None", None), + ], +) +def test_dict_from_string(s, expected): + _from_string_test(Dict, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("[]", []), + ('[1, 2, "x"]', [1, 2, "x"]), + (["1", "x"], ["1", "x"]), + (["None"], None), + ], +) +def test_list_from_string(s, expected): + _from_string_test(List, s, expected) + + +@pytest.mark.parametrize( + "s, expected, value_trait", + [ + (["1", "2", "3"], [1, 2, 3], Integer()), + (["x"], ValueError, Integer()), + (["1", "x"], ["1", "x"], Unicode()), + (["None"], [None], Unicode(allow_none=True)), + (["None"], ["None"], Unicode(allow_none=False)), + ], +) +def test_list_items_from_string(s, expected, value_trait): + _from_string_test(List(value_trait), s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("[]", set()), + ('[1, 2, "x"]', {1, 2, "x"}), + ('{1, 2, "x"}', {1, 2, "x"}), + (["1", "x"], {"1", "x"}), + (["None"], None), + ], +) +def test_set_from_string(s, expected): + _from_string_test(Set, s, expected) + + +@pytest.mark.parametrize( + "s, expected, value_trait", + [ + (["1", "2", "3"], {1, 2, 3}, Integer()), + (["x"], ValueError, Integer()), + (["1", "x"], {"1", "x"}, Unicode()), + (["None"], {None}, Unicode(allow_none=True)), + ], +) +def test_set_items_from_string(s, expected, value_trait): + _from_string_test(Set(value_trait), s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("[]", ()), + ("()", ()), + ('[1, 2, "x"]', (1, 2, "x")), + ('(1, 2, "x")', (1, 2, "x")), + (["1", "x"], ("1", "x")), + (["None"], None), + ], +) +def test_tuple_from_string(s, expected): + _from_string_test(Tuple, s, expected) + + +@pytest.mark.parametrize( + "s, expected, value_traits", + [ + (["1", "2", "3"], (1, 2, 3), [Integer(), Integer(), Integer()]), + (["x"], ValueError, [Integer()]), + (["1", "x"], ("1", "x"), [Unicode()]), + (["None"], ("None",), [Unicode(allow_none=False)]), + (["None"], (None,), [Unicode(allow_none=True)]), + ], +) +def test_tuple_items_from_string(s, expected, value_traits): + _from_string_test(Tuple(*value_traits), s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("x", "x"), + ("mod.submod", "mod.submod"), + ("not an identifier", TraitError), + ("1", "1"), + ("None", None), + ], +) +def test_object_from_string(s, expected): + _from_string_test(DottedObjectName, s, expected) + + +@pytest.mark.parametrize( + "s, expected", + [ + ("127.0.0.1:8000", ("127.0.0.1", 8000)), + ("host.tld:80", ("host.tld", 80)), + ("host:notaport", ValueError), + ("127.0.0.1", ValueError), + ("None", None), + ], +) +def test_tcp_from_string(s, expected): + _from_string_test(TCPAddress, s, expected) diff --git a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets_enum.py b/contrib/python/traitlets/py3/traitlets/tests/test_traitlets_enum.py index 6fe49898be..769e830b33 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets_enum.py +++ b/contrib/python/traitlets/py3/traitlets/tests/test_traitlets_enum.py @@ -5,13 +5,13 @@ Test the trait-type ``UseEnum``. import unittest import enum -from traitlets import HasTraits, TraitError, Enum, UseEnum, CaselessStrEnum, FuzzyEnum +from traitlets import HasTraits, TraitError, Enum, UseEnum, CaselessStrEnum, FuzzyEnum # ----------------------------------------------------------------------------- # TEST SUPPORT: # ----------------------------------------------------------------------------- - + class Color(enum.Enum): red = 1 green = 2 @@ -23,16 +23,16 @@ class OtherColor(enum.Enum): green = 1 -class CSColor(enum.Enum): - red = 1 - Green = 2 - BLUE = 3 - YeLLoW = 4 - - -color_choices = 'red Green BLUE YeLLoW'.split() - - +class CSColor(enum.Enum): + red = 1 + Green = 2 + BLUE = 3 + YeLLoW = 4 + + +color_choices = 'red Green BLUE YeLLoW'.split() + + # ----------------------------------------------------------------------------- # TESTSUITE: # ----------------------------------------------------------------------------- @@ -73,7 +73,7 @@ class TestUseEnum(unittest.TestCase): # pylint: disable=no-member enum_names = [enum_val.name for enum_val in Color.__members__.values()] for value in enum_names: - self.assertIsInstance(value, str) + self.assertIsInstance(value, str) example = self.Example() enum_value = Color.__members__.get(value) example.color = value @@ -188,193 +188,193 @@ class TestUseEnum(unittest.TestCase): with self.assertRaises(TraitError): example.color = "BAD_VALUE" - def test_info(self): - import sys - - choices = color_choices - class Example(HasTraits): - enum1 = Enum(choices, allow_none=False) - enum2 = CaselessStrEnum(choices, allow_none=False) - enum3 = FuzzyEnum(choices, allow_none=False) - enum4 = UseEnum(CSColor, allow_none=False) - - for i in range(1,5): - attr = 'enum%s' % i - enum = getattr(Example, attr) - - enum.allow_none = True - - info = enum.info() - self.assertEqual(len(info.split(', ')), len(choices), info.split(', ')) - self.assertIn('or None', info) - - info = enum.info_rst() - self.assertEqual(len(info.split('|')), len(choices), info.split('|')) - self.assertIn('or `None`', info) - ## Check no single `\` exists. - self.assertNotRegex(info, r'\b\\\b') - - enum.allow_none = False - - info = enum.info() - self.assertEqual(len(info.split(', ')), len(choices), info.split(', ')) - self.assertNotIn('None', info) - - info = enum.info_rst() - self.assertEqual(len(info.split('|')), len(choices), info.split('|')) - self.assertNotIn('None', info) - ## Check no single `\` exists. - self.assertNotRegex(info, r'\b\\\b') - - - -# ----------------------------------------------------------------------------- -# TESTSUITE: -# ----------------------------------------------------------------------------- - -class TestFuzzyEnum(unittest.TestCase): - ## Check mostly `validate()`, Ctor must be checked on generic `Enum` - # or `CaselessStrEnum`. - - def test_search_all_prefixes__overwrite(self): - class FuzzyExample(HasTraits): - color = FuzzyEnum(color_choices, help="Color enum") - - example = FuzzyExample() - for color in color_choices: - for wlen in range(1, len(color)): - value = color[:wlen] - - example.color = value - self.assertEqual(example.color, color) - - example.color = value.upper() - self.assertEqual(example.color, color) - - example.color = value.lower() - self.assertEqual(example.color, color) - - def test_search_all_prefixes__ctor(self): - class FuzzyExample(HasTraits): - color = FuzzyEnum(color_choices, help="Color enum") - - for color in color_choices: - for wlen in range(1, len(color)): - value = color[:wlen] - - example = FuzzyExample() - example.color = value - self.assertEqual(example.color, color) - - example = FuzzyExample() - example.color = value.upper() - self.assertEqual(example.color, color) - - example = FuzzyExample() - example.color = value.lower() - self.assertEqual(example.color, color) - - def test_search_substrings__overwrite(self): - class FuzzyExample(HasTraits): - color = FuzzyEnum(color_choices, help="Color enum", - substring_matching=True) - - example = FuzzyExample() - for color in color_choices: - for wlen in range(0, 2): - value = color[wlen:] - - example.color = value - self.assertEqual(example.color, color) - - example.color = value.upper() - self.assertEqual(example.color, color) - - example.color = value.lower() - self.assertEqual(example.color, color) - - def test_search_substrings__ctor(self): - class FuzzyExample(HasTraits): - color = FuzzyEnum(color_choices, help="Color enum", - substring_matching=True) - - color = color_choices[-1] # 'YeLLoW' - for end in (-1, len(color)): - for start in range(1, len(color) - 2): - value = color[start:end] - - example = FuzzyExample() - example.color = value - self.assertEqual(example.color, color) - - example = FuzzyExample() - example.color = value.upper() - self.assertEqual(example.color, color) - - def test_assign_other_raises(self): - def new_trait_class(case_sensitive, substring_matching): - class Example(HasTraits): - color = FuzzyEnum(color_choices, - case_sensitive=case_sensitive, - substring_matching=substring_matching) - - return Example - - example = new_trait_class(case_sensitive=False, substring_matching=False)() - with self.assertRaises(TraitError): - example.color = '' - with self.assertRaises(TraitError): - example.color = 'BAD COLOR' - with self.assertRaises(TraitError): - example.color = 'ed' - - example = new_trait_class(case_sensitive=True, substring_matching=False)() - with self.assertRaises(TraitError): - example.color = '' - with self.assertRaises(TraitError): - example.color = 'Red' # not 'red' - - example = new_trait_class(case_sensitive=True, substring_matching=True)() - with self.assertRaises(TraitError): - example.color = '' - with self.assertRaises(TraitError): - example.color = 'BAD COLOR' - with self.assertRaises(TraitError): - example.color = 'green' # not 'Green' - with self.assertRaises(TraitError): - example.color = 'lue' # not (b)'LUE' - with self.assertRaises(TraitError): - example.color = 'lUE' # not (b)'LUE' - - example = new_trait_class(case_sensitive=False, substring_matching=True)() - with self.assertRaises(TraitError): - example.color = '' - with self.assertRaises(TraitError): - example.color = 'BAD COLOR' - - def test_ctor_with_default_value(self): - def new_trait_class(default_value, - case_sensitive, substring_matching): - class Example(HasTraits): - color = FuzzyEnum(color_choices, - default_value=default_value, - case_sensitive=case_sensitive, - substring_matching=substring_matching) - - return Example - - for color in color_choices: - example = new_trait_class(color, False, False)() - self.assertEqual(example.color, color) - - example = new_trait_class(color.upper(), False, False)() - self.assertEqual(example.color, color) - - color = color_choices[-1] # 'YeLLoW' - example = new_trait_class(color, True, False)() - self.assertEqual(example.color, color) - - ## FIXME: default value not validated! - #with self.assertRaises(TraitError): - # example = new_trait_class(color.lower(), True, False) - + def test_info(self): + import sys + + choices = color_choices + class Example(HasTraits): + enum1 = Enum(choices, allow_none=False) + enum2 = CaselessStrEnum(choices, allow_none=False) + enum3 = FuzzyEnum(choices, allow_none=False) + enum4 = UseEnum(CSColor, allow_none=False) + + for i in range(1,5): + attr = 'enum%s' % i + enum = getattr(Example, attr) + + enum.allow_none = True + + info = enum.info() + self.assertEqual(len(info.split(', ')), len(choices), info.split(', ')) + self.assertIn('or None', info) + + info = enum.info_rst() + self.assertEqual(len(info.split('|')), len(choices), info.split('|')) + self.assertIn('or `None`', info) + ## Check no single `\` exists. + self.assertNotRegex(info, r'\b\\\b') + + enum.allow_none = False + + info = enum.info() + self.assertEqual(len(info.split(', ')), len(choices), info.split(', ')) + self.assertNotIn('None', info) + + info = enum.info_rst() + self.assertEqual(len(info.split('|')), len(choices), info.split('|')) + self.assertNotIn('None', info) + ## Check no single `\` exists. + self.assertNotRegex(info, r'\b\\\b') + + + +# ----------------------------------------------------------------------------- +# TESTSUITE: +# ----------------------------------------------------------------------------- + +class TestFuzzyEnum(unittest.TestCase): + ## Check mostly `validate()`, Ctor must be checked on generic `Enum` + # or `CaselessStrEnum`. + + def test_search_all_prefixes__overwrite(self): + class FuzzyExample(HasTraits): + color = FuzzyEnum(color_choices, help="Color enum") + + example = FuzzyExample() + for color in color_choices: + for wlen in range(1, len(color)): + value = color[:wlen] + + example.color = value + self.assertEqual(example.color, color) + + example.color = value.upper() + self.assertEqual(example.color, color) + + example.color = value.lower() + self.assertEqual(example.color, color) + + def test_search_all_prefixes__ctor(self): + class FuzzyExample(HasTraits): + color = FuzzyEnum(color_choices, help="Color enum") + + for color in color_choices: + for wlen in range(1, len(color)): + value = color[:wlen] + + example = FuzzyExample() + example.color = value + self.assertEqual(example.color, color) + + example = FuzzyExample() + example.color = value.upper() + self.assertEqual(example.color, color) + + example = FuzzyExample() + example.color = value.lower() + self.assertEqual(example.color, color) + + def test_search_substrings__overwrite(self): + class FuzzyExample(HasTraits): + color = FuzzyEnum(color_choices, help="Color enum", + substring_matching=True) + + example = FuzzyExample() + for color in color_choices: + for wlen in range(0, 2): + value = color[wlen:] + + example.color = value + self.assertEqual(example.color, color) + + example.color = value.upper() + self.assertEqual(example.color, color) + + example.color = value.lower() + self.assertEqual(example.color, color) + + def test_search_substrings__ctor(self): + class FuzzyExample(HasTraits): + color = FuzzyEnum(color_choices, help="Color enum", + substring_matching=True) + + color = color_choices[-1] # 'YeLLoW' + for end in (-1, len(color)): + for start in range(1, len(color) - 2): + value = color[start:end] + + example = FuzzyExample() + example.color = value + self.assertEqual(example.color, color) + + example = FuzzyExample() + example.color = value.upper() + self.assertEqual(example.color, color) + + def test_assign_other_raises(self): + def new_trait_class(case_sensitive, substring_matching): + class Example(HasTraits): + color = FuzzyEnum(color_choices, + case_sensitive=case_sensitive, + substring_matching=substring_matching) + + return Example + + example = new_trait_class(case_sensitive=False, substring_matching=False)() + with self.assertRaises(TraitError): + example.color = '' + with self.assertRaises(TraitError): + example.color = 'BAD COLOR' + with self.assertRaises(TraitError): + example.color = 'ed' + + example = new_trait_class(case_sensitive=True, substring_matching=False)() + with self.assertRaises(TraitError): + example.color = '' + with self.assertRaises(TraitError): + example.color = 'Red' # not 'red' + + example = new_trait_class(case_sensitive=True, substring_matching=True)() + with self.assertRaises(TraitError): + example.color = '' + with self.assertRaises(TraitError): + example.color = 'BAD COLOR' + with self.assertRaises(TraitError): + example.color = 'green' # not 'Green' + with self.assertRaises(TraitError): + example.color = 'lue' # not (b)'LUE' + with self.assertRaises(TraitError): + example.color = 'lUE' # not (b)'LUE' + + example = new_trait_class(case_sensitive=False, substring_matching=True)() + with self.assertRaises(TraitError): + example.color = '' + with self.assertRaises(TraitError): + example.color = 'BAD COLOR' + + def test_ctor_with_default_value(self): + def new_trait_class(default_value, + case_sensitive, substring_matching): + class Example(HasTraits): + color = FuzzyEnum(color_choices, + default_value=default_value, + case_sensitive=case_sensitive, + substring_matching=substring_matching) + + return Example + + for color in color_choices: + example = new_trait_class(color, False, False)() + self.assertEqual(example.color, color) + + example = new_trait_class(color.upper(), False, False)() + self.assertEqual(example.color, color) + + color = color_choices[-1] # 'YeLLoW' + example = new_trait_class(color, True, False)() + self.assertEqual(example.color, color) + + ## FIXME: default value not validated! + #with self.assertRaises(TraitError): + # example = new_trait_class(color.lower(), True, False) + diff --git a/contrib/python/traitlets/py3/traitlets/tests/utils.py b/contrib/python/traitlets/py3/traitlets/tests/utils.py index 95bd7c2750..c5ac591435 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/utils.py +++ b/contrib/python/traitlets/py3/traitlets/tests/utils.py @@ -1,5 +1,5 @@ -from subprocess import Popen, PIPE -import sys +from subprocess import Popen, PIPE +import sys import os @@ -38,5 +38,5 @@ def check_help_all_output(pkg, subcommand=None): assert rc == 0, err assert "Traceback" not in err assert "Options" in out - assert "Class options" in out + assert "Class options" in out return out, err diff --git a/contrib/python/traitlets/py3/traitlets/traitlets.py b/contrib/python/traitlets/py3/traitlets/traitlets.py index f39dd4602c..6bdf7414d3 100644 --- a/contrib/python/traitlets/py3/traitlets/traitlets.py +++ b/contrib/python/traitlets/py3/traitlets/traitlets.py @@ -39,7 +39,7 @@ Inheritance diagram: # Adapted from enthought.traits, Copyright (c) Enthought, Inc., # also under the terms of the Modified BSD License. -from ast import literal_eval +from ast import literal_eval import contextlib import inspect import os @@ -53,38 +53,38 @@ 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 +from .utils.descriptions import describe, class_of, add_article, repr_type SequenceTypes = (list, tuple, set, frozenset) -# backward compatibility, use to differ between Python 2 and 3. -ClassTypes = (type,) - -# exports: - -__all__ = [ - "default", - "validate", - "observe", - "observe_compat", - "link", - "directional_link", - "dlink", - "Undefined", - "All", - "NoDefaultSpecified", - "TraitError", - "HasDescriptors", - "HasTraits", - "MetaHasDescriptors", - "MetaHasTraits", - "BaseDescriptor", - "TraitType", - "parse_notifier_name", -] - -# any TraitType subclass (that doesn't start with _) will be added automatically - +# backward compatibility, use to differ between Python 2 and 3. +ClassTypes = (type,) + +# exports: + +__all__ = [ + "default", + "validate", + "observe", + "observe_compat", + "link", + "directional_link", + "dlink", + "Undefined", + "All", + "NoDefaultSpecified", + "TraitError", + "HasDescriptors", + "HasTraits", + "MetaHasDescriptors", + "MetaHasTraits", + "BaseDescriptor", + "TraitType", + "parse_notifier_name", +] + +# any TraitType subclass (that doesn't start with _) will be added automatically + #----------------------------------------------------------------------------- # Basic classes #----------------------------------------------------------------------------- @@ -116,7 +116,7 @@ class TraitError(Exception): _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") def isidentifier(s): - return s.isidentifier() + return s.isidentifier() _deprecations_shown = set() def _should_warn(key): @@ -156,23 +156,23 @@ def _deprecated_method(method, cls, method_name, msg): try: fname = inspect.getsourcefile(method) or "<unknown>" lineno = inspect.getsourcelines(method)[1] or 0 - except (OSError, TypeError) as e: + 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) -def _safe_literal_eval(s): - """Safely evaluate an expression +def _safe_literal_eval(s): + """Safely evaluate an expression - Returns original string if eval fails. + Returns original string if eval fails. - Use only where types are ambiguous. + Use only where types are ambiguous. """ - try: - return literal_eval(s) - except (NameError, SyntaxError, ValueError): - return s + 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. @@ -188,20 +188,20 @@ def parse_notifier_name(names): -------- >>> parse_notifier_name([]) [All] - >>> parse_notifier_name("a") + >>> parse_notifier_name("a") ['a'] - >>> parse_notifier_name(["a", "b"]) + >>> parse_notifier_name(["a", "b"]) ['a', 'b'] >>> parse_notifier_name(All) [All] """ - if names is All or isinstance(names, str): + if names is All or isinstance(names, str): return [names] else: if not names or All in names: return [All] for n in names: - if not isinstance(n, str): + if not isinstance(n, str): raise TypeError("names must be strings, not %r" % n) return names @@ -253,32 +253,32 @@ class link(object): ---------- 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. + transform: iterable with two callables (optional) + Data transformation between source and target and target and source. Examples -------- - >>> c = link((src, "value"), (tgt, "value")) + >>> c = link((src, "value"), (tgt, "value")) >>> src.value = 5 # updates other objects as well """ updating = False - def __init__(self, source, target, transform=None): + def __init__(self, source, target, transform=None): _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): + self._transform, self._transform_inv = ( + transform if transform else (lambda x: x,) * 2) + + self.link() + + def link(self): try: - setattr(self.target[0], self.target[1], - self._transform(getattr(self.source[0], self.source[1]))) - + setattr(self.target[0], self.target[1], + self._transform(getattr(self.source[0], self.source[1]))) + finally: - self.source[0].observe(self._update_target, names=self.source[1]) - self.target[0].observe(self._update_source, names=self.target[1]) + 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): @@ -292,22 +292,22 @@ class link(object): 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)) + 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(): - 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)) + 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]) @@ -326,7 +326,7 @@ class directional_link(object): Examples -------- - >>> c = directional_link((src, "value"), (tgt, "value")) + >>> c = directional_link((src, "value"), (tgt, "value")) >>> src.value = 5 # updates target objects >>> tgt.value = 6 # does not update source object """ @@ -336,12 +336,12 @@ class directional_link(object): self._transform = transform if transform else lambda x: x _validate_link(source, target) self.source, self.target = source, target - self.link() - - def link(self): + self.link() + + def link(self): try: - setattr(self.target[0], self.target[1], - self._transform(getattr(self.source[0], self.source[1]))) + 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]) @@ -376,7 +376,7 @@ class BaseDescriptor(object): Notes ----- - This implements Python's descriptor protocol. + 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` @@ -406,9 +406,9 @@ class BaseDescriptor(object): self.this_class = cls self.name = name - def subclass_init(self, cls): - pass - + def subclass_init(self, cls): + pass + def instance_init(self, obj): """Part of the initialization which may depend on the underlying HasDescriptors instance. @@ -430,7 +430,7 @@ class TraitType(BaseDescriptor): allow_none = False read_only = False info_text = 'any value' - default_value = Undefined + default_value = Undefined def __init__(self, default_value=Undefined, allow_none=False, read_only=None, help=None, config=None, **kwargs): @@ -439,9 +439,9 @@ class TraitType(BaseDescriptor): 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. - + + 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. """ @@ -483,38 +483,38 @@ class TraitType(BaseDescriptor): if help is not None: self.metadata['help'] = help - def from_string(self, s): - """Get a value from a config string - - such as an environment variable or CLI arguments. - - Traits can override this method to define their own - parsing of config strings. - - .. seealso:: item_from_string - - .. versionadded:: 5.0 - """ - if self.allow_none and s == 'None': - return None - return s - - def default(self, obj=None): - """The default generator for this trait - - Notes - ----- - This method is registered to HasTraits classes during ``class_init`` - in the same way that dynamic defaults defined by ``@default`` are. - """ - if self.default_value is not Undefined: - return self.default_value - elif hasattr(self, 'make_dynamic_default'): - return self.make_dynamic_default() - else: - # Undefined will raise in TraitType.get - return self.default_value - + def from_string(self, s): + """Get a value from a config string + + such as an environment variable or CLI arguments. + + Traits can override this method to define their own + parsing of config strings. + + .. seealso:: item_from_string + + .. versionadded:: 5.0 + """ + if self.allow_none and s == 'None': + return None + return s + + def default(self, obj=None): + """The default generator for this trait + + Notes + ----- + This method is registered to HasTraits classes during ``class_init`` + in the same way that dynamic defaults defined by ``@default`` are. + """ + if self.default_value is not Undefined: + return self.default_value + elif hasattr(self, 'make_dynamic_default'): + return self.make_dynamic_default() + else: + # 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 @@ -537,24 +537,24 @@ class TraitType(BaseDescriptor): value = obj._trait_values[self.name] except KeyError: # Check for a dynamic initializer. - default = obj.trait_defaults(self.name) - if default is Undefined: - warn( - "Explicit using of Undefined as the default value " - "is deprecated in traitlets 5.0, and may cause " - "exceptions in the future.", - DeprecationWarning, - stacklevel=2 - ) - with obj.cross_validation_lock: - value = self._validate(obj, default) + default = obj.trait_defaults(self.name) + if default is Undefined: + warn( + "Explicit using of Undefined as the default value " + "is deprecated in traitlets 5.0, and may cause " + "exceptions in the future.", + DeprecationWarning, + stacklevel=2 + ) + with obj.cross_validation_lock: + value = self._validate(obj, default) obj._trait_values[self.name] = value - obj._notify_observers(Bunch( - name=self.name, - value=value, - owner=obj, - type='default', - )) + obj._notify_observers(Bunch( + name=self.name, + value=value, + owner=obj, + type='default', + )) return value except Exception: # This should never be reached. @@ -586,7 +586,7 @@ class TraitType(BaseDescriptor): obj._trait_values[self.name] = new_value try: silent = bool(old_value == new_value) - except Exception: + except Exception: # if there is an error in comparing, default to notify silent = False if silent is not True: @@ -635,61 +635,61 @@ class TraitType(BaseDescriptor): def info(self): return self.info_text - def error(self, obj, value, error=None, info=None): - """Raise a TraitError - - Parameters - ---------- - obj : HasTraits or None - The instance which owns the trait. If not - object is given, then an object agnostic - error will be raised. - value : any - The value that caused the error. - error : Exception (default: None) - An error that was raised by a child trait. - The arguments of this exception should be - of the form ``(value, info, *traits)``. - Where the ``value`` and ``info`` are the - problem value, and string describing the - expected value. The ``traits`` are a series - of :class:`TraitType` instances that are - "children" of this one (the first being - the deepest). - info : str (default: None) - A description of the expected value. By - default this is infered from this trait's - ``info`` method. - """ - if error is not None: - # handle nested error - error.args += (self,) - if self.name is not None: - # this is the root trait that must format the final message - chain = " of ".join(describe("a", t) for t in error.args[2:]) - if obj is not None: - error.args = ("The '%s' trait of %s instance contains %s which " - "expected %s, not %s." % (self.name, describe("an", obj), - chain, error.args[1], describe("the", error.args[0])),) - else: - error.args = ("The '%s' trait contains %s which " - "expected %s, not %s." % (self.name, chain, - error.args[1], describe("the", error.args[0])),) - raise error + def error(self, obj, value, error=None, info=None): + """Raise a TraitError + + Parameters + ---------- + obj : HasTraits or None + The instance which owns the trait. If not + object is given, then an object agnostic + error will be raised. + value : any + The value that caused the error. + error : Exception (default: None) + An error that was raised by a child trait. + The arguments of this exception should be + of the form ``(value, info, *traits)``. + Where the ``value`` and ``info`` are the + problem value, and string describing the + expected value. The ``traits`` are a series + of :class:`TraitType` instances that are + "children" of this one (the first being + the deepest). + info : str (default: None) + A description of the expected value. By + default this is infered from this trait's + ``info`` method. + """ + if error is not None: + # handle nested error + error.args += (self,) + if self.name is not None: + # this is the root trait that must format the final message + chain = " of ".join(describe("a", t) for t in error.args[2:]) + if obj is not None: + error.args = ("The '%s' trait of %s instance contains %s which " + "expected %s, not %s." % (self.name, describe("an", obj), + chain, error.args[1], describe("the", error.args[0])),) + else: + error.args = ("The '%s' trait contains %s which " + "expected %s, not %s." % (self.name, chain, + error.args[1], describe("the", error.args[0])),) + raise error else: - # this trait caused an error - if self.name is None: - # this is not the root trait - raise TraitError(value, info or self.info(), self) - else: - # this is the root trait - if obj is not None: - e = "The '%s' trait of %s instance expected %s, not %s." % ( - self.name, class_of(obj), self.info(), describe("the", value)) - else: - e = "The '%s' trait expected %s, not %s." % ( - self.name, self.info(), describe("the", value)) - raise TraitError(e) + # this trait caused an error + if self.name is None: + # this is not the root trait + raise TraitError(value, info or self.info(), self) + else: + # this is the root trait + if obj is not None: + e = "The '%s' trait of %s instance expected %s, not %s." % ( + self.name, class_of(obj), self.info(), describe("the", value)) + else: + 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. @@ -817,11 +817,11 @@ class MetaHasDescriptors(type): if isinstance(v, BaseDescriptor): v.class_init(cls, k) - for k, v in getmembers(cls): - if isinstance(v, BaseDescriptor): - v.subclass_init(cls) + for k, v in getmembers(cls): + if isinstance(v, BaseDescriptor): + v.subclass_init(cls) + - class MetaHasTraits(MetaHasDescriptors): """A metaclass for HasTraits.""" @@ -830,7 +830,7 @@ class MetaHasTraits(MetaHasDescriptors): super(MetaHasTraits, cls).setup_class(classdict) -def observe(*names, type="change"): +def observe(*names, type="change"): """A decorator which can be used to observe Traits on a class. The handler passed to the decorator will be called with one ``change`` @@ -849,15 +849,15 @@ def observe(*names, type="change"): ---------- *names The str names of the Traits to observe on the object. - type : str, kwarg-only + 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): + 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) + return ObserveHandler(names, type=type) def observe_compat(func): @@ -897,14 +897,14 @@ def validate(*names): 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 + *names The str names of the Traits to validate. Notes @@ -919,7 +919,7 @@ def validate(*names): 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): + 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) @@ -960,7 +960,7 @@ def default(name): return 3.0 # ignored since it is defined in a # class derived from B.a.this_class. """ - if not isinstance(name, str): + if not isinstance(name, str): raise TypeError("Trait name must be a string or All, not %r" % name) return DefaultHandler(name) @@ -1009,19 +1009,19 @@ class DefaultHandler(EventHandler): self.trait_name = name def class_init(self, cls, name): - super().class_init(cls, name) + super().class_init(cls, name) cls._trait_default_generators[self.trait_name] = self -class HasDescriptors(metaclass=MetaHasDescriptors): +class HasDescriptors(metaclass=MetaHasDescriptors): """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:] - + 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__ @@ -1032,14 +1032,14 @@ class HasDescriptors(metaclass=MetaHasDescriptors): inst.setup_instance(*args, **kwargs) return inst - def setup_instance(*args, **kwargs): + def setup_instance(*args, **kwargs): """ This is called **before** self.__init__ is called. """ - # Pass self as args[0] to allow "self" as keyword argument - self = args[0] - args = args[1:] - + # Pass self as args[0] to allow "self" as keyword argument + self = args[0] + args = args[1:] + self._cross_validation_lock = False cls = self.__class__ for key in dir(cls): @@ -1055,13 +1055,13 @@ class HasDescriptors(metaclass=MetaHasDescriptors): value.instance_init(self) -class HasTraits(HasDescriptors, metaclass=MetaHasTraits): +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:] - 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 = {} @@ -1088,7 +1088,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): arg_s_list.append("%s=%r" % (k, v)) arg_s = ', '.join(arg_s_list) warn( - "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n" + "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n" "{error}\n" "This is deprecated in traitlets 4.2." "This error will be raised in a future release of traitlets." @@ -1107,9 +1107,9 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): # 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! - + d['_trait_values'] = self._trait_values.copy() + d['_cross_validation_lock'] = False # FIXME: raise if cloning locked! + return d def __setstate__(self, state): @@ -1225,15 +1225,15 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): )) def notify_change(self, change): - """Notify observers of a change event""" - return self._notify_observers(change) - - def _notify_observers(self, event): - """Notify observers of any event""" - if not isinstance(event, Bunch): + """Notify observers of a change event""" + return self._notify_observers(change) + + def _notify_observers(self, event): + """Notify observers of any event""" + if not isinstance(event, Bunch): # cast to bunch if given a dict - event = Bunch(event) - name, type = event.name, event.type + event = Bunch(event) + name, type = event.name, event.type callables = [] callables.extend(self._trait_notifiers.get(name, {}).get(type, [])) @@ -1243,7 +1243,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): # Now static ones magic_name = '_%s_changed' % name - if event.type == "change" and hasattr(self, magic_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, @@ -1263,7 +1263,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): elif isinstance(c, EventHandler) and c.name is not None: c = getattr(self, c.name) - c(event) + c(event) def _add_notifiers(self, handler, name, type): if name not in self._trait_notifiers: @@ -1411,20 +1411,20 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): 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, + _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.""" - cls = self.__class__ - attrs = {"__module__": cls.__module__} - if hasattr(cls, "__qualname__"): - # __qualname__ introduced in Python 3.3 (see PEP 3155) - attrs["__qualname__"] = cls.__qualname__ - attrs.update(traits) - self.__class__ = type(cls.__name__, (cls,), attrs) + cls = self.__class__ + attrs = {"__module__": cls.__module__} + if hasattr(cls, "__qualname__"): + # __qualname__ introduced in Python 3.3 (see PEP 3155) + attrs["__qualname__"] = cls.__qualname__ + attrs.update(traits) + self.__class__ = type(cls.__name__, (cls,), attrs) for trait in traits.values(): trait.instance_init(self) @@ -1472,7 +1472,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): result = {} for name, trait in traits.items(): for meta_name, meta_eval in metadata.items(): - if not callable(meta_eval): + if not callable(meta_eval): meta_eval = _SimpleTest(meta_eval) if not meta_eval(trait.metadata.get(meta_name, None)): break @@ -1495,98 +1495,98 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): """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. - - This will return false even if ``getattr`` would return a - dynamically generated default value. These default values - will be recognized as existing only after they have been - generated. - - Example - - .. code-block:: python - - class MyClass(HasTraits): - i = Int() - - mc = MyClass() - assert not mc.trait_has_value("i") - mc.i # generates a default value - assert mc.trait_has_value("i") - """ - return name in self._trait_values - - def trait_values(self, **metadata): - """A ``dict`` of trait names and their values. - - 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. - - Returns - ------- - A ``dict`` of trait names and their values. - - Notes - ----- - Trait values are retrieved via ``getattr``, any exceptions raised - by traits or the operations they may trigger will result in the - absence of a trait value in the result ``dict``. - """ - return {name: getattr(self, name) for name in self.trait_names(**metadata)} - - def _get_trait_default_generator(self, name): - """Return default generator for a given trait - - Walk the MRO to resolve the correct default generator according to inheritance. - """ - method_name = '_%s_default' % name - if method_name in self.__dict__: - return getattr(self, method_name) - cls = self.__class__ - trait = getattr(cls, name) - assert isinstance(trait, TraitType) - # truncate mro to the class on which the trait is defined - mro = cls.mro() - try: - mro = mro[:mro.index(trait.this_class) + 1] - except ValueError: - # this_class not in mro - pass - for c in mro: - if method_name in c.__dict__: - return getattr(c, method_name) - if name in c.__dict__.get('_trait_default_generators', {}): - return c._trait_default_generators[name] - return trait.default - - def trait_defaults(self, *names, **metadata): - """Return a trait's default value or a dictionary of them - - Notes - ----- - Dynamically generated default values may - depend on the current state of the object.""" - for n in names: - if not self.has_trait(n): - raise TraitError("'%s' is not a trait of '%s' " - "instances" % (n, type(self).__name__)) - - if len(names) == 1 and len(metadata) == 0: - return self._get_trait_default_generator(names[0])(self) - - trait_names = self.trait_names(**metadata) - trait_names.extend(names) - - defaults = {} - for n in trait_names: - defaults[n] = self._get_trait_default_generator(n)(self) - return defaults - + def trait_has_value(self, name): + """Returns True if the specified trait has a value. + + This will return false even if ``getattr`` would return a + dynamically generated default value. These default values + will be recognized as existing only after they have been + generated. + + Example + + .. code-block:: python + + class MyClass(HasTraits): + i = Int() + + mc = MyClass() + assert not mc.trait_has_value("i") + mc.i # generates a default value + assert mc.trait_has_value("i") + """ + return name in self._trait_values + + def trait_values(self, **metadata): + """A ``dict`` of trait names and their values. + + 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. + + Returns + ------- + A ``dict`` of trait names and their values. + + Notes + ----- + Trait values are retrieved via ``getattr``, any exceptions raised + by traits or the operations they may trigger will result in the + absence of a trait value in the result ``dict``. + """ + return {name: getattr(self, name) for name in self.trait_names(**metadata)} + + def _get_trait_default_generator(self, name): + """Return default generator for a given trait + + Walk the MRO to resolve the correct default generator according to inheritance. + """ + method_name = '_%s_default' % name + if method_name in self.__dict__: + return getattr(self, method_name) + cls = self.__class__ + trait = getattr(cls, name) + assert isinstance(trait, TraitType) + # truncate mro to the class on which the trait is defined + mro = cls.mro() + try: + mro = mro[:mro.index(trait.this_class) + 1] + except ValueError: + # this_class not in mro + pass + for c in mro: + if method_name in c.__dict__: + return getattr(c, method_name) + if name in c.__dict__.get('_trait_default_generators', {}): + return c._trait_default_generators[name] + return trait.default + + def trait_defaults(self, *names, **metadata): + """Return a trait's default value or a dictionary of them + + Notes + ----- + Dynamically generated default values may + depend on the current state of the object.""" + for n in names: + if not self.has_trait(n): + raise TraitError("'%s' is not a trait of '%s' " + "instances" % (n, type(self).__name__)) + + if len(names) == 1 and len(metadata) == 0: + return self._get_trait_default_generator(names[0])(self) + + trait_names = self.trait_names(**metadata) + trait_names.extend(names) + + defaults = {} + for n in trait_names: + 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.""" return list(self.traits(**metadata)) @@ -1614,7 +1614,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): result = {} for name, trait in traits.items(): for meta_name, meta_eval in metadata.items(): - if not callable(meta_eval): + if not callable(meta_eval): meta_eval = _SimpleTest(meta_eval) if not meta_eval(trait.metadata.get(meta_name, None)): break @@ -1652,7 +1652,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): Parameters ---------- - name : str (default: None) + name : str (default: None) The name of a trait of this class. If name is ``None`` then all the event handlers of this class will be returned instead. @@ -1697,7 +1697,7 @@ class ClassBasedTraitType(TraitType): class Type(ClassBasedTraitType): """A trait whose value must be a subclass of a specified class.""" - def __init__(self, default_value=Undefined, klass=None, **kwargs): + def __init__(self, default_value=Undefined, klass=None, **kwargs): """Construct a Type trait A Type trait specifies that its values must be subclasses of @@ -1720,8 +1720,8 @@ class Type(ClassBasedTraitType): :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` + **kwargs + extra kwargs passed to `ClassBasedTraitType` """ if default_value is Undefined: new_default_value = object if (klass is None) else klass @@ -1734,16 +1734,16 @@ class Type(ClassBasedTraitType): else: klass = default_value - if not (inspect.isclass(klass) or isinstance(klass, str)): + if not (inspect.isclass(klass) or isinstance(klass, str)): raise TraitError("A Type trait must specify a class.") self.klass = klass - super().__init__(new_default_value, **kwargs) + super().__init__(new_default_value, **kwargs) def validate(self, obj, value): """Validates that the value is a valid object instance.""" - if isinstance(value, str): + if isinstance(value, str): try: value = self._resolve_string(value) except ImportError: @@ -1752,14 +1752,14 @@ class Type(ClassBasedTraitType): try: if issubclass(value, self.klass): return value - except Exception: + except Exception: pass self.error(obj, value) def info(self): """ Returns a description of the trait.""" - if isinstance(self.klass, str): + if isinstance(self.klass, str): klass = self.klass else: klass = self.klass.__module__ + '.' + self.klass.__name__ @@ -1770,20 +1770,20 @@ class Type(ClassBasedTraitType): def instance_init(self, obj): self._resolve_classes() - super().instance_init(obj) + super().instance_init(obj) def _resolve_classes(self): - if isinstance(self.klass, str): + if isinstance(self.klass, str): self.klass = self._resolve_string(self.klass) - if isinstance(self.default_value, str): + if isinstance(self.default_value, str): self.default_value = self._resolve_string(self.default_value) def default_value_repr(self): value = self.default_value - if isinstance(value, str): + if isinstance(value, str): return repr(value) else: - return repr(f'{value.__module__}.{value.__name__}') + return repr(f'{value.__module__}.{value.__name__}') class Instance(ClassBasedTraitType): @@ -1815,8 +1815,8 @@ class Instance(ClassBasedTraitType): 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` + **kwargs + Extra kwargs passed to `ClassBasedTraitType` Notes ----- @@ -1828,7 +1828,7 @@ class Instance(ClassBasedTraitType): if klass is None: klass = self.klass - if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)): + 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' @@ -1851,20 +1851,20 @@ class Instance(ClassBasedTraitType): self.error(obj, value) def info(self): - if isinstance(self.klass, str): - result = add_article(self.klass) + if isinstance(self.klass, str): + result = add_article(self.klass) else: - result = describe("a", self.klass) + result = describe("a", self.klass) if self.allow_none: - result += ' or None' + result += ' or None' return result def instance_init(self, obj): self._resolve_classes() - super().instance_init(obj) + super().instance_init(obj) def _resolve_classes(self): - if isinstance(self.klass, str): + if isinstance(self.klass, str): self.klass = self._resolve_string(self.klass) def make_dynamic_default(self): @@ -1876,10 +1876,10 @@ class Instance(ClassBasedTraitType): def default_value_repr(self): return repr(self.make_dynamic_default()) - def from_string(self, s): - return _safe_literal_eval(s) + def from_string(self, s): + return _safe_literal_eval(s) + - class ForwardDeclaredMixin(object): """ Mixin for forward-declared versions of Instance and Type. @@ -1942,7 +1942,7 @@ class Union(TraitType): Parameters ---------- - trait_types : sequence + trait_types : sequence The list of trait types of length at least 1. Notes @@ -1950,26 +1950,26 @@ class Union(TraitType): 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.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: - if default is Undefined: - default = t.default(obj) - else: - break - return default - + def default(self, obj=None): + default = super(Union, self).default(obj) + for t in self.trait_types: + if default is Undefined: + default = t.default(obj) + else: + break + return default + def class_init(self, cls, name): - for trait_type in reversed(self.trait_types): + 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): - for trait_type in reversed(self.trait_types): + for trait_type in reversed(self.trait_types): trait_type.instance_init(obj) super(Union, self).instance_init(obj) @@ -2001,7 +2001,7 @@ class Union(TraitType): class Any(TraitType): """A trait which allows any value.""" default_value = None - allow_none = True + allow_none = True info_text = 'any value' @@ -2046,25 +2046,25 @@ class Int(TraitType): 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) + 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: value = int(value) - except Exception: + except Exception: self.error(obj, value) return _validate_bounds(self, obj, value) -Long, CLong = Int, CInt -Integer = Int +Long, CLong = Int, CInt +Integer = Int class Float(TraitType): @@ -2086,19 +2086,19 @@ class Float(TraitType): 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) + 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: value = float(value) - except Exception: + except Exception: self.error(obj, value) return _validate_bounds(self, obj, value) @@ -2116,19 +2116,19 @@ class Complex(TraitType): return complex(value) self.error(obj, value) - def from_string(self, s): - if self.allow_none and s == 'None': - return None - return complex(s) + 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) - except Exception: + except Exception: self.error(obj, value) # We should always be explicit about whether we're using bytes or unicode, both @@ -2145,41 +2145,41 @@ class Bytes(TraitType): return value self.error(obj, value) - def from_string(self, s): - if self.allow_none and s == "None": - return None - if len(s) >= 3: - # handle deprecated b"string" - for quote in ('"', "'"): - if s[:2] == f"b{quote}" and s[-1] == quote: - old_s = s - s = s[2:-1] - warn( - "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. " - "Use %r instead of %r." % (s, old_s), - FutureWarning) - break - return s.encode("utf8") - - + def from_string(self, s): + if self.allow_none and s == "None": + return None + if len(s) >= 3: + # handle deprecated b"string" + for quote in ('"', "'"): + if s[:2] == f"b{quote}" and s[-1] == quote: + old_s = s + s = s[2:-1] + warn( + "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. " + "Use %r instead of %r." % (s, old_s), + 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) - except Exception: + except Exception: self.error(obj, value) class Unicode(TraitType): """A trait for unicode strings.""" - default_value = '' + default_value = '' info_text = 'a unicode string' def validate(self, obj, value): - if isinstance(value, str): + if isinstance(value, str): return value if isinstance(value, bytes): try: @@ -2189,31 +2189,31 @@ class Unicode(TraitType): 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 - s = os.path.expanduser(s) - if len(s) >= 2: - # handle deprecated "1" - for c in ('"', "'"): - if s[0] == s[-1] == c: - old_s = s - s = s[1:-1] - warn( - "Supporting extra quotes around strings is deprecated in traitlets 5.0. " - "You can use %r instead of %r if you require traitlets >=5." % (s, old_s), - FutureWarning) - return s - - - + def from_string(self, s): + if self.allow_none and s == 'None': + return None + s = os.path.expanduser(s) + if len(s) >= 2: + # handle deprecated "1" + for c in ('"', "'"): + if s[0] == s[-1] == c: + old_s = s + s = s[1:-1] + warn( + "Supporting extra quotes around strings is deprecated in traitlets 5.0. " + "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: - return str(value) - except Exception: + return str(value) + except Exception: self.error(obj, value) @@ -2223,26 +2223,26 @@ class ObjectName(TraitType): 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) + coerce_str = staticmethod(lambda _,s: s) def validate(self, obj, value): value = self.coerce_str(obj, value) - if isinstance(value, str) and isidentifier(value): + if isinstance(value, str) and isidentifier(value): return value self.error(obj, value) - def from_string(self, s): - if self.allow_none and s == 'None': - return None - return s - + 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) - if isinstance(value, str) and all(isidentifier(a) + if isinstance(value, str) and all(isidentifier(a) for a in value.split('.')): return value self.error(obj, value) @@ -2257,32 +2257,32 @@ class Bool(TraitType): 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 + elif isinstance(value, int): + if value == 1: + return True + elif value == 0: + return False self.error(obj, value) - def from_string(self, s): - if self.allow_none and s == 'None': - return None - s = s.lower() - if s in {'true', '1'}: - return True - elif s in {'false', '0'}: - return False - else: - raise ValueError("%r is not 1, 0, true, or false") - - + def from_string(self, s): + if self.allow_none and s == 'None': + return None + s = s.lower() + if s in {'true', '1'}: + return True + elif s in {'false', '0'}: + 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) - except Exception: + except Exception: self.error(obj, value) @@ -2300,43 +2300,43 @@ class Enum(TraitType): 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 - if as_rst: - choices = '|'.join('``%r``' % x for x in choices) - else: - choices = repr(list(choices)) - return choices - - def _info(self, as_rst=False): + def _choices_str(self, as_rst=False): + """ Returns a description of the trait choices (not none).""" + choices = self.values + if as_rst: + choices = '|'.join('``%r``' % x for x in choices) + else: + choices = repr(list(choices)) + return choices + + def _info(self, as_rst=False): """ 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) - - def info_rst(self): - return self._info(as_rst=True) - - def from_string(self, s): - try: - return self.validate(None, s) - except TraitError: - return _safe_literal_eval(s) - - + 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) + + def info_rst(self): + return self._info(as_rst=True) + + def from_string(self, s): + try: + return self.validate(None, s) + except TraitError: + return _safe_literal_eval(s) + + 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) + super().__init__(values, default_value=default_value, **kwargs) def validate(self, obj, value): - if not isinstance(value, str): + if not isinstance(value, str): self.error(obj, value) for v in self.values: @@ -2344,83 +2344,83 @@ class CaselessStrEnum(Enum): 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') - if self.allow_none else - '') - return 'any of %s (case-insensitive)%s' % (self._choices_str(as_rst), none) - - def info(self): - return self._info(as_rst=False) - - def info_rst(self): - return self._info(as_rst=True) - - -class FuzzyEnum(Enum): - """An case-ignoring enum matching choices by unique prefixes/substrings.""" - - case_sensitive = False - #: If True, choices match anywhere in the string, otherwise match prefixes. - substring_matching = False - - def __init__(self, values, default_value=Undefined, - case_sensitive=False, substring_matching=False, **kwargs): - self.case_sensitive = case_sensitive - self.substring_matching = substring_matching - super().__init__(values, default_value=default_value, **kwargs) - - def validate(self, obj, value): - if not isinstance(value, str): - self.error(obj, value) - - conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower() - substring_matching = self.substring_matching - match_func = ((lambda v, c: v in c) - if substring_matching - else (lambda v, c: c.startswith(v))) - value = conv_func(value) - choices = self.values - matches = [match_func(value, conv_func(c)) for c in choices] - if sum(matches) == 1: - for v, m in zip(choices, matches): - if m: - 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') - if self.allow_none else - '') - case = 'sensitive' if self.case_sensitive else 'insensitive' - substr = 'substring' if self.substring_matching else 'prefix' - return 'any case-%s %s of %s%s' % (case, substr, - self._choices_str(as_rst), - none) - - def info(self): - return self._info(as_rst=False) - - def info_rst(self): - return self._info(as_rst=True) - - + def _info(self, as_rst=False): + """ Returns a description of the trait.""" + none = (' or %s' % ('`None`' if as_rst else 'None') + if self.allow_none else + '') + return 'any of %s (case-insensitive)%s' % (self._choices_str(as_rst), none) + + def info(self): + return self._info(as_rst=False) + + def info_rst(self): + return self._info(as_rst=True) + + +class FuzzyEnum(Enum): + """An case-ignoring enum matching choices by unique prefixes/substrings.""" + + case_sensitive = False + #: If True, choices match anywhere in the string, otherwise match prefixes. + substring_matching = False + + def __init__(self, values, default_value=Undefined, + case_sensitive=False, substring_matching=False, **kwargs): + self.case_sensitive = case_sensitive + self.substring_matching = substring_matching + super().__init__(values, default_value=default_value, **kwargs) + + def validate(self, obj, value): + if not isinstance(value, str): + self.error(obj, value) + + conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower() + substring_matching = self.substring_matching + match_func = ((lambda v, c: v in c) + if substring_matching + else (lambda v, c: c.startswith(v))) + value = conv_func(value) + choices = self.values + matches = [match_func(value, conv_func(c)) for c in choices] + if sum(matches) == 1: + for v, m in zip(choices, matches): + if m: + 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') + if self.allow_none else + '') + case = 'sensitive' if self.case_sensitive else 'insensitive' + substr = 'substring' if self.substring_matching else 'prefix' + return 'any case-%s %s of %s%s' % (case, substr, + self._choices_str(as_rst), + none) + + def info(self): + return self._info(as_rst=False) + + def info_rst(self): + return self._info(as_rst=True) + + 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 - _literal_from_string_pairs = ("[]", "()") + _literal_from_string_pairs = ("[]", "()") - def __init__(self, trait=None, default_value=Undefined, **kwargs): + 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)``, @@ -2448,53 +2448,53 @@ class Container(Instance): further keys for extensions to the Trait (e.g. config) """ - + # allow List([values]): - if trait is not None and default_value is Undefined and not is_trait(trait): + if trait is not None and default_value is Undefined and not is_trait(trait): 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, - # keeping 'unspecified' behavior (i.e. empty container) - warn( - f"Specifying {self.__class__.__name__}(default_value=None)" - " for no default is deprecated in traitlets 5.0.5." - " Use default_value=Undefined", - DeprecationWarning, - stacklevel=2, - ) - default_value = Undefined - - if default_value is Undefined: + if default_value is None and not kwargs.get("allow_none", False): + # improve backward-compatibility for possible subclasses + # specifying default_value=None as default, + # keeping 'unspecified' behavior (i.e. empty container) + warn( + f"Specifying {self.__class__.__name__}(default_value=None)" + " for no default is deprecated in traitlets 5.0.5." + " Use default_value=Undefined", + DeprecationWarning, + stacklevel=2, + ) + default_value = Undefined + + 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 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: - raise TypeError( - "default value of %s was %s" % (self.__class__.__name__, default_value) - ) + 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, - ) + 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) - ) + raise TypeError( + "`trait` must be a Trait or None, got %s" % repr_type(trait) + ) - super(Container, self).__init__(klass=self.klass, args=args, **kwargs) + super(Container, self).__init__(klass=self.klass, args=args, **kwargs) def validate(self, obj, value): if isinstance(value, self._cast_types): @@ -2514,8 +2514,8 @@ class Container(Instance): for v in value: try: v = self._trait._validate(obj, v) - except TraitError as error: - self.error(obj, v, error) + except TraitError as error: + self.error(obj, v, error) else: validated.append(v) return self.klass(validated) @@ -2530,77 +2530,77 @@ class Container(Instance): 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): - raise TraitError(f"Expected string, got {s!r}") - try: - test = literal_eval(s) - 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 - - This is where we parse CLI configuration - """ - if len(s_list) == 1: - # check for deprecated --Class.trait="['a', 'b', 'c']" - r = s_list[0] - if r == "None" and self.allow_none: - return None - if len(r) >= 2 and any( - r.startswith(start) and r.endswith(end) - for start, end in self._literal_from_string_pairs - ): - if self.this_class: - clsname = self.this_class.__name__ + "." - else: - clsname = "" - - warn( - "--{0}={1} for containers is deprecated in traitlets 5.0. " - "You can pass `--{0} item` ... multiple times to add items to a list.".format( - clsname + self.name, r - ), - FutureWarning, - ) - return self.klass(literal_eval(r)) - sig = inspect.signature(self.item_from_string) - if "index" in sig.parameters: - item_from_string = self.item_from_string - else: - # backward-compat: allow item_from_string to ignore index arg - item_from_string = lambda s, index=None: self.item_from_string(s) - return self.klass( - [item_from_string(s, index=idx) for idx, s in enumerate(s_list)] - ) - - def item_from_string(self, s, index=None): - """Cast a single item from a string - - Evaluated when parsing CLI configuration from a string - """ - if self._trait: - return self._trait.from_string(s) - else: - return s - - + def from_string(self, s): + """Load value from a single string""" + if not isinstance(s, str): + raise TraitError(f"Expected string, got {s!r}") + try: + test = literal_eval(s) + 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 + + This is where we parse CLI configuration + """ + if len(s_list) == 1: + # check for deprecated --Class.trait="['a', 'b', 'c']" + r = s_list[0] + if r == "None" and self.allow_none: + return None + if len(r) >= 2 and any( + r.startswith(start) and r.endswith(end) + for start, end in self._literal_from_string_pairs + ): + if self.this_class: + clsname = self.this_class.__name__ + "." + else: + clsname = "" + + warn( + "--{0}={1} for containers is deprecated in traitlets 5.0. " + "You can pass `--{0} item` ... multiple times to add items to a list.".format( + clsname + self.name, r + ), + FutureWarning, + ) + return self.klass(literal_eval(r)) + sig = inspect.signature(self.item_from_string) + if "index" in sig.parameters: + item_from_string = self.item_from_string + else: + # backward-compat: allow item_from_string to ignore index arg + item_from_string = lambda s, index=None: self.item_from_string(s) + return self.klass( + [item_from_string(s, index=idx) for idx, s in enumerate(s_list)] + ) + + def item_from_string(self, s, index=None): + """Cast a single item from a string + + Evaluated when parsing CLI configuration from a string + """ + if self._trait: + return self._trait.from_string(s) + else: + return s + + class List(Container): """An instance of a Python list.""" klass = list _cast_types = (tuple,) - def __init__( - self, - trait=None, - default_value=Undefined, - minlen=0, - maxlen=sys.maxsize, - **kwargs, - ): + def __init__( + self, + trait=None, + default_value=Undefined, + 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)``, @@ -2644,29 +2644,29 @@ class List(Container): 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) + 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) - _literal_from_string_pairs = ("[]", "()", "{}") - + _literal_from_string_pairs = ("[]", "()", "{}") + # Redefine __init__ just to make the docstring more accurate. - def __init__( - self, - trait=None, - default_value=Undefined, - minlen=0, - maxlen=sys.maxsize, - **kwargs, - ): + def __init__( + self, + trait=None, + default_value=Undefined, + 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)``, @@ -2695,17 +2695,17 @@ class Set(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]+'}' + 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.""" - + klass = tuple _cast_types = (list,) @@ -2727,7 +2727,7 @@ class Tuple(Container): Parameters ---------- - *traits : TraitTypes [ optional ] + *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 @@ -2736,69 +2736,69 @@ class Tuple(Container): 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` + **kwargs + Other kwargs passed to `Container` """ - default_value = kwargs.pop("default_value", Undefined) + 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 None and not kwargs.get("allow_none", False): - # improve backward-compatibility for possible subclasses - # specifying default_value=None as default, - # keeping 'unspecified' behavior (i.e. empty container) - warn( - f"Specifying {self.__class__.__name__}(default_value=None)" - " for no default is deprecated in traitlets 5.0.5." - " Use default_value=Undefined", - DeprecationWarning, - stacklevel=2, - ) - default_value = Undefined - + if default_value is None and not kwargs.get("allow_none", False): + # improve backward-compatibility for possible subclasses + # specifying default_value=None as default, + # keeping 'unspecified' behavior (i.e. empty container) + warn( + f"Specifying {self.__class__.__name__}(default_value=None)" + " for no default is deprecated in traitlets 5.0.5." + " Use default_value=Undefined", + DeprecationWarning, + stacklevel=2, + ) + default_value = Undefined + 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 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: - raise TypeError( - "default value of %s was %s" % (self.__class__.__name__, default_value) - ) + 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, - ) - trait = trait() - self._traits.append(trait) - - if self._traits and (default_value is None or default_value is Undefined): + 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, + ) + 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 - super(Container, self).__init__(klass=self.klass, args=args, **kwargs) - - def item_from_string(self, s, index): - """Cast a single item from a string - - Evaluated when parsing CLI configuration from a string - """ - if not self._traits or index >= len(self._traits): - # return s instead of raising index error - # length errors will be raised later on validation - return s - return self._traits[index].from_string(s) - + super(Container, self).__init__(klass=self.klass, args=args, **kwargs) + + def item_from_string(self, s, index): + """Cast a single item from a string + + Evaluated when parsing CLI configuration from a string + """ + if not self._traits or index >= len(self._traits): + # return s instead of raising index error + # length errors will be raised later on validation + return s + return self._traits[index].from_string(s) + def validate_elements(self, obj, value): if not self._traits: # nothing to validate @@ -2812,8 +2812,8 @@ class Tuple(Container): for t, v in zip(self._traits, value): try: v = t._validate(obj, v) - except TraitError as error: - self.error(obj, v, error) + except TraitError as error: + self.error(obj, v, error) else: validated.append(v) return tuple(validated) @@ -2832,23 +2832,23 @@ class Tuple(Container): 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, - you may use a custom validator method. - - .. versionchanged:: 5.0 - Added key_trait for validating dict keys. - - .. versionchanged:: 5.0 - Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``. - """ - _value_trait = None - _key_trait = None - - def __init__(self, value_trait=None, per_key_traits=None, key_trait=None, default_value=Undefined, + """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, + you may use a custom validator method. + + .. versionchanged:: 5.0 + Added key_trait for validating dict keys. + + .. versionchanged:: 5.0 + Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``. + """ + _value_trait = None + _key_trait = None + + 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. @@ -2857,68 +2857,68 @@ class Dict(Instance): Parameters ---------- - value_trait : TraitType [ optional ] - The specified trait type to check and use to restrict the values of - the dict. If unspecified, values are not checked. - per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ] - A Python dictionary containing the types that are valid for - restricting the values of the dict on a per-key basis. - Each value in this dict should be a Trait for validating - key_trait : TraitType [ optional, keyword-only ] - The type for restricting the keys of the dict. If - unspecified, the types of the keys are not checked. - default_value : SequenceType [ optional, keyword-only ] - The default value for the Dict. Must be dict, tuple, or None, and - will be cast to a dict if not None. If any key or value traits are specified, - the `default_value` must conform to the constraints. - - Examples - -------- - >>> 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 + value_trait : TraitType [ optional ] + The specified trait type to check and use to restrict the values of + the dict. If unspecified, values are not checked. + per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ] + A Python dictionary containing the types that are valid for + restricting the values of the dict on a per-key basis. + Each value in this dict should be a Trait for validating + key_trait : TraitType [ optional, keyword-only ] + The type for restricting the keys of the dict. If + unspecified, the types of the keys are not checked. + default_value : SequenceType [ optional, keyword-only ] + The default value for the Dict. Must be dict, tuple, or None, and + will be cast to a dict if not None. If any key or value traits are specified, + the `default_value` must conform to the constraints. + + Examples + -------- + >>> 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) - if trait is not None: - if value_trait is not None: - raise TypeError("Found a value for both `value_trait` and its deprecated alias `trait`.") - value_trait = trait - warn( - "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead", - DeprecationWarning, - stacklevel=2, - ) - traits = kwargs.pop("traits", None) - if traits is not None: - if per_key_traits is not None: - raise TypeError("Found a value for both `per_key_traits` and its deprecated alias `traits`.") - per_key_traits = traits - warn( - "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead", - DeprecationWarning, - stacklevel=2, - ) - + + # handle deprecated keywords + trait = kwargs.pop('trait', None) + if trait is not None: + if value_trait is not None: + raise TypeError("Found a value for both `value_trait` and its deprecated alias `trait`.") + value_trait = trait + warn( + "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead", + DeprecationWarning, + stacklevel=2, + ) + traits = kwargs.pop("traits", None) + if traits is not None: + if per_key_traits is not None: + raise TypeError("Found a value for both `per_key_traits` and its deprecated alias `traits`.") + per_key_traits = traits + warn( + "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead", + DeprecationWarning, + stacklevel=2, + ) + # 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 - + 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 = {} @@ -2932,32 +2932,32 @@ class Dict(Instance): 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): + 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) - 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`)" - " Passing types is deprecated in traitlets 4.1.", - DeprecationWarning, stacklevel=2) - key_trait = key_trait() - 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 - + 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`)" + " Passing types is deprecated in traitlets 4.1.", + DeprecationWarning, stacklevel=2) + key_trait = key_trait() + 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." \ + 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) @@ -2969,123 +2969,123 @@ class Dict(Instance): 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): + 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 validated = {} for key in value: - v = value[key] - if key_trait: - try: - key = key_trait._validate(obj, key) - except TraitError as error: - self.element_error(obj, key, key_trait, 'Keys') - active_value_trait = per_key_override.get(key, value_trait) - if active_value_trait: - try: - v = active_value_trait._validate(obj, v) - except TraitError: - self.element_error(obj, v, active_value_trait, 'Values') - validated[key] = v + v = value[key] + if key_trait: + try: + key = key_trait._validate(obj, key) + except TraitError as error: + self.element_error(obj, key, key_trait, 'Keys') + active_value_trait = per_key_override.get(key, value_trait) + if active_value_trait: + try: + v = active_value_trait._validate(obj, v) + except TraitError: + self.element_error(obj, v, active_value_trait, 'Values') + validated[key] = v 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(): + 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): - 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(): + 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) - def from_string(self, s): - """Load value from a single string""" - if not isinstance(s, str): - raise TypeError(f"from_string expects a string, got {repr(s)} of type {type(s)}") - try: - return self.from_string_list([s]) - except Exception: - test = _safe_literal_eval(s) - if isinstance(test, dict): - return test - raise - - def from_string_list(self, s_list): - """Return a dict from a list of config strings. - - This is where we parse CLI configuration. - - Each item should have the form ``"key=value"``. - - item parsing is done in :meth:`.item_from_string`. - """ - if len(s_list) == 1 and s_list[0] == "None" and self.allow_none: - return None - if ( - len(s_list) == 1 - and s_list[0].startswith("{") - and s_list[0].endswith("}") - ): - warn( - "--{0}={1} for dict-traits is deprecated in traitlets 5.0. " - "You can pass --{0} <key=value> ... multiple times to add items to a dict.".format( - self.name, - s_list[0], - ), - FutureWarning, - ) - - return literal_eval(s_list[0]) - - combined = {} - for d in [self.item_from_string(s) for s in s_list]: - combined.update(d) - return combined - - def item_from_string(self, s): - """Cast a single-key dict from a string. - - Evaluated when parsing CLI configuration from a string. - - Dicts expect strings of the form key=value. - - Returns a one-key dictionary, - which will be merged in :meth:`.from_string_list`. - """ - - if '=' not in s: - raise TraitError( - "'%s' options must have the form 'key=value', got %s" - % (self.__class__.__name__, repr(s),) - ) - key, value = s.split("=", 1) - - # cast key with key trait, if defined - if self._key_trait: - key = self._key_trait.from_string(key) - - # cast value with value trait, if defined (per-key or global) - value_trait = (self._per_key_traits or {}).get(key, self._value_trait) - if value_trait: - value = value_trait.from_string(value) - return {key: value} - - + def from_string(self, s): + """Load value from a single string""" + if not isinstance(s, str): + raise TypeError(f"from_string expects a string, got {repr(s)} of type {type(s)}") + try: + return self.from_string_list([s]) + except Exception: + test = _safe_literal_eval(s) + if isinstance(test, dict): + return test + raise + + def from_string_list(self, s_list): + """Return a dict from a list of config strings. + + This is where we parse CLI configuration. + + Each item should have the form ``"key=value"``. + + item parsing is done in :meth:`.item_from_string`. + """ + if len(s_list) == 1 and s_list[0] == "None" and self.allow_none: + return None + if ( + len(s_list) == 1 + and s_list[0].startswith("{") + and s_list[0].endswith("}") + ): + warn( + "--{0}={1} for dict-traits is deprecated in traitlets 5.0. " + "You can pass --{0} <key=value> ... multiple times to add items to a dict.".format( + self.name, + s_list[0], + ), + FutureWarning, + ) + + return literal_eval(s_list[0]) + + combined = {} + for d in [self.item_from_string(s) for s in s_list]: + combined.update(d) + return combined + + def item_from_string(self, s): + """Cast a single-key dict from a string. + + Evaluated when parsing CLI configuration from a string. + + Dicts expect strings of the form key=value. + + Returns a one-key dictionary, + which will be merged in :meth:`.from_string_list`. + """ + + if '=' not in s: + raise TraitError( + "'%s' options must have the form 'key=value', got %s" + % (self.__class__.__name__, repr(s),) + ) + key, value = s.split("=", 1) + + # cast key with key trait, if defined + if self._key_trait: + key = self._key_trait.from_string(key) + + # cast value with value trait, if defined (per-key or global) + value_trait = (self._per_key_traits or {}).get(key, self._value_trait) + if value_trait: + value = value_trait.from_string(value) + return {key: value} + + class TCPAddress(TraitType): """A trait for an (ip, port) tuple. @@ -3098,22 +3098,22 @@ class TCPAddress(TraitType): def validate(self, obj, value): if isinstance(value, tuple): if len(value) == 2: - if isinstance(value[0], str) and isinstance(value[1], int): + 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) - def from_string(self, s): - if self.allow_none and s == 'None': - return None - if ':' not in s: - raise ValueError('Require `ip:port`, got %r' % s) - ip, port = s.split(':', 1) - port = int(port) - return (ip, port) - - + def from_string(self, s): + if self.allow_none and s == 'None': + return None + if ':' not in s: + raise ValueError('Require `ip:port`, got %r' % s) + ip, port = s.split(':', 1) + port = int(port) + return (ip, port) + + class CRegExp(TraitType): """A casting compiled regular expression trait. @@ -3125,7 +3125,7 @@ class CRegExp(TraitType): def validate(self, obj, value): try: return re.compile(value) - except Exception: + except Exception: self.error(obj, value) @@ -3180,7 +3180,7 @@ class UseEnum(TraitType): def select_by_name(self, value, default=Undefined): """Selects enum-value by using its name or scoped-name.""" - assert isinstance(value, str) + assert isinstance(value, str) if value.startswith(self.name_prefix): # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red" value = value.replace(self.name_prefix, "", 1) @@ -3194,7 +3194,7 @@ class UseEnum(TraitType): value2 = self.select_by_number(value) if value2 is not Undefined: return value2 - elif isinstance(value, str): + elif isinstance(value, str): # -- CONVERT: name or scoped_name (as string) => enum_value (item) value2 = self.select_by_name(value) if value2 is not Undefined: @@ -3206,53 +3206,53 @@ class UseEnum(TraitType): return self.default_value self.error(obj, value) - def _choices_str(self, as_rst=False): - """ Returns a description of the trait choices (not none).""" - choices = self.enum_class.__members__.keys() - if as_rst: - return '|'.join('``%r``' % x for x in choices) - else: - return repr(list(choices)) # Listify because py3.4- prints odict-class - - def _info(self, as_rst=False): - """ 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 _choices_str(self, as_rst=False): + """ Returns a description of the trait choices (not none).""" + choices = self.enum_class.__members__.keys() + if as_rst: + return '|'.join('``%r``' % x for x in choices) + else: + return repr(list(choices)) # Listify because py3.4- prints odict-class + + def _info(self, as_rst=False): + """ 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) - - def info_rst(self): - return self._info(as_rst=True) - - - -class Callable(TraitType): - """A trait which is callable. - - Notes - ----- - Classes are callable, as are instances - with a __call__() method.""" - - info_text = 'a callable' - - def validate(self, obj, value): - if callable(value): - return value - else: - self.error(obj, value) - - -def _add_all(): - """add all trait types to `__all__` - - do in a function to avoid iterating through globals while defining local variables - """ - for _name, _value in globals().items(): - if not _name.startswith('_') and isinstance(_value, type) and issubclass(_value, TraitType): - __all__.append(_name) - -_add_all() + return self._info(as_rst=False) + + def info_rst(self): + return self._info(as_rst=True) + + + +class Callable(TraitType): + """A trait which is callable. + + Notes + ----- + Classes are callable, as are instances + with a __call__() method.""" + + info_text = 'a callable' + + def validate(self, obj, value): + if callable(value): + return value + else: + self.error(obj, value) + + +def _add_all(): + """add all trait types to `__all__` + + do in a function to avoid iterating through globals while defining local variables + """ + for _name, _value in globals().items(): + if not _name.startswith('_') and isinstance(_value, type) and issubclass(_value, TraitType): + __all__.append(_name) + +_add_all() diff --git a/contrib/python/traitlets/py3/traitlets/utils/__init__.py b/contrib/python/traitlets/py3/traitlets/utils/__init__.py index b6c39485a3..0fbba3d358 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/utils/__init__.py @@ -1,86 +1,86 @@ -import os - -# vestigal things from IPython_genutils. -def cast_unicode(s, encoding='utf-8'): - if isinstance(s, bytes): - return s.decode(encoding, 'replace') - return s - - -def filefind(filename, path_dirs=None): - """Find a file by looking through a sequence of paths. - - This iterates through a sequence of paths looking for a file and returns - the full, absolute path of the first occurence of the file. If no set of - path dirs is given, the filename is tested as is, after running through - :func:`expandvars` and :func:`expanduser`. Thus a simple call:: - - filefind('myfile.txt') - - will find the file in the current working dir, but:: - - filefind('~/myfile.txt') - - Will find the file in the users home directory. This function does not - automatically try any paths, such as the cwd or the user's home directory. - - Parameters - ---------- - filename : str - The filename to look for. - path_dirs : str, None or sequence of str - The sequence of paths to look for the file in. If None, the filename - need to be absolute or be in the cwd. If a string, the string is - put into a sequence and the searched. If a sequence, walk through - each element and join with ``filename``, calling :func:`expandvars` - and :func:`expanduser` before testing for existence. - - Returns - ------- - Raises :exc:`IOError` or returns absolute path to file. - """ - - # If paths are quoted, abspath gets confused, strip them... - filename = filename.strip('"').strip("'") - # If the input is an absolute path, just check it exists - if os.path.isabs(filename) and os.path.isfile(filename): - return filename - - if path_dirs is None: - path_dirs = ("",) - elif isinstance(path_dirs, str): - path_dirs = (path_dirs,) - - for path in path_dirs: - if path == ".": - path = os.getcwd() - testname = expand_path(os.path.join(path, filename)) - if os.path.isfile(testname): - return os.path.abspath(testname) - - raise IOError( - "File %r does not exist in any of the search paths: %r" % (filename, path_dirs) - ) - - -def expand_path(s): - """Expand $VARS and ~names in a string, like a shell - - :Examples: - - In [2]: os.environ['FOO']='test' - - In [3]: expand_path('variable FOO is $FOO') - Out[3]: 'variable FOO is test' - """ - # This is a pretty subtle hack. When expand user is given a UNC path - # on Windows (\\server\share$\%username%), os.path.expandvars, removes - # the $ to get (\\server\share\%username%). I think it considered $ - # alone an empty var. But, we need the $ to remains there (it indicates - # a hidden share). - if os.name == "nt": - s = s.replace("$\\", "IPYTHON_TEMP") - s = os.path.expandvars(os.path.expanduser(s)) - if os.name == "nt": - s = s.replace("IPYTHON_TEMP", "$\\") - return s +import os + +# vestigal things from IPython_genutils. +def cast_unicode(s, encoding='utf-8'): + if isinstance(s, bytes): + return s.decode(encoding, 'replace') + return s + + +def filefind(filename, path_dirs=None): + """Find a file by looking through a sequence of paths. + + This iterates through a sequence of paths looking for a file and returns + the full, absolute path of the first occurence of the file. If no set of + path dirs is given, the filename is tested as is, after running through + :func:`expandvars` and :func:`expanduser`. Thus a simple call:: + + filefind('myfile.txt') + + will find the file in the current working dir, but:: + + filefind('~/myfile.txt') + + Will find the file in the users home directory. This function does not + automatically try any paths, such as the cwd or the user's home directory. + + Parameters + ---------- + filename : str + The filename to look for. + path_dirs : str, None or sequence of str + The sequence of paths to look for the file in. If None, the filename + need to be absolute or be in the cwd. If a string, the string is + put into a sequence and the searched. If a sequence, walk through + each element and join with ``filename``, calling :func:`expandvars` + and :func:`expanduser` before testing for existence. + + Returns + ------- + Raises :exc:`IOError` or returns absolute path to file. + """ + + # If paths are quoted, abspath gets confused, strip them... + filename = filename.strip('"').strip("'") + # If the input is an absolute path, just check it exists + if os.path.isabs(filename) and os.path.isfile(filename): + return filename + + if path_dirs is None: + path_dirs = ("",) + elif isinstance(path_dirs, str): + path_dirs = (path_dirs,) + + for path in path_dirs: + if path == ".": + path = os.getcwd() + testname = expand_path(os.path.join(path, filename)) + if os.path.isfile(testname): + return os.path.abspath(testname) + + raise IOError( + "File %r does not exist in any of the search paths: %r" % (filename, path_dirs) + ) + + +def expand_path(s): + """Expand $VARS and ~names in a string, like a shell + + :Examples: + + In [2]: os.environ['FOO']='test' + + In [3]: expand_path('variable FOO is $FOO') + Out[3]: 'variable FOO is test' + """ + # This is a pretty subtle hack. When expand user is given a UNC path + # on Windows (\\server\share$\%username%), os.path.expandvars, removes + # the $ to get (\\server\share\%username%). I think it considered $ + # alone an empty var. But, we need the $ to remains there (it indicates + # a hidden share). + if os.name == "nt": + s = s.replace("$\\", "IPYTHON_TEMP") + s = os.path.expandvars(os.path.expanduser(s)) + if os.name == "nt": + s = s.replace("IPYTHON_TEMP", "$\\") + return s diff --git a/contrib/python/traitlets/py3/traitlets/utils/decorators.py b/contrib/python/traitlets/py3/traitlets/utils/decorators.py index 32a0b9fff8..656f968c8d 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/decorators.py +++ b/contrib/python/traitlets/py3/traitlets/utils/decorators.py @@ -1,80 +1,80 @@ -"""Useful decorators for Traitlets users.""" - -import copy - -from inspect import Signature, Parameter, signature - -from ..traitlets import Undefined - - -def _get_default(value): - """Get default argument value, given the trait default value.""" - return Parameter.empty if value == Undefined else value - - -def signature_has_traits(cls): - """Return a decorated class with a constructor signature that contain Trait names as kwargs.""" - traits = [ - (name, _get_default(value.default_value)) - for name, value in cls.class_traits().items() - if not name.startswith('_') - ] - - # Taking the __init__ signature, as the cls signature is not initialized yet - old_signature = signature(cls.__init__) - old_parameter_names = list(old_signature.parameters) - - old_positional_parameters = [] - old_var_positional_parameter = None # This won't be None if the old signature contains *args - old_keyword_only_parameters = [] - old_var_keyword_parameter = None # This won't be None if the old signature contains **kwargs - - for parameter_name in old_signature.parameters: - # Copy the parameter - parameter = copy.copy(old_signature.parameters[parameter_name]) - - if parameter.kind is Parameter.POSITIONAL_ONLY or parameter.kind is Parameter.POSITIONAL_OR_KEYWORD: - old_positional_parameters.append(parameter) - - elif parameter.kind is Parameter.VAR_POSITIONAL: - old_var_positional_parameter = parameter - - elif parameter.kind is Parameter.KEYWORD_ONLY: - old_keyword_only_parameters.append(parameter) - - elif parameter.kind is Parameter.VAR_KEYWORD: - old_var_keyword_parameter = parameter - - # Unfortunately, if the old signature does not contain **kwargs, we can't do anything, - # because it can't accept traits as keyword arguments - if old_var_keyword_parameter is None: - raise RuntimeError( - 'The {} constructor does not take **kwargs, which means that the signature can not be expanded with trait names' - .format(cls) - ) - - new_parameters = [] - - # Append the old positional parameters (except `self` which is the first parameter) - new_parameters += old_positional_parameters[1:] - - # Append *args if the old signature had it - if old_var_positional_parameter is not None: - new_parameters.append(old_var_positional_parameter) - - # Append the old keyword only parameters - new_parameters += old_keyword_only_parameters - - # Append trait names as keyword only parameters in the signature - new_parameters += [ - Parameter(name, kind=Parameter.KEYWORD_ONLY, default=default) - for name, default in traits - if name not in old_parameter_names - ] - - # Append **kwargs - new_parameters.append(old_var_keyword_parameter) - - cls.__signature__ = Signature(new_parameters) - - return cls +"""Useful decorators for Traitlets users.""" + +import copy + +from inspect import Signature, Parameter, signature + +from ..traitlets import Undefined + + +def _get_default(value): + """Get default argument value, given the trait default value.""" + return Parameter.empty if value == Undefined else value + + +def signature_has_traits(cls): + """Return a decorated class with a constructor signature that contain Trait names as kwargs.""" + traits = [ + (name, _get_default(value.default_value)) + for name, value in cls.class_traits().items() + if not name.startswith('_') + ] + + # Taking the __init__ signature, as the cls signature is not initialized yet + old_signature = signature(cls.__init__) + old_parameter_names = list(old_signature.parameters) + + old_positional_parameters = [] + old_var_positional_parameter = None # This won't be None if the old signature contains *args + old_keyword_only_parameters = [] + old_var_keyword_parameter = None # This won't be None if the old signature contains **kwargs + + for parameter_name in old_signature.parameters: + # Copy the parameter + parameter = copy.copy(old_signature.parameters[parameter_name]) + + if parameter.kind is Parameter.POSITIONAL_ONLY or parameter.kind is Parameter.POSITIONAL_OR_KEYWORD: + old_positional_parameters.append(parameter) + + elif parameter.kind is Parameter.VAR_POSITIONAL: + old_var_positional_parameter = parameter + + elif parameter.kind is Parameter.KEYWORD_ONLY: + old_keyword_only_parameters.append(parameter) + + elif parameter.kind is Parameter.VAR_KEYWORD: + old_var_keyword_parameter = parameter + + # Unfortunately, if the old signature does not contain **kwargs, we can't do anything, + # because it can't accept traits as keyword arguments + if old_var_keyword_parameter is None: + raise RuntimeError( + 'The {} constructor does not take **kwargs, which means that the signature can not be expanded with trait names' + .format(cls) + ) + + new_parameters = [] + + # Append the old positional parameters (except `self` which is the first parameter) + new_parameters += old_positional_parameters[1:] + + # Append *args if the old signature had it + if old_var_positional_parameter is not None: + new_parameters.append(old_var_positional_parameter) + + # Append the old keyword only parameters + new_parameters += old_keyword_only_parameters + + # Append trait names as keyword only parameters in the signature + new_parameters += [ + Parameter(name, kind=Parameter.KEYWORD_ONLY, default=default) + for name, default in traits + if name not in old_parameter_names + ] + + # Append **kwargs + new_parameters.append(old_var_keyword_parameter) + + cls.__signature__ = Signature(new_parameters) + + return cls diff --git a/contrib/python/traitlets/py3/traitlets/utils/descriptions.py b/contrib/python/traitlets/py3/traitlets/utils/descriptions.py index 056a28f170..7b2996491f 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/descriptions.py +++ b/contrib/python/traitlets/py3/traitlets/utils/descriptions.py @@ -1,171 +1,171 @@ -import inspect -import re -import types - - -def describe(article, value, name=None, verbose=False, capital=False): - """Return string that describes a value - - Parameters - ---------- - article : str or None - A definite or indefinite article. If the article is - indefinite (i.e. "a" or "an") the appropriate one - will be infered. Thus, the arguments of ``describe`` - can themselves represent what the resulting string - will actually look like. If None, then no article - will be prepended to the result. For non-articled - description, values that are instances are treated - definitely, while classes are handled indefinitely. - value : any - The value which will be named. - name : str or None (default: None) - Only applies when ``article`` is "the" - this - ``name`` is a definite reference to the value. - By default one will be infered from the value's - type and repr methods. - verbose : bool (default: False) - Whether the name should be concise or verbose. When - possible, verbose names include the module, and/or - class name where an object was defined. - capital : bool (default: False) - Whether the first letter of the article should - be capitalized or not. By default it is not. - - Examples - -------- - Indefinite description: - - >>> describe("a", object()) - 'an object' - >>> describe("a", object) - 'an object' - >>> describe("a", type(object)) - 'a type' - - Definite description: - - >>> describe("the", object()) - "the object at '0x10741f1b0'" - >>> describe("the", object) - "the type 'object'" - >>> describe("the", type(object)) - "the type 'type'" - - Definitely named description: - - >>> describe("the", object(), "I made") - 'the object I made' - >>> describe("the", object, "I will use") - 'the object I will use' - """ - if isinstance(article, str): - article = article.lower() - - if not inspect.isclass(value): - typename = type(value).__name__ - else: - typename = value.__name__ - if verbose: - typename = _prefix(value) + typename - - if article == "the" or (article is None and not inspect.isclass(value)): - if name is not None: - result = "{} {}".format(typename, name) - if article is not None: - return add_article(result, True, capital) - else: - return result - else: - tick_wrap = False - if inspect.isclass(value): - name = value.__name__ - elif isinstance(value, types.FunctionType): - name = value.__name__ - tick_wrap = True - elif isinstance(value, types.MethodType): - name = value.__func__.__name__ - tick_wrap = True - elif type(value).__repr__ in (object.__repr__, type.__repr__): - name = "at '%s'" % hex(id(value)) - verbose = False - else: - name = repr(value) - verbose = False - if verbose: - name = _prefix(value) + name - if tick_wrap: - name = name.join("''") - return describe(article, value, name=name, - verbose=verbose, capital=capital) - elif article in ("a", "an") or article is None: - if article is None: - return typename - return add_article(typename, False, capital) - else: - raise ValueError("The 'article' argument should " - "be 'the', 'a', 'an', or None not %r" % article) - - -def _prefix(value): - if isinstance(value, types.MethodType): - name = describe(None, value.__self__, verbose=True) + '.' - else: - module = inspect.getmodule(value) - if module is not None and module.__name__ != "builtins": - name = module.__name__ + '.' - else: - name = "" - return name - - -def class_of(value): - """Returns a string of the value's type with an indefinite article. - - For example 'an Image' or 'a PlotValue'. - """ - if inspect.isclass(value): - return add_article(value.__name__) - else: - return class_of(type(value)) - - -def add_article(name, definite=False, capital=False): - """Returns the string with a prepended article. - - The input does not need to begin with a charater. - - Parameters - ---------- - name : str - Name to which to prepend an article - definite : bool (default: False) - Whether the article is definite or not. - Indefinite articles being 'a' and 'an', - while 'the' is definite. - capital : bool (default: False) - Whether the added article should have - its first letter capitalized or not. - """ - if definite: - result = "the " + name - else: - first_letters = re.compile(r'[\W_]+').sub('', name) - if first_letters[:1].lower() in 'aeiou': - result = 'an ' + name - else: - result = 'a ' + name - if capital: - return result[0].upper() + result[1:] - else: - return result - - -def repr_type(obj): - """Return a string representation of a value and its type for readable - - error messages. - """ - the_type = type(obj) - msg = '{!r} {!r}'.format(obj, the_type) - return msg +import inspect +import re +import types + + +def describe(article, value, name=None, verbose=False, capital=False): + """Return string that describes a value + + Parameters + ---------- + article : str or None + A definite or indefinite article. If the article is + indefinite (i.e. "a" or "an") the appropriate one + will be infered. Thus, the arguments of ``describe`` + can themselves represent what the resulting string + will actually look like. If None, then no article + will be prepended to the result. For non-articled + description, values that are instances are treated + definitely, while classes are handled indefinitely. + value : any + The value which will be named. + name : str or None (default: None) + Only applies when ``article`` is "the" - this + ``name`` is a definite reference to the value. + By default one will be infered from the value's + type and repr methods. + verbose : bool (default: False) + Whether the name should be concise or verbose. When + possible, verbose names include the module, and/or + class name where an object was defined. + capital : bool (default: False) + Whether the first letter of the article should + be capitalized or not. By default it is not. + + Examples + -------- + Indefinite description: + + >>> describe("a", object()) + 'an object' + >>> describe("a", object) + 'an object' + >>> describe("a", type(object)) + 'a type' + + Definite description: + + >>> describe("the", object()) + "the object at '0x10741f1b0'" + >>> describe("the", object) + "the type 'object'" + >>> describe("the", type(object)) + "the type 'type'" + + Definitely named description: + + >>> describe("the", object(), "I made") + 'the object I made' + >>> describe("the", object, "I will use") + 'the object I will use' + """ + if isinstance(article, str): + article = article.lower() + + if not inspect.isclass(value): + typename = type(value).__name__ + else: + typename = value.__name__ + if verbose: + typename = _prefix(value) + typename + + if article == "the" or (article is None and not inspect.isclass(value)): + if name is not None: + result = "{} {}".format(typename, name) + if article is not None: + return add_article(result, True, capital) + else: + return result + else: + tick_wrap = False + if inspect.isclass(value): + name = value.__name__ + elif isinstance(value, types.FunctionType): + name = value.__name__ + tick_wrap = True + elif isinstance(value, types.MethodType): + name = value.__func__.__name__ + tick_wrap = True + elif type(value).__repr__ in (object.__repr__, type.__repr__): + name = "at '%s'" % hex(id(value)) + verbose = False + else: + name = repr(value) + verbose = False + if verbose: + name = _prefix(value) + name + if tick_wrap: + name = name.join("''") + return describe(article, value, name=name, + verbose=verbose, capital=capital) + elif article in ("a", "an") or article is None: + if article is None: + return typename + return add_article(typename, False, capital) + else: + raise ValueError("The 'article' argument should " + "be 'the', 'a', 'an', or None not %r" % article) + + +def _prefix(value): + if isinstance(value, types.MethodType): + name = describe(None, value.__self__, verbose=True) + '.' + else: + module = inspect.getmodule(value) + if module is not None and module.__name__ != "builtins": + name = module.__name__ + '.' + else: + name = "" + return name + + +def class_of(value): + """Returns a string of the value's type with an indefinite article. + + For example 'an Image' or 'a PlotValue'. + """ + if inspect.isclass(value): + return add_article(value.__name__) + else: + return class_of(type(value)) + + +def add_article(name, definite=False, capital=False): + """Returns the string with a prepended article. + + The input does not need to begin with a charater. + + Parameters + ---------- + name : str + Name to which to prepend an article + definite : bool (default: False) + Whether the article is definite or not. + Indefinite articles being 'a' and 'an', + while 'the' is definite. + capital : bool (default: False) + Whether the added article should have + its first letter capitalized or not. + """ + if definite: + result = "the " + name + else: + first_letters = re.compile(r'[\W_]+').sub('', name) + if first_letters[:1].lower() in 'aeiou': + result = 'an ' + name + else: + result = 'a ' + name + if capital: + return result[0].upper() + result[1:] + else: + return result + + +def repr_type(obj): + """Return a string representation of a value and its type for readable + + error messages. + """ + the_type = type(obj) + msg = '{!r} {!r}'.format(obj, the_type) + return msg diff --git a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py index 23ab899a69..22511437bd 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py +++ b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py @@ -12,39 +12,39 @@ import inspect # Unmodified from sphinx below this line -from functools import partial +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: +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] + 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) - + 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) + diff --git a/contrib/python/traitlets/py3/traitlets/utils/importstring.py b/contrib/python/traitlets/py3/traitlets/utils/importstring.py index cae82f2a31..defad8f183 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/importstring.py +++ b/contrib/python/traitlets/py3/traitlets/utils/importstring.py @@ -14,14 +14,14 @@ def import_item(name): Parameters ---------- name : string - The fully qualified name of the module/package being imported. + The fully qualified name of the module/package being imported. Returns ------- mod : module object - The module that was imported. + The module that was imported. """ - if not isinstance(name, str): + if not isinstance(name, str): raise TypeError("import_item accepts strings, not '%s'." % type(name)) parts = name.rsplit('.', 1) if len(parts) == 2: diff --git a/contrib/python/traitlets/py3/traitlets/utils/sentinel.py b/contrib/python/traitlets/py3/traitlets/utils/sentinel.py index 59d93ccd1e..0760bec8b5 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/sentinel.py +++ b/contrib/python/traitlets/py3/traitlets/utils/sentinel.py @@ -3,7 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - + class Sentinel(object): def __init__(self, name, module, docstring=None): @@ -13,10 +13,10 @@ class Sentinel(object): self.__doc__ = docstring def __repr__(self): - return str(self.module) + '.' + self.name + return str(self.module) + '.' + self.name + + def __copy__(self): + return self - def __copy__(self): - return self - - def __deepcopy__(self, memo): - return self + def __deepcopy__(self, memo): + return self diff --git a/contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py b/contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py index f23de12ff6..3f71fab957 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py +++ b/contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py @@ -1,14 +1,14 @@ -from traitlets.utils.bunch import Bunch - -def test_bunch(): - b = Bunch(x=5, y=10) - assert 'y' in b - assert 'x' in b - assert b.x == 5 - b['a'] = 'hi' - assert b.a == 'hi' - -def test_bunch_dir(): - b = Bunch(x=5, y=10) - assert 'x' in dir(b) - assert 'keys' in dir(b) +from traitlets.utils.bunch import Bunch + +def test_bunch(): + b = Bunch(x=5, y=10) + assert 'y' in b + assert 'x' in b + assert b.x == 5 + b['a'] = 'hi' + assert b.a == 'hi' + +def test_bunch_dir(): + b = Bunch(x=5, y=10) + assert 'x' in dir(b) + assert 'keys' in dir(b) diff --git a/contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py b/contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py index 16cbb0e438..aafd372f3c 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py +++ b/contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py @@ -1,139 +1,139 @@ -from unittest import TestCase - -from inspect import Signature, Parameter, signature - -from traitlets.traitlets import HasTraits, Int, Unicode - -from traitlets.utils.decorators import signature_has_traits - - -class TestExpandSignature(TestCase): - - def test_no_init(self): - @signature_has_traits - class Foo(HasTraits): - number1 = Int() - number2 = Int() - value = Unicode('Hello') - - parameters = signature(Foo).parameters - parameter_names = list(parameters) - - self.assertIs(parameters['args'].kind, Parameter.VAR_POSITIONAL) - self.assertEqual('args', parameter_names[0]) - - self.assertIs(parameters['number1'].kind, Parameter.KEYWORD_ONLY) - self.assertIs(parameters['number2'].kind, Parameter.KEYWORD_ONLY) - self.assertIs(parameters['value'].kind, Parameter.KEYWORD_ONLY) - - self.assertIs(parameters['kwargs'].kind, Parameter.VAR_KEYWORD) - self.assertEqual('kwargs', parameter_names[-1]) - - f = Foo(number1=32, value='World') - self.assertEqual(f.number1, 32) - self.assertEqual(f.number2, 0) - self.assertEqual(f.value, 'World') - - def test_partial_init(self): - @signature_has_traits - class Foo(HasTraits): - number1 = Int() - number2 = Int() - value = Unicode('Hello') - - def __init__(self, arg1, **kwargs): - self.arg1 = arg1 - - super(Foo, self).__init__(**kwargs) - - parameters = signature(Foo).parameters - parameter_names = list(parameters) - - self.assertIs(parameters['arg1'].kind, Parameter.POSITIONAL_OR_KEYWORD) - self.assertEqual('arg1', parameter_names[0]) - - self.assertIs(parameters['number1'].kind, Parameter.KEYWORD_ONLY) - self.assertIs(parameters['number2'].kind, Parameter.KEYWORD_ONLY) - self.assertIs(parameters['value'].kind, Parameter.KEYWORD_ONLY) - - self.assertIs(parameters['kwargs'].kind, Parameter.VAR_KEYWORD) - self.assertEqual('kwargs', parameter_names[-1]) - - f = Foo(1, number1=32, value='World') - self.assertEqual(f.arg1, 1) - self.assertEqual(f.number1, 32) - self.assertEqual(f.number2, 0) - self.assertEqual(f.value, 'World') - - def test_duplicate_init(self): - @signature_has_traits - class Foo(HasTraits): - number1 = Int() - number2 = Int() - - def __init__(self, number1, **kwargs): - self.test = number1 - - super(Foo, self).__init__(number1=number1, **kwargs) - - parameters = signature(Foo).parameters - parameter_names = list(parameters) - - self.assertListEqual(parameter_names, ['number1', 'number2', 'kwargs']) - - f = Foo(number1=32, number2=36) - self.assertEqual(f.test, 32) - self.assertEqual(f.number1, 32) - self.assertEqual(f.number2, 36) - - def test_full_init(self): - @signature_has_traits - class Foo(HasTraits): - number1 = Int() - number2 = Int() - value = Unicode('Hello') - - def __init__(self, arg1, arg2=None, *pos_args, **kw_args): - self.arg1 = arg1 - self.arg2 = arg2 - self.pos_args = pos_args - self.kw_args = kw_args - - super(Foo, self).__init__(*pos_args, **kw_args) - - parameters = signature(Foo).parameters - parameter_names = list(parameters) - - self.assertIs(parameters['arg1'].kind, Parameter.POSITIONAL_OR_KEYWORD) - self.assertEqual('arg1', parameter_names[0]) - - self.assertIs(parameters['arg2'].kind, Parameter.POSITIONAL_OR_KEYWORD) - self.assertEqual('arg2', parameter_names[1]) - - self.assertIs(parameters['pos_args'].kind, Parameter.VAR_POSITIONAL) - self.assertEqual('pos_args', parameter_names[2]) - - self.assertIs(parameters['number1'].kind, Parameter.KEYWORD_ONLY) - self.assertIs(parameters['number2'].kind, Parameter.KEYWORD_ONLY) - self.assertIs(parameters['value'].kind, Parameter.KEYWORD_ONLY) - - self.assertIs(parameters['kw_args'].kind, Parameter.VAR_KEYWORD) - self.assertEqual('kw_args', parameter_names[-1]) - - f = Foo(1, 3, 45, 'hey', number1=32, value='World') - self.assertEqual(f.arg1, 1) - self.assertEqual(f.arg2, 3) - self.assertTupleEqual(f.pos_args, (45, 'hey')) - self.assertEqual(f.number1, 32) - self.assertEqual(f.number2, 0) - self.assertEqual(f.value, 'World') - - def test_no_kwargs(self): - with self.assertRaises(RuntimeError): - @signature_has_traits - class Foo(HasTraits): - number1 = Int() - number2 = Int() - - def __init__(self, arg1, arg2=None): - pass +from unittest import TestCase + +from inspect import Signature, Parameter, signature + +from traitlets.traitlets import HasTraits, Int, Unicode + +from traitlets.utils.decorators import signature_has_traits + + +class TestExpandSignature(TestCase): + + def test_no_init(self): + @signature_has_traits + class Foo(HasTraits): + number1 = Int() + number2 = Int() + value = Unicode('Hello') + + parameters = signature(Foo).parameters + parameter_names = list(parameters) + + self.assertIs(parameters['args'].kind, Parameter.VAR_POSITIONAL) + self.assertEqual('args', parameter_names[0]) + + self.assertIs(parameters['number1'].kind, Parameter.KEYWORD_ONLY) + self.assertIs(parameters['number2'].kind, Parameter.KEYWORD_ONLY) + self.assertIs(parameters['value'].kind, Parameter.KEYWORD_ONLY) + + self.assertIs(parameters['kwargs'].kind, Parameter.VAR_KEYWORD) + self.assertEqual('kwargs', parameter_names[-1]) + + f = Foo(number1=32, value='World') + self.assertEqual(f.number1, 32) + self.assertEqual(f.number2, 0) + self.assertEqual(f.value, 'World') + + def test_partial_init(self): + @signature_has_traits + class Foo(HasTraits): + number1 = Int() + number2 = Int() + value = Unicode('Hello') + + def __init__(self, arg1, **kwargs): + self.arg1 = arg1 + + super(Foo, self).__init__(**kwargs) + + parameters = signature(Foo).parameters + parameter_names = list(parameters) + + self.assertIs(parameters['arg1'].kind, Parameter.POSITIONAL_OR_KEYWORD) + self.assertEqual('arg1', parameter_names[0]) + + self.assertIs(parameters['number1'].kind, Parameter.KEYWORD_ONLY) + self.assertIs(parameters['number2'].kind, Parameter.KEYWORD_ONLY) + self.assertIs(parameters['value'].kind, Parameter.KEYWORD_ONLY) + + self.assertIs(parameters['kwargs'].kind, Parameter.VAR_KEYWORD) + self.assertEqual('kwargs', parameter_names[-1]) + + f = Foo(1, number1=32, value='World') + self.assertEqual(f.arg1, 1) + self.assertEqual(f.number1, 32) + self.assertEqual(f.number2, 0) + self.assertEqual(f.value, 'World') + + def test_duplicate_init(self): + @signature_has_traits + class Foo(HasTraits): + number1 = Int() + number2 = Int() + + def __init__(self, number1, **kwargs): + self.test = number1 + + super(Foo, self).__init__(number1=number1, **kwargs) + + parameters = signature(Foo).parameters + parameter_names = list(parameters) + + self.assertListEqual(parameter_names, ['number1', 'number2', 'kwargs']) + + f = Foo(number1=32, number2=36) + self.assertEqual(f.test, 32) + self.assertEqual(f.number1, 32) + self.assertEqual(f.number2, 36) + + def test_full_init(self): + @signature_has_traits + class Foo(HasTraits): + number1 = Int() + number2 = Int() + value = Unicode('Hello') + + def __init__(self, arg1, arg2=None, *pos_args, **kw_args): + self.arg1 = arg1 + self.arg2 = arg2 + self.pos_args = pos_args + self.kw_args = kw_args + + super(Foo, self).__init__(*pos_args, **kw_args) + + parameters = signature(Foo).parameters + parameter_names = list(parameters) + + self.assertIs(parameters['arg1'].kind, Parameter.POSITIONAL_OR_KEYWORD) + self.assertEqual('arg1', parameter_names[0]) + + self.assertIs(parameters['arg2'].kind, Parameter.POSITIONAL_OR_KEYWORD) + self.assertEqual('arg2', parameter_names[1]) + + self.assertIs(parameters['pos_args'].kind, Parameter.VAR_POSITIONAL) + self.assertEqual('pos_args', parameter_names[2]) + + self.assertIs(parameters['number1'].kind, Parameter.KEYWORD_ONLY) + self.assertIs(parameters['number2'].kind, Parameter.KEYWORD_ONLY) + self.assertIs(parameters['value'].kind, Parameter.KEYWORD_ONLY) + + self.assertIs(parameters['kw_args'].kind, Parameter.VAR_KEYWORD) + self.assertEqual('kw_args', parameter_names[-1]) + + f = Foo(1, 3, 45, 'hey', number1=32, value='World') + self.assertEqual(f.arg1, 1) + self.assertEqual(f.arg2, 3) + self.assertTupleEqual(f.pos_args, (45, 'hey')) + self.assertEqual(f.number1, 32) + self.assertEqual(f.number2, 0) + self.assertEqual(f.value, 'World') + + def test_no_kwargs(self): + with self.assertRaises(RuntimeError): + @signature_has_traits + class Foo(HasTraits): + number1 = Int() + number2 = Int() + + def __init__(self, arg1, arg2=None): + pass diff --git a/contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py b/contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py index a263e4eb89..fb3266942f 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py +++ b/contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py @@ -1,29 +1,29 @@ -# 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. -"""Tests for traitlets.utils.importstring.""" - -import os -from unittest import TestCase - -from traitlets.utils.importstring import import_item - - -class TestImportItem(TestCase): - - def test_import_unicode(self): - self.assertIs(os, import_item('os')) - self.assertIs(os.path, import_item('os.path')) - self.assertIs(os.path.join, import_item('os.path.join')) - - def test_bad_input(self): - class NotAString(object): - pass - msg = ( - "import_item accepts strings, " - "not '%s'." % NotAString - ) - with self.assertRaisesRegex(TypeError, msg): - import_item(NotAString()) +# 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. +"""Tests for traitlets.utils.importstring.""" + +import os +from unittest import TestCase + +from traitlets.utils.importstring import import_item + + +class TestImportItem(TestCase): + + def test_import_unicode(self): + self.assertIs(os, import_item('os')) + self.assertIs(os.path, import_item('os.path')) + self.assertIs(os.path.join, import_item('os.path.join')) + + def test_bad_input(self): + class NotAString(object): + pass + msg = ( + "import_item accepts strings, " + "not '%s'." % NotAString + ) + with self.assertRaisesRegex(TypeError, msg): + import_item(NotAString()) diff --git a/contrib/python/traitlets/py3/traitlets/utils/text.py b/contrib/python/traitlets/py3/traitlets/utils/text.py index 330a42b26c..92464a5a6c 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/text.py +++ b/contrib/python/traitlets/py3/traitlets/utils/text.py @@ -1,38 +1,38 @@ -""" -Utilities imported from ipython_genutils -""" - -import re -from textwrap import dedent, indent as _indent -import textwrap - - -def indent(val): - res = _indent(val, " ") - return res - - -def wrap_paragraphs(text: str, ncols=80): - """Wrap multiple paragraphs to fit a specified width. - - This is equivalent to textwrap.wrap, but with support for multiple - paragraphs, as separated by empty lines. - - Returns - ------- - - list of complete paragraphs, wrapped to fill `ncols` columns. - """ - paragraph_re = re.compile(r"\n(\s*\n)+", re.MULTILINE) - text = dedent(text).strip() - paragraphs = paragraph_re.split(text)[::2] # every other entry is space - out_ps = [] - indent_re = re.compile(r"\n\s+", re.MULTILINE) - for p in paragraphs: - # presume indentation that survives dedent is meaningful formatting, - # so don't fill unless text is flush. - if indent_re.search(p) is None: - # wrap paragraph - p = textwrap.fill(p, ncols) - out_ps.append(p) - return out_ps +""" +Utilities imported from ipython_genutils +""" + +import re +from textwrap import dedent, indent as _indent +import textwrap + + +def indent(val): + res = _indent(val, " ") + return res + + +def wrap_paragraphs(text: str, ncols=80): + """Wrap multiple paragraphs to fit a specified width. + + This is equivalent to textwrap.wrap, but with support for multiple + paragraphs, as separated by empty lines. + + Returns + ------- + + list of complete paragraphs, wrapped to fill `ncols` columns. + """ + paragraph_re = re.compile(r"\n(\s*\n)+", re.MULTILINE) + text = dedent(text).strip() + paragraphs = paragraph_re.split(text)[::2] # every other entry is space + out_ps = [] + indent_re = re.compile(r"\n\s+", re.MULTILINE) + for p in paragraphs: + # presume indentation that survives dedent is meaningful formatting, + # so don't fill unless text is flush. + if indent_re.search(p) is None: + # wrap paragraph + p = textwrap.fill(p, ncols) + out_ps.append(p) + return out_ps diff --git a/contrib/python/traitlets/py3/ya.make b/contrib/python/traitlets/py3/ya.make index b8322bc4fa..46980f21b3 100644 --- a/contrib/python/traitlets/py3/ya.make +++ b/contrib/python/traitlets/py3/ya.make @@ -1,16 +1,16 @@ -# Generated by devtools/yamaker (pypi). - -PY3_LIBRARY() +# Generated by devtools/yamaker (pypi). -PROVIDES(python_traitlets) +PY3_LIBRARY() -OWNER(borman nslus g:python-contrib) +PROVIDES(python_traitlets) + +OWNER(borman nslus g:python-contrib) VERSION(5.1.1) - -LICENSE(BSD-3-Clause) -NO_LINT() +LICENSE(BSD-3-Clause) + +NO_LINT() PY_SRCS( TOP_LEVEL @@ -21,30 +21,30 @@ PY_SRCS( traitlets/config/configurable.py traitlets/config/loader.py traitlets/config/manager.py - traitlets/config/sphinxdoc.py + traitlets/config/sphinxdoc.py traitlets/log.py - traitlets/tests/__init__.py - traitlets/tests/_warnings.py - traitlets/tests/utils.py + traitlets/tests/__init__.py + traitlets/tests/_warnings.py + traitlets/tests/utils.py traitlets/traitlets.py traitlets/utils/__init__.py traitlets/utils/bunch.py - traitlets/utils/decorators.py - traitlets/utils/descriptions.py + traitlets/utils/decorators.py + traitlets/utils/descriptions.py traitlets/utils/getargspec.py traitlets/utils/importstring.py traitlets/utils/sentinel.py - traitlets/utils/text.py + traitlets/utils/text.py +) + +RESOURCE_FILES( + PREFIX contrib/python/traitlets/py3/ + .dist-info/METADATA + .dist-info/top_level.txt ) -RESOURCE_FILES( - PREFIX contrib/python/traitlets/py3/ - .dist-info/METADATA - .dist-info/top_level.txt -) - END() RECURSE_FOR_TESTS( - tests + tests ) diff --git a/contrib/python/traitlets/ya.make b/contrib/python/traitlets/ya.make index 15dba47ae3..3156aae8c5 100644 --- a/contrib/python/traitlets/ya.make +++ b/contrib/python/traitlets/ya.make @@ -2,19 +2,19 @@ PY23_LIBRARY() LICENSE(Service-Py23-Proxy) -OWNER(g:python-contrib) +OWNER(g:python-contrib) -IF (PYTHON2) - PEERDIR(contrib/python/traitlets/py2) -ELSE() - PEERDIR(contrib/python/traitlets/py3) +IF (PYTHON2) + PEERDIR(contrib/python/traitlets/py2) +ELSE() + PEERDIR(contrib/python/traitlets/py3) ENDIF() NO_LINT() END() -RECURSE( - py2 - py3 +RECURSE( + py2 + py3 ) |