Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nicer falsy behavior for LazyConfigValue #584

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
27 changes: 23 additions & 4 deletions traitlets/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ def print_help(self, file=None):
# Config class for holding config information
#-----------------------------------------------------------------------------


def execfile(fname, glob):
with open(fname, 'rb') as f:
exec(compile(f.read(), fname, 'exec'), glob, glob)


class LazyConfigValue(HasTraits):
"""Proxy object for exposing methods on configurable containers

Expand All @@ -82,6 +84,15 @@ class LazyConfigValue(HasTraits):
_prepend = List()
_inserts = List()

def __bool__(self):
# be falsy if we're empty
return bool(
self._extend
or self._prepend
or self._inserts
or self._update
)

def append(self, obj):
self._extend.append(obj)

Expand All @@ -92,7 +103,6 @@ def prepend(self, other):
"""like list.extend, but for the front"""
self._prepend[:0] = other


def merge_into(self, other):
"""
Merge with another earlier LazyConfig Value or an earlier container.
Expand Down Expand Up @@ -126,8 +136,6 @@ def merge_into(self, other):
# 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")
Expand Down Expand Up @@ -272,7 +280,15 @@ def __contains__(self, key):
return False
return remainder in self[first]

return super(Config, self).__contains__(key)
if super(Config, self).__contains__(key):
item = self[key]
if isinstance(item, LazyConfigValue) and not item:
# don't consider empty lazy config present
# since it doesn't contain anything
return False
return True
return False


# .has_key is deprecated for dictionaries.
has_key = __contains__
Expand Down Expand Up @@ -345,6 +361,9 @@ def __delattr__(self, key):
except KeyError as e:
raise AttributeError(e)

def __repr__(self):
return 'Config('+super().__repr__()+')'


#-----------------------------------------------------------------------------
# Config loading classes
Expand Down
50 changes: 47 additions & 3 deletions traitlets/config/tests/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,34 @@ def test_getattr_not_section(self):
self.assertNotIn('foo', cfg)
foo = cfg.foo
assert isinstance(foo, LazyConfigValue)
self.assertIn('foo', cfg)
# empty lazy value indicates no config is actually there
assert 'foo' not in cfg
foo.append('x')
assert 'foo' in cfg

def test_lazy_truthiness(self):
cfg = Config()
lazy = cfg.empty_trait
assert not lazy
assert not cfg.empty_trait

cfg.append_trait.append('x')
assert cfg.append_trait

cfg.prepend_trait.prepend('x')
assert cfg.prepend_trait

cfg.extend_trait.extend(['x'])
assert cfg.extend_trait

cfg.Class.insert_trait.insert(0, 'x')
assert cfg.Class.insert_trait

cfg.Class.update_trait.update({'key': 'value'})
assert cfg.Class.update_trait

cfg.Class.add_trait.add('item')
assert cfg.Class.add_trait

def test_getattr_private_missing(self):
cfg = Config()
Expand All @@ -508,13 +535,15 @@ def test_lazy_config_repr(self):
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)
# empty lazy value indicates no config is actually there
assert 'foo' not in cfg
foo.append('x')
assert 'foo' in cfg

def test_merge_no_copies(self):
c = Config()
Expand Down Expand Up @@ -655,3 +684,18 @@ def test_merge_multi_lazy_update_III(self):
c.merge(c2)

self.assertEqual(c.Foo.trait._update, {"a": 1, "z": 26, "b": 1})

def test_empty_lazy_not_in(self):
c = Config()
lazy1 = c.Foo.trait
# 'in' check looks for *actual* config
# so creation of a lazy config handle
# doesn't mean we have config there
assert 'trait' not in c.Foo
# make sure that subsequent lazy access
# doesn't create a new LazyConfigValue, though
c.Foo.trait.append("x")
assert 'trait' in c.Foo
assert c.Foo.trait
assert lazy1 is c.Foo.trait
assert lazy1