Skip to content

Commit f4943a9

Browse files
authored
Merge pull request #985 from python-cmd2/dynamic_value
Added Cmd2AttributeWrapper class
2 parents 97c348c + 478ea83 commit f4943a9

14 files changed

+86
-41
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 1.3.6 (August 26, 2020)
2+
* Breaking changes
3+
* The functions cmd2 adds to Namespaces (`get_statement()` and `get_handler()`) are now
4+
`Cmd2AttributeWrapper` objects named `cmd2_statement` and `cmd2_handler`. This makes it
5+
easy to filter out which attributes in an `argparse.Namespace` were added by `cmd2`.
6+
* Deprecations
7+
* ``Namespace.__statement__`` will be removed in `cmd2` 2.0.0. Use `Namespace.get_statement()` going forward.
8+
19
## 1.3.5 (August 25, 2020)
210
* Bug Fixes
311
* Fixed `RecursionError` when printing an `argparse.Namespace` caused by custom attribute cmd2 was adding

cmd2/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
pass
1717

1818
from .ansi import style, fg, bg
19-
from .argparse_custom import Cmd2ArgumentParser, CompletionItem, set_default_argument_parser
19+
from .argparse_custom import Cmd2ArgumentParser, Cmd2AttributeWrapper, CompletionItem, set_default_argument_parser
2020

2121
# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER
2222
import argparse

cmd2/argparse_custom.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ def my_completer_method(self, text, line, begidx, endidx, arg_tokens)
221221
import sys
222222
# noinspection PyUnresolvedReferences,PyProtectedMember
223223
from argparse import ONE_OR_MORE, ZERO_OR_MORE, ArgumentError, _
224-
from typing import Callable, Optional, Tuple, Type, Union
224+
from typing import Any, Callable, Optional, Tuple, Type, Union
225225

226226
from . import ansi, constants
227227

@@ -904,6 +904,24 @@ def _print_message(self, message, file=None):
904904
ansi.style_aware_write(file, message)
905905

906906

907+
class Cmd2AttributeWrapper:
908+
"""
909+
Wraps a cmd2-specific attribute added to an argparse Namespace.
910+
This makes it easy to know which attributes in a Namespace are
911+
arguments from a parser and which were added by cmd2.
912+
"""
913+
def __init__(self, attribute: Any):
914+
self.__attribute = attribute
915+
916+
def get(self) -> Any:
917+
"""Get the value of the attribute"""
918+
return self.__attribute
919+
920+
def set(self, new_val: Any) -> None:
921+
"""Set the value of the attribute"""
922+
self.__attribute = new_val
923+
924+
907925
# The default ArgumentParser class for a cmd2 app
908926
DEFAULT_ARGUMENT_PARSER = Cmd2ArgumentParser
909927

cmd2/cmd2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,7 +2685,7 @@ def _cmdloop(self) -> None:
26852685
def do_alias(self, args: argparse.Namespace) -> None:
26862686
"""Manage aliases"""
26872687
# Call handler for whatever subcommand was selected
2688-
handler = args.get_handler()
2688+
handler = args.cmd2_handler.get()
26892689
handler(args)
26902690

26912691
# alias -> create
@@ -2812,7 +2812,7 @@ def _alias_list(self, args: argparse.Namespace) -> None:
28122812
def do_macro(self, args: argparse.Namespace) -> None:
28132813
"""Manage macros"""
28142814
# Call handler for whatever subcommand was selected
2815-
handler = args.get_handler()
2815+
handler = args.cmd2_handler.get()
28162816
handler(args)
28172817

28182818
# macro -> create

cmd2/decorators.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
55

66
from . import constants
7+
from .argparse_custom import Cmd2AttributeWrapper
78
from .exceptions import Cmd2ArgparseError
89
from .parsing import Statement
910

