diff --git a/traitlets/config/configurable.py b/traitlets/config/configurable.py index 0d074bb4..be3087f5 100644 --- a/traitlets/config/configurable.py +++ b/traitlets/config/configurable.py @@ -79,6 +79,17 @@ 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): + """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: @@ -93,12 +104,11 @@ def __init__(self, config=None): else: # allow _config_default to return something self._load_config(self.config) + self.unobserve(notice_config_override) + + for name in config_override_names: + setattr(self, name, kwargs[name]) - # Ensure explicit kwargs are applied after loading config. - # This is usually redundant, but ensures config doesn't override - # explicitly assigned values. - for key, value in kwargs.items(): - setattr(self, key, value) #------------------------------------------------------------------------- # Static trait notifiations diff --git a/traitlets/config/tests/test_configurable.py b/traitlets/config/tests/test_configurable.py index 2fbe21de..4baa1069 100644 --- a/traitlets/config/tests/test_configurable.py +++ b/traitlets/config/tests/test_configurable.py @@ -6,6 +6,7 @@ import logging import sys +import warnings from unittest import TestCase from pytest import mark @@ -18,7 +19,7 @@ from traitlets.traitlets import ( Integer, Float, Unicode, List, Dict, Set, Enum, - _deprecations_shown, + _deprecations_shown, validate, ) from traitlets.config.loader import Config @@ -498,6 +499,32 @@ 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 + # 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):