diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2020-04-09 13:59:33 -0400 |
---|---|---|
committer | Todd Leonhardt <todd.leonhardt@gmail.com> | 2020-04-09 13:59:33 -0400 |
commit | f9a3aca8ae61448faf2349df14ce4175e2874be3 (patch) | |
tree | f0ed2d74dbe54c3a262620750eab6de4123e40b2 | |
parent | 22d61a87386c73f3ea8686402ce8327346b58376 (diff) | |
download | cmd2-git-f9a3aca8ae61448faf2349df14ce4175e2874be3.tar.gz |
Added documentation about decorator order and updated an example
Also:
- Deal with warnings when building docs with Sphinx 3.0.0
-rw-r--r-- | docs/conf.py | 13 | ||||
-rw-r--r-- | docs/features/argument_processing.rst | 35 | ||||
-rwxr-xr-x | examples/help_categories.py | 29 |
3 files changed, 71 insertions, 6 deletions
diff --git a/docs/conf.py b/docs/conf.py index cd320f52..9bb2e855 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -169,3 +169,16 @@ intersphinx_mapping = {'http://docs.python.org/': None} autodoc_default_options = { 'member-order': 'bysource' } + +# Ignore nitpicky warnings from autodoc which are occurring for very new versions of Sphinx and autodoc +# They seem to be happening because autodoc is now trying to add hyperlinks to docs for typehint classes +nitpick_ignore = [ + ('py:class', 'Callable[[None], None]'), + ('py:class', 'cmd2.cmd2.Cmd'), + ('py:class', 'cmd2.parsing.Statement'), + ('py:class', 'IO'), + ('py:class', 'None'), + ('py:class', 'Optional[Callable[[...], argparse.Namespace]]'), + ('py:class', 'TextIO'), + ('py:class', 'Union[None, Iterable, Callable]'), +] diff --git a/docs/features/argument_processing.rst b/docs/features/argument_processing.rst index 204c2876..e8e5457d 100644 --- a/docs/features/argument_processing.rst +++ b/docs/features/argument_processing.rst @@ -347,3 +347,38 @@ class which inherits from ``argparse.ArgumentParser`` and improves error and help output. +Decorator Order +--------------- + +If you are using custom decorators in combination with either +``@cmd2.with_argparser`` or ``@cmd2.with_argparser_and_unknown_args``, then the +order of your custom decorator(s) relative to the ``cmd2`` decorator matters +when it comes to runtime behavior and ``argparse`` errors. There is nothing +``cmd2``-specific here, this is just a side-effect of how decorators work in +Python. To learn more about how decorators work, see decorator_primer_. + +If you want your custom decorator's runtime behavior to occur in the case of +an ``argparse`` error, then that decorator needs to go **after** the +``argparse`` one, e.g.:: + + @cmd2.with_argparser(foo_parser) + @my_decorator + def do_foo(self, args: argparse.Namespace) -> None: + """foo docs""" + pass + +However, if you do NOT want the customer decorator runtime behavior to occur +even in the case of an `argparse` error, then that decorator needs to go +**before** the ``arpgarse`` one, e.g.:: + + @my_decorator + @cmd2.with_argparser(bar_parser) + def do_bar(self, args: argparse.Namespace) -> None: + """bar docs""" + pass + +The help_categories_ example demonstrates both above cases in a concrete +fashion. + +.. _decorator_primer: https://realpython.com/primer-on-python-decorators +.. _help_categories: https://github.com/python-cmd2/cmd2/blob/master/examples/help_categories.py diff --git a/examples/help_categories.py b/examples/help_categories.py index 602bf441..7401bafe 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -2,17 +2,28 @@ # coding=utf-8 """ A sample application for tagging categories on commands. -""" -import argparse +It also demonstrates the effects of decorator order when it comes to argparse errors occurring. +""" +import functools import cmd2 from cmd2 import COMMAND_NAME +def my_decorator(f): + @functools.wraps(f) + def wrapper(*args, **kwds): + print('Calling decorated function') + return f(*args, **kwds) + return wrapper + + class HelpCategories(cmd2.Cmd): """ Example cmd2 application. """ + START_TIMES = ['now', 'later', 'sometime', 'whenever'] + # Command categories CMD_CAT_CONNECTING = 'Connecting' CMD_CAT_APP_MGMT = 'Application Management' @@ -42,6 +53,11 @@ class HelpCategories(cmd2.Cmd): """Deploy command""" self.poutput('Deploy') + start_parser = cmd2.DEFAULT_ARGUMENT_PARSER(description='Start', epilog='my_decorator runs even with argparse errors') + start_parser.add_argument('when', choices=START_TIMES, help='Specify when to start') + + @my_decorator + @cmd2.with_argparser(start_parser) def do_start(self, _): """Start command""" self.poutput('Start') @@ -54,13 +70,13 @@ class HelpCategories(cmd2.Cmd): """Redeploy command""" self.poutput('Redeploy') - restart_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) - restart_parser.add_argument('when', default='now', - choices=['now', 'later', 'sometime', 'whenever'], - help='Specify when to restart') + restart_parser = cmd2.DEFAULT_ARGUMENT_PARSER(description='Restart', + epilog='my_decorator does not run when argparse errors') + restart_parser.add_argument('when', choices=START_TIMES, help='Specify when to restart') @cmd2.with_argparser(restart_parser) @cmd2.with_category(CMD_CAT_APP_MGMT) + @my_decorator def do_restart(self, _): """Restart command""" self.poutput('Restart') @@ -158,5 +174,6 @@ class HelpCategories(cmd2.Cmd): if __name__ == '__main__': import sys + c = HelpCategories() sys.exit(c.cmdloop()) |