@@ -186,10 +187,10 @@ def with_argparser_and_unknown_args(parser: argparse.ArgumentParser, *,
186187
needs to be prepopulated with state data that affects parsing.
187188
:param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes
188189
:return: function that gets passed argparse-parsed args in a ``Namespace`` and a list
189-
of unknown argument strings. A member called ``__statement__`` is added to the
190-
``Namespace`` to provide command functions access to the :class:`cmd2.Statement`
191-
object. This can be useful if the command function needs to know the command line.
192-
``__statement__`` can also be retrieved by calling ``get_statement()`` on the ``Namespace``.
190+
of unknown argument strings. A :class:`cmd2.argparse_custom.Cmd2AttributeWrapper` called
191+
``cmd2_statement`` is included in the ``Namespace`` to provide access to the :class:`cmd2.Statement`
192+
object. that was created when parsing the command line. This can be useful if the command function
193+
needs to know the command line.
193194
194195
:Example:
195196
@@ -223,12 +224,12 @@ def with_argparser(parser: argparse.ArgumentParser, *,
223224
:param ns_provider: An optional function that accepts a cmd2.Cmd object as an argument and returns an
224225
argparse.Namespace. This is useful if the Namespace needs to be prepopulated with
225226
state data that affects parsing.
226-
:param preserve_quotes: if True, then arguments passed to argparse maintain their quotes
227+
:param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes
227228
:param with_unknown_args: if true, then capture unknown args
228-
:return: function that gets passed the argparse-parsed args in a Namespace
229-
A member called __statement__ is added to the Namespace to provide command functions access to the
230-
Statement object. This can be useful if the command function needs to know the command line.
231-
``__statement__`` can also be retrieved by calling ``get_statement()`` on the ``Namespace``.
229+
:return: function that gets passed argparse-parsed args in a ``Namespace``
230+
A :class:`cmd2.argparse_custom.Cmd2AttributeWrapper` called ``cmd2_statement`` is included
231+
in the ``Namespace`` to provide access to the :class:`cmd2.Statement` object that was created when
232+
parsing the command line. This can be useful if the command function needs to know the command line.
232233
233234
:Example:
234235
@@ -298,13 +299,20 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]:
298299
except SystemExit:
299300
raise Cmd2ArgparseError
300301
else:
301-
# Add statement to Namespace and a getter function for it
302+
# Add statement to Namespace as __statement__ (this is deprecated and will be removed in 2.0)
302303
setattr(ns, constants.NS_ATTR_STATEMENT, statement)
303-
setattr(ns, 'get_statement', lambda: statement)
304304

305-
# Add getter function for subcmd handler, which can be None
306-
subcmd_handler = getattr(ns, constants.NS_ATTR_SUBCMD_HANDLER, None)
307-
setattr(ns, 'get_handler', lambda: subcmd_handler)
305+
# Add wrapped statement to Namespace as cmd2_statement
306+
setattr(ns, 'cmd2_statement', Cmd2AttributeWrapper(statement))
307+
308+
# Add wrapped subcmd handler (which can be None) to Namespace as cmd2_handler
309+
handler = getattr(ns, constants.NS_ATTR_SUBCMD_HANDLER, None)
310+
setattr(ns, 'cmd2_handler', Cmd2AttributeWrapper(handler))
311+
312+
# Remove the subcmd handler attribute from the Namespace
313+
# since cmd2_handler is how a developer accesses it.
314+
if hasattr(ns, constants.NS_ATTR_SUBCMD_HANDLER):
315+
delattr(ns, constants.NS_ATTR_SUBCMD_HANDLER)
308316

309317
args_list = _arg_swap(args, statement, *new_args)
310318
return func(*args_list, **kwargs)

docs/features/argument_processing.rst

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ handles the following for you:
1313

1414
3. Passes the resulting ``argparse.Namespace`` object to your command function.
1515
The ``Namespace`` includes the ``Statement`` object that was created when
16-
parsing the command line. It is stored in the ``__statement__`` attribute of
17-
the ``Namespace`` and can also be retrieved by calling ``get_statement()``
18-
on the ``Namespace``.
16+
parsing the command line. It can be retrieved by calling
17+
``cmd2_statement.get()`` on the ``Namespace``.
1918

2019
4. Adds the usage message from the argument parser to your command.
2120

@@ -391,10 +390,13 @@ Reserved Argument Names
391390
Namespaces. To avoid naming collisions, do not use any of the names for your
392391
argparse arguments.
393392

393+
- ``cmd2_statement`` - ``cmd2.Cmd2AttributeWrapper`` object containing
394+
``cmd2.Statement`` object that was created when parsing the command line.
394395
- ``__statement__`` - ``cmd2.Statement`` object that was created when parsing
395-
the command line.
396-
- ``get_statement()`` - convenience function which returns the ``Statement``
397-
- ``__subcmd_handler__`` - points to subcommand handler function. This is added
398-
when using the ``@cmd2.as_subcommand_to`` decorator.
399-
- ``get_handler()`` - convenience function which returns the subcommand handler
400-
or ``None`` if one was not set
396+
the command line. (This is deprecated and will be removed in 2.0.0.) Use
397+
``cmd2_statement`` instead.
398+
399+
- ``__subcmd_handler__`` - used by cmd2 to identify the handler for a
400+
subcommand created with ``@cmd2.as_subcommand_to`` decorator.
401+
- ``cmd2_handler`` - ``cmd2.Cmd2AttributeWrapper`` object containing
402+
a subcommand handler function or ``None`` if one was not set.

docs/features/modular_commands.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ command and each CommandSet
316316
317317
@with_argparser(cut_parser)
318318
def do_cut(self, ns: argparse.Namespace):
319-
handler = ns.get_handler()
319+
handler = ns.cmd2_handler.get()
320320
if handler is not None:
321321
# Call whatever subcommand function was selected
322322
handler(ns)

docs/testing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ this, you should always mock with `Autospeccing <python_mock_autospeccing_>`_ or
3838
enabled.
3939

4040
Example of spec=True
41-
====================
41+
~~~~~~~~~~~~~~~~~~~~
4242
.. code-block:: python
4343
4444
def test_mocked_methods():

examples/decorator_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def do_speak(self, args: argparse.Namespace):
6767
def do_tag(self, args: argparse.Namespace):
6868
"""create an html tag"""
6969
# The Namespace always includes the Statement object created when parsing the command line
70-
statement = args.get_statement()
70+
statement = args.cmd2_statement.get()
7171

7272
self.poutput("The command line you ran was: {}".format(statement.command_and_args))
7373
self.poutput("It generated this tag:")

examples/modular_subcommands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def do_unload(self, ns: argparse.Namespace):
9898
@with_argparser(cut_parser)
9999
def do_cut(self, ns: argparse.Namespace):
100100
# Call handler for whatever subcommand was selected
101-
handler = ns.get_handler()
101+
handler = ns.cmd2_handler.get()
102102
if handler is not None:
103103
handler(ns)
104104
else:

0 commit comments

Comments
 (0)