Repository: ipython/traitlets
Pull request #365 (2017-01-26 18:11:21)

FIX: KVConfigLoader could not handle lone dashes('-') as extra arguments
`KeyValueConfigLoader.load_config()` was considering lone dash(`-`) args as invalids
(these are frequently used to denote STDIN).

+ Fixed and added a TCs on App & Loader.
+ Minor refactorings 7 pep8 on the method fixed.


Patch:
diff --git a/docs/source/index.rst b/docs/source/index.rst
index d32b3f9..cd82207 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,8 +1,12 @@
 Traitlets
 =========
 
-:Release: |release|
-:Date: |today|
+:Release:       |release|
+:Date:          |today|
+:home:          https://github.com/ipython/traitlets
+:pypi-repo:     https://pypi.org/project/traitlets/
+:docs:          https://traitlets.readthedocs.io/
+:license:       Modified BSD License
 
 Traitlets is a framework that lets Python classes have attributes with type
 checking, dynamically calculated default values, and 'on change' callbacks.
diff --git a/traitlets/config/application.py b/traitlets/config/application.py
index 4bba244..c5f0ede 100644
--- a/traitlets/config/application.py
+++ b/traitlets/config/application.py
@@ -483,7 +483,7 @@ class Application(SingletonConfigurable):
             # or ask factory to create it...
             self.subapp = subapp(self)
         else:
-            raise AssertionError('Invalid mappings for subcommand %s!' % subc)
+            raise AssertionError("Invalid mappings for subcommand '%s'!" % subc)
 
         # ... and finally initialize subapp.
         self.subapp.initialize(argv)
diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py
index 0c809e0..68c0b68 100644
--- a/traitlets/config/loader.py
+++ b/traitlets/config/loader.py
@@ -687,19 +687,25 @@ class KeyValueConfigLoader(CommandLineConfigLoader):
 
         # ensure argv is a list of unicode strings:
         uargv = self._decode_argv(argv)
-        for idx,raw in enumerate(uargv):
+        for idx, raw in enumerate(uargv):
+            if raw == '--':
+                # Don't parse arguments after '--'.
+                # This is useful for relaying arguments to scripts, e.g.:
+                #     ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
+                self.extra_args.extend(uargv[idx + 1:])
+                break
+
             # strip leading '-'
             item = raw.lstrip('-')
 
-            if raw == '--':
-                # don't parse arguments after '--'
-                # this is useful for relaying arguments to scripts, e.g.
-                # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
-                self.extra_args.extend(uargv[idx+1:])
-                break
+            if not item:
+                ## It was either a lone '-' (i.e. denoting STDIN),
+                #  or more than 3 '---'; append it as is.
+                self.extra_args.append(raw)
+                continue
 
             if kv_pattern.match(raw):
-                lhs,rhs = item.split('=',1)
+                lhs, rhs = item.split('=', 1)
                 # Substitute longnames for aliases.
                 if lhs in aliases:
                     lhs = aliases[lhs]
@@ -713,22 +719,23 @@ class KeyValueConfigLoader(CommandLineConfigLoader):
 
             elif flag_pattern.match(raw):
                 if item in flags:
-                    cfg,help = flags[item]
+                    cfg, _ = flags[item]
                     self._load_flag(cfg)
                 else:
-                    raise ArgumentError("Unrecognized flag: '%s'"%raw)
+                    raise ArgumentError("Unrecognized flag: '%s'" % raw)
             elif raw.startswith('-'):
-                kv = '--'+item
+                kv = '--' + item
                 if kv_pattern.match(kv):
-                    raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
+                    raise ArgumentError("Invalid argument: '%s', did you mean '%s'?" % (raw, kv))
                 else:
-                    raise ArgumentError("Invalid argument: '%s'"%raw)
+                    raise ArgumentError("Invalid argument: '%s'" % raw)
             else:
                 # keep all args that aren't valid in a list,
                 # in case our parent knows what to do with them.
                 self.extra_args.append(item)
         return self.config
 
+
 class ArgParseConfigLoader(CommandLineConfigLoader):
     """A loader that uses the argparse module to load from the command line."""
 


Test patch:
diff --git a/traitlets/config/tests/test_application.py b/traitlets/config/tests/test_application.py
index eb05632..ce301be 100644
--- a/traitlets/config/tests/test_application.py
+++ b/traitlets/config/tests/test_application.py
@@ -341,12 +341,14 @@ class TestApplication(TestCase):
         self.assertEqual(app.config.MyApp.log_level, "CRITICAL")
 
     def test_extra_args(self):
+
         app = MyApp()
         app.parse_command_line(["--Bar.b=5", 'extra', "--disable", 'args'])
         app.init_bar()
         self.assertEqual(app.bar.enabled, False)
         self.assertEqual(app.bar.b, 5)
         self.assertEqual(app.extra_args, ['extra', 'args'])
+
         app = MyApp()
         app.parse_command_line(["--Bar.b=5", '--', 'extra', "--disable", 'args'])
         app.init_bar()
@@ -354,6 +356,10 @@ class TestApplication(TestCase):
         self.assertEqual(app.bar.b, 5)
         self.assertEqual(app.extra_args, ['extra', '--disable', 'args'])
 
+        app = MyApp()
+        app.parse_command_line(['-', '--disable', '--Bar.b=1', '-', 'extra'])
+        self.assertEqual(app.extra_args, ['-', '-', 'extra'])
+
     def test_unicode_argv(self):
         app = MyApp()
         app.parse_command_line(['ünîcødé'])
@@ -474,8 +480,8 @@ class TestApplication(TestCase):
         self.assertIsInstance(app.subapp.subapp, Sub3)
         self.assertTrue(app.subapp.subapp.flag)               # Set by factory.
         ## Check parent hierarchy.
-#        self.assertIs(app.subapp.parent, app)
-#        self.assertIs(app.subapp.subapp.parent, app.subapp)     # Set by factory.
+        self.assertIs(app.subapp.parent, app)
+        self.assertIs(app.subapp.subapp.parent, app.subapp)     # Set by factory.
 
 
 class Root(Application):
diff --git a/traitlets/config/tests/test_loader.py b/traitlets/config/tests/test_loader.py
index bfca88f..7a1ac3d 100644
--- a/traitlets/config/tests/test_loader.py
+++ b/traitlets/config/tests/test_loader.py
@@ -277,6 +277,10 @@ class TestKeyValueCL(TestCase):
         config = cl.load_config(['--', '--a=5', '--c=10'])
         self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
 
+        cl = self.klass(log=log)
+        config = cl.load_config(['-', 'extra', '--a=2', '--c=1', '-'])
+        self.assertEqual(cl.extra_args, ['-', 'extra', '-'])
+
     def test_unicode_args(self):
         cl = self.klass(log=log)
         argv = [u'--a=épsîlön']
@@ -483,7 +487,7 @@ class TestConfig(TestCase):
             _ = cfg._repr_html_
         self.assertNotIn('_repr_html_', cfg)
         self.assertEqual(len(cfg), 0)
-    
+
     def test_lazy_config_repr(self):
         cfg = Config()
         cfg.Class.lazy.append(1)
@@ -496,7 +500,7 @@ class TestConfig(TestCase):
         repr2 = repr(cfg)
         assert repr([0,1]) in repr2
         assert 'value=' in repr2
-        
+
 
     def test_getitem_not_section(self):
         cfg = Config()