Skip to content

Commit 484b6cb

Browse files
committed
The functions cmd2 adds to Namespaces (get_statement() and get_handler()) are now
Cmd2AttributeWrapper objects named cmd2_statement and cmd2_handler. This makes it easy to filter out which attributes in an argparse.Namespace were added by cmd2.
1 parent 97c348c commit 484b6cb

File tree

13 files changed

+76
-41
lines changed

13 files changed

+76
-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:

tests/test_argparse.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,14 @@ def do_base(self, args):
289289
func = getattr(args, 'func')
290290
func(self, args)
291291

292-
293292
# Add a subcommand using as_subcommand_to decorator
294293
has_subcmd_parser = cmd2.Cmd2ArgumentParser(description="Tests as_subcmd_to decorator")
295294
has_subcmd_subparsers = has_subcmd_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND')
296295
has_subcmd_subparsers.required = True
297296

298297
@cmd2.with_argparser(has_subcmd_parser)
299298
def do_test_subcmd_decorator(self, args: argparse.Namespace):
300-
handler = args.get_handler()
299+
handler = args.cmd2_handler.get()
301300
handler(args)
302301

303302
subcmd_parser = cmd2.Cmd2ArgumentParser(add_help=False, description="The subcommand")
@@ -306,7 +305,7 @@ def do_test_subcmd_decorator(self, args: argparse.Namespace):
306305
def subcmd_func(self, args: argparse.Namespace):
307306
# Make sure printing the Namespace works. The way we originally added get_hander()
308307
# to it resulted in a RecursionError when printing.
309-
print(args)
308+
self.poutput(args)
310309

311310
@pytest.fixture
312311
def subcommand_app():

tests/test_plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def do_skip_postcmd_hooks(self, _):
279279
@with_argparser(parser)
280280
def do_argparse_cmd(self, namespace: argparse.Namespace):
281281
"""Repeat back the arguments"""
282-
self.poutput(namespace.get_statement())
282+
self.poutput(namespace.cmd2_statement.get())
283283

284284
###
285285
#

tests_isolated/test_commandset/test_commandset.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def do_elderberry(self, ns: argparse.Namespace):
7272
@cmd2.with_argparser(main_parser)
7373
def do_main(self, args: argparse.Namespace) -> None:
7474
# Call handler for whatever subcommand was selected
75-
handler = args.get_handler()
75+
handler = args.cmd2_handler.get()
7676
handler(args)
7777

7878
# main -> sub
@@ -309,7 +309,7 @@ def namespace_provider(self) -> argparse.Namespace:
309309
@cmd2.with_argparser(cut_parser)
310310
def do_cut(self, ns: argparse.Namespace):
311311
"""Cut something"""
312-
handler = ns.get_handler()
312+
handler = ns.cmd2_handler.get()
313313
if handler is not None:
314314
# Call whatever subcommand function was selected
315315
handler(ns)
@@ -330,7 +330,7 @@ def do_stir(self, ns: argparse.Namespace):
330330
self._cmd.poutput('Need to cut before stirring')
331331
return
332332

333-
handler = ns.get_handler()
333+
handler = ns.cmd2_handler.get()
334334
if handler is not None:
335335
# Call whatever subcommand function was selected
336336
handler(ns)
@@ -345,7 +345,7 @@ def do_stir(self, ns: argparse.Namespace):
345345

346346
@cmd2.as_subcommand_to('stir', 'pasta', stir_pasta_parser)
347347
def stir_pasta(self, ns: argparse.Namespace):
348-
handler = ns.get_handler()
348+
handler = ns.cmd2_handler.get()
349349
if handler is not None:
350350
# Call whatever subcommand function was selected
351351
handler(ns)
@@ -360,7 +360,7 @@ def __init__(self, dummy):
360360

361361
def do_cut(self, ns: argparse.Namespace):
362362
"""Cut something"""
363-
handler = ns.get_handler()
363+
handler = ns.cmd2_handler.get()
364364
if handler is not None:
365365
# Call whatever subcommand function was selected
366366
handler(ns)
@@ -598,7 +598,7 @@ def __init__(self, *args, **kwargs):
598598
@cmd2.with_argparser(cut_parser)
599599
def do_cut(self, ns: argparse.Namespace):
600600
"""Cut something"""
601-
handler = ns.get_handler()
601+
handler = ns.cmd2_handler.get()
602602
if handler is not None:
603603
# Call whatever subcommand function was selected
604604
handler(ns)

0 commit comments

Comments
 (0)