Repository: python-cmd2/cmd2
Pull request #305 (2018-03-13 08:49:46)

AddSubMenu should work with default kwargs
The `AddSubMenu` class decorator should work also with default kwargs.

Before this PR if one tries to use it without `shared_attributes` and without setting `require_predefined_shares` to `False` the code crashes because it tries to access the keys of a `None` object (`shared_attributes` default kwarg)

codecov[bot]: # [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=h1) Report
> Merging [#305](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=desc) into [master](https://codecov.io/gh/python-cmd2/cmd2/commit/ece4ab4e8c02421efd467eebaa6b36d8b7dead00?src=pr&el=desc) will **increase** coverage by `0.06%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/python-cmd2/cmd2/pull/305/graphs/tree.svg?height=150&width=650&src=pr&token=eks2PUwa6A)](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #305      +/-   ##
==========================================
+ Coverage   91.06%   91.13%   +0.06%     
==========================================
  Files           1        1              
  Lines        1522     1522              
==========================================
+ Hits         1386     1387       +1     
+ Misses        136      135       -1
```


| [Impacted Files](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cmd2.py](https://codecov.io/gh/python-cmd2/cmd2/pull/305/diff?src=pr&el=tree#diff-Y21kMi5weQ==) | `91.13% <100%> (+0.06%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=footer). Last update [ece4ab4...50c6bb6](https://codecov.io/gh/python-cmd2/cmd2/pull/305?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cmd2.py b/cmd2.py
index 0fa31ebb..519d9925 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -867,14 +867,14 @@ class AddSubmenu(object):
             raise ValueError("reformat_prompt should be either a format string or None")
         self.reformat_prompt = reformat_prompt
 
+        self.shared_attributes = {} if shared_attributes is None else shared_attributes
         if require_predefined_shares:
-            for attr in shared_attributes.keys():
+            for attr in self.shared_attributes.keys():
                 if not hasattr(submenu, attr):
                     raise AttributeError("The shared attribute '{attr}' is not defined in {cmd}. Either define {attr} "
                                          "in {cmd} or set require_predefined_shares=False."
                                          .format(cmd=submenu.__class__.__name__, attr=attr))
 
-        self.shared_attributes = {} if shared_attributes is None else shared_attributes
         self.create_subclass = create_subclass
 
     def __call__(self, cmd_obj):


Test patch:
diff --git a/tests/test_submenu.py b/tests/test_submenu.py
index 9b42f38f..0b5330a8 100644
--- a/tests/test_submenu.py
+++ b/tests/test_submenu.py
@@ -8,8 +8,17 @@ import cmd2
 from conftest import run_cmd, StdOut, normalize
 
 
+class SecondLevelB(cmd2.Cmd):
+    """To be used as a second level command class. """
+
+    def __init__(self, *args, **kwargs):
+        cmd2.Cmd.__init__(self, *args, **kwargs)
+        self.prompt = '2ndLevel B '
+
+
 class SecondLevel(cmd2.Cmd):
     """To be used as a second level command class. """
+
     def __init__(self, *args, **kwargs):
         cmd2.Cmd.__init__(self, *args, **kwargs)
         self.prompt = '2ndLevel '
@@ -32,8 +41,10 @@ class SecondLevel(cmd2.Cmd):
 
 
 second_level_cmd = SecondLevel()
+second_level_b_cmd = SecondLevelB()
 
 
+@cmd2.AddSubmenu(second_level_b_cmd, command='secondb')
 @cmd2.AddSubmenu(second_level_cmd,
                  command='second',
                  aliases=('second_alias',),
@@ -63,6 +74,7 @@ def submenu_app():
     second_level_cmd.stdout = StdOut()
     return app
 
+
 @pytest.fixture
 def secondlevel_app():
     app = SecondLevel()
@@ -89,6 +101,7 @@ def test_submenu_say_from_top_level(submenu_app):
     assert len(out2) == 0
     assert out1[0] == "You called a command in TopLevel with {!r}.".format(line)
 
+
 def test_submenu_second_say_from_top_level(submenu_app):
     line = 'testing'
     out1, out2 = run_submenu_cmd(submenu_app, 'second say ' + line)
@@ -100,6 +113,7 @@ def test_submenu_second_say_from_top_level(submenu_app):
     assert len(out2) == 1
     assert out2[0] == "You called a command in SecondLevel with {!r}.".format(line)
 
+
 def test_submenu_say_from_second_level(secondlevel_app):
     line = 'testing'
     out = run_cmd(secondlevel_app, 'say ' + line)
@@ -135,4 +149,3 @@ def test_submenu_from_top_help_second_say(submenu_app):
 def test_submenu_shared_attribute(submenu_app):
     out1, out2 = run_submenu_cmd(submenu_app, 'second get_top_level_attr')
     assert out2 == [str(submenu_app.top_level_attr)]
-