Repository: ipython/traitlets (python)

Issue:
Use absolute filename when configuration file conflict
Cf this issue:

https://github.com/jupyter/notebook/issues/1690

it is hard to figure out which files are conflicting, getting the absolute path woudl help a lot. 

Pull request:
ensure .py/.json collision detection only affects pairs in the same directory
and include absolute path in the warning to clarify where the collision happened.

avoids collision warning when path priority causes override, which is normal and shouldn't result in a warning.

Previously, the warning could be triggered when `/etc/jupyter/some_config.py` and `~/.jupyter/some_config.py` 

closes #279

Patch:
diff --git a/traitlets/config/application.py b/traitlets/config/application.py
index a373124..889b8c2 100644
--- a/traitlets/config/application.py
+++ b/traitlets/config/application.py
@@ -550,6 +550,8 @@ class Application(SingletonConfigurable):
                 log.debug("Looking for %s in %s", basefilename, path)
             jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
             config = None
+            loaded = []
+            filenames = []
             for loader in [pyloader, jsonloader]:
                 try:
                     config = loader.load_config()
@@ -569,32 +571,31 @@ class Application(SingletonConfigurable):
                     if log:
                         log.debug("Loaded config file: %s", loader.full_filename)
                 if config:
-                     yield config
-
-        raise StopIteration
+                    for filename, earlier_config in zip(filenames, loaded):
+                        collisions = earlier_config.collisions(config)
+                        if collisions and log:
+                            log.warning("Collisions detected in {0} and {1} config files."
+                                " {1} has higher priority: {2}".format(
+                                filename, loader.full_filename, json.dumps(collisions, indent=2),
+                            ))
+                    yield config
+                    loaded.append(config)
+                    filenames.append(loader.full_filename)
+            
 
 
     @catch_config_error
     def load_config_file(self, filename, path=None):
         """Load config files by filename and path."""
         filename, ext = os.path.splitext(filename)
-        loaded = []
         new_config = Config()
         for config in self._load_config_files(filename, path=path, log=self.log,
             raise_config_file_errors=self.raise_config_file_errors,
         ):
-            loaded.append(config)
             new_config.merge(config)
         # add self.cli_config to preserve CLI config priority
         new_config.merge(self.cli_config)
         self.update_config(new_config)
-        if len(loaded) > 1:
-            collisions = loaded[0].collisions(loaded[1])
-            if collisions:
-                self.log.warning("Collisions detected in {0}.py and {0}.json config files."
-                              " {0}.json has higher priority: {1}".format(
-                              filename, json.dumps(collisions, indent=2),
-                ))
 
 
     def _classes_in_config_sample(self):


Test patch:
diff --git a/traitlets/config/tests/test_application.py b/traitlets/config/tests/test_application.py
index 2a1caf2..5db0243 100644
--- a/traitlets/config/tests/test_application.py
+++ b/traitlets/config/tests/test_application.py
@@ -6,6 +6,7 @@ Tests for traitlets.config.application.Application
 # Copyright (c) IPython Development Team.
 # Distributed under the terms of the Modified BSD License.
 
+import json
 import logging
 import os
 from io import StringIO
@@ -299,6 +300,31 @@ class TestApplication(TestCase):
                 app.init_bar()
                 self.assertEqual(app.bar.b, 1)
 
+    @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs')
+    def test_log_collisions(self):
+        app = MyApp()
+        app.log = logging.getLogger()
+        app.log.setLevel(logging.INFO)
+        name = 'config'
+        with TemporaryDirectory('_1') as td:
+            with open(pjoin(td, name + '.py'), 'w') as f:
+                f.write("get_config().Bar.b = 1")
+            with open(pjoin(td, name + '.json'), 'w') as f:
+                json.dump({
+                    'Bar': {
+                        'b': 2
+                    }
+                }, f)
+            with self.assertLogs(app.log, logging.WARNING) as captured:
+                app.load_config_file(name, path=[td])
+                app.init_bar()
+        assert app.bar.b == 2
+        output = '\n'.join(captured.output)
+        assert 'Collision' in output
+        assert '1 ignored, using 2' in output
+        assert pjoin(td, name + '.py') in output
+        assert pjoin(td, name + '.json') in output
+
     @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs')
     def test_log_bad_config(self):
         app = MyApp()