Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions traitlets/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __init__(self, config=None):

# 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):
Expand Down Expand Up @@ -272,7 +272,7 @@ def class_get_trait_help(cls, trait, inst=None, helptext=None):

if 'Enum' in trait.__class__.__name__:
# include Enum choices
lines.append(indent('Choices: %r' % (trait.values,)))
lines.append(indent('Choices: %s' % trait.info()))

if inst is not None:
lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
Expand Down Expand Up @@ -322,7 +322,7 @@ def _defining_class(cls, trait, classes):
@classmethod
def class_config_section(cls, classes=None):
"""Get the config section for this class.

Parameters
----------
classes: list, optional
Expand Down Expand Up @@ -368,7 +368,7 @@ def c(s):
lines.append(c(trait.help))
if 'Enum' in type(trait).__name__:
# include Enum choices
lines.append('# Choices: %r' % (trait.values,))
lines.append('# Choices: %s' % trait.info())
lines.append('# Default: %s' % default_repr)
else:
# Trait appears multiple times and isn't defined here.
Expand Down
118 changes: 98 additions & 20 deletions traitlets/config/tests/test_configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
)

from traitlets.traitlets import (
Integer, Float, Unicode, List, Dict, Set, Enum,
_deprecations_shown, validate,
Integer, Float, Unicode, List, Dict, Set, Enum, FuzzyEnum,
CaselessStrEnum, _deprecations_shown, validate,
)

from traitlets.config.loader import Config
Expand Down Expand Up @@ -192,38 +192,116 @@ class MyConf(Configurable):
help="Many choices.").tag(config=True)

help_str = "Many choices."
enum_choices_str = "Choices: ['Choice1', 'choice2']"
enum_choices_str = "Choices: any of ['Choice1', 'choice2']"
or_none_str = "or None"

self.assertIn(help_str, MyConf.class_get_help())
self.assertIn(enum_choices_str, MyConf.class_get_help())
cls_help = MyConf.class_get_help()

self.assertIn(help_str, MyConf.class_config_section())
self.assertIn(enum_choices_str, MyConf.class_config_section())
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(MyConf.class_config_section().index(enum_choices_str),
MyConf.class_config_section().index(help_str))
self.assertGreater(cls_cfg.index(enum_choices_str),
cls_cfg.index(help_str))

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'"

self.assertIn(help_str, MyConf2.class_get_help())
self.assertIn(enum_choices_str, MyConf2.class_get_help())
self.assertIn(defaults_str, MyConf2.class_get_help())
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))

@mark.skipif(sys.version_info < (3, ),
reason="Unicodes printed with `u` prefix in PY2!'")
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(MyConf2.class_get_help().index(defaults_str),
MyConf2.class_get_help().index(enum_choices_str))
self.assertGreater(cls3_cfg.index(defaults_str),
cls3_cfg.index(enum_choices_str))

self.assertIn(help_str, MyConf2.class_config_section())
self.assertIn(enum_choices_str, MyConf2.class_config_section())
self.assertIn(defaults_str, MyConf2.class_config_section())
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(MyConf2.class_config_section().index(defaults_str),
MyConf2.class_config_section().index(enum_choices_str))
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))



Expand Down Expand Up @@ -499,7 +577,7 @@ def _config_default(self):
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
Expand Down
17 changes: 17 additions & 0 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2214,6 +2214,7 @@ def info(self):
return result + ' or None'
return result


class CaselessStrEnum(Enum):
"""An enum of strings where the case should be ignored."""

Expand All @@ -2232,6 +2233,13 @@ def validate(self, obj, value):
return v
self.error(obj, value)

def info(self):
""" Returns a description of the trait."""
result = 'any of %s (case-insensitive)' % (self.values, )
if self.allow_none:
return result + ' or None'
return result


class FuzzyEnum(Enum):
"""An case-ignoring enum matching choices by unique prefixes/substrings."""
Expand Down Expand Up @@ -2268,6 +2276,15 @@ def validate(self, obj, value):

self.error(obj, value)

def info(self):
""" Returns a description of the trait."""
case = 'sensitive' if self.case_sensitive else 'insensitive'
substr = 'substring' if self.substring_matching else 'prefix'
result = 'any case-%s %s of %s' % (case, substr, self.values)
if self.allow_none:
return result + ' or None'
return result


class Container(Instance):
"""An instance of a container (list, set, etc.)
Expand Down