Skip to content

Commit 6488e49

Browse files
committed
copy self.config in update_config
protects IPython's CLI priority, which accidentally relies upon self.config being replaced during load_config_files. for backport to 4.2.2
1 parent 0f490a6 commit 6488e49

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

traitlets/config/configurable.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ def _config_changed(self, change):
188188

189189
def update_config(self, config):
190190
"""Update config and load the new values"""
191+
# make a copy of our config prior to loading to protect IPython,
192+
# which relies on self.config being immutable
193+
# to preserve CLI config priority
194+
self.config = deepcopy(self.config)
191195
# load config
192196
self._load_config(config)
193197
# merge it into self.config

traitlets/config/tests/test_application.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,32 @@ def test_config_propagation(self):
115115
self.assertEqual(app.foo.i, 10)
116116
self.assertEqual(app.foo.j, 10)
117117
self.assertEqual(app.bar.enabled, False)
118+
119+
def test_ipython_cli_priority(self):
120+
name = 'config.py'
121+
class TestApp(Application):
122+
value = Unicode().tag(config=True)
123+
aliases = {'v': 'TestApp.value'}
124+
app = TestApp()
125+
with TemporaryDirectory() as td:
126+
config_file = pjoin(td, name)
127+
with open(config_file, 'w') as f:
128+
f.write("c.TestApp.value = 'config file'")
129+
# follow IPython's config-loading sequence to ensure CLI priority is preserved
130+
app.parse_command_line(['--v=cli'])
131+
# this is where IPython makes a mistake:
132+
# it assumes app.config will not be modified,
133+
# and storing a reference is storing a copy
134+
cli_config = app.config
135+
assert 'value' in app.config.TestApp
136+
assert app.config.TestApp.value == 'cli'
137+
app.load_config_file(name, path=[td])
138+
assert app.config.TestApp.value == 'config file'
139+
# enforce cl-opts override config file opts:
140+
# this is where IPython makes a mistake: it assumes
141+
# that cl_config is a different object, but it isn't.
142+
app.update_config(cli_config)
143+
assert app.config.TestApp.value == 'cli'
118144

119145
def test_flags(self):
120146
app = MyApp()

0 commit comments

Comments
 (0)