Skip to content

Commit 6fbf084

Browse files
authored
Removed Cmd2AttributeWrapper class. (#1625)
1 parent 6b12864 commit 6fbf084

17 files changed

+103
-117
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ prompt is displayed.
6969
- Removed `set_ap_completer_type()` and `get_ap_completer_type()` since `ap_completer_type` is
7070
now a public member of `Cmd2ArgumentParser`.
7171
- Moved `set_parser_prog()` to `Cmd2ArgumentParser.update_prog()`.
72+
- Renamed `cmd2_handler` to `cmd2_subcmd_handler` in the `argparse.Namespace` for clarity.
73+
- Removed `Cmd2AttributeWrapper` class. `argparse.Namespace` objects passed to command functions
74+
now contain direct attributes for `cmd2_statement` and `cmd2_subcmd_handler`.
7275
- Enhancements
7376
- New `cmd2.Cmd` parameters
7477
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These

cmd2/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from .argparse_completer import set_default_ap_completer_type
1515
from .argparse_custom import (
1616
Cmd2ArgumentParser,
17-
Cmd2AttributeWrapper,
1817
register_argparse_argument_parameter,
1918
set_default_argument_parser_type,
2019
)
@@ -63,7 +62,6 @@
6362
'DEFAULT_SHORTCUTS',
6463
# Argparse Exports
6564
'Cmd2ArgumentParser',
66-
'Cmd2AttributeWrapper',
6765
'register_argparse_argument_parameter',
6866
'set_default_ap_completer_type',
6967
'set_default_argument_parser_type',

cmd2/argparse_custom.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def register_argparse_argument_parameter(
329329
raise KeyError(f"Accessor methods for '{param_name}' already exist on argparse.Action")
330330

331331
# Check for the prefixed internal attribute name collision (e.g., _cmd2_<param_name>)
332-
attr_name = constants.cmd2_attr_name(param_name)
332+
attr_name = constants.cmd2_private_attr_name(param_name)
333333
if hasattr(argparse.Action, attr_name):
334334
raise KeyError(f"The internal attribute '{attr_name}' already exists on argparse.Action")
335335

@@ -1047,26 +1047,6 @@ def _check_value(self, action: argparse.Action, value: Any) -> None:
10471047
raise ArgumentError(action, msg % args)
10481048

10491049

1050-
class Cmd2AttributeWrapper:
1051-
"""Wraps a cmd2-specific attribute added to an argparse Namespace.
1052-
1053-
This makes it easy to know which attributes in a Namespace are
1054-
arguments from a parser and which were added by cmd2.
1055-
"""
1056-
1057-
def __init__(self, attribute: Any) -> None:
1058-
"""Initialize Cmd2AttributeWrapper instances."""
1059-
self.__attribute = attribute
1060-
1061-
def get(self) -> Any:
1062-
"""Get the value of the attribute."""
1063-
return self.__attribute
1064-
1065-
def set(self, new_val: Any) -> None:
1066-
"""Set the value of the attribute."""
1067-
self.__attribute = new_val
1068-
1069-
10701050
# Parser type used by cmd2's built-in commands.
10711051
# Set it using cmd2.set_default_argument_parser_type().
10721052
DEFAULT_ARGUMENT_PARSER: type[Cmd2ArgumentParser] = Cmd2ArgumentParser

cmd2/cmd2.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
Completions,
124124
)
125125
from .constants import (
126-
CLASS_ATTR_DEFAULT_HELP_CATEGORY,
126+
CMDSET_ATTR_DEFAULT_HELP_CATEGORY,
127127
COMMAND_FUNC_PREFIX,
128128
COMPLETER_FUNC_PREFIX,
129129
HELP_FUNC_PREFIX,
@@ -840,7 +840,7 @@ def register_command_set(self, cmdset: CommandSet) -> None:
840840
),
841841
)
842842

843-
default_category = getattr(cmdset, CLASS_ATTR_DEFAULT_HELP_CATEGORY, None)
843+
default_category = getattr(cmdset, CMDSET_ATTR_DEFAULT_HELP_CATEGORY, None)
844844

845845
installed_attributes = []
846846
try:
@@ -3729,8 +3729,7 @@ def _build_alias_parser() -> Cmd2ArgumentParser:
37293729
def do_alias(self, args: argparse.Namespace) -> None:
37303730
"""Manage aliases."""
37313731
# Call handler for whatever subcommand was selected
3732-
handler = args.cmd2_handler.get()
3733-
handler(args)
3732+
args.cmd2_subcmd_handler(args)
37343733

37353734
# alias -> create
37363735
@classmethod
@@ -3946,8 +3945,7 @@ def _build_macro_parser() -> Cmd2ArgumentParser:
39463945
def do_macro(self, args: argparse.Namespace) -> None:
39473946
"""Manage macros."""
39483947
# Call handler for whatever subcommand was selected
3949-
handler = args.cmd2_handler.get()
3950-
handler(args)
3948+
args.cmd2_subcmd_handler(args)
39513949

39523950
# macro -> create
39533951
@classmethod

cmd2/command_definition.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
)
1212

1313
from .constants import (
14-
CLASS_ATTR_DEFAULT_HELP_CATEGORY,
14+
CMDSET_ATTR_DEFAULT_HELP_CATEGORY,
1515
COMMAND_FUNC_PREFIX,
1616
)
1717
from .exceptions import CommandSetRegistrationError
@@ -46,16 +46,12 @@ def with_default_category(category: str, *, heritable: bool = True) -> Callable[
4646

4747
def decorate_class(cls: CommandSetType) -> CommandSetType:
4848
if heritable:
49-
setattr(cls, CLASS_ATTR_DEFAULT_HELP_CATEGORY, category)
49+
setattr(cls, CMDSET_ATTR_DEFAULT_HELP_CATEGORY, category)
5050

5151
import inspect
5252

53-
from .constants import (
54-
CMD_ATTR_HELP_CATEGORY,
55-
)
56-
from .decorators import (
57-
with_category,
58-
)
53+
from .constants import CMD_ATTR_HELP_CATEGORY
54+
from .decorators import with_category
5955

6056
# get members of the class that meet the following criteria:
6157
# 1. Must be a function

cmd2/constants.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,35 +32,72 @@
3232
COMPLETER_FUNC_PREFIX = 'complete_'
3333

3434
# Prefix for private attributes injected by cmd2
35-
CMD2_ATTR_PREFIX = '_cmd2_'
35+
PRIVATE_ATTR_PREFIX = '_cmd2_'
3636

37+
# Prefix for public attributes injected by cmd2
38+
PUBLIC_ATTR_PREFIX = 'cmd2_'
3739

38-
def cmd2_attr_name(name: str) -> str:
39-
"""Build an attribute name with the cmd2 prefix.
40+
41+
def cmd2_private_attr_name(name: str) -> str:
42+
"""Build a private attribute name with the _cmd2_ prefix.
43+
44+
:param name: the name of the attribute
45+
:return: the prefixed attribute name
46+
"""
47+
return f'{PRIVATE_ATTR_PREFIX}{name}'
48+
49+
50+
def cmd2_public_attr_name(name: str) -> str:
51+
"""Build a public attribute name with the cmd2_ prefix.
4052
4153
:param name: the name of the attribute
4254
:return: the prefixed attribute name
4355
"""
44-
return f'{CMD2_ATTR_PREFIX}{name}'
56+
return f'{PUBLIC_ATTR_PREFIX}{name}'
57+
58+
59+
##################################################################################################
60+
# Attribute Injection Constants
61+
#
62+
# cmd2 attaches custom attributes to various objects (functions, classes, and parsers) to
63+
# track metadata and manage command state.
64+
#
65+
# Private attributes (_cmd2_ prefix) are for internal framework logic.
66+
# Public attributes (cmd2_ prefix) are available for developer use, typically within
67+
# argparse Namespaces.
68+
##################################################################################################
69+
70+
# --- Private Internal Attributes ---
71+
72+
# Attached to a command function; defines its argument parser
73+
CMD_ATTR_ARGPARSER = cmd2_private_attr_name('argparser')
74+
75+
# Attached to a command function; defines its help section category
76+
CMD_ATTR_HELP_CATEGORY = cmd2_private_attr_name('help_category')
77+
78+
# Attached to a command function; defines whether tokens are unquoted before reaching argparse
79+
CMD_ATTR_PRESERVE_QUOTES = cmd2_private_attr_name('preserve_quotes')
80+
81+
# Attached to a CommandSet class; defines a default help category for its member functions
82+
CMDSET_ATTR_DEFAULT_HELP_CATEGORY = cmd2_private_attr_name('default_help_category')
83+
84+
# Attached to a subcommand function; defines the full command path to the parent (e.g., "foo" or "foo bar")
85+
SUBCMD_ATTR_COMMAND = cmd2_private_attr_name('parent_command')
4586

87+
# Attached to a subcommand function; defines the name of this specific subcommand (e.g., "bar")
88+
SUBCMD_ATTR_NAME = cmd2_private_attr_name('subcommand_name')
4689

47-
# The custom help category a command belongs to
48-
CMD_ATTR_HELP_CATEGORY = cmd2_attr_name('help_category')
49-
CLASS_ATTR_DEFAULT_HELP_CATEGORY = cmd2_attr_name('default_help_category')
90+
# Attached to a subcommand function; specifies kwargs passed to add_parser()
91+
SUBCMD_ATTR_ADD_PARSER_KWARGS = cmd2_private_attr_name('subcommand_add_parser_kwargs')
5092

51-
# The argparse parser for the command
52-
CMD_ATTR_ARGPARSER = cmd2_attr_name('argparser')
93+
# Attached to an argparse parser; identifies the CommandSet instance it belongs to
94+
PARSER_ATTR_COMMANDSET_ID = cmd2_private_attr_name('command_set_id')
5395

54-
# Whether or not tokens are unquoted before sending to argparse
55-
CMD_ATTR_PRESERVE_QUOTES = cmd2_attr_name('preserve_quotes')
5696

57-
# subcommand attributes for the base command name and the subcommand name
58-
SUBCMD_ATTR_COMMAND = cmd2_attr_name('parent_command')
59-
SUBCMD_ATTR_NAME = cmd2_attr_name('subcommand_name')
60-
SUBCMD_ATTR_ADD_PARSER_KWARGS = cmd2_attr_name('subcommand_add_parser_kwargs')
97+
# --- Public Developer Attributes ---
6198

62-
# argparse attribute uniquely identifying the command set instance
63-
PARSER_ATTR_COMMANDSET_ID = cmd2_attr_name('command_set_id')
99+
# Attached to an argparse Namespace; contains the Statement object created during parsing
100+
NS_ATTR_STATEMENT = cmd2_public_attr_name('statement')
64101

65-
# custom attributes added to argparse Namespaces
66-
NS_ATTR_SUBCMD_HANDLER = cmd2_attr_name('subcmd_handler')
102+
# Attached to an argparse Namespace; the function to handle the subcommand (or None)
103+
NS_ATTR_SUBCMD_HANDLER = cmd2_public_attr_name('subcmd_handler')

cmd2/decorators.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
)
1414

1515
from . import constants
16-
from .argparse_custom import (
17-
Cmd2ArgumentParser,
18-
Cmd2AttributeWrapper,
19-
)
16+
from .argparse_custom import Cmd2ArgumentParser
2017
from .command_definition import (
2118
CommandFunc,
2219
CommandSet,
@@ -233,9 +230,9 @@ def with_argparser(
233230
:param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes
234231
:param with_unknown_args: if true, then capture unknown args
235232
:return: function that gets passed argparse-parsed args in a ``Namespace``
236-
A [cmd2.argparse_custom.Cmd2AttributeWrapper][] called ``cmd2_statement`` is included
237-
in the ``Namespace`` to provide access to the [cmd2.Statement][] object that was created when
238-
parsing the command line. This can be useful if the command function needs to know the command line.
233+
A ``cmd2_statement`` attribute is included in the ``Namespace`` to provide access to the
234+
[cmd2.Statement][] object that was created when parsing the command line. This can be useful
235+
if the command function needs to know the command line.
239236
240237
Example:
241238
```py
@@ -320,20 +317,15 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
320317
except SystemExit as exc:
321318
raise Cmd2ArgparseError from exc
322319

323-
# Add cmd2-specific metadata to the Namespace
320+
# Add cmd2-specific attributes to the Namespace
324321
parsed_namespace = parsing_results[0]
325322

326-
# Add wrapped statement to Namespace as cmd2_statement
327-
parsed_namespace.cmd2_statement = Cmd2AttributeWrapper(statement)
328-
329-
# Add wrapped subcmd handler (which can be None) to Namespace as cmd2_handler
330-
handler = getattr(parsed_namespace, constants.NS_ATTR_SUBCMD_HANDLER, None)
331-
parsed_namespace.cmd2_handler = Cmd2AttributeWrapper(handler)
323+
# Include the Statement object created from the command line
324+
setattr(parsed_namespace, constants.NS_ATTR_STATEMENT, statement)
332325

333-
# Remove the subcmd handler attribute from the Namespace
334-
# since cmd2_handler is how a developer accesses it.
335-
if hasattr(parsed_namespace, constants.NS_ATTR_SUBCMD_HANDLER):
336-
delattr(parsed_namespace, constants.NS_ATTR_SUBCMD_HANDLER)
326+
# Ensure NS_ATTR_SUBCMD_HANDLER is always present.
327+
if not hasattr(parsed_namespace, constants.NS_ATTR_SUBCMD_HANDLER):
328+
setattr(parsed_namespace, constants.NS_ATTR_SUBCMD_HANDLER, None)
337329

338330
func_arg_list = _arg_swap(args, statement_arg, *parsing_results)
339331
return func(*func_arg_list, **kwargs)

docs/features/argument_processing.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ following for you:
1111
1. Passes the resulting
1212
[argparse.Namespace](https://docs.python.org/3/library/argparse.html#argparse.Namespace) object
1313
to your command function. The `Namespace` includes the [Statement][cmd2.Statement] object that
14-
was created when parsing the command line. It can be retrieved by calling `cmd2_statement.get()`
15-
on the `Namespace`.
14+
was created when parsing the command line. It is accessible via the `cmd2_statement` attribute on
15+
the `Namespace`.
1616
1. Adds the usage message from the argument parser to your command's help.
1717
1. Checks if the `-h/--help` option is present, and if so, displays the help message for the command
1818

@@ -397,11 +397,7 @@ example demonstrates both above cases in a concrete fashion.
397397
## Reserved Argument Names
398398

399399
`cmd2`'s `@with_argparser` decorator adds the following attributes to argparse Namespaces. To avoid
400-
naming collisions, do not use any of the names for your argparse arguments.
401-
402-
- `cmd2_statement` - `cmd2.Cmd2AttributeWrapper` object containing the `cmd2.Statement` object that
403-
was created when parsing the command line.
404-
- `cmd2_handler` - `cmd2.Cmd2AttributeWrapper` object containing a subcommand handler function or
405-
`None` if one was not set.
406-
- `__subcmd_handler__` - used by cmd2 to identify the handler for a subcommand created with the
407-
`@cmd2.as_subcommand_to` decorator.
400+
naming collisions, do not use any of these names for your argparse arguments.
401+
402+
- `cmd2_statement` - [cmd2.Statement][] object that was created when parsing the command line.
403+
- `cmd2_subcmd_handler` - subcommand handler function or `None` if one was not set.

docs/features/modular_commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class ExampleApp(cmd2.Cmd):
337337

338338
@with_argparser(cut_parser)
339339
def do_cut(self, ns: argparse.Namespace):
340-
handler = ns.cmd2_handler.get()
340+
handler = ns.cmd2_subcmd_handler
341341
if handler is not None:
342342
# Call whatever subcommand function was selected
343343
handler(ns)

examples/argparse_example.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ def subtract(self, args: argparse.Namespace) -> None:
139139
@cmd2.with_category(ARGPARSE_SUBCOMMANDS)
140140
def do_calculate(self, args: argparse.Namespace) -> None:
141141
"""Calculate a simple mathematical operation on two integers."""
142-
handler = args.cmd2_handler.get()
143-
handler(args)
142+
args.cmd2_subcmd_handler(args)
144143

145144

146145
if __name__ == '__main__':

0 commit comments

Comments
 (0)