Skip to content

Commit 4c0bdad

Browse files
committed
Minor formatting fixes. Injecting a function into namespace objects before passing to command handlers to access sub-command handlers
1 parent 2d24953 commit 4c0bdad

File tree

8 files changed

+54
-31
lines changed

8 files changed

+54
-31
lines changed

cmd2/argparse_custom.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,6 @@ class so cmd2 can remove subcommands from a parser.
584584
del self.choices[name]
585585

586586

587-
588587
# noinspection PyProtectedMember
589588
setattr(argparse._SubParsersAction, 'remove_parser', _SubParsersAction_remove_parser)
590589

cmd2/cmd2.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,9 @@ def _autoload_commands(self) -> None:
413413
existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets]
414414
for cmdset_type in all_commandset_defs:
415415
init_sig = inspect.signature(cmdset_type.__init__)
416-
if not (cmdset_type in existing_commandset_types or
417-
len(init_sig.parameters) != 1 or
418-
'self' not in init_sig.parameters):
416+
if not (cmdset_type in existing_commandset_types
417+
or len(init_sig.parameters) != 1
418+
or 'self' not in init_sig.parameters):
419419
cmdset = cmdset_type()
420420
self.install_command_set(cmdset)
421421

@@ -550,7 +550,7 @@ def _check_uninstallable(self, cmdset: CommandSet):
550550
methods = inspect.getmembers(
551551
cmdset,
552552
predicate=lambda meth: isinstance(meth, Callable)
553-
and hasattr(meth, '__name__') and meth.__name__.startswith(COMMAND_FUNC_PREFIX))
553+
and hasattr(meth, '__name__') and meth.__name__.startswith(COMMAND_FUNC_PREFIX))
554554

555555
for method in methods:
556556
command_name = method[0][len(COMMAND_FUNC_PREFIX):]
@@ -562,6 +562,7 @@ def _check_uninstallable(self, cmdset: CommandSet):
562562
command_func = self.cmd_func(command_name)
563563

564564
command_parser = getattr(command_func, constants.CMD_ATTR_ARGPARSER, None)
565+
565566
def check_parser_uninstallable(parser):
566567
for action in parser._actions:
567568
if isinstance(action, argparse._SubParsersAction):
@@ -621,7 +622,7 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
621622
command_handler = _partial_passthru(method, self)
622623
else:
623624
command_handler = method
624-
subcmd_parser.set_defaults(handler=command_handler)
625+
subcmd_parser.set_defaults(cmd2_handler=command_handler)
625626

626627
def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser:
627628
if not subcmd_names:
@@ -671,8 +672,8 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
671672
command_func = self.cmd_func(command_name)
672673

673674
if command_func is None: # pragma: no cover
674-
# This really shouldn't be possible since _register_subcommands would prevent this from happening
675-
# but keeping in case it does for some strange reason
675+
# This really shouldn't be possible since _register_subcommands would prevent this from happening
676+
# but keeping in case it does for some strange reason
676677
raise CommandSetRegistrationError('Could not find command "{}" needed by subcommand: {}'
677678
.format(command_name, str(method)))
678679
command_parser = getattr(command_func, constants.CMD_ATTR_ARGPARSER, None)

cmd2/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
# Whether or not tokens are unquoted before sending to argparse
5151
CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes'
5252

53+
# optional attribute
54+
SUBCMD_HANDLER = 'cmd2_handler'
55+
5356
# subcommand attributes for the base command name and the subcommand name
5457
SUBCMD_ATTR_COMMAND = 'parent_command'
5558
SUBCMD_ATTR_NAME = 'subcommand_name'

cmd2/decorators.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# coding=utf-8
22
"""Decorators for ``cmd2`` commands"""
33
import argparse
4+
import types
45
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
56

67
from . import constants
@@ -231,6 +232,12 @@ def cmd_wrapper(*args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> Optional[bo
231232
raise Cmd2ArgparseError
232233
else:
233234
setattr(ns, '__statement__', statement)
235+
236+
def get_handler(self: argparse.Namespace) -> Optional[Callable]:
237+
return getattr(self, constants.SUBCMD_HANDLER, None)
238+
239+
setattr(ns, 'get_handler', types.MethodType(get_handler, ns))
240+
234241
args_list = _arg_swap(args, statement, ns, unknown)
235242
return func(*args_list, **kwargs)
236243

@@ -316,6 +323,12 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]:
316323
raise Cmd2ArgparseError
317324
else:
318325
setattr(ns, '__statement__', statement)
326+
327+
def get_handler(self: argparse.Namespace) -> Optional[Callable]:
328+
return getattr(self, constants.SUBCMD_HANDLER, None)
329+
330+
setattr(ns, 'get_handler', types.MethodType(get_handler, ns))
331+
319332
args_list = _arg_swap(args, statement, ns)
320333
return func(*args_list, **kwargs)
321334

cmd2/exceptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class CommandSetRegistrationError(Exception):
3131
# The following exceptions are NOT part of the public API and are intended for internal use only.
3232
############################################################################################################
3333

34+
3435
class Cmd2ShlexError(Exception):
3536
"""Raised when shlex fails to parse a command line string in StatementParser"""
3637
pass

docs/features/modular_commands.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,10 @@ command and each CommandSet
314314
315315
@with_argparser(cut_parser)
316316
def do_cut(self, ns: argparse.Namespace):
317-
func = getattr(ns, 'handler', None)
318-
if func is not None:
317+
handler = ns.get_handler()
318+
if handler is not None:
319319
# Call whatever subcommand function was selected
320-
func(ns)
320+
handler(ns)
321321
else:
322322
# No subcommand was provided, so call help
323323
self.poutput('This command does nothing without sub-parsers registered')

examples/modular_subcommands.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ def do_unload(self, ns: argparse.Namespace):
9595

9696
@with_argparser(cut_parser)
9797
def do_cut(self, ns: argparse.Namespace):
98-
func = getattr(ns, 'handler', None)
99-
if func is not None:
98+
handler = ns.get_handler()
99+
if handler is not None:
100100
# Call whatever subcommand function was selected
101-
func(ns)
101+
handler(ns)
102102
else:
103103
# No subcommand was provided, so call help
104104
self.poutput('This command does nothing without sub-parsers registered')

isolated_tests/test_commandset/test_commandset.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,13 @@ def __init__(self, dummy):
281281
@cmd2.with_argparser(cut_parser)
282282
def do_cut(self, cmd: cmd2.Cmd, ns: argparse.Namespace):
283283
"""Cut something"""
284-
func = getattr(ns, 'handler', None)
285-
if func is not None:
284+
handler = ns.get_handler()
285+
if handler is not None:
286286
# Call whatever subcommand function was selected
287-
func(ns)
287+
handler(ns)
288288
else:
289289
# No subcommand was provided, so call help
290-
cmd.poutput('This command does nothing without sub-parsers registered')
290+
cmd.pwarning('This command does nothing without sub-parsers registered')
291291
cmd.do_help('cut')
292292

293293

@@ -297,13 +297,13 @@ def do_cut(self, cmd: cmd2.Cmd, ns: argparse.Namespace):
297297
@cmd2.with_argparser(stir_parser)
298298
def do_stir(self, cmd: cmd2.Cmd, ns: argparse.Namespace):
299299
"""Stir something"""
300-
func = getattr(ns, 'handler', None)
301-
if func is not None:
300+
handler = ns.get_handler()
301+
if handler is not None:
302302
# Call whatever subcommand function was selected
303-
func(ns)
303+
handler(ns)
304304
else:
305305
# No subcommand was provided, so call help
306-
cmd.poutput('This command does nothing without sub-parsers registered')
306+
cmd.pwarning('This command does nothing without sub-parsers registered')
307307
cmd.do_help('stir')
308308

309309
stir_pasta_parser = cmd2.Cmd2ArgumentParser('pasta', add_help=False)
@@ -312,10 +312,10 @@ def do_stir(self, cmd: cmd2.Cmd, ns: argparse.Namespace):
312312

313313
@cmd2.as_subcommand_to('stir', 'pasta', stir_pasta_parser)
314314
def stir_pasta(self, cmd: cmd2.Cmd, ns: argparse.Namespace):
315-
func = getattr(ns, 'handler', None)
316-
if func is not None:
315+
handler = ns.get_handler()
316+
if handler is not None:
317317
# Call whatever subcommand function was selected
318-
func(ns)
318+
handler(ns)
319319
else:
320320
cmd.poutput('Stir pasta haphazardly')
321321

@@ -327,10 +327,10 @@ def __init__(self, dummy):
327327

328328
def do_cut(self, cmd: cmd2.Cmd, ns: argparse.Namespace):
329329
"""Cut something"""
330-
func = getattr(ns, 'handler', None)
331-
if func is not None:
330+
handler = ns.get_handler()
331+
if handler is not None:
332332
# Call whatever subcommand function was selected
333-
func(ns)
333+
handler(ns)
334334
else:
335335
# No subcommand was provided, so call help
336336
cmd.poutput('This command does nothing without sub-parsers registered')
@@ -417,6 +417,9 @@ def test_subcommands(command_sets_manual):
417417
with pytest.raises(CommandSetRegistrationError):
418418
command_sets_manual._register_subcommands(fruit_cmds)
419419

420+
cmd_result = command_sets_manual.app_cmd('cut')
421+
assert 'This command does nothing without sub-parsers registered' in cmd_result.stderr
422+
420423
# verify that command set install without problems
421424
command_sets_manual.install_command_set(fruit_cmds)
422425
command_sets_manual.install_command_set(veg_cmds)
@@ -433,6 +436,9 @@ def test_subcommands(command_sets_manual):
433436
# check that the alias shows up correctly
434437
assert ['banana', 'bananer', 'bokchoy'] == command_sets_manual.completion_matches
435438

439+
cmd_result = command_sets_manual.app_cmd('cut banana discs')
440+
assert 'cutting banana: discs' in cmd_result.stdout
441+
436442
text = ''
437443
line = 'cut bokchoy {}'.format(text)
438444
endidx = len(line)
@@ -546,10 +552,10 @@ def __init__(self, *args, **kwargs):
546552
@cmd2.with_argparser(cut_parser)
547553
def do_cut(self, ns: argparse.Namespace):
548554
"""Cut something"""
549-
func = getattr(ns, 'handler', None)
550-
if func is not None:
555+
handler = ns.get_handler()
556+
if handler is not None:
551557
# Call whatever subcommand function was selected
552-
func(ns)
558+
handler(ns)
553559
else:
554560
# No subcommand was provided, so call help
555561
self.poutput('This command does nothing without sub-parsers registered')

0 commit comments

Comments
 (0)