Repository: python-cmd2/cmd2

Issue:
Re-evaluate visibility of EmptyStatement exception
In `cmd2.py` we define an `EmptyStatement` exception. In `__init__.py` we import that exception, making it available as `cmd2.EmptyStatement`. As far as I can tell, we only use this exception for internal purposes: no methods available as part of our public API throw this exception.

If this is true, then it seem like:

1. We shouldn't document `EmptyStatement` as part of our public API, and
2. We should remove the package level import of `EmptyStatement` from `__init__.py`.

What do you think?

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb81ab17..6d91aaca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## 0.10.1 (TBD)
+## 0.10.1 (February TBD, 2020)
 * Bug Fixes
     * Corrected issue where the actual new value was not always being printed in do_set. This occurred in cases where
       the typed value differed from what the setter had converted it to.
@@ -19,6 +19,7 @@
 * Other
     * Removed undocumented `py run` command since it was replaced by `run_pyscript` a while ago
     * Renamed `AutoCompleter` to `ArgparseCompleter` for clarity
+    * Custom `EmptyStatement` exception is no longer part of the documented public API
 
 ## 0.10.0 (February 7, 2020)
 * Enhancements
diff --git a/cmd2/__init__.py b/cmd2/__init__.py
index 73d70821..63e27812 100644
--- a/cmd2/__init__.py
+++ b/cmd2/__init__.py
@@ -22,7 +22,7 @@ if cmd2_parser_module is not None:
 
 # Get the current value for argparse_custom.DEFAULT_ARGUMENT_PARSER
 from .argparse_custom import DEFAULT_ARGUMENT_PARSER
-from .cmd2 import Cmd, EmptyStatement
+from .cmd2 import Cmd
 from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS
 from .decorators import categorize, with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
 from .parsing import Statement
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 0bb4921e..7b88eaf8 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -50,6 +50,7 @@ from . import utils
 from .argparse_custom import CompletionItem, DEFAULT_ARGUMENT_PARSER
 from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
 from .decorators import with_argparser
+from .exceptions import EmbeddedConsoleExit, EmptyStatement
 from .history import History, HistoryItem
 from .parsing import StatementParser, Statement, Macro, MacroArg, shlex_split
 from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt, vt100_support, rl_make_safe_prompt, rl_warning
@@ -106,16 +107,6 @@ class _SavedCmd2Env:
         self.sys_stdin = None
 
 
-class EmbeddedConsoleExit(SystemExit):
-    """Custom exception class for use with the py command."""
-    pass
-
-
-class EmptyStatement(Exception):
-    """Custom exception class for handling behavior when the user just presses <Enter>."""
-    pass
-
-
 # Contains data about a disabled command which is used to restore its original functions when the command is enabled
 DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function', 'completer_function'])
 
diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py
new file mode 100644
index 00000000..747e2368
--- /dev/null
+++ b/cmd2/exceptions.py
@@ -0,0 +1,12 @@
+# coding=utf-8
+"""Custom exceptions for cmd2.  These are NOT part of the public API and are intended for internal use only."""
+
+
+class EmbeddedConsoleExit(SystemExit):
+    """Custom exception class for use with the py command."""
+    pass
+
+
+class EmptyStatement(Exception):
+    """Custom exception class for handling behavior when the user just presses <Enter>."""
+    pass
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
deleted file mode 100644
index 400993aa..00000000
--- a/docs/api/exceptions.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Exceptions
-==========
-
-.. autoexception:: cmd2.EmptyStatement
diff --git a/docs/api/index.rst b/docs/api/index.rst
index f0324eab..346fc274 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -6,7 +6,6 @@ API Reference
 
    cmd
    decorators
-   exceptions
    ansi
    utility_classes
    utility_functions
diff --git a/docs/features/hooks.rst b/docs/features/hooks.rst
index ee1d5fbc..ba9af573 100644
--- a/docs/features/hooks.rst
+++ b/docs/features/hooks.rst
@@ -102,12 +102,7 @@ called each time it was registered.
 Postparsing, precommand, and postcommand hook methods share some common ways to
 influence the command processing loop.
 
-If a hook raises a ``cmd2.EmptyStatement`` exception:
-- no more hooks (except command finalization hooks) of any kind will be called
-- if the command has not yet been executed, it will not be executed
-- no error message will be displayed to the user
-
-If a hook raises any other exception:
+If a hook raises an exception:
 - no more hooks (except command finalization hooks) of any kind will be called
 - if the command has not yet been executed, it will not be executed
 - the exception message will be displayed for the user.


Test patch:
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index dc9a3fa1..2db52d39 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -20,7 +20,7 @@ except ImportError:
     from unittest import mock
 
 import cmd2
-from cmd2 import ansi, clipboard, constants, plugin, utils, COMMAND_NAME
+from cmd2 import ansi, clipboard, constants, exceptions, plugin, utils, COMMAND_NAME
 from .conftest import (run_cmd, normalize, verify_help_text, HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT,
                        SHOW_LONG, complete_tester, odd_file_names)
 
@@ -1258,7 +1258,7 @@ def multiline_app():
     return app
 
 def test_multiline_complete_empty_statement_raises_exception(multiline_app):
-    with pytest.raises(cmd2.EmptyStatement):
+    with pytest.raises(exceptions.EmptyStatement):
         multiline_app._complete_statement('')
 
 def test_multiline_complete_statement_without_terminator(multiline_app):
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 2114bfaa..435f22eb 100755
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -7,7 +7,7 @@ import attr
 import pytest
 
 import cmd2
-from cmd2 import constants, utils
+from cmd2 import constants, exceptions, utils
 from cmd2.parsing import StatementParser, shlex_split
 
 @pytest.fixture
@@ -588,10 +588,10 @@ def test_parse_unclosed_quotes(parser):
 
 def test_empty_statement_raises_exception():
     app = cmd2.Cmd()
-    with pytest.raises(cmd2.EmptyStatement):
+    with pytest.raises(exceptions.EmptyStatement):
         app._complete_statement('')
 
-    with pytest.raises(cmd2.EmptyStatement):
+    with pytest.raises(exceptions.EmptyStatement):
         app._complete_statement(' ')
 
 @pytest.mark.parametrize('line,command,args', [
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index f7065db5..c118b60d 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -14,7 +14,7 @@ except ImportError:
     from unittest import mock
 
 import cmd2
-from cmd2 import plugin
+from cmd2 import exceptions, plugin
 
 
 class Plugin:
@@ -81,7 +81,7 @@ class Plugin:
     def postparse_hook_emptystatement(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData:
         """A postparsing hook with raises an EmptyStatement exception"""
         self.called_postparsing += 1
-        raise cmd2.EmptyStatement
+        raise exceptions.EmptyStatement
 
     def postparse_hook_exception(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData:
         """A postparsing hook which raises an exception"""
@@ -126,7 +126,7 @@ class Plugin:
     def precmd_hook_emptystatement(self, data: plugin.PrecommandData) -> plugin.PrecommandData:
         """A precommand hook which raises an EmptyStatement exception"""
         self.called_precmd += 1
-        raise cmd2.EmptyStatement
+        raise exceptions.EmptyStatement
 
     def precmd_hook_exception(self, data: plugin.PrecommandData) -> plugin.PrecommandData:
         """A precommand hook which raises an exception"""