From 3e275731c62f7f2db89d59017b8c986d3c5fd18f Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 30 Jun 2020 10:00:44 +0200 Subject: [PATCH 1/3] nicer falsy behavior for LazyConfigValue - empty lazy config is falsy, non-empty is truthy - checking `in` for empty lazy config returns False, indicating there's no config there --- traitlets/config/loader.py | 24 +++++++++++++--- traitlets/config/tests/test_loader.py | 40 ++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py index c389af17..cd690d2f 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -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 @@ -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) @@ -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. @@ -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") @@ -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__ diff --git a/traitlets/config/tests/test_loader.py b/traitlets/config/tests/test_loader.py index 4263fb66..c890b787 100644 --- a/traitlets/config/tests/test_loader.py +++ b/traitlets/config/tests/test_loader.py @@ -487,6 +487,30 @@ def test_getattr_not_section(self): assert isinstance(foo, LazyConfigValue) self.assertIn('foo', 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() self.assertNotIn('_repr_html_', cfg) @@ -508,7 +532,6 @@ 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) @@ -655,3 +678,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 From acffb9cfa4ce8eab15ff2cc3d4096de9f92cdcde Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 2 Jul 2020 08:09:19 -0700 Subject: [PATCH 2/3] nicer repr to not be confised in tests --- traitlets/config/loader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py index cd690d2f..f385226d 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -361,6 +361,9 @@ def __delattr__(self, key): except KeyError as e: raise AttributeError(e) + def __repr__(self): + return 'Config('+super().__repr__()+')' + #----------------------------------------------------------------------------- # Config loading classes From 380dc4a29783dfa31b4cd7a131fbd1cd8652f348 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 4 Sep 2020 08:39:39 +0200 Subject: [PATCH 3/3] fix a couple of tests now that lazyconfig is falsy --- traitlets/config/tests/test_loader.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/traitlets/config/tests/test_loader.py b/traitlets/config/tests/test_loader.py index c890b787..0b6db8a2 100644 --- a/traitlets/config/tests/test_loader.py +++ b/traitlets/config/tests/test_loader.py @@ -485,7 +485,10 @@ 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() @@ -537,7 +540,10 @@ def test_getitem_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_merge_no_copies(self): c = Config